[
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": \"eslint:recommended\",\n  \"rules\": {\n    \"array-bracket-spacing\": [\"error\", \"always\"],\n    \"block-spacing\": [\"error\", \"always\"],\n    \"camelcase\": [\"error\"],\n    \"comma-spacing\": [\"error\"],\n    \"curly\": [\"error\", \"multi-line\"],\n    \"dot-notation\": [\"error\"],\n    \"eol-last\": [\"error\"],\n    \"getter-return\": [\"error\"],\n    \"id-length\": [\"error\", { \"properties\": \"never\", \"exceptions\": [\"_\", \"i\", \"j\", \"n\"] }],\n    \"keyword-spacing\": [\"error\"],\n    \"no-extra-parens\": [\"error\"],\n    \"no-multi-spaces\": [\"error\", { \"exceptions\": { \"VariableDeclarator\": true } }],\n    \"no-multiple-empty-lines\": [\"error\", { \"max\": 2 }],\n    \"no-restricted-globals\": [\"error\", \"event\"],\n    \"no-trailing-spaces\": [\"error\"],\n    \"no-unused-vars\": [\"error\", { \"vars\": \"all\", \"args\": \"none\" }],\n    \"no-var\": [\"error\"],\n    \"object-curly-spacing\": [\"error\", \"always\"],\n    \"prefer-const\": [\"error\"],\n    \"quotes\": [\"error\", \"double\"],\n    \"semi\": [\"error\", \"never\"],\n    \"sort-imports\": [\"error\", { \"ignoreDeclarationSort\": true }]\n  },\n  \"ignorePatterns\": [\"dist/**/*.js\", \"**/vendor/**/*.js\", \"action_text-trix/**/*.js\"],\n  \"globals\": {\n    \"after\": true,\n    \"getComposition\": true,\n    \"getDocument\": true,\n    \"getEditor\": true,\n    \"getEditorController\": true,\n    \"getEditorElement\": true,\n    \"getSelectionManager\": true,\n    \"getToolbarElement\": true,\n    \"QUnit\": true,\n    \"rangy\": true,\n    \"Trix\": true\n  },\n  \"env\": {\n    \"browser\": true,\n    \"node\": true,\n    \"es6\": true\n  }\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "Describe the bug or issue here…\n\n##### Steps to Reproduce\n\n1. \n2. \n3. \n\n##### Details\n\n* Trix version: \n* Browser name and version:\n* Operating system: \n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 90\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - bug\n  - enhancement\n  - pinned\n  - security\n  - WIP\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale after 90 days of inactivity.\n  It will be closed if no further activity occurs.\npulls:\n  markComment: >\n    This pull request has been automatically marked as stale after 90 days of inactivity.\n    It will be closed if no further activity occurs.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\nconcurrency:\n  group: \"${{github.workflow}}-${{github.ref}}\"\n  cancel-in-progress: true\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ main ]\n  pull_request:\n    types: [opened, synchronize]\n    branches: [ '*' ]\n\nenv:\n  SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}\n  SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}\n  SAUCE_REGION: us\n  SAUCE_TUNNEL_IDENTIFIER: trix-${{ github.run_id }}\n\njobs:\n  build:\n    name: Browser tests\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n          cache: \"yarn\"\n      - name: Install Dependencies\n        run: yarn install --frozen-lockfile\n      - name: Start Sauce Connect\n        if: ${{ env.SAUCE_ACCESS_KEY != '' }}\n        uses: saucelabs/sauce-connect-action@v3\n        with:\n          username: ${{ env.SAUCE_USERNAME }}\n          accessKey: ${{ env.SAUCE_ACCESS_KEY }}\n          tunnelName: ${{ env.SAUCE_TUNNEL_IDENTIFIER }}\n          region: ${{ env.SAUCE_REGION }}\n          proxyLocalhost: allow\n      - name: Install Playwright Browsers\n        if: ${{ env.SAUCE_ACCESS_KEY == '' }}\n        run: npx playwright install --with-deps chromium\n      - run: bin/ci\n      - name: Fail when generated npm changes are not checked-in\n        run: |\n          git update-index --refresh && git diff-index --quiet HEAD --\n\n  rails-tests:\n    name: Downstream Rails integration tests\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n          cache: \"yarn\"\n      - uses: ruby/setup-ruby-pkgs@v1\n        with:\n          ruby-version: \"3.4\"\n          apt-get: libvips-tools\n      - name: Install Dependencies\n        run: yarn install --frozen-lockfile\n      - name: Packaging\n        run: yarn build\n      - name: Clone Rails\n        run: git clone --depth=1 https://github.com/rails/rails\n      - name: Configure Rails\n        run: |\n          cd rails\n          yarn install --frozen-lockfile\n          bundle add action_text-trix --path \"..\"\n          bundle show --paths action_text-trix\n      - name: Action Text tests\n        env:\n          RACK_ENV: test # see https://github.com/rails/rails/issues/56563\n        run: |\n          cd rails/actiontext\n          bundle exec rake test test:system\n  action_text-trix:\n    name: Action Text tests\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - ruby: \"3.2\"\n            rails_branch: 7-2-stable\n          - ruby: \"3.3\"\n            rails_branch: 7-2-stable\n          - ruby: \"3.4\"\n            rails_branch: 7-2-stable\n\n          - ruby: \"3.2\"\n            rails_branch: 8-0-stable\n          - ruby: \"3.3\"\n            rails_branch: 8-0-stable\n          - ruby: \"3.4\"\n            rails_branch: 8-0-stable\n\n          - ruby: \"3.2\"\n            rails_branch: 8-1-stable\n          - ruby: \"3.3\"\n            rails_branch: 8-1-stable\n          - ruby: \"3.4\"\n            rails_branch: 8-1-stable\n          - ruby: \"4.0\"\n            rails_branch: 8-1-stable\n\n          - ruby: \"3.3\"\n            rails_branch: main\n            experimental: true\n          - ruby: \"3.4\"\n            rails_branch: main\n            experimental: true\n          - ruby: \"4.0\"\n            rails_branch: main\n            experimental: true\n\n          - ruby: head\n            rails_branch: main\n            experimental: true\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n          cache: \"yarn\"\n      - uses: ruby/setup-ruby@v1\n        env:\n          RAILS_BRANCH: ${{ matrix.rails_branch }}\n        with:\n          working-directory: \"./action_text-trix\"\n          ruby-version: ${{ matrix.ruby }}\n          bundler-cache: true\n      - name: Run tests\n        env:\n          RAILS_BRANCH: ${{ matrix.rails_branch }}\n        continue-on-error: ${{ matrix.experimental || false }}\n        working-directory: \"./action_text-trix\"\n        run: bin/rails test:all\n"
  },
  {
    "path": ".gitignore",
    "content": "yarn-error.log\npackage-lock.json\n/dist\n/node_modules\n/tmp\n"
  },
  {
    "path": ".node-version",
    "content": "18.18.0\n"
  },
  {
    "path": ".npmignore",
    "content": ".DS_Store\n/node_modules\n/.github\n/bin\n/assets\nyarn-error.log\nyarn.lock\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the [project maintainers](#project-maintainers). All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Project Maintainers\n\n* Javan Makhmali <<javan@basecamp.com>>\n* Sam Stephenson <<sam@basecamp.com>>\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 37signals, LLC\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Trix\n### A Rich Text Editor for Everyday Writing\n\n**Compose beautifully formatted text in your web application.** Trix is a WYSIWYG editor for writing messages, comments, articles, and lists—the simple documents most web apps are made of. It features a sophisticated document model, support for embedded attachments, and outputs terse and consistent HTML.\n\nTrix is an open-source project from [37signals](https://37signals.com), the creators of [Ruby on Rails](http://rubyonrails.org/). Millions of people trust their text to us, and we built Trix to give them the best possible editing experience. See Trix in action in [Basecamp](https://basecamp.com).\n\n### Different By Design\n\nWhen Trix was designed in 2014, most WYSIWYG editors were wrappers around HTML’s `contenteditable` and `execCommand` APIs, designed by Microsoft to support live editing of web pages in Internet Explorer 5.5, and [eventually reverse-engineered](https://blog.whatwg.org/the-road-to-html-5-contenteditable#history) and copied by other browsers.\n\nBecause these APIs were not fully specified or documented, and because WYSIWYG HTML editors are enormous in scope, each browser’s implementation has its own set of bugs and quirks, and JavaScript developers are left to resolve the inconsistencies.\n\nTrix sidestepped these inconsistencies by treating `contenteditable` as an I/O device: when input makes its way to the editor, Trix converts that input into an editing operation on its internal document model, then re-renders that document back into the editor. This gives Trix complete control over what happens after every keystroke, and avoids the need to use `execCommand` at all.\n\nThis is the approach that all modern, production ready, WYSIWYG editors now take.\n\n### Built on Web standards\n\n<details><summary>Trix supports all evergreen, self-updating desktop and mobile browsers.</summary><img src=\"https://app.saucelabs.com/browser-matrix/basecamp_trix.svg\"></details>\n\nTrix is built with established web standards, notably [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements), [Element Internals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), [Mutation Observer](https://dom.spec.whatwg.org/#mutation-observers), and [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).\n\n# Getting Started\n\nTrix comes bundled in ESM and UMD formats and works with any asset packaging system.\n\nThe easiest way to start with Trix is including it from an npm CDN in the `<head>` of your page:\n\n```html\n<head>\n  …\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://unpkg.com/trix@2.0.8/dist/trix.css\">\n  <script type=\"text/javascript\" src=\"https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js\"></script>\n</head>\n```\n\n`trix.css` includes default styles for the Trix toolbar, editor, and attachments. Skip this file if you prefer to define these styles yourself.\n\nAlternatively, you can install the npm package and import it in your application:\n\n```js\nimport Trix from \"trix\"\n\ndocument.addEventListener(\"trix-before-initialize\", () => {\n  // Change Trix.config if you need\n})\n```\n\n## Creating an Editor\n\nPlace an empty `<trix-editor></trix-editor>` tag on the page. Trix will automatically insert a separate `<trix-toolbar>` before the editor.\n\nLike an HTML `<textarea>`, `<trix-editor>` accepts `autofocus` and `placeholder` attributes. Unlike a `<textarea>`, `<trix-editor>` automatically expands vertically to fit its contents.\n\n## Creating a Toolbar\n\nTrix automatically will create a toolbar for you and attach it right before the `<trix-editor>` element. If you'd like to place the toolbar in a different place you can use the `toolbar` attribute:\n\n```html\n<main>\n  <trix-toolbar id=\"my_toolbar\"></trix-toolbar>\n  <div class=\"more-stuff-inbetween\"></div>\n  <trix-editor toolbar=\"my_toolbar\" input=\"my_input\"></trix-editor>\n</main>\n```\n\nTo change the toolbar without modifying Trix, you can overwrite the `Trix.config.toolbar.getDefaultHTML()` function. The default toolbar HTML is in `config/toolbar.js`. Trix uses data attributes to determine how to respond to a toolbar button click.\n\n**Toggle Attribute**\n\nWith `data-trix-attribute=\"<attribute name>\"`, you can add an attribute to the current selection.\nFor example, to apply bold styling to the selected text the button is:\n\n``` html\n<button type=\"button\" class=\"bold\" data-trix-attribute=\"bold\" data-trix-key=\"b\"></button>\n```\n\nTrix will determine that a range of text is selected and will apply the formatting defined in `Trix.config.textAttributes` (found in `config/text_attributes.js`).\n\n`data-trix-key=\"b\"` tells Trix that this attribute should be applied when you use <kbd>meta</kbd>+<kbd>b</kdb>.\n\nIf the attribute is defined in `Trix.config.blockAttributes`, Trix will apply the attribute to the current block of text.\n\n``` html\n<button type=\"button\" class=\"quote\" data-trix-attribute=\"quote\"></button>\n```\n\nClicking the quote button toggles whether the block should be rendered with `<blockquote>`.\n\n## Integrating with Element Internals\n\nTrix will integrate `<trix-editor>` elements with forms depending on the browser's support for [Element Internals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals). If there is a need to disable support for `ElementInternals`, set `Trix.elements.TrixEditorElement.formAssociated = false`:\n\n```js\nimport Trix from \"trix\"\n\nTrix.elements.TrixEditorElement.formAssociated = false\n```\n\nWhen Trix is configured to be compatible with `ElementInternals`, it is also\ncapable of functioning without an `<input type=\"hidden\">` element. To configure\na `<trix-editor>` element to skip creating its `<input type=\"hidden\">`, set the\nelement's `willCreateInput = false`:\n\n```js\naddEventListener(\"before-trix-initialize\", (event) => {\n  const trixEditor = event.target\n\n  trixEditor.willCreateInput = false\n})\n```\n\n> [!NOTE]\n> Trix will *always* use an associated `<input type=\"hidden\">` element when the\n> `[input]` attribute is set. To migrate to `<input>`-free support, set\n> `willCreateInput = false`, then render the `<trix-editor>` without the\n> `[input]` attribute.\n\n> [!WARNING]\n> In the absence of an `<input type=\"hidden\">` element, the `<trix-editor>`\n> element's value will not be included in `<form>` element submissions unless it\n> is rendered with a `[name]` attribute. Set the `[name]` attribute to the same\n> value that the `<input type=\"hidden\">` element would have.\n\n## Invoking Internal Trix Actions\n\nInternal actions are defined in `controllers/editor_controller.js` and consist of:\n\n* undo\n* redo\n* link\n* increaseBlockLevel\n* decreaseBlockLevel\n\n``` html\n<button type=\"button\" class=\"block-level decrease\" data-trix-action=\"decreaseBlockLevel\"></button>\n```\n\n## Invoking External Custom Actions\n\nIf you want to add a button to the toolbar and have it invoke an external action, you can prefix your action name with `x-`. For example, if I want to print a log statement any time my new button is clicked, I would set by button's data attribute to be `data-trix-action=\"x-log\"`\n\n``` html\n<button id=\"log-button\" type=\"button\" data-trix-action=\"x-log\"></button>\n```\n\nTo respond to the action, listen for `trix-action-invoke`. The event's `target` property returns a reference to the `<trix-editor>` element, its `invokingElement` property returns a reference to the `<button>` element, and its `actionName` property returns the value of the `[data-trix-action]` attribute. Use the value of the `actionName` property to detect which external action was invoked.\n\n```javascript\ndocument.addEventListener(\"trix-action-invoke\", function(event) {\n  const { target, invokingElement, actionName } = event\n\n  if (actionName === \"x-log\") {\n    console.log(`Custom ${actionName} invoked from ${invokingElement.id} button on ${target.id} trix-editor`)\n  }\n})\n```\n\n## Integrating With Forms\n\nTo submit the contents of a `<trix-editor>` with a form, first define a hidden input field in the form and assign it an `id`. Then reference that `id` in the editor’s `input` attribute.\n\n```html\n<form …>\n  <input id=\"x\" type=\"hidden\" name=\"content\">\n  <trix-editor input=\"x\"></trix-editor>\n</form>\n```\n\nTrix will automatically update the value of the hidden input field with each change to the editor.\n\n## Populating With Stored Content\n\nTo populate a `<trix-editor>` with stored content, include that content in the associated input element’s `value` attribute.\n\n```html\n<form …>\n  <input id=\"x\" value=\"Editor content goes here\" type=\"hidden\" name=\"content\">\n  <trix-editor input=\"x\"></trix-editor>\n</form>\n```\n\nUse an associated input element to initially populate an editor. When an associated input element is absent, Trix will safely sanitize then load any HTML content inside a `<trix-editor>…</trix-editor>` tag.\n\n```html\n<form …>\n  <trix-editor>Editor content goes here</trix-editor>\n</form>\n```\n\n> [!WARNING]\n> When a `<trix-editor>` element initially connects with both HTML content *and*\n> an associated input element, Trix will *always* disregard the HTML content and\n> load its initial content from the associated input element.\n\n## Validating the Editor\n\nOut of the box, `<trix-editor>` elements support browsers' built-in [Constraint\nvalidation][]. When rendered with the [required][] attribute, editors will be\ninvalid when they're completely empty. For example, consider the following HTML:\n\n```html\n<input id=\"x\" value=\"\" type=\"hidden\" name=\"content\">\n<trix-editor input=\"x\" required></trix-editor>\n```\n\nSince the `<trix-editor>` element is `[required]`, it is invalid when its value\nis empty:\n\n```js\nconst editor = document.querySelector(\"trix-editor\")\n\neditor.validity.valid        // => false\neditor.validity.valueMissing // => true\neditor.matches(\":valid\")     // => false\neditor.matches(\":invalid\")   // => true\n\neditor.value = \"A value that isn't empty\"\n\neditor.validity.valid         // => true\neditor.validity.valueMissing  // => false\neditor.matches(\":valid\")      // => true\neditor.matches(\":invalid\")    // => false\n```\n\nIn addition to the built-in `[required]` attribute, `<trix-editor>`\nelements support custom validation through their [setCustomValidity][] method.\nFor example, consider the following HTML:\n\n```js\n<input id=\"x\" value=\"\" type=\"hidden\" name=\"content\">\n<trix-editor input=\"x\"></trix-editor>\n```\n\nCustom validation can occur at any time. For example, validation can occur after\na `trix-change` event fired after the editor's contents change:\n\n```js\naddEventListener(\"trix-change\", (event) => {\n  const editorElement = event.target\n  const trixDocument = editorElement.editor.getDocument()\n  const isValid = (trixDocument) => {\n    // determine the validity based on your custom criteria\n    return true\n  }\n\n  if (isValid(trixDocument)) {\n    editorElement.setCustomValidity(\"\")\n  } else {\n    editorElement.setCustomValidity(\"The document is not valid.\")\n  }\n}\n```\n\n[Constraint validation]: https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation\n[required]: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required\n[setCustomValidity]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/setCustomValidity\n\n## Disabling the Editor\n\nTo disable the `<trix-editor>`, render it with the `[disabled]` attribute:\n\n```html\n<trix-editor disabled></trix-editor>\n```\n\nDisabled editors are not editable, cannot receive focus, and their values will\nbe ignored when their related `<form>` element is submitted.\n\nTo change whether or not an editor is disabled, either toggle the `[disabled]`\nattribute or assign a boolean to the `.disabled` property:\n\n```html\n<trix-editor id=\"editor\" disabled></trix-editor>\n\n<script>\n  const editor = document.getElementById(\"editor\")\n\n  editor.toggleAttribute(\"disabled\", false)\n  editor.disabled = true\n</script>\n```\n\nWhen disabled, the editor will match the [:disabled CSS\npseudo-class][:disabled].\n\n[:disabled]: https://developer.mozilla.org/en-US/docs/Web/CSS/:disabled\n\n## Providing an Accessible Name\n\nLike other form controls, `<trix-editor>` elements should have an accessible name. The `<trix-editor>` element integrates with `<label>` elements. It supports two styles of integrating with `<label>` elements:\n\n1. render the `<trix-editor>` element with an `[id]` attribute that the `<label>` element references through its `[for]` attribute:\n\n```html\n<label for=\"editor\">Editor</label>\n<trix-editor id=\"editor\"></trix-editor>\n```\n\n2. render the `<trix-editor>` element as a child of the `<label>` element:\n\n```html\n<trix-toolbar id=\"editor-toolbar\"></trix-toolbar>\n<label>\n  Editor\n\n  <trix-editor toolbar=\"editor-toolbar\"></trix-editor>\n</label>\n```\n\n> [!WARNING]\n> When rendering the `<trix-editor>` element as a child of the `<label>` element, [explicitly render](#creating-an-editor) the corresponding `<trix-toolbar>` element outside of the `<label>` element.\n\nIn addition to integrating with `<label>` elements, `<trix-editor>` elements support `[aria-label]` and `[aria-labelledby]` attributes.\n\n## Styling Formatted Content\n\nTo ensure what you see when you edit is what you see when you save, use a CSS class name to scope styles for Trix formatted content. Apply this class name to your `<trix-editor>` element, and to a containing element when you render stored Trix content for display in your application.\n\n```html\n<trix-editor class=\"trix-content\"></trix-editor>\n```\n\n```html\n<div class=\"trix-content\">Stored content here</div>\n```\n\nThe default `trix.css` file includes styles for basic formatted content—including bulleted and numbered lists, code blocks, and block quotes—under the class name `trix-content`. We encourage you to use these styles as a starting point by copying them into your application’s CSS with a different class name.\n\n## Storing Attached Files\n\nTrix automatically accepts files dragged or pasted into an editor and inserts them as attachments in the document. Each attachment is considered _pending_ until you store it remotely and provide Trix with a permanent URL.\n\nTo store attachments, listen for the `trix-attachment-add` event. Upload the attached files with XMLHttpRequest yourself and set the attachment’s URL attribute upon completion. See the [attachment example](https://trix-editor.org/js/attachments.js) for detailed information.\n\nIf you don’t want to accept dropped or pasted files, call `preventDefault()` on the `trix-file-accept` event, which Trix dispatches just before the `trix-attachment-add` event.\n\n## Previewing Attached Files\n\nTrix automatically previews attached image files. To determine whether or not to preview an attached file, Trix compares the file's content type against the [Trix.Attachment.previewablePattern](./src/trix/models/attachment.js#L7). By default, Trix will preview the following content types:\n\n* `image/gif`\n* `image/png`\n* `image/webp`\n* `image/jpg`\n* `image/jpeg`\n\nTo customize an attachment's preview, listen for the `trix-attachment-add` event. When handling the event, set the attachment's `previewable` attribute, then change its preview URL by calling `setPreviewURL`:\n\n```js\naddEventListener(\"trix-attachment-add\", (event) => {\n  if (event.attachment.file instanceof File) {\n    event.attachment.setAttribute(\"previewable\", true)\n    event.attachment.setPreviewURL(\"...\")\n  }\n})\n```\n\n# Editing Text Programmatically\n\nYou can manipulate a Trix editor programmatically through the `Trix.Editor` interface, available on each `<trix-editor>` element through its `editor` property.\n\n```js\nvar element = document.querySelector(\"trix-editor\")\nelement.editor  // is a Trix.Editor instance\n```\n\n## Understanding the Document Model\n\nThe formatted content of a Trix editor is known as a _document_, and is represented as an instance of the `Trix.Document` class. To get the editor’s current document, use the `editor.getDocument` method.\n\n```js\nelement.editor.getDocument()  // is a Trix.Document instance\n```\n\nYou can convert a document to an unformatted JavaScript string with the `document.toString` method.\n\n```js\nvar document = element.editor.getDocument()\ndocument.toString()  // is a JavaScript string\n```\n\n### Immutability and Equality\n\nDocuments are immutable values. Each change you make in an editor replaces the previous document with a new document. Capturing a snapshot of the editor’s content is as simple as keeping a reference to its document, since that document will never change over time. (This is how Trix implements undo.)\n\nTo compare two documents for equality, use the `document.isEqualTo` method.\n\n```js\nvar document = element.editor.getDocument()\ndocument.isEqualTo(element.editor.getDocument())  // true\n```\n\n## Getting and Setting the Selection\n\nTrix documents are structured as sequences of individually addressable characters. The index of one character in a document is called a _position_, and a start and end position together make up a _range_.\n\nTo get the editor’s current selection, use the `editor.getSelectedRange` method, which returns a two-element array containing the start and end positions.\n\n```js\nelement.editor.getSelectedRange()  // [0, 0]\n```\n\nYou can set the editor’s current selection by passing a range array to the `editor.setSelectedRange` method.\n\n```js\n// Select the first character in the document\nelement.editor.setSelectedRange([0, 1])\n```\n\n### Collapsed Selections\n\nWhen the start and end positions of a range are equal, the range is said to be _collapsed_. In the editor, a collapsed selection appears as a blinking cursor rather than a highlighted span of text.\n\nFor convenience, the following calls to `setSelectedRange` are equivalent when working with collapsed selections:\n\n```js\nelement.editor.setSelectedRange(1)\nelement.editor.setSelectedRange([1])\nelement.editor.setSelectedRange([1, 1])\n```\n\n### Directional Movement\n\nTo programmatically move the cursor or selection through the document, call the `editor.moveCursorInDirection` or `editor.expandSelectionInDirection` methods with a _direction_ argument. The direction can be either `\"forward\"` or `\"backward\"`.\n\n```js\n// Move the cursor backward one character\nelement.editor.moveCursorInDirection(\"backward\")\n\n// Expand the end of the selection forward by one character\nelement.editor.expandSelectionInDirection(\"forward\")\n```\n\n### Converting Positions to Pixel Offsets\n\nSometimes you need to know the _x_ and _y_ coordinates of a character at a given position in the editor. For example, you might want to absolutely position a pop-up menu element below the editor’s cursor.\n\nCall the `editor.getClientRectAtPosition` method with a position argument to get a [`DOMRect`](https://drafts.fxtf.org/geometry/#DOMRect) instance representing the left and top offsets, width, and height of the character at the given position.\n\n```js\nvar rect = element.editor.getClientRectAtPosition(0)\n[rect.left, rect.top]  // [17, 49]\n```\n\n## Inserting and Deleting Text\n\nThe editor interface provides methods for inserting, replacing, and deleting text at the current selection.\n\nTo insert or replace text, begin by setting the selected range, then call one of the insertion methods below. Trix will first remove any selected text, then insert the new text at the start position of the selected range.\n\n### Inserting Plain Text\n\nTo insert unformatted text into the document, call the `editor.insertString` method.\n\n```js\n// Insert “Hello” at the beginning of the document\nelement.editor.setSelectedRange([0, 0])\nelement.editor.insertString(\"Hello\")\n```\n\n### Inserting HTML\n\nTo insert HTML into the document, call the `editor.insertHTML` method. Trix will first convert the HTML into its internal document model. During this conversion, any formatting that cannot be represented in a Trix document will be lost.\n\n```js\n// Insert a bold “Hello” at the beginning of the document\nelement.editor.setSelectedRange([0, 0])\nelement.editor.insertHTML(\"<strong>Hello</strong>\")\n```\n\n### Inserting a File\n\nTo insert a DOM [`File`](http://www.w3.org/TR/FileAPI/#file) object into the document, call the `editor.insertFile` method. Trix will insert a pending attachment for the file as if you had dragged and dropped it onto the editor.\n\n```js\n// Insert the selected file from the first file input element\nvar file = document.querySelector(\"input[type=file]\").file\nelement.editor.insertFile(file)\n```\n\n### Inserting a Content Attachment\n\nContent attachments are self-contained units of HTML that behave like files in the editor. They can be moved or removed, but not edited directly, and are represented by a single character position in the document model.\n\nTo insert HTML as an attachment, create a `Trix.Attachment` with a `content` attribute and call the `editor.insertAttachment` method. The HTML inside a content attachment is not subject to Trix’s document conversion rules and will be rendered as-is.\n\n```js\nvar attachment = new Trix.Attachment({ content: '<span class=\"mention\">@trix</span>' })\nelement.editor.insertAttachment(attachment)\n```\n\n### Inserting a Line Break\n\nTo insert a line break, call the `editor.insertLineBreak` method, which is functionally equivalent to pressing the return key.\n\n```js\n// Insert “Hello\\n”\nelement.editor.insertString(\"Hello\")\nelement.editor.insertLineBreak()\n```\n\n### Deleting Text\n\nIf the current selection is collapsed, you can simulate deleting text before or after the cursor with the `editor.deleteInDirection` method.\n\n```js\n// “Backspace” the first character in the document\nelement.editor.setSelectedRange([1, 1])\nelement.editor.deleteInDirection(\"backward\")\n\n// Delete the second character in the document\nelement.editor.setSelectedRange([1, 1])\nelement.editor.deleteInDirection(\"forward\")\n```\n\nTo delete a range of text, first set the selected range, then call `editor.deleteInDirection` with either direction as the argument.\n\n```js\n// Delete the first five characters\nelement.editor.setSelectedRange([0, 4])\nelement.editor.deleteInDirection(\"forward\")\n```\n\n## Working With Attributes and Nesting\n\nTrix represents formatting as sets of _attributes_ applied across ranges of a document.\n\nBy default, Trix supports the inline attributes `bold`, `italic`, `href`, and `strike`, and the block-level attributes `heading1`, `quote`, `code`, `bullet`, and `number`.\n\n### Applying Formatting\n\nTo apply formatting to the current selection, use the `editor.activateAttribute` method.\n\n```js\nelement.editor.insertString(\"Hello\")\nelement.editor.setSelectedRange([0, 5])\nelement.editor.activateAttribute(\"bold\")\n```\n\nTo set the `href` attribute, pass a URL as the second argument to `editor.activateAttribute`.\n\n```js\nelement.editor.insertString(\"Trix\")\nelement.editor.setSelectedRange([0, 4])\nelement.editor.activateAttribute(\"href\", \"https://trix-editor.org/\")\n```\n\n### Removing Formatting\n\nUse the `editor.deactivateAttribute` method to remove formatting from a selection.\n\n```js\nelement.editor.setSelectedRange([2, 4])\nelement.editor.deactivateAttribute(\"bold\")\n```\n\n### Formatting With a Collapsed Selection\n\nIf you activate or deactivate attributes when the selection is collapsed, your formatting changes will apply to the text inserted by any subsequent calls to `editor.insertString`.\n\n```js\nelement.editor.activateAttribute(\"italic\")\nelement.editor.insertString(\"This is italic\")\n```\n\n### Adjusting the Nesting Level\n\nTo adjust the nesting level of quotes, bulleted lists, or numbered lists, call the `editor.increaseNestingLevel` and `editor.decreaseNestingLevel` methods.\n\n```js\nelement.editor.activateAttribute(\"quote\")\nelement.editor.increaseNestingLevel()\nelement.editor.decreaseNestingLevel()\n```\n\n## Using Undo and Redo\n\nTrix editors support unlimited undo and redo. Successive typing and formatting changes are consolidated together at five-second intervals; all other input changes are recorded individually in undo history.\n\nCall the `editor.undo` and `editor.redo` methods to perform an undo or redo operation.\n\n```js\nelement.editor.undo()\nelement.editor.redo()\n```\n\nChanges you make through the editor interface will not automatically record undo entries. You can save your own undo entries by calling the `editor.recordUndoEntry` method with a description argument.\n\n```js\nelement.editor.recordUndoEntry(\"Insert Text\")\nelement.editor.insertString(\"Hello\")\n```\n\n## Loading and Saving Editor State\n\nSerialize an editor’s state with `JSON.stringify` and restore saved state with the `editor.loadJSON` method. The serialized state includes the document and current selection, but does not include undo history.\n\n```js\n// Save editor state to local storage\nlocalStorage[\"editorState\"] = JSON.stringify(element.editor)\n\n// Restore editor state from local storage\nelement.editor.loadJSON(JSON.parse(localStorage[\"editorState\"]))\n```\n\n## HTML Sanitization\n\nTrix uses [DOMPurify](https://github.com/cure53/DOMPurify/) to sanitize the editor content. You can set the DOMPurify config via `Trix.config.dompurify`.\n\nFor example if you want to keep a custom tag, you can access do that with:\n\n```js\nTrix.config.dompurify.ADD_TAGS = [ \"my-custom-tag\" ]\n```\n\n## HTML Rendering\n\nTrix renders changes to editor content by replacing existing nodes with new nodes.\n\nTo customize how Trix renders changes, set the `<trix-editor>` element's\n`render` property to a function that accepts a `<trix-editor>` instance and a\n[DocumentFragment][]:\n\n```js\ndocument.addEventListener(\"trix-before-render\", (event) => {\n  const defaultRender = event.render\n\n  event.render = function(editorElement, documentFragment) {\n    // modify the documentFragment…\n    customize(documentFragment)\n\n    // render it with the default rendering function\n    defaultRender(editorElement, documentFragment)\n  }\n})\n```\n\n> [!CAUTION]\n> By the time that `render(editorElement, documentFragment)` is\n> invoked, Trix will have finalized modifications to the HTML content (like HTML\n> sanitization, for example). If you make further modifications to the content,\n> be sure that they are safe.\n\n[DocumentFragment]: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment\n\n## Observing Editor Changes\n\nThe `<trix-editor>` element emits several events which you can use to observe and respond to changes in editor state.\n\n* `trix-before-initialize` fires when the `<trix-editor>` element is attached to the DOM just before Trix installs its `editor` object. If you need to use a custom Trix configuration you can change `Trix.config` here.\n\n* `trix-initialize` fires when the `<trix-editor>` element is attached to the DOM and its `editor` object is ready for use.\n\n* `trix-change` fires whenever the editor’s contents have changed.\n\n* `trix-before-render` fires before the editor’s new contents are rendered. You can override the function used to render the content through the `render` property on the event. The `render` function expects two positional arguments: the `<trix-editor>` element that will render and a [DocumentFragment](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) instance that contains the new content. Read [HTML Rendering](#html-rendering) to learn more.\n\n* `trix-before-paste` fires just before text is pasted into the editor. You can use this to modify the content being pasted or prevent the paste event from happening at all. The `paste` property on the event contains the pasted `string` or `html`, and the `range` of the inserted text.\n\n* `trix-paste` fires whenever text is pasted into the editor. The `paste` property on the event contains the pasted `string` or `html`, and the `range` of the inserted text.\n\n* `trix-selection-change` fires any time the selected range changes in the editor.\n\n* `trix-focus` and `trix-blur` fire when the editor gains or loses focus, respectively.\n\n* `trix-file-accept` fires when a file is dropped or inserted into the editor. You can access the DOM `File` object through the `file` property on the event. Call `preventDefault` on the event to prevent attaching the file to the document.\n\n* `trix-attachment-add` fires after an attachment is added to the document. You can access the Trix attachment object through the `attachment` property on the event. If the `attachment` object has a `file` property, you should store this file remotely and set the attachment’s URL attribute. See the [attachment example](http://trix-editor.org/js/attachments.js) for detailed information.\n\n* `trix-attachment-edit` fires after an attachment is edited in the document. You can access the Trix attachment object through the `attachment` property on the event.\n\n* `trix-attachment-remove` fires when an attachment is removed from the document. You can access the Trix attachment object through the `attachment` property on the event. You may wish to use this event to clean up remotely stored files.\n\n* `trix-action-invoke` fires when a Trix action is invoked. You can access the `<trix-editor>` element through the event's `target` property, the element responsible for invoking the action through the `invokingElement` property, and the action's name through the `actionName` property. The `trix-action-invoke` event will only fire for [custom](#invoking-external-custom-actions) actions and not for [built-in](#invoking-internal-trix-actions).\n\n# Contributing to Trix\n\nTrix is open-source software, freely distributable under the terms of an [MIT-style license](LICENSE). The [source code is hosted on GitHub](https://github.com/basecamp/trix).\n\nWe welcome contributions in the form of bug reports, pull requests, or thoughtful discussions in the [GitHub issue tracker](https://github.com/basecamp/trix/issues). Please see the [Code of Conduct](CODE_OF_CONDUCT.md) for our pledge to contributors.\n\nTrix was created by [Javan Makhmali](https://twitter.com/javan) and [Sam Stephenson](https://twitter.com/sstephenson), with development sponsored by [37signals](https://37signals.com).\n\n### Building From Source\n\nTrix uses [Yarn](https://yarnpkg.com/) to manage dependencies and [Rollup](https://rollupjs.org/guide/en/) to bundle its source.\n\nInstall development dependencies with:\n\n```\n$ yarn install\n```\n\nTo generate distribution files run:\n\n```\n$ yarn build\n```\n\n### Developing In-Browser\n\nYou can run a watch process to automatically generate distribution files when your source file change:\n\n```\n$ yarn watch\n```\n\nWhen the watch process is running you can run a web server to serve the compiled assets:\n\n```\n$ yarn dev\n```\n\nWith the development server running, you can visit `/index.html` to see a Trix debugger inspector, or `/test.html` to run the tests on a browser.\n\nFor easier development, you can watch for changes to the JavaScript and style files, and serve the results in a browser, with a single command:\n\n```\n$ yarn start\n```\n\n### Running Tests\n\nYou can also run the test in a headless mode with:\n\n```\n$ yarn test\n```\n\n---\n\n© 37signals, LLC.\n"
  },
  {
    "path": "action_text-trix/.gitignore",
    "content": "Gemfile.lock\npkg\ntmp/\nlog/\nstorage/\nvendor/\n"
  },
  {
    "path": "action_text-trix/Gemfile",
    "content": "source \"https://rubygems.org\"\n\n# Specify your gem's dependencies in trix.gemspec.\ngemspec\n\nbranch = ENV.fetch(\"RAILS_BRANCH\", \"main\")\ngem \"rails\", github: \"rails/rails\", branch: branch\ngem \"importmap-rails\"\ngem \"propshaft\"\ngem \"puma\"\ngem \"sqlite3\"\n\ngroup :test do\n  gem \"cuprite\", require: \"capybara/cuprite\"\n  gem \"minitest\", \"< 6.0\" # can be removed once Rails' line_filtering supports Minitest 6+\nend\n"
  },
  {
    "path": "action_text-trix/LICENSE",
    "content": "Copyright (c) 37signals, LLC\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "action_text-trix/README.md",
    "content": "## Building the Trix Ruby gem\n\n1. `cd action_text-trix`\n2. `bundle exec rake sync` (updates files which must be committed to git)\n3. `bundle exec rake build`\n4. `gem push pkg/*.gem`\n"
  },
  {
    "path": "action_text-trix/Rakefile",
    "content": "require \"bundler/gem_tasks\"\nrequire \"rake/clean\"\n\nif defined?(Rails)\n  APP_RAKEFILE = File.expand_path(\"test/dummy/Rakefile\", __dir__)\n  load \"rails/tasks/engine.rake\"\nend\n\ntask :sync do\n  require \"json\"\n\n  FileUtils.cp File.expand_path(\"../LICENSE\", __dir__), __dir__, verbose: true\n\n  package_json = JSON.load(File.read(File.join(__dir__, \"../package.json\")))\n  version = package_json[\"version\"]\n  File.write(File.join(__dir__, \"lib\", \"action_text\", \"trix\", \"version.rb\"), <<~RUBY)\n    module Trix\n      VERSION = \"#{version}\"\n    end\n  RUBY\n  puts \"Updated gem version to #{version}\"\nend\n\nCLEAN.add \"pkg\"\nCLOBBER.add \"app/assets/javascripts/trix.js\", \"app/assets/stylesheets/trix.css\"\n"
  },
  {
    "path": "action_text-trix/action_text-trix.gemspec",
    "content": "require_relative \"lib/action_text/trix/version\"\n\nGem::Specification.new do |spec|\n  spec.name     = \"action_text-trix\"\n  spec.version  = Trix::VERSION\n  spec.authors  = \"37signals, LLC\"\n  spec.summary  = \"A rich text editor for everyday writing\"\n  spec.license  = \"MIT\"\n\n  spec.homepage                    = \"https://github.com/basecamp/trix\"\n  spec.metadata[\"homepage_uri\"]    = spec.homepage\n  spec.metadata[\"source_code_uri\"] = spec.homepage\n  spec.metadata[\"changelog_uri\"]   = \"#{spec.homepage}/releases\"\n\n  spec.metadata[\"rubygems_mfa_required\"] = \"true\"\n\n  spec.files = [\n    \"LICENSE\",\n    \"app/assets/javascripts/trix.js\",\n    \"app/assets/stylesheets/trix.css\",\n    \"lib/action_text/trix.rb\",\n    \"lib/action_text/trix/engine.rb\",\n    \"lib/action_text/trix/version.rb\"\n  ]\n\n  spec.add_dependency \"railties\"\nend\n"
  },
  {
    "path": "action_text-trix/app/assets/javascripts/.gitattributes",
    "content": "trix.js linguist-vendored -whitespace\n"
  },
  {
    "path": "action_text-trix/app/assets/javascripts/trix.js",
    "content": "/*\nTrix 2.1.17\nCopyright © 2026 37signals, LLC\n */\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Trix = factory());\n})(this, (function () { 'use strict';\n\n  var name = \"trix\";\n  var version = \"2.1.17\";\n  var description = \"A rich text editor for everyday writing\";\n  var main = \"dist/trix.umd.min.js\";\n  var module = \"dist/trix.esm.min.js\";\n  var style = \"dist/trix.css\";\n  var files = [\n  \t\"dist/*.css\",\n  \t\"dist/*.js\",\n  \t\"dist/*.map\",\n  \t\"src/{inspector,trix}/*.js\"\n  ];\n  var repository = {\n  \ttype: \"git\",\n  \turl: \"git+https://github.com/basecamp/trix.git\"\n  };\n  var keywords = [\n  \t\"rich text\",\n  \t\"wysiwyg\",\n  \t\"editor\"\n  ];\n  var author = \"37signals, LLC\";\n  var license = \"MIT\";\n  var bugs = {\n  \turl: \"https://github.com/basecamp/trix/issues\"\n  };\n  var homepage = \"https://trix-editor.org/\";\n  var devDependencies = {\n  \t\"@babel/core\": \"^7.16.0\",\n  \t\"@babel/preset-env\": \"^7.16.4\",\n  \t\"@rollup/plugin-babel\": \"^5.3.0\",\n  \t\"@rollup/plugin-commonjs\": \"^22.0.2\",\n  \t\"@rollup/plugin-json\": \"^4.1.0\",\n  \t\"@rollup/plugin-node-resolve\": \"^13.3.0\",\n  \t\"@web/dev-server\": \"^0.1.34\",\n  \t\"@web/test-runner\": \"^0.20.2\",\n  \t\"@web/test-runner-playwright\": \"^0.11.1\",\n  \t\"@web/test-runner-webdriver\": \"^0.9.0\",\n  \t\"babel-eslint\": \"^10.1.0\",\n  \tchokidar: \"^4.0.2\",\n  \tconcurrently: \"^7.4.0\",\n  \teslint: \"^7.32.0\",\n  \tesm: \"^3.2.25\",\n  \tidiomorph: \"^0.7.3\",\n  \tqunit: \"2.19.1\",\n  \trangy: \"^1.3.0\",\n  \trollup: \"^2.56.3\",\n  \t\"rollup-plugin-includepaths\": \"^0.2.4\",\n  \t\"rollup-plugin-terser\": \"^7.0.2\",\n  \tsass: \"^1.83.0\",\n  \t\"source-map\": \"^0.7.6\",\n  \tsvgo: \"^2.8.0\",\n  \twebdriverio: \"^7.19.5\"\n  };\n  var resolutions = {\n  \twebdriverio: \"^7.19.5\"\n  };\n  var scripts = {\n  \t\"build-css\": \"bin/sass-build assets/trix.scss dist/trix.css action_text-trix/app/assets/stylesheets/trix.css\",\n  \t\"build-js\": \"rollup -c\",\n  \t\"build-assets\": \"cp -f assets/*.html dist/\",\n  \t\"build-ruby\": \"rake -C action_text-trix sync\",\n  \tbuild: \"yarn run build-js && yarn run build-css && yarn run build-assets && yarn run build-ruby\",\n  \twatch: \"rollup -c -w\",\n  \tlint: \"eslint .\",\n  \tpretest: \"yarn run lint && yarn run build\",\n  \ttest: \"web-test-runner\",\n  \t\"test:watch\": \"web-test-runner --watch\",\n  \tversion: \"yarn build && git add action_text-trix\",\n  \tprerelease: \"yarn version && yarn test\",\n  \t\"release-npm\": \"npm adduser && npm publish\",\n  \t\"release-ruby\": \"rake -C action_text-trix release\",\n  \trelease: \"yarn run release-npm && yarn run release-ruby\",\n  \tpostrelease: \"git push && git push --tags\",\n  \tdev: \"web-dev-server --app-index index.html  --root-dir dist --node-resolve --open\",\n  \tstart: \"yarn build-assets && concurrently --kill-others --names js,css,dev-server 'yarn watch' 'yarn build-css --watch' 'yarn dev'\"\n  };\n  var dependencies = {\n  \tdompurify: \"^3.2.5\"\n  };\n  var engines = {\n  \tnode: \">= 18\"\n  };\n  var _package = {\n  \tname: name,\n  \tversion: version,\n  \tdescription: description,\n  \tmain: main,\n  \tmodule: module,\n  \tstyle: style,\n  \tfiles: files,\n  \trepository: repository,\n  \tkeywords: keywords,\n  \tauthor: author,\n  \tlicense: license,\n  \tbugs: bugs,\n  \thomepage: homepage,\n  \tdevDependencies: devDependencies,\n  \tresolutions: resolutions,\n  \tscripts: scripts,\n  \tdependencies: dependencies,\n  \tengines: engines\n  };\n\n  const attachmentSelector = \"[data-trix-attachment]\";\n  const attachments = {\n    preview: {\n      presentation: \"gallery\",\n      caption: {\n        name: true,\n        size: true\n      }\n    },\n    file: {\n      caption: {\n        size: true\n      }\n    }\n  };\n\n  const attributes = {\n    default: {\n      tagName: \"div\",\n      parse: false\n    },\n    quote: {\n      tagName: \"blockquote\",\n      nestable: true\n    },\n    heading1: {\n      tagName: \"h1\",\n      terminal: true,\n      breakOnReturn: true,\n      group: false\n    },\n    code: {\n      tagName: \"pre\",\n      terminal: true,\n      htmlAttributes: [\"language\"],\n      text: {\n        plaintext: true\n      }\n    },\n    bulletList: {\n      tagName: \"ul\",\n      parse: false\n    },\n    bullet: {\n      tagName: \"li\",\n      listAttribute: \"bulletList\",\n      group: false,\n      nestable: true,\n      test(element) {\n        return tagName$1(element.parentNode) === attributes[this.listAttribute].tagName;\n      }\n    },\n    numberList: {\n      tagName: \"ol\",\n      parse: false\n    },\n    number: {\n      tagName: \"li\",\n      listAttribute: \"numberList\",\n      group: false,\n      nestable: true,\n      test(element) {\n        return tagName$1(element.parentNode) === attributes[this.listAttribute].tagName;\n      }\n    },\n    attachmentGallery: {\n      tagName: \"div\",\n      exclusive: true,\n      terminal: true,\n      parse: false,\n      group: false\n    }\n  };\n  const tagName$1 = element => {\n    var _element$tagName;\n    return element === null || element === void 0 || (_element$tagName = element.tagName) === null || _element$tagName === void 0 ? void 0 : _element$tagName.toLowerCase();\n  };\n\n  const androidVersionMatch = navigator.userAgent.match(/android\\s([0-9]+.*Chrome)/i);\n  const androidVersion = androidVersionMatch && parseInt(androidVersionMatch[1]);\n  var browser$1 = {\n    // Android emits composition events when moving the cursor through existing text\n    // Introduced in Chrome 65: https://bugs.chromium.org/p/chromium/issues/detail?id=764439#c9\n    composesExistingText: /Android.*Chrome/.test(navigator.userAgent),\n    // Android 13, especially on Samsung keyboards, emits extra compositionend and beforeinput events\n    // that can make the input handler lose the current selection or enter an infinite input -> render -> input\n    // loop.\n    recentAndroid: androidVersion && androidVersion > 12,\n    samsungAndroid: androidVersion && navigator.userAgent.match(/Android.*SM-/),\n    // IE 11 activates resizing handles on editable elements that have \"layout\"\n    forcesObjectResizing: /Trident.*rv:11/.test(navigator.userAgent),\n    // https://www.w3.org/TR/input-events-1/ + https://www.w3.org/TR/input-events-2/\n    supportsInputEvents: typeof InputEvent !== \"undefined\" && [\"data\", \"getTargetRanges\", \"inputType\"].every(prop => prop in InputEvent.prototype)\n  };\n\n  var css$3 = {\n    attachment: \"attachment\",\n    attachmentCaption: \"attachment__caption\",\n    attachmentCaptionEditor: \"attachment__caption-editor\",\n    attachmentMetadata: \"attachment__metadata\",\n    attachmentMetadataContainer: \"attachment__metadata-container\",\n    attachmentName: \"attachment__name\",\n    attachmentProgress: \"attachment__progress\",\n    attachmentSize: \"attachment__size\",\n    attachmentToolbar: \"attachment__toolbar\",\n    attachmentGallery: \"attachment-gallery\"\n  };\n\n  var dompurify = {\n    ADD_ATTR: [\"language\"],\n    SAFE_FOR_XML: false,\n    RETURN_DOM: true\n  };\n\n  var lang$1 = {\n    attachFiles: \"Attach Files\",\n    bold: \"Bold\",\n    bullets: \"Bullets\",\n    byte: \"Byte\",\n    bytes: \"Bytes\",\n    captionPlaceholder: \"Add a caption…\",\n    code: \"Code\",\n    heading1: \"Heading\",\n    indent: \"Increase Level\",\n    italic: \"Italic\",\n    link: \"Link\",\n    numbers: \"Numbers\",\n    outdent: \"Decrease Level\",\n    quote: \"Quote\",\n    redo: \"Redo\",\n    remove: \"Remove\",\n    strike: \"Strikethrough\",\n    undo: \"Undo\",\n    unlink: \"Unlink\",\n    url: \"URL\",\n    urlPlaceholder: \"Enter a URL…\",\n    GB: \"GB\",\n    KB: \"KB\",\n    MB: \"MB\",\n    PB: \"PB\",\n    TB: \"TB\"\n  };\n\n  /* eslint-disable\n      no-case-declarations,\n  */\n  const sizes = [lang$1.bytes, lang$1.KB, lang$1.MB, lang$1.GB, lang$1.TB, lang$1.PB];\n  var file_size_formatting = {\n    prefix: \"IEC\",\n    precision: 2,\n    formatter(number) {\n      switch (number) {\n        case 0:\n          return \"0 \".concat(lang$1.bytes);\n        case 1:\n          return \"1 \".concat(lang$1.byte);\n        default:\n          let base;\n          if (this.prefix === \"SI\") {\n            base = 1000;\n          } else if (this.prefix === \"IEC\") {\n            base = 1024;\n          }\n          const exp = Math.floor(Math.log(number) / Math.log(base));\n          const humanSize = number / Math.pow(base, exp);\n          const string = humanSize.toFixed(this.precision);\n          const withoutInsignificantZeros = string.replace(/0*$/, \"\").replace(/\\.$/, \"\");\n          return \"\".concat(withoutInsignificantZeros, \" \").concat(sizes[exp]);\n      }\n    }\n  };\n\n  const ZERO_WIDTH_SPACE = \"\\uFEFF\";\n  const NON_BREAKING_SPACE = \"\\u00A0\";\n  const OBJECT_REPLACEMENT_CHARACTER = \"\\uFFFC\";\n\n  const extend = function (properties) {\n    for (const key in properties) {\n      const value = properties[key];\n      this[key] = value;\n    }\n    return this;\n  };\n\n  const html$2 = document.documentElement;\n  const match = html$2.matches;\n  const handleEvent = function (eventName) {\n    let {\n      onElement,\n      matchingSelector,\n      withCallback,\n      inPhase,\n      preventDefault,\n      times\n    } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    const element = onElement ? onElement : html$2;\n    const selector = matchingSelector;\n    const useCapture = inPhase === \"capturing\";\n    const handler = function (event) {\n      if (times != null && --times === 0) {\n        handler.destroy();\n      }\n      const target = findClosestElementFromNode(event.target, {\n        matchingSelector: selector\n      });\n      if (target != null) {\n        withCallback === null || withCallback === void 0 || withCallback.call(target, event, target);\n        if (preventDefault) {\n          event.preventDefault();\n        }\n      }\n    };\n    handler.destroy = () => element.removeEventListener(eventName, handler, useCapture);\n    element.addEventListener(eventName, handler, useCapture);\n    return handler;\n  };\n  const handleEventOnce = function (eventName) {\n    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    options.times = 1;\n    return handleEvent(eventName, options);\n  };\n  const createEvent = function (eventName) {\n    let {\n      bubbles,\n      cancelable,\n      attributes\n    } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    bubbles = bubbles !== false;\n    cancelable = cancelable !== false;\n    const event = document.createEvent(\"Events\");\n    event.initEvent(eventName, bubbles, cancelable);\n    if (attributes != null) {\n      extend.call(event, attributes);\n    }\n    return event;\n  };\n  const triggerEvent = function (eventName) {\n    let {\n      onElement,\n      bubbles,\n      cancelable,\n      attributes\n    } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    const element = onElement != null ? onElement : html$2;\n    const event = createEvent(eventName, {\n      bubbles,\n      cancelable,\n      attributes\n    });\n    return element.dispatchEvent(event);\n  };\n  const elementMatchesSelector = function (element, selector) {\n    if ((element === null || element === void 0 ? void 0 : element.nodeType) === 1) {\n      return match.call(element, selector);\n    }\n  };\n  const findClosestElementFromNode = function (node) {\n    let {\n      matchingSelector,\n      untilNode\n    } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    while (node && node.nodeType !== Node.ELEMENT_NODE) {\n      node = node.parentNode;\n    }\n    if (node == null) {\n      return;\n    }\n    if (matchingSelector != null) {\n      if (node.closest && untilNode == null) {\n        return node.closest(matchingSelector);\n      } else {\n        while (node && node !== untilNode) {\n          if (elementMatchesSelector(node, matchingSelector)) {\n            return node;\n          }\n          node = node.parentNode;\n        }\n      }\n    } else {\n      return node;\n    }\n  };\n  const findInnerElement = function (element) {\n    while ((_element = element) !== null && _element !== void 0 && _element.firstElementChild) {\n      var _element;\n      element = element.firstElementChild;\n    }\n    return element;\n  };\n  const innerElementIsActive = element => document.activeElement !== element && elementContainsNode(element, document.activeElement);\n  const elementContainsNode = function (element, node) {\n    if (!element || !node) {\n      return;\n    }\n    while (node) {\n      if (node === element) {\n        return true;\n      }\n      node = node.parentNode;\n    }\n  };\n  const findNodeFromContainerAndOffset = function (container, offset) {\n    if (!container) {\n      return;\n    }\n    if (container.nodeType === Node.TEXT_NODE) {\n      return container;\n    } else if (offset === 0) {\n      return container.firstChild != null ? container.firstChild : container;\n    } else {\n      return container.childNodes.item(offset - 1);\n    }\n  };\n  const findElementFromContainerAndOffset = function (container, offset) {\n    const node = findNodeFromContainerAndOffset(container, offset);\n    return findClosestElementFromNode(node);\n  };\n  const findChildIndexOfNode = function (node) {\n    var _node;\n    if (!((_node = node) !== null && _node !== void 0 && _node.parentNode)) {\n      return;\n    }\n    let childIndex = 0;\n    node = node.previousSibling;\n    while (node) {\n      childIndex++;\n      node = node.previousSibling;\n    }\n    return childIndex;\n  };\n  const removeNode = node => {\n    var _node$parentNode;\n    return node === null || node === void 0 || (_node$parentNode = node.parentNode) === null || _node$parentNode === void 0 ? void 0 : _node$parentNode.removeChild(node);\n  };\n  const walkTree = function (tree) {\n    let {\n      onlyNodesOfType,\n      usingFilter,\n      expandEntityReferences\n    } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    const whatToShow = (() => {\n      switch (onlyNodesOfType) {\n        case \"element\":\n          return NodeFilter.SHOW_ELEMENT;\n        case \"text\":\n          return NodeFilter.SHOW_TEXT;\n        case \"comment\":\n          return NodeFilter.SHOW_COMMENT;\n        default:\n          return NodeFilter.SHOW_ALL;\n      }\n    })();\n    return document.createTreeWalker(tree, whatToShow, usingFilter != null ? usingFilter : null, expandEntityReferences === true);\n  };\n  const tagName = element => {\n    var _element$tagName;\n    return element === null || element === void 0 || (_element$tagName = element.tagName) === null || _element$tagName === void 0 ? void 0 : _element$tagName.toLowerCase();\n  };\n  const makeElement = function (tag) {\n    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    let key, value;\n    if (typeof tag === \"object\") {\n      options = tag;\n      tag = options.tagName;\n    } else {\n      options = {\n        attributes: options\n      };\n    }\n    const element = document.createElement(tag);\n    if (options.editable != null) {\n      if (options.attributes == null) {\n        options.attributes = {};\n      }\n      options.attributes.contenteditable = options.editable;\n    }\n    if (options.attributes) {\n      for (key in options.attributes) {\n        value = options.attributes[key];\n        element.setAttribute(key, value);\n      }\n    }\n    if (options.style) {\n      for (key in options.style) {\n        value = options.style[key];\n        element.style[key] = value;\n      }\n    }\n    if (options.data) {\n      for (key in options.data) {\n        value = options.data[key];\n        element.dataset[key] = value;\n      }\n    }\n    if (options.className) {\n      options.className.split(\" \").forEach(className => {\n        element.classList.add(className);\n      });\n    }\n    if (options.textContent) {\n      element.textContent = options.textContent;\n    }\n    if (options.childNodes) {\n      [].concat(options.childNodes).forEach(childNode => {\n        element.appendChild(childNode);\n      });\n    }\n    return element;\n  };\n  let blockTagNames = undefined;\n  const getBlockTagNames = function () {\n    if (blockTagNames != null) {\n      return blockTagNames;\n    }\n    blockTagNames = [];\n    for (const key in attributes) {\n      const attributes$1 = attributes[key];\n      if (attributes$1.tagName) {\n        blockTagNames.push(attributes$1.tagName);\n      }\n    }\n    return blockTagNames;\n  };\n  const nodeIsBlockContainer = node => nodeIsBlockStartComment(node === null || node === void 0 ? void 0 : node.firstChild);\n  const nodeProbablyIsBlockContainer = function (node) {\n    return getBlockTagNames().includes(tagName(node)) && !getBlockTagNames().includes(tagName(node.firstChild));\n  };\n  const nodeIsBlockStart = function (node) {\n    let {\n      strict\n    } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n      strict: true\n    };\n    if (strict) {\n      return nodeIsBlockStartComment(node);\n    } else {\n      return nodeIsBlockStartComment(node) || !nodeIsBlockStartComment(node.firstChild) && nodeProbablyIsBlockContainer(node);\n    }\n  };\n  const nodeIsBlockStartComment = node => nodeIsCommentNode(node) && (node === null || node === void 0 ? void 0 : node.data) === \"block\";\n  const nodeIsCommentNode = node => (node === null || node === void 0 ? void 0 : node.nodeType) === Node.COMMENT_NODE;\n  const nodeIsCursorTarget = function (node) {\n    let {\n      name\n    } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    if (!node) {\n      return;\n    }\n    if (nodeIsTextNode(node)) {\n      if (node.data === ZERO_WIDTH_SPACE) {\n        if (name) {\n          return node.parentNode.dataset.trixCursorTarget === name;\n        } else {\n          return true;\n        }\n      }\n    } else {\n      return nodeIsCursorTarget(node.firstChild);\n    }\n  };\n  const nodeIsAttachmentElement = node => elementMatchesSelector(node, attachmentSelector);\n  const nodeIsEmptyTextNode = node => nodeIsTextNode(node) && (node === null || node === void 0 ? void 0 : node.data) === \"\";\n  const nodeIsTextNode = node => (node === null || node === void 0 ? void 0 : node.nodeType) === Node.TEXT_NODE;\n\n  const input = {\n    level2Enabled: true,\n    getLevel() {\n      if (this.level2Enabled && browser$1.supportsInputEvents) {\n        return 2;\n      } else {\n        return 0;\n      }\n    },\n    pickFiles(callback) {\n      const input = makeElement(\"input\", {\n        type: \"file\",\n        multiple: true,\n        hidden: true,\n        id: this.fileInputId\n      });\n      input.addEventListener(\"change\", () => {\n        callback(input.files);\n        removeNode(input);\n      });\n      removeNode(document.getElementById(this.fileInputId));\n      document.body.appendChild(input);\n      input.click();\n    }\n  };\n\n  var key_names = {\n    8: \"backspace\",\n    9: \"tab\",\n    13: \"return\",\n    27: \"escape\",\n    37: \"left\",\n    39: \"right\",\n    46: \"delete\",\n    68: \"d\",\n    72: \"h\",\n    79: \"o\"\n  };\n\n  var parser = {\n    removeBlankTableCells: false,\n    tableCellSeparator: \" | \",\n    tableRowSeparator: \"\\n\"\n  };\n\n  var text_attributes = {\n    bold: {\n      tagName: \"strong\",\n      inheritable: true,\n      parser(element) {\n        const style = window.getComputedStyle(element);\n        return style.fontWeight === \"bold\" || style.fontWeight >= 600;\n      }\n    },\n    italic: {\n      tagName: \"em\",\n      inheritable: true,\n      parser(element) {\n        const style = window.getComputedStyle(element);\n        return style.fontStyle === \"italic\";\n      }\n    },\n    href: {\n      groupTagName: \"a\",\n      parser(element) {\n        const matchingSelector = \"a:not(\".concat(attachmentSelector, \")\");\n        const link = element.closest(matchingSelector);\n        if (link) {\n          return link.getAttribute(\"href\");\n        }\n      }\n    },\n    strike: {\n      tagName: \"del\",\n      inheritable: true\n    },\n    frozen: {\n      style: {\n        backgroundColor: \"highlight\"\n      }\n    }\n  };\n\n  var toolbar = {\n    getDefaultHTML() {\n      return \"<div class=\\\"trix-button-row\\\">\\n      <span class=\\\"trix-button-group trix-button-group--text-tools\\\" data-trix-button-group=\\\"text-tools\\\">\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-bold\\\" data-trix-attribute=\\\"bold\\\" data-trix-key=\\\"b\\\" title=\\\"\".concat(lang$1.bold, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.bold, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-italic\\\" data-trix-attribute=\\\"italic\\\" data-trix-key=\\\"i\\\" title=\\\"\").concat(lang$1.italic, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.italic, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-strike\\\" data-trix-attribute=\\\"strike\\\" title=\\\"\").concat(lang$1.strike, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.strike, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-link\\\" data-trix-attribute=\\\"href\\\" data-trix-action=\\\"link\\\" data-trix-key=\\\"k\\\" title=\\\"\").concat(lang$1.link, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.link, \"</button>\\n      </span>\\n\\n      <span class=\\\"trix-button-group trix-button-group--block-tools\\\" data-trix-button-group=\\\"block-tools\\\">\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-heading-1\\\" data-trix-attribute=\\\"heading1\\\" title=\\\"\").concat(lang$1.heading1, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.heading1, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-quote\\\" data-trix-attribute=\\\"quote\\\" title=\\\"\").concat(lang$1.quote, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.quote, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-code\\\" data-trix-attribute=\\\"code\\\" title=\\\"\").concat(lang$1.code, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.code, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-bullet-list\\\" data-trix-attribute=\\\"bullet\\\" title=\\\"\").concat(lang$1.bullets, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.bullets, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-number-list\\\" data-trix-attribute=\\\"number\\\" title=\\\"\").concat(lang$1.numbers, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.numbers, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-decrease-nesting-level\\\" data-trix-action=\\\"decreaseNestingLevel\\\" title=\\\"\").concat(lang$1.outdent, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.outdent, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-increase-nesting-level\\\" data-trix-action=\\\"increaseNestingLevel\\\" title=\\\"\").concat(lang$1.indent, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.indent, \"</button>\\n      </span>\\n\\n      <span class=\\\"trix-button-group trix-button-group--file-tools\\\" data-trix-button-group=\\\"file-tools\\\">\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-attach\\\" data-trix-action=\\\"attachFiles\\\" title=\\\"\").concat(lang$1.attachFiles, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.attachFiles, \"</button>\\n      </span>\\n\\n      <span class=\\\"trix-button-group-spacer\\\"></span>\\n\\n      <span class=\\\"trix-button-group trix-button-group--history-tools\\\" data-trix-button-group=\\\"history-tools\\\">\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-undo\\\" data-trix-action=\\\"undo\\\" data-trix-key=\\\"z\\\" title=\\\"\").concat(lang$1.undo, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.undo, \"</button>\\n        <button type=\\\"button\\\" class=\\\"trix-button trix-button--icon trix-button--icon-redo\\\" data-trix-action=\\\"redo\\\" data-trix-key=\\\"shift+z\\\" title=\\\"\").concat(lang$1.redo, \"\\\" tabindex=\\\"-1\\\">\").concat(lang$1.redo, \"</button>\\n      </span>\\n    </div>\\n\\n    <div class=\\\"trix-dialogs\\\" data-trix-dialogs>\\n      <div class=\\\"trix-dialog trix-dialog--link\\\" data-trix-dialog=\\\"href\\\" data-trix-dialog-attribute=\\\"href\\\">\\n        <div class=\\\"trix-dialog__link-fields\\\">\\n          <input type=\\\"url\\\" name=\\\"href\\\" class=\\\"trix-input trix-input--dialog\\\" placeholder=\\\"\").concat(lang$1.urlPlaceholder, \"\\\" aria-label=\\\"\").concat(lang$1.url, \"\\\" data-trix-validate-href required data-trix-input>\\n          <div class=\\\"trix-button-group\\\">\\n            <input type=\\\"button\\\" class=\\\"trix-button trix-button--dialog\\\" value=\\\"\").concat(lang$1.link, \"\\\" data-trix-method=\\\"setAttribute\\\">\\n            <input type=\\\"button\\\" class=\\\"trix-button trix-button--dialog\\\" value=\\\"\").concat(lang$1.unlink, \"\\\" data-trix-method=\\\"removeAttribute\\\">\\n          </div>\\n        </div>\\n      </div>\\n    </div>\");\n    }\n  };\n\n  const undo = {\n    interval: 5000\n  };\n\n  var config = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    attachments: attachments,\n    blockAttributes: attributes,\n    browser: browser$1,\n    css: css$3,\n    dompurify: dompurify,\n    fileSize: file_size_formatting,\n    input: input,\n    keyNames: key_names,\n    lang: lang$1,\n    parser: parser,\n    textAttributes: text_attributes,\n    toolbar: toolbar,\n    undo: undo\n  });\n\n  class BasicObject {\n    static proxyMethod(expression) {\n      const {\n        name,\n        toMethod,\n        toProperty,\n        optional\n      } = parseProxyMethodExpression(expression);\n      this.prototype[name] = function () {\n        let subject;\n        let object;\n        if (toMethod) {\n          if (optional) {\n            var _this$toMethod;\n            object = (_this$toMethod = this[toMethod]) === null || _this$toMethod === void 0 ? void 0 : _this$toMethod.call(this);\n          } else {\n            object = this[toMethod]();\n          }\n        } else if (toProperty) {\n          object = this[toProperty];\n        }\n        if (optional) {\n          var _object;\n          subject = (_object = object) === null || _object === void 0 ? void 0 : _object[name];\n          if (subject) {\n            return apply$1.call(subject, object, arguments);\n          }\n        } else {\n          subject = object[name];\n          return apply$1.call(subject, object, arguments);\n        }\n      };\n    }\n  }\n  const parseProxyMethodExpression = function (expression) {\n    const match = expression.match(proxyMethodExpressionPattern);\n    if (!match) {\n      throw new Error(\"can't parse @proxyMethod expression: \".concat(expression));\n    }\n    const args = {\n      name: match[4]\n    };\n    if (match[2] != null) {\n      args.toMethod = match[1];\n    } else {\n      args.toProperty = match[1];\n    }\n    if (match[3] != null) {\n      args.optional = true;\n    }\n    return args;\n  };\n  const {\n    apply: apply$1\n  } = Function.prototype;\n  const proxyMethodExpressionPattern = new RegExp(\"\\\n^\\\n(.+?)\\\n(\\\\(\\\\))?\\\n(\\\\?)?\\\n\\\\.\\\n(.+?)\\\n$\\\n\");\n\n  var _Array$from, _$codePointAt$1, _$1, _String$fromCodePoint;\n  class UTF16String extends BasicObject {\n    static box() {\n      let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : \"\";\n      if (value instanceof this) {\n        return value;\n      } else {\n        return this.fromUCS2String(value === null || value === void 0 ? void 0 : value.toString());\n      }\n    }\n    static fromUCS2String(ucs2String) {\n      return new this(ucs2String, ucs2decode(ucs2String));\n    }\n    static fromCodepoints(codepoints) {\n      return new this(ucs2encode(codepoints), codepoints);\n    }\n    constructor(ucs2String, codepoints) {\n      super(...arguments);\n      this.ucs2String = ucs2String;\n      this.codepoints = codepoints;\n      this.length = this.codepoints.length;\n      this.ucs2Length = this.ucs2String.length;\n    }\n    offsetToUCS2Offset(offset) {\n      return ucs2encode(this.codepoints.slice(0, Math.max(0, offset))).length;\n    }\n    offsetFromUCS2Offset(ucs2Offset) {\n      return ucs2decode(this.ucs2String.slice(0, Math.max(0, ucs2Offset))).length;\n    }\n    slice() {\n      return this.constructor.fromCodepoints(this.codepoints.slice(...arguments));\n    }\n    charAt(offset) {\n      return this.slice(offset, offset + 1);\n    }\n    isEqualTo(value) {\n      return this.constructor.box(value).ucs2String === this.ucs2String;\n    }\n    toJSON() {\n      return this.ucs2String;\n    }\n    getCacheKey() {\n      return this.ucs2String;\n    }\n    toString() {\n      return this.ucs2String;\n    }\n  }\n  const hasArrayFrom = ((_Array$from = Array.from) === null || _Array$from === void 0 ? void 0 : _Array$from.call(Array, \"\\ud83d\\udc7c\").length) === 1;\n  const hasStringCodePointAt$1 = ((_$codePointAt$1 = (_$1 = \" \").codePointAt) === null || _$codePointAt$1 === void 0 ? void 0 : _$codePointAt$1.call(_$1, 0)) != null;\n  const hasStringFromCodePoint = ((_String$fromCodePoint = String.fromCodePoint) === null || _String$fromCodePoint === void 0 ? void 0 : _String$fromCodePoint.call(String, 32, 128124)) === \" \\ud83d\\udc7c\";\n\n  // UCS-2 conversion helpers ported from Mathias Bynens' Punycode.js:\n  // https://github.com/bestiejs/punycode.js#punycodeucs2\n\n  let ucs2decode, ucs2encode;\n\n  // Creates an array containing the numeric code points of each Unicode\n  // character in the string. While JavaScript uses UCS-2 internally,\n  // this function will convert a pair of surrogate halves (each of which\n  // UCS-2 exposes as separate characters) into a single code point,\n  // matching UTF-16.\n  if (hasArrayFrom && hasStringCodePointAt$1) {\n    ucs2decode = string => Array.from(string).map(char => char.codePointAt(0));\n  } else {\n    ucs2decode = function (string) {\n      const output = [];\n      let counter = 0;\n      const {\n        length\n      } = string;\n      while (counter < length) {\n        let value = string.charCodeAt(counter++);\n        if (0xd800 <= value && value <= 0xdbff && counter < length) {\n          // high surrogate, and there is a next character\n          const extra = string.charCodeAt(counter++);\n          if ((extra & 0xfc00) === 0xdc00) {\n            // low surrogate\n            value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;\n          } else {\n            // unmatched surrogate; only append this code unit, in case the\n            // next code unit is the high surrogate of a surrogate pair\n            counter--;\n          }\n        }\n        output.push(value);\n      }\n      return output;\n    };\n  }\n\n  // Creates a string based on an array of numeric code points.\n  if (hasStringFromCodePoint) {\n    ucs2encode = array => String.fromCodePoint(...Array.from(array || []));\n  } else {\n    ucs2encode = function (array) {\n      const characters = (() => {\n        const result = [];\n        Array.from(array).forEach(value => {\n          let output = \"\";\n          if (value > 0xffff) {\n            value -= 0x10000;\n            output += String.fromCharCode(value >>> 10 & 0x3ff | 0xd800);\n            value = 0xdc00 | value & 0x3ff;\n          }\n          result.push(output + String.fromCharCode(value));\n        });\n        return result;\n      })();\n      return characters.join(\"\");\n    };\n  }\n\n  let id$2 = 0;\n  class TrixObject extends BasicObject {\n    static fromJSONString(jsonString) {\n      return this.fromJSON(JSON.parse(jsonString));\n    }\n    constructor() {\n      super(...arguments);\n      this.id = ++id$2;\n    }\n    hasSameConstructorAs(object) {\n      return this.constructor === (object === null || object === void 0 ? void 0 : object.constructor);\n    }\n    isEqualTo(object) {\n      return this === object;\n    }\n    inspect() {\n      const parts = [];\n      const contents = this.contentsForInspection() || {};\n      for (const key in contents) {\n        const value = contents[key];\n        parts.push(\"\".concat(key, \"=\").concat(value));\n      }\n      return \"#<\".concat(this.constructor.name, \":\").concat(this.id).concat(parts.length ? \" \".concat(parts.join(\", \")) : \"\", \">\");\n    }\n    contentsForInspection() {}\n    toJSONString() {\n      return JSON.stringify(this);\n    }\n    toUTF16String() {\n      return UTF16String.box(this);\n    }\n    getCacheKey() {\n      return this.id.toString();\n    }\n  }\n\n  /* eslint-disable\n      id-length,\n  */\n  const arraysAreEqual = function () {\n    let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n    let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];\n    if (a.length !== b.length) {\n      return false;\n    }\n    for (let index = 0; index < a.length; index++) {\n      const value = a[index];\n      if (value !== b[index]) {\n        return false;\n      }\n    }\n    return true;\n  };\n  const arrayStartsWith = function () {\n    let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n    let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];\n    return arraysAreEqual(a.slice(0, b.length), b);\n  };\n  const spliceArray = function (array) {\n    const result = array.slice(0);\n    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n      args[_key - 1] = arguments[_key];\n    }\n    result.splice(...args);\n    return result;\n  };\n  const summarizeArrayChange = function () {\n    let oldArray = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n    let newArray = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];\n    const added = [];\n    const removed = [];\n    const existingValues = new Set();\n    oldArray.forEach(value => {\n      existingValues.add(value);\n    });\n    const currentValues = new Set();\n    newArray.forEach(value => {\n      currentValues.add(value);\n      if (!existingValues.has(value)) {\n        added.push(value);\n      }\n    });\n    oldArray.forEach(value => {\n      if (!currentValues.has(value)) {\n        removed.push(value);\n      }\n    });\n    return {\n      added,\n      removed\n    };\n  };\n\n  // https://github.com/mathiasbynens/unicode-2.1.8/blob/master/Bidi_Class/Right_To_Left/regex.js\n  const RTL_PATTERN = /[\\u05BE\\u05C0\\u05C3\\u05D0-\\u05EA\\u05F0-\\u05F4\\u061B\\u061F\\u0621-\\u063A\\u0640-\\u064A\\u066D\\u0671-\\u06B7\\u06BA-\\u06BE\\u06C0-\\u06CE\\u06D0-\\u06D5\\u06E5\\u06E6\\u200F\\u202B\\u202E\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE72\\uFE74\\uFE76-\\uFEFC]/;\n  const getDirection = function () {\n    const input = makeElement(\"input\", {\n      dir: \"auto\",\n      name: \"x\",\n      dirName: \"x.dir\"\n    });\n    const textArea = makeElement(\"textarea\", {\n      dir: \"auto\",\n      name: \"y\",\n      dirName: \"y.dir\"\n    });\n    const form = makeElement(\"form\");\n    form.appendChild(input);\n    form.appendChild(textArea);\n    const supportsDirName = function () {\n      try {\n        return new FormData(form).has(textArea.dirName);\n      } catch (error) {\n        return false;\n      }\n    }();\n    const supportsDirSelector = function () {\n      try {\n        return input.matches(\":dir(ltr),:dir(rtl)\");\n      } catch (error) {\n        return false;\n      }\n    }();\n    if (supportsDirName) {\n      return function (string) {\n        textArea.value = string;\n        return new FormData(form).get(textArea.dirName);\n      };\n    } else if (supportsDirSelector) {\n      return function (string) {\n        input.value = string;\n        if (input.matches(\":dir(rtl)\")) {\n          return \"rtl\";\n        } else {\n          return \"ltr\";\n        }\n      };\n    } else {\n      return function (string) {\n        const char = string.trim().charAt(0);\n        if (RTL_PATTERN.test(char)) {\n          return \"rtl\";\n        } else {\n          return \"ltr\";\n        }\n      };\n    }\n  }();\n\n  let allAttributeNames = null;\n  let blockAttributeNames = null;\n  let textAttributeNames = null;\n  let listAttributeNames = null;\n  const getAllAttributeNames = () => {\n    if (!allAttributeNames) {\n      allAttributeNames = getTextAttributeNames().concat(getBlockAttributeNames());\n    }\n    return allAttributeNames;\n  };\n  const getBlockConfig = attributeName => attributes[attributeName];\n  const getBlockAttributeNames = () => {\n    if (!blockAttributeNames) {\n      blockAttributeNames = Object.keys(attributes);\n    }\n    return blockAttributeNames;\n  };\n  const getTextConfig = attributeName => text_attributes[attributeName];\n  const getTextAttributeNames = () => {\n    if (!textAttributeNames) {\n      textAttributeNames = Object.keys(text_attributes);\n    }\n    return textAttributeNames;\n  };\n  const getListAttributeNames = () => {\n    if (!listAttributeNames) {\n      listAttributeNames = [];\n      for (const key in attributes) {\n        const {\n          listAttribute\n        } = attributes[key];\n        if (listAttribute != null) {\n          listAttributeNames.push(listAttribute);\n        }\n      }\n    }\n    return listAttributeNames;\n  };\n\n  /* eslint-disable\n  */\n  const installDefaultCSSForTagName = function (tagName, defaultCSS) {\n    const styleElement = insertStyleElementForTagName(tagName);\n    styleElement.textContent = defaultCSS.replace(/%t/g, tagName);\n  };\n  const insertStyleElementForTagName = function (tagName) {\n    const element = document.createElement(\"style\");\n    element.setAttribute(\"type\", \"text/css\");\n    element.setAttribute(\"data-tag-name\", tagName.toLowerCase());\n    const nonce = getCSPNonce();\n    if (nonce) {\n      element.setAttribute(\"nonce\", nonce);\n    }\n    document.head.insertBefore(element, document.head.firstChild);\n    return element;\n  };\n  const getCSPNonce = function () {\n    const element = getMetaElement(\"trix-csp-nonce\") || getMetaElement(\"csp-nonce\");\n    if (element) {\n      const {\n        nonce,\n        content\n      } = element;\n      return nonce == \"\" ? content : nonce;\n    }\n  };\n  const getMetaElement = name => document.head.querySelector(\"meta[name=\".concat(name, \"]\"));\n\n  const testTransferData = {\n    \"application/x-trix-feature-detection\": \"test\"\n  };\n  const dataTransferIsPlainText = function (dataTransfer) {\n    const text = dataTransfer.getData(\"text/plain\");\n    const html = dataTransfer.getData(\"text/html\");\n    if (text && html) {\n      const {\n        body\n      } = new DOMParser().parseFromString(html, \"text/html\");\n      if (body.textContent === text) {\n        return !body.querySelector(\"*\");\n      }\n    } else {\n      return text === null || text === void 0 ? void 0 : text.length;\n    }\n  };\n  const dataTransferIsMsOfficePaste = _ref => {\n    let {\n      dataTransfer\n    } = _ref;\n    return dataTransfer.types.includes(\"Files\") && dataTransfer.types.includes(\"text/html\") && dataTransfer.getData(\"text/html\").includes(\"urn:schemas-microsoft-com:office:office\");\n  };\n  const dataTransferIsWritable = function (dataTransfer) {\n    if (!(dataTransfer !== null && dataTransfer !== void 0 && dataTransfer.setData)) return false;\n    for (const key in testTransferData) {\n      const value = testTransferData[key];\n      try {\n        dataTransfer.setData(key, value);\n        if (!dataTransfer.getData(key) === value) return false;\n      } catch (error) {\n        return false;\n      }\n    }\n    return true;\n  };\n  const keyEventIsKeyboardCommand = function () {\n    if (/Mac|^iP/.test(navigator.platform)) {\n      return event => event.metaKey;\n    } else {\n      return event => event.ctrlKey;\n    }\n  }();\n  function shouldRenderInmmediatelyToDealWithIOSDictation(inputEvent) {\n    if (/iPhone|iPad/.test(navigator.userAgent)) {\n      // Handle garbled content and duplicated newlines when using dictation on iOS 18+. Upon dictation completion, iOS sends\n      // the list of insertText / insertParagraph events in a quick sequence. If we don't render\n      // the editor synchronously, the internal range fails to update and results in garbled content or duplicated newlines.\n      //\n      // This workaround is necessary because iOS doesn't send composing events as expected while dictating:\n      // https://bugs.webkit.org/show_bug.cgi?id=261764\n      return !inputEvent.inputType || inputEvent.inputType === \"insertParagraph\";\n    } else {\n      return false;\n    }\n  }\n\n  const defer = fn => setTimeout(fn, 1);\n\n  /* eslint-disable\n      id-length,\n  */\n  const copyObject = function () {\n    let object = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n    const result = {};\n    for (const key in object) {\n      const value = object[key];\n      result[key] = value;\n    }\n    return result;\n  };\n  const objectsAreEqual = function () {\n    let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n    let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    if (Object.keys(a).length !== Object.keys(b).length) {\n      return false;\n    }\n    for (const key in a) {\n      const value = a[key];\n      if (value !== b[key]) {\n        return false;\n      }\n    }\n    return true;\n  };\n\n  const normalizeRange = function (range) {\n    if (range == null) return;\n    if (!Array.isArray(range)) {\n      range = [range, range];\n    }\n    return [copyValue(range[0]), copyValue(range[1] != null ? range[1] : range[0])];\n  };\n  const rangeIsCollapsed = function (range) {\n    if (range == null) return;\n    const [start, end] = normalizeRange(range);\n    return rangeValuesAreEqual(start, end);\n  };\n  const rangesAreEqual = function (leftRange, rightRange) {\n    if (leftRange == null || rightRange == null) return;\n    const [leftStart, leftEnd] = normalizeRange(leftRange);\n    const [rightStart, rightEnd] = normalizeRange(rightRange);\n    return rangeValuesAreEqual(leftStart, rightStart) && rangeValuesAreEqual(leftEnd, rightEnd);\n  };\n  const copyValue = function (value) {\n    if (typeof value === \"number\") {\n      return value;\n    } else {\n      return copyObject(value);\n    }\n  };\n  const rangeValuesAreEqual = function (left, right) {\n    if (typeof left === \"number\") {\n      return left === right;\n    } else {\n      return objectsAreEqual(left, right);\n    }\n  };\n\n  class SelectionChangeObserver extends BasicObject {\n    constructor() {\n      super(...arguments);\n      this.update = this.update.bind(this);\n      this.selectionManagers = [];\n    }\n    start() {\n      if (!this.started) {\n        this.started = true;\n        document.addEventListener(\"selectionchange\", this.update, true);\n      }\n    }\n    stop() {\n      if (this.started) {\n        this.started = false;\n        return document.removeEventListener(\"selectionchange\", this.update, true);\n      }\n    }\n    registerSelectionManager(selectionManager) {\n      if (!this.selectionManagers.includes(selectionManager)) {\n        this.selectionManagers.push(selectionManager);\n        return this.start();\n      }\n    }\n    unregisterSelectionManager(selectionManager) {\n      this.selectionManagers = this.selectionManagers.filter(sm => sm !== selectionManager);\n      if (this.selectionManagers.length === 0) {\n        return this.stop();\n      }\n    }\n    notifySelectionManagersOfSelectionChange() {\n      return this.selectionManagers.map(selectionManager => selectionManager.selectionDidChange());\n    }\n    update() {\n      this.notifySelectionManagersOfSelectionChange();\n    }\n    reset() {\n      this.update();\n    }\n  }\n  const selectionChangeObserver = new SelectionChangeObserver();\n  const getDOMSelection = function () {\n    const selection = window.getSelection();\n    if (selection.rangeCount > 0) {\n      return selection;\n    }\n  };\n  const getDOMRange = function () {\n    var _getDOMSelection;\n    const domRange = (_getDOMSelection = getDOMSelection()) === null || _getDOMSelection === void 0 ? void 0 : _getDOMSelection.getRangeAt(0);\n    if (domRange) {\n      if (!domRangeIsPrivate(domRange)) {\n        return domRange;\n      }\n    }\n  };\n  const setDOMRange = function (domRange) {\n    const selection = window.getSelection();\n    selection.removeAllRanges();\n    selection.addRange(domRange);\n    return selectionChangeObserver.update();\n  };\n\n  // In Firefox, clicking certain <input> elements changes the selection to a\n  // private element used to draw its UI. Attempting to access properties of those\n  // elements throws an error.\n  // https://bugzilla.mozilla.org/show_bug.cgi?id=208427\n  const domRangeIsPrivate = domRange => nodeIsPrivate(domRange.startContainer) || nodeIsPrivate(domRange.endContainer);\n  const nodeIsPrivate = node => !Object.getPrototypeOf(node);\n\n  /* eslint-disable\n      id-length,\n      no-useless-escape,\n  */\n  const normalizeSpaces = string => string.replace(new RegExp(\"\".concat(ZERO_WIDTH_SPACE), \"g\"), \"\").replace(new RegExp(\"\".concat(NON_BREAKING_SPACE), \"g\"), \" \");\n  const normalizeNewlines = string => string.replace(/\\r\\n?/g, \"\\n\");\n  const breakableWhitespacePattern = new RegExp(\"[^\\\\S\".concat(NON_BREAKING_SPACE, \"]\"));\n  const squishBreakableWhitespace = string => string\n  // Replace all breakable whitespace characters with a space\n  .replace(new RegExp(\"\".concat(breakableWhitespacePattern.source), \"g\"), \" \")\n  // Replace two or more spaces with a single space\n  .replace(/\\ {2,}/g, \" \");\n  const summarizeStringChange = function (oldString, newString) {\n    let added, removed;\n    oldString = UTF16String.box(oldString);\n    newString = UTF16String.box(newString);\n    if (newString.length < oldString.length) {\n      [removed, added] = utf16StringDifferences(oldString, newString);\n    } else {\n      [added, removed] = utf16StringDifferences(newString, oldString);\n    }\n    return {\n      added,\n      removed\n    };\n  };\n  const utf16StringDifferences = function (a, b) {\n    if (a.isEqualTo(b)) {\n      return [\"\", \"\"];\n    }\n    const diffA = utf16StringDifference(a, b);\n    const {\n      length\n    } = diffA.utf16String;\n    let diffB;\n    if (length) {\n      const {\n        offset\n      } = diffA;\n      const codepoints = a.codepoints.slice(0, offset).concat(a.codepoints.slice(offset + length));\n      diffB = utf16StringDifference(b, UTF16String.fromCodepoints(codepoints));\n    } else {\n      diffB = utf16StringDifference(b, a);\n    }\n    return [diffA.utf16String.toString(), diffB.utf16String.toString()];\n  };\n  const utf16StringDifference = function (a, b) {\n    let leftIndex = 0;\n    let rightIndexA = a.length;\n    let rightIndexB = b.length;\n    while (leftIndex < rightIndexA && a.charAt(leftIndex).isEqualTo(b.charAt(leftIndex))) {\n      leftIndex++;\n    }\n    while (rightIndexA > leftIndex + 1 && a.charAt(rightIndexA - 1).isEqualTo(b.charAt(rightIndexB - 1))) {\n      rightIndexA--;\n      rightIndexB--;\n    }\n    return {\n      utf16String: a.slice(leftIndex, rightIndexA),\n      offset: leftIndex\n    };\n  };\n\n  class Hash extends TrixObject {\n    static fromCommonAttributesOfObjects() {\n      let objects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      if (!objects.length) {\n        return new this();\n      }\n      let hash = box(objects[0]);\n      let keys = hash.getKeys();\n      objects.slice(1).forEach(object => {\n        keys = hash.getKeysCommonToHash(box(object));\n        hash = hash.slice(keys);\n      });\n      return hash;\n    }\n    static box(values) {\n      return box(values);\n    }\n    constructor() {\n      let values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      super(...arguments);\n      this.values = copy(values);\n    }\n    add(key, value) {\n      return this.merge(object(key, value));\n    }\n    remove(key) {\n      return new Hash(copy(this.values, key));\n    }\n    get(key) {\n      return this.values[key];\n    }\n    has(key) {\n      return key in this.values;\n    }\n    merge(values) {\n      return new Hash(merge(this.values, unbox(values)));\n    }\n    slice(keys) {\n      const values = {};\n      Array.from(keys).forEach(key => {\n        if (this.has(key)) {\n          values[key] = this.values[key];\n        }\n      });\n      return new Hash(values);\n    }\n    getKeys() {\n      return Object.keys(this.values);\n    }\n    getKeysCommonToHash(hash) {\n      hash = box(hash);\n      return this.getKeys().filter(key => this.values[key] === hash.values[key]);\n    }\n    isEqualTo(values) {\n      return arraysAreEqual(this.toArray(), box(values).toArray());\n    }\n    isEmpty() {\n      return this.getKeys().length === 0;\n    }\n    toArray() {\n      if (!this.array) {\n        const result = [];\n        for (const key in this.values) {\n          const value = this.values[key];\n          result.push(result.push(key, value));\n        }\n        this.array = result.slice(0);\n      }\n      return this.array;\n    }\n    toObject() {\n      return copy(this.values);\n    }\n    toJSON() {\n      return this.toObject();\n    }\n    contentsForInspection() {\n      return {\n        values: JSON.stringify(this.values)\n      };\n    }\n  }\n  const object = function (key, value) {\n    const result = {};\n    result[key] = value;\n    return result;\n  };\n  const merge = function (object, values) {\n    const result = copy(object);\n    for (const key in values) {\n      const value = values[key];\n      result[key] = value;\n    }\n    return result;\n  };\n  const copy = function (object, keyToRemove) {\n    const result = {};\n    const sortedKeys = Object.keys(object).sort();\n    sortedKeys.forEach(key => {\n      if (key !== keyToRemove) {\n        result[key] = object[key];\n      }\n    });\n    return result;\n  };\n  const box = function (object) {\n    if (object instanceof Hash) {\n      return object;\n    } else {\n      return new Hash(object);\n    }\n  };\n  const unbox = function (object) {\n    if (object instanceof Hash) {\n      return object.values;\n    } else {\n      return object;\n    }\n  };\n\n  class ObjectGroup {\n    static groupObjects() {\n      let ungroupedObjects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      let {\n        depth,\n        asTree\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let group;\n      if (asTree) {\n        if (depth == null) {\n          depth = 0;\n        }\n      }\n      const objects = [];\n      Array.from(ungroupedObjects).forEach(object => {\n        var _object$canBeGrouped2;\n        if (group) {\n          var _object$canBeGrouped, _group$canBeGroupedWi, _group;\n          if ((_object$canBeGrouped = object.canBeGrouped) !== null && _object$canBeGrouped !== void 0 && _object$canBeGrouped.call(object, depth) && (_group$canBeGroupedWi = (_group = group[group.length - 1]).canBeGroupedWith) !== null && _group$canBeGroupedWi !== void 0 && _group$canBeGroupedWi.call(_group, object, depth)) {\n            group.push(object);\n            return;\n          } else {\n            objects.push(new this(group, {\n              depth,\n              asTree\n            }));\n            group = null;\n          }\n        }\n        if ((_object$canBeGrouped2 = object.canBeGrouped) !== null && _object$canBeGrouped2 !== void 0 && _object$canBeGrouped2.call(object, depth)) {\n          group = [object];\n        } else {\n          objects.push(object);\n        }\n      });\n      if (group) {\n        objects.push(new this(group, {\n          depth,\n          asTree\n        }));\n      }\n      return objects;\n    }\n    constructor() {\n      let objects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      let {\n        depth,\n        asTree\n      } = arguments.length > 1 ? arguments[1] : undefined;\n      this.objects = objects;\n      if (asTree) {\n        this.depth = depth;\n        this.objects = this.constructor.groupObjects(this.objects, {\n          asTree,\n          depth: this.depth + 1\n        });\n      }\n    }\n    getObjects() {\n      return this.objects;\n    }\n    getDepth() {\n      return this.depth;\n    }\n    getCacheKey() {\n      const keys = [\"objectGroup\"];\n      Array.from(this.getObjects()).forEach(object => {\n        keys.push(object.getCacheKey());\n      });\n      return keys.join(\"/\");\n    }\n  }\n\n  class ObjectMap extends BasicObject {\n    constructor() {\n      let objects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      super(...arguments);\n      this.objects = {};\n      Array.from(objects).forEach(object => {\n        const hash = JSON.stringify(object);\n        if (this.objects[hash] == null) {\n          this.objects[hash] = object;\n        }\n      });\n    }\n    find(object) {\n      const hash = JSON.stringify(object);\n      return this.objects[hash];\n    }\n  }\n\n  class ElementStore {\n    constructor(elements) {\n      this.reset(elements);\n    }\n    add(element) {\n      const key = getKey(element);\n      this.elements[key] = element;\n    }\n    remove(element) {\n      const key = getKey(element);\n      const value = this.elements[key];\n      if (value) {\n        delete this.elements[key];\n        return value;\n      }\n    }\n    reset() {\n      let elements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      this.elements = {};\n      Array.from(elements).forEach(element => {\n        this.add(element);\n      });\n      return elements;\n    }\n  }\n  const getKey = element => element.dataset.trixStoreKey;\n\n  class Operation extends BasicObject {\n    isPerforming() {\n      return this.performing === true;\n    }\n    hasPerformed() {\n      return this.performed === true;\n    }\n    hasSucceeded() {\n      return this.performed && this.succeeded;\n    }\n    hasFailed() {\n      return this.performed && !this.succeeded;\n    }\n    getPromise() {\n      if (!this.promise) {\n        this.promise = new Promise((resolve, reject) => {\n          this.performing = true;\n          return this.perform((succeeded, result) => {\n            this.succeeded = succeeded;\n            this.performing = false;\n            this.performed = true;\n            if (this.succeeded) {\n              resolve(result);\n            } else {\n              reject(result);\n            }\n          });\n        });\n      }\n      return this.promise;\n    }\n    perform(callback) {\n      return callback(false);\n    }\n    release() {\n      var _this$promise, _this$promise$cancel;\n      (_this$promise = this.promise) === null || _this$promise === void 0 || (_this$promise$cancel = _this$promise.cancel) === null || _this$promise$cancel === void 0 || _this$promise$cancel.call(_this$promise);\n      this.promise = null;\n      this.performing = null;\n      this.performed = null;\n      this.succeeded = null;\n    }\n  }\n  Operation.proxyMethod(\"getPromise().then\");\n  Operation.proxyMethod(\"getPromise().catch\");\n\n  class ObjectView extends BasicObject {\n    constructor(object) {\n      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      super(...arguments);\n      this.object = object;\n      this.options = options;\n      this.childViews = [];\n      this.rootView = this;\n    }\n    getNodes() {\n      if (!this.nodes) {\n        this.nodes = this.createNodes();\n      }\n      return this.nodes.map(node => node.cloneNode(true));\n    }\n    invalidate() {\n      var _this$parentView;\n      this.nodes = null;\n      this.childViews = [];\n      return (_this$parentView = this.parentView) === null || _this$parentView === void 0 ? void 0 : _this$parentView.invalidate();\n    }\n    invalidateViewForObject(object) {\n      var _this$findViewForObje;\n      return (_this$findViewForObje = this.findViewForObject(object)) === null || _this$findViewForObje === void 0 ? void 0 : _this$findViewForObje.invalidate();\n    }\n    findOrCreateCachedChildView(viewClass, object, options) {\n      let view = this.getCachedViewForObject(object);\n      if (view) {\n        this.recordChildView(view);\n      } else {\n        view = this.createChildView(...arguments);\n        this.cacheViewForObject(view, object);\n      }\n      return view;\n    }\n    createChildView(viewClass, object) {\n      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};\n      if (object instanceof ObjectGroup) {\n        options.viewClass = viewClass;\n        viewClass = ObjectGroupView;\n      }\n      const view = new viewClass(object, options);\n      return this.recordChildView(view);\n    }\n    recordChildView(view) {\n      view.parentView = this;\n      view.rootView = this.rootView;\n      this.childViews.push(view);\n      return view;\n    }\n    getAllChildViews() {\n      let views = [];\n      this.childViews.forEach(childView => {\n        views.push(childView);\n        views = views.concat(childView.getAllChildViews());\n      });\n      return views;\n    }\n    findElement() {\n      return this.findElementForObject(this.object);\n    }\n    findElementForObject(object) {\n      const id = object === null || object === void 0 ? void 0 : object.id;\n      if (id) {\n        return this.rootView.element.querySelector(\"[data-trix-id='\".concat(id, \"']\"));\n      }\n    }\n    findViewForObject(object) {\n      for (const view of this.getAllChildViews()) {\n        if (view.object === object) {\n          return view;\n        }\n      }\n    }\n    getViewCache() {\n      if (this.rootView === this) {\n        if (this.isViewCachingEnabled()) {\n          if (!this.viewCache) {\n            this.viewCache = {};\n          }\n          return this.viewCache;\n        }\n      } else {\n        return this.rootView.getViewCache();\n      }\n    }\n    isViewCachingEnabled() {\n      return this.shouldCacheViews !== false;\n    }\n    enableViewCaching() {\n      this.shouldCacheViews = true;\n    }\n    disableViewCaching() {\n      this.shouldCacheViews = false;\n    }\n    getCachedViewForObject(object) {\n      var _this$getViewCache;\n      return (_this$getViewCache = this.getViewCache()) === null || _this$getViewCache === void 0 ? void 0 : _this$getViewCache[object.getCacheKey()];\n    }\n    cacheViewForObject(view, object) {\n      const cache = this.getViewCache();\n      if (cache) {\n        cache[object.getCacheKey()] = view;\n      }\n    }\n    garbageCollectCachedViews() {\n      const cache = this.getViewCache();\n      if (cache) {\n        const views = this.getAllChildViews().concat(this);\n        const objectKeys = views.map(view => view.object.getCacheKey());\n        for (const key in cache) {\n          if (!objectKeys.includes(key)) {\n            delete cache[key];\n          }\n        }\n      }\n    }\n  }\n  class ObjectGroupView extends ObjectView {\n    constructor() {\n      super(...arguments);\n      this.objectGroup = this.object;\n      this.viewClass = this.options.viewClass;\n      delete this.options.viewClass;\n    }\n    getChildViews() {\n      if (!this.childViews.length) {\n        Array.from(this.objectGroup.getObjects()).forEach(object => {\n          this.findOrCreateCachedChildView(this.viewClass, object, this.options);\n        });\n      }\n      return this.childViews;\n    }\n    createNodes() {\n      const element = this.createContainerElement();\n      this.getChildViews().forEach(view => {\n        Array.from(view.getNodes()).forEach(node => {\n          element.appendChild(node);\n        });\n      });\n      return [element];\n    }\n    createContainerElement() {\n      let depth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.objectGroup.getDepth();\n      return this.getChildViews()[0].createContainerElement(depth);\n    }\n  }\n\n  /*! @license DOMPurify 3.2.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.7/LICENSE */\n\n  const {\n    entries,\n    setPrototypeOf,\n    isFrozen,\n    getPrototypeOf,\n    getOwnPropertyDescriptor\n  } = Object;\n  let {\n    freeze,\n    seal,\n    create\n  } = Object; // eslint-disable-line import/no-mutable-exports\n  let {\n    apply,\n    construct\n  } = typeof Reflect !== 'undefined' && Reflect;\n  if (!freeze) {\n    freeze = function freeze(x) {\n      return x;\n    };\n  }\n  if (!seal) {\n    seal = function seal(x) {\n      return x;\n    };\n  }\n  if (!apply) {\n    apply = function apply(func, thisArg) {\n      for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n        args[_key - 2] = arguments[_key];\n      }\n      return func.apply(thisArg, args);\n    };\n  }\n  if (!construct) {\n    construct = function construct(Func) {\n      for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n        args[_key2 - 1] = arguments[_key2];\n      }\n      return new Func(...args);\n    };\n  }\n  const arrayForEach = unapply(Array.prototype.forEach);\n  const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\n  const arrayPop = unapply(Array.prototype.pop);\n  const arrayPush = unapply(Array.prototype.push);\n  const arraySplice = unapply(Array.prototype.splice);\n  const stringToLowerCase = unapply(String.prototype.toLowerCase);\n  const stringToString = unapply(String.prototype.toString);\n  const stringMatch = unapply(String.prototype.match);\n  const stringReplace = unapply(String.prototype.replace);\n  const stringIndexOf = unapply(String.prototype.indexOf);\n  const stringTrim = unapply(String.prototype.trim);\n  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\n  const regExpTest = unapply(RegExp.prototype.test);\n  const typeErrorCreate = unconstruct(TypeError);\n  /**\n   * Creates a new function that calls the given function with a specified thisArg and arguments.\n   *\n   * @param func - The function to be wrapped and called.\n   * @returns A new function that calls the given function with a specified thisArg and arguments.\n   */\n  function unapply(func) {\n    return function (thisArg) {\n      if (thisArg instanceof RegExp) {\n        thisArg.lastIndex = 0;\n      }\n      for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n        args[_key3 - 1] = arguments[_key3];\n      }\n      return apply(func, thisArg, args);\n    };\n  }\n  /**\n   * Creates a new function that constructs an instance of the given constructor function with the provided arguments.\n   *\n   * @param func - The constructor function to be wrapped and called.\n   * @returns A new function that constructs an instance of the given constructor function with the provided arguments.\n   */\n  function unconstruct(Func) {\n    return function () {\n      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {\n        args[_key4] = arguments[_key4];\n      }\n      return construct(Func, args);\n    };\n  }\n  /**\n   * Add properties to a lookup table\n   *\n   * @param set - The set to which elements will be added.\n   * @param array - The array containing elements to be added to the set.\n   * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.\n   * @returns The modified set with added elements.\n   */\n  function addToSet(set, array) {\n    let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;\n    if (setPrototypeOf) {\n      // Make 'in' and truthy checks like Boolean(set.constructor)\n      // independent of any properties defined on Object.prototype.\n      // Prevent prototype setters from intercepting set as a this value.\n      setPrototypeOf(set, null);\n    }\n    let l = array.length;\n    while (l--) {\n      let element = array[l];\n      if (typeof element === 'string') {\n        const lcElement = transformCaseFunc(element);\n        if (lcElement !== element) {\n          // Config presets (e.g. tags.js, attrs.js) are immutable.\n          if (!isFrozen(array)) {\n            array[l] = lcElement;\n          }\n          element = lcElement;\n        }\n      }\n      set[element] = true;\n    }\n    return set;\n  }\n  /**\n   * Clean up an array to harden against CSPP\n   *\n   * @param array - The array to be cleaned.\n   * @returns The cleaned version of the array\n   */\n  function cleanArray(array) {\n    for (let index = 0; index < array.length; index++) {\n      const isPropertyExist = objectHasOwnProperty(array, index);\n      if (!isPropertyExist) {\n        array[index] = null;\n      }\n    }\n    return array;\n  }\n  /**\n   * Shallow clone an object\n   *\n   * @param object - The object to be cloned.\n   * @returns A new object that copies the original.\n   */\n  function clone(object) {\n    const newObject = create(null);\n    for (const [property, value] of entries(object)) {\n      const isPropertyExist = objectHasOwnProperty(object, property);\n      if (isPropertyExist) {\n        if (Array.isArray(value)) {\n          newObject[property] = cleanArray(value);\n        } else if (value && typeof value === 'object' && value.constructor === Object) {\n          newObject[property] = clone(value);\n        } else {\n          newObject[property] = value;\n        }\n      }\n    }\n    return newObject;\n  }\n  /**\n   * This method automatically checks if the prop is function or getter and behaves accordingly.\n   *\n   * @param object - The object to look up the getter function in its prototype chain.\n   * @param prop - The property name for which to find the getter function.\n   * @returns The getter function found in the prototype chain or a fallback function.\n   */\n  function lookupGetter(object, prop) {\n    while (object !== null) {\n      const desc = getOwnPropertyDescriptor(object, prop);\n      if (desc) {\n        if (desc.get) {\n          return unapply(desc.get);\n        }\n        if (typeof desc.value === 'function') {\n          return unapply(desc.value);\n        }\n      }\n      object = getPrototypeOf(object);\n    }\n    function fallbackValue() {\n      return null;\n    }\n    return fallbackValue;\n  }\n  const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);\n  const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'slot', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\n  const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);\n  // List of SVG elements that are disallowed by default.\n  // We still need to know them so that we can do namespace\n  // checks properly in case one wants to add them to\n  // allow-list.\n  const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);\n  const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);\n  // Similarly to SVG, we want to know all MathML elements,\n  // even those that we disallow by default.\n  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\n  const text = freeze(['#text']);\n  const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);\n  const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);\n  const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);\n  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n  // eslint-disable-next-line unicorn/better-regex\n  const MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\n  const ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\n  const TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\n  const DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\n  const ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\n  const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n  );\n  const IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\n  const ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n  );\n  const DOCTYPE_NAME = seal(/^html$/i);\n  const CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n  var EXPRESSIONS = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    ARIA_ATTR: ARIA_ATTR,\n    ATTR_WHITESPACE: ATTR_WHITESPACE,\n    CUSTOM_ELEMENT: CUSTOM_ELEMENT,\n    DATA_ATTR: DATA_ATTR,\n    DOCTYPE_NAME: DOCTYPE_NAME,\n    ERB_EXPR: ERB_EXPR,\n    IS_ALLOWED_URI: IS_ALLOWED_URI,\n    IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,\n    MUSTACHE_EXPR: MUSTACHE_EXPR,\n    TMPLIT_EXPR: TMPLIT_EXPR\n  });\n\n  /* eslint-disable @typescript-eslint/indent */\n  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType\n  const NODE_TYPE = {\n    element: 1,\n    attribute: 2,\n    text: 3,\n    cdataSection: 4,\n    entityReference: 5,\n    // Deprecated\n    entityNode: 6,\n    // Deprecated\n    progressingInstruction: 7,\n    comment: 8,\n    document: 9,\n    documentType: 10,\n    documentFragment: 11,\n    notation: 12 // Deprecated\n  };\n  const getGlobal = function getGlobal() {\n    return typeof window === 'undefined' ? null : window;\n  };\n  /**\n   * Creates a no-op policy for internal use only.\n   * Don't export this function outside this module!\n   * @param trustedTypes The policy factory.\n   * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).\n   * @return The policy created (or null, if Trusted Types\n   * are not supported or creating the policy failed).\n   */\n  const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {\n    if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {\n      return null;\n    }\n    // Allow the callers to control the unique policy name\n    // by adding a data-tt-policy-suffix to the script element with the DOMPurify.\n    // Policy creation with duplicate names throws in Trusted Types.\n    let suffix = null;\n    const ATTR_NAME = 'data-tt-policy-suffix';\n    if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {\n      suffix = purifyHostElement.getAttribute(ATTR_NAME);\n    }\n    const policyName = 'dompurify' + (suffix ? '#' + suffix : '');\n    try {\n      return trustedTypes.createPolicy(policyName, {\n        createHTML(html) {\n          return html;\n        },\n        createScriptURL(scriptUrl) {\n          return scriptUrl;\n        }\n      });\n    } catch (_) {\n      // Policy creation failed (most likely another DOMPurify script has\n      // already run). Skip creating the policy, as this will only cause errors\n      // if TT are enforced.\n      console.warn('TrustedTypes policy ' + policyName + ' could not be created.');\n      return null;\n    }\n  };\n  const _createHooksMap = function _createHooksMap() {\n    return {\n      afterSanitizeAttributes: [],\n      afterSanitizeElements: [],\n      afterSanitizeShadowDOM: [],\n      beforeSanitizeAttributes: [],\n      beforeSanitizeElements: [],\n      beforeSanitizeShadowDOM: [],\n      uponSanitizeAttribute: [],\n      uponSanitizeElement: [],\n      uponSanitizeShadowNode: []\n    };\n  };\n  function createDOMPurify() {\n    let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n    const DOMPurify = root => createDOMPurify(root);\n    DOMPurify.version = '3.2.7';\n    DOMPurify.removed = [];\n    if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {\n      // Not running in a browser, provide a factory function\n      // so that you can pass your own Window\n      DOMPurify.isSupported = false;\n      return DOMPurify;\n    }\n    let {\n      document\n    } = window;\n    const originalDocument = document;\n    const currentScript = originalDocument.currentScript;\n    const {\n      DocumentFragment,\n      HTMLTemplateElement,\n      Node,\n      Element,\n      NodeFilter,\n      NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,\n      HTMLFormElement,\n      DOMParser,\n      trustedTypes\n    } = window;\n    const ElementPrototype = Element.prototype;\n    const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');\n    const remove = lookupGetter(ElementPrototype, 'remove');\n    const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');\n    const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');\n    const getParentNode = lookupGetter(ElementPrototype, 'parentNode');\n    // As per issue #47, the web-components registry is inherited by a\n    // new document created via createHTMLDocument. As per the spec\n    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)\n    // a new empty registry is used when creating a template contents owner\n    // document, so we use that as our parent document to ensure nothing\n    // is inherited.\n    if (typeof HTMLTemplateElement === 'function') {\n      const template = document.createElement('template');\n      if (template.content && template.content.ownerDocument) {\n        document = template.content.ownerDocument;\n      }\n    }\n    let trustedTypesPolicy;\n    let emptyHTML = '';\n    const {\n      implementation,\n      createNodeIterator,\n      createDocumentFragment,\n      getElementsByTagName\n    } = document;\n    const {\n      importNode\n    } = originalDocument;\n    let hooks = _createHooksMap();\n    /**\n     * Expose whether this browser supports running the full DOMPurify.\n     */\n    DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;\n    const {\n      MUSTACHE_EXPR,\n      ERB_EXPR,\n      TMPLIT_EXPR,\n      DATA_ATTR,\n      ARIA_ATTR,\n      IS_SCRIPT_OR_DATA,\n      ATTR_WHITESPACE,\n      CUSTOM_ELEMENT\n    } = EXPRESSIONS;\n    let {\n      IS_ALLOWED_URI: IS_ALLOWED_URI$1\n    } = EXPRESSIONS;\n    /**\n     * We consider the elements and attributes below to be safe. Ideally\n     * don't add any new ones but feel free to remove unwanted ones.\n     */\n    /* allowed element names */\n    let ALLOWED_TAGS = null;\n    const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);\n    /* Allowed attribute names */\n    let ALLOWED_ATTR = null;\n    const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);\n    /*\n     * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.\n     * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)\n     * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)\n     * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.\n     */\n    let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {\n      tagNameCheck: {\n        writable: true,\n        configurable: false,\n        enumerable: true,\n        value: null\n      },\n      attributeNameCheck: {\n        writable: true,\n        configurable: false,\n        enumerable: true,\n        value: null\n      },\n      allowCustomizedBuiltInElements: {\n        writable: true,\n        configurable: false,\n        enumerable: true,\n        value: false\n      }\n    }));\n    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */\n    let FORBID_TAGS = null;\n    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */\n    let FORBID_ATTR = null;\n    /* Decide if ARIA attributes are okay */\n    let ALLOW_ARIA_ATTR = true;\n    /* Decide if custom data attributes are okay */\n    let ALLOW_DATA_ATTR = true;\n    /* Decide if unknown protocols are okay */\n    let ALLOW_UNKNOWN_PROTOCOLS = false;\n    /* Decide if self-closing tags in attributes are allowed.\n     * Usually removed due to a mXSS issue in jQuery 3.0 */\n    let ALLOW_SELF_CLOSE_IN_ATTR = true;\n    /* Output should be safe for common template engines.\n     * This means, DOMPurify removes data attributes, mustaches and ERB\n     */\n    let SAFE_FOR_TEMPLATES = false;\n    /* Output should be safe even for XML used within HTML and alike.\n     * This means, DOMPurify removes comments when containing risky content.\n     */\n    let SAFE_FOR_XML = true;\n    /* Decide if document with <html>... should be returned */\n    let WHOLE_DOCUMENT = false;\n    /* Track whether config is already set on this instance of DOMPurify. */\n    let SET_CONFIG = false;\n    /* Decide if all elements (e.g. style, script) must be children of\n     * document.body. By default, browsers might move them to document.head */\n    let FORCE_BODY = false;\n    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html\n     * string (or a TrustedHTML object if Trusted Types are supported).\n     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead\n     */\n    let RETURN_DOM = false;\n    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html\n     * string  (or a TrustedHTML object if Trusted Types are supported) */\n    let RETURN_DOM_FRAGMENT = false;\n    /* Try to return a Trusted Type object instead of a string, return a string in\n     * case Trusted Types are not supported  */\n    let RETURN_TRUSTED_TYPE = false;\n    /* Output should be free from DOM clobbering attacks?\n     * This sanitizes markups named with colliding, clobberable built-in DOM APIs.\n     */\n    let SANITIZE_DOM = true;\n    /* Achieve full DOM Clobbering protection by isolating the namespace of named\n     * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.\n     *\n     * HTML/DOM spec rules that enable DOM Clobbering:\n     *   - Named Access on Window (§7.3.3)\n     *   - DOM Tree Accessors (§3.1.5)\n     *   - Form Element Parent-Child Relations (§4.10.3)\n     *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)\n     *   - HTMLCollection (§4.2.10.2)\n     *\n     * Namespace isolation is implemented by prefixing `id` and `name` attributes\n     * with a constant string, i.e., `user-content-`\n     */\n    let SANITIZE_NAMED_PROPS = false;\n    const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';\n    /* Keep element content when removing element? */\n    let KEEP_CONTENT = true;\n    /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead\n     * of importing it into a new Document and returning a sanitized copy */\n    let IN_PLACE = false;\n    /* Allow usage of profiles like html, svg and mathMl */\n    let USE_PROFILES = {};\n    /* Tags to ignore content of when KEEP_CONTENT is true */\n    let FORBID_CONTENTS = null;\n    const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);\n    /* Tags that are safe for data: URIs */\n    let DATA_URI_TAGS = null;\n    const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);\n    /* Attributes safe for values like \"javascript:\" */\n    let URI_SAFE_ATTRIBUTES = null;\n    const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);\n    const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n    const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n    const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\n    /* Document namespace */\n    let NAMESPACE = HTML_NAMESPACE;\n    let IS_EMPTY_INPUT = false;\n    /* Allowed XHTML+XML namespaces */\n    let ALLOWED_NAMESPACES = null;\n    const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);\n    let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);\n    let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);\n    // Certain elements are allowed in both SVG and HTML\n    // namespace. We need to specify them explicitly\n    // so that they don't get erroneously deleted from\n    // HTML namespace.\n    const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);\n    /* Parsing of strict XHTML documents */\n    let PARSER_MEDIA_TYPE = null;\n    const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];\n    const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';\n    let transformCaseFunc = null;\n    /* Keep a reference to config to pass to hooks */\n    let CONFIG = null;\n    /* Ideally, do not touch anything below this line */\n    /* ______________________________________________ */\n    const formElement = document.createElement('form');\n    const isRegexOrFunction = function isRegexOrFunction(testValue) {\n      return testValue instanceof RegExp || testValue instanceof Function;\n    };\n    /**\n     * _parseConfig\n     *\n     * @param cfg optional config literal\n     */\n    // eslint-disable-next-line complexity\n    const _parseConfig = function _parseConfig() {\n      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      if (CONFIG && CONFIG === cfg) {\n        return;\n      }\n      /* Shield configuration object from tampering */\n      if (!cfg || typeof cfg !== 'object') {\n        cfg = {};\n      }\n      /* Shield configuration object from prototype pollution */\n      cfg = clone(cfg);\n      PARSER_MEDIA_TYPE =\n      // eslint-disable-next-line unicorn/prefer-includes\n      SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;\n      // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.\n      transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;\n      /* Set configuration parameters */\n      ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;\n      ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;\n      ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;\n      URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;\n      DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;\n      FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;\n      FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});\n      FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});\n      USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;\n      ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true\n      ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true\n      ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false\n      ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true\n      SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false\n      SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true\n      WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false\n      RETURN_DOM = cfg.RETURN_DOM || false; // Default false\n      RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false\n      RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false\n      FORCE_BODY = cfg.FORCE_BODY || false; // Default false\n      SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true\n      SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false\n      KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true\n      IN_PLACE = cfg.IN_PLACE || false; // Default false\n      IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;\n      NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;\n      MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;\n      HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;\n      CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};\n      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;\n      }\n      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {\n        CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;\n      }\n      if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {\n        CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;\n      }\n      if (SAFE_FOR_TEMPLATES) {\n        ALLOW_DATA_ATTR = false;\n      }\n      if (RETURN_DOM_FRAGMENT) {\n        RETURN_DOM = true;\n      }\n      /* Parse profile info */\n      if (USE_PROFILES) {\n        ALLOWED_TAGS = addToSet({}, text);\n        ALLOWED_ATTR = [];\n        if (USE_PROFILES.html === true) {\n          addToSet(ALLOWED_TAGS, html$1);\n          addToSet(ALLOWED_ATTR, html);\n        }\n        if (USE_PROFILES.svg === true) {\n          addToSet(ALLOWED_TAGS, svg$1);\n          addToSet(ALLOWED_ATTR, svg);\n          addToSet(ALLOWED_ATTR, xml);\n        }\n        if (USE_PROFILES.svgFilters === true) {\n          addToSet(ALLOWED_TAGS, svgFilters);\n          addToSet(ALLOWED_ATTR, svg);\n          addToSet(ALLOWED_ATTR, xml);\n        }\n        if (USE_PROFILES.mathMl === true) {\n          addToSet(ALLOWED_TAGS, mathMl$1);\n          addToSet(ALLOWED_ATTR, mathMl);\n          addToSet(ALLOWED_ATTR, xml);\n        }\n      }\n      /* Merge configuration parameters */\n      if (cfg.ADD_TAGS) {\n        if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n          ALLOWED_TAGS = clone(ALLOWED_TAGS);\n        }\n        addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n      }\n      if (cfg.ADD_ATTR) {\n        if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n          ALLOWED_ATTR = clone(ALLOWED_ATTR);\n        }\n        addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n      }\n      if (cfg.ADD_URI_SAFE_ATTR) {\n        addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);\n      }\n      if (cfg.FORBID_CONTENTS) {\n        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n          FORBID_CONTENTS = clone(FORBID_CONTENTS);\n        }\n        addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);\n      }\n      /* Add #text in case KEEP_CONTENT is set to true */\n      if (KEEP_CONTENT) {\n        ALLOWED_TAGS['#text'] = true;\n      }\n      /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */\n      if (WHOLE_DOCUMENT) {\n        addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);\n      }\n      /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */\n      if (ALLOWED_TAGS.table) {\n        addToSet(ALLOWED_TAGS, ['tbody']);\n        delete FORBID_TAGS.tbody;\n      }\n      if (cfg.TRUSTED_TYPES_POLICY) {\n        if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.');\n        }\n        if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.');\n        }\n        // Overwrite existing TrustedTypes policy.\n        trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;\n        // Sign local variables required by `sanitize`.\n        emptyHTML = trustedTypesPolicy.createHTML('');\n      } else {\n        // Uninitialized policy, attempt to initialize the internal dompurify policy.\n        if (trustedTypesPolicy === undefined) {\n          trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);\n        }\n        // If creating the internal policy succeeded sign internal variables.\n        if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {\n          emptyHTML = trustedTypesPolicy.createHTML('');\n        }\n      }\n      // Prevent further manipulation of configuration.\n      // Not available in IE8, Safari 5, etc.\n      if (freeze) {\n        freeze(cfg);\n      }\n      CONFIG = cfg;\n    };\n    /* Keep track of all possible SVG and MathML tags\n     * so that we can perform the namespace checks\n     * correctly. */\n    const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);\n    const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);\n    /**\n     * @param element a DOM element whose namespace is being checked\n     * @returns Return false if the element has a\n     *  namespace that a spec-compliant parser would never\n     *  return. Return true otherwise.\n     */\n    const _checkValidNamespace = function _checkValidNamespace(element) {\n      let parent = getParentNode(element);\n      // In JSDOM, if we're inside shadow DOM, then parentNode\n      // can be null. We just simulate parent in this case.\n      if (!parent || !parent.tagName) {\n        parent = {\n          namespaceURI: NAMESPACE,\n          tagName: 'template'\n        };\n      }\n      const tagName = stringToLowerCase(element.tagName);\n      const parentTagName = stringToLowerCase(parent.tagName);\n      if (!ALLOWED_NAMESPACES[element.namespaceURI]) {\n        return false;\n      }\n      if (element.namespaceURI === SVG_NAMESPACE) {\n        // The only way to switch from HTML namespace to SVG\n        // is via <svg>. If it happens via any other tag, then\n        // it should be killed.\n        if (parent.namespaceURI === HTML_NAMESPACE) {\n          return tagName === 'svg';\n        }\n        // The only way to switch from MathML to SVG is via`\n        // svg if parent is either <annotation-xml> or MathML\n        // text integration points.\n        if (parent.namespaceURI === MATHML_NAMESPACE) {\n          return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);\n        }\n        // We only allow elements that are defined in SVG\n        // spec. All others are disallowed in SVG namespace.\n        return Boolean(ALL_SVG_TAGS[tagName]);\n      }\n      if (element.namespaceURI === MATHML_NAMESPACE) {\n        // The only way to switch from HTML namespace to MathML\n        // is via <math>. If it happens via any other tag, then\n        // it should be killed.\n        if (parent.namespaceURI === HTML_NAMESPACE) {\n          return tagName === 'math';\n        }\n        // The only way to switch from SVG to MathML is via\n        // <math> and HTML integration points\n        if (parent.namespaceURI === SVG_NAMESPACE) {\n          return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];\n        }\n        // We only allow elements that are defined in MathML\n        // spec. All others are disallowed in MathML namespace.\n        return Boolean(ALL_MATHML_TAGS[tagName]);\n      }\n      if (element.namespaceURI === HTML_NAMESPACE) {\n        // The only way to switch from SVG to HTML is via\n        // HTML integration points, and from MathML to HTML\n        // is via MathML text integration points\n        if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {\n          return false;\n        }\n        if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {\n          return false;\n        }\n        // We disallow tags that are specific for MathML\n        // or SVG and should never appear in HTML namespace\n        return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);\n      }\n      // For XHTML and XML documents that support custom namespaces\n      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {\n        return true;\n      }\n      // The code should never reach this place (this means\n      // that the element somehow got namespace that is not\n      // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).\n      // Return false just in case.\n      return false;\n    };\n    /**\n     * _forceRemove\n     *\n     * @param node a DOM node\n     */\n    const _forceRemove = function _forceRemove(node) {\n      arrayPush(DOMPurify.removed, {\n        element: node\n      });\n      try {\n        // eslint-disable-next-line unicorn/prefer-dom-node-remove\n        getParentNode(node).removeChild(node);\n      } catch (_) {\n        remove(node);\n      }\n    };\n    /**\n     * _removeAttribute\n     *\n     * @param name an Attribute name\n     * @param element a DOM node\n     */\n    const _removeAttribute = function _removeAttribute(name, element) {\n      try {\n        arrayPush(DOMPurify.removed, {\n          attribute: element.getAttributeNode(name),\n          from: element\n        });\n      } catch (_) {\n        arrayPush(DOMPurify.removed, {\n          attribute: null,\n          from: element\n        });\n      }\n      element.removeAttribute(name);\n      // We void attribute values for unremovable \"is\" attributes\n      if (name === 'is') {\n        if (RETURN_DOM || RETURN_DOM_FRAGMENT) {\n          try {\n            _forceRemove(element);\n          } catch (_) {}\n        } else {\n          try {\n            element.setAttribute(name, '');\n          } catch (_) {}\n        }\n      }\n    };\n    /**\n     * _initDocument\n     *\n     * @param dirty - a string of dirty markup\n     * @return a DOM, filled with the dirty markup\n     */\n    const _initDocument = function _initDocument(dirty) {\n      /* Create a HTML document */\n      let doc = null;\n      let leadingWhitespace = null;\n      if (FORCE_BODY) {\n        dirty = '<remove></remove>' + dirty;\n      } else {\n        /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */\n        const matches = stringMatch(dirty, /^[\\r\\n\\t ]+/);\n        leadingWhitespace = matches && matches[0];\n      }\n      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {\n        // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)\n        dirty = '<html xmlns=\"http://www.w3.org/1999/xhtml\"><head></head><body>' + dirty + '</body></html>';\n      }\n      const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;\n      /*\n       * Use the DOMParser API by default, fallback later if needs be\n       * DOMParser not work for svg when has multiple root element.\n       */\n      if (NAMESPACE === HTML_NAMESPACE) {\n        try {\n          doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);\n        } catch (_) {}\n      }\n      /* Use createHTMLDocument in case DOMParser is not available */\n      if (!doc || !doc.documentElement) {\n        doc = implementation.createDocument(NAMESPACE, 'template', null);\n        try {\n          doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;\n        } catch (_) {\n          // Syntax error if dirtyPayload is invalid xml\n        }\n      }\n      const body = doc.body || doc.documentElement;\n      if (dirty && leadingWhitespace) {\n        body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);\n      }\n      /* Work on whole document or just its body */\n      if (NAMESPACE === HTML_NAMESPACE) {\n        return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];\n      }\n      return WHOLE_DOCUMENT ? doc.documentElement : body;\n    };\n    /**\n     * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.\n     *\n     * @param root The root element or node to start traversing on.\n     * @return The created NodeIterator\n     */\n    const _createNodeIterator = function _createNodeIterator(root) {\n      return createNodeIterator.call(root.ownerDocument || root, root,\n      // eslint-disable-next-line no-bitwise\n      NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);\n    };\n    /**\n     * _isClobbered\n     *\n     * @param element element to check for clobbering attacks\n     * @return true if clobbered, false if safe\n     */\n    const _isClobbered = function _isClobbered(element) {\n      return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');\n    };\n    /**\n     * Checks whether the given object is a DOM node.\n     *\n     * @param value object to check whether it's a DOM node\n     * @return true is object is a DOM node\n     */\n    const _isNode = function _isNode(value) {\n      return typeof Node === 'function' && value instanceof Node;\n    };\n    function _executeHooks(hooks, currentNode, data) {\n      arrayForEach(hooks, hook => {\n        hook.call(DOMPurify, currentNode, data, CONFIG);\n      });\n    }\n    /**\n     * _sanitizeElements\n     *\n     * @protect nodeName\n     * @protect textContent\n     * @protect removeChild\n     * @param currentNode to check for permission to exist\n     * @return true if node was killed, false if left alive\n     */\n    const _sanitizeElements = function _sanitizeElements(currentNode) {\n      let content = null;\n      /* Execute a hook if present */\n      _executeHooks(hooks.beforeSanitizeElements, currentNode, null);\n      /* Check if element is clobbered or can clobber */\n      if (_isClobbered(currentNode)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n      /* Now let's check the element's type and name */\n      const tagName = transformCaseFunc(currentNode.nodeName);\n      /* Execute a hook if present */\n      _executeHooks(hooks.uponSanitizeElement, currentNode, {\n        tagName,\n        allowedTags: ALLOWED_TAGS\n      });\n      /* Detect mXSS attempts abusing namespace confusion */\n      if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\\w!]/g, currentNode.textContent)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n      /* Remove any occurrence of processing instructions */\n      if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {\n        _forceRemove(currentNode);\n        return true;\n      }\n      /* Remove any kind of possibly harmful comments */\n      if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\\w]/g, currentNode.data)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n      /* Remove element if anything forbids its presence */\n      if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n        /* Check if we have a custom element to handle */\n        if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {\n          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {\n            return false;\n          }\n          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {\n            return false;\n          }\n        }\n        /* Keep content except for bad-listed elements */\n        if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {\n          const parentNode = getParentNode(currentNode) || currentNode.parentNode;\n          const childNodes = getChildNodes(currentNode) || currentNode.childNodes;\n          if (childNodes && parentNode) {\n            const childCount = childNodes.length;\n            for (let i = childCount - 1; i >= 0; --i) {\n              const childClone = cloneNode(childNodes[i], true);\n              childClone.__removalCount = (currentNode.__removalCount || 0) + 1;\n              parentNode.insertBefore(childClone, getNextSibling(currentNode));\n            }\n          }\n        }\n        _forceRemove(currentNode);\n        return true;\n      }\n      /* Check whether element has a valid namespace */\n      if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n      /* Make sure that older browsers don't get fallback-tag mXSS */\n      if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\\/no(script|embed|frames)/i, currentNode.innerHTML)) {\n        _forceRemove(currentNode);\n        return true;\n      }\n      /* Sanitize element content to be template-safe */\n      if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {\n        /* Get the element's text content */\n        content = currentNode.textContent;\n        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {\n          content = stringReplace(content, expr, ' ');\n        });\n        if (currentNode.textContent !== content) {\n          arrayPush(DOMPurify.removed, {\n            element: currentNode.cloneNode()\n          });\n          currentNode.textContent = content;\n        }\n      }\n      /* Execute a hook if present */\n      _executeHooks(hooks.afterSanitizeElements, currentNode, null);\n      return false;\n    };\n    /**\n     * _isValidAttribute\n     *\n     * @param lcTag Lowercase tag name of containing element.\n     * @param lcName Lowercase attribute name.\n     * @param value Attribute value.\n     * @return Returns true if `value` is valid, otherwise false.\n     */\n    // eslint-disable-next-line complexity\n    const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {\n      /* Make sure attribute cannot clobber */\n      if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {\n        return false;\n      }\n      /* Allow valid data-* attributes: At least one character after \"-\"\n          (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)\n          XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)\n          We don't need to check the value; it's always URI safe. */\n      if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ;else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ;else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {\n        if (\n        // First condition does a very basic check if a) it's basically a valid custom element tagname AND\n        // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n        // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck\n        _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) ||\n        // Alternative, second condition checks if it's an `is`-attribute, AND\n        // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n        lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ;else {\n          return false;\n        }\n        /* Check value is safe. First, is attr inert? If so, is safe */\n      } else if (URI_SAFE_ATTRIBUTES[lcName]) ;else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ;else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ;else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ;else if (value) {\n        return false;\n      } else ;\n      return true;\n    };\n    /**\n     * _isBasicCustomElement\n     * checks if at least one dash is included in tagName, and it's not the first char\n     * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name\n     *\n     * @param tagName name of the tag of the node to sanitize\n     * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.\n     */\n    const _isBasicCustomElement = function _isBasicCustomElement(tagName) {\n      return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);\n    };\n    /**\n     * _sanitizeAttributes\n     *\n     * @protect attributes\n     * @protect nodeName\n     * @protect removeAttribute\n     * @protect setAttribute\n     *\n     * @param currentNode to sanitize\n     */\n    const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {\n      /* Execute a hook if present */\n      _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);\n      const {\n        attributes\n      } = currentNode;\n      /* Check if we have attributes; if not we might have a text node */\n      if (!attributes || _isClobbered(currentNode)) {\n        return;\n      }\n      const hookEvent = {\n        attrName: '',\n        attrValue: '',\n        keepAttr: true,\n        allowedAttributes: ALLOWED_ATTR,\n        forceKeepAttr: undefined\n      };\n      let l = attributes.length;\n      /* Go backwards over all attributes; safely remove bad ones */\n      while (l--) {\n        const attr = attributes[l];\n        const {\n          name,\n          namespaceURI,\n          value: attrValue\n        } = attr;\n        const lcName = transformCaseFunc(name);\n        const initValue = attrValue;\n        let value = name === 'value' ? initValue : stringTrim(initValue);\n        /* Execute a hook if present */\n        hookEvent.attrName = lcName;\n        hookEvent.attrValue = value;\n        hookEvent.keepAttr = true;\n        hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set\n        _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);\n        value = hookEvent.attrValue;\n        /* Full DOM Clobbering protection via namespace isolation,\n         * Prefix id and name attributes with `user-content-`\n         */\n        if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {\n          // Remove the attribute with this value\n          _removeAttribute(name, currentNode);\n          // Prefix the value and later re-create the attribute with the sanitized value\n          value = SANITIZE_NAMED_PROPS_PREFIX + value;\n        }\n        /* Work around a security issue with comments inside attributes */\n        if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\\/(style|title|textarea)/i, value)) {\n          _removeAttribute(name, currentNode);\n          continue;\n        }\n        /* Make sure we cannot easily use animated hrefs, even if animations are allowed */\n        if (lcName === 'attributename' && stringMatch(value, 'href')) {\n          _removeAttribute(name, currentNode);\n          continue;\n        }\n        /* Did the hooks approve of the attribute? */\n        if (hookEvent.forceKeepAttr) {\n          continue;\n        }\n        /* Did the hooks approve of the attribute? */\n        if (!hookEvent.keepAttr) {\n          _removeAttribute(name, currentNode);\n          continue;\n        }\n        /* Work around a security issue in jQuery 3.0 */\n        if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\\/>/i, value)) {\n          _removeAttribute(name, currentNode);\n          continue;\n        }\n        /* Sanitize attribute content to be template-safe */\n        if (SAFE_FOR_TEMPLATES) {\n          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {\n            value = stringReplace(value, expr, ' ');\n          });\n        }\n        /* Is `value` valid for this attribute? */\n        const lcTag = transformCaseFunc(currentNode.nodeName);\n        if (!_isValidAttribute(lcTag, lcName, value)) {\n          _removeAttribute(name, currentNode);\n          continue;\n        }\n        /* Handle attributes that require Trusted Types */\n        if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {\n          if (namespaceURI) ;else {\n            switch (trustedTypes.getAttributeType(lcTag, lcName)) {\n              case 'TrustedHTML':\n                {\n                  value = trustedTypesPolicy.createHTML(value);\n                  break;\n                }\n              case 'TrustedScriptURL':\n                {\n                  value = trustedTypesPolicy.createScriptURL(value);\n                  break;\n                }\n            }\n          }\n        }\n        /* Handle invalid data-* attribute set by try-catching it */\n        if (value !== initValue) {\n          try {\n            if (namespaceURI) {\n              currentNode.setAttributeNS(namespaceURI, name, value);\n            } else {\n              /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. \"x-schema\". */\n              currentNode.setAttribute(name, value);\n            }\n            if (_isClobbered(currentNode)) {\n              _forceRemove(currentNode);\n            } else {\n              arrayPop(DOMPurify.removed);\n            }\n          } catch (_) {\n            _removeAttribute(name, currentNode);\n          }\n        }\n      }\n      /* Execute a hook if present */\n      _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);\n    };\n    /**\n     * _sanitizeShadowDOM\n     *\n     * @param fragment to iterate over recursively\n     */\n    const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {\n      let shadowNode = null;\n      const shadowIterator = _createNodeIterator(fragment);\n      /* Execute a hook if present */\n      _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);\n      while (shadowNode = shadowIterator.nextNode()) {\n        /* Execute a hook if present */\n        _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);\n        /* Sanitize tags and elements */\n        _sanitizeElements(shadowNode);\n        /* Check attributes next */\n        _sanitizeAttributes(shadowNode);\n        /* Deep shadow DOM detected */\n        if (shadowNode.content instanceof DocumentFragment) {\n          _sanitizeShadowDOM(shadowNode.content);\n        }\n      }\n      /* Execute a hook if present */\n      _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);\n    };\n    // eslint-disable-next-line complexity\n    DOMPurify.sanitize = function (dirty) {\n      let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let body = null;\n      let importedNode = null;\n      let currentNode = null;\n      let returnNode = null;\n      /* Make sure we have a string to sanitize.\n        DO NOT return early, as this will return the wrong type if\n        the user has requested a DOM object rather than a string */\n      IS_EMPTY_INPUT = !dirty;\n      if (IS_EMPTY_INPUT) {\n        dirty = '<!-->';\n      }\n      /* Stringify, in case dirty is an object */\n      if (typeof dirty !== 'string' && !_isNode(dirty)) {\n        if (typeof dirty.toString === 'function') {\n          dirty = dirty.toString();\n          if (typeof dirty !== 'string') {\n            throw typeErrorCreate('dirty is not a string, aborting');\n          }\n        } else {\n          throw typeErrorCreate('toString is not a function');\n        }\n      }\n      /* Return dirty HTML if DOMPurify cannot run */\n      if (!DOMPurify.isSupported) {\n        return dirty;\n      }\n      /* Assign config vars */\n      if (!SET_CONFIG) {\n        _parseConfig(cfg);\n      }\n      /* Clean up removed elements */\n      DOMPurify.removed = [];\n      /* Check if dirty is correctly typed for IN_PLACE */\n      if (typeof dirty === 'string') {\n        IN_PLACE = false;\n      }\n      if (IN_PLACE) {\n        /* Do some early pre-sanitization to avoid unsafe root nodes */\n        if (dirty.nodeName) {\n          const tagName = transformCaseFunc(dirty.nodeName);\n          if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n            throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');\n          }\n        }\n      } else if (dirty instanceof Node) {\n        /* If dirty is a DOM element, append to an empty document to avoid\n           elements being stripped by the parser */\n        body = _initDocument('<!---->');\n        importedNode = body.ownerDocument.importNode(dirty, true);\n        if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {\n          /* Node is already a body, use as is */\n          body = importedNode;\n        } else if (importedNode.nodeName === 'HTML') {\n          body = importedNode;\n        } else {\n          // eslint-disable-next-line unicorn/prefer-dom-node-append\n          body.appendChild(importedNode);\n        }\n      } else {\n        /* Exit directly if we have nothing to do */\n        if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&\n        // eslint-disable-next-line unicorn/prefer-includes\n        dirty.indexOf('<') === -1) {\n          return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;\n        }\n        /* Initialize the document to work on */\n        body = _initDocument(dirty);\n        /* Check we have a DOM node from the data */\n        if (!body) {\n          return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';\n        }\n      }\n      /* Remove first element node (ours) if FORCE_BODY is set */\n      if (body && FORCE_BODY) {\n        _forceRemove(body.firstChild);\n      }\n      /* Get node iterator */\n      const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);\n      /* Now start iterating over the created document */\n      while (currentNode = nodeIterator.nextNode()) {\n        /* Sanitize tags and elements */\n        _sanitizeElements(currentNode);\n        /* Check attributes next */\n        _sanitizeAttributes(currentNode);\n        /* Shadow DOM detected, sanitize it */\n        if (currentNode.content instanceof DocumentFragment) {\n          _sanitizeShadowDOM(currentNode.content);\n        }\n      }\n      /* If we sanitized `dirty` in-place, return it. */\n      if (IN_PLACE) {\n        return dirty;\n      }\n      /* Return sanitized string or DOM */\n      if (RETURN_DOM) {\n        if (RETURN_DOM_FRAGMENT) {\n          returnNode = createDocumentFragment.call(body.ownerDocument);\n          while (body.firstChild) {\n            // eslint-disable-next-line unicorn/prefer-dom-node-append\n            returnNode.appendChild(body.firstChild);\n          }\n        } else {\n          returnNode = body;\n        }\n        if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {\n          /*\n            AdoptNode() is not used because internal state is not reset\n            (e.g. the past names map of a HTMLFormElement), this is safe\n            in theory but we would rather not risk another attack vector.\n            The state that is cloned by importNode() is explicitly defined\n            by the specs.\n          */\n          returnNode = importNode.call(originalDocument, returnNode, true);\n        }\n        return returnNode;\n      }\n      let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;\n      /* Serialize doctype if allowed */\n      if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {\n        serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\\n' + serializedHTML;\n      }\n      /* Sanitize final string template-safe */\n      if (SAFE_FOR_TEMPLATES) {\n        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {\n          serializedHTML = stringReplace(serializedHTML, expr, ' ');\n        });\n      }\n      return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;\n    };\n    DOMPurify.setConfig = function () {\n      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      _parseConfig(cfg);\n      SET_CONFIG = true;\n    };\n    DOMPurify.clearConfig = function () {\n      CONFIG = null;\n      SET_CONFIG = false;\n    };\n    DOMPurify.isValidAttribute = function (tag, attr, value) {\n      /* Initialize shared config vars if necessary. */\n      if (!CONFIG) {\n        _parseConfig({});\n      }\n      const lcTag = transformCaseFunc(tag);\n      const lcName = transformCaseFunc(attr);\n      return _isValidAttribute(lcTag, lcName, value);\n    };\n    DOMPurify.addHook = function (entryPoint, hookFunction) {\n      if (typeof hookFunction !== 'function') {\n        return;\n      }\n      arrayPush(hooks[entryPoint], hookFunction);\n    };\n    DOMPurify.removeHook = function (entryPoint, hookFunction) {\n      if (hookFunction !== undefined) {\n        const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);\n        return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];\n      }\n      return arrayPop(hooks[entryPoint]);\n    };\n    DOMPurify.removeHooks = function (entryPoint) {\n      hooks[entryPoint] = [];\n    };\n    DOMPurify.removeAllHooks = function () {\n      hooks = _createHooksMap();\n    };\n    return DOMPurify;\n  }\n  var purify = createDOMPurify();\n\n  purify.addHook(\"uponSanitizeAttribute\", function (node, data) {\n    if (data.attrName === \"data-trix-serialized-attributes\") {\n      data.keepAttr = false;\n      return;\n    }\n    const allowedAttributePattern = /^data-trix-/;\n    if (allowedAttributePattern.test(data.attrName)) {\n      data.forceKeepAttr = true;\n    }\n  });\n  const DEFAULT_ALLOWED_ATTRIBUTES = \"style href src width height language class\".split(\" \");\n  const DEFAULT_FORBIDDEN_PROTOCOLS = \"javascript:\".split(\" \");\n  const DEFAULT_FORBIDDEN_ELEMENTS = \"script iframe form noscript\".split(\" \");\n  class HTMLSanitizer extends BasicObject {\n    static setHTML(element, html, options) {\n      const sanitizedElement = new this(html, options).sanitize();\n      const sanitizedHtml = sanitizedElement.getHTML ? sanitizedElement.getHTML() : sanitizedElement.outerHTML;\n      element.innerHTML = sanitizedHtml;\n    }\n    static sanitize(html, options) {\n      const sanitizer = new this(html, options);\n      sanitizer.sanitize();\n      return sanitizer;\n    }\n    constructor(html) {\n      let {\n        allowedAttributes,\n        forbiddenProtocols,\n        forbiddenElements,\n        purifyOptions\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      super(...arguments);\n      this.allowedAttributes = allowedAttributes || DEFAULT_ALLOWED_ATTRIBUTES;\n      this.forbiddenProtocols = forbiddenProtocols || DEFAULT_FORBIDDEN_PROTOCOLS;\n      this.forbiddenElements = forbiddenElements || DEFAULT_FORBIDDEN_ELEMENTS;\n      this.purifyOptions = purifyOptions || {};\n      this.body = createBodyElementForHTML(html);\n    }\n    sanitize() {\n      this.sanitizeElements();\n      this.normalizeListElementNesting();\n      const purifyConfig = Object.assign({}, dompurify, this.purifyOptions);\n      purify.setConfig(purifyConfig);\n      this.body = purify.sanitize(this.body);\n      return this.body;\n    }\n    getHTML() {\n      return this.body.innerHTML;\n    }\n    getBody() {\n      return this.body;\n    }\n\n    // Private\n\n    sanitizeElements() {\n      const walker = walkTree(this.body);\n      const nodesToRemove = [];\n      while (walker.nextNode()) {\n        const node = walker.currentNode;\n        switch (node.nodeType) {\n          case Node.ELEMENT_NODE:\n            if (this.elementIsRemovable(node)) {\n              nodesToRemove.push(node);\n            } else {\n              this.sanitizeElement(node);\n            }\n            break;\n          case Node.COMMENT_NODE:\n            nodesToRemove.push(node);\n            break;\n        }\n      }\n      nodesToRemove.forEach(node => removeNode(node));\n      return this.body;\n    }\n    sanitizeElement(element) {\n      if (element.hasAttribute(\"href\")) {\n        if (this.forbiddenProtocols.includes(element.protocol)) {\n          element.removeAttribute(\"href\");\n        }\n      }\n      Array.from(element.attributes).forEach(_ref => {\n        let {\n          name\n        } = _ref;\n        if (!this.allowedAttributes.includes(name) && name.indexOf(\"data-trix\") !== 0) {\n          element.removeAttribute(name);\n        }\n      });\n      return element;\n    }\n    normalizeListElementNesting() {\n      Array.from(this.body.querySelectorAll(\"ul,ol\")).forEach(listElement => {\n        const previousElement = listElement.previousElementSibling;\n        if (previousElement) {\n          if (tagName(previousElement) === \"li\") {\n            previousElement.appendChild(listElement);\n          }\n        }\n      });\n      return this.body;\n    }\n    elementIsRemovable(element) {\n      if ((element === null || element === void 0 ? void 0 : element.nodeType) !== Node.ELEMENT_NODE) return;\n      return this.elementIsForbidden(element) || this.elementIsntSerializable(element);\n    }\n    elementIsForbidden(element) {\n      return this.forbiddenElements.includes(tagName(element));\n    }\n    elementIsntSerializable(element) {\n      return element.getAttribute(\"data-trix-serialize\") === \"false\" && !nodeIsAttachmentElement(element);\n    }\n  }\n  const createBodyElementForHTML = function () {\n    let html = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : \"\";\n    // Remove everything after </html>\n    html = html.replace(/<\\/html[^>]*>[^]*$/i, \"</html>\");\n    const doc = document.implementation.createHTMLDocument(\"\");\n    doc.documentElement.innerHTML = html;\n    Array.from(doc.head.querySelectorAll(\"style\")).forEach(element => {\n      doc.body.appendChild(element);\n    });\n    return doc.body;\n  };\n\n  const {\n    css: css$2\n  } = config;\n  class AttachmentView extends ObjectView {\n    constructor() {\n      super(...arguments);\n      this.attachment = this.object;\n      this.attachment.uploadProgressDelegate = this;\n      this.attachmentPiece = this.options.piece;\n    }\n    createContentNodes() {\n      return [];\n    }\n    createNodes() {\n      let innerElement;\n      const figure = innerElement = makeElement({\n        tagName: \"figure\",\n        className: this.getClassName(),\n        data: this.getData(),\n        editable: false\n      });\n      const href = this.getHref();\n      if (href) {\n        innerElement = makeElement({\n          tagName: \"a\",\n          editable: false,\n          attributes: {\n            href,\n            tabindex: -1\n          }\n        });\n        figure.appendChild(innerElement);\n      }\n      if (this.attachment.hasContent()) {\n        HTMLSanitizer.setHTML(innerElement, this.attachment.getContent());\n      } else {\n        this.createContentNodes().forEach(node => {\n          innerElement.appendChild(node);\n        });\n      }\n      innerElement.appendChild(this.createCaptionElement());\n      if (this.attachment.isPending()) {\n        this.progressElement = makeElement({\n          tagName: \"progress\",\n          attributes: {\n            class: css$2.attachmentProgress,\n            value: this.attachment.getUploadProgress(),\n            max: 100\n          },\n          data: {\n            trixMutable: true,\n            trixStoreKey: [\"progressElement\", this.attachment.id].join(\"/\")\n          }\n        });\n        figure.appendChild(this.progressElement);\n      }\n      return [createCursorTarget(\"left\"), figure, createCursorTarget(\"right\")];\n    }\n    createCaptionElement() {\n      const figcaption = makeElement({\n        tagName: \"figcaption\",\n        className: css$2.attachmentCaption\n      });\n      const caption = this.attachmentPiece.getCaption();\n      if (caption) {\n        figcaption.classList.add(\"\".concat(css$2.attachmentCaption, \"--edited\"));\n        figcaption.textContent = caption;\n      } else {\n        let name, size;\n        const captionConfig = this.getCaptionConfig();\n        if (captionConfig.name) {\n          name = this.attachment.getFilename();\n        }\n        if (captionConfig.size) {\n          size = this.attachment.getFormattedFilesize();\n        }\n        if (name) {\n          const nameElement = makeElement({\n            tagName: \"span\",\n            className: css$2.attachmentName,\n            textContent: name\n          });\n          figcaption.appendChild(nameElement);\n        }\n        if (size) {\n          if (name) {\n            figcaption.appendChild(document.createTextNode(\" \"));\n          }\n          const sizeElement = makeElement({\n            tagName: \"span\",\n            className: css$2.attachmentSize,\n            textContent: size\n          });\n          figcaption.appendChild(sizeElement);\n        }\n      }\n      return figcaption;\n    }\n    getClassName() {\n      const names = [css$2.attachment, \"\".concat(css$2.attachment, \"--\").concat(this.attachment.getType())];\n      const extension = this.attachment.getExtension();\n      if (extension) {\n        names.push(\"\".concat(css$2.attachment, \"--\").concat(extension));\n      }\n      return names.join(\" \");\n    }\n    getData() {\n      const data = {\n        trixAttachment: JSON.stringify(this.attachment),\n        trixContentType: this.attachment.getContentType(),\n        trixId: this.attachment.id\n      };\n      const {\n        attributes\n      } = this.attachmentPiece;\n      if (!attributes.isEmpty()) {\n        data.trixAttributes = JSON.stringify(attributes);\n      }\n      if (this.attachment.isPending()) {\n        data.trixSerialize = false;\n      }\n      return data;\n    }\n    getHref() {\n      if (!htmlContainsTagName(this.attachment.getContent(), \"a\")) {\n        const href = this.attachment.getHref();\n        if (href && purify.isValidAttribute(\"a\", \"href\", href)) {\n          return href;\n        }\n      }\n    }\n    getCaptionConfig() {\n      var _config$attachments$t;\n      const type = this.attachment.getType();\n      const captionConfig = copyObject((_config$attachments$t = attachments[type]) === null || _config$attachments$t === void 0 ? void 0 : _config$attachments$t.caption);\n      if (type === \"file\") {\n        captionConfig.name = true;\n      }\n      return captionConfig;\n    }\n    findProgressElement() {\n      var _this$findElement;\n      return (_this$findElement = this.findElement()) === null || _this$findElement === void 0 ? void 0 : _this$findElement.querySelector(\"progress\");\n    }\n\n    // Attachment delegate\n\n    attachmentDidChangeUploadProgress() {\n      const value = this.attachment.getUploadProgress();\n      const progressElement = this.findProgressElement();\n      if (progressElement) {\n        progressElement.value = value;\n      }\n    }\n  }\n  const createCursorTarget = name => makeElement({\n    tagName: \"span\",\n    textContent: ZERO_WIDTH_SPACE,\n    data: {\n      trixCursorTarget: name,\n      trixSerialize: false\n    }\n  });\n  const htmlContainsTagName = function (html, tagName) {\n    const div = makeElement(\"div\");\n    HTMLSanitizer.setHTML(div, html || \"\");\n    return div.querySelector(tagName);\n  };\n\n  class PreviewableAttachmentView extends AttachmentView {\n    constructor() {\n      super(...arguments);\n      this.attachment.previewDelegate = this;\n    }\n    createContentNodes() {\n      this.image = makeElement({\n        tagName: \"img\",\n        attributes: {\n          src: \"\"\n        },\n        data: {\n          trixMutable: true\n        }\n      });\n      this.refresh(this.image);\n      return [this.image];\n    }\n    createCaptionElement() {\n      const figcaption = super.createCaptionElement(...arguments);\n      if (!figcaption.textContent) {\n        figcaption.setAttribute(\"data-trix-placeholder\", lang$1.captionPlaceholder);\n      }\n      return figcaption;\n    }\n    refresh(image) {\n      if (!image) {\n        var _this$findElement;\n        image = (_this$findElement = this.findElement()) === null || _this$findElement === void 0 ? void 0 : _this$findElement.querySelector(\"img\");\n      }\n      if (image) {\n        return this.updateAttributesForImage(image);\n      }\n    }\n    updateAttributesForImage(image) {\n      const url = this.attachment.getURL();\n      const previewURL = this.attachment.getPreviewURL();\n      image.src = previewURL || url;\n      if (previewURL === url) {\n        image.removeAttribute(\"data-trix-serialized-attributes\");\n      } else {\n        const serializedAttributes = JSON.stringify({\n          src: url\n        });\n        image.setAttribute(\"data-trix-serialized-attributes\", serializedAttributes);\n      }\n      const width = this.attachment.getWidth();\n      const height = this.attachment.getHeight();\n      const alt = this.attachment.getAttribute(\"alt\");\n      if (width != null) {\n        image.width = width;\n      }\n      if (height != null) {\n        image.height = height;\n      }\n      if (alt != null) {\n        image.alt = alt;\n      }\n      const storeKey = [\"imageElement\", this.attachment.id, image.src, image.width, image.height].join(\"/\");\n      image.dataset.trixStoreKey = storeKey;\n    }\n\n    // Attachment delegate\n\n    attachmentDidChangeAttributes() {\n      this.refresh(this.image);\n      return this.refresh();\n    }\n  }\n\n  /* eslint-disable\n      no-useless-escape,\n      no-var,\n  */\n  class PieceView extends ObjectView {\n    constructor() {\n      super(...arguments);\n      this.piece = this.object;\n      this.attributes = this.piece.getAttributes();\n      this.textConfig = this.options.textConfig;\n      this.context = this.options.context;\n      if (this.piece.attachment) {\n        this.attachment = this.piece.attachment;\n      } else {\n        this.string = this.piece.toString();\n      }\n    }\n    createNodes() {\n      let nodes = this.attachment ? this.createAttachmentNodes() : this.createStringNodes();\n      const element = this.createElement();\n      if (element) {\n        const innerElement = findInnerElement(element);\n        Array.from(nodes).forEach(node => {\n          innerElement.appendChild(node);\n        });\n        nodes = [element];\n      }\n      return nodes;\n    }\n    createAttachmentNodes() {\n      const constructor = this.attachment.isPreviewable() ? PreviewableAttachmentView : AttachmentView;\n      const view = this.createChildView(constructor, this.piece.attachment, {\n        piece: this.piece\n      });\n      return view.getNodes();\n    }\n    createStringNodes() {\n      var _this$textConfig;\n      if ((_this$textConfig = this.textConfig) !== null && _this$textConfig !== void 0 && _this$textConfig.plaintext) {\n        return [document.createTextNode(this.string)];\n      } else {\n        const nodes = [];\n        const iterable = this.string.split(\"\\n\");\n        for (let index = 0; index < iterable.length; index++) {\n          const substring = iterable[index];\n          if (index > 0) {\n            const element = makeElement(\"br\");\n            nodes.push(element);\n          }\n          if (substring.length) {\n            const node = document.createTextNode(this.preserveSpaces(substring));\n            nodes.push(node);\n          }\n        }\n        return nodes;\n      }\n    }\n    createElement() {\n      let element, key, value;\n      const styles = {};\n      for (key in this.attributes) {\n        value = this.attributes[key];\n        const config = getTextConfig(key);\n        if (config) {\n          if (config.tagName) {\n            var innerElement;\n            const pendingElement = makeElement(config.tagName);\n            if (innerElement) {\n              innerElement.appendChild(pendingElement);\n              innerElement = pendingElement;\n            } else {\n              element = innerElement = pendingElement;\n            }\n          }\n          if (config.styleProperty) {\n            styles[config.styleProperty] = value;\n          }\n          if (config.style) {\n            for (key in config.style) {\n              value = config.style[key];\n              styles[key] = value;\n            }\n          }\n        }\n      }\n      if (Object.keys(styles).length) {\n        if (!element) {\n          element = makeElement(\"span\");\n        }\n        for (key in styles) {\n          value = styles[key];\n          element.style[key] = value;\n        }\n      }\n      return element;\n    }\n    createContainerElement() {\n      for (const key in this.attributes) {\n        const value = this.attributes[key];\n        const config = getTextConfig(key);\n        if (config) {\n          if (config.groupTagName) {\n            const attributes = {};\n            attributes[key] = value;\n            return makeElement(config.groupTagName, attributes);\n          }\n        }\n      }\n    }\n    preserveSpaces(string) {\n      if (this.context.isLast) {\n        string = string.replace(/\\ $/, NON_BREAKING_SPACE);\n      }\n      string = string.replace(/(\\S)\\ {3}(\\S)/g, \"$1 \".concat(NON_BREAKING_SPACE, \" $2\")).replace(/\\ {2}/g, \"\".concat(NON_BREAKING_SPACE, \" \")).replace(/\\ {2}/g, \" \".concat(NON_BREAKING_SPACE));\n      if (this.context.isFirst || this.context.followsWhitespace) {\n        string = string.replace(/^\\ /, NON_BREAKING_SPACE);\n      }\n      return string;\n    }\n  }\n\n  /* eslint-disable\n      no-var,\n  */\n  class TextView extends ObjectView {\n    constructor() {\n      super(...arguments);\n      this.text = this.object;\n      this.textConfig = this.options.textConfig;\n    }\n    createNodes() {\n      const nodes = [];\n      const pieces = ObjectGroup.groupObjects(this.getPieces());\n      const lastIndex = pieces.length - 1;\n      for (let index = 0; index < pieces.length; index++) {\n        const piece = pieces[index];\n        const context = {};\n        if (index === 0) {\n          context.isFirst = true;\n        }\n        if (index === lastIndex) {\n          context.isLast = true;\n        }\n        if (endsWithWhitespace(previousPiece)) {\n          context.followsWhitespace = true;\n        }\n        const view = this.findOrCreateCachedChildView(PieceView, piece, {\n          textConfig: this.textConfig,\n          context\n        });\n        nodes.push(...Array.from(view.getNodes() || []));\n        var previousPiece = piece;\n      }\n      return nodes;\n    }\n    getPieces() {\n      return Array.from(this.text.getPieces()).filter(piece => !piece.hasAttribute(\"blockBreak\"));\n    }\n  }\n  const endsWithWhitespace = piece => /\\s$/.test(piece === null || piece === void 0 ? void 0 : piece.toString());\n\n  const {\n    css: css$1\n  } = config;\n  class BlockView extends ObjectView {\n    constructor() {\n      super(...arguments);\n      this.block = this.object;\n      this.attributes = this.block.getAttributes();\n    }\n    createNodes() {\n      const comment = document.createComment(\"block\");\n      const nodes = [comment];\n      if (this.block.isEmpty()) {\n        nodes.push(makeElement(\"br\"));\n      } else {\n        var _getBlockConfig;\n        const textConfig = (_getBlockConfig = getBlockConfig(this.block.getLastAttribute())) === null || _getBlockConfig === void 0 ? void 0 : _getBlockConfig.text;\n        const textView = this.findOrCreateCachedChildView(TextView, this.block.text, {\n          textConfig\n        });\n        nodes.push(...Array.from(textView.getNodes() || []));\n        if (this.shouldAddExtraNewlineElement()) {\n          nodes.push(makeElement(\"br\"));\n        }\n      }\n      if (this.attributes.length) {\n        return nodes;\n      } else {\n        let attributes$1;\n        const {\n          tagName\n        } = attributes.default;\n        if (this.block.isRTL()) {\n          attributes$1 = {\n            dir: \"rtl\"\n          };\n        }\n        const element = makeElement({\n          tagName,\n          attributes: attributes$1\n        });\n        nodes.forEach(node => element.appendChild(node));\n        return [element];\n      }\n    }\n    createContainerElement(depth) {\n      const attributes = {};\n      let className;\n      const attributeName = this.attributes[depth];\n      const {\n        tagName,\n        htmlAttributes = []\n      } = getBlockConfig(attributeName);\n      if (depth === 0 && this.block.isRTL()) {\n        Object.assign(attributes, {\n          dir: \"rtl\"\n        });\n      }\n      if (attributeName === \"attachmentGallery\") {\n        const size = this.block.getBlockBreakPosition();\n        className = \"\".concat(css$1.attachmentGallery, \" \").concat(css$1.attachmentGallery, \"--\").concat(size);\n      }\n      Object.entries(this.block.htmlAttributes).forEach(_ref => {\n        let [name, value] = _ref;\n        if (htmlAttributes.includes(name)) {\n          attributes[name] = value;\n        }\n      });\n      return makeElement({\n        tagName,\n        className,\n        attributes\n      });\n    }\n\n    // A single <br> at the end of a block element has no visual representation\n    // so add an extra one.\n    shouldAddExtraNewlineElement() {\n      return /\\n\\n$/.test(this.block.toString());\n    }\n  }\n\n  class DocumentView extends ObjectView {\n    static render(document) {\n      const element = makeElement(\"div\");\n      const view = new this(document, {\n        element\n      });\n      view.render();\n      view.sync();\n      return element;\n    }\n    constructor() {\n      super(...arguments);\n      this.element = this.options.element;\n      this.elementStore = new ElementStore();\n      this.setDocument(this.object);\n    }\n    setDocument(document) {\n      if (!document.isEqualTo(this.document)) {\n        this.document = this.object = document;\n      }\n    }\n    render() {\n      this.childViews = [];\n      this.shadowElement = makeElement(\"div\");\n      if (!this.document.isEmpty()) {\n        const objects = ObjectGroup.groupObjects(this.document.getBlocks(), {\n          asTree: true\n        });\n        Array.from(objects).forEach(object => {\n          const view = this.findOrCreateCachedChildView(BlockView, object);\n          Array.from(view.getNodes()).map(node => this.shadowElement.appendChild(node));\n        });\n      }\n    }\n    isSynced() {\n      return elementsHaveEqualHTML(this.shadowElement, this.element);\n    }\n    sync() {\n      const render = (element, documentFragment) => {\n        while (element.lastChild) {\n          element.removeChild(element.lastChild);\n        }\n        element.appendChild(documentFragment);\n      };\n      const event = createEvent(\"trix-before-render\", {\n        cancelable: false,\n        attributes: {\n          render\n        }\n      });\n      this.element.dispatchEvent(event);\n      const fragment = this.createDocumentFragmentForSync();\n      event.render(this.element, fragment);\n      return this.didSync();\n    }\n\n    // Private\n\n    didSync() {\n      this.elementStore.reset(findStoredElements(this.element));\n      return defer(() => this.garbageCollectCachedViews());\n    }\n    createDocumentFragmentForSync() {\n      const fragment = document.createDocumentFragment();\n      Array.from(this.shadowElement.childNodes).forEach(node => {\n        fragment.appendChild(node.cloneNode(true));\n      });\n      Array.from(findStoredElements(fragment)).forEach(element => {\n        const storedElement = this.elementStore.remove(element);\n        if (storedElement) {\n          element.parentNode.replaceChild(storedElement, element);\n        }\n      });\n      return fragment;\n    }\n  }\n  const findStoredElements = element => element.querySelectorAll(\"[data-trix-store-key]\");\n  const elementsHaveEqualHTML = (element, otherElement) => ignoreSpaces(element.innerHTML) === ignoreSpaces(otherElement.innerHTML);\n  const ignoreSpaces = html => html.replace(/&nbsp;/g, \" \");\n\n  function _AsyncGenerator(e) {\n    var r, t;\n    function resume(r, t) {\n      try {\n        var n = e[r](t),\n          o = n.value,\n          u = o instanceof _OverloadYield;\n        Promise.resolve(u ? o.v : o).then(function (t) {\n          if (u) {\n            var i = \"return\" === r ? \"return\" : \"next\";\n            if (!o.k || t.done) return resume(i, t);\n            t = e[i](t).value;\n          }\n          settle(n.done ? \"return\" : \"normal\", t);\n        }, function (e) {\n          resume(\"throw\", e);\n        });\n      } catch (e) {\n        settle(\"throw\", e);\n      }\n    }\n    function settle(e, n) {\n      switch (e) {\n        case \"return\":\n          r.resolve({\n            value: n,\n            done: !0\n          });\n          break;\n        case \"throw\":\n          r.reject(n);\n          break;\n        default:\n          r.resolve({\n            value: n,\n            done: !1\n          });\n      }\n      (r = r.next) ? resume(r.key, r.arg) : t = null;\n    }\n    this._invoke = function (e, n) {\n      return new Promise(function (o, u) {\n        var i = {\n          key: e,\n          arg: n,\n          resolve: o,\n          reject: u,\n          next: null\n        };\n        t ? t = t.next = i : (r = t = i, resume(e, n));\n      });\n    }, \"function\" != typeof e.return && (this.return = void 0);\n  }\n  _AsyncGenerator.prototype[\"function\" == typeof Symbol && Symbol.asyncIterator || \"@@asyncIterator\"] = function () {\n    return this;\n  }, _AsyncGenerator.prototype.next = function (e) {\n    return this._invoke(\"next\", e);\n  }, _AsyncGenerator.prototype.throw = function (e) {\n    return this._invoke(\"throw\", e);\n  }, _AsyncGenerator.prototype.return = function (e) {\n    return this._invoke(\"return\", e);\n  };\n  function _OverloadYield(t, e) {\n    this.v = t, this.k = e;\n  }\n  function old_createMetadataMethodsForProperty(e, t, a, r) {\n    return {\n      getMetadata: function (o) {\n        old_assertNotFinished(r, \"getMetadata\"), old_assertMetadataKey(o);\n        var i = e[o];\n        if (void 0 !== i) if (1 === t) {\n          var n = i.public;\n          if (void 0 !== n) return n[a];\n        } else if (2 === t) {\n          var l = i.private;\n          if (void 0 !== l) return l.get(a);\n        } else if (Object.hasOwnProperty.call(i, \"constructor\")) return i.constructor;\n      },\n      setMetadata: function (o, i) {\n        old_assertNotFinished(r, \"setMetadata\"), old_assertMetadataKey(o);\n        var n = e[o];\n        if (void 0 === n && (n = e[o] = {}), 1 === t) {\n          var l = n.public;\n          void 0 === l && (l = n.public = {}), l[a] = i;\n        } else if (2 === t) {\n          var s = n.priv;\n          void 0 === s && (s = n.private = new Map()), s.set(a, i);\n        } else n.constructor = i;\n      }\n    };\n  }\n  function old_convertMetadataMapToFinal(e, t) {\n    var a = e[Symbol.metadata || Symbol.for(\"Symbol.metadata\")],\n      r = Object.getOwnPropertySymbols(t);\n    if (0 !== r.length) {\n      for (var o = 0; o < r.length; o++) {\n        var i = r[o],\n          n = t[i],\n          l = a ? a[i] : null,\n          s = n.public,\n          c = l ? l.public : null;\n        s && c && Object.setPrototypeOf(s, c);\n        var d = n.private;\n        if (d) {\n          var u = Array.from(d.values()),\n            f = l ? l.private : null;\n          f && (u = u.concat(f)), n.private = u;\n        }\n        l && Object.setPrototypeOf(n, l);\n      }\n      a && Object.setPrototypeOf(t, a), e[Symbol.metadata || Symbol.for(\"Symbol.metadata\")] = t;\n    }\n  }\n  function old_createAddInitializerMethod(e, t) {\n    return function (a) {\n      old_assertNotFinished(t, \"addInitializer\"), old_assertCallable(a, \"An initializer\"), e.push(a);\n    };\n  }\n  function old_memberDec(e, t, a, r, o, i, n, l, s) {\n    var c;\n    switch (i) {\n      case 1:\n        c = \"accessor\";\n        break;\n      case 2:\n        c = \"method\";\n        break;\n      case 3:\n        c = \"getter\";\n        break;\n      case 4:\n        c = \"setter\";\n        break;\n      default:\n        c = \"field\";\n    }\n    var d,\n      u,\n      f = {\n        kind: c,\n        name: l ? \"#\" + t : _toPropertyKey(t),\n        isStatic: n,\n        isPrivate: l\n      },\n      p = {\n        v: !1\n      };\n    if (0 !== i && (f.addInitializer = old_createAddInitializerMethod(o, p)), l) {\n      d = 2, u = Symbol(t);\n      var v = {};\n      0 === i ? (v.get = a.get, v.set = a.set) : 2 === i ? v.get = function () {\n        return a.value;\n      } : (1 !== i && 3 !== i || (v.get = function () {\n        return a.get.call(this);\n      }), 1 !== i && 4 !== i || (v.set = function (e) {\n        a.set.call(this, e);\n      })), f.access = v;\n    } else d = 1, u = t;\n    try {\n      return e(s, Object.assign(f, old_createMetadataMethodsForProperty(r, d, u, p)));\n    } finally {\n      p.v = !0;\n    }\n  }\n  function old_assertNotFinished(e, t) {\n    if (e.v) throw new Error(\"attempted to call \" + t + \" after decoration was finished\");\n  }\n  function old_assertMetadataKey(e) {\n    if (\"symbol\" != typeof e) throw new TypeError(\"Metadata keys must be symbols, received: \" + e);\n  }\n  function old_assertCallable(e, t) {\n    if (\"function\" != typeof e) throw new TypeError(t + \" must be a function\");\n  }\n  function old_assertValidReturnValue(e, t) {\n    var a = typeof t;\n    if (1 === e) {\n      if (\"object\" !== a || null === t) throw new TypeError(\"accessor decorators must return an object with get, set, or init properties or void 0\");\n      void 0 !== t.get && old_assertCallable(t.get, \"accessor.get\"), void 0 !== t.set && old_assertCallable(t.set, \"accessor.set\"), void 0 !== t.init && old_assertCallable(t.init, \"accessor.init\"), void 0 !== t.initializer && old_assertCallable(t.initializer, \"accessor.initializer\");\n    } else if (\"function\" !== a) throw new TypeError((0 === e ? \"field\" : 10 === e ? \"class\" : \"method\") + \" decorators must return a function or void 0\");\n  }\n  function old_getInit(e) {\n    var t;\n    return null == (t = e.init) && (t = e.initializer) && \"undefined\" != typeof console && console.warn(\".initializer has been renamed to .init as of March 2022\"), t;\n  }\n  function old_applyMemberDec(e, t, a, r, o, i, n, l, s) {\n    var c,\n      d,\n      u,\n      f,\n      p,\n      v,\n      y,\n      h = a[0];\n    if (n ? (0 === o || 1 === o ? (c = {\n      get: a[3],\n      set: a[4]\n    }, u = \"get\") : 3 === o ? (c = {\n      get: a[3]\n    }, u = \"get\") : 4 === o ? (c = {\n      set: a[3]\n    }, u = \"set\") : c = {\n      value: a[3]\n    }, 0 !== o && (1 === o && _setFunctionName(a[4], \"#\" + r, \"set\"), _setFunctionName(a[3], \"#\" + r, u))) : 0 !== o && (c = Object.getOwnPropertyDescriptor(t, r)), 1 === o ? f = {\n      get: c.get,\n      set: c.set\n    } : 2 === o ? f = c.value : 3 === o ? f = c.get : 4 === o && (f = c.set), \"function\" == typeof h) void 0 !== (p = old_memberDec(h, r, c, l, s, o, i, n, f)) && (old_assertValidReturnValue(o, p), 0 === o ? d = p : 1 === o ? (d = old_getInit(p), v = p.get || f.get, y = p.set || f.set, f = {\n      get: v,\n      set: y\n    }) : f = p);else for (var m = h.length - 1; m >= 0; m--) {\n      var b;\n      void 0 !== (p = old_memberDec(h[m], r, c, l, s, o, i, n, f)) && (old_assertValidReturnValue(o, p), 0 === o ? b = p : 1 === o ? (b = old_getInit(p), v = p.get || f.get, y = p.set || f.set, f = {\n        get: v,\n        set: y\n      }) : f = p, void 0 !== b && (void 0 === d ? d = b : \"function\" == typeof d ? d = [d, b] : d.push(b)));\n    }\n    if (0 === o || 1 === o) {\n      if (void 0 === d) d = function (e, t) {\n        return t;\n      };else if (\"function\" != typeof d) {\n        var g = d;\n        d = function (e, t) {\n          for (var a = t, r = 0; r < g.length; r++) a = g[r].call(e, a);\n          return a;\n        };\n      } else {\n        var _ = d;\n        d = function (e, t) {\n          return _.call(e, t);\n        };\n      }\n      e.push(d);\n    }\n    0 !== o && (1 === o ? (c.get = f.get, c.set = f.set) : 2 === o ? c.value = f : 3 === o ? c.get = f : 4 === o && (c.set = f), n ? 1 === o ? (e.push(function (e, t) {\n      return f.get.call(e, t);\n    }), e.push(function (e, t) {\n      return f.set.call(e, t);\n    })) : 2 === o ? e.push(f) : e.push(function (e, t) {\n      return f.call(e, t);\n    }) : Object.defineProperty(t, r, c));\n  }\n  function old_applyMemberDecs(e, t, a, r, o) {\n    for (var i, n, l = new Map(), s = new Map(), c = 0; c < o.length; c++) {\n      var d = o[c];\n      if (Array.isArray(d)) {\n        var u,\n          f,\n          p,\n          v = d[1],\n          y = d[2],\n          h = d.length > 3,\n          m = v >= 5;\n        if (m ? (u = t, f = r, 0 != (v -= 5) && (p = n = n || [])) : (u = t.prototype, f = a, 0 !== v && (p = i = i || [])), 0 !== v && !h) {\n          var b = m ? s : l,\n            g = b.get(y) || 0;\n          if (!0 === g || 3 === g && 4 !== v || 4 === g && 3 !== v) throw new Error(\"Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: \" + y);\n          !g && v > 2 ? b.set(y, v) : b.set(y, !0);\n        }\n        old_applyMemberDec(e, u, d, y, v, m, h, f, p);\n      }\n    }\n    old_pushInitializers(e, i), old_pushInitializers(e, n);\n  }\n  function old_pushInitializers(e, t) {\n    t && e.push(function (e) {\n      for (var a = 0; a < t.length; a++) t[a].call(e);\n      return e;\n    });\n  }\n  function old_applyClassDecs(e, t, a, r) {\n    if (r.length > 0) {\n      for (var o = [], i = t, n = t.name, l = r.length - 1; l >= 0; l--) {\n        var s = {\n          v: !1\n        };\n        try {\n          var c = Object.assign({\n              kind: \"class\",\n              name: n,\n              addInitializer: old_createAddInitializerMethod(o, s)\n            }, old_createMetadataMethodsForProperty(a, 0, n, s)),\n            d = r[l](i, c);\n        } finally {\n          s.v = !0;\n        }\n        void 0 !== d && (old_assertValidReturnValue(10, d), i = d);\n      }\n      e.push(i, function () {\n        for (var e = 0; e < o.length; e++) o[e].call(i);\n      });\n    }\n  }\n  function _applyDecs(e, t, a) {\n    var r = [],\n      o = {},\n      i = {};\n    return old_applyMemberDecs(r, e, i, o, t), old_convertMetadataMapToFinal(e.prototype, i), old_applyClassDecs(r, e, o, a), old_convertMetadataMapToFinal(e, o), r;\n  }\n  function applyDecs2203Factory() {\n    function createAddInitializerMethod(e, t) {\n      return function (r) {\n        !function (e, t) {\n          if (e.v) throw new Error(\"attempted to call addInitializer after decoration was finished\");\n        }(t), assertCallable(r, \"An initializer\"), e.push(r);\n      };\n    }\n    function memberDec(e, t, r, a, n, i, s, o) {\n      var c;\n      switch (n) {\n        case 1:\n          c = \"accessor\";\n          break;\n        case 2:\n          c = \"method\";\n          break;\n        case 3:\n          c = \"getter\";\n          break;\n        case 4:\n          c = \"setter\";\n          break;\n        default:\n          c = \"field\";\n      }\n      var l,\n        u,\n        f = {\n          kind: c,\n          name: s ? \"#\" + t : t,\n          static: i,\n          private: s\n        },\n        p = {\n          v: !1\n        };\n      0 !== n && (f.addInitializer = createAddInitializerMethod(a, p)), 0 === n ? s ? (l = r.get, u = r.set) : (l = function () {\n        return this[t];\n      }, u = function (e) {\n        this[t] = e;\n      }) : 2 === n ? l = function () {\n        return r.value;\n      } : (1 !== n && 3 !== n || (l = function () {\n        return r.get.call(this);\n      }), 1 !== n && 4 !== n || (u = function (e) {\n        r.set.call(this, e);\n      })), f.access = l && u ? {\n        get: l,\n        set: u\n      } : l ? {\n        get: l\n      } : {\n        set: u\n      };\n      try {\n        return e(o, f);\n      } finally {\n        p.v = !0;\n      }\n    }\n    function assertCallable(e, t) {\n      if (\"function\" != typeof e) throw new TypeError(t + \" must be a function\");\n    }\n    function assertValidReturnValue(e, t) {\n      var r = typeof t;\n      if (1 === e) {\n        if (\"object\" !== r || null === t) throw new TypeError(\"accessor decorators must return an object with get, set, or init properties or void 0\");\n        void 0 !== t.get && assertCallable(t.get, \"accessor.get\"), void 0 !== t.set && assertCallable(t.set, \"accessor.set\"), void 0 !== t.init && assertCallable(t.init, \"accessor.init\");\n      } else if (\"function\" !== r) throw new TypeError((0 === e ? \"field\" : 10 === e ? \"class\" : \"method\") + \" decorators must return a function or void 0\");\n    }\n    function applyMemberDec(e, t, r, a, n, i, s, o) {\n      var c,\n        l,\n        u,\n        f,\n        p,\n        d,\n        h = r[0];\n      if (s ? c = 0 === n || 1 === n ? {\n        get: r[3],\n        set: r[4]\n      } : 3 === n ? {\n        get: r[3]\n      } : 4 === n ? {\n        set: r[3]\n      } : {\n        value: r[3]\n      } : 0 !== n && (c = Object.getOwnPropertyDescriptor(t, a)), 1 === n ? u = {\n        get: c.get,\n        set: c.set\n      } : 2 === n ? u = c.value : 3 === n ? u = c.get : 4 === n && (u = c.set), \"function\" == typeof h) void 0 !== (f = memberDec(h, a, c, o, n, i, s, u)) && (assertValidReturnValue(n, f), 0 === n ? l = f : 1 === n ? (l = f.init, p = f.get || u.get, d = f.set || u.set, u = {\n        get: p,\n        set: d\n      }) : u = f);else for (var v = h.length - 1; v >= 0; v--) {\n        var g;\n        void 0 !== (f = memberDec(h[v], a, c, o, n, i, s, u)) && (assertValidReturnValue(n, f), 0 === n ? g = f : 1 === n ? (g = f.init, p = f.get || u.get, d = f.set || u.set, u = {\n          get: p,\n          set: d\n        }) : u = f, void 0 !== g && (void 0 === l ? l = g : \"function\" == typeof l ? l = [l, g] : l.push(g)));\n      }\n      if (0 === n || 1 === n) {\n        if (void 0 === l) l = function (e, t) {\n          return t;\n        };else if (\"function\" != typeof l) {\n          var y = l;\n          l = function (e, t) {\n            for (var r = t, a = 0; a < y.length; a++) r = y[a].call(e, r);\n            return r;\n          };\n        } else {\n          var m = l;\n          l = function (e, t) {\n            return m.call(e, t);\n          };\n        }\n        e.push(l);\n      }\n      0 !== n && (1 === n ? (c.get = u.get, c.set = u.set) : 2 === n ? c.value = u : 3 === n ? c.get = u : 4 === n && (c.set = u), s ? 1 === n ? (e.push(function (e, t) {\n        return u.get.call(e, t);\n      }), e.push(function (e, t) {\n        return u.set.call(e, t);\n      })) : 2 === n ? e.push(u) : e.push(function (e, t) {\n        return u.call(e, t);\n      }) : Object.defineProperty(t, a, c));\n    }\n    function pushInitializers(e, t) {\n      t && e.push(function (e) {\n        for (var r = 0; r < t.length; r++) t[r].call(e);\n        return e;\n      });\n    }\n    return function (e, t, r) {\n      var a = [];\n      return function (e, t, r) {\n        for (var a, n, i = new Map(), s = new Map(), o = 0; o < r.length; o++) {\n          var c = r[o];\n          if (Array.isArray(c)) {\n            var l,\n              u,\n              f = c[1],\n              p = c[2],\n              d = c.length > 3,\n              h = f >= 5;\n            if (h ? (l = t, 0 != (f -= 5) && (u = n = n || [])) : (l = t.prototype, 0 !== f && (u = a = a || [])), 0 !== f && !d) {\n              var v = h ? s : i,\n                g = v.get(p) || 0;\n              if (!0 === g || 3 === g && 4 !== f || 4 === g && 3 !== f) throw new Error(\"Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: \" + p);\n              !g && f > 2 ? v.set(p, f) : v.set(p, !0);\n            }\n            applyMemberDec(e, l, c, p, f, h, d, u);\n          }\n        }\n        pushInitializers(e, a), pushInitializers(e, n);\n      }(a, e, t), function (e, t, r) {\n        if (r.length > 0) {\n          for (var a = [], n = t, i = t.name, s = r.length - 1; s >= 0; s--) {\n            var o = {\n              v: !1\n            };\n            try {\n              var c = r[s](n, {\n                kind: \"class\",\n                name: i,\n                addInitializer: createAddInitializerMethod(a, o)\n              });\n            } finally {\n              o.v = !0;\n            }\n            void 0 !== c && (assertValidReturnValue(10, c), n = c);\n          }\n          e.push(n, function () {\n            for (var e = 0; e < a.length; e++) a[e].call(n);\n          });\n        }\n      }(a, e, r), a;\n    };\n  }\n  var applyDecs2203Impl;\n  function _applyDecs2203(e, t, r) {\n    return (applyDecs2203Impl = applyDecs2203Impl || applyDecs2203Factory())(e, t, r);\n  }\n  function applyDecs2203RFactory() {\n    function createAddInitializerMethod(e, t) {\n      return function (r) {\n        !function (e, t) {\n          if (e.v) throw new Error(\"attempted to call addInitializer after decoration was finished\");\n        }(t), assertCallable(r, \"An initializer\"), e.push(r);\n      };\n    }\n    function memberDec(e, t, r, n, a, i, o, s) {\n      var c;\n      switch (a) {\n        case 1:\n          c = \"accessor\";\n          break;\n        case 2:\n          c = \"method\";\n          break;\n        case 3:\n          c = \"getter\";\n          break;\n        case 4:\n          c = \"setter\";\n          break;\n        default:\n          c = \"field\";\n      }\n      var l,\n        u,\n        f = {\n          kind: c,\n          name: o ? \"#\" + t : _toPropertyKey(t),\n          static: i,\n          private: o\n        },\n        p = {\n          v: !1\n        };\n      0 !== a && (f.addInitializer = createAddInitializerMethod(n, p)), 0 === a ? o ? (l = r.get, u = r.set) : (l = function () {\n        return this[t];\n      }, u = function (e) {\n        this[t] = e;\n      }) : 2 === a ? l = function () {\n        return r.value;\n      } : (1 !== a && 3 !== a || (l = function () {\n        return r.get.call(this);\n      }), 1 !== a && 4 !== a || (u = function (e) {\n        r.set.call(this, e);\n      })), f.access = l && u ? {\n        get: l,\n        set: u\n      } : l ? {\n        get: l\n      } : {\n        set: u\n      };\n      try {\n        return e(s, f);\n      } finally {\n        p.v = !0;\n      }\n    }\n    function assertCallable(e, t) {\n      if (\"function\" != typeof e) throw new TypeError(t + \" must be a function\");\n    }\n    function assertValidReturnValue(e, t) {\n      var r = typeof t;\n      if (1 === e) {\n        if (\"object\" !== r || null === t) throw new TypeError(\"accessor decorators must return an object with get, set, or init properties or void 0\");\n        void 0 !== t.get && assertCallable(t.get, \"accessor.get\"), void 0 !== t.set && assertCallable(t.set, \"accessor.set\"), void 0 !== t.init && assertCallable(t.init, \"accessor.init\");\n      } else if (\"function\" !== r) throw new TypeError((0 === e ? \"field\" : 10 === e ? \"class\" : \"method\") + \" decorators must return a function or void 0\");\n    }\n    function applyMemberDec(e, t, r, n, a, i, o, s) {\n      var c,\n        l,\n        u,\n        f,\n        p,\n        d,\n        h,\n        v = r[0];\n      if (o ? (0 === a || 1 === a ? (c = {\n        get: r[3],\n        set: r[4]\n      }, u = \"get\") : 3 === a ? (c = {\n        get: r[3]\n      }, u = \"get\") : 4 === a ? (c = {\n        set: r[3]\n      }, u = \"set\") : c = {\n        value: r[3]\n      }, 0 !== a && (1 === a && _setFunctionName(r[4], \"#\" + n, \"set\"), _setFunctionName(r[3], \"#\" + n, u))) : 0 !== a && (c = Object.getOwnPropertyDescriptor(t, n)), 1 === a ? f = {\n        get: c.get,\n        set: c.set\n      } : 2 === a ? f = c.value : 3 === a ? f = c.get : 4 === a && (f = c.set), \"function\" == typeof v) void 0 !== (p = memberDec(v, n, c, s, a, i, o, f)) && (assertValidReturnValue(a, p), 0 === a ? l = p : 1 === a ? (l = p.init, d = p.get || f.get, h = p.set || f.set, f = {\n        get: d,\n        set: h\n      }) : f = p);else for (var g = v.length - 1; g >= 0; g--) {\n        var y;\n        void 0 !== (p = memberDec(v[g], n, c, s, a, i, o, f)) && (assertValidReturnValue(a, p), 0 === a ? y = p : 1 === a ? (y = p.init, d = p.get || f.get, h = p.set || f.set, f = {\n          get: d,\n          set: h\n        }) : f = p, void 0 !== y && (void 0 === l ? l = y : \"function\" == typeof l ? l = [l, y] : l.push(y)));\n      }\n      if (0 === a || 1 === a) {\n        if (void 0 === l) l = function (e, t) {\n          return t;\n        };else if (\"function\" != typeof l) {\n          var m = l;\n          l = function (e, t) {\n            for (var r = t, n = 0; n < m.length; n++) r = m[n].call(e, r);\n            return r;\n          };\n        } else {\n          var b = l;\n          l = function (e, t) {\n            return b.call(e, t);\n          };\n        }\n        e.push(l);\n      }\n      0 !== a && (1 === a ? (c.get = f.get, c.set = f.set) : 2 === a ? c.value = f : 3 === a ? c.get = f : 4 === a && (c.set = f), o ? 1 === a ? (e.push(function (e, t) {\n        return f.get.call(e, t);\n      }), e.push(function (e, t) {\n        return f.set.call(e, t);\n      })) : 2 === a ? e.push(f) : e.push(function (e, t) {\n        return f.call(e, t);\n      }) : Object.defineProperty(t, n, c));\n    }\n    function applyMemberDecs(e, t) {\n      for (var r, n, a = [], i = new Map(), o = new Map(), s = 0; s < t.length; s++) {\n        var c = t[s];\n        if (Array.isArray(c)) {\n          var l,\n            u,\n            f = c[1],\n            p = c[2],\n            d = c.length > 3,\n            h = f >= 5;\n          if (h ? (l = e, 0 != (f -= 5) && (u = n = n || [])) : (l = e.prototype, 0 !== f && (u = r = r || [])), 0 !== f && !d) {\n            var v = h ? o : i,\n              g = v.get(p) || 0;\n            if (!0 === g || 3 === g && 4 !== f || 4 === g && 3 !== f) throw new Error(\"Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: \" + p);\n            !g && f > 2 ? v.set(p, f) : v.set(p, !0);\n          }\n          applyMemberDec(a, l, c, p, f, h, d, u);\n        }\n      }\n      return pushInitializers(a, r), pushInitializers(a, n), a;\n    }\n    function pushInitializers(e, t) {\n      t && e.push(function (e) {\n        for (var r = 0; r < t.length; r++) t[r].call(e);\n        return e;\n      });\n    }\n    return function (e, t, r) {\n      return {\n        e: applyMemberDecs(e, t),\n        get c() {\n          return function (e, t) {\n            if (t.length > 0) {\n              for (var r = [], n = e, a = e.name, i = t.length - 1; i >= 0; i--) {\n                var o = {\n                  v: !1\n                };\n                try {\n                  var s = t[i](n, {\n                    kind: \"class\",\n                    name: a,\n                    addInitializer: createAddInitializerMethod(r, o)\n                  });\n                } finally {\n                  o.v = !0;\n                }\n                void 0 !== s && (assertValidReturnValue(10, s), n = s);\n              }\n              return [n, function () {\n                for (var e = 0; e < r.length; e++) r[e].call(n);\n              }];\n            }\n          }(e, r);\n        }\n      };\n    };\n  }\n  function _applyDecs2203R(e, t, r) {\n    return (_applyDecs2203R = applyDecs2203RFactory())(e, t, r);\n  }\n  function applyDecs2301Factory() {\n    function createAddInitializerMethod(e, t) {\n      return function (r) {\n        !function (e, t) {\n          if (e.v) throw new Error(\"attempted to call addInitializer after decoration was finished\");\n        }(t), assertCallable(r, \"An initializer\"), e.push(r);\n      };\n    }\n    function assertInstanceIfPrivate(e, t) {\n      if (!e(t)) throw new TypeError(\"Attempted to access private element on non-instance\");\n    }\n    function memberDec(e, t, r, n, a, i, s, o, c) {\n      var u;\n      switch (a) {\n        case 1:\n          u = \"accessor\";\n          break;\n        case 2:\n          u = \"method\";\n          break;\n        case 3:\n          u = \"getter\";\n          break;\n        case 4:\n          u = \"setter\";\n          break;\n        default:\n          u = \"field\";\n      }\n      var l,\n        f,\n        p = {\n          kind: u,\n          name: s ? \"#\" + t : _toPropertyKey(t),\n          static: i,\n          private: s\n        },\n        d = {\n          v: !1\n        };\n      if (0 !== a && (p.addInitializer = createAddInitializerMethod(n, d)), s || 0 !== a && 2 !== a) {\n        if (2 === a) l = function (e) {\n          return assertInstanceIfPrivate(c, e), r.value;\n        };else {\n          var h = 0 === a || 1 === a;\n          (h || 3 === a) && (l = s ? function (e) {\n            return assertInstanceIfPrivate(c, e), r.get.call(e);\n          } : function (e) {\n            return r.get.call(e);\n          }), (h || 4 === a) && (f = s ? function (e, t) {\n            assertInstanceIfPrivate(c, e), r.set.call(e, t);\n          } : function (e, t) {\n            r.set.call(e, t);\n          });\n        }\n      } else l = function (e) {\n        return e[t];\n      }, 0 === a && (f = function (e, r) {\n        e[t] = r;\n      });\n      var v = s ? c.bind() : function (e) {\n        return t in e;\n      };\n      p.access = l && f ? {\n        get: l,\n        set: f,\n        has: v\n      } : l ? {\n        get: l,\n        has: v\n      } : {\n        set: f,\n        has: v\n      };\n      try {\n        return e(o, p);\n      } finally {\n        d.v = !0;\n      }\n    }\n    function assertCallable(e, t) {\n      if (\"function\" != typeof e) throw new TypeError(t + \" must be a function\");\n    }\n    function assertValidReturnValue(e, t) {\n      var r = typeof t;\n      if (1 === e) {\n        if (\"object\" !== r || null === t) throw new TypeError(\"accessor decorators must return an object with get, set, or init properties or void 0\");\n        void 0 !== t.get && assertCallable(t.get, \"accessor.get\"), void 0 !== t.set && assertCallable(t.set, \"accessor.set\"), void 0 !== t.init && assertCallable(t.init, \"accessor.init\");\n      } else if (\"function\" !== r) throw new TypeError((0 === e ? \"field\" : 10 === e ? \"class\" : \"method\") + \" decorators must return a function or void 0\");\n    }\n    function curryThis2(e) {\n      return function (t) {\n        e(this, t);\n      };\n    }\n    function applyMemberDec(e, t, r, n, a, i, s, o, c) {\n      var u,\n        l,\n        f,\n        p,\n        d,\n        h,\n        v,\n        y,\n        g = r[0];\n      if (s ? (0 === a || 1 === a ? (u = {\n        get: (d = r[3], function () {\n          return d(this);\n        }),\n        set: curryThis2(r[4])\n      }, f = \"get\") : 3 === a ? (u = {\n        get: r[3]\n      }, f = \"get\") : 4 === a ? (u = {\n        set: r[3]\n      }, f = \"set\") : u = {\n        value: r[3]\n      }, 0 !== a && (1 === a && _setFunctionName(u.set, \"#\" + n, \"set\"), _setFunctionName(u[f || \"value\"], \"#\" + n, f))) : 0 !== a && (u = Object.getOwnPropertyDescriptor(t, n)), 1 === a ? p = {\n        get: u.get,\n        set: u.set\n      } : 2 === a ? p = u.value : 3 === a ? p = u.get : 4 === a && (p = u.set), \"function\" == typeof g) void 0 !== (h = memberDec(g, n, u, o, a, i, s, p, c)) && (assertValidReturnValue(a, h), 0 === a ? l = h : 1 === a ? (l = h.init, v = h.get || p.get, y = h.set || p.set, p = {\n        get: v,\n        set: y\n      }) : p = h);else for (var m = g.length - 1; m >= 0; m--) {\n        var b;\n        void 0 !== (h = memberDec(g[m], n, u, o, a, i, s, p, c)) && (assertValidReturnValue(a, h), 0 === a ? b = h : 1 === a ? (b = h.init, v = h.get || p.get, y = h.set || p.set, p = {\n          get: v,\n          set: y\n        }) : p = h, void 0 !== b && (void 0 === l ? l = b : \"function\" == typeof l ? l = [l, b] : l.push(b)));\n      }\n      if (0 === a || 1 === a) {\n        if (void 0 === l) l = function (e, t) {\n          return t;\n        };else if (\"function\" != typeof l) {\n          var I = l;\n          l = function (e, t) {\n            for (var r = t, n = 0; n < I.length; n++) r = I[n].call(e, r);\n            return r;\n          };\n        } else {\n          var w = l;\n          l = function (e, t) {\n            return w.call(e, t);\n          };\n        }\n        e.push(l);\n      }\n      0 !== a && (1 === a ? (u.get = p.get, u.set = p.set) : 2 === a ? u.value = p : 3 === a ? u.get = p : 4 === a && (u.set = p), s ? 1 === a ? (e.push(function (e, t) {\n        return p.get.call(e, t);\n      }), e.push(function (e, t) {\n        return p.set.call(e, t);\n      })) : 2 === a ? e.push(p) : e.push(function (e, t) {\n        return p.call(e, t);\n      }) : Object.defineProperty(t, n, u));\n    }\n    function applyMemberDecs(e, t, r) {\n      for (var n, a, i, s = [], o = new Map(), c = new Map(), u = 0; u < t.length; u++) {\n        var l = t[u];\n        if (Array.isArray(l)) {\n          var f,\n            p,\n            d = l[1],\n            h = l[2],\n            v = l.length > 3,\n            y = d >= 5,\n            g = r;\n          if (y ? (f = e, 0 != (d -= 5) && (p = a = a || []), v && !i && (i = function (t) {\n            return _checkInRHS(t) === e;\n          }), g = i) : (f = e.prototype, 0 !== d && (p = n = n || [])), 0 !== d && !v) {\n            var m = y ? c : o,\n              b = m.get(h) || 0;\n            if (!0 === b || 3 === b && 4 !== d || 4 === b && 3 !== d) throw new Error(\"Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: \" + h);\n            !b && d > 2 ? m.set(h, d) : m.set(h, !0);\n          }\n          applyMemberDec(s, f, l, h, d, y, v, p, g);\n        }\n      }\n      return pushInitializers(s, n), pushInitializers(s, a), s;\n    }\n    function pushInitializers(e, t) {\n      t && e.push(function (e) {\n        for (var r = 0; r < t.length; r++) t[r].call(e);\n        return e;\n      });\n    }\n    return function (e, t, r, n) {\n      return {\n        e: applyMemberDecs(e, t, n),\n        get c() {\n          return function (e, t) {\n            if (t.length > 0) {\n              for (var r = [], n = e, a = e.name, i = t.length - 1; i >= 0; i--) {\n                var s = {\n                  v: !1\n                };\n                try {\n                  var o = t[i](n, {\n                    kind: \"class\",\n                    name: a,\n                    addInitializer: createAddInitializerMethod(r, s)\n                  });\n                } finally {\n                  s.v = !0;\n                }\n                void 0 !== o && (assertValidReturnValue(10, o), n = o);\n              }\n              return [n, function () {\n                for (var e = 0; e < r.length; e++) r[e].call(n);\n              }];\n            }\n          }(e, r);\n        }\n      };\n    };\n  }\n  function _applyDecs2301(e, t, r, n) {\n    return (_applyDecs2301 = applyDecs2301Factory())(e, t, r, n);\n  }\n  function _applyDecs2305(e, t, r, n, o, a) {\n    function i(e, t, r) {\n      return function (n, o) {\n        return r && r(n), e[t].call(n, o);\n      };\n    }\n    function c(e, t) {\n      for (var r = 0; r < e.length; r++) e[r].call(t);\n      return t;\n    }\n    function s(e, t, r, n) {\n      if (\"function\" != typeof e && (n || void 0 !== e)) throw new TypeError(t + \" must \" + (r || \"be\") + \" a function\" + (n ? \"\" : \" or undefined\"));\n      return e;\n    }\n    function applyDec(e, t, r, n, o, a, c, u, l, f, p, d, h) {\n      function m(e) {\n        if (!h(e)) throw new TypeError(\"Attempted to access private element on non-instance\");\n      }\n      var y,\n        v = t[0],\n        g = t[3],\n        b = !u;\n      if (!b) {\n        r || Array.isArray(v) || (v = [v]);\n        var w = {},\n          S = [],\n          A = 3 === o ? \"get\" : 4 === o || d ? \"set\" : \"value\";\n        f ? (p || d ? w = {\n          get: _setFunctionName(function () {\n            return g(this);\n          }, n, \"get\"),\n          set: function (e) {\n            t[4](this, e);\n          }\n        } : w[A] = g, p || _setFunctionName(w[A], n, 2 === o ? \"\" : A)) : p || (w = Object.getOwnPropertyDescriptor(e, n));\n      }\n      for (var P = e, j = v.length - 1; j >= 0; j -= r ? 2 : 1) {\n        var D = v[j],\n          E = r ? v[j - 1] : void 0,\n          I = {},\n          O = {\n            kind: [\"field\", \"accessor\", \"method\", \"getter\", \"setter\", \"class\"][o],\n            name: n,\n            metadata: a,\n            addInitializer: function (e, t) {\n              if (e.v) throw new Error(\"attempted to call addInitializer after decoration was finished\");\n              s(t, \"An initializer\", \"be\", !0), c.push(t);\n            }.bind(null, I)\n          };\n        try {\n          if (b) (y = s(D.call(E, P, O), \"class decorators\", \"return\")) && (P = y);else {\n            var k, F;\n            O.static = l, O.private = f, f ? 2 === o ? k = function (e) {\n              return m(e), w.value;\n            } : (o < 4 && (k = i(w, \"get\", m)), 3 !== o && (F = i(w, \"set\", m))) : (k = function (e) {\n              return e[n];\n            }, (o < 2 || 4 === o) && (F = function (e, t) {\n              e[n] = t;\n            }));\n            var N = O.access = {\n              has: f ? h.bind() : function (e) {\n                return n in e;\n              }\n            };\n            if (k && (N.get = k), F && (N.set = F), P = D.call(E, d ? {\n              get: w.get,\n              set: w.set\n            } : w[A], O), d) {\n              if (\"object\" == typeof P && P) (y = s(P.get, \"accessor.get\")) && (w.get = y), (y = s(P.set, \"accessor.set\")) && (w.set = y), (y = s(P.init, \"accessor.init\")) && S.push(y);else if (void 0 !== P) throw new TypeError(\"accessor decorators must return an object with get, set, or init properties or void 0\");\n            } else s(P, (p ? \"field\" : \"method\") + \" decorators\", \"return\") && (p ? S.push(P) : w[A] = P);\n          }\n        } finally {\n          I.v = !0;\n        }\n      }\n      return (p || d) && u.push(function (e, t) {\n        for (var r = S.length - 1; r >= 0; r--) t = S[r].call(e, t);\n        return t;\n      }), p || b || (f ? d ? u.push(i(w, \"get\"), i(w, \"set\")) : u.push(2 === o ? w[A] : i.call.bind(w[A])) : Object.defineProperty(e, n, w)), P;\n    }\n    function u(e, t) {\n      return Object.defineProperty(e, Symbol.metadata || Symbol.for(\"Symbol.metadata\"), {\n        configurable: !0,\n        enumerable: !0,\n        value: t\n      });\n    }\n    if (arguments.length >= 6) var l = a[Symbol.metadata || Symbol.for(\"Symbol.metadata\")];\n    var f = Object.create(null == l ? null : l),\n      p = function (e, t, r, n) {\n        var o,\n          a,\n          i = [],\n          s = function (t) {\n            return _checkInRHS(t) === e;\n          },\n          u = new Map();\n        function l(e) {\n          e && i.push(c.bind(null, e));\n        }\n        for (var f = 0; f < t.length; f++) {\n          var p = t[f];\n          if (Array.isArray(p)) {\n            var d = p[1],\n              h = p[2],\n              m = p.length > 3,\n              y = 16 & d,\n              v = !!(8 & d),\n              g = 0 == (d &= 7),\n              b = h + \"/\" + v;\n            if (!g && !m) {\n              var w = u.get(b);\n              if (!0 === w || 3 === w && 4 !== d || 4 === w && 3 !== d) throw new Error(\"Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: \" + h);\n              u.set(b, !(d > 2) || d);\n            }\n            applyDec(v ? e : e.prototype, p, y, m ? \"#\" + h : _toPropertyKey(h), d, n, v ? a = a || [] : o = o || [], i, v, m, g, 1 === d, v && m ? s : r);\n          }\n        }\n        return l(o), l(a), i;\n      }(e, t, o, f);\n    return r.length || u(e, f), {\n      e: p,\n      get c() {\n        var t = [];\n        return r.length && [u(applyDec(e, [r], n, e.name, 5, f, t), f), c.bind(null, t, e)];\n      }\n    };\n  }\n  function _asyncGeneratorDelegate(t) {\n    var e = {},\n      n = !1;\n    function pump(e, r) {\n      return n = !0, r = new Promise(function (n) {\n        n(t[e](r));\n      }), {\n        done: !1,\n        value: new _OverloadYield(r, 1)\n      };\n    }\n    return e[\"undefined\" != typeof Symbol && Symbol.iterator || \"@@iterator\"] = function () {\n      return this;\n    }, e.next = function (t) {\n      return n ? (n = !1, t) : pump(\"next\", t);\n    }, \"function\" == typeof t.throw && (e.throw = function (t) {\n      if (n) throw n = !1, t;\n      return pump(\"throw\", t);\n    }), \"function\" == typeof t.return && (e.return = function (t) {\n      return n ? (n = !1, t) : pump(\"return\", t);\n    }), e;\n  }\n  function _asyncIterator(r) {\n    var n,\n      t,\n      o,\n      e = 2;\n    for (\"undefined\" != typeof Symbol && (t = Symbol.asyncIterator, o = Symbol.iterator); e--;) {\n      if (t && null != (n = r[t])) return n.call(r);\n      if (o && null != (n = r[o])) return new AsyncFromSyncIterator(n.call(r));\n      t = \"@@asyncIterator\", o = \"@@iterator\";\n    }\n    throw new TypeError(\"Object is not async iterable\");\n  }\n  function AsyncFromSyncIterator(r) {\n    function AsyncFromSyncIteratorContinuation(r) {\n      if (Object(r) !== r) return Promise.reject(new TypeError(r + \" is not an object.\"));\n      var n = r.done;\n      return Promise.resolve(r.value).then(function (r) {\n        return {\n          value: r,\n          done: n\n        };\n      });\n    }\n    return AsyncFromSyncIterator = function (r) {\n      this.s = r, this.n = r.next;\n    }, AsyncFromSyncIterator.prototype = {\n      s: null,\n      n: null,\n      next: function () {\n        return AsyncFromSyncIteratorContinuation(this.n.apply(this.s, arguments));\n      },\n      return: function (r) {\n        var n = this.s.return;\n        return void 0 === n ? Promise.resolve({\n          value: r,\n          done: !0\n        }) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments));\n      },\n      throw: function (r) {\n        var n = this.s.return;\n        return void 0 === n ? Promise.reject(r) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments));\n      }\n    }, new AsyncFromSyncIterator(r);\n  }\n  function _awaitAsyncGenerator(e) {\n    return new _OverloadYield(e, 0);\n  }\n  function _callSuper(t, o, e) {\n    return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e));\n  }\n  function _checkInRHS(e) {\n    if (Object(e) !== e) throw TypeError(\"right-hand side of 'in' should be an object, got \" + (null !== e ? typeof e : \"null\"));\n    return e;\n  }\n  function _construct(t, e, r) {\n    if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments);\n    var o = [null];\n    o.push.apply(o, e);\n    var p = new (t.bind.apply(t, o))();\n    return r && _setPrototypeOf(p, r.prototype), p;\n  }\n  function _defineAccessor(e, r, n, t) {\n    var c = {\n      configurable: !0,\n      enumerable: !0\n    };\n    return c[e] = t, Object.defineProperty(r, n, c);\n  }\n  function dispose_SuppressedError(r, e) {\n    return \"undefined\" != typeof SuppressedError ? dispose_SuppressedError = SuppressedError : (dispose_SuppressedError = function (r, e) {\n      this.suppressed = e, this.error = r, this.stack = new Error().stack;\n    }, dispose_SuppressedError.prototype = Object.create(Error.prototype, {\n      constructor: {\n        value: dispose_SuppressedError,\n        writable: !0,\n        configurable: !0\n      }\n    })), new dispose_SuppressedError(r, e);\n  }\n  function _dispose(r, e, s) {\n    function next() {\n      for (; r.length > 0;) try {\n        var o = r.pop(),\n          p = o.d.call(o.v);\n        if (o.a) return Promise.resolve(p).then(next, err);\n      } catch (r) {\n        return err(r);\n      }\n      if (s) throw e;\n    }\n    function err(r) {\n      return e = s ? new dispose_SuppressedError(e, r) : r, s = !0, next();\n    }\n    return next();\n  }\n  function _importDeferProxy(e) {\n    var t = null,\n      constValue = function (e) {\n        return function () {\n          return e;\n        };\n      },\n      proxy = function (r) {\n        return function (n, o, f) {\n          return null === t && (t = e()), r(t, o, f);\n        };\n      };\n    return new Proxy({}, {\n      defineProperty: constValue(!1),\n      deleteProperty: constValue(!1),\n      get: proxy(Reflect.get),\n      getOwnPropertyDescriptor: proxy(Reflect.getOwnPropertyDescriptor),\n      getPrototypeOf: constValue(null),\n      isExtensible: constValue(!1),\n      has: proxy(Reflect.has),\n      ownKeys: proxy(Reflect.ownKeys),\n      preventExtensions: constValue(!0),\n      set: constValue(!1),\n      setPrototypeOf: constValue(!1)\n    });\n  }\n  function _getRequireWildcardCache(e) {\n    if (\"function\" != typeof WeakMap) return null;\n    var r = new WeakMap(),\n      t = new WeakMap();\n    return (_getRequireWildcardCache = function (e) {\n      return e ? t : r;\n    })(e);\n  }\n  function _interopRequireWildcard(e, r) {\n    if (!r && e && e.__esModule) return e;\n    if (null === e || \"object\" != typeof e && \"function\" != typeof e) return {\n      default: e\n    };\n    var t = _getRequireWildcardCache(r);\n    if (t && t.has(e)) return t.get(e);\n    var n = {\n        __proto__: null\n      },\n      a = Object.defineProperty && Object.getOwnPropertyDescriptor;\n    for (var u in e) if (\"default\" !== u && Object.prototype.hasOwnProperty.call(e, u)) {\n      var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;\n      i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u];\n    }\n    return n.default = e, t && t.set(e, n), n;\n  }\n  function _isNativeReflectConstruct() {\n    try {\n      var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));\n    } catch (t) {}\n    return (_isNativeReflectConstruct = function () {\n      return !!t;\n    })();\n  }\n  function _iterableToArrayLimit(r, l) {\n    var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"];\n    if (null != t) {\n      var e,\n        n,\n        i,\n        u,\n        a = [],\n        f = !0,\n        o = !1;\n      try {\n        if (i = (t = t.call(r)).next, 0 === l) {\n          if (Object(t) !== t) return;\n          f = !1;\n        } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);\n      } catch (r) {\n        o = !0, n = r;\n      } finally {\n        try {\n          if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;\n        } finally {\n          if (o) throw n;\n        }\n      }\n      return a;\n    }\n  }\n  function _iterableToArrayLimitLoose(e, r) {\n    var t = e && (\"undefined\" != typeof Symbol && e[Symbol.iterator] || e[\"@@iterator\"]);\n    if (null != t) {\n      var o,\n        l = [];\n      for (t = t.call(e); e.length < r && !(o = t.next()).done;) l.push(o.value);\n      return l;\n    }\n  }\n  var REACT_ELEMENT_TYPE;\n  function _jsx(e, r, E, l) {\n    REACT_ELEMENT_TYPE || (REACT_ELEMENT_TYPE = \"function\" == typeof Symbol && Symbol.for && Symbol.for(\"react.element\") || 60103);\n    var o = e && e.defaultProps,\n      n = arguments.length - 3;\n    if (r || 0 === n || (r = {\n      children: void 0\n    }), 1 === n) r.children = l;else if (n > 1) {\n      for (var t = new Array(n), f = 0; f < n; f++) t[f] = arguments[f + 3];\n      r.children = t;\n    }\n    if (r && o) for (var i in o) void 0 === r[i] && (r[i] = o[i]);else r || (r = o || {});\n    return {\n      $$typeof: REACT_ELEMENT_TYPE,\n      type: e,\n      key: void 0 === E ? null : \"\" + E,\n      ref: null,\n      props: r,\n      _owner: null\n    };\n  }\n  function ownKeys(e, r) {\n    var t = Object.keys(e);\n    if (Object.getOwnPropertySymbols) {\n      var o = Object.getOwnPropertySymbols(e);\n      r && (o = o.filter(function (r) {\n        return Object.getOwnPropertyDescriptor(e, r).enumerable;\n      })), t.push.apply(t, o);\n    }\n    return t;\n  }\n  function _objectSpread2(e) {\n    for (var r = 1; r < arguments.length; r++) {\n      var t = null != arguments[r] ? arguments[r] : {};\n      r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {\n        _defineProperty(e, r, t[r]);\n      }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {\n        Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));\n      });\n    }\n    return e;\n  }\n  function _regeneratorRuntime() {\n    \"use strict\"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */\n    _regeneratorRuntime = function () {\n      return e;\n    };\n    var t,\n      e = {},\n      r = Object.prototype,\n      n = r.hasOwnProperty,\n      o = Object.defineProperty || function (t, e, r) {\n        t[e] = r.value;\n      },\n      i = \"function\" == typeof Symbol ? Symbol : {},\n      a = i.iterator || \"@@iterator\",\n      c = i.asyncIterator || \"@@asyncIterator\",\n      u = i.toStringTag || \"@@toStringTag\";\n    function define(t, e, r) {\n      return Object.defineProperty(t, e, {\n        value: r,\n        enumerable: !0,\n        configurable: !0,\n        writable: !0\n      }), t[e];\n    }\n    try {\n      define({}, \"\");\n    } catch (t) {\n      define = function (t, e, r) {\n        return t[e] = r;\n      };\n    }\n    function wrap(t, e, r, n) {\n      var i = e && e.prototype instanceof Generator ? e : Generator,\n        a = Object.create(i.prototype),\n        c = new Context(n || []);\n      return o(a, \"_invoke\", {\n        value: makeInvokeMethod(t, r, c)\n      }), a;\n    }\n    function tryCatch(t, e, r) {\n      try {\n        return {\n          type: \"normal\",\n          arg: t.call(e, r)\n        };\n      } catch (t) {\n        return {\n          type: \"throw\",\n          arg: t\n        };\n      }\n    }\n    e.wrap = wrap;\n    var h = \"suspendedStart\",\n      l = \"suspendedYield\",\n      f = \"executing\",\n      s = \"completed\",\n      y = {};\n    function Generator() {}\n    function GeneratorFunction() {}\n    function GeneratorFunctionPrototype() {}\n    var p = {};\n    define(p, a, function () {\n      return this;\n    });\n    var d = Object.getPrototypeOf,\n      v = d && d(d(values([])));\n    v && v !== r && n.call(v, a) && (p = v);\n    var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p);\n    function defineIteratorMethods(t) {\n      [\"next\", \"throw\", \"return\"].forEach(function (e) {\n        define(t, e, function (t) {\n          return this._invoke(e, t);\n        });\n      });\n    }\n    function AsyncIterator(t, e) {\n      function invoke(r, o, i, a) {\n        var c = tryCatch(t[r], t, o);\n        if (\"throw\" !== c.type) {\n          var u = c.arg,\n            h = u.value;\n          return h && \"object\" == typeof h && n.call(h, \"__await\") ? e.resolve(h.__await).then(function (t) {\n            invoke(\"next\", t, i, a);\n          }, function (t) {\n            invoke(\"throw\", t, i, a);\n          }) : e.resolve(h).then(function (t) {\n            u.value = t, i(u);\n          }, function (t) {\n            return invoke(\"throw\", t, i, a);\n          });\n        }\n        a(c.arg);\n      }\n      var r;\n      o(this, \"_invoke\", {\n        value: function (t, n) {\n          function callInvokeWithMethodAndArg() {\n            return new e(function (e, r) {\n              invoke(t, n, e, r);\n            });\n          }\n          return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg();\n        }\n      });\n    }\n    function makeInvokeMethod(e, r, n) {\n      var o = h;\n      return function (i, a) {\n        if (o === f) throw new Error(\"Generator is already running\");\n        if (o === s) {\n          if (\"throw\" === i) throw a;\n          return {\n            value: t,\n            done: !0\n          };\n        }\n        for (n.method = i, n.arg = a;;) {\n          var c = n.delegate;\n          if (c) {\n            var u = maybeInvokeDelegate(c, n);\n            if (u) {\n              if (u === y) continue;\n              return u;\n            }\n          }\n          if (\"next\" === n.method) n.sent = n._sent = n.arg;else if (\"throw\" === n.method) {\n            if (o === h) throw o = s, n.arg;\n            n.dispatchException(n.arg);\n          } else \"return\" === n.method && n.abrupt(\"return\", n.arg);\n          o = f;\n          var p = tryCatch(e, r, n);\n          if (\"normal\" === p.type) {\n            if (o = n.done ? s : l, p.arg === y) continue;\n            return {\n              value: p.arg,\n              done: n.done\n            };\n          }\n          \"throw\" === p.type && (o = s, n.method = \"throw\", n.arg = p.arg);\n        }\n      };\n    }\n    function maybeInvokeDelegate(e, r) {\n      var n = r.method,\n        o = e.iterator[n];\n      if (o === t) return r.delegate = null, \"throw\" === n && e.iterator.return && (r.method = \"return\", r.arg = t, maybeInvokeDelegate(e, r), \"throw\" === r.method) || \"return\" !== n && (r.method = \"throw\", r.arg = new TypeError(\"The iterator does not provide a '\" + n + \"' method\")), y;\n      var i = tryCatch(o, e.iterator, r.arg);\n      if (\"throw\" === i.type) return r.method = \"throw\", r.arg = i.arg, r.delegate = null, y;\n      var a = i.arg;\n      return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, \"return\" !== r.method && (r.method = \"next\", r.arg = t), r.delegate = null, y) : a : (r.method = \"throw\", r.arg = new TypeError(\"iterator result is not an object\"), r.delegate = null, y);\n    }\n    function pushTryEntry(t) {\n      var e = {\n        tryLoc: t[0]\n      };\n      1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e);\n    }\n    function resetTryEntry(t) {\n      var e = t.completion || {};\n      e.type = \"normal\", delete e.arg, t.completion = e;\n    }\n    function Context(t) {\n      this.tryEntries = [{\n        tryLoc: \"root\"\n      }], t.forEach(pushTryEntry, this), this.reset(!0);\n    }\n    function values(e) {\n      if (e || \"\" === e) {\n        var r = e[a];\n        if (r) return r.call(e);\n        if (\"function\" == typeof e.next) return e;\n        if (!isNaN(e.length)) {\n          var o = -1,\n            i = function next() {\n              for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next;\n              return next.value = t, next.done = !0, next;\n            };\n          return i.next = i;\n        }\n      }\n      throw new TypeError(typeof e + \" is not iterable\");\n    }\n    return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, \"constructor\", {\n      value: GeneratorFunctionPrototype,\n      configurable: !0\n    }), o(GeneratorFunctionPrototype, \"constructor\", {\n      value: GeneratorFunction,\n      configurable: !0\n    }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, \"GeneratorFunction\"), e.isGeneratorFunction = function (t) {\n      var e = \"function\" == typeof t && t.constructor;\n      return !!e && (e === GeneratorFunction || \"GeneratorFunction\" === (e.displayName || e.name));\n    }, e.mark = function (t) {\n      return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, \"GeneratorFunction\")), t.prototype = Object.create(g), t;\n    }, e.awrap = function (t) {\n      return {\n        __await: t\n      };\n    }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () {\n      return this;\n    }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) {\n      void 0 === i && (i = Promise);\n      var a = new AsyncIterator(wrap(t, r, n, o), i);\n      return e.isGeneratorFunction(r) ? a : a.next().then(function (t) {\n        return t.done ? t.value : a.next();\n      });\n    }, defineIteratorMethods(g), define(g, u, \"Generator\"), define(g, a, function () {\n      return this;\n    }), define(g, \"toString\", function () {\n      return \"[object Generator]\";\n    }), e.keys = function (t) {\n      var e = Object(t),\n        r = [];\n      for (var n in e) r.push(n);\n      return r.reverse(), function next() {\n        for (; r.length;) {\n          var t = r.pop();\n          if (t in e) return next.value = t, next.done = !1, next;\n        }\n        return next.done = !0, next;\n      };\n    }, e.values = values, Context.prototype = {\n      constructor: Context,\n      reset: function (e) {\n        if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = \"next\", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) \"t\" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t);\n      },\n      stop: function () {\n        this.done = !0;\n        var t = this.tryEntries[0].completion;\n        if (\"throw\" === t.type) throw t.arg;\n        return this.rval;\n      },\n      dispatchException: function (e) {\n        if (this.done) throw e;\n        var r = this;\n        function handle(n, o) {\n          return a.type = \"throw\", a.arg = e, r.next = n, o && (r.method = \"next\", r.arg = t), !!o;\n        }\n        for (var o = this.tryEntries.length - 1; o >= 0; --o) {\n          var i = this.tryEntries[o],\n            a = i.completion;\n          if (\"root\" === i.tryLoc) return handle(\"end\");\n          if (i.tryLoc <= this.prev) {\n            var c = n.call(i, \"catchLoc\"),\n              u = n.call(i, \"finallyLoc\");\n            if (c && u) {\n              if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);\n              if (this.prev < i.finallyLoc) return handle(i.finallyLoc);\n            } else if (c) {\n              if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);\n            } else {\n              if (!u) throw new Error(\"try statement without catch or finally\");\n              if (this.prev < i.finallyLoc) return handle(i.finallyLoc);\n            }\n          }\n        }\n      },\n      abrupt: function (t, e) {\n        for (var r = this.tryEntries.length - 1; r >= 0; --r) {\n          var o = this.tryEntries[r];\n          if (o.tryLoc <= this.prev && n.call(o, \"finallyLoc\") && this.prev < o.finallyLoc) {\n            var i = o;\n            break;\n          }\n        }\n        i && (\"break\" === t || \"continue\" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null);\n        var a = i ? i.completion : {};\n        return a.type = t, a.arg = e, i ? (this.method = \"next\", this.next = i.finallyLoc, y) : this.complete(a);\n      },\n      complete: function (t, e) {\n        if (\"throw\" === t.type) throw t.arg;\n        return \"break\" === t.type || \"continue\" === t.type ? this.next = t.arg : \"return\" === t.type ? (this.rval = this.arg = t.arg, this.method = \"return\", this.next = \"end\") : \"normal\" === t.type && e && (this.next = e), y;\n      },\n      finish: function (t) {\n        for (var e = this.tryEntries.length - 1; e >= 0; --e) {\n          var r = this.tryEntries[e];\n          if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y;\n        }\n      },\n      catch: function (t) {\n        for (var e = this.tryEntries.length - 1; e >= 0; --e) {\n          var r = this.tryEntries[e];\n          if (r.tryLoc === t) {\n            var n = r.completion;\n            if (\"throw\" === n.type) {\n              var o = n.arg;\n              resetTryEntry(r);\n            }\n            return o;\n          }\n        }\n        throw new Error(\"illegal catch attempt\");\n      },\n      delegateYield: function (e, r, n) {\n        return this.delegate = {\n          iterator: values(e),\n          resultName: r,\n          nextLoc: n\n        }, \"next\" === this.method && (this.arg = t), y;\n      }\n    }, e;\n  }\n  function _setFunctionName(e, t, n) {\n    \"symbol\" == typeof t && (t = (t = t.description) ? \"[\" + t + \"]\" : \"\");\n    try {\n      Object.defineProperty(e, \"name\", {\n        configurable: !0,\n        value: n ? n + \" \" + t : t\n      });\n    } catch (e) {}\n    return e;\n  }\n  function _toPrimitive(t, r) {\n    if (\"object\" != typeof t || !t) return t;\n    var e = t[Symbol.toPrimitive];\n    if (void 0 !== e) {\n      var i = e.call(t, r || \"default\");\n      if (\"object\" != typeof i) return i;\n      throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n    }\n    return (\"string\" === r ? String : Number)(t);\n  }\n  function _toPropertyKey(t) {\n    var i = _toPrimitive(t, \"string\");\n    return \"symbol\" == typeof i ? i : String(i);\n  }\n  function _typeof(o) {\n    \"@babel/helpers - typeof\";\n\n    return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n      return typeof o;\n    } : function (o) {\n      return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n    }, _typeof(o);\n  }\n  function _using(o, n, e) {\n    if (null == n) return n;\n    if (Object(n) !== n) throw new TypeError(\"using declarations can only be used with objects, functions, null, or undefined.\");\n    if (e) var r = n[Symbol.asyncDispose || Symbol.for(\"Symbol.asyncDispose\")];\n    if (null == r && (r = n[Symbol.dispose || Symbol.for(\"Symbol.dispose\")]), \"function\" != typeof r) throw new TypeError(\"Property [Symbol.dispose] is not a function.\");\n    return o.push({\n      v: n,\n      d: r,\n      a: e\n    }), n;\n  }\n  function _usingCtx() {\n    var r = \"function\" == typeof SuppressedError ? SuppressedError : function (r, n) {\n        var e = new Error();\n        return e.name = \"SuppressedError\", e.suppressed = n, e.error = r, e;\n      },\n      n = {},\n      e = [];\n    function using(r, n) {\n      if (null != n) {\n        if (Object(n) !== n) throw new TypeError(\"using declarations can only be used with objects, functions, null, or undefined.\");\n        if (r) var o = n[Symbol.asyncDispose || Symbol.for(\"Symbol.asyncDispose\")];\n        if (null == o && (o = n[Symbol.dispose || Symbol.for(\"Symbol.dispose\")]), \"function\" != typeof o) throw new TypeError(\"Property [Symbol.dispose] is not a function.\");\n        e.push({\n          v: n,\n          d: o,\n          a: r\n        });\n      }\n      return n;\n    }\n    return {\n      e: n,\n      u: using.bind(null, !1),\n      a: using.bind(null, !0),\n      d: function () {\n        var o = this.e;\n        function next() {\n          for (; r = e.pop();) try {\n            var r,\n              t = r.d.call(r.v);\n            if (r.a) return Promise.resolve(t).then(next, err);\n          } catch (r) {\n            return err(r);\n          }\n          if (o !== n) throw o;\n        }\n        function err(e) {\n          return o = o !== n ? new r(o, e) : e, next();\n        }\n        return next();\n      }\n    };\n  }\n  function _wrapRegExp() {\n    _wrapRegExp = function (e, r) {\n      return new BabelRegExp(e, void 0, r);\n    };\n    var e = RegExp.prototype,\n      r = new WeakMap();\n    function BabelRegExp(e, t, p) {\n      var o = new RegExp(e, t);\n      return r.set(o, p || r.get(e)), _setPrototypeOf(o, BabelRegExp.prototype);\n    }\n    function buildGroups(e, t) {\n      var p = r.get(t);\n      return Object.keys(p).reduce(function (r, t) {\n        var o = p[t];\n        if (\"number\" == typeof o) r[t] = e[o];else {\n          for (var i = 0; void 0 === e[o[i]] && i + 1 < o.length;) i++;\n          r[t] = e[o[i]];\n        }\n        return r;\n      }, Object.create(null));\n    }\n    return _inherits(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (r) {\n      var t = e.exec.call(this, r);\n      if (t) {\n        t.groups = buildGroups(t, this);\n        var p = t.indices;\n        p && (p.groups = buildGroups(p, this));\n      }\n      return t;\n    }, BabelRegExp.prototype[Symbol.replace] = function (t, p) {\n      if (\"string\" == typeof p) {\n        var o = r.get(this);\n        return e[Symbol.replace].call(this, t, p.replace(/\\$<([^>]+)>/g, function (e, r) {\n          var t = o[r];\n          return \"$\" + (Array.isArray(t) ? t.join(\"$\") : t);\n        }));\n      }\n      if (\"function\" == typeof p) {\n        var i = this;\n        return e[Symbol.replace].call(this, t, function () {\n          var e = arguments;\n          return \"object\" != typeof e[e.length - 1] && (e = [].slice.call(e)).push(buildGroups(e, i)), p.apply(this, e);\n        });\n      }\n      return e[Symbol.replace].call(this, t, p);\n    }, _wrapRegExp.apply(this, arguments);\n  }\n  function _AwaitValue(value) {\n    this.wrapped = value;\n  }\n  function _wrapAsyncGenerator(fn) {\n    return function () {\n      return new _AsyncGenerator(fn.apply(this, arguments));\n    };\n  }\n  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {\n    try {\n      var info = gen[key](arg);\n      var value = info.value;\n    } catch (error) {\n      reject(error);\n      return;\n    }\n    if (info.done) {\n      resolve(value);\n    } else {\n      Promise.resolve(value).then(_next, _throw);\n    }\n  }\n  function _asyncToGenerator(fn) {\n    return function () {\n      var self = this,\n        args = arguments;\n      return new Promise(function (resolve, reject) {\n        var gen = fn.apply(self, args);\n        function _next(value) {\n          asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value);\n        }\n        function _throw(err) {\n          asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err);\n        }\n        _next(undefined);\n      });\n    };\n  }\n  function _classCallCheck(instance, Constructor) {\n    if (!(instance instanceof Constructor)) {\n      throw new TypeError(\"Cannot call a class as a function\");\n    }\n  }\n  function _defineProperties(target, props) {\n    for (var i = 0; i < props.length; i++) {\n      var descriptor = props[i];\n      descriptor.enumerable = descriptor.enumerable || false;\n      descriptor.configurable = true;\n      if (\"value\" in descriptor) descriptor.writable = true;\n      Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);\n    }\n  }\n  function _createClass(Constructor, protoProps, staticProps) {\n    if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n    if (staticProps) _defineProperties(Constructor, staticProps);\n    Object.defineProperty(Constructor, \"prototype\", {\n      writable: false\n    });\n    return Constructor;\n  }\n  function _defineEnumerableProperties(obj, descs) {\n    for (var key in descs) {\n      var desc = descs[key];\n      desc.configurable = desc.enumerable = true;\n      if (\"value\" in desc) desc.writable = true;\n      Object.defineProperty(obj, key, desc);\n    }\n    if (Object.getOwnPropertySymbols) {\n      var objectSymbols = Object.getOwnPropertySymbols(descs);\n      for (var i = 0; i < objectSymbols.length; i++) {\n        var sym = objectSymbols[i];\n        var desc = descs[sym];\n        desc.configurable = desc.enumerable = true;\n        if (\"value\" in desc) desc.writable = true;\n        Object.defineProperty(obj, sym, desc);\n      }\n    }\n    return obj;\n  }\n  function _defaults(obj, defaults) {\n    var keys = Object.getOwnPropertyNames(defaults);\n    for (var i = 0; i < keys.length; i++) {\n      var key = keys[i];\n      var value = Object.getOwnPropertyDescriptor(defaults, key);\n      if (value && value.configurable && obj[key] === undefined) {\n        Object.defineProperty(obj, key, value);\n      }\n    }\n    return obj;\n  }\n  function _defineProperty(obj, key, value) {\n    key = _toPropertyKey(key);\n    if (key in obj) {\n      Object.defineProperty(obj, key, {\n        value: value,\n        enumerable: true,\n        configurable: true,\n        writable: true\n      });\n    } else {\n      obj[key] = value;\n    }\n    return obj;\n  }\n  function _extends() {\n    _extends = Object.assign ? Object.assign.bind() : function (target) {\n      for (var i = 1; i < arguments.length; i++) {\n        var source = arguments[i];\n        for (var key in source) {\n          if (Object.prototype.hasOwnProperty.call(source, key)) {\n            target[key] = source[key];\n          }\n        }\n      }\n      return target;\n    };\n    return _extends.apply(this, arguments);\n  }\n  function _objectSpread(target) {\n    for (var i = 1; i < arguments.length; i++) {\n      var source = arguments[i] != null ? Object(arguments[i]) : {};\n      var ownKeys = Object.keys(source);\n      if (typeof Object.getOwnPropertySymbols === 'function') {\n        ownKeys.push.apply(ownKeys, Object.getOwnPropertySymbols(source).filter(function (sym) {\n          return Object.getOwnPropertyDescriptor(source, sym).enumerable;\n        }));\n      }\n      ownKeys.forEach(function (key) {\n        _defineProperty(target, key, source[key]);\n      });\n    }\n    return target;\n  }\n  function _inherits(subClass, superClass) {\n    if (typeof superClass !== \"function\" && superClass !== null) {\n      throw new TypeError(\"Super expression must either be null or a function\");\n    }\n    subClass.prototype = Object.create(superClass && superClass.prototype, {\n      constructor: {\n        value: subClass,\n        writable: true,\n        configurable: true\n      }\n    });\n    Object.defineProperty(subClass, \"prototype\", {\n      writable: false\n    });\n    if (superClass) _setPrototypeOf(subClass, superClass);\n  }\n  function _inheritsLoose(subClass, superClass) {\n    subClass.prototype = Object.create(superClass.prototype);\n    subClass.prototype.constructor = subClass;\n    _setPrototypeOf(subClass, superClass);\n  }\n  function _getPrototypeOf(o) {\n    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {\n      return o.__proto__ || Object.getPrototypeOf(o);\n    };\n    return _getPrototypeOf(o);\n  }\n  function _setPrototypeOf(o, p) {\n    _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {\n      o.__proto__ = p;\n      return o;\n    };\n    return _setPrototypeOf(o, p);\n  }\n  function _isNativeFunction(fn) {\n    try {\n      return Function.toString.call(fn).indexOf(\"[native code]\") !== -1;\n    } catch (e) {\n      return typeof fn === \"function\";\n    }\n  }\n  function _wrapNativeSuper(Class) {\n    var _cache = typeof Map === \"function\" ? new Map() : undefined;\n    _wrapNativeSuper = function _wrapNativeSuper(Class) {\n      if (Class === null || !_isNativeFunction(Class)) return Class;\n      if (typeof Class !== \"function\") {\n        throw new TypeError(\"Super expression must either be null or a function\");\n      }\n      if (typeof _cache !== \"undefined\") {\n        if (_cache.has(Class)) return _cache.get(Class);\n        _cache.set(Class, Wrapper);\n      }\n      function Wrapper() {\n        return _construct(Class, arguments, _getPrototypeOf(this).constructor);\n      }\n      Wrapper.prototype = Object.create(Class.prototype, {\n        constructor: {\n          value: Wrapper,\n          enumerable: false,\n          writable: true,\n          configurable: true\n        }\n      });\n      return _setPrototypeOf(Wrapper, Class);\n    };\n    return _wrapNativeSuper(Class);\n  }\n  function _instanceof(left, right) {\n    if (right != null && typeof Symbol !== \"undefined\" && right[Symbol.hasInstance]) {\n      return !!right[Symbol.hasInstance](left);\n    } else {\n      return left instanceof right;\n    }\n  }\n  function _interopRequireDefault(obj) {\n    return obj && obj.__esModule ? obj : {\n      default: obj\n    };\n  }\n  function _newArrowCheck(innerThis, boundThis) {\n    if (innerThis !== boundThis) {\n      throw new TypeError(\"Cannot instantiate an arrow function\");\n    }\n  }\n  function _objectDestructuringEmpty(obj) {\n    if (obj == null) throw new TypeError(\"Cannot destructure \" + obj);\n  }\n  function _objectWithoutPropertiesLoose(source, excluded) {\n    if (source == null) return {};\n    var target = {};\n    var sourceKeys = Object.keys(source);\n    var key, i;\n    for (i = 0; i < sourceKeys.length; i++) {\n      key = sourceKeys[i];\n      if (excluded.indexOf(key) >= 0) continue;\n      target[key] = source[key];\n    }\n    return target;\n  }\n  function _objectWithoutProperties(source, excluded) {\n    if (source == null) return {};\n    var target = _objectWithoutPropertiesLoose(source, excluded);\n    var key, i;\n    if (Object.getOwnPropertySymbols) {\n      var sourceSymbolKeys = Object.getOwnPropertySymbols(source);\n      for (i = 0; i < sourceSymbolKeys.length; i++) {\n        key = sourceSymbolKeys[i];\n        if (excluded.indexOf(key) >= 0) continue;\n        if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;\n        target[key] = source[key];\n      }\n    }\n    return target;\n  }\n  function _assertThisInitialized(self) {\n    if (self === void 0) {\n      throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");\n    }\n    return self;\n  }\n  function _possibleConstructorReturn(self, call) {\n    if (call && (typeof call === \"object\" || typeof call === \"function\")) {\n      return call;\n    } else if (call !== void 0) {\n      throw new TypeError(\"Derived constructors may only return object or undefined\");\n    }\n    return _assertThisInitialized(self);\n  }\n  function _createSuper(Derived) {\n    var hasNativeReflectConstruct = _isNativeReflectConstruct();\n    return function _createSuperInternal() {\n      var Super = _getPrototypeOf(Derived),\n        result;\n      if (hasNativeReflectConstruct) {\n        var NewTarget = _getPrototypeOf(this).constructor;\n        result = Reflect.construct(Super, arguments, NewTarget);\n      } else {\n        result = Super.apply(this, arguments);\n      }\n      return _possibleConstructorReturn(this, result);\n    };\n  }\n  function _superPropBase(object, property) {\n    while (!Object.prototype.hasOwnProperty.call(object, property)) {\n      object = _getPrototypeOf(object);\n      if (object === null) break;\n    }\n    return object;\n  }\n  function _get() {\n    if (typeof Reflect !== \"undefined\" && Reflect.get) {\n      _get = Reflect.get.bind();\n    } else {\n      _get = function _get(target, property, receiver) {\n        var base = _superPropBase(target, property);\n        if (!base) return;\n        var desc = Object.getOwnPropertyDescriptor(base, property);\n        if (desc.get) {\n          return desc.get.call(arguments.length < 3 ? target : receiver);\n        }\n        return desc.value;\n      };\n    }\n    return _get.apply(this, arguments);\n  }\n  function set(target, property, value, receiver) {\n    if (typeof Reflect !== \"undefined\" && Reflect.set) {\n      set = Reflect.set;\n    } else {\n      set = function set(target, property, value, receiver) {\n        var base = _superPropBase(target, property);\n        var desc;\n        if (base) {\n          desc = Object.getOwnPropertyDescriptor(base, property);\n          if (desc.set) {\n            desc.set.call(receiver, value);\n            return true;\n          } else if (!desc.writable) {\n            return false;\n          }\n        }\n        desc = Object.getOwnPropertyDescriptor(receiver, property);\n        if (desc) {\n          if (!desc.writable) {\n            return false;\n          }\n          desc.value = value;\n          Object.defineProperty(receiver, property, desc);\n        } else {\n          _defineProperty(receiver, property, value);\n        }\n        return true;\n      };\n    }\n    return set(target, property, value, receiver);\n  }\n  function _set(target, property, value, receiver, isStrict) {\n    var s = set(target, property, value, receiver || target);\n    if (!s && isStrict) {\n      throw new TypeError('failed to set property');\n    }\n    return value;\n  }\n  function _taggedTemplateLiteral(strings, raw) {\n    if (!raw) {\n      raw = strings.slice(0);\n    }\n    return Object.freeze(Object.defineProperties(strings, {\n      raw: {\n        value: Object.freeze(raw)\n      }\n    }));\n  }\n  function _taggedTemplateLiteralLoose(strings, raw) {\n    if (!raw) {\n      raw = strings.slice(0);\n    }\n    strings.raw = raw;\n    return strings;\n  }\n  function _readOnlyError(name) {\n    throw new TypeError(\"\\\"\" + name + \"\\\" is read-only\");\n  }\n  function _writeOnlyError(name) {\n    throw new TypeError(\"\\\"\" + name + \"\\\" is write-only\");\n  }\n  function _classNameTDZError(name) {\n    throw new ReferenceError(\"Class \\\"\" + name + \"\\\" cannot be referenced in computed property keys.\");\n  }\n  function _temporalUndefined() {}\n  function _tdz(name) {\n    throw new ReferenceError(name + \" is not defined - temporal dead zone\");\n  }\n  function _temporalRef(val, name) {\n    return val === _temporalUndefined ? _tdz(name) : val;\n  }\n  function _slicedToArray(arr, i) {\n    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n  }\n  function _slicedToArrayLoose(arr, i) {\n    return _arrayWithHoles(arr) || _iterableToArrayLimitLoose(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n  }\n  function _toArray(arr) {\n    return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest();\n  }\n  function _toConsumableArray(arr) {\n    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();\n  }\n  function _arrayWithoutHoles(arr) {\n    if (Array.isArray(arr)) return _arrayLikeToArray(arr);\n  }\n  function _arrayWithHoles(arr) {\n    if (Array.isArray(arr)) return arr;\n  }\n  function _maybeArrayLike(next, arr, i) {\n    if (arr && !Array.isArray(arr) && typeof arr.length === \"number\") {\n      var len = arr.length;\n      return _arrayLikeToArray(arr, i !== void 0 && i < len ? i : len);\n    }\n    return next(arr, i);\n  }\n  function _iterableToArray(iter) {\n    if (typeof Symbol !== \"undefined\" && iter[Symbol.iterator] != null || iter[\"@@iterator\"] != null) return Array.from(iter);\n  }\n  function _unsupportedIterableToArray(o, minLen) {\n    if (!o) return;\n    if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n    var n = Object.prototype.toString.call(o).slice(8, -1);\n    if (n === \"Object\" && o.constructor) n = o.constructor.name;\n    if (n === \"Map\" || n === \"Set\") return Array.from(o);\n    if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n  }\n  function _arrayLikeToArray(arr, len) {\n    if (len == null || len > arr.length) len = arr.length;\n    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n    return arr2;\n  }\n  function _nonIterableSpread() {\n    throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n  }\n  function _nonIterableRest() {\n    throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n  }\n  function _createForOfIteratorHelper(o, allowArrayLike) {\n    var it = typeof Symbol !== \"undefined\" && o[Symbol.iterator] || o[\"@@iterator\"];\n    if (!it) {\n      if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") {\n        if (it) o = it;\n        var i = 0;\n        var F = function () {};\n        return {\n          s: F,\n          n: function () {\n            if (i >= o.length) return {\n              done: true\n            };\n            return {\n              done: false,\n              value: o[i++]\n            };\n          },\n          e: function (e) {\n            throw e;\n          },\n          f: F\n        };\n      }\n      throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n    }\n    var normalCompletion = true,\n      didErr = false,\n      err;\n    return {\n      s: function () {\n        it = it.call(o);\n      },\n      n: function () {\n        var step = it.next();\n        normalCompletion = step.done;\n        return step;\n      },\n      e: function (e) {\n        didErr = true;\n        err = e;\n      },\n      f: function () {\n        try {\n          if (!normalCompletion && it.return != null) it.return();\n        } finally {\n          if (didErr) throw err;\n        }\n      }\n    };\n  }\n  function _createForOfIteratorHelperLoose(o, allowArrayLike) {\n    var it = typeof Symbol !== \"undefined\" && o[Symbol.iterator] || o[\"@@iterator\"];\n    if (it) return (it = it.call(o)).next.bind(it);\n    if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") {\n      if (it) o = it;\n      var i = 0;\n      return function () {\n        if (i >= o.length) return {\n          done: true\n        };\n        return {\n          done: false,\n          value: o[i++]\n        };\n      };\n    }\n    throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n  }\n  function _skipFirstGeneratorNext(fn) {\n    return function () {\n      var it = fn.apply(this, arguments);\n      it.next();\n      return it;\n    };\n  }\n  function _initializerWarningHelper(descriptor, context) {\n    throw new Error('Decorating class property failed. Please ensure that ' + 'transform-class-properties is enabled and runs after the decorators transform.');\n  }\n  function _initializerDefineProperty(target, property, descriptor, context) {\n    if (!descriptor) return;\n    Object.defineProperty(target, property, {\n      enumerable: descriptor.enumerable,\n      configurable: descriptor.configurable,\n      writable: descriptor.writable,\n      value: descriptor.initializer ? descriptor.initializer.call(context) : void 0\n    });\n  }\n  function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {\n    var desc = {};\n    Object.keys(descriptor).forEach(function (key) {\n      desc[key] = descriptor[key];\n    });\n    desc.enumerable = !!desc.enumerable;\n    desc.configurable = !!desc.configurable;\n    if ('value' in desc || desc.initializer) {\n      desc.writable = true;\n    }\n    desc = decorators.slice().reverse().reduce(function (desc, decorator) {\n      return decorator(target, property, desc) || desc;\n    }, desc);\n    if (context && desc.initializer !== void 0) {\n      desc.value = desc.initializer ? desc.initializer.call(context) : void 0;\n      desc.initializer = undefined;\n    }\n    if (desc.initializer === void 0) {\n      Object.defineProperty(target, property, desc);\n      desc = null;\n    }\n    return desc;\n  }\n  var id$1 = 0;\n  function _classPrivateFieldLooseKey(name) {\n    return \"__private_\" + id$1++ + \"_\" + name;\n  }\n  function _classPrivateFieldLooseBase(receiver, privateKey) {\n    if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {\n      throw new TypeError(\"attempted to use private field on non-instance\");\n    }\n    return receiver;\n  }\n  function _classPrivateFieldGet(receiver, privateMap) {\n    var descriptor = _classExtractFieldDescriptor(receiver, privateMap, \"get\");\n    return _classApplyDescriptorGet(receiver, descriptor);\n  }\n  function _classPrivateFieldSet(receiver, privateMap, value) {\n    var descriptor = _classExtractFieldDescriptor(receiver, privateMap, \"set\");\n    _classApplyDescriptorSet(receiver, descriptor, value);\n    return value;\n  }\n  function _classPrivateFieldDestructureSet(receiver, privateMap) {\n    var descriptor = _classExtractFieldDescriptor(receiver, privateMap, \"set\");\n    return _classApplyDescriptorDestructureSet(receiver, descriptor);\n  }\n  function _classExtractFieldDescriptor(receiver, privateMap, action) {\n    if (!privateMap.has(receiver)) {\n      throw new TypeError(\"attempted to \" + action + \" private field on non-instance\");\n    }\n    return privateMap.get(receiver);\n  }\n  function _classStaticPrivateFieldSpecGet(receiver, classConstructor, descriptor) {\n    _classCheckPrivateStaticAccess(receiver, classConstructor);\n    _classCheckPrivateStaticFieldDescriptor(descriptor, \"get\");\n    return _classApplyDescriptorGet(receiver, descriptor);\n  }\n  function _classStaticPrivateFieldSpecSet(receiver, classConstructor, descriptor, value) {\n    _classCheckPrivateStaticAccess(receiver, classConstructor);\n    _classCheckPrivateStaticFieldDescriptor(descriptor, \"set\");\n    _classApplyDescriptorSet(receiver, descriptor, value);\n    return value;\n  }\n  function _classStaticPrivateMethodGet(receiver, classConstructor, method) {\n    _classCheckPrivateStaticAccess(receiver, classConstructor);\n    return method;\n  }\n  function _classStaticPrivateMethodSet() {\n    throw new TypeError(\"attempted to set read only static private field\");\n  }\n  function _classApplyDescriptorGet(receiver, descriptor) {\n    if (descriptor.get) {\n      return descriptor.get.call(receiver);\n    }\n    return descriptor.value;\n  }\n  function _classApplyDescriptorSet(receiver, descriptor, value) {\n    if (descriptor.set) {\n      descriptor.set.call(receiver, value);\n    } else {\n      if (!descriptor.writable) {\n        throw new TypeError(\"attempted to set read only private field\");\n      }\n      descriptor.value = value;\n    }\n  }\n  function _classApplyDescriptorDestructureSet(receiver, descriptor) {\n    if (descriptor.set) {\n      if (!(\"__destrObj\" in descriptor)) {\n        descriptor.__destrObj = {\n          set value(v) {\n            descriptor.set.call(receiver, v);\n          }\n        };\n      }\n      return descriptor.__destrObj;\n    } else {\n      if (!descriptor.writable) {\n        throw new TypeError(\"attempted to set read only private field\");\n      }\n      return descriptor;\n    }\n  }\n  function _classStaticPrivateFieldDestructureSet(receiver, classConstructor, descriptor) {\n    _classCheckPrivateStaticAccess(receiver, classConstructor);\n    _classCheckPrivateStaticFieldDescriptor(descriptor, \"set\");\n    return _classApplyDescriptorDestructureSet(receiver, descriptor);\n  }\n  function _classCheckPrivateStaticAccess(receiver, classConstructor) {\n    if (receiver !== classConstructor) {\n      throw new TypeError(\"Private static access of wrong provenance\");\n    }\n  }\n  function _classCheckPrivateStaticFieldDescriptor(descriptor, action) {\n    if (descriptor === undefined) {\n      throw new TypeError(\"attempted to \" + action + \" private static field before its declaration\");\n    }\n  }\n  function _decorate(decorators, factory, superClass, mixins) {\n    var api = _getDecoratorsApi();\n    if (mixins) {\n      for (var i = 0; i < mixins.length; i++) {\n        api = mixins[i](api);\n      }\n    }\n    var r = factory(function initialize(O) {\n      api.initializeInstanceElements(O, decorated.elements);\n    }, superClass);\n    var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators);\n    api.initializeClassElements(r.F, decorated.elements);\n    return api.runClassFinishers(r.F, decorated.finishers);\n  }\n  function _getDecoratorsApi() {\n    _getDecoratorsApi = function () {\n      return api;\n    };\n    var api = {\n      elementsDefinitionOrder: [[\"method\"], [\"field\"]],\n      initializeInstanceElements: function (O, elements) {\n        [\"method\", \"field\"].forEach(function (kind) {\n          elements.forEach(function (element) {\n            if (element.kind === kind && element.placement === \"own\") {\n              this.defineClassElement(O, element);\n            }\n          }, this);\n        }, this);\n      },\n      initializeClassElements: function (F, elements) {\n        var proto = F.prototype;\n        [\"method\", \"field\"].forEach(function (kind) {\n          elements.forEach(function (element) {\n            var placement = element.placement;\n            if (element.kind === kind && (placement === \"static\" || placement === \"prototype\")) {\n              var receiver = placement === \"static\" ? F : proto;\n              this.defineClassElement(receiver, element);\n            }\n          }, this);\n        }, this);\n      },\n      defineClassElement: function (receiver, element) {\n        var descriptor = element.descriptor;\n        if (element.kind === \"field\") {\n          var initializer = element.initializer;\n          descriptor = {\n            enumerable: descriptor.enumerable,\n            writable: descriptor.writable,\n            configurable: descriptor.configurable,\n            value: initializer === void 0 ? void 0 : initializer.call(receiver)\n          };\n        }\n        Object.defineProperty(receiver, element.key, descriptor);\n      },\n      decorateClass: function (elements, decorators) {\n        var newElements = [];\n        var finishers = [];\n        var placements = {\n          static: [],\n          prototype: [],\n          own: []\n        };\n        elements.forEach(function (element) {\n          this.addElementPlacement(element, placements);\n        }, this);\n        elements.forEach(function (element) {\n          if (!_hasDecorators(element)) return newElements.push(element);\n          var elementFinishersExtras = this.decorateElement(element, placements);\n          newElements.push(elementFinishersExtras.element);\n          newElements.push.apply(newElements, elementFinishersExtras.extras);\n          finishers.push.apply(finishers, elementFinishersExtras.finishers);\n        }, this);\n        if (!decorators) {\n          return {\n            elements: newElements,\n            finishers: finishers\n          };\n        }\n        var result = this.decorateConstructor(newElements, decorators);\n        finishers.push.apply(finishers, result.finishers);\n        result.finishers = finishers;\n        return result;\n      },\n      addElementPlacement: function (element, placements, silent) {\n        var keys = placements[element.placement];\n        if (!silent && keys.indexOf(element.key) !== -1) {\n          throw new TypeError(\"Duplicated element (\" + element.key + \")\");\n        }\n        keys.push(element.key);\n      },\n      decorateElement: function (element, placements) {\n        var extras = [];\n        var finishers = [];\n        for (var decorators = element.decorators, i = decorators.length - 1; i >= 0; i--) {\n          var keys = placements[element.placement];\n          keys.splice(keys.indexOf(element.key), 1);\n          var elementObject = this.fromElementDescriptor(element);\n          var elementFinisherExtras = this.toElementFinisherExtras((0, decorators[i])(elementObject) || elementObject);\n          element = elementFinisherExtras.element;\n          this.addElementPlacement(element, placements);\n          if (elementFinisherExtras.finisher) {\n            finishers.push(elementFinisherExtras.finisher);\n          }\n          var newExtras = elementFinisherExtras.extras;\n          if (newExtras) {\n            for (var j = 0; j < newExtras.length; j++) {\n              this.addElementPlacement(newExtras[j], placements);\n            }\n            extras.push.apply(extras, newExtras);\n          }\n        }\n        return {\n          element: element,\n          finishers: finishers,\n          extras: extras\n        };\n      },\n      decorateConstructor: function (elements, decorators) {\n        var finishers = [];\n        for (var i = decorators.length - 1; i >= 0; i--) {\n          var obj = this.fromClassDescriptor(elements);\n          var elementsAndFinisher = this.toClassDescriptor((0, decorators[i])(obj) || obj);\n          if (elementsAndFinisher.finisher !== undefined) {\n            finishers.push(elementsAndFinisher.finisher);\n          }\n          if (elementsAndFinisher.elements !== undefined) {\n            elements = elementsAndFinisher.elements;\n            for (var j = 0; j < elements.length - 1; j++) {\n              for (var k = j + 1; k < elements.length; k++) {\n                if (elements[j].key === elements[k].key && elements[j].placement === elements[k].placement) {\n                  throw new TypeError(\"Duplicated element (\" + elements[j].key + \")\");\n                }\n              }\n            }\n          }\n        }\n        return {\n          elements: elements,\n          finishers: finishers\n        };\n      },\n      fromElementDescriptor: function (element) {\n        var obj = {\n          kind: element.kind,\n          key: element.key,\n          placement: element.placement,\n          descriptor: element.descriptor\n        };\n        var desc = {\n          value: \"Descriptor\",\n          configurable: true\n        };\n        Object.defineProperty(obj, Symbol.toStringTag, desc);\n        if (element.kind === \"field\") obj.initializer = element.initializer;\n        return obj;\n      },\n      toElementDescriptors: function (elementObjects) {\n        if (elementObjects === undefined) return;\n        return _toArray(elementObjects).map(function (elementObject) {\n          var element = this.toElementDescriptor(elementObject);\n          this.disallowProperty(elementObject, \"finisher\", \"An element descriptor\");\n          this.disallowProperty(elementObject, \"extras\", \"An element descriptor\");\n          return element;\n        }, this);\n      },\n      toElementDescriptor: function (elementObject) {\n        var kind = String(elementObject.kind);\n        if (kind !== \"method\" && kind !== \"field\") {\n          throw new TypeError('An element descriptor\\'s .kind property must be either \"method\" or' + ' \"field\", but a decorator created an element descriptor with' + ' .kind \"' + kind + '\"');\n        }\n        var key = _toPropertyKey(elementObject.key);\n        var placement = String(elementObject.placement);\n        if (placement !== \"static\" && placement !== \"prototype\" && placement !== \"own\") {\n          throw new TypeError('An element descriptor\\'s .placement property must be one of \"static\",' + ' \"prototype\" or \"own\", but a decorator created an element descriptor' + ' with .placement \"' + placement + '\"');\n        }\n        var descriptor = elementObject.descriptor;\n        this.disallowProperty(elementObject, \"elements\", \"An element descriptor\");\n        var element = {\n          kind: kind,\n          key: key,\n          placement: placement,\n          descriptor: Object.assign({}, descriptor)\n        };\n        if (kind !== \"field\") {\n          this.disallowProperty(elementObject, \"initializer\", \"A method descriptor\");\n        } else {\n          this.disallowProperty(descriptor, \"get\", \"The property descriptor of a field descriptor\");\n          this.disallowProperty(descriptor, \"set\", \"The property descriptor of a field descriptor\");\n          this.disallowProperty(descriptor, \"value\", \"The property descriptor of a field descriptor\");\n          element.initializer = elementObject.initializer;\n        }\n        return element;\n      },\n      toElementFinisherExtras: function (elementObject) {\n        var element = this.toElementDescriptor(elementObject);\n        var finisher = _optionalCallableProperty(elementObject, \"finisher\");\n        var extras = this.toElementDescriptors(elementObject.extras);\n        return {\n          element: element,\n          finisher: finisher,\n          extras: extras\n        };\n      },\n      fromClassDescriptor: function (elements) {\n        var obj = {\n          kind: \"class\",\n          elements: elements.map(this.fromElementDescriptor, this)\n        };\n        var desc = {\n          value: \"Descriptor\",\n          configurable: true\n        };\n        Object.defineProperty(obj, Symbol.toStringTag, desc);\n        return obj;\n      },\n      toClassDescriptor: function (obj) {\n        var kind = String(obj.kind);\n        if (kind !== \"class\") {\n          throw new TypeError('A class descriptor\\'s .kind property must be \"class\", but a decorator' + ' created a class descriptor with .kind \"' + kind + '\"');\n        }\n        this.disallowProperty(obj, \"key\", \"A class descriptor\");\n        this.disallowProperty(obj, \"placement\", \"A class descriptor\");\n        this.disallowProperty(obj, \"descriptor\", \"A class descriptor\");\n        this.disallowProperty(obj, \"initializer\", \"A class descriptor\");\n        this.disallowProperty(obj, \"extras\", \"A class descriptor\");\n        var finisher = _optionalCallableProperty(obj, \"finisher\");\n        var elements = this.toElementDescriptors(obj.elements);\n        return {\n          elements: elements,\n          finisher: finisher\n        };\n      },\n      runClassFinishers: function (constructor, finishers) {\n        for (var i = 0; i < finishers.length; i++) {\n          var newConstructor = (0, finishers[i])(constructor);\n          if (newConstructor !== undefined) {\n            if (typeof newConstructor !== \"function\") {\n              throw new TypeError(\"Finishers must return a constructor.\");\n            }\n            constructor = newConstructor;\n          }\n        }\n        return constructor;\n      },\n      disallowProperty: function (obj, name, objectType) {\n        if (obj[name] !== undefined) {\n          throw new TypeError(objectType + \" can't have a .\" + name + \" property.\");\n        }\n      }\n    };\n    return api;\n  }\n  function _createElementDescriptor(def) {\n    var key = _toPropertyKey(def.key);\n    var descriptor;\n    if (def.kind === \"method\") {\n      descriptor = {\n        value: def.value,\n        writable: true,\n        configurable: true,\n        enumerable: false\n      };\n    } else if (def.kind === \"get\") {\n      descriptor = {\n        get: def.value,\n        configurable: true,\n        enumerable: false\n      };\n    } else if (def.kind === \"set\") {\n      descriptor = {\n        set: def.value,\n        configurable: true,\n        enumerable: false\n      };\n    } else if (def.kind === \"field\") {\n      descriptor = {\n        configurable: true,\n        writable: true,\n        enumerable: true\n      };\n    }\n    var element = {\n      kind: def.kind === \"field\" ? \"field\" : \"method\",\n      key: key,\n      placement: def.static ? \"static\" : def.kind === \"field\" ? \"own\" : \"prototype\",\n      descriptor: descriptor\n    };\n    if (def.decorators) element.decorators = def.decorators;\n    if (def.kind === \"field\") element.initializer = def.value;\n    return element;\n  }\n  function _coalesceGetterSetter(element, other) {\n    if (element.descriptor.get !== undefined) {\n      other.descriptor.get = element.descriptor.get;\n    } else {\n      other.descriptor.set = element.descriptor.set;\n    }\n  }\n  function _coalesceClassElements(elements) {\n    var newElements = [];\n    var isSameElement = function (other) {\n      return other.kind === \"method\" && other.key === element.key && other.placement === element.placement;\n    };\n    for (var i = 0; i < elements.length; i++) {\n      var element = elements[i];\n      var other;\n      if (element.kind === \"method\" && (other = newElements.find(isSameElement))) {\n        if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) {\n          if (_hasDecorators(element) || _hasDecorators(other)) {\n            throw new ReferenceError(\"Duplicated methods (\" + element.key + \") can't be decorated.\");\n          }\n          other.descriptor = element.descriptor;\n        } else {\n          if (_hasDecorators(element)) {\n            if (_hasDecorators(other)) {\n              throw new ReferenceError(\"Decorators can't be placed on different accessors with for \" + \"the same property (\" + element.key + \").\");\n            }\n            other.decorators = element.decorators;\n          }\n          _coalesceGetterSetter(element, other);\n        }\n      } else {\n        newElements.push(element);\n      }\n    }\n    return newElements;\n  }\n  function _hasDecorators(element) {\n    return element.decorators && element.decorators.length;\n  }\n  function _isDataDescriptor(desc) {\n    return desc !== undefined && !(desc.value === undefined && desc.writable === undefined);\n  }\n  function _optionalCallableProperty(obj, name) {\n    var value = obj[name];\n    if (value !== undefined && typeof value !== \"function\") {\n      throw new TypeError(\"Expected '\" + name + \"' to be a function\");\n    }\n    return value;\n  }\n  function _classPrivateMethodGet(receiver, privateSet, fn) {\n    if (!privateSet.has(receiver)) {\n      throw new TypeError(\"attempted to get private field on non-instance\");\n    }\n    return fn;\n  }\n  function _checkPrivateRedeclaration(obj, privateCollection) {\n    if (privateCollection.has(obj)) {\n      throw new TypeError(\"Cannot initialize the same private elements twice on an object\");\n    }\n  }\n  function _classPrivateFieldInitSpec(obj, privateMap, value) {\n    _checkPrivateRedeclaration(obj, privateMap);\n    privateMap.set(obj, value);\n  }\n  function _classPrivateMethodInitSpec(obj, privateSet) {\n    _checkPrivateRedeclaration(obj, privateSet);\n    privateSet.add(obj);\n  }\n  function _classPrivateMethodSet() {\n    throw new TypeError(\"attempted to reassign private method\");\n  }\n  function _identity(x) {\n    return x;\n  }\n  function _nullishReceiverError(r) {\n    throw new TypeError(\"Cannot set property of null or undefined.\");\n  }\n\n  class Piece extends TrixObject {\n    static registerType(type, constructor) {\n      constructor.type = type;\n      this.types[type] = constructor;\n    }\n    static fromJSON(pieceJSON) {\n      const constructor = this.types[pieceJSON.type];\n      if (constructor) {\n        return constructor.fromJSON(pieceJSON);\n      }\n    }\n    constructor(value) {\n      let attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      super(...arguments);\n      this.attributes = Hash.box(attributes);\n    }\n    copyWithAttributes(attributes) {\n      return new this.constructor(this.getValue(), attributes);\n    }\n    copyWithAdditionalAttributes(attributes) {\n      return this.copyWithAttributes(this.attributes.merge(attributes));\n    }\n    copyWithoutAttribute(attribute) {\n      return this.copyWithAttributes(this.attributes.remove(attribute));\n    }\n    copy() {\n      return this.copyWithAttributes(this.attributes);\n    }\n    getAttribute(attribute) {\n      return this.attributes.get(attribute);\n    }\n    getAttributesHash() {\n      return this.attributes;\n    }\n    getAttributes() {\n      return this.attributes.toObject();\n    }\n    hasAttribute(attribute) {\n      return this.attributes.has(attribute);\n    }\n    hasSameStringValueAsPiece(piece) {\n      return piece && this.toString() === piece.toString();\n    }\n    hasSameAttributesAsPiece(piece) {\n      return piece && (this.attributes === piece.attributes || this.attributes.isEqualTo(piece.attributes));\n    }\n    isBlockBreak() {\n      return false;\n    }\n    isEqualTo(piece) {\n      return super.isEqualTo(...arguments) || this.hasSameConstructorAs(piece) && this.hasSameStringValueAsPiece(piece) && this.hasSameAttributesAsPiece(piece);\n    }\n    isEmpty() {\n      return this.length === 0;\n    }\n    isSerializable() {\n      return true;\n    }\n    toJSON() {\n      return {\n        type: this.constructor.type,\n        attributes: this.getAttributes()\n      };\n    }\n    contentsForInspection() {\n      return {\n        type: this.constructor.type,\n        attributes: this.attributes.inspect()\n      };\n    }\n\n    // Grouping\n\n    canBeGrouped() {\n      return this.hasAttribute(\"href\");\n    }\n    canBeGroupedWith(piece) {\n      return this.getAttribute(\"href\") === piece.getAttribute(\"href\");\n    }\n\n    // Splittable\n\n    getLength() {\n      return this.length;\n    }\n    canBeConsolidatedWith(piece) {\n      return false;\n    }\n  }\n  _defineProperty(Piece, \"types\", {});\n\n  class ImagePreloadOperation extends Operation {\n    constructor(url) {\n      super(...arguments);\n      this.url = url;\n    }\n    perform(callback) {\n      const image = new Image();\n      image.onload = () => {\n        image.width = this.width = image.naturalWidth;\n        image.height = this.height = image.naturalHeight;\n        return callback(true, image);\n      };\n      image.onerror = () => callback(false);\n      image.src = this.url;\n    }\n  }\n\n  class Attachment extends TrixObject {\n    static attachmentForFile(file) {\n      const attributes = this.attributesForFile(file);\n      const attachment = new this(attributes);\n      attachment.setFile(file);\n      return attachment;\n    }\n    static attributesForFile(file) {\n      return new Hash({\n        filename: file.name,\n        filesize: file.size,\n        contentType: file.type\n      });\n    }\n    static fromJSON(attachmentJSON) {\n      return new this(attachmentJSON);\n    }\n    constructor() {\n      let attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      super(attributes);\n      this.releaseFile = this.releaseFile.bind(this);\n      this.attributes = Hash.box(attributes);\n      this.didChangeAttributes();\n    }\n    setAttribute(attribute, value) {\n      this.setAttributes({\n        [attribute]: value\n      });\n    }\n    getAttribute(attribute) {\n      return this.attributes.get(attribute);\n    }\n    hasAttribute(attribute) {\n      return this.attributes.has(attribute);\n    }\n    getAttributes() {\n      return this.attributes.toObject();\n    }\n    setAttributes() {\n      let attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      const newAttributes = this.attributes.merge(attributes);\n      if (!this.attributes.isEqualTo(newAttributes)) {\n        var _this$previewDelegate, _this$previewDelegate2, _this$delegate, _this$delegate$attach;\n        this.attributes = newAttributes;\n        this.didChangeAttributes();\n        (_this$previewDelegate = this.previewDelegate) === null || _this$previewDelegate === void 0 || (_this$previewDelegate2 = _this$previewDelegate.attachmentDidChangeAttributes) === null || _this$previewDelegate2 === void 0 || _this$previewDelegate2.call(_this$previewDelegate, this);\n        return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$attach = _this$delegate.attachmentDidChangeAttributes) === null || _this$delegate$attach === void 0 ? void 0 : _this$delegate$attach.call(_this$delegate, this);\n      }\n    }\n    didChangeAttributes() {\n      if (this.isPreviewable()) {\n        return this.preloadURL();\n      }\n    }\n    isPending() {\n      return this.file != null && !(this.getURL() || this.getHref());\n    }\n    isPreviewable() {\n      if (this.attributes.has(\"previewable\")) {\n        return this.attributes.get(\"previewable\");\n      } else {\n        return Attachment.previewablePattern.test(this.getContentType());\n      }\n    }\n    getType() {\n      if (this.hasContent()) {\n        return \"content\";\n      } else if (this.isPreviewable()) {\n        return \"preview\";\n      } else {\n        return \"file\";\n      }\n    }\n    getURL() {\n      return this.attributes.get(\"url\");\n    }\n    getHref() {\n      return this.attributes.get(\"href\");\n    }\n    getFilename() {\n      return this.attributes.get(\"filename\") || \"\";\n    }\n    getFilesize() {\n      return this.attributes.get(\"filesize\");\n    }\n    getFormattedFilesize() {\n      const filesize = this.attributes.get(\"filesize\");\n      if (typeof filesize === \"number\") {\n        return file_size_formatting.formatter(filesize);\n      } else {\n        return \"\";\n      }\n    }\n    getExtension() {\n      var _this$getFilename$mat;\n      return (_this$getFilename$mat = this.getFilename().match(/\\.(\\w+)$/)) === null || _this$getFilename$mat === void 0 ? void 0 : _this$getFilename$mat[1].toLowerCase();\n    }\n    getContentType() {\n      return this.attributes.get(\"contentType\");\n    }\n    hasContent() {\n      return this.attributes.has(\"content\");\n    }\n    getContent() {\n      return this.attributes.get(\"content\");\n    }\n    getWidth() {\n      return this.attributes.get(\"width\");\n    }\n    getHeight() {\n      return this.attributes.get(\"height\");\n    }\n    getFile() {\n      return this.file;\n    }\n    setFile(file) {\n      this.file = file;\n      if (this.isPreviewable()) {\n        return this.preloadFile();\n      }\n    }\n    releaseFile() {\n      this.releasePreloadedFile();\n      this.file = null;\n    }\n    getUploadProgress() {\n      return this.uploadProgress != null ? this.uploadProgress : 0;\n    }\n    setUploadProgress(value) {\n      if (this.uploadProgress !== value) {\n        var _this$uploadProgressD, _this$uploadProgressD2;\n        this.uploadProgress = value;\n        return (_this$uploadProgressD = this.uploadProgressDelegate) === null || _this$uploadProgressD === void 0 || (_this$uploadProgressD2 = _this$uploadProgressD.attachmentDidChangeUploadProgress) === null || _this$uploadProgressD2 === void 0 ? void 0 : _this$uploadProgressD2.call(_this$uploadProgressD, this);\n      }\n    }\n    toJSON() {\n      return this.getAttributes();\n    }\n    getCacheKey() {\n      return [super.getCacheKey(...arguments), this.attributes.getCacheKey(), this.getPreviewURL()].join(\"/\");\n    }\n\n    // Previewable\n\n    getPreviewURL() {\n      return this.previewURL || this.preloadingURL;\n    }\n    setPreviewURL(url) {\n      if (url !== this.getPreviewURL()) {\n        var _this$previewDelegate3, _this$previewDelegate4, _this$delegate2, _this$delegate2$attac;\n        this.previewURL = url;\n        (_this$previewDelegate3 = this.previewDelegate) === null || _this$previewDelegate3 === void 0 || (_this$previewDelegate4 = _this$previewDelegate3.attachmentDidChangeAttributes) === null || _this$previewDelegate4 === void 0 || _this$previewDelegate4.call(_this$previewDelegate3, this);\n        return (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$attac = _this$delegate2.attachmentDidChangePreviewURL) === null || _this$delegate2$attac === void 0 ? void 0 : _this$delegate2$attac.call(_this$delegate2, this);\n      }\n    }\n    preloadURL() {\n      return this.preload(this.getURL(), this.releaseFile);\n    }\n    preloadFile() {\n      if (this.file) {\n        this.fileObjectURL = URL.createObjectURL(this.file);\n        return this.preload(this.fileObjectURL);\n      }\n    }\n    releasePreloadedFile() {\n      if (this.fileObjectURL) {\n        URL.revokeObjectURL(this.fileObjectURL);\n        this.fileObjectURL = null;\n      }\n    }\n    preload(url, callback) {\n      if (url && url !== this.getPreviewURL()) {\n        this.preloadingURL = url;\n        const operation = new ImagePreloadOperation(url);\n        return operation.then(_ref => {\n          let {\n            width,\n            height\n          } = _ref;\n          if (!this.getWidth() || !this.getHeight()) {\n            this.setAttributes({\n              width,\n              height\n            });\n          }\n          this.preloadingURL = null;\n          this.setPreviewURL(url);\n          return callback === null || callback === void 0 ? void 0 : callback();\n        }).catch(() => {\n          this.preloadingURL = null;\n          return callback === null || callback === void 0 ? void 0 : callback();\n        });\n      }\n    }\n  }\n  _defineProperty(Attachment, \"previewablePattern\", /^image(\\/(gif|png|webp|jpe?g)|$)/);\n\n  class AttachmentPiece extends Piece {\n    static fromJSON(pieceJSON) {\n      return new this(Attachment.fromJSON(pieceJSON.attachment), pieceJSON.attributes);\n    }\n    constructor(attachment) {\n      super(...arguments);\n      this.attachment = attachment;\n      this.length = 1;\n      this.ensureAttachmentExclusivelyHasAttribute(\"href\");\n      if (!this.attachment.hasContent()) {\n        this.removeProhibitedAttributes();\n      }\n    }\n    ensureAttachmentExclusivelyHasAttribute(attribute) {\n      if (this.hasAttribute(attribute)) {\n        if (!this.attachment.hasAttribute(attribute)) {\n          this.attachment.setAttributes(this.attributes.slice([attribute]));\n        }\n        this.attributes = this.attributes.remove(attribute);\n      }\n    }\n    removeProhibitedAttributes() {\n      const attributes = this.attributes.slice(AttachmentPiece.permittedAttributes);\n      if (!attributes.isEqualTo(this.attributes)) {\n        this.attributes = attributes;\n      }\n    }\n    getValue() {\n      return this.attachment;\n    }\n    isSerializable() {\n      return !this.attachment.isPending();\n    }\n    getCaption() {\n      return this.attributes.get(\"caption\") || \"\";\n    }\n    isEqualTo(piece) {\n      var _piece$attachment;\n      return super.isEqualTo(piece) && this.attachment.id === (piece === null || piece === void 0 || (_piece$attachment = piece.attachment) === null || _piece$attachment === void 0 ? void 0 : _piece$attachment.id);\n    }\n    toString() {\n      return OBJECT_REPLACEMENT_CHARACTER;\n    }\n    toJSON() {\n      const json = super.toJSON(...arguments);\n      json.attachment = this.attachment;\n      return json;\n    }\n    getCacheKey() {\n      return [super.getCacheKey(...arguments), this.attachment.getCacheKey()].join(\"/\");\n    }\n    toConsole() {\n      return JSON.stringify(this.toString());\n    }\n  }\n  _defineProperty(AttachmentPiece, \"permittedAttributes\", [\"caption\", \"presentation\"]);\n  Piece.registerType(\"attachment\", AttachmentPiece);\n\n  class StringPiece extends Piece {\n    static fromJSON(pieceJSON) {\n      return new this(pieceJSON.string, pieceJSON.attributes);\n    }\n    constructor(string) {\n      super(...arguments);\n      this.string = normalizeNewlines(string);\n      this.length = this.string.length;\n    }\n    getValue() {\n      return this.string;\n    }\n    toString() {\n      return this.string.toString();\n    }\n    isBlockBreak() {\n      return this.toString() === \"\\n\" && this.getAttribute(\"blockBreak\") === true;\n    }\n    toJSON() {\n      const result = super.toJSON(...arguments);\n      result.string = this.string;\n      return result;\n    }\n\n    // Splittable\n\n    canBeConsolidatedWith(piece) {\n      return piece && this.hasSameConstructorAs(piece) && this.hasSameAttributesAsPiece(piece);\n    }\n    consolidateWith(piece) {\n      return new this.constructor(this.toString() + piece.toString(), this.attributes);\n    }\n    splitAtOffset(offset) {\n      let left, right;\n      if (offset === 0) {\n        left = null;\n        right = this;\n      } else if (offset === this.length) {\n        left = this;\n        right = null;\n      } else {\n        left = new this.constructor(this.string.slice(0, offset), this.attributes);\n        right = new this.constructor(this.string.slice(offset), this.attributes);\n      }\n      return [left, right];\n    }\n    toConsole() {\n      let {\n        string\n      } = this;\n      if (string.length > 15) {\n        string = string.slice(0, 14) + \"…\";\n      }\n      return JSON.stringify(string.toString());\n    }\n  }\n  Piece.registerType(\"string\", StringPiece);\n\n  /* eslint-disable\n      prefer-const,\n  */\n  class SplittableList extends TrixObject {\n    static box(objects) {\n      if (objects instanceof this) {\n        return objects;\n      } else {\n        return new this(objects);\n      }\n    }\n    constructor() {\n      let objects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      super(...arguments);\n      this.objects = objects.slice(0);\n      this.length = this.objects.length;\n    }\n    indexOf(object) {\n      return this.objects.indexOf(object);\n    }\n    splice() {\n      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n        args[_key] = arguments[_key];\n      }\n      return new this.constructor(spliceArray(this.objects, ...args));\n    }\n    eachObject(callback) {\n      return this.objects.map((object, index) => callback(object, index));\n    }\n    insertObjectAtIndex(object, index) {\n      return this.splice(index, 0, object);\n    }\n    insertSplittableListAtIndex(splittableList, index) {\n      return this.splice(index, 0, ...splittableList.objects);\n    }\n    insertSplittableListAtPosition(splittableList, position) {\n      const [objects, index] = this.splitObjectAtPosition(position);\n      return new this.constructor(objects).insertSplittableListAtIndex(splittableList, index);\n    }\n    editObjectAtIndex(index, callback) {\n      return this.replaceObjectAtIndex(callback(this.objects[index]), index);\n    }\n    replaceObjectAtIndex(object, index) {\n      return this.splice(index, 1, object);\n    }\n    removeObjectAtIndex(index) {\n      return this.splice(index, 1);\n    }\n    getObjectAtIndex(index) {\n      return this.objects[index];\n    }\n    getSplittableListInRange(range) {\n      const [objects, leftIndex, rightIndex] = this.splitObjectsAtRange(range);\n      return new this.constructor(objects.slice(leftIndex, rightIndex + 1));\n    }\n    selectSplittableList(test) {\n      const objects = this.objects.filter(object => test(object));\n      return new this.constructor(objects);\n    }\n    removeObjectsInRange(range) {\n      const [objects, leftIndex, rightIndex] = this.splitObjectsAtRange(range);\n      return new this.constructor(objects).splice(leftIndex, rightIndex - leftIndex + 1);\n    }\n    transformObjectsInRange(range, transform) {\n      const [objects, leftIndex, rightIndex] = this.splitObjectsAtRange(range);\n      const transformedObjects = objects.map((object, index) => leftIndex <= index && index <= rightIndex ? transform(object) : object);\n      return new this.constructor(transformedObjects);\n    }\n    splitObjectsAtRange(range) {\n      let rightOuterIndex;\n      let [objects, leftInnerIndex, offset] = this.splitObjectAtPosition(startOfRange(range));\n      [objects, rightOuterIndex] = new this.constructor(objects).splitObjectAtPosition(endOfRange(range) + offset);\n      return [objects, leftInnerIndex, rightOuterIndex - 1];\n    }\n    getObjectAtPosition(position) {\n      const {\n        index\n      } = this.findIndexAndOffsetAtPosition(position);\n      return this.objects[index];\n    }\n    splitObjectAtPosition(position) {\n      let splitIndex, splitOffset;\n      const {\n        index,\n        offset\n      } = this.findIndexAndOffsetAtPosition(position);\n      const objects = this.objects.slice(0);\n      if (index != null) {\n        if (offset === 0) {\n          splitIndex = index;\n          splitOffset = 0;\n        } else {\n          const object = this.getObjectAtIndex(index);\n          const [leftObject, rightObject] = object.splitAtOffset(offset);\n          objects.splice(index, 1, leftObject, rightObject);\n          splitIndex = index + 1;\n          splitOffset = leftObject.getLength() - offset;\n        }\n      } else {\n        splitIndex = objects.length;\n        splitOffset = 0;\n      }\n      return [objects, splitIndex, splitOffset];\n    }\n    consolidate() {\n      const objects = [];\n      let pendingObject = this.objects[0];\n      this.objects.slice(1).forEach(object => {\n        var _pendingObject$canBeC, _pendingObject;\n        if ((_pendingObject$canBeC = (_pendingObject = pendingObject).canBeConsolidatedWith) !== null && _pendingObject$canBeC !== void 0 && _pendingObject$canBeC.call(_pendingObject, object)) {\n          pendingObject = pendingObject.consolidateWith(object);\n        } else {\n          objects.push(pendingObject);\n          pendingObject = object;\n        }\n      });\n      if (pendingObject) {\n        objects.push(pendingObject);\n      }\n      return new this.constructor(objects);\n    }\n    consolidateFromIndexToIndex(startIndex, endIndex) {\n      const objects = this.objects.slice(0);\n      const objectsInRange = objects.slice(startIndex, endIndex + 1);\n      const consolidatedInRange = new this.constructor(objectsInRange).consolidate().toArray();\n      return this.splice(startIndex, objectsInRange.length, ...consolidatedInRange);\n    }\n    findIndexAndOffsetAtPosition(position) {\n      let index;\n      let currentPosition = 0;\n      for (index = 0; index < this.objects.length; index++) {\n        const object = this.objects[index];\n        const nextPosition = currentPosition + object.getLength();\n        if (currentPosition <= position && position < nextPosition) {\n          return {\n            index,\n            offset: position - currentPosition\n          };\n        }\n        currentPosition = nextPosition;\n      }\n      return {\n        index: null,\n        offset: null\n      };\n    }\n    findPositionAtIndexAndOffset(index, offset) {\n      let position = 0;\n      for (let currentIndex = 0; currentIndex < this.objects.length; currentIndex++) {\n        const object = this.objects[currentIndex];\n        if (currentIndex < index) {\n          position += object.getLength();\n        } else if (currentIndex === index) {\n          position += offset;\n          break;\n        }\n      }\n      return position;\n    }\n    getEndPosition() {\n      if (this.endPosition == null) {\n        this.endPosition = 0;\n        this.objects.forEach(object => this.endPosition += object.getLength());\n      }\n      return this.endPosition;\n    }\n    toString() {\n      return this.objects.join(\"\");\n    }\n    toArray() {\n      return this.objects.slice(0);\n    }\n    toJSON() {\n      return this.toArray();\n    }\n    isEqualTo(splittableList) {\n      return super.isEqualTo(...arguments) || objectArraysAreEqual(this.objects, splittableList === null || splittableList === void 0 ? void 0 : splittableList.objects);\n    }\n    contentsForInspection() {\n      return {\n        objects: \"[\".concat(this.objects.map(object => object.inspect()).join(\", \"), \"]\")\n      };\n    }\n  }\n  const objectArraysAreEqual = function (left) {\n    let right = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];\n    if (left.length !== right.length) {\n      return false;\n    }\n    let result = true;\n    for (let index = 0; index < left.length; index++) {\n      const object = left[index];\n      if (result && !object.isEqualTo(right[index])) {\n        result = false;\n      }\n    }\n    return result;\n  };\n  const startOfRange = range => range[0];\n  const endOfRange = range => range[1];\n\n  class Text extends TrixObject {\n    static textForAttachmentWithAttributes(attachment, attributes) {\n      const piece = new AttachmentPiece(attachment, attributes);\n      return new this([piece]);\n    }\n    static textForStringWithAttributes(string, attributes) {\n      const piece = new StringPiece(string, attributes);\n      return new this([piece]);\n    }\n    static fromJSON(textJSON) {\n      const pieces = Array.from(textJSON).map(pieceJSON => Piece.fromJSON(pieceJSON));\n      return new this(pieces);\n    }\n    constructor() {\n      let pieces = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      super(...arguments);\n      const notEmpty = pieces.filter(piece => !piece.isEmpty());\n      this.pieceList = new SplittableList(notEmpty);\n    }\n    copy() {\n      return this.copyWithPieceList(this.pieceList);\n    }\n    copyWithPieceList(pieceList) {\n      return new this.constructor(pieceList.consolidate().toArray());\n    }\n    copyUsingObjectMap(objectMap) {\n      const pieces = this.getPieces().map(piece => objectMap.find(piece) || piece);\n      return new this.constructor(pieces);\n    }\n    appendText(text) {\n      return this.insertTextAtPosition(text, this.getLength());\n    }\n    insertTextAtPosition(text, position) {\n      return this.copyWithPieceList(this.pieceList.insertSplittableListAtPosition(text.pieceList, position));\n    }\n    removeTextAtRange(range) {\n      return this.copyWithPieceList(this.pieceList.removeObjectsInRange(range));\n    }\n    replaceTextAtRange(text, range) {\n      return this.removeTextAtRange(range).insertTextAtPosition(text, range[0]);\n    }\n    moveTextFromRangeToPosition(range, position) {\n      if (range[0] <= position && position <= range[1]) return;\n      const text = this.getTextAtRange(range);\n      const length = text.getLength();\n      if (range[0] < position) {\n        position -= length;\n      }\n      return this.removeTextAtRange(range).insertTextAtPosition(text, position);\n    }\n    addAttributeAtRange(attribute, value, range) {\n      const attributes = {};\n      attributes[attribute] = value;\n      return this.addAttributesAtRange(attributes, range);\n    }\n    addAttributesAtRange(attributes, range) {\n      return this.copyWithPieceList(this.pieceList.transformObjectsInRange(range, piece => piece.copyWithAdditionalAttributes(attributes)));\n    }\n    removeAttributeAtRange(attribute, range) {\n      return this.copyWithPieceList(this.pieceList.transformObjectsInRange(range, piece => piece.copyWithoutAttribute(attribute)));\n    }\n    setAttributesAtRange(attributes, range) {\n      return this.copyWithPieceList(this.pieceList.transformObjectsInRange(range, piece => piece.copyWithAttributes(attributes)));\n    }\n    getAttributesAtPosition(position) {\n      var _this$pieceList$getOb;\n      return ((_this$pieceList$getOb = this.pieceList.getObjectAtPosition(position)) === null || _this$pieceList$getOb === void 0 ? void 0 : _this$pieceList$getOb.getAttributes()) || {};\n    }\n    getCommonAttributes() {\n      const objects = Array.from(this.pieceList.toArray()).map(piece => piece.getAttributes());\n      return Hash.fromCommonAttributesOfObjects(objects).toObject();\n    }\n    getCommonAttributesAtRange(range) {\n      return this.getTextAtRange(range).getCommonAttributes() || {};\n    }\n    getExpandedRangeForAttributeAtOffset(attributeName, offset) {\n      let right;\n      let left = right = offset;\n      const length = this.getLength();\n      while (left > 0 && this.getCommonAttributesAtRange([left - 1, right])[attributeName]) {\n        left--;\n      }\n      while (right < length && this.getCommonAttributesAtRange([offset, right + 1])[attributeName]) {\n        right++;\n      }\n      return [left, right];\n    }\n    getTextAtRange(range) {\n      return this.copyWithPieceList(this.pieceList.getSplittableListInRange(range));\n    }\n    getStringAtRange(range) {\n      return this.pieceList.getSplittableListInRange(range).toString();\n    }\n    getStringAtPosition(position) {\n      return this.getStringAtRange([position, position + 1]);\n    }\n    startsWithString(string) {\n      return this.getStringAtRange([0, string.length]) === string;\n    }\n    endsWithString(string) {\n      const length = this.getLength();\n      return this.getStringAtRange([length - string.length, length]) === string;\n    }\n    getAttachmentPieces() {\n      return this.pieceList.toArray().filter(piece => !!piece.attachment);\n    }\n    getAttachments() {\n      return this.getAttachmentPieces().map(piece => piece.attachment);\n    }\n    getAttachmentAndPositionById(attachmentId) {\n      let position = 0;\n      for (const piece of this.pieceList.toArray()) {\n        var _piece$attachment;\n        if (((_piece$attachment = piece.attachment) === null || _piece$attachment === void 0 ? void 0 : _piece$attachment.id) === attachmentId) {\n          return {\n            attachment: piece.attachment,\n            position\n          };\n        }\n        position += piece.length;\n      }\n      return {\n        attachment: null,\n        position: null\n      };\n    }\n    getAttachmentById(attachmentId) {\n      const {\n        attachment\n      } = this.getAttachmentAndPositionById(attachmentId);\n      return attachment;\n    }\n    getRangeOfAttachment(attachment) {\n      const attachmentAndPosition = this.getAttachmentAndPositionById(attachment.id);\n      const position = attachmentAndPosition.position;\n      attachment = attachmentAndPosition.attachment;\n      if (attachment) {\n        return [position, position + 1];\n      }\n    }\n    updateAttributesForAttachment(attributes, attachment) {\n      const range = this.getRangeOfAttachment(attachment);\n      if (range) {\n        return this.addAttributesAtRange(attributes, range);\n      } else {\n        return this;\n      }\n    }\n    getLength() {\n      return this.pieceList.getEndPosition();\n    }\n    isEmpty() {\n      return this.getLength() === 0;\n    }\n    isEqualTo(text) {\n      var _text$pieceList;\n      return super.isEqualTo(text) || (text === null || text === void 0 || (_text$pieceList = text.pieceList) === null || _text$pieceList === void 0 ? void 0 : _text$pieceList.isEqualTo(this.pieceList));\n    }\n    isBlockBreak() {\n      return this.getLength() === 1 && this.pieceList.getObjectAtIndex(0).isBlockBreak();\n    }\n    eachPiece(callback) {\n      return this.pieceList.eachObject(callback);\n    }\n    getPieces() {\n      return this.pieceList.toArray();\n    }\n    getPieceAtPosition(position) {\n      return this.pieceList.getObjectAtPosition(position);\n    }\n    contentsForInspection() {\n      return {\n        pieceList: this.pieceList.inspect()\n      };\n    }\n    toSerializableText() {\n      const pieceList = this.pieceList.selectSplittableList(piece => piece.isSerializable());\n      return this.copyWithPieceList(pieceList);\n    }\n    toString() {\n      return this.pieceList.toString();\n    }\n    toJSON() {\n      return this.pieceList.toJSON();\n    }\n    toConsole() {\n      return JSON.stringify(this.pieceList.toArray().map(piece => JSON.parse(piece.toConsole())));\n    }\n\n    // BIDI\n\n    getDirection() {\n      return getDirection(this.toString());\n    }\n    isRTL() {\n      return this.getDirection() === \"rtl\";\n    }\n  }\n\n  class Block extends TrixObject {\n    static fromJSON(blockJSON) {\n      const text = Text.fromJSON(blockJSON.text);\n      return new this(text, blockJSON.attributes, blockJSON.htmlAttributes);\n    }\n    constructor(text, attributes, htmlAttributes) {\n      super(...arguments);\n      this.text = applyBlockBreakToText(text || new Text());\n      this.attributes = attributes || [];\n      this.htmlAttributes = htmlAttributes || {};\n    }\n    isEmpty() {\n      return this.text.isBlockBreak();\n    }\n    isEqualTo(block) {\n      if (super.isEqualTo(block)) return true;\n      return this.text.isEqualTo(block === null || block === void 0 ? void 0 : block.text) && arraysAreEqual(this.attributes, block === null || block === void 0 ? void 0 : block.attributes) && objectsAreEqual(this.htmlAttributes, block === null || block === void 0 ? void 0 : block.htmlAttributes);\n    }\n    copyWithText(text) {\n      return new Block(text, this.attributes, this.htmlAttributes);\n    }\n    copyWithoutText() {\n      return this.copyWithText(null);\n    }\n    copyWithAttributes(attributes) {\n      return new Block(this.text, attributes, this.htmlAttributes);\n    }\n    copyWithoutAttributes() {\n      return this.copyWithAttributes(null);\n    }\n    copyUsingObjectMap(objectMap) {\n      const mappedText = objectMap.find(this.text);\n      if (mappedText) {\n        return this.copyWithText(mappedText);\n      } else {\n        return this.copyWithText(this.text.copyUsingObjectMap(objectMap));\n      }\n    }\n    addAttribute(attribute) {\n      const attributes = this.attributes.concat(expandAttribute(attribute));\n      return this.copyWithAttributes(attributes);\n    }\n    addHTMLAttribute(attribute, value) {\n      const htmlAttributes = Object.assign({}, this.htmlAttributes, {\n        [attribute]: value\n      });\n      return new Block(this.text, this.attributes, htmlAttributes);\n    }\n    removeAttribute(attribute) {\n      const {\n        listAttribute\n      } = getBlockConfig(attribute);\n      const attributes = removeLastValue(removeLastValue(this.attributes, attribute), listAttribute);\n      return this.copyWithAttributes(attributes);\n    }\n    removeLastAttribute() {\n      return this.removeAttribute(this.getLastAttribute());\n    }\n    getLastAttribute() {\n      return getLastElement(this.attributes);\n    }\n    getAttributes() {\n      return this.attributes.slice(0);\n    }\n    getAttributeLevel() {\n      return this.attributes.length;\n    }\n    getAttributeAtLevel(level) {\n      return this.attributes[level - 1];\n    }\n    hasAttribute(attributeName) {\n      return this.attributes.includes(attributeName);\n    }\n    hasAttributes() {\n      return this.getAttributeLevel() > 0;\n    }\n    getLastNestableAttribute() {\n      return getLastElement(this.getNestableAttributes());\n    }\n    getNestableAttributes() {\n      return this.attributes.filter(attribute => getBlockConfig(attribute).nestable);\n    }\n    getNestingLevel() {\n      return this.getNestableAttributes().length;\n    }\n    decreaseNestingLevel() {\n      const attribute = this.getLastNestableAttribute();\n      if (attribute) {\n        return this.removeAttribute(attribute);\n      } else {\n        return this;\n      }\n    }\n    increaseNestingLevel() {\n      const attribute = this.getLastNestableAttribute();\n      if (attribute) {\n        const index = this.attributes.lastIndexOf(attribute);\n        const attributes = spliceArray(this.attributes, index + 1, 0, ...expandAttribute(attribute));\n        return this.copyWithAttributes(attributes);\n      } else {\n        return this;\n      }\n    }\n    getListItemAttributes() {\n      return this.attributes.filter(attribute => getBlockConfig(attribute).listAttribute);\n    }\n    isListItem() {\n      var _getBlockConfig;\n      return (_getBlockConfig = getBlockConfig(this.getLastAttribute())) === null || _getBlockConfig === void 0 ? void 0 : _getBlockConfig.listAttribute;\n    }\n    isTerminalBlock() {\n      var _getBlockConfig2;\n      return (_getBlockConfig2 = getBlockConfig(this.getLastAttribute())) === null || _getBlockConfig2 === void 0 ? void 0 : _getBlockConfig2.terminal;\n    }\n    breaksOnReturn() {\n      var _getBlockConfig3;\n      return (_getBlockConfig3 = getBlockConfig(this.getLastAttribute())) === null || _getBlockConfig3 === void 0 ? void 0 : _getBlockConfig3.breakOnReturn;\n    }\n    findLineBreakInDirectionFromPosition(direction, position) {\n      const string = this.toString();\n      let result;\n      switch (direction) {\n        case \"forward\":\n          result = string.indexOf(\"\\n\", position);\n          break;\n        case \"backward\":\n          result = string.slice(0, position).lastIndexOf(\"\\n\");\n      }\n      if (result !== -1) {\n        return result;\n      }\n    }\n    contentsForInspection() {\n      return {\n        text: this.text.inspect(),\n        attributes: this.attributes\n      };\n    }\n    toString() {\n      return this.text.toString();\n    }\n    toJSON() {\n      return {\n        text: this.text,\n        attributes: this.attributes,\n        htmlAttributes: this.htmlAttributes\n      };\n    }\n\n    // BIDI\n\n    getDirection() {\n      return this.text.getDirection();\n    }\n    isRTL() {\n      return this.text.isRTL();\n    }\n\n    // Splittable\n\n    getLength() {\n      return this.text.getLength();\n    }\n    canBeConsolidatedWith(block) {\n      return !this.hasAttributes() && !block.hasAttributes() && this.getDirection() === block.getDirection();\n    }\n    consolidateWith(block) {\n      const newlineText = Text.textForStringWithAttributes(\"\\n\");\n      const text = this.getTextWithoutBlockBreak().appendText(newlineText);\n      return this.copyWithText(text.appendText(block.text));\n    }\n    splitAtOffset(offset) {\n      let left, right;\n      if (offset === 0) {\n        left = null;\n        right = this;\n      } else if (offset === this.getLength()) {\n        left = this;\n        right = null;\n      } else {\n        left = this.copyWithText(this.text.getTextAtRange([0, offset]));\n        right = this.copyWithText(this.text.getTextAtRange([offset, this.getLength()]));\n      }\n      return [left, right];\n    }\n    getBlockBreakPosition() {\n      return this.text.getLength() - 1;\n    }\n    getTextWithoutBlockBreak() {\n      if (textEndsInBlockBreak(this.text)) {\n        return this.text.getTextAtRange([0, this.getBlockBreakPosition()]);\n      } else {\n        return this.text.copy();\n      }\n    }\n\n    // Grouping\n\n    canBeGrouped(depth) {\n      return this.attributes[depth];\n    }\n    canBeGroupedWith(otherBlock, depth) {\n      const otherAttributes = otherBlock.getAttributes();\n      const otherAttribute = otherAttributes[depth];\n      const attribute = this.attributes[depth];\n      return attribute === otherAttribute && !(getBlockConfig(attribute).group === false && !getListAttributeNames().includes(otherAttributes[depth + 1])) && (this.getDirection() === otherBlock.getDirection() || otherBlock.isEmpty());\n    }\n  }\n\n  // Block breaks\n\n  const applyBlockBreakToText = function (text) {\n    text = unmarkExistingInnerBlockBreaksInText(text);\n    text = addBlockBreakToText(text);\n    return text;\n  };\n  const unmarkExistingInnerBlockBreaksInText = function (text) {\n    let modified = false;\n    const pieces = text.getPieces();\n    let innerPieces = pieces.slice(0, pieces.length - 1);\n    const lastPiece = pieces[pieces.length - 1];\n    if (!lastPiece) return text;\n    innerPieces = innerPieces.map(piece => {\n      if (piece.isBlockBreak()) {\n        modified = true;\n        return unmarkBlockBreakPiece(piece);\n      } else {\n        return piece;\n      }\n    });\n    if (modified) {\n      return new Text([...innerPieces, lastPiece]);\n    } else {\n      return text;\n    }\n  };\n  const blockBreakText = Text.textForStringWithAttributes(\"\\n\", {\n    blockBreak: true\n  });\n  const addBlockBreakToText = function (text) {\n    if (textEndsInBlockBreak(text)) {\n      return text;\n    } else {\n      return text.appendText(blockBreakText);\n    }\n  };\n  const textEndsInBlockBreak = function (text) {\n    const length = text.getLength();\n    if (length === 0) {\n      return false;\n    }\n    const endText = text.getTextAtRange([length - 1, length]);\n    return endText.isBlockBreak();\n  };\n  const unmarkBlockBreakPiece = piece => piece.copyWithoutAttribute(\"blockBreak\");\n\n  // Attributes\n\n  const expandAttribute = function (attribute) {\n    const {\n      listAttribute\n    } = getBlockConfig(attribute);\n    if (listAttribute) {\n      return [listAttribute, attribute];\n    } else {\n      return [attribute];\n    }\n  };\n\n  // Array helpers\n\n  const getLastElement = array => array.slice(-1)[0];\n  const removeLastValue = function (array, value) {\n    const index = array.lastIndexOf(value);\n    if (index === -1) {\n      return array;\n    } else {\n      return spliceArray(array, index, 1);\n    }\n  };\n\n  class Document extends TrixObject {\n    static fromJSON(documentJSON) {\n      const blocks = Array.from(documentJSON).map(blockJSON => Block.fromJSON(blockJSON));\n      return new this(blocks);\n    }\n    static fromString(string, textAttributes) {\n      const text = Text.textForStringWithAttributes(string, textAttributes);\n      return new this([new Block(text)]);\n    }\n    constructor() {\n      let blocks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      super(...arguments);\n      if (blocks.length === 0) {\n        blocks = [new Block()];\n      }\n      this.blockList = SplittableList.box(blocks);\n    }\n    isEmpty() {\n      const block = this.getBlockAtIndex(0);\n      return this.blockList.length === 1 && block.isEmpty() && !block.hasAttributes();\n    }\n    copy() {\n      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      const blocks = options.consolidateBlocks ? this.blockList.consolidate().toArray() : this.blockList.toArray();\n      return new this.constructor(blocks);\n    }\n    copyUsingObjectsFromDocument(sourceDocument) {\n      const objectMap = new ObjectMap(sourceDocument.getObjects());\n      return this.copyUsingObjectMap(objectMap);\n    }\n    copyUsingObjectMap(objectMap) {\n      const blocks = this.getBlocks().map(block => {\n        const mappedBlock = objectMap.find(block);\n        return mappedBlock || block.copyUsingObjectMap(objectMap);\n      });\n      return new this.constructor(blocks);\n    }\n    copyWithBaseBlockAttributes() {\n      let blockAttributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      const blocks = this.getBlocks().map(block => {\n        const attributes = blockAttributes.concat(block.getAttributes());\n        return block.copyWithAttributes(attributes);\n      });\n      return new this.constructor(blocks);\n    }\n    replaceBlock(oldBlock, newBlock) {\n      const index = this.blockList.indexOf(oldBlock);\n      if (index === -1) {\n        return this;\n      }\n      return new this.constructor(this.blockList.replaceObjectAtIndex(newBlock, index));\n    }\n    insertDocumentAtRange(document, range) {\n      const {\n        blockList\n      } = document;\n      range = normalizeRange(range);\n      let [position] = range;\n      const {\n        index,\n        offset\n      } = this.locationFromPosition(position);\n      let result = this;\n      const block = this.getBlockAtPosition(position);\n      if (rangeIsCollapsed(range) && block.isEmpty() && !block.hasAttributes()) {\n        result = new this.constructor(result.blockList.removeObjectAtIndex(index));\n      } else if (block.getBlockBreakPosition() === offset) {\n        position++;\n      }\n      result = result.removeTextAtRange(range);\n      return new this.constructor(result.blockList.insertSplittableListAtPosition(blockList, position));\n    }\n    mergeDocumentAtRange(document, range) {\n      let formattedDocument, result;\n      range = normalizeRange(range);\n      const [startPosition] = range;\n      const startLocation = this.locationFromPosition(startPosition);\n      const blockAttributes = this.getBlockAtIndex(startLocation.index).getAttributes();\n      const baseBlockAttributes = document.getBaseBlockAttributes();\n      const trailingBlockAttributes = blockAttributes.slice(-baseBlockAttributes.length);\n      if (arraysAreEqual(baseBlockAttributes, trailingBlockAttributes)) {\n        const leadingBlockAttributes = blockAttributes.slice(0, -baseBlockAttributes.length);\n        formattedDocument = document.copyWithBaseBlockAttributes(leadingBlockAttributes);\n      } else {\n        formattedDocument = document.copy({\n          consolidateBlocks: true\n        }).copyWithBaseBlockAttributes(blockAttributes);\n      }\n      const blockCount = formattedDocument.getBlockCount();\n      const firstBlock = formattedDocument.getBlockAtIndex(0);\n      if (arraysAreEqual(blockAttributes, firstBlock.getAttributes())) {\n        const firstText = firstBlock.getTextWithoutBlockBreak();\n        result = this.insertTextAtRange(firstText, range);\n        if (blockCount > 1) {\n          formattedDocument = new this.constructor(formattedDocument.getBlocks().slice(1));\n          const position = startPosition + firstText.getLength();\n          result = result.insertDocumentAtRange(formattedDocument, position);\n        }\n      } else {\n        result = this.insertDocumentAtRange(formattedDocument, range);\n      }\n      return result;\n    }\n    insertTextAtRange(text, range) {\n      range = normalizeRange(range);\n      const [startPosition] = range;\n      const {\n        index,\n        offset\n      } = this.locationFromPosition(startPosition);\n      const document = this.removeTextAtRange(range);\n      return new this.constructor(document.blockList.editObjectAtIndex(index, block => block.copyWithText(block.text.insertTextAtPosition(text, offset))));\n    }\n    removeTextAtRange(range) {\n      let blocks;\n      range = normalizeRange(range);\n      const [leftPosition, rightPosition] = range;\n      if (rangeIsCollapsed(range)) {\n        return this;\n      }\n      const [leftLocation, rightLocation] = Array.from(this.locationRangeFromRange(range));\n      const leftIndex = leftLocation.index;\n      const leftOffset = leftLocation.offset;\n      const leftBlock = this.getBlockAtIndex(leftIndex);\n      const rightIndex = rightLocation.index;\n      const rightOffset = rightLocation.offset;\n      const rightBlock = this.getBlockAtIndex(rightIndex);\n      const removeRightNewline = rightPosition - leftPosition === 1 && leftBlock.getBlockBreakPosition() === leftOffset && rightBlock.getBlockBreakPosition() !== rightOffset && rightBlock.text.getStringAtPosition(rightOffset) === \"\\n\";\n      if (removeRightNewline) {\n        blocks = this.blockList.editObjectAtIndex(rightIndex, block => block.copyWithText(block.text.removeTextAtRange([rightOffset, rightOffset + 1])));\n      } else {\n        let block;\n        const leftText = leftBlock.text.getTextAtRange([0, leftOffset]);\n        const rightText = rightBlock.text.getTextAtRange([rightOffset, rightBlock.getLength()]);\n        const text = leftText.appendText(rightText);\n        const removingLeftBlock = leftIndex !== rightIndex && leftOffset === 0;\n        const useRightBlock = removingLeftBlock && leftBlock.getAttributeLevel() >= rightBlock.getAttributeLevel();\n        if (useRightBlock) {\n          block = rightBlock.copyWithText(text);\n        } else {\n          block = leftBlock.copyWithText(text);\n        }\n        const affectedBlockCount = rightIndex + 1 - leftIndex;\n        blocks = this.blockList.splice(leftIndex, affectedBlockCount, block);\n      }\n      return new this.constructor(blocks);\n    }\n    moveTextFromRangeToPosition(range, position) {\n      let text;\n      range = normalizeRange(range);\n      const [startPosition, endPosition] = range;\n      if (startPosition <= position && position <= endPosition) {\n        return this;\n      }\n      let document = this.getDocumentAtRange(range);\n      let result = this.removeTextAtRange(range);\n      const movingRightward = startPosition < position;\n      if (movingRightward) {\n        position -= document.getLength();\n      }\n      const [firstBlock, ...blocks] = document.getBlocks();\n      if (blocks.length === 0) {\n        text = firstBlock.getTextWithoutBlockBreak();\n        if (movingRightward) {\n          position += 1;\n        }\n      } else {\n        text = firstBlock.text;\n      }\n      result = result.insertTextAtRange(text, position);\n      if (blocks.length === 0) {\n        return result;\n      }\n      document = new this.constructor(blocks);\n      position += text.getLength();\n      return result.insertDocumentAtRange(document, position);\n    }\n    addAttributeAtRange(attribute, value, range) {\n      let {\n        blockList\n      } = this;\n      this.eachBlockAtRange(range, (block, textRange, index) => blockList = blockList.editObjectAtIndex(index, function () {\n        if (getBlockConfig(attribute)) {\n          return block.addAttribute(attribute, value);\n        } else {\n          if (textRange[0] === textRange[1]) {\n            return block;\n          } else {\n            return block.copyWithText(block.text.addAttributeAtRange(attribute, value, textRange));\n          }\n        }\n      }));\n      return new this.constructor(blockList);\n    }\n    addAttribute(attribute, value) {\n      let {\n        blockList\n      } = this;\n      this.eachBlock((block, index) => blockList = blockList.editObjectAtIndex(index, () => block.addAttribute(attribute, value)));\n      return new this.constructor(blockList);\n    }\n    removeAttributeAtRange(attribute, range) {\n      let {\n        blockList\n      } = this;\n      this.eachBlockAtRange(range, function (block, textRange, index) {\n        if (getBlockConfig(attribute)) {\n          blockList = blockList.editObjectAtIndex(index, () => block.removeAttribute(attribute));\n        } else if (textRange[0] !== textRange[1]) {\n          blockList = blockList.editObjectAtIndex(index, () => block.copyWithText(block.text.removeAttributeAtRange(attribute, textRange)));\n        }\n      });\n      return new this.constructor(blockList);\n    }\n    updateAttributesForAttachment(attributes, attachment) {\n      const range = this.getRangeOfAttachment(attachment);\n      const [startPosition] = Array.from(range);\n      const {\n        index\n      } = this.locationFromPosition(startPosition);\n      const text = this.getTextAtIndex(index);\n      return new this.constructor(this.blockList.editObjectAtIndex(index, block => block.copyWithText(text.updateAttributesForAttachment(attributes, attachment))));\n    }\n    removeAttributeForAttachment(attribute, attachment) {\n      const range = this.getRangeOfAttachment(attachment);\n      return this.removeAttributeAtRange(attribute, range);\n    }\n    setHTMLAttributeAtPosition(position, name, value) {\n      const block = this.getBlockAtPosition(position);\n      const updatedBlock = block.addHTMLAttribute(name, value);\n      return this.replaceBlock(block, updatedBlock);\n    }\n    insertBlockBreakAtRange(range) {\n      let blocks;\n      range = normalizeRange(range);\n      const [startPosition] = range;\n      const {\n        offset\n      } = this.locationFromPosition(startPosition);\n      const document = this.removeTextAtRange(range);\n      if (offset === 0) {\n        blocks = [new Block()];\n      }\n      return new this.constructor(document.blockList.insertSplittableListAtPosition(new SplittableList(blocks), startPosition));\n    }\n    applyBlockAttributeAtRange(attributeName, value, range) {\n      const expanded = this.expandRangeToLineBreaksAndSplitBlocks(range);\n      let document = expanded.document;\n      range = expanded.range;\n      const blockConfig = getBlockConfig(attributeName);\n      if (blockConfig.listAttribute) {\n        document = document.removeLastListAttributeAtRange(range, {\n          exceptAttributeName: attributeName\n        });\n        const converted = document.convertLineBreaksToBlockBreaksInRange(range);\n        document = converted.document;\n        range = converted.range;\n      } else if (blockConfig.exclusive) {\n        document = document.removeBlockAttributesAtRange(range);\n      } else if (blockConfig.terminal) {\n        document = document.removeLastTerminalAttributeAtRange(range);\n      } else {\n        document = document.consolidateBlocksAtRange(range);\n      }\n      return document.addAttributeAtRange(attributeName, value, range);\n    }\n    removeLastListAttributeAtRange(range) {\n      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let {\n        blockList\n      } = this;\n      this.eachBlockAtRange(range, function (block, textRange, index) {\n        const lastAttributeName = block.getLastAttribute();\n        if (!lastAttributeName) {\n          return;\n        }\n        if (!getBlockConfig(lastAttributeName).listAttribute) {\n          return;\n        }\n        if (lastAttributeName === options.exceptAttributeName) {\n          return;\n        }\n        blockList = blockList.editObjectAtIndex(index, () => block.removeAttribute(lastAttributeName));\n      });\n      return new this.constructor(blockList);\n    }\n    removeLastTerminalAttributeAtRange(range) {\n      let {\n        blockList\n      } = this;\n      this.eachBlockAtRange(range, function (block, textRange, index) {\n        const lastAttributeName = block.getLastAttribute();\n        if (!lastAttributeName) {\n          return;\n        }\n        if (!getBlockConfig(lastAttributeName).terminal) {\n          return;\n        }\n        blockList = blockList.editObjectAtIndex(index, () => block.removeAttribute(lastAttributeName));\n      });\n      return new this.constructor(blockList);\n    }\n    removeBlockAttributesAtRange(range) {\n      let {\n        blockList\n      } = this;\n      this.eachBlockAtRange(range, function (block, textRange, index) {\n        if (block.hasAttributes()) {\n          blockList = blockList.editObjectAtIndex(index, () => block.copyWithoutAttributes());\n        }\n      });\n      return new this.constructor(blockList);\n    }\n    expandRangeToLineBreaksAndSplitBlocks(range) {\n      let position;\n      range = normalizeRange(range);\n      let [startPosition, endPosition] = range;\n      const startLocation = this.locationFromPosition(startPosition);\n      const endLocation = this.locationFromPosition(endPosition);\n      let document = this;\n      const startBlock = document.getBlockAtIndex(startLocation.index);\n      startLocation.offset = startBlock.findLineBreakInDirectionFromPosition(\"backward\", startLocation.offset);\n      if (startLocation.offset != null) {\n        position = document.positionFromLocation(startLocation);\n        document = document.insertBlockBreakAtRange([position, position + 1]);\n        endLocation.index += 1;\n        endLocation.offset -= document.getBlockAtIndex(startLocation.index).getLength();\n        startLocation.index += 1;\n      }\n      startLocation.offset = 0;\n      if (endLocation.offset === 0 && endLocation.index > startLocation.index) {\n        endLocation.index -= 1;\n        endLocation.offset = document.getBlockAtIndex(endLocation.index).getBlockBreakPosition();\n      } else {\n        const endBlock = document.getBlockAtIndex(endLocation.index);\n        if (endBlock.text.getStringAtRange([endLocation.offset - 1, endLocation.offset]) === \"\\n\") {\n          endLocation.offset -= 1;\n        } else {\n          endLocation.offset = endBlock.findLineBreakInDirectionFromPosition(\"forward\", endLocation.offset);\n        }\n        if (endLocation.offset !== endBlock.getBlockBreakPosition()) {\n          position = document.positionFromLocation(endLocation);\n          document = document.insertBlockBreakAtRange([position, position + 1]);\n        }\n      }\n      startPosition = document.positionFromLocation(startLocation);\n      endPosition = document.positionFromLocation(endLocation);\n      range = normalizeRange([startPosition, endPosition]);\n      return {\n        document,\n        range\n      };\n    }\n    convertLineBreaksToBlockBreaksInRange(range) {\n      range = normalizeRange(range);\n      let [position] = range;\n      const string = this.getStringAtRange(range).slice(0, -1);\n      let document = this;\n      string.replace(/.*?\\n/g, function (match) {\n        position += match.length;\n        document = document.insertBlockBreakAtRange([position - 1, position]);\n      });\n      return {\n        document,\n        range\n      };\n    }\n    consolidateBlocksAtRange(range) {\n      range = normalizeRange(range);\n      const [startPosition, endPosition] = range;\n      const startIndex = this.locationFromPosition(startPosition).index;\n      const endIndex = this.locationFromPosition(endPosition).index;\n      return new this.constructor(this.blockList.consolidateFromIndexToIndex(startIndex, endIndex));\n    }\n    getDocumentAtRange(range) {\n      range = normalizeRange(range);\n      const blocks = this.blockList.getSplittableListInRange(range).toArray();\n      return new this.constructor(blocks);\n    }\n    getStringAtRange(range) {\n      let endIndex;\n      const array = range = normalizeRange(range),\n        endPosition = array[array.length - 1];\n      if (endPosition !== this.getLength()) {\n        endIndex = -1;\n      }\n      return this.getDocumentAtRange(range).toString().slice(0, endIndex);\n    }\n    getBlockAtIndex(index) {\n      return this.blockList.getObjectAtIndex(index);\n    }\n    getBlockAtPosition(position) {\n      const {\n        index\n      } = this.locationFromPosition(position);\n      return this.getBlockAtIndex(index);\n    }\n    getTextAtIndex(index) {\n      var _this$getBlockAtIndex;\n      return (_this$getBlockAtIndex = this.getBlockAtIndex(index)) === null || _this$getBlockAtIndex === void 0 ? void 0 : _this$getBlockAtIndex.text;\n    }\n    getTextAtPosition(position) {\n      const {\n        index\n      } = this.locationFromPosition(position);\n      return this.getTextAtIndex(index);\n    }\n    getPieceAtPosition(position) {\n      const {\n        index,\n        offset\n      } = this.locationFromPosition(position);\n      return this.getTextAtIndex(index).getPieceAtPosition(offset);\n    }\n    getCharacterAtPosition(position) {\n      const {\n        index,\n        offset\n      } = this.locationFromPosition(position);\n      return this.getTextAtIndex(index).getStringAtRange([offset, offset + 1]);\n    }\n    getLength() {\n      return this.blockList.getEndPosition();\n    }\n    getBlocks() {\n      return this.blockList.toArray();\n    }\n    getBlockCount() {\n      return this.blockList.length;\n    }\n    getEditCount() {\n      return this.editCount;\n    }\n    eachBlock(callback) {\n      return this.blockList.eachObject(callback);\n    }\n    eachBlockAtRange(range, callback) {\n      let block, textRange;\n      range = normalizeRange(range);\n      const [startPosition, endPosition] = range;\n      const startLocation = this.locationFromPosition(startPosition);\n      const endLocation = this.locationFromPosition(endPosition);\n      if (startLocation.index === endLocation.index) {\n        block = this.getBlockAtIndex(startLocation.index);\n        textRange = [startLocation.offset, endLocation.offset];\n        return callback(block, textRange, startLocation.index);\n      } else {\n        for (let index = startLocation.index; index <= endLocation.index; index++) {\n          block = this.getBlockAtIndex(index);\n          if (block) {\n            switch (index) {\n              case startLocation.index:\n                textRange = [startLocation.offset, block.text.getLength()];\n                break;\n              case endLocation.index:\n                textRange = [0, endLocation.offset];\n                break;\n              default:\n                textRange = [0, block.text.getLength()];\n            }\n            callback(block, textRange, index);\n          }\n        }\n      }\n    }\n    getCommonAttributesAtRange(range) {\n      range = normalizeRange(range);\n      const [startPosition] = range;\n      if (rangeIsCollapsed(range)) {\n        return this.getCommonAttributesAtPosition(startPosition);\n      } else {\n        const textAttributes = [];\n        const blockAttributes = [];\n        this.eachBlockAtRange(range, function (block, textRange) {\n          if (textRange[0] !== textRange[1]) {\n            textAttributes.push(block.text.getCommonAttributesAtRange(textRange));\n            return blockAttributes.push(attributesForBlock(block));\n          }\n        });\n        return Hash.fromCommonAttributesOfObjects(textAttributes).merge(Hash.fromCommonAttributesOfObjects(blockAttributes)).toObject();\n      }\n    }\n    getCommonAttributesAtPosition(position) {\n      let key, value;\n      const {\n        index,\n        offset\n      } = this.locationFromPosition(position);\n      const block = this.getBlockAtIndex(index);\n      if (!block) {\n        return {};\n      }\n      const commonAttributes = attributesForBlock(block);\n      const attributes = block.text.getAttributesAtPosition(offset);\n      const attributesLeft = block.text.getAttributesAtPosition(offset - 1);\n      const inheritableAttributes = Object.keys(text_attributes).filter(key => {\n        return text_attributes[key].inheritable;\n      });\n      for (key in attributesLeft) {\n        value = attributesLeft[key];\n        if (value === attributes[key] || inheritableAttributes.includes(key)) {\n          commonAttributes[key] = value;\n        }\n      }\n      return commonAttributes;\n    }\n    getRangeOfCommonAttributeAtPosition(attributeName, position) {\n      const {\n        index,\n        offset\n      } = this.locationFromPosition(position);\n      const text = this.getTextAtIndex(index);\n      const [startOffset, endOffset] = Array.from(text.getExpandedRangeForAttributeAtOffset(attributeName, offset));\n      const start = this.positionFromLocation({\n        index,\n        offset: startOffset\n      });\n      const end = this.positionFromLocation({\n        index,\n        offset: endOffset\n      });\n      return normalizeRange([start, end]);\n    }\n    getBaseBlockAttributes() {\n      let baseBlockAttributes = this.getBlockAtIndex(0).getAttributes();\n      for (let blockIndex = 1; blockIndex < this.getBlockCount(); blockIndex++) {\n        const blockAttributes = this.getBlockAtIndex(blockIndex).getAttributes();\n        const lastAttributeIndex = Math.min(baseBlockAttributes.length, blockAttributes.length);\n        baseBlockAttributes = (() => {\n          const result = [];\n          for (let index = 0; index < lastAttributeIndex; index++) {\n            if (blockAttributes[index] !== baseBlockAttributes[index]) {\n              break;\n            }\n            result.push(blockAttributes[index]);\n          }\n          return result;\n        })();\n      }\n      return baseBlockAttributes;\n    }\n    getAttachmentById(attachmentId) {\n      for (const attachment of this.getAttachments()) {\n        if (attachment.id === attachmentId) {\n          return attachment;\n        }\n      }\n    }\n    getAttachmentPieces() {\n      let attachmentPieces = [];\n      this.blockList.eachObject(_ref => {\n        let {\n          text\n        } = _ref;\n        return attachmentPieces = attachmentPieces.concat(text.getAttachmentPieces());\n      });\n      return attachmentPieces;\n    }\n    getAttachments() {\n      return this.getAttachmentPieces().map(piece => piece.attachment);\n    }\n    getRangeOfAttachment(attachment) {\n      let position = 0;\n      const iterable = this.blockList.toArray();\n      for (let index = 0; index < iterable.length; index++) {\n        const {\n          text\n        } = iterable[index];\n        const textRange = text.getRangeOfAttachment(attachment);\n        if (textRange) {\n          return normalizeRange([position + textRange[0], position + textRange[1]]);\n        }\n        position += text.getLength();\n      }\n    }\n    getLocationRangeOfAttachment(attachment) {\n      const range = this.getRangeOfAttachment(attachment);\n      return this.locationRangeFromRange(range);\n    }\n    getAttachmentPieceForAttachment(attachment) {\n      for (const piece of this.getAttachmentPieces()) {\n        if (piece.attachment === attachment) {\n          return piece;\n        }\n      }\n    }\n    findRangesForBlockAttribute(attributeName) {\n      let position = 0;\n      const ranges = [];\n      this.getBlocks().forEach(block => {\n        const length = block.getLength();\n        if (block.hasAttribute(attributeName)) {\n          ranges.push([position, position + length]);\n        }\n        position += length;\n      });\n      return ranges;\n    }\n    findRangesForTextAttribute(attributeName) {\n      let {\n        withValue\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let position = 0;\n      let range = [];\n      const ranges = [];\n      const match = function (piece) {\n        if (withValue) {\n          return piece.getAttribute(attributeName) === withValue;\n        } else {\n          return piece.hasAttribute(attributeName);\n        }\n      };\n      this.getPieces().forEach(piece => {\n        const length = piece.getLength();\n        if (match(piece)) {\n          if (range[1] === position) {\n            range[1] = position + length;\n          } else {\n            ranges.push(range = [position, position + length]);\n          }\n        }\n        position += length;\n      });\n      return ranges;\n    }\n    locationFromPosition(position) {\n      const location = this.blockList.findIndexAndOffsetAtPosition(Math.max(0, position));\n      if (location.index != null) {\n        return location;\n      } else {\n        const blocks = this.getBlocks();\n        return {\n          index: blocks.length - 1,\n          offset: blocks[blocks.length - 1].getLength()\n        };\n      }\n    }\n    positionFromLocation(location) {\n      return this.blockList.findPositionAtIndexAndOffset(location.index, location.offset);\n    }\n    locationRangeFromPosition(position) {\n      return normalizeRange(this.locationFromPosition(position));\n    }\n    locationRangeFromRange(range) {\n      range = normalizeRange(range);\n      if (!range) return;\n      const [startPosition, endPosition] = Array.from(range);\n      const startLocation = this.locationFromPosition(startPosition);\n      const endLocation = this.locationFromPosition(endPosition);\n      return normalizeRange([startLocation, endLocation]);\n    }\n    rangeFromLocationRange(locationRange) {\n      let rightPosition;\n      locationRange = normalizeRange(locationRange);\n      const leftPosition = this.positionFromLocation(locationRange[0]);\n      if (!rangeIsCollapsed(locationRange)) {\n        rightPosition = this.positionFromLocation(locationRange[1]);\n      }\n      return normalizeRange([leftPosition, rightPosition]);\n    }\n    isEqualTo(document) {\n      return this.blockList.isEqualTo(document === null || document === void 0 ? void 0 : document.blockList);\n    }\n    getTexts() {\n      return this.getBlocks().map(block => block.text);\n    }\n    getPieces() {\n      const pieces = [];\n      Array.from(this.getTexts()).forEach(text => {\n        pieces.push(...Array.from(text.getPieces() || []));\n      });\n      return pieces;\n    }\n    getObjects() {\n      return this.getBlocks().concat(this.getTexts()).concat(this.getPieces());\n    }\n    toSerializableDocument() {\n      const blocks = [];\n      this.blockList.eachObject(block => blocks.push(block.copyWithText(block.text.toSerializableText())));\n      return new this.constructor(blocks);\n    }\n    toString() {\n      return this.blockList.toString();\n    }\n    toJSON() {\n      return this.blockList.toJSON();\n    }\n    toConsole() {\n      return JSON.stringify(this.blockList.toArray().map(block => JSON.parse(block.text.toConsole())));\n    }\n  }\n  const attributesForBlock = function (block) {\n    const attributes = {};\n    const attributeName = block.getLastAttribute();\n    if (attributeName) {\n      attributes[attributeName] = true;\n    }\n    return attributes;\n  };\n\n  /* eslint-disable\n      no-case-declarations,\n      no-irregular-whitespace,\n  */\n  const pieceForString = function (string) {\n    let attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    const type = \"string\";\n    string = normalizeSpaces(string);\n    return {\n      string,\n      attributes,\n      type\n    };\n  };\n  const pieceForAttachment = function (attachment) {\n    let attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    const type = \"attachment\";\n    return {\n      attachment,\n      attributes,\n      type\n    };\n  };\n  const blockForAttributes = function () {\n    let attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n    let htmlAttributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n    const text = [];\n    return {\n      text,\n      attributes,\n      htmlAttributes\n    };\n  };\n  const parseTrixDataAttribute = (element, name) => {\n    try {\n      return JSON.parse(element.getAttribute(\"data-trix-\".concat(name)));\n    } catch (error) {\n      return {};\n    }\n  };\n  const getImageDimensions = element => {\n    const width = element.getAttribute(\"width\");\n    const height = element.getAttribute(\"height\");\n    const dimensions = {};\n    if (width) {\n      dimensions.width = parseInt(width, 10);\n    }\n    if (height) {\n      dimensions.height = parseInt(height, 10);\n    }\n    return dimensions;\n  };\n  class HTMLParser extends BasicObject {\n    static parse(html, options) {\n      const parser = new this(html, options);\n      parser.parse();\n      return parser;\n    }\n    constructor(html) {\n      let {\n        referenceElement,\n        purifyOptions\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      super(...arguments);\n      this.html = html;\n      this.referenceElement = referenceElement;\n      this.purifyOptions = purifyOptions;\n      this.blocks = [];\n      this.blockElements = [];\n      this.processedElements = [];\n    }\n    getDocument() {\n      return Document.fromJSON(this.blocks);\n    }\n\n    // HTML parsing\n\n    parse() {\n      try {\n        this.createHiddenContainer();\n        HTMLSanitizer.setHTML(this.containerElement, this.html, {\n          purifyOptions: this.purifyOptions\n        });\n        const walker = walkTree(this.containerElement, {\n          usingFilter: nodeFilter\n        });\n        while (walker.nextNode()) {\n          this.processNode(walker.currentNode);\n        }\n        return this.translateBlockElementMarginsToNewlines();\n      } finally {\n        this.removeHiddenContainer();\n      }\n    }\n    createHiddenContainer() {\n      if (this.referenceElement) {\n        this.containerElement = this.referenceElement.cloneNode(false);\n        this.containerElement.removeAttribute(\"id\");\n        this.containerElement.setAttribute(\"data-trix-internal\", \"\");\n        this.containerElement.style.display = \"none\";\n        return this.referenceElement.parentNode.insertBefore(this.containerElement, this.referenceElement.nextSibling);\n      } else {\n        this.containerElement = makeElement({\n          tagName: \"div\",\n          style: {\n            display: \"none\"\n          }\n        });\n        return document.body.appendChild(this.containerElement);\n      }\n    }\n    removeHiddenContainer() {\n      return removeNode(this.containerElement);\n    }\n    processNode(node) {\n      switch (node.nodeType) {\n        case Node.TEXT_NODE:\n          if (!this.isInsignificantTextNode(node)) {\n            this.appendBlockForTextNode(node);\n            return this.processTextNode(node);\n          }\n          break;\n        case Node.ELEMENT_NODE:\n          this.appendBlockForElement(node);\n          return this.processElement(node);\n      }\n    }\n    appendBlockForTextNode(node) {\n      const element = node.parentNode;\n      if (element === this.currentBlockElement && this.isBlockElement(node.previousSibling)) {\n        return this.appendStringWithAttributes(\"\\n\");\n      } else if (element === this.containerElement || this.isBlockElement(element)) {\n        var _this$currentBlock;\n        const attributes = this.getBlockAttributes(element);\n        const htmlAttributes = this.getBlockHTMLAttributes(element);\n        if (!arraysAreEqual(attributes, (_this$currentBlock = this.currentBlock) === null || _this$currentBlock === void 0 ? void 0 : _this$currentBlock.attributes)) {\n          this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes);\n          this.currentBlockElement = element;\n        }\n      }\n    }\n    appendBlockForElement(element) {\n      const elementIsBlockElement = this.isBlockElement(element);\n      const currentBlockContainsElement = elementContainsNode(this.currentBlockElement, element);\n      if (elementIsBlockElement && !this.isBlockElement(element.firstChild)) {\n        if (!this.isInsignificantTextNode(element.firstChild) || !this.isBlockElement(element.firstElementChild)) {\n          const attributes = this.getBlockAttributes(element);\n          const htmlAttributes = this.getBlockHTMLAttributes(element);\n          if (element.firstChild) {\n            if (!(currentBlockContainsElement && arraysAreEqual(attributes, this.currentBlock.attributes))) {\n              this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes);\n              this.currentBlockElement = element;\n            } else {\n              return this.appendStringWithAttributes(\"\\n\");\n            }\n          }\n        }\n      } else if (this.currentBlockElement && !currentBlockContainsElement && !elementIsBlockElement) {\n        const parentBlockElement = this.findParentBlockElement(element);\n        if (parentBlockElement) {\n          return this.appendBlockForElement(parentBlockElement);\n        } else {\n          this.currentBlock = this.appendEmptyBlock();\n          this.currentBlockElement = null;\n        }\n      }\n    }\n    findParentBlockElement(element) {\n      let {\n        parentElement\n      } = element;\n      while (parentElement && parentElement !== this.containerElement) {\n        if (this.isBlockElement(parentElement) && this.blockElements.includes(parentElement)) {\n          return parentElement;\n        } else {\n          parentElement = parentElement.parentElement;\n        }\n      }\n      return null;\n    }\n    processTextNode(node) {\n      let string = node.data;\n      if (!elementCanDisplayPreformattedText(node.parentNode)) {\n        var _node$previousSibling;\n        string = squishBreakableWhitespace(string);\n        if (stringEndsWithWhitespace((_node$previousSibling = node.previousSibling) === null || _node$previousSibling === void 0 ? void 0 : _node$previousSibling.textContent)) {\n          string = leftTrimBreakableWhitespace(string);\n        }\n      }\n      return this.appendStringWithAttributes(string, this.getTextAttributes(node.parentNode));\n    }\n    processElement(element) {\n      let attributes;\n      if (nodeIsAttachmentElement(element)) {\n        attributes = parseTrixDataAttribute(element, \"attachment\");\n        if (Object.keys(attributes).length) {\n          const textAttributes = this.getTextAttributes(element);\n          this.appendAttachmentWithAttributes(attributes, textAttributes);\n          // We have everything we need so avoid processing inner nodes\n          element.innerHTML = \"\";\n        }\n        return this.processedElements.push(element);\n      } else {\n        switch (tagName(element)) {\n          case \"br\":\n            if (!this.isExtraBR(element) && !this.isBlockElement(element.nextSibling)) {\n              this.appendStringWithAttributes(\"\\n\", this.getTextAttributes(element));\n            }\n            return this.processedElements.push(element);\n          case \"img\":\n            attributes = {\n              url: element.getAttribute(\"src\"),\n              contentType: \"image\"\n            };\n            const object = getImageDimensions(element);\n            for (const key in object) {\n              const value = object[key];\n              attributes[key] = value;\n            }\n            this.appendAttachmentWithAttributes(attributes, this.getTextAttributes(element));\n            return this.processedElements.push(element);\n          case \"tr\":\n            if (this.needsTableSeparator(element)) {\n              return this.appendStringWithAttributes(parser.tableRowSeparator);\n            }\n            break;\n          case \"td\":\n            if (this.needsTableSeparator(element)) {\n              return this.appendStringWithAttributes(parser.tableCellSeparator);\n            }\n            break;\n        }\n      }\n    }\n\n    // Document construction\n\n    appendBlockForAttributesWithElement(attributes, element) {\n      let htmlAttributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};\n      this.blockElements.push(element);\n      const block = blockForAttributes(attributes, htmlAttributes);\n      this.blocks.push(block);\n      return block;\n    }\n    appendEmptyBlock() {\n      return this.appendBlockForAttributesWithElement([], null);\n    }\n    appendStringWithAttributes(string, attributes) {\n      return this.appendPiece(pieceForString(string, attributes));\n    }\n    appendAttachmentWithAttributes(attachment, attributes) {\n      return this.appendPiece(pieceForAttachment(attachment, attributes));\n    }\n    appendPiece(piece) {\n      if (this.blocks.length === 0) {\n        this.appendEmptyBlock();\n      }\n      return this.blocks[this.blocks.length - 1].text.push(piece);\n    }\n    appendStringToTextAtIndex(string, index) {\n      const {\n        text\n      } = this.blocks[index];\n      const piece = text[text.length - 1];\n      if ((piece === null || piece === void 0 ? void 0 : piece.type) === \"string\") {\n        piece.string += string;\n      } else {\n        return text.push(pieceForString(string));\n      }\n    }\n    prependStringToTextAtIndex(string, index) {\n      const {\n        text\n      } = this.blocks[index];\n      const piece = text[0];\n      if ((piece === null || piece === void 0 ? void 0 : piece.type) === \"string\") {\n        piece.string = string + piece.string;\n      } else {\n        return text.unshift(pieceForString(string));\n      }\n    }\n\n    // Attribute parsing\n\n    getTextAttributes(element) {\n      let value;\n      const attributes = {};\n      for (const attribute in text_attributes) {\n        const configAttr = text_attributes[attribute];\n        if (configAttr.tagName && findClosestElementFromNode(element, {\n          matchingSelector: configAttr.tagName,\n          untilNode: this.containerElement\n        })) {\n          attributes[attribute] = true;\n        } else if (configAttr.parser) {\n          value = configAttr.parser(element);\n          if (value) {\n            let attributeInheritedFromBlock = false;\n            for (const blockElement of this.findBlockElementAncestors(element)) {\n              if (configAttr.parser(blockElement) === value) {\n                attributeInheritedFromBlock = true;\n                break;\n              }\n            }\n            if (!attributeInheritedFromBlock) {\n              attributes[attribute] = value;\n            }\n          }\n        } else if (configAttr.styleProperty) {\n          value = element.style[configAttr.styleProperty];\n          if (value) {\n            attributes[attribute] = value;\n          }\n        }\n      }\n      if (nodeIsAttachmentElement(element)) {\n        const object = parseTrixDataAttribute(element, \"attributes\");\n        for (const key in object) {\n          value = object[key];\n          attributes[key] = value;\n        }\n      }\n      return attributes;\n    }\n    getBlockAttributes(element) {\n      const attributes$1 = [];\n      while (element && element !== this.containerElement) {\n        for (const attribute in attributes) {\n          const attrConfig = attributes[attribute];\n          if (attrConfig.parse !== false) {\n            if (tagName(element) === attrConfig.tagName) {\n              var _attrConfig$test;\n              if ((_attrConfig$test = attrConfig.test) !== null && _attrConfig$test !== void 0 && _attrConfig$test.call(attrConfig, element) || !attrConfig.test) {\n                attributes$1.push(attribute);\n                if (attrConfig.listAttribute) {\n                  attributes$1.push(attrConfig.listAttribute);\n                }\n              }\n            }\n          }\n        }\n        element = element.parentNode;\n      }\n      return attributes$1.reverse();\n    }\n    getBlockHTMLAttributes(element) {\n      const attributes$1 = {};\n      const blockConfig = Object.values(attributes).find(settings => settings.tagName === tagName(element));\n      const allowedAttributes = (blockConfig === null || blockConfig === void 0 ? void 0 : blockConfig.htmlAttributes) || [];\n      allowedAttributes.forEach(attribute => {\n        if (element.hasAttribute(attribute)) {\n          attributes$1[attribute] = element.getAttribute(attribute);\n        }\n      });\n      return attributes$1;\n    }\n    findBlockElementAncestors(element) {\n      const ancestors = [];\n      while (element && element !== this.containerElement) {\n        const tag = tagName(element);\n        if (getBlockTagNames().includes(tag)) {\n          ancestors.push(element);\n        }\n        element = element.parentNode;\n      }\n      return ancestors;\n    }\n\n    // Element inspection\n\n    isBlockElement(element) {\n      if ((element === null || element === void 0 ? void 0 : element.nodeType) !== Node.ELEMENT_NODE) return;\n      if (nodeIsAttachmentElement(element)) return;\n      if (findClosestElementFromNode(element, {\n        matchingSelector: \"td\",\n        untilNode: this.containerElement\n      })) return;\n      return getBlockTagNames().includes(tagName(element)) || window.getComputedStyle(element).display === \"block\";\n    }\n    isInsignificantTextNode(node) {\n      if ((node === null || node === void 0 ? void 0 : node.nodeType) !== Node.TEXT_NODE) return;\n      if (!stringIsAllBreakableWhitespace(node.data)) return;\n      const {\n        parentNode,\n        previousSibling,\n        nextSibling\n      } = node;\n      if (nodeEndsWithNonWhitespace(parentNode.previousSibling) && !this.isBlockElement(parentNode.previousSibling)) return;\n      if (elementCanDisplayPreformattedText(parentNode)) return;\n      return !previousSibling || this.isBlockElement(previousSibling) || !nextSibling || this.isBlockElement(nextSibling);\n    }\n    isExtraBR(element) {\n      return tagName(element) === \"br\" && this.isBlockElement(element.parentNode) && element.parentNode.lastChild === element;\n    }\n    needsTableSeparator(element) {\n      if (parser.removeBlankTableCells) {\n        var _element$previousSibl;\n        const content = (_element$previousSibl = element.previousSibling) === null || _element$previousSibl === void 0 ? void 0 : _element$previousSibl.textContent;\n        return content && /\\S/.test(content);\n      } else {\n        return element.previousSibling;\n      }\n    }\n\n    // Margin translation\n\n    translateBlockElementMarginsToNewlines() {\n      const defaultMargin = this.getMarginOfDefaultBlockElement();\n      for (let index = 0; index < this.blocks.length; index++) {\n        const margin = this.getMarginOfBlockElementAtIndex(index);\n        if (margin) {\n          if (margin.top > defaultMargin.top * 2) {\n            this.prependStringToTextAtIndex(\"\\n\", index);\n          }\n          if (margin.bottom > defaultMargin.bottom * 2) {\n            this.appendStringToTextAtIndex(\"\\n\", index);\n          }\n        }\n      }\n    }\n    getMarginOfBlockElementAtIndex(index) {\n      const element = this.blockElements[index];\n      if (element) {\n        if (element.textContent) {\n          if (!getBlockTagNames().includes(tagName(element)) && !this.processedElements.includes(element)) {\n            return getBlockElementMargin(element);\n          }\n        }\n      }\n    }\n    getMarginOfDefaultBlockElement() {\n      const element = makeElement(attributes.default.tagName);\n      this.containerElement.appendChild(element);\n      return getBlockElementMargin(element);\n    }\n  }\n\n  // Helpers\n\n  const elementCanDisplayPreformattedText = function (element) {\n    const {\n      whiteSpace\n    } = window.getComputedStyle(element);\n    return [\"pre\", \"pre-wrap\", \"pre-line\"].includes(whiteSpace);\n  };\n  const nodeEndsWithNonWhitespace = node => node && !stringEndsWithWhitespace(node.textContent);\n  const getBlockElementMargin = function (element) {\n    const style = window.getComputedStyle(element);\n    if (style.display === \"block\") {\n      return {\n        top: parseInt(style.marginTop),\n        bottom: parseInt(style.marginBottom)\n      };\n    }\n  };\n  const nodeFilter = function (node) {\n    if (tagName(node) === \"style\") {\n      return NodeFilter.FILTER_REJECT;\n    } else {\n      return NodeFilter.FILTER_ACCEPT;\n    }\n  };\n\n  // Whitespace\n\n  const leftTrimBreakableWhitespace = string => string.replace(new RegExp(\"^\".concat(breakableWhitespacePattern.source, \"+\")), \"\");\n  const stringIsAllBreakableWhitespace = string => new RegExp(\"^\".concat(breakableWhitespacePattern.source, \"*$\")).test(string);\n  const stringEndsWithWhitespace = string => /\\s$/.test(string);\n\n  /* eslint-disable\n      no-empty,\n  */\n  const unserializableElementSelector = \"[data-trix-serialize=false]\";\n  const unserializableAttributeNames = [\"contenteditable\", \"data-trix-id\", \"data-trix-store-key\", \"data-trix-mutable\", \"data-trix-placeholder\", \"tabindex\"];\n  const serializedAttributesAttribute = \"data-trix-serialized-attributes\";\n  const serializedAttributesSelector = \"[\".concat(serializedAttributesAttribute, \"]\");\n  const blockCommentPattern = new RegExp(\"<!--block-->\", \"g\");\n  const serializers = {\n    \"application/json\": function (serializable) {\n      let document;\n      if (serializable instanceof Document) {\n        document = serializable;\n      } else if (serializable instanceof HTMLElement) {\n        document = HTMLParser.parse(serializable.innerHTML).getDocument();\n      } else {\n        throw new Error(\"unserializable object\");\n      }\n      return document.toSerializableDocument().toJSONString();\n    },\n    \"text/html\": function (serializable) {\n      let element;\n      if (serializable instanceof Document) {\n        element = DocumentView.render(serializable);\n      } else if (serializable instanceof HTMLElement) {\n        element = serializable.cloneNode(true);\n      } else {\n        throw new Error(\"unserializable object\");\n      }\n\n      // Remove unserializable elements\n      Array.from(element.querySelectorAll(unserializableElementSelector)).forEach(el => {\n        removeNode(el);\n      });\n\n      // Remove unserializable attributes\n      unserializableAttributeNames.forEach(attribute => {\n        Array.from(element.querySelectorAll(\"[\".concat(attribute, \"]\"))).forEach(el => {\n          el.removeAttribute(attribute);\n        });\n      });\n\n      // Rewrite elements with serialized attribute overrides\n      Array.from(element.querySelectorAll(serializedAttributesSelector)).forEach(el => {\n        try {\n          const attributes = JSON.parse(el.getAttribute(serializedAttributesAttribute));\n          el.removeAttribute(serializedAttributesAttribute);\n          for (const name in attributes) {\n            const value = attributes[name];\n            el.setAttribute(name, value);\n          }\n        } catch (error) {}\n      });\n      return element.innerHTML.replace(blockCommentPattern, \"\");\n    }\n  };\n  const deserializers = {\n    \"application/json\": function (string) {\n      return Document.fromJSONString(string);\n    },\n    \"text/html\": function (string) {\n      return HTMLParser.parse(string).getDocument();\n    }\n  };\n  const serializeToContentType = function (serializable, contentType) {\n    const serializer = serializers[contentType];\n    if (serializer) {\n      return serializer(serializable);\n    } else {\n      throw new Error(\"unknown content type: \".concat(contentType));\n    }\n  };\n  const deserializeFromContentType = function (string, contentType) {\n    const deserializer = deserializers[contentType];\n    if (deserializer) {\n      return deserializer(string);\n    } else {\n      throw new Error(\"unknown content type: \".concat(contentType));\n    }\n  };\n\n  var core = /*#__PURE__*/Object.freeze({\n    __proto__: null\n  });\n\n  class ManagedAttachment extends BasicObject {\n    constructor(attachmentManager, attachment) {\n      super(...arguments);\n      this.attachmentManager = attachmentManager;\n      this.attachment = attachment;\n      this.id = this.attachment.id;\n      this.file = this.attachment.file;\n    }\n    remove() {\n      return this.attachmentManager.requestRemovalOfAttachment(this.attachment);\n    }\n  }\n  ManagedAttachment.proxyMethod(\"attachment.getAttribute\");\n  ManagedAttachment.proxyMethod(\"attachment.hasAttribute\");\n  ManagedAttachment.proxyMethod(\"attachment.setAttribute\");\n  ManagedAttachment.proxyMethod(\"attachment.getAttributes\");\n  ManagedAttachment.proxyMethod(\"attachment.setAttributes\");\n  ManagedAttachment.proxyMethod(\"attachment.isPending\");\n  ManagedAttachment.proxyMethod(\"attachment.isPreviewable\");\n  ManagedAttachment.proxyMethod(\"attachment.getURL\");\n  ManagedAttachment.proxyMethod(\"attachment.getPreviewURL\");\n  ManagedAttachment.proxyMethod(\"attachment.setPreviewURL\");\n  ManagedAttachment.proxyMethod(\"attachment.getHref\");\n  ManagedAttachment.proxyMethod(\"attachment.getFilename\");\n  ManagedAttachment.proxyMethod(\"attachment.getFilesize\");\n  ManagedAttachment.proxyMethod(\"attachment.getFormattedFilesize\");\n  ManagedAttachment.proxyMethod(\"attachment.getExtension\");\n  ManagedAttachment.proxyMethod(\"attachment.getContentType\");\n  ManagedAttachment.proxyMethod(\"attachment.getFile\");\n  ManagedAttachment.proxyMethod(\"attachment.setFile\");\n  ManagedAttachment.proxyMethod(\"attachment.releaseFile\");\n  ManagedAttachment.proxyMethod(\"attachment.getUploadProgress\");\n  ManagedAttachment.proxyMethod(\"attachment.setUploadProgress\");\n\n  class AttachmentManager extends BasicObject {\n    constructor() {\n      let attachments = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n      super(...arguments);\n      this.managedAttachments = {};\n      Array.from(attachments).forEach(attachment => {\n        this.manageAttachment(attachment);\n      });\n    }\n    getAttachments() {\n      const result = [];\n      for (const id in this.managedAttachments) {\n        const attachment = this.managedAttachments[id];\n        result.push(attachment);\n      }\n      return result;\n    }\n    manageAttachment(attachment) {\n      if (!this.managedAttachments[attachment.id]) {\n        this.managedAttachments[attachment.id] = new ManagedAttachment(this, attachment);\n      }\n      return this.managedAttachments[attachment.id];\n    }\n    attachmentIsManaged(attachment) {\n      return attachment.id in this.managedAttachments;\n    }\n    requestRemovalOfAttachment(attachment) {\n      if (this.attachmentIsManaged(attachment)) {\n        var _this$delegate, _this$delegate$attach;\n        return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$attach = _this$delegate.attachmentManagerDidRequestRemovalOfAttachment) === null || _this$delegate$attach === void 0 ? void 0 : _this$delegate$attach.call(_this$delegate, attachment);\n      }\n    }\n    unmanageAttachment(attachment) {\n      const managedAttachment = this.managedAttachments[attachment.id];\n      delete this.managedAttachments[attachment.id];\n      return managedAttachment;\n    }\n  }\n\n  class LineBreakInsertion {\n    constructor(composition) {\n      this.composition = composition;\n      this.document = this.composition.document;\n      const selectedRange = this.composition.getSelectedRange();\n      this.startPosition = selectedRange[0];\n      this.endPosition = selectedRange[1];\n      this.startLocation = this.document.locationFromPosition(this.startPosition);\n      this.endLocation = this.document.locationFromPosition(this.endPosition);\n      this.block = this.document.getBlockAtIndex(this.endLocation.index);\n      this.breaksOnReturn = this.block.breaksOnReturn();\n      this.previousCharacter = this.block.text.getStringAtPosition(this.endLocation.offset - 1);\n      this.nextCharacter = this.block.text.getStringAtPosition(this.endLocation.offset);\n    }\n    shouldInsertBlockBreak() {\n      if (this.block.hasAttributes() && this.block.isListItem() && !this.block.isEmpty()) {\n        return this.startLocation.offset !== 0;\n      } else {\n        return this.breaksOnReturn && this.nextCharacter !== \"\\n\";\n      }\n    }\n    shouldBreakFormattedBlock() {\n      return this.block.hasAttributes() && !this.block.isListItem() && (this.breaksOnReturn && this.nextCharacter === \"\\n\" || this.previousCharacter === \"\\n\");\n    }\n    shouldDecreaseListLevel() {\n      return this.block.hasAttributes() && this.block.isListItem() && this.block.isEmpty();\n    }\n    shouldPrependListItem() {\n      return this.block.isListItem() && this.startLocation.offset === 0 && !this.block.isEmpty();\n    }\n    shouldRemoveLastBlockAttribute() {\n      return this.block.hasAttributes() && !this.block.isListItem() && this.block.isEmpty();\n    }\n  }\n\n  const PLACEHOLDER = \" \";\n  class Composition extends BasicObject {\n    constructor() {\n      super(...arguments);\n      this.document = new Document();\n      this.attachments = [];\n      this.currentAttributes = {};\n      this.revision = 0;\n    }\n    setDocument(document) {\n      if (!document.isEqualTo(this.document)) {\n        var _this$delegate, _this$delegate$compos;\n        this.document = document;\n        this.refreshAttachments();\n        this.revision++;\n        return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$compos = _this$delegate.compositionDidChangeDocument) === null || _this$delegate$compos === void 0 ? void 0 : _this$delegate$compos.call(_this$delegate, document);\n      }\n    }\n\n    // Snapshots\n\n    getSnapshot() {\n      return {\n        document: this.document,\n        selectedRange: this.getSelectedRange()\n      };\n    }\n    loadSnapshot(_ref) {\n      var _this$delegate2, _this$delegate2$compo, _this$delegate3, _this$delegate3$compo;\n      let {\n        document,\n        selectedRange\n      } = _ref;\n      (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$compo = _this$delegate2.compositionWillLoadSnapshot) === null || _this$delegate2$compo === void 0 || _this$delegate2$compo.call(_this$delegate2);\n      this.setDocument(document != null ? document : new Document());\n      this.setSelection(selectedRange != null ? selectedRange : [0, 0]);\n      return (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || (_this$delegate3$compo = _this$delegate3.compositionDidLoadSnapshot) === null || _this$delegate3$compo === void 0 ? void 0 : _this$delegate3$compo.call(_this$delegate3);\n    }\n\n    // Responder protocol\n\n    insertText(text) {\n      let {\n        updatePosition\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n        updatePosition: true\n      };\n      const selectedRange = this.getSelectedRange();\n      this.setDocument(this.document.insertTextAtRange(text, selectedRange));\n      const startPosition = selectedRange[0];\n      const endPosition = startPosition + text.getLength();\n      if (updatePosition) {\n        this.setSelection(endPosition);\n      }\n      return this.notifyDelegateOfInsertionAtRange([startPosition, endPosition]);\n    }\n    insertBlock() {\n      let block = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Block();\n      const document = new Document([block]);\n      return this.insertDocument(document);\n    }\n    insertDocument() {\n      let document = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Document();\n      const selectedRange = this.getSelectedRange();\n      this.setDocument(this.document.insertDocumentAtRange(document, selectedRange));\n      const startPosition = selectedRange[0];\n      const endPosition = startPosition + document.getLength();\n      this.setSelection(endPosition);\n      return this.notifyDelegateOfInsertionAtRange([startPosition, endPosition]);\n    }\n    insertString(string, options) {\n      const attributes = this.getCurrentTextAttributes();\n      const text = Text.textForStringWithAttributes(string, attributes);\n      return this.insertText(text, options);\n    }\n    insertBlockBreak() {\n      const selectedRange = this.getSelectedRange();\n      this.setDocument(this.document.insertBlockBreakAtRange(selectedRange));\n      const startPosition = selectedRange[0];\n      const endPosition = startPosition + 1;\n      this.setSelection(endPosition);\n      return this.notifyDelegateOfInsertionAtRange([startPosition, endPosition]);\n    }\n    insertLineBreak() {\n      const insertion = new LineBreakInsertion(this);\n      if (insertion.shouldDecreaseListLevel()) {\n        this.decreaseListLevel();\n        return this.setSelection(insertion.startPosition);\n      } else if (insertion.shouldPrependListItem()) {\n        const document = new Document([insertion.block.copyWithoutText()]);\n        return this.insertDocument(document);\n      } else if (insertion.shouldInsertBlockBreak()) {\n        return this.insertBlockBreak();\n      } else if (insertion.shouldRemoveLastBlockAttribute()) {\n        return this.removeLastBlockAttribute();\n      } else if (insertion.shouldBreakFormattedBlock()) {\n        return this.breakFormattedBlock(insertion);\n      } else {\n        return this.insertString(\"\\n\");\n      }\n    }\n    insertHTML(html) {\n      const document = HTMLParser.parse(html, {\n        purifyOptions: {\n          SAFE_FOR_XML: true\n        }\n      }).getDocument();\n      const selectedRange = this.getSelectedRange();\n      this.setDocument(this.document.mergeDocumentAtRange(document, selectedRange));\n      const startPosition = selectedRange[0];\n      const endPosition = startPosition + document.getLength() - 1;\n      this.setSelection(endPosition);\n      return this.notifyDelegateOfInsertionAtRange([startPosition, endPosition]);\n    }\n    replaceHTML(html) {\n      const document = HTMLParser.parse(html).getDocument().copyUsingObjectsFromDocument(this.document);\n      const locationRange = this.getLocationRange({\n        strict: false\n      });\n      const selectedRange = this.document.rangeFromLocationRange(locationRange);\n      this.setDocument(document);\n      return this.setSelection(selectedRange);\n    }\n    insertFile(file) {\n      return this.insertFiles([file]);\n    }\n    insertFiles(files) {\n      const attachments = [];\n      Array.from(files).forEach(file => {\n        var _this$delegate4;\n        if ((_this$delegate4 = this.delegate) !== null && _this$delegate4 !== void 0 && _this$delegate4.compositionShouldAcceptFile(file)) {\n          const attachment = Attachment.attachmentForFile(file);\n          attachments.push(attachment);\n        }\n      });\n      return this.insertAttachments(attachments);\n    }\n    insertAttachment(attachment) {\n      return this.insertAttachments([attachment]);\n    }\n    insertAttachments(attachments$1) {\n      let text = new Text();\n      Array.from(attachments$1).forEach(attachment => {\n        var _config$attachments$t;\n        const type = attachment.getType();\n        const presentation = (_config$attachments$t = attachments[type]) === null || _config$attachments$t === void 0 ? void 0 : _config$attachments$t.presentation;\n        const attributes = this.getCurrentTextAttributes();\n        if (presentation) {\n          attributes.presentation = presentation;\n        }\n        const attachmentText = Text.textForAttachmentWithAttributes(attachment, attributes);\n        text = text.appendText(attachmentText);\n      });\n      return this.insertText(text);\n    }\n    shouldManageDeletingInDirection(direction) {\n      const locationRange = this.getLocationRange();\n      if (rangeIsCollapsed(locationRange)) {\n        if (direction === \"backward\" && locationRange[0].offset === 0) {\n          return true;\n        }\n        if (this.shouldManageMovingCursorInDirection(direction)) {\n          return true;\n        }\n      } else {\n        if (locationRange[0].index !== locationRange[1].index) {\n          return true;\n        }\n      }\n      return false;\n    }\n    deleteInDirection(direction) {\n      let {\n        length\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let attachment, deletingIntoPreviousBlock, selectionSpansBlocks;\n      const locationRange = this.getLocationRange();\n      let range = this.getSelectedRange();\n      const selectionIsCollapsed = rangeIsCollapsed(range);\n      if (selectionIsCollapsed) {\n        deletingIntoPreviousBlock = direction === \"backward\" && locationRange[0].offset === 0;\n      } else {\n        selectionSpansBlocks = locationRange[0].index !== locationRange[1].index;\n      }\n      if (deletingIntoPreviousBlock) {\n        if (this.canDecreaseBlockAttributeLevel()) {\n          const block = this.getBlock();\n          if (block.isListItem()) {\n            this.decreaseListLevel();\n          } else {\n            this.decreaseBlockAttributeLevel();\n          }\n          this.setSelection(range[0]);\n          if (block.isEmpty()) {\n            return false;\n          }\n        }\n      }\n      if (selectionIsCollapsed) {\n        range = this.getExpandedRangeInDirection(direction, {\n          length\n        });\n        if (direction === \"backward\") {\n          attachment = this.getAttachmentAtRange(range);\n        }\n      }\n      if (attachment) {\n        this.editAttachment(attachment);\n        return false;\n      } else {\n        this.setDocument(this.document.removeTextAtRange(range));\n        this.setSelection(range[0]);\n        if (deletingIntoPreviousBlock || selectionSpansBlocks) {\n          return false;\n        }\n      }\n    }\n    moveTextFromRange(range) {\n      const [position] = Array.from(this.getSelectedRange());\n      this.setDocument(this.document.moveTextFromRangeToPosition(range, position));\n      return this.setSelection(position);\n    }\n    removeAttachment(attachment) {\n      const range = this.document.getRangeOfAttachment(attachment);\n      if (range) {\n        this.stopEditingAttachment();\n        this.setDocument(this.document.removeTextAtRange(range));\n        return this.setSelection(range[0]);\n      }\n    }\n    removeLastBlockAttribute() {\n      const [startPosition, endPosition] = Array.from(this.getSelectedRange());\n      const block = this.document.getBlockAtPosition(endPosition);\n      this.removeCurrentAttribute(block.getLastAttribute());\n      return this.setSelection(startPosition);\n    }\n    insertPlaceholder() {\n      this.placeholderPosition = this.getPosition();\n      return this.insertString(PLACEHOLDER);\n    }\n    selectPlaceholder() {\n      if (this.placeholderPosition != null) {\n        this.setSelectedRange([this.placeholderPosition, this.placeholderPosition + PLACEHOLDER.length]);\n        return this.getSelectedRange();\n      }\n    }\n    forgetPlaceholder() {\n      this.placeholderPosition = null;\n    }\n\n    // Current attributes\n\n    hasCurrentAttribute(attributeName) {\n      const value = this.currentAttributes[attributeName];\n      return value != null && value !== false;\n    }\n    toggleCurrentAttribute(attributeName) {\n      const value = !this.currentAttributes[attributeName];\n      if (value) {\n        return this.setCurrentAttribute(attributeName, value);\n      } else {\n        return this.removeCurrentAttribute(attributeName);\n      }\n    }\n    canSetCurrentAttribute(attributeName) {\n      if (getBlockConfig(attributeName)) {\n        return this.canSetCurrentBlockAttribute(attributeName);\n      } else {\n        return this.canSetCurrentTextAttribute(attributeName);\n      }\n    }\n    canSetCurrentTextAttribute(attributeName) {\n      const document = this.getSelectedDocument();\n      if (!document) return;\n      for (const attachment of Array.from(document.getAttachments())) {\n        if (!attachment.hasContent()) {\n          return false;\n        }\n      }\n      return true;\n    }\n    canSetCurrentBlockAttribute(attributeName) {\n      const block = this.getBlock();\n      if (!block) return;\n      return !block.isTerminalBlock();\n    }\n    setCurrentAttribute(attributeName, value) {\n      if (getBlockConfig(attributeName)) {\n        return this.setBlockAttribute(attributeName, value);\n      } else {\n        this.setTextAttribute(attributeName, value);\n        this.currentAttributes[attributeName] = value;\n        return this.notifyDelegateOfCurrentAttributesChange();\n      }\n    }\n    setHTMLAtributeAtPosition(position, attributeName, value) {\n      var _getBlockConfig;\n      const block = this.document.getBlockAtPosition(position);\n      const allowedHTMLAttributes = (_getBlockConfig = getBlockConfig(block.getLastAttribute())) === null || _getBlockConfig === void 0 ? void 0 : _getBlockConfig.htmlAttributes;\n      if (block && allowedHTMLAttributes !== null && allowedHTMLAttributes !== void 0 && allowedHTMLAttributes.includes(attributeName)) {\n        const newDocument = this.document.setHTMLAttributeAtPosition(position, attributeName, value);\n        this.setDocument(newDocument);\n      }\n    }\n    setTextAttribute(attributeName, value) {\n      const selectedRange = this.getSelectedRange();\n      if (!selectedRange) return;\n      const [startPosition, endPosition] = Array.from(selectedRange);\n      if (startPosition === endPosition) {\n        if (attributeName === \"href\") {\n          const text = Text.textForStringWithAttributes(value, {\n            href: value\n          });\n          return this.insertText(text);\n        }\n      } else {\n        return this.setDocument(this.document.addAttributeAtRange(attributeName, value, selectedRange));\n      }\n    }\n    setBlockAttribute(attributeName, value) {\n      const selectedRange = this.getSelectedRange();\n      if (this.canSetCurrentAttribute(attributeName)) {\n        this.setDocument(this.document.applyBlockAttributeAtRange(attributeName, value, selectedRange));\n        return this.setSelection(selectedRange);\n      }\n    }\n    removeCurrentAttribute(attributeName) {\n      if (getBlockConfig(attributeName)) {\n        this.removeBlockAttribute(attributeName);\n        return this.updateCurrentAttributes();\n      } else {\n        this.removeTextAttribute(attributeName);\n        delete this.currentAttributes[attributeName];\n        return this.notifyDelegateOfCurrentAttributesChange();\n      }\n    }\n    removeTextAttribute(attributeName) {\n      const selectedRange = this.getSelectedRange();\n      if (!selectedRange) return;\n      return this.setDocument(this.document.removeAttributeAtRange(attributeName, selectedRange));\n    }\n    removeBlockAttribute(attributeName) {\n      const selectedRange = this.getSelectedRange();\n      if (!selectedRange) return;\n      return this.setDocument(this.document.removeAttributeAtRange(attributeName, selectedRange));\n    }\n    canDecreaseNestingLevel() {\n      var _this$getBlock;\n      return ((_this$getBlock = this.getBlock()) === null || _this$getBlock === void 0 ? void 0 : _this$getBlock.getNestingLevel()) > 0;\n    }\n    canIncreaseNestingLevel() {\n      var _getBlockConfig2;\n      const block = this.getBlock();\n      if (!block) return;\n      if ((_getBlockConfig2 = getBlockConfig(block.getLastNestableAttribute())) !== null && _getBlockConfig2 !== void 0 && _getBlockConfig2.listAttribute) {\n        const previousBlock = this.getPreviousBlock();\n        if (previousBlock) {\n          return arrayStartsWith(previousBlock.getListItemAttributes(), block.getListItemAttributes());\n        }\n      } else {\n        return block.getNestingLevel() > 0;\n      }\n    }\n    decreaseNestingLevel() {\n      const block = this.getBlock();\n      if (!block) return;\n      return this.setDocument(this.document.replaceBlock(block, block.decreaseNestingLevel()));\n    }\n    increaseNestingLevel() {\n      const block = this.getBlock();\n      if (!block) return;\n      return this.setDocument(this.document.replaceBlock(block, block.increaseNestingLevel()));\n    }\n    canDecreaseBlockAttributeLevel() {\n      var _this$getBlock2;\n      return ((_this$getBlock2 = this.getBlock()) === null || _this$getBlock2 === void 0 ? void 0 : _this$getBlock2.getAttributeLevel()) > 0;\n    }\n    decreaseBlockAttributeLevel() {\n      var _this$getBlock3;\n      const attribute = (_this$getBlock3 = this.getBlock()) === null || _this$getBlock3 === void 0 ? void 0 : _this$getBlock3.getLastAttribute();\n      if (attribute) {\n        return this.removeCurrentAttribute(attribute);\n      }\n    }\n    decreaseListLevel() {\n      let [startPosition] = Array.from(this.getSelectedRange());\n      const {\n        index\n      } = this.document.locationFromPosition(startPosition);\n      let endIndex = index;\n      const attributeLevel = this.getBlock().getAttributeLevel();\n      let block = this.document.getBlockAtIndex(endIndex + 1);\n      while (block) {\n        if (!block.isListItem() || block.getAttributeLevel() <= attributeLevel) {\n          break;\n        }\n        endIndex++;\n        block = this.document.getBlockAtIndex(endIndex + 1);\n      }\n      startPosition = this.document.positionFromLocation({\n        index,\n        offset: 0\n      });\n      const endPosition = this.document.positionFromLocation({\n        index: endIndex,\n        offset: 0\n      });\n      return this.setDocument(this.document.removeLastListAttributeAtRange([startPosition, endPosition]));\n    }\n    updateCurrentAttributes() {\n      const selectedRange = this.getSelectedRange({\n        ignoreLock: true\n      });\n      if (selectedRange) {\n        const currentAttributes = this.document.getCommonAttributesAtRange(selectedRange);\n        Array.from(getAllAttributeNames()).forEach(attributeName => {\n          if (!currentAttributes[attributeName]) {\n            if (!this.canSetCurrentAttribute(attributeName)) {\n              currentAttributes[attributeName] = false;\n            }\n          }\n        });\n        if (!objectsAreEqual(currentAttributes, this.currentAttributes)) {\n          this.currentAttributes = currentAttributes;\n          return this.notifyDelegateOfCurrentAttributesChange();\n        }\n      }\n    }\n    getCurrentAttributes() {\n      return extend.call({}, this.currentAttributes);\n    }\n    getCurrentTextAttributes() {\n      const attributes = {};\n      for (const key in this.currentAttributes) {\n        const value = this.currentAttributes[key];\n        if (value !== false) {\n          if (getTextConfig(key)) {\n            attributes[key] = value;\n          }\n        }\n      }\n      return attributes;\n    }\n\n    // Selection freezing\n\n    freezeSelection() {\n      return this.setCurrentAttribute(\"frozen\", true);\n    }\n    thawSelection() {\n      return this.removeCurrentAttribute(\"frozen\");\n    }\n    hasFrozenSelection() {\n      return this.hasCurrentAttribute(\"frozen\");\n    }\n    setSelection(selectedRange) {\n      var _this$delegate5;\n      const locationRange = this.document.locationRangeFromRange(selectedRange);\n      return (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 ? void 0 : _this$delegate5.compositionDidRequestChangingSelectionToLocationRange(locationRange);\n    }\n    getSelectedRange() {\n      const locationRange = this.getLocationRange();\n      if (locationRange) {\n        return this.document.rangeFromLocationRange(locationRange);\n      }\n    }\n    setSelectedRange(selectedRange) {\n      const locationRange = this.document.locationRangeFromRange(selectedRange);\n      return this.getSelectionManager().setLocationRange(locationRange);\n    }\n    getPosition() {\n      const locationRange = this.getLocationRange();\n      if (locationRange) {\n        return this.document.positionFromLocation(locationRange[0]);\n      }\n    }\n    getLocationRange(options) {\n      if (this.targetLocationRange) {\n        return this.targetLocationRange;\n      } else {\n        return this.getSelectionManager().getLocationRange(options) || normalizeRange({\n          index: 0,\n          offset: 0\n        });\n      }\n    }\n    withTargetLocationRange(locationRange, fn) {\n      let result;\n      this.targetLocationRange = locationRange;\n      try {\n        result = fn();\n      } finally {\n        this.targetLocationRange = null;\n      }\n      return result;\n    }\n    withTargetRange(range, fn) {\n      const locationRange = this.document.locationRangeFromRange(range);\n      return this.withTargetLocationRange(locationRange, fn);\n    }\n    withTargetDOMRange(domRange, fn) {\n      const locationRange = this.createLocationRangeFromDOMRange(domRange, {\n        strict: false\n      });\n      return this.withTargetLocationRange(locationRange, fn);\n    }\n    getExpandedRangeInDirection(direction) {\n      let {\n        length\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let [startPosition, endPosition] = Array.from(this.getSelectedRange());\n      if (direction === \"backward\") {\n        if (length) {\n          startPosition -= length;\n        } else {\n          startPosition = this.translateUTF16PositionFromOffset(startPosition, -1);\n        }\n      } else {\n        if (length) {\n          endPosition += length;\n        } else {\n          endPosition = this.translateUTF16PositionFromOffset(endPosition, 1);\n        }\n      }\n      return normalizeRange([startPosition, endPosition]);\n    }\n    shouldManageMovingCursorInDirection(direction) {\n      if (this.editingAttachment) {\n        return true;\n      }\n      const range = this.getExpandedRangeInDirection(direction);\n      return this.getAttachmentAtRange(range) != null;\n    }\n    moveCursorInDirection(direction) {\n      let canEditAttachment, range;\n      if (this.editingAttachment) {\n        range = this.document.getRangeOfAttachment(this.editingAttachment);\n      } else {\n        const selectedRange = this.getSelectedRange();\n        range = this.getExpandedRangeInDirection(direction);\n        canEditAttachment = !rangesAreEqual(selectedRange, range);\n      }\n      if (direction === \"backward\") {\n        this.setSelectedRange(range[0]);\n      } else {\n        this.setSelectedRange(range[1]);\n      }\n      if (canEditAttachment) {\n        const attachment = this.getAttachmentAtRange(range);\n        if (attachment) {\n          return this.editAttachment(attachment);\n        }\n      }\n    }\n    expandSelectionInDirection(direction) {\n      let {\n        length\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      const range = this.getExpandedRangeInDirection(direction, {\n        length\n      });\n      return this.setSelectedRange(range);\n    }\n    expandSelectionForEditing() {\n      if (this.hasCurrentAttribute(\"href\")) {\n        return this.expandSelectionAroundCommonAttribute(\"href\");\n      }\n    }\n    expandSelectionAroundCommonAttribute(attributeName) {\n      const position = this.getPosition();\n      const range = this.document.getRangeOfCommonAttributeAtPosition(attributeName, position);\n      return this.setSelectedRange(range);\n    }\n    selectionContainsAttachments() {\n      var _this$getSelectedAtta;\n      return ((_this$getSelectedAtta = this.getSelectedAttachments()) === null || _this$getSelectedAtta === void 0 ? void 0 : _this$getSelectedAtta.length) > 0;\n    }\n    selectionIsInCursorTarget() {\n      return this.editingAttachment || this.positionIsCursorTarget(this.getPosition());\n    }\n    positionIsCursorTarget(position) {\n      const location = this.document.locationFromPosition(position);\n      if (location) {\n        return this.locationIsCursorTarget(location);\n      }\n    }\n    positionIsBlockBreak(position) {\n      var _this$document$getPie;\n      return (_this$document$getPie = this.document.getPieceAtPosition(position)) === null || _this$document$getPie === void 0 ? void 0 : _this$document$getPie.isBlockBreak();\n    }\n    getSelectedDocument() {\n      const selectedRange = this.getSelectedRange();\n      if (selectedRange) {\n        return this.document.getDocumentAtRange(selectedRange);\n      }\n    }\n    getSelectedAttachments() {\n      var _this$getSelectedDocu;\n      return (_this$getSelectedDocu = this.getSelectedDocument()) === null || _this$getSelectedDocu === void 0 ? void 0 : _this$getSelectedDocu.getAttachments();\n    }\n\n    // Attachments\n\n    getAttachments() {\n      return this.attachments.slice(0);\n    }\n    refreshAttachments() {\n      const attachments = this.document.getAttachments();\n      const {\n        added,\n        removed\n      } = summarizeArrayChange(this.attachments, attachments);\n      this.attachments = attachments;\n      Array.from(removed).forEach(attachment => {\n        var _this$delegate6, _this$delegate6$compo;\n        attachment.delegate = null;\n        (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || (_this$delegate6$compo = _this$delegate6.compositionDidRemoveAttachment) === null || _this$delegate6$compo === void 0 || _this$delegate6$compo.call(_this$delegate6, attachment);\n      });\n      return (() => {\n        const result = [];\n        Array.from(added).forEach(attachment => {\n          var _this$delegate7, _this$delegate7$compo;\n          attachment.delegate = this;\n          result.push((_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || (_this$delegate7$compo = _this$delegate7.compositionDidAddAttachment) === null || _this$delegate7$compo === void 0 ? void 0 : _this$delegate7$compo.call(_this$delegate7, attachment));\n        });\n        return result;\n      })();\n    }\n\n    // Attachment delegate\n\n    attachmentDidChangeAttributes(attachment) {\n      var _this$delegate8, _this$delegate8$compo;\n      this.revision++;\n      return (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || (_this$delegate8$compo = _this$delegate8.compositionDidEditAttachment) === null || _this$delegate8$compo === void 0 ? void 0 : _this$delegate8$compo.call(_this$delegate8, attachment);\n    }\n    attachmentDidChangePreviewURL(attachment) {\n      var _this$delegate9, _this$delegate9$compo;\n      this.revision++;\n      return (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || (_this$delegate9$compo = _this$delegate9.compositionDidChangeAttachmentPreviewURL) === null || _this$delegate9$compo === void 0 ? void 0 : _this$delegate9$compo.call(_this$delegate9, attachment);\n    }\n\n    // Attachment editing\n\n    editAttachment(attachment, options) {\n      var _this$delegate10, _this$delegate10$comp;\n      if (attachment === this.editingAttachment) return;\n      this.stopEditingAttachment();\n      this.editingAttachment = attachment;\n      return (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 || (_this$delegate10$comp = _this$delegate10.compositionDidStartEditingAttachment) === null || _this$delegate10$comp === void 0 ? void 0 : _this$delegate10$comp.call(_this$delegate10, this.editingAttachment, options);\n    }\n    stopEditingAttachment() {\n      var _this$delegate11, _this$delegate11$comp;\n      if (!this.editingAttachment) return;\n      (_this$delegate11 = this.delegate) === null || _this$delegate11 === void 0 || (_this$delegate11$comp = _this$delegate11.compositionDidStopEditingAttachment) === null || _this$delegate11$comp === void 0 || _this$delegate11$comp.call(_this$delegate11, this.editingAttachment);\n      this.editingAttachment = null;\n    }\n    updateAttributesForAttachment(attributes, attachment) {\n      return this.setDocument(this.document.updateAttributesForAttachment(attributes, attachment));\n    }\n    removeAttributeForAttachment(attribute, attachment) {\n      return this.setDocument(this.document.removeAttributeForAttachment(attribute, attachment));\n    }\n\n    // Private\n\n    breakFormattedBlock(insertion) {\n      let {\n        document\n      } = insertion;\n      const {\n        block\n      } = insertion;\n      let position = insertion.startPosition;\n      let range = [position - 1, position];\n      if (block.getBlockBreakPosition() === insertion.startLocation.offset) {\n        if (block.breaksOnReturn() && insertion.nextCharacter === \"\\n\") {\n          position += 1;\n        } else {\n          document = document.removeTextAtRange(range);\n        }\n        range = [position, position];\n      } else if (insertion.nextCharacter === \"\\n\") {\n        if (insertion.previousCharacter === \"\\n\") {\n          range = [position - 1, position + 1];\n        } else {\n          range = [position, position + 1];\n          position += 1;\n        }\n      } else if (insertion.startLocation.offset - 1 !== 0) {\n        position += 1;\n      }\n      const newDocument = new Document([block.removeLastAttribute().copyWithoutText()]);\n      this.setDocument(document.insertDocumentAtRange(newDocument, range));\n      return this.setSelection(position);\n    }\n    getPreviousBlock() {\n      const locationRange = this.getLocationRange();\n      if (locationRange) {\n        const {\n          index\n        } = locationRange[0];\n        if (index > 0) {\n          return this.document.getBlockAtIndex(index - 1);\n        }\n      }\n    }\n    getBlock() {\n      const locationRange = this.getLocationRange();\n      if (locationRange) {\n        return this.document.getBlockAtIndex(locationRange[0].index);\n      }\n    }\n    getAttachmentAtRange(range) {\n      const document = this.document.getDocumentAtRange(range);\n      if (document.toString() === \"\".concat(OBJECT_REPLACEMENT_CHARACTER, \"\\n\")) {\n        return document.getAttachments()[0];\n      }\n    }\n    notifyDelegateOfCurrentAttributesChange() {\n      var _this$delegate12, _this$delegate12$comp;\n      return (_this$delegate12 = this.delegate) === null || _this$delegate12 === void 0 || (_this$delegate12$comp = _this$delegate12.compositionDidChangeCurrentAttributes) === null || _this$delegate12$comp === void 0 ? void 0 : _this$delegate12$comp.call(_this$delegate12, this.currentAttributes);\n    }\n    notifyDelegateOfInsertionAtRange(range) {\n      var _this$delegate13, _this$delegate13$comp;\n      return (_this$delegate13 = this.delegate) === null || _this$delegate13 === void 0 || (_this$delegate13$comp = _this$delegate13.compositionDidPerformInsertionAtRange) === null || _this$delegate13$comp === void 0 ? void 0 : _this$delegate13$comp.call(_this$delegate13, range);\n    }\n    translateUTF16PositionFromOffset(position, offset) {\n      const utf16string = this.document.toUTF16String();\n      const utf16position = utf16string.offsetFromUCS2Offset(position);\n      return utf16string.offsetToUCS2Offset(utf16position + offset);\n    }\n  }\n  Composition.proxyMethod(\"getSelectionManager().getPointRange\");\n  Composition.proxyMethod(\"getSelectionManager().setLocationRangeFromPointRange\");\n  Composition.proxyMethod(\"getSelectionManager().createLocationRangeFromDOMRange\");\n  Composition.proxyMethod(\"getSelectionManager().locationIsCursorTarget\");\n  Composition.proxyMethod(\"getSelectionManager().selectionIsExpanded\");\n  Composition.proxyMethod(\"delegate?.getSelectionManager\");\n\n  class UndoManager extends BasicObject {\n    constructor(composition) {\n      super(...arguments);\n      this.composition = composition;\n      this.undoEntries = [];\n      this.redoEntries = [];\n    }\n    recordUndoEntry(description) {\n      let {\n        context,\n        consolidatable\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      const previousEntry = this.undoEntries.slice(-1)[0];\n      if (!consolidatable || !entryHasDescriptionAndContext(previousEntry, description, context)) {\n        const undoEntry = this.createEntry({\n          description,\n          context\n        });\n        this.undoEntries.push(undoEntry);\n        this.redoEntries = [];\n      }\n    }\n    undo() {\n      const undoEntry = this.undoEntries.pop();\n      if (undoEntry) {\n        const redoEntry = this.createEntry(undoEntry);\n        this.redoEntries.push(redoEntry);\n        return this.composition.loadSnapshot(undoEntry.snapshot);\n      }\n    }\n    redo() {\n      const redoEntry = this.redoEntries.pop();\n      if (redoEntry) {\n        const undoEntry = this.createEntry(redoEntry);\n        this.undoEntries.push(undoEntry);\n        return this.composition.loadSnapshot(redoEntry.snapshot);\n      }\n    }\n    canUndo() {\n      return this.undoEntries.length > 0;\n    }\n    canRedo() {\n      return this.redoEntries.length > 0;\n    }\n\n    // Private\n\n    createEntry() {\n      let {\n        description,\n        context\n      } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      return {\n        description: description === null || description === void 0 ? void 0 : description.toString(),\n        context: JSON.stringify(context),\n        snapshot: this.composition.getSnapshot()\n      };\n    }\n  }\n  const entryHasDescriptionAndContext = (entry, description, context) => (entry === null || entry === void 0 ? void 0 : entry.description) === (description === null || description === void 0 ? void 0 : description.toString()) && (entry === null || entry === void 0 ? void 0 : entry.context) === JSON.stringify(context);\n\n  const BLOCK_ATTRIBUTE_NAME = \"attachmentGallery\";\n  const TEXT_ATTRIBUTE_NAME = \"presentation\";\n  const TEXT_ATTRIBUTE_VALUE = \"gallery\";\n  class Filter {\n    constructor(snapshot) {\n      this.document = snapshot.document;\n      this.selectedRange = snapshot.selectedRange;\n    }\n    perform() {\n      this.removeBlockAttribute();\n      return this.applyBlockAttribute();\n    }\n    getSnapshot() {\n      return {\n        document: this.document,\n        selectedRange: this.selectedRange\n      };\n    }\n\n    // Private\n\n    removeBlockAttribute() {\n      return this.findRangesOfBlocks().map(range => this.document = this.document.removeAttributeAtRange(BLOCK_ATTRIBUTE_NAME, range));\n    }\n    applyBlockAttribute() {\n      let offset = 0;\n      this.findRangesOfPieces().forEach(range => {\n        if (range[1] - range[0] > 1) {\n          range[0] += offset;\n          range[1] += offset;\n          if (this.document.getCharacterAtPosition(range[1]) !== \"\\n\") {\n            this.document = this.document.insertBlockBreakAtRange(range[1]);\n            if (range[1] < this.selectedRange[1]) {\n              this.moveSelectedRangeForward();\n            }\n            range[1]++;\n            offset++;\n          }\n          if (range[0] !== 0) {\n            if (this.document.getCharacterAtPosition(range[0] - 1) !== \"\\n\") {\n              this.document = this.document.insertBlockBreakAtRange(range[0]);\n              if (range[0] < this.selectedRange[0]) {\n                this.moveSelectedRangeForward();\n              }\n              range[0]++;\n              offset++;\n            }\n          }\n          this.document = this.document.applyBlockAttributeAtRange(BLOCK_ATTRIBUTE_NAME, true, range);\n        }\n      });\n    }\n    findRangesOfBlocks() {\n      return this.document.findRangesForBlockAttribute(BLOCK_ATTRIBUTE_NAME);\n    }\n    findRangesOfPieces() {\n      return this.document.findRangesForTextAttribute(TEXT_ATTRIBUTE_NAME, {\n        withValue: TEXT_ATTRIBUTE_VALUE\n      });\n    }\n    moveSelectedRangeForward() {\n      this.selectedRange[0] += 1;\n      this.selectedRange[1] += 1;\n    }\n  }\n\n  const attachmentGalleryFilter = function (snapshot) {\n    const filter = new Filter(snapshot);\n    filter.perform();\n    return filter.getSnapshot();\n  };\n\n  const DEFAULT_FILTERS = [attachmentGalleryFilter];\n  class Editor {\n    constructor(composition, selectionManager, element) {\n      this.insertFiles = this.insertFiles.bind(this);\n      this.composition = composition;\n      this.selectionManager = selectionManager;\n      this.element = element;\n      this.undoManager = new UndoManager(this.composition);\n      this.filters = DEFAULT_FILTERS.slice(0);\n    }\n    loadDocument(document) {\n      return this.loadSnapshot({\n        document,\n        selectedRange: [0, 0]\n      });\n    }\n    loadHTML() {\n      let html = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : \"\";\n      const document = HTMLParser.parse(html, {\n        referenceElement: this.element\n      }).getDocument();\n      return this.loadDocument(document);\n    }\n    loadJSON(_ref) {\n      let {\n        document,\n        selectedRange\n      } = _ref;\n      document = Document.fromJSON(document);\n      return this.loadSnapshot({\n        document,\n        selectedRange\n      });\n    }\n    loadSnapshot(snapshot) {\n      this.undoManager = new UndoManager(this.composition);\n      return this.composition.loadSnapshot(snapshot);\n    }\n    getDocument() {\n      return this.composition.document;\n    }\n    getSelectedDocument() {\n      return this.composition.getSelectedDocument();\n    }\n    getSnapshot() {\n      return this.composition.getSnapshot();\n    }\n    toJSON() {\n      return this.getSnapshot();\n    }\n\n    // Document manipulation\n\n    deleteInDirection(direction) {\n      return this.composition.deleteInDirection(direction);\n    }\n    insertAttachment(attachment) {\n      return this.composition.insertAttachment(attachment);\n    }\n    insertAttachments(attachments) {\n      return this.composition.insertAttachments(attachments);\n    }\n    insertDocument(document) {\n      return this.composition.insertDocument(document);\n    }\n    insertFile(file) {\n      return this.composition.insertFile(file);\n    }\n    insertFiles(files) {\n      return this.composition.insertFiles(files);\n    }\n    insertHTML(html) {\n      return this.composition.insertHTML(html);\n    }\n    insertString(string) {\n      return this.composition.insertString(string);\n    }\n    insertText(text) {\n      return this.composition.insertText(text);\n    }\n    insertLineBreak() {\n      return this.composition.insertLineBreak();\n    }\n\n    // Selection\n\n    getSelectedRange() {\n      return this.composition.getSelectedRange();\n    }\n    getPosition() {\n      return this.composition.getPosition();\n    }\n    getClientRectAtPosition(position) {\n      const locationRange = this.getDocument().locationRangeFromRange([position, position + 1]);\n      return this.selectionManager.getClientRectAtLocationRange(locationRange);\n    }\n    expandSelectionInDirection(direction) {\n      return this.composition.expandSelectionInDirection(direction);\n    }\n    moveCursorInDirection(direction) {\n      return this.composition.moveCursorInDirection(direction);\n    }\n    setSelectedRange(selectedRange) {\n      return this.composition.setSelectedRange(selectedRange);\n    }\n\n    // Attributes\n\n    activateAttribute(name) {\n      let value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;\n      return this.composition.setCurrentAttribute(name, value);\n    }\n    attributeIsActive(name) {\n      return this.composition.hasCurrentAttribute(name);\n    }\n    canActivateAttribute(name) {\n      return this.composition.canSetCurrentAttribute(name);\n    }\n    deactivateAttribute(name) {\n      return this.composition.removeCurrentAttribute(name);\n    }\n\n    // HTML attributes\n    setHTMLAtributeAtPosition(position, name, value) {\n      this.composition.setHTMLAtributeAtPosition(position, name, value);\n    }\n\n    // Nesting level\n\n    canDecreaseNestingLevel() {\n      return this.composition.canDecreaseNestingLevel();\n    }\n    canIncreaseNestingLevel() {\n      return this.composition.canIncreaseNestingLevel();\n    }\n    decreaseNestingLevel() {\n      if (this.canDecreaseNestingLevel()) {\n        return this.composition.decreaseNestingLevel();\n      }\n    }\n    increaseNestingLevel() {\n      if (this.canIncreaseNestingLevel()) {\n        return this.composition.increaseNestingLevel();\n      }\n    }\n\n    // Undo/redo\n\n    canRedo() {\n      return this.undoManager.canRedo();\n    }\n    canUndo() {\n      return this.undoManager.canUndo();\n    }\n    recordUndoEntry(description) {\n      let {\n        context,\n        consolidatable\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      return this.undoManager.recordUndoEntry(description, {\n        context,\n        consolidatable\n      });\n    }\n    redo() {\n      if (this.canRedo()) {\n        return this.undoManager.redo();\n      }\n    }\n    undo() {\n      if (this.canUndo()) {\n        return this.undoManager.undo();\n      }\n    }\n  }\n\n  /* eslint-disable\n      no-var,\n      prefer-const,\n  */\n  class LocationMapper {\n    constructor(element) {\n      this.element = element;\n    }\n    findLocationFromContainerAndOffset(container, offset) {\n      let {\n        strict\n      } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {\n        strict: true\n      };\n      let childIndex = 0;\n      let foundBlock = false;\n      const location = {\n        index: 0,\n        offset: 0\n      };\n      const attachmentElement = this.findAttachmentElementParentForNode(container);\n      if (attachmentElement) {\n        container = attachmentElement.parentNode;\n        offset = findChildIndexOfNode(attachmentElement);\n      }\n      const walker = walkTree(this.element, {\n        usingFilter: rejectAttachmentContents\n      });\n      while (walker.nextNode()) {\n        const node = walker.currentNode;\n        if (node === container && nodeIsTextNode(container)) {\n          if (!nodeIsCursorTarget(node)) {\n            location.offset += offset;\n          }\n          break;\n        } else {\n          if (node.parentNode === container) {\n            if (childIndex++ === offset) {\n              if (!strict && nodeIsBlockStart(node, {\n                strict\n              })) {\n                if (foundBlock) {\n                  location.index++;\n                }\n                location.offset = 0;\n                foundBlock = true;\n              }\n              break;\n            }\n          } else if (!elementContainsNode(container, node)) {\n            if (childIndex > 0) {\n              break;\n            }\n          }\n          if (nodeIsBlockStart(node, {\n            strict\n          })) {\n            if (foundBlock) {\n              location.index++;\n            }\n            location.offset = 0;\n            foundBlock = true;\n          } else {\n            location.offset += nodeLength(node);\n          }\n        }\n      }\n      return location;\n    }\n    findContainerAndOffsetFromLocation(location) {\n      let container, offset;\n      if (location.index === 0 && location.offset === 0) {\n        container = this.element;\n        offset = 0;\n        while (container.firstChild) {\n          container = container.firstChild;\n          if (nodeIsBlockContainer(container)) {\n            offset = 1;\n            break;\n          }\n        }\n        return [container, offset];\n      }\n      let [node, nodeOffset] = this.findNodeAndOffsetFromLocation(location);\n      if (!node) return;\n      if (nodeIsTextNode(node)) {\n        if (nodeLength(node) === 0) {\n          container = node.parentNode.parentNode;\n          offset = findChildIndexOfNode(node.parentNode);\n          if (nodeIsCursorTarget(node, {\n            name: \"right\"\n          })) {\n            offset++;\n          }\n        } else {\n          container = node;\n          offset = location.offset - nodeOffset;\n        }\n      } else {\n        container = node.parentNode;\n        if (!nodeIsBlockStart(node.previousSibling)) {\n          if (!nodeIsBlockContainer(container)) {\n            while (node === container.lastChild) {\n              node = container;\n              container = container.parentNode;\n              if (nodeIsBlockContainer(container)) {\n                break;\n              }\n            }\n          }\n        }\n        offset = findChildIndexOfNode(node);\n        if (location.offset !== 0) {\n          offset++;\n        }\n      }\n      return [container, offset];\n    }\n    findNodeAndOffsetFromLocation(location) {\n      let node, nodeOffset;\n      let offset = 0;\n      for (const currentNode of this.getSignificantNodesForIndex(location.index)) {\n        const length = nodeLength(currentNode);\n        if (location.offset <= offset + length) {\n          if (nodeIsTextNode(currentNode)) {\n            node = currentNode;\n            nodeOffset = offset;\n            if (location.offset === nodeOffset && nodeIsCursorTarget(node)) {\n              break;\n            }\n          } else if (!node) {\n            node = currentNode;\n            nodeOffset = offset;\n          }\n        }\n        offset += length;\n        if (offset > location.offset) {\n          break;\n        }\n      }\n      return [node, nodeOffset];\n    }\n\n    // Private\n\n    findAttachmentElementParentForNode(node) {\n      while (node && node !== this.element) {\n        if (nodeIsAttachmentElement(node)) {\n          return node;\n        }\n        node = node.parentNode;\n      }\n    }\n    getSignificantNodesForIndex(index) {\n      const nodes = [];\n      const walker = walkTree(this.element, {\n        usingFilter: acceptSignificantNodes\n      });\n      let recordingNodes = false;\n      while (walker.nextNode()) {\n        const node = walker.currentNode;\n        if (nodeIsBlockStartComment(node)) {\n          var blockIndex;\n          if (blockIndex != null) {\n            blockIndex++;\n          } else {\n            blockIndex = 0;\n          }\n          if (blockIndex === index) {\n            recordingNodes = true;\n          } else if (recordingNodes) {\n            break;\n          }\n        } else if (recordingNodes) {\n          nodes.push(node);\n        }\n      }\n      return nodes;\n    }\n  }\n  const nodeLength = function (node) {\n    if (node.nodeType === Node.TEXT_NODE) {\n      if (nodeIsCursorTarget(node)) {\n        return 0;\n      } else {\n        const string = node.textContent;\n        return string.length;\n      }\n    } else if (tagName(node) === \"br\" || nodeIsAttachmentElement(node)) {\n      return 1;\n    } else {\n      return 0;\n    }\n  };\n  const acceptSignificantNodes = function (node) {\n    if (rejectEmptyTextNodes(node) === NodeFilter.FILTER_ACCEPT) {\n      return rejectAttachmentContents(node);\n    } else {\n      return NodeFilter.FILTER_REJECT;\n    }\n  };\n  const rejectEmptyTextNodes = function (node) {\n    if (nodeIsEmptyTextNode(node)) {\n      return NodeFilter.FILTER_REJECT;\n    } else {\n      return NodeFilter.FILTER_ACCEPT;\n    }\n  };\n  const rejectAttachmentContents = function (node) {\n    if (nodeIsAttachmentElement(node.parentNode)) {\n      return NodeFilter.FILTER_REJECT;\n    } else {\n      return NodeFilter.FILTER_ACCEPT;\n    }\n  };\n\n  /* eslint-disable\n      id-length,\n      no-empty,\n  */\n  class PointMapper {\n    createDOMRangeFromPoint(_ref) {\n      let {\n        x,\n        y\n      } = _ref;\n      let domRange;\n      if (document.caretPositionFromPoint) {\n        const {\n          offsetNode,\n          offset\n        } = document.caretPositionFromPoint(x, y);\n        domRange = document.createRange();\n        domRange.setStart(offsetNode, offset);\n        return domRange;\n      } else if (document.caretRangeFromPoint) {\n        return document.caretRangeFromPoint(x, y);\n      } else if (document.body.createTextRange) {\n        const originalDOMRange = getDOMRange();\n        try {\n          // IE 11 throws \"Unspecified error\" when using moveToPoint\n          // during a drag-and-drop operation.\n          const textRange = document.body.createTextRange();\n          textRange.moveToPoint(x, y);\n          textRange.select();\n        } catch (error) {}\n        domRange = getDOMRange();\n        setDOMRange(originalDOMRange);\n        return domRange;\n      }\n    }\n    getClientRectsForDOMRange(domRange) {\n      const array = Array.from(domRange.getClientRects());\n      const start = array[0];\n      const end = array[array.length - 1];\n      return [start, end];\n    }\n  }\n\n  /* eslint-disable\n  */\n  class SelectionManager extends BasicObject {\n    constructor(element) {\n      super(...arguments);\n      this.didMouseDown = this.didMouseDown.bind(this);\n      this.selectionDidChange = this.selectionDidChange.bind(this);\n      this.element = element;\n      this.locationMapper = new LocationMapper(this.element);\n      this.pointMapper = new PointMapper();\n      this.lockCount = 0;\n      handleEvent(\"mousedown\", {\n        onElement: this.element,\n        withCallback: this.didMouseDown\n      });\n    }\n    getLocationRange() {\n      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      if (options.strict === false) {\n        return this.createLocationRangeFromDOMRange(getDOMRange());\n      } else if (options.ignoreLock) {\n        return this.currentLocationRange;\n      } else if (this.lockedLocationRange) {\n        return this.lockedLocationRange;\n      } else {\n        return this.currentLocationRange;\n      }\n    }\n    setLocationRange(locationRange) {\n      if (this.lockedLocationRange) return;\n      locationRange = normalizeRange(locationRange);\n      const domRange = this.createDOMRangeFromLocationRange(locationRange);\n      if (domRange) {\n        setDOMRange(domRange);\n        this.updateCurrentLocationRange(locationRange);\n      }\n    }\n    setLocationRangeFromPointRange(pointRange) {\n      pointRange = normalizeRange(pointRange);\n      const startLocation = this.getLocationAtPoint(pointRange[0]);\n      const endLocation = this.getLocationAtPoint(pointRange[1]);\n      this.setLocationRange([startLocation, endLocation]);\n    }\n    getClientRectAtLocationRange(locationRange) {\n      const domRange = this.createDOMRangeFromLocationRange(locationRange);\n      if (domRange) {\n        return this.getClientRectsForDOMRange(domRange)[1];\n      }\n    }\n    locationIsCursorTarget(location) {\n      const node = Array.from(this.findNodeAndOffsetFromLocation(location))[0];\n      return nodeIsCursorTarget(node);\n    }\n    lock() {\n      if (this.lockCount++ === 0) {\n        this.updateCurrentLocationRange();\n        this.lockedLocationRange = this.getLocationRange();\n      }\n    }\n    unlock() {\n      if (--this.lockCount === 0) {\n        const {\n          lockedLocationRange\n        } = this;\n        this.lockedLocationRange = null;\n        if (lockedLocationRange != null) {\n          return this.setLocationRange(lockedLocationRange);\n        }\n      }\n    }\n    clearSelection() {\n      var _getDOMSelection;\n      return (_getDOMSelection = getDOMSelection()) === null || _getDOMSelection === void 0 ? void 0 : _getDOMSelection.removeAllRanges();\n    }\n    selectionIsCollapsed() {\n      var _getDOMRange;\n      return ((_getDOMRange = getDOMRange()) === null || _getDOMRange === void 0 ? void 0 : _getDOMRange.collapsed) === true;\n    }\n    selectionIsExpanded() {\n      return !this.selectionIsCollapsed();\n    }\n    createLocationRangeFromDOMRange(domRange, options) {\n      if (domRange == null || !this.domRangeWithinElement(domRange)) return;\n      const start = this.findLocationFromContainerAndOffset(domRange.startContainer, domRange.startOffset, options);\n      if (!start) return;\n      const end = domRange.collapsed ? undefined : this.findLocationFromContainerAndOffset(domRange.endContainer, domRange.endOffset, options);\n      return normalizeRange([start, end]);\n    }\n    didMouseDown() {\n      return this.pauseTemporarily();\n    }\n    pauseTemporarily() {\n      let resumeHandlers;\n      this.paused = true;\n      const resume = () => {\n        this.paused = false;\n        clearTimeout(resumeTimeout);\n        Array.from(resumeHandlers).forEach(handler => {\n          handler.destroy();\n        });\n        if (elementContainsNode(document, this.element)) {\n          return this.selectionDidChange();\n        }\n      };\n      const resumeTimeout = setTimeout(resume, 200);\n      resumeHandlers = [\"mousemove\", \"keydown\"].map(eventName => handleEvent(eventName, {\n        onElement: document,\n        withCallback: resume\n      }));\n    }\n    selectionDidChange() {\n      if (!this.paused && !innerElementIsActive(this.element)) {\n        return this.updateCurrentLocationRange();\n      }\n    }\n    updateCurrentLocationRange(locationRange) {\n      if (locationRange != null ? locationRange : locationRange = this.createLocationRangeFromDOMRange(getDOMRange())) {\n        if (!rangesAreEqual(locationRange, this.currentLocationRange)) {\n          var _this$delegate, _this$delegate$locati;\n          this.currentLocationRange = locationRange;\n          return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$locati = _this$delegate.locationRangeDidChange) === null || _this$delegate$locati === void 0 ? void 0 : _this$delegate$locati.call(_this$delegate, this.currentLocationRange.slice(0));\n        }\n      }\n    }\n    createDOMRangeFromLocationRange(locationRange) {\n      const rangeStart = this.findContainerAndOffsetFromLocation(locationRange[0]);\n      const rangeEnd = rangeIsCollapsed(locationRange) ? rangeStart : this.findContainerAndOffsetFromLocation(locationRange[1]) || rangeStart;\n      if (rangeStart != null && rangeEnd != null) {\n        const domRange = document.createRange();\n        domRange.setStart(...Array.from(rangeStart || []));\n        domRange.setEnd(...Array.from(rangeEnd || []));\n        return domRange;\n      }\n    }\n    getLocationAtPoint(point) {\n      const domRange = this.createDOMRangeFromPoint(point);\n      if (domRange) {\n        var _this$createLocationR;\n        return (_this$createLocationR = this.createLocationRangeFromDOMRange(domRange)) === null || _this$createLocationR === void 0 ? void 0 : _this$createLocationR[0];\n      }\n    }\n    domRangeWithinElement(domRange) {\n      if (domRange.collapsed) {\n        return elementContainsNode(this.element, domRange.startContainer);\n      } else {\n        return elementContainsNode(this.element, domRange.startContainer) && elementContainsNode(this.element, domRange.endContainer);\n      }\n    }\n  }\n  SelectionManager.proxyMethod(\"locationMapper.findLocationFromContainerAndOffset\");\n  SelectionManager.proxyMethod(\"locationMapper.findContainerAndOffsetFromLocation\");\n  SelectionManager.proxyMethod(\"locationMapper.findNodeAndOffsetFromLocation\");\n  SelectionManager.proxyMethod(\"pointMapper.createDOMRangeFromPoint\");\n  SelectionManager.proxyMethod(\"pointMapper.getClientRectsForDOMRange\");\n\n  var models = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    Attachment: Attachment,\n    AttachmentManager: AttachmentManager,\n    AttachmentPiece: AttachmentPiece,\n    Block: Block,\n    Composition: Composition,\n    Document: Document,\n    Editor: Editor,\n    HTMLParser: HTMLParser,\n    HTMLSanitizer: HTMLSanitizer,\n    LineBreakInsertion: LineBreakInsertion,\n    LocationMapper: LocationMapper,\n    ManagedAttachment: ManagedAttachment,\n    Piece: Piece,\n    PointMapper: PointMapper,\n    SelectionManager: SelectionManager,\n    SplittableList: SplittableList,\n    StringPiece: StringPiece,\n    Text: Text,\n    UndoManager: UndoManager\n  });\n\n  var views = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    ObjectView: ObjectView,\n    AttachmentView: AttachmentView,\n    BlockView: BlockView,\n    DocumentView: DocumentView,\n    PieceView: PieceView,\n    PreviewableAttachmentView: PreviewableAttachmentView,\n    TextView: TextView\n  });\n\n  const {\n    lang,\n    css,\n    keyNames: keyNames$1\n  } = config;\n  const undoable = function (fn) {\n    return function () {\n      const commands = fn.apply(this, arguments);\n      commands.do();\n      if (!this.undos) {\n        this.undos = [];\n      }\n      this.undos.push(commands.undo);\n    };\n  };\n  class AttachmentEditorController extends BasicObject {\n    constructor(attachmentPiece, _element, container) {\n      let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};\n      super(...arguments);\n      // Installing and uninstalling\n      _defineProperty(this, \"makeElementMutable\", undoable(() => {\n        return {\n          do: () => {\n            this.element.dataset.trixMutable = true;\n          },\n          undo: () => delete this.element.dataset.trixMutable\n        };\n      }));\n      _defineProperty(this, \"addToolbar\", undoable(() => {\n        // <div class=\"#{css.attachmentMetadataContainer}\" data-trix-mutable=\"true\">\n        //   <div class=\"trix-button-row\">\n        //     <span class=\"trix-button-group trix-button-group--actions\">\n        //       <button type=\"button\" class=\"trix-button trix-button--remove\" title=\"#{lang.remove}\" data-trix-action=\"remove\">#{lang.remove}</button>\n        //     </span>\n        //   </div>\n        // </div>\n        const element = makeElement({\n          tagName: \"div\",\n          className: css.attachmentToolbar,\n          data: {\n            trixMutable: true\n          },\n          childNodes: makeElement({\n            tagName: \"div\",\n            className: \"trix-button-row\",\n            childNodes: makeElement({\n              tagName: \"span\",\n              className: \"trix-button-group trix-button-group--actions\",\n              childNodes: makeElement({\n                tagName: \"button\",\n                className: \"trix-button trix-button--remove\",\n                textContent: lang.remove,\n                attributes: {\n                  title: lang.remove\n                },\n                data: {\n                  trixAction: \"remove\"\n                }\n              })\n            })\n          })\n        });\n        if (this.attachment.isPreviewable()) {\n          // <div class=\"#{css.attachmentMetadataContainer}\">\n          //   <span class=\"#{css.attachmentMetadata}\">\n          //     <span class=\"#{css.attachmentName}\" title=\"#{name}\">#{name}</span>\n          //     <span class=\"#{css.attachmentSize}\">#{size}</span>\n          //   </span>\n          // </div>\n          element.appendChild(makeElement({\n            tagName: \"div\",\n            className: css.attachmentMetadataContainer,\n            childNodes: makeElement({\n              tagName: \"span\",\n              className: css.attachmentMetadata,\n              childNodes: [makeElement({\n                tagName: \"span\",\n                className: css.attachmentName,\n                textContent: this.attachment.getFilename(),\n                attributes: {\n                  title: this.attachment.getFilename()\n                }\n              }), makeElement({\n                tagName: \"span\",\n                className: css.attachmentSize,\n                textContent: this.attachment.getFormattedFilesize()\n              })]\n            })\n          }));\n        }\n        handleEvent(\"click\", {\n          onElement: element,\n          withCallback: this.didClickToolbar\n        });\n        handleEvent(\"click\", {\n          onElement: element,\n          matchingSelector: \"[data-trix-action]\",\n          withCallback: this.didClickActionButton\n        });\n        triggerEvent(\"trix-attachment-before-toolbar\", {\n          onElement: this.element,\n          attributes: {\n            toolbar: element,\n            attachment: this.attachment\n          }\n        });\n        return {\n          do: () => this.element.appendChild(element),\n          undo: () => removeNode(element)\n        };\n      }));\n      _defineProperty(this, \"installCaptionEditor\", undoable(() => {\n        const textarea = makeElement({\n          tagName: \"textarea\",\n          className: css.attachmentCaptionEditor,\n          attributes: {\n            placeholder: lang.captionPlaceholder\n          },\n          data: {\n            trixMutable: true\n          }\n        });\n        textarea.value = this.attachmentPiece.getCaption();\n        const textareaClone = textarea.cloneNode();\n        textareaClone.classList.add(\"trix-autoresize-clone\");\n        textareaClone.tabIndex = -1;\n        const autoresize = function () {\n          textareaClone.value = textarea.value;\n          textarea.style.height = textareaClone.scrollHeight + \"px\";\n        };\n        handleEvent(\"input\", {\n          onElement: textarea,\n          withCallback: autoresize\n        });\n        handleEvent(\"input\", {\n          onElement: textarea,\n          withCallback: this.didInputCaption\n        });\n        handleEvent(\"keydown\", {\n          onElement: textarea,\n          withCallback: this.didKeyDownCaption\n        });\n        handleEvent(\"change\", {\n          onElement: textarea,\n          withCallback: this.didChangeCaption\n        });\n        handleEvent(\"blur\", {\n          onElement: textarea,\n          withCallback: this.didBlurCaption\n        });\n        const figcaption = this.element.querySelector(\"figcaption\");\n        const editingFigcaption = figcaption.cloneNode();\n        return {\n          do: () => {\n            figcaption.style.display = \"none\";\n            editingFigcaption.appendChild(textarea);\n            editingFigcaption.appendChild(textareaClone);\n            editingFigcaption.classList.add(\"\".concat(css.attachmentCaption, \"--editing\"));\n            figcaption.parentElement.insertBefore(editingFigcaption, figcaption);\n            autoresize();\n            if (this.options.editCaption) {\n              return defer(() => textarea.focus());\n            }\n          },\n          undo() {\n            removeNode(editingFigcaption);\n            figcaption.style.display = null;\n          }\n        };\n      }));\n      this.didClickToolbar = this.didClickToolbar.bind(this);\n      this.didClickActionButton = this.didClickActionButton.bind(this);\n      this.didKeyDownCaption = this.didKeyDownCaption.bind(this);\n      this.didInputCaption = this.didInputCaption.bind(this);\n      this.didChangeCaption = this.didChangeCaption.bind(this);\n      this.didBlurCaption = this.didBlurCaption.bind(this);\n      this.attachmentPiece = attachmentPiece;\n      this.element = _element;\n      this.container = container;\n      this.options = options;\n      this.attachment = this.attachmentPiece.attachment;\n      if (tagName(this.element) === \"a\") {\n        this.element = this.element.firstChild;\n      }\n      this.install();\n    }\n    install() {\n      this.makeElementMutable();\n      this.addToolbar();\n      if (this.attachment.isPreviewable()) {\n        this.installCaptionEditor();\n      }\n    }\n    uninstall() {\n      var _this$delegate;\n      let undo = this.undos.pop();\n      this.savePendingCaption();\n      while (undo) {\n        undo();\n        undo = this.undos.pop();\n      }\n      (_this$delegate = this.delegate) === null || _this$delegate === void 0 || _this$delegate.didUninstallAttachmentEditor(this);\n    }\n\n    // Private\n\n    savePendingCaption() {\n      if (this.pendingCaption != null) {\n        const caption = this.pendingCaption;\n        this.pendingCaption = null;\n        if (caption) {\n          var _this$delegate2, _this$delegate2$attac;\n          (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$attac = _this$delegate2.attachmentEditorDidRequestUpdatingAttributesForAttachment) === null || _this$delegate2$attac === void 0 || _this$delegate2$attac.call(_this$delegate2, {\n            caption\n          }, this.attachment);\n        } else {\n          var _this$delegate3, _this$delegate3$attac;\n          (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || (_this$delegate3$attac = _this$delegate3.attachmentEditorDidRequestRemovingAttributeForAttachment) === null || _this$delegate3$attac === void 0 || _this$delegate3$attac.call(_this$delegate3, \"caption\", this.attachment);\n        }\n      }\n    }\n    // Event handlers\n\n    didClickToolbar(event) {\n      event.preventDefault();\n      return event.stopPropagation();\n    }\n    didClickActionButton(event) {\n      var _this$delegate4;\n      const action = event.target.getAttribute(\"data-trix-action\");\n      switch (action) {\n        case \"remove\":\n          return (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 ? void 0 : _this$delegate4.attachmentEditorDidRequestRemovalOfAttachment(this.attachment);\n      }\n    }\n    didKeyDownCaption(event) {\n      if (keyNames$1[event.keyCode] === \"return\") {\n        var _this$delegate5, _this$delegate5$attac;\n        event.preventDefault();\n        this.savePendingCaption();\n        return (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$attac = _this$delegate5.attachmentEditorDidRequestDeselectingAttachment) === null || _this$delegate5$attac === void 0 ? void 0 : _this$delegate5$attac.call(_this$delegate5, this.attachment);\n      }\n    }\n    didInputCaption(event) {\n      this.pendingCaption = event.target.value.replace(/\\s/g, \" \").trim();\n    }\n    didChangeCaption(event) {\n      return this.savePendingCaption();\n    }\n    didBlurCaption(event) {\n      return this.savePendingCaption();\n    }\n  }\n\n  class CompositionController extends BasicObject {\n    constructor(element, composition) {\n      super(...arguments);\n      this.didFocus = this.didFocus.bind(this);\n      this.didBlur = this.didBlur.bind(this);\n      this.didClickAttachment = this.didClickAttachment.bind(this);\n      this.element = element;\n      this.composition = composition;\n      this.documentView = new DocumentView(this.composition.document, {\n        element: this.element\n      });\n      handleEvent(\"focus\", {\n        onElement: this.element,\n        withCallback: this.didFocus\n      });\n      handleEvent(\"blur\", {\n        onElement: this.element,\n        withCallback: this.didBlur\n      });\n      handleEvent(\"click\", {\n        onElement: this.element,\n        matchingSelector: \"a[contenteditable=false]\",\n        preventDefault: true\n      });\n      handleEvent(\"mousedown\", {\n        onElement: this.element,\n        matchingSelector: attachmentSelector,\n        withCallback: this.didClickAttachment\n      });\n      handleEvent(\"click\", {\n        onElement: this.element,\n        matchingSelector: \"a\".concat(attachmentSelector),\n        preventDefault: true\n      });\n    }\n    didFocus(event) {\n      var _this$blurPromise;\n      const perform = () => {\n        if (!this.focused) {\n          var _this$delegate, _this$delegate$compos;\n          this.focused = true;\n          return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$compos = _this$delegate.compositionControllerDidFocus) === null || _this$delegate$compos === void 0 ? void 0 : _this$delegate$compos.call(_this$delegate);\n        }\n      };\n      return ((_this$blurPromise = this.blurPromise) === null || _this$blurPromise === void 0 ? void 0 : _this$blurPromise.then(perform)) || perform();\n    }\n    didBlur(event) {\n      this.blurPromise = new Promise(resolve => {\n        return defer(() => {\n          if (!innerElementIsActive(this.element)) {\n            var _this$delegate2, _this$delegate2$compo;\n            this.focused = null;\n            (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$compo = _this$delegate2.compositionControllerDidBlur) === null || _this$delegate2$compo === void 0 || _this$delegate2$compo.call(_this$delegate2);\n          }\n          this.blurPromise = null;\n          return resolve();\n        });\n      });\n    }\n    didClickAttachment(event, target) {\n      var _this$delegate3, _this$delegate3$compo;\n      const attachment = this.findAttachmentForElement(target);\n      const editCaption = !!findClosestElementFromNode(event.target, {\n        matchingSelector: \"figcaption\"\n      });\n      return (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || (_this$delegate3$compo = _this$delegate3.compositionControllerDidSelectAttachment) === null || _this$delegate3$compo === void 0 ? void 0 : _this$delegate3$compo.call(_this$delegate3, attachment, {\n        editCaption\n      });\n    }\n    getSerializableElement() {\n      if (this.isEditingAttachment()) {\n        return this.documentView.shadowElement;\n      } else {\n        return this.element;\n      }\n    }\n    render() {\n      var _this$delegate6, _this$delegate6$compo;\n      if (this.revision !== this.composition.revision) {\n        this.documentView.setDocument(this.composition.document);\n        this.documentView.render();\n        this.revision = this.composition.revision;\n      }\n      if (this.canSyncDocumentView() && !this.documentView.isSynced()) {\n        var _this$delegate4, _this$delegate4$compo, _this$delegate5, _this$delegate5$compo;\n        (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || (_this$delegate4$compo = _this$delegate4.compositionControllerWillSyncDocumentView) === null || _this$delegate4$compo === void 0 || _this$delegate4$compo.call(_this$delegate4);\n        this.documentView.sync();\n        (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$compo = _this$delegate5.compositionControllerDidSyncDocumentView) === null || _this$delegate5$compo === void 0 || _this$delegate5$compo.call(_this$delegate5);\n      }\n      return (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || (_this$delegate6$compo = _this$delegate6.compositionControllerDidRender) === null || _this$delegate6$compo === void 0 ? void 0 : _this$delegate6$compo.call(_this$delegate6);\n    }\n    rerenderViewForObject(object) {\n      this.invalidateViewForObject(object);\n      return this.render();\n    }\n    invalidateViewForObject(object) {\n      return this.documentView.invalidateViewForObject(object);\n    }\n    isViewCachingEnabled() {\n      return this.documentView.isViewCachingEnabled();\n    }\n    enableViewCaching() {\n      return this.documentView.enableViewCaching();\n    }\n    disableViewCaching() {\n      return this.documentView.disableViewCaching();\n    }\n    refreshViewCache() {\n      return this.documentView.garbageCollectCachedViews();\n    }\n\n    // Attachment editor management\n\n    isEditingAttachment() {\n      return !!this.attachmentEditor;\n    }\n    installAttachmentEditorForAttachment(attachment, options) {\n      var _this$attachmentEdito;\n      if (((_this$attachmentEdito = this.attachmentEditor) === null || _this$attachmentEdito === void 0 ? void 0 : _this$attachmentEdito.attachment) === attachment) return;\n      const element = this.documentView.findElementForObject(attachment);\n      if (!element) return;\n      this.uninstallAttachmentEditor();\n      const attachmentPiece = this.composition.document.getAttachmentPieceForAttachment(attachment);\n      this.attachmentEditor = new AttachmentEditorController(attachmentPiece, element, this.element, options);\n      this.attachmentEditor.delegate = this;\n    }\n    uninstallAttachmentEditor() {\n      var _this$attachmentEdito2;\n      return (_this$attachmentEdito2 = this.attachmentEditor) === null || _this$attachmentEdito2 === void 0 ? void 0 : _this$attachmentEdito2.uninstall();\n    }\n\n    // Attachment controller delegate\n\n    didUninstallAttachmentEditor() {\n      this.attachmentEditor = null;\n      return this.render();\n    }\n    attachmentEditorDidRequestUpdatingAttributesForAttachment(attributes, attachment) {\n      var _this$delegate7, _this$delegate7$compo;\n      (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || (_this$delegate7$compo = _this$delegate7.compositionControllerWillUpdateAttachment) === null || _this$delegate7$compo === void 0 || _this$delegate7$compo.call(_this$delegate7, attachment);\n      return this.composition.updateAttributesForAttachment(attributes, attachment);\n    }\n    attachmentEditorDidRequestRemovingAttributeForAttachment(attribute, attachment) {\n      var _this$delegate8, _this$delegate8$compo;\n      (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || (_this$delegate8$compo = _this$delegate8.compositionControllerWillUpdateAttachment) === null || _this$delegate8$compo === void 0 || _this$delegate8$compo.call(_this$delegate8, attachment);\n      return this.composition.removeAttributeForAttachment(attribute, attachment);\n    }\n    attachmentEditorDidRequestRemovalOfAttachment(attachment) {\n      var _this$delegate9, _this$delegate9$compo;\n      return (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || (_this$delegate9$compo = _this$delegate9.compositionControllerDidRequestRemovalOfAttachment) === null || _this$delegate9$compo === void 0 ? void 0 : _this$delegate9$compo.call(_this$delegate9, attachment);\n    }\n    attachmentEditorDidRequestDeselectingAttachment(attachment) {\n      var _this$delegate10, _this$delegate10$comp;\n      return (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 || (_this$delegate10$comp = _this$delegate10.compositionControllerDidRequestDeselectingAttachment) === null || _this$delegate10$comp === void 0 ? void 0 : _this$delegate10$comp.call(_this$delegate10, attachment);\n    }\n\n    // Private\n\n    canSyncDocumentView() {\n      return !this.isEditingAttachment();\n    }\n    findAttachmentForElement(element) {\n      return this.composition.document.getAttachmentById(parseInt(element.dataset.trixId, 10));\n    }\n  }\n\n  class Controller extends BasicObject {}\n\n  const mutableAttributeName = \"data-trix-mutable\";\n  const mutableSelector = \"[\".concat(mutableAttributeName, \"]\");\n  const options = {\n    attributes: true,\n    childList: true,\n    characterData: true,\n    characterDataOldValue: true,\n    subtree: true\n  };\n  class MutationObserver extends BasicObject {\n    constructor(element) {\n      super(element);\n      this.didMutate = this.didMutate.bind(this);\n      this.element = element;\n      this.observer = new window.MutationObserver(this.didMutate);\n      this.start();\n    }\n    start() {\n      this.reset();\n      return this.observer.observe(this.element, options);\n    }\n    stop() {\n      return this.observer.disconnect();\n    }\n    didMutate(mutations) {\n      this.mutations.push(...Array.from(this.findSignificantMutations(mutations) || []));\n      if (this.mutations.length) {\n        var _this$delegate, _this$delegate$elemen;\n        (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$elemen = _this$delegate.elementDidMutate) === null || _this$delegate$elemen === void 0 || _this$delegate$elemen.call(_this$delegate, this.getMutationSummary());\n        return this.reset();\n      }\n    }\n\n    // Private\n\n    reset() {\n      this.mutations = [];\n    }\n    findSignificantMutations(mutations) {\n      return mutations.filter(mutation => {\n        return this.mutationIsSignificant(mutation);\n      });\n    }\n    mutationIsSignificant(mutation) {\n      if (this.nodeIsMutable(mutation.target)) {\n        return false;\n      }\n      for (const node of Array.from(this.nodesModifiedByMutation(mutation))) {\n        if (this.nodeIsSignificant(node)) return true;\n      }\n      return false;\n    }\n    nodeIsSignificant(node) {\n      return node !== this.element && !this.nodeIsMutable(node) && !nodeIsEmptyTextNode(node);\n    }\n    nodeIsMutable(node) {\n      return findClosestElementFromNode(node, {\n        matchingSelector: mutableSelector\n      });\n    }\n    nodesModifiedByMutation(mutation) {\n      const nodes = [];\n      switch (mutation.type) {\n        case \"attributes\":\n          if (mutation.attributeName !== mutableAttributeName) {\n            nodes.push(mutation.target);\n          }\n          break;\n        case \"characterData\":\n          // Changes to text nodes should consider the parent element\n          nodes.push(mutation.target.parentNode);\n          nodes.push(mutation.target);\n          break;\n        case \"childList\":\n          // Consider each added or removed node\n          nodes.push(...Array.from(mutation.addedNodes || []));\n          nodes.push(...Array.from(mutation.removedNodes || []));\n          break;\n      }\n      return nodes;\n    }\n    getMutationSummary() {\n      return this.getTextMutationSummary();\n    }\n    getTextMutationSummary() {\n      const {\n        additions,\n        deletions\n      } = this.getTextChangesFromCharacterData();\n      const textChanges = this.getTextChangesFromChildList();\n      Array.from(textChanges.additions).forEach(addition => {\n        if (!Array.from(additions).includes(addition)) {\n          additions.push(addition);\n        }\n      });\n      deletions.push(...Array.from(textChanges.deletions || []));\n      const summary = {};\n      const added = additions.join(\"\");\n      if (added) {\n        summary.textAdded = added;\n      }\n      const deleted = deletions.join(\"\");\n      if (deleted) {\n        summary.textDeleted = deleted;\n      }\n      return summary;\n    }\n    getMutationsByType(type) {\n      return Array.from(this.mutations).filter(mutation => mutation.type === type);\n    }\n    getTextChangesFromChildList() {\n      let textAdded, textRemoved;\n      const addedNodes = [];\n      const removedNodes = [];\n      Array.from(this.getMutationsByType(\"childList\")).forEach(mutation => {\n        addedNodes.push(...Array.from(mutation.addedNodes || []));\n        removedNodes.push(...Array.from(mutation.removedNodes || []));\n      });\n      const singleBlockCommentRemoved = addedNodes.length === 0 && removedNodes.length === 1 && nodeIsBlockStartComment(removedNodes[0]);\n      if (singleBlockCommentRemoved) {\n        textAdded = [];\n        textRemoved = [\"\\n\"];\n      } else {\n        textAdded = getTextForNodes(addedNodes);\n        textRemoved = getTextForNodes(removedNodes);\n      }\n      const additions = textAdded.filter((text, index) => text !== textRemoved[index]).map(normalizeSpaces);\n      const deletions = textRemoved.filter((text, index) => text !== textAdded[index]).map(normalizeSpaces);\n      return {\n        additions,\n        deletions\n      };\n    }\n    getTextChangesFromCharacterData() {\n      let added, removed;\n      const characterMutations = this.getMutationsByType(\"characterData\");\n      if (characterMutations.length) {\n        const startMutation = characterMutations[0],\n          endMutation = characterMutations[characterMutations.length - 1];\n        const oldString = normalizeSpaces(startMutation.oldValue);\n        const newString = normalizeSpaces(endMutation.target.data);\n        const summarized = summarizeStringChange(oldString, newString);\n        added = summarized.added;\n        removed = summarized.removed;\n      }\n      return {\n        additions: added ? [added] : [],\n        deletions: removed ? [removed] : []\n      };\n    }\n  }\n  const getTextForNodes = function () {\n    let nodes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n    const text = [];\n    for (const node of Array.from(nodes)) {\n      switch (node.nodeType) {\n        case Node.TEXT_NODE:\n          text.push(node.data);\n          break;\n        case Node.ELEMENT_NODE:\n          if (tagName(node) === \"br\") {\n            text.push(\"\\n\");\n          } else {\n            text.push(...Array.from(getTextForNodes(node.childNodes) || []));\n          }\n          break;\n      }\n    }\n    return text;\n  };\n\n  /* eslint-disable\n      no-empty,\n  */\n  class FileVerificationOperation extends Operation {\n    constructor(file) {\n      super(...arguments);\n      this.file = file;\n    }\n    perform(callback) {\n      const reader = new FileReader();\n      reader.onerror = () => callback(false);\n      reader.onload = () => {\n        reader.onerror = null;\n        try {\n          reader.abort();\n        } catch (error) {}\n        return callback(true, this.file);\n      };\n      return reader.readAsArrayBuffer(this.file);\n    }\n  }\n\n  // Each software keyboard on Android emits its own set of events and some of them can be buggy.\n  // This class detects when some buggy events are being emitted and lets know the input controller\n  // that they should be ignored.\n  class FlakyAndroidKeyboardDetector {\n    constructor(element) {\n      this.element = element;\n    }\n    shouldIgnore(event) {\n      if (!browser$1.samsungAndroid) return false;\n      this.previousEvent = this.event;\n      this.event = event;\n      this.checkSamsungKeyboardBuggyModeStart();\n      this.checkSamsungKeyboardBuggyModeEnd();\n      return this.buggyMode;\n    }\n\n    // private\n\n    // The Samsung keyboard on Android can enter a buggy state in which it emits a flurry of confused events that,\n    // if processed, corrupts the editor. The buggy mode always starts with an insertText event, right after a\n    // keydown event with for an \"Unidentified\" key, with the same text as the editor element, except for a few\n    // extra whitespace, or exotic utf8, characters.\n    checkSamsungKeyboardBuggyModeStart() {\n      if (this.insertingLongTextAfterUnidentifiedChar() && differsInWhitespace(this.element.innerText, this.event.data)) {\n        this.buggyMode = true;\n        this.event.preventDefault();\n      }\n    }\n\n    // The flurry of buggy events are always insertText. If we see any other type, it means it's over.\n    checkSamsungKeyboardBuggyModeEnd() {\n      if (this.buggyMode && this.event.inputType !== \"insertText\") {\n        this.buggyMode = false;\n      }\n    }\n    insertingLongTextAfterUnidentifiedChar() {\n      var _this$event$data;\n      return this.isBeforeInputInsertText() && this.previousEventWasUnidentifiedKeydown() && ((_this$event$data = this.event.data) === null || _this$event$data === void 0 ? void 0 : _this$event$data.length) > 50;\n    }\n    isBeforeInputInsertText() {\n      return this.event.type === \"beforeinput\" && this.event.inputType === \"insertText\";\n    }\n    previousEventWasUnidentifiedKeydown() {\n      var _this$previousEvent, _this$previousEvent2;\n      return ((_this$previousEvent = this.previousEvent) === null || _this$previousEvent === void 0 ? void 0 : _this$previousEvent.type) === \"keydown\" && ((_this$previousEvent2 = this.previousEvent) === null || _this$previousEvent2 === void 0 ? void 0 : _this$previousEvent2.key) === \"Unidentified\";\n    }\n  }\n  const differsInWhitespace = (text1, text2) => {\n    return normalize(text1) === normalize(text2);\n  };\n  const whiteSpaceNormalizerRegexp = new RegExp(\"(\".concat(OBJECT_REPLACEMENT_CHARACTER, \"|\").concat(ZERO_WIDTH_SPACE, \"|\").concat(NON_BREAKING_SPACE, \"|\\\\s)+\"), \"g\");\n  const normalize = text => text.replace(whiteSpaceNormalizerRegexp, \" \").trim();\n\n  class InputController extends BasicObject {\n    constructor(element) {\n      super(...arguments);\n      this.element = element;\n      this.mutationObserver = new MutationObserver(this.element);\n      this.mutationObserver.delegate = this;\n      this.flakyKeyboardDetector = new FlakyAndroidKeyboardDetector(this.element);\n      for (const eventName in this.constructor.events) {\n        handleEvent(eventName, {\n          onElement: this.element,\n          withCallback: this.handlerFor(eventName)\n        });\n      }\n    }\n    elementDidMutate(mutationSummary) {}\n    editorWillSyncDocumentView() {\n      return this.mutationObserver.stop();\n    }\n    editorDidSyncDocumentView() {\n      return this.mutationObserver.start();\n    }\n    requestRender() {\n      var _this$delegate, _this$delegate$inputC;\n      return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$inputC = _this$delegate.inputControllerDidRequestRender) === null || _this$delegate$inputC === void 0 ? void 0 : _this$delegate$inputC.call(_this$delegate);\n    }\n    requestReparse() {\n      var _this$delegate2, _this$delegate2$input;\n      (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$input = _this$delegate2.inputControllerDidRequestReparse) === null || _this$delegate2$input === void 0 || _this$delegate2$input.call(_this$delegate2);\n      return this.requestRender();\n    }\n    attachFiles(files) {\n      const operations = Array.from(files).map(file => new FileVerificationOperation(file));\n      return Promise.all(operations).then(files => {\n        this.handleInput(function () {\n          var _this$delegate3, _this$responder;\n          (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || _this$delegate3.inputControllerWillAttachFiles();\n          (_this$responder = this.responder) === null || _this$responder === void 0 || _this$responder.insertFiles(files);\n          return this.requestRender();\n        });\n      });\n    }\n\n    // Private\n\n    handlerFor(eventName) {\n      return event => {\n        if (!event.defaultPrevented) {\n          this.handleInput(() => {\n            if (!innerElementIsActive(this.element)) {\n              if (this.flakyKeyboardDetector.shouldIgnore(event)) return;\n              this.eventName = eventName;\n              this.constructor.events[eventName].call(this, event);\n            }\n          });\n        }\n      };\n    }\n    handleInput(callback) {\n      try {\n        var _this$delegate4;\n        (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.inputControllerWillHandleInput();\n        callback.call(this);\n      } finally {\n        var _this$delegate5;\n        (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || _this$delegate5.inputControllerDidHandleInput();\n      }\n    }\n    createLinkHTML(href, text) {\n      const link = document.createElement(\"a\");\n      link.href = href;\n      link.textContent = text ? text : href;\n      return link.outerHTML;\n    }\n  }\n  _defineProperty(InputController, \"events\", {});\n\n  var _$codePointAt, _;\n  const {\n    browser,\n    keyNames\n  } = config;\n  let pastedFileCount = 0;\n  class Level0InputController extends InputController {\n    constructor() {\n      super(...arguments);\n      this.resetInputSummary();\n    }\n    setInputSummary() {\n      let summary = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      this.inputSummary.eventName = this.eventName;\n      for (const key in summary) {\n        const value = summary[key];\n        this.inputSummary[key] = value;\n      }\n      return this.inputSummary;\n    }\n    resetInputSummary() {\n      this.inputSummary = {};\n    }\n    reset() {\n      this.resetInputSummary();\n      return selectionChangeObserver.reset();\n    }\n\n    // Mutation observer delegate\n\n    elementDidMutate(mutationSummary) {\n      if (this.isComposing()) {\n        var _this$delegate, _this$delegate$inputC;\n        return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$inputC = _this$delegate.inputControllerDidAllowUnhandledInput) === null || _this$delegate$inputC === void 0 ? void 0 : _this$delegate$inputC.call(_this$delegate);\n      } else {\n        return this.handleInput(function () {\n          if (this.mutationIsSignificant(mutationSummary)) {\n            if (this.mutationIsExpected(mutationSummary)) {\n              this.requestRender();\n            } else {\n              this.requestReparse();\n            }\n          }\n          return this.reset();\n        });\n      }\n    }\n    mutationIsExpected(_ref) {\n      let {\n        textAdded,\n        textDeleted\n      } = _ref;\n      if (this.inputSummary.preferDocument) {\n        return true;\n      }\n      const mutationAdditionMatchesSummary = textAdded != null ? textAdded === this.inputSummary.textAdded : !this.inputSummary.textAdded;\n      const mutationDeletionMatchesSummary = textDeleted != null ? this.inputSummary.didDelete : !this.inputSummary.didDelete;\n      const unexpectedNewlineAddition = [\"\\n\", \" \\n\"].includes(textAdded) && !mutationAdditionMatchesSummary;\n      const unexpectedNewlineDeletion = textDeleted === \"\\n\" && !mutationDeletionMatchesSummary;\n      const singleUnexpectedNewline = unexpectedNewlineAddition && !unexpectedNewlineDeletion || unexpectedNewlineDeletion && !unexpectedNewlineAddition;\n      if (singleUnexpectedNewline) {\n        const range = this.getSelectedRange();\n        if (range) {\n          var _this$responder;\n          const offset = unexpectedNewlineAddition ? textAdded.replace(/\\n$/, \"\").length || -1 : (textAdded === null || textAdded === void 0 ? void 0 : textAdded.length) || 1;\n          if ((_this$responder = this.responder) !== null && _this$responder !== void 0 && _this$responder.positionIsBlockBreak(range[1] + offset)) {\n            return true;\n          }\n        }\n      }\n      return mutationAdditionMatchesSummary && mutationDeletionMatchesSummary;\n    }\n    mutationIsSignificant(mutationSummary) {\n      var _this$compositionInpu;\n      const textChanged = Object.keys(mutationSummary).length > 0;\n      const composedEmptyString = ((_this$compositionInpu = this.compositionInput) === null || _this$compositionInpu === void 0 ? void 0 : _this$compositionInpu.getEndData()) === \"\";\n      return textChanged || !composedEmptyString;\n    }\n\n    // Private\n\n    getCompositionInput() {\n      if (this.isComposing()) {\n        return this.compositionInput;\n      } else {\n        this.compositionInput = new CompositionInput(this);\n      }\n    }\n    isComposing() {\n      return this.compositionInput && !this.compositionInput.isEnded();\n    }\n    deleteInDirection(direction, event) {\n      var _this$responder2;\n      if (((_this$responder2 = this.responder) === null || _this$responder2 === void 0 ? void 0 : _this$responder2.deleteInDirection(direction)) === false) {\n        if (event) {\n          event.preventDefault();\n          return this.requestRender();\n        }\n      } else {\n        return this.setInputSummary({\n          didDelete: true\n        });\n      }\n    }\n    serializeSelectionToDataTransfer(dataTransfer) {\n      var _this$responder3;\n      if (!dataTransferIsWritable(dataTransfer)) return;\n      const document = (_this$responder3 = this.responder) === null || _this$responder3 === void 0 ? void 0 : _this$responder3.getSelectedDocument().toSerializableDocument();\n      dataTransfer.setData(\"application/x-trix-document\", JSON.stringify(document));\n      dataTransfer.setData(\"text/html\", DocumentView.render(document).innerHTML);\n      dataTransfer.setData(\"text/plain\", document.toString().replace(/\\n$/, \"\"));\n      return true;\n    }\n    canAcceptDataTransfer(dataTransfer) {\n      const types = {};\n      Array.from((dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.types) || []).forEach(type => {\n        types[type] = true;\n      });\n      return types.Files || types[\"application/x-trix-document\"] || types[\"text/html\"] || types[\"text/plain\"];\n    }\n    getPastedHTMLUsingHiddenElement(callback) {\n      const selectedRange = this.getSelectedRange();\n      const style = {\n        position: \"absolute\",\n        left: \"\".concat(window.pageXOffset, \"px\"),\n        top: \"\".concat(window.pageYOffset, \"px\"),\n        opacity: 0\n      };\n      const element = makeElement({\n        style,\n        tagName: \"div\",\n        editable: true\n      });\n      document.body.appendChild(element);\n      element.focus();\n      return requestAnimationFrame(() => {\n        const html = element.innerHTML;\n        removeNode(element);\n        this.setSelectedRange(selectedRange);\n        return callback(html);\n      });\n    }\n  }\n  _defineProperty(Level0InputController, \"events\", {\n    keydown(event) {\n      if (!this.isComposing()) {\n        this.resetInputSummary();\n      }\n      this.inputSummary.didInput = true;\n      const keyName = keyNames[event.keyCode];\n      if (keyName) {\n        var _context2;\n        let context = this.keys;\n        [\"ctrl\", \"alt\", \"shift\", \"meta\"].forEach(modifier => {\n          if (event[\"\".concat(modifier, \"Key\")]) {\n            var _context;\n            if (modifier === \"ctrl\") {\n              modifier = \"control\";\n            }\n            context = (_context = context) === null || _context === void 0 ? void 0 : _context[modifier];\n          }\n        });\n        if (((_context2 = context) === null || _context2 === void 0 ? void 0 : _context2[keyName]) != null) {\n          this.setInputSummary({\n            keyName\n          });\n          selectionChangeObserver.reset();\n          context[keyName].call(this, event);\n        }\n      }\n      if (keyEventIsKeyboardCommand(event)) {\n        const character = String.fromCharCode(event.keyCode).toLowerCase();\n        if (character) {\n          var _this$delegate3;\n          const keys = [\"alt\", \"shift\"].map(modifier => {\n            if (event[\"\".concat(modifier, \"Key\")]) {\n              return modifier;\n            }\n          }).filter(key => key);\n          keys.push(character);\n          if ((_this$delegate3 = this.delegate) !== null && _this$delegate3 !== void 0 && _this$delegate3.inputControllerDidReceiveKeyboardCommand(keys)) {\n            event.preventDefault();\n          }\n        }\n      }\n    },\n    keypress(event) {\n      if (this.inputSummary.eventName != null) return;\n      if (event.metaKey) return;\n      if (event.ctrlKey && !event.altKey) return;\n      const string = stringFromKeyEvent(event);\n      if (string) {\n        var _this$delegate4, _this$responder9;\n        (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.inputControllerWillPerformTyping();\n        (_this$responder9 = this.responder) === null || _this$responder9 === void 0 || _this$responder9.insertString(string);\n        return this.setInputSummary({\n          textAdded: string,\n          didDelete: this.selectionIsExpanded()\n        });\n      }\n    },\n    textInput(event) {\n      // Handle autocapitalization\n      const {\n        data\n      } = event;\n      const {\n        textAdded\n      } = this.inputSummary;\n      if (textAdded && textAdded !== data && textAdded.toUpperCase() === data) {\n        var _this$responder10;\n        const range = this.getSelectedRange();\n        this.setSelectedRange([range[0], range[1] + textAdded.length]);\n        (_this$responder10 = this.responder) === null || _this$responder10 === void 0 || _this$responder10.insertString(data);\n        this.setInputSummary({\n          textAdded: data\n        });\n        return this.setSelectedRange(range);\n      }\n    },\n    dragenter(event) {\n      event.preventDefault();\n    },\n    dragstart(event) {\n      var _this$delegate5, _this$delegate5$input;\n      this.serializeSelectionToDataTransfer(event.dataTransfer);\n      this.draggedRange = this.getSelectedRange();\n      return (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$input = _this$delegate5.inputControllerDidStartDrag) === null || _this$delegate5$input === void 0 ? void 0 : _this$delegate5$input.call(_this$delegate5);\n    },\n    dragover(event) {\n      if (this.draggedRange || this.canAcceptDataTransfer(event.dataTransfer)) {\n        event.preventDefault();\n        const draggingPoint = {\n          x: event.clientX,\n          y: event.clientY\n        };\n        if (!objectsAreEqual(draggingPoint, this.draggingPoint)) {\n          var _this$delegate6, _this$delegate6$input;\n          this.draggingPoint = draggingPoint;\n          return (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || (_this$delegate6$input = _this$delegate6.inputControllerDidReceiveDragOverPoint) === null || _this$delegate6$input === void 0 ? void 0 : _this$delegate6$input.call(_this$delegate6, this.draggingPoint);\n        }\n      }\n    },\n    dragend(event) {\n      var _this$delegate7, _this$delegate7$input;\n      (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || (_this$delegate7$input = _this$delegate7.inputControllerDidCancelDrag) === null || _this$delegate7$input === void 0 || _this$delegate7$input.call(_this$delegate7);\n      this.draggedRange = null;\n      this.draggingPoint = null;\n    },\n    drop(event) {\n      var _event$dataTransfer, _this$responder11;\n      event.preventDefault();\n      const files = (_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.files;\n      const documentJSON = event.dataTransfer.getData(\"application/x-trix-document\");\n      const point = {\n        x: event.clientX,\n        y: event.clientY\n      };\n      (_this$responder11 = this.responder) === null || _this$responder11 === void 0 || _this$responder11.setLocationRangeFromPointRange(point);\n      if (files !== null && files !== void 0 && files.length) {\n        this.attachFiles(files);\n      } else if (this.draggedRange) {\n        var _this$delegate8, _this$responder12;\n        (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || _this$delegate8.inputControllerWillMoveText();\n        (_this$responder12 = this.responder) === null || _this$responder12 === void 0 || _this$responder12.moveTextFromRange(this.draggedRange);\n        this.draggedRange = null;\n        this.requestRender();\n      } else if (documentJSON) {\n        var _this$responder13;\n        const document = Document.fromJSONString(documentJSON);\n        (_this$responder13 = this.responder) === null || _this$responder13 === void 0 || _this$responder13.insertDocument(document);\n        this.requestRender();\n      }\n      this.draggedRange = null;\n      this.draggingPoint = null;\n    },\n    cut(event) {\n      var _this$responder14;\n      if ((_this$responder14 = this.responder) !== null && _this$responder14 !== void 0 && _this$responder14.selectionIsExpanded()) {\n        var _this$delegate9;\n        if (this.serializeSelectionToDataTransfer(event.clipboardData)) {\n          event.preventDefault();\n        }\n        (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || _this$delegate9.inputControllerWillCutText();\n        this.deleteInDirection(\"backward\");\n        if (event.defaultPrevented) {\n          return this.requestRender();\n        }\n      }\n    },\n    copy(event) {\n      var _this$responder15;\n      if ((_this$responder15 = this.responder) !== null && _this$responder15 !== void 0 && _this$responder15.selectionIsExpanded()) {\n        if (this.serializeSelectionToDataTransfer(event.clipboardData)) {\n          event.preventDefault();\n        }\n      }\n    },\n    paste(event) {\n      const clipboard = event.clipboardData || event.testClipboardData;\n      const paste = {\n        clipboard\n      };\n      if (!clipboard || pasteEventIsCrippledSafariHTMLPaste(event)) {\n        this.getPastedHTMLUsingHiddenElement(html => {\n          var _this$delegate10, _this$responder16, _this$delegate11;\n          paste.type = \"text/html\";\n          paste.html = html;\n          (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 || _this$delegate10.inputControllerWillPaste(paste);\n          (_this$responder16 = this.responder) === null || _this$responder16 === void 0 || _this$responder16.insertHTML(paste.html);\n          this.requestRender();\n          return (_this$delegate11 = this.delegate) === null || _this$delegate11 === void 0 ? void 0 : _this$delegate11.inputControllerDidPaste(paste);\n        });\n        return;\n      }\n      const href = clipboard.getData(\"URL\");\n      const html = clipboard.getData(\"text/html\");\n      const name = clipboard.getData(\"public.url-name\");\n      if (href) {\n        var _this$delegate12, _this$responder17, _this$delegate13;\n        let string;\n        paste.type = \"text/html\";\n        if (name) {\n          string = squishBreakableWhitespace(name).trim();\n        } else {\n          string = href;\n        }\n        paste.html = this.createLinkHTML(href, string);\n        (_this$delegate12 = this.delegate) === null || _this$delegate12 === void 0 || _this$delegate12.inputControllerWillPaste(paste);\n        this.setInputSummary({\n          textAdded: string,\n          didDelete: this.selectionIsExpanded()\n        });\n        (_this$responder17 = this.responder) === null || _this$responder17 === void 0 || _this$responder17.insertHTML(paste.html);\n        this.requestRender();\n        (_this$delegate13 = this.delegate) === null || _this$delegate13 === void 0 || _this$delegate13.inputControllerDidPaste(paste);\n      } else if (dataTransferIsPlainText(clipboard)) {\n        var _this$delegate14, _this$responder18, _this$delegate15;\n        paste.type = \"text/plain\";\n        paste.string = clipboard.getData(\"text/plain\");\n        (_this$delegate14 = this.delegate) === null || _this$delegate14 === void 0 || _this$delegate14.inputControllerWillPaste(paste);\n        this.setInputSummary({\n          textAdded: paste.string,\n          didDelete: this.selectionIsExpanded()\n        });\n        (_this$responder18 = this.responder) === null || _this$responder18 === void 0 || _this$responder18.insertString(paste.string);\n        this.requestRender();\n        (_this$delegate15 = this.delegate) === null || _this$delegate15 === void 0 || _this$delegate15.inputControllerDidPaste(paste);\n      } else if (html) {\n        var _this$delegate16, _this$responder19, _this$delegate17;\n        paste.type = \"text/html\";\n        paste.html = html;\n        (_this$delegate16 = this.delegate) === null || _this$delegate16 === void 0 || _this$delegate16.inputControllerWillPaste(paste);\n        (_this$responder19 = this.responder) === null || _this$responder19 === void 0 || _this$responder19.insertHTML(paste.html);\n        this.requestRender();\n        (_this$delegate17 = this.delegate) === null || _this$delegate17 === void 0 || _this$delegate17.inputControllerDidPaste(paste);\n      } else if (Array.from(clipboard.types).includes(\"Files\")) {\n        var _clipboard$items, _clipboard$items$getA;\n        const file = (_clipboard$items = clipboard.items) === null || _clipboard$items === void 0 || (_clipboard$items = _clipboard$items[0]) === null || _clipboard$items === void 0 || (_clipboard$items$getA = _clipboard$items.getAsFile) === null || _clipboard$items$getA === void 0 ? void 0 : _clipboard$items$getA.call(_clipboard$items);\n        if (file) {\n          var _this$delegate18, _this$responder20, _this$delegate19;\n          const extension = extensionForFile(file);\n          if (!file.name && extension) {\n            file.name = \"pasted-file-\".concat(++pastedFileCount, \".\").concat(extension);\n          }\n          paste.type = \"File\";\n          paste.file = file;\n          (_this$delegate18 = this.delegate) === null || _this$delegate18 === void 0 || _this$delegate18.inputControllerWillAttachFiles();\n          (_this$responder20 = this.responder) === null || _this$responder20 === void 0 || _this$responder20.insertFile(paste.file);\n          this.requestRender();\n          (_this$delegate19 = this.delegate) === null || _this$delegate19 === void 0 || _this$delegate19.inputControllerDidPaste(paste);\n        }\n      }\n      event.preventDefault();\n    },\n    compositionstart(event) {\n      return this.getCompositionInput().start(event.data);\n    },\n    compositionupdate(event) {\n      return this.getCompositionInput().update(event.data);\n    },\n    compositionend(event) {\n      return this.getCompositionInput().end(event.data);\n    },\n    beforeinput(event) {\n      this.inputSummary.didInput = true;\n    },\n    input(event) {\n      this.inputSummary.didInput = true;\n      return event.stopPropagation();\n    }\n  });\n  _defineProperty(Level0InputController, \"keys\", {\n    backspace(event) {\n      var _this$delegate20;\n      (_this$delegate20 = this.delegate) === null || _this$delegate20 === void 0 || _this$delegate20.inputControllerWillPerformTyping();\n      return this.deleteInDirection(\"backward\", event);\n    },\n    delete(event) {\n      var _this$delegate21;\n      (_this$delegate21 = this.delegate) === null || _this$delegate21 === void 0 || _this$delegate21.inputControllerWillPerformTyping();\n      return this.deleteInDirection(\"forward\", event);\n    },\n    return(event) {\n      var _this$delegate22, _this$responder21;\n      this.setInputSummary({\n        preferDocument: true\n      });\n      (_this$delegate22 = this.delegate) === null || _this$delegate22 === void 0 || _this$delegate22.inputControllerWillPerformTyping();\n      return (_this$responder21 = this.responder) === null || _this$responder21 === void 0 ? void 0 : _this$responder21.insertLineBreak();\n    },\n    tab(event) {\n      var _this$responder22;\n      if ((_this$responder22 = this.responder) !== null && _this$responder22 !== void 0 && _this$responder22.canIncreaseNestingLevel()) {\n        var _this$responder23;\n        (_this$responder23 = this.responder) === null || _this$responder23 === void 0 || _this$responder23.increaseNestingLevel();\n        this.requestRender();\n        event.preventDefault();\n      }\n    },\n    left(event) {\n      if (this.selectionIsInCursorTarget()) {\n        var _this$responder24;\n        event.preventDefault();\n        return (_this$responder24 = this.responder) === null || _this$responder24 === void 0 ? void 0 : _this$responder24.moveCursorInDirection(\"backward\");\n      }\n    },\n    right(event) {\n      if (this.selectionIsInCursorTarget()) {\n        var _this$responder25;\n        event.preventDefault();\n        return (_this$responder25 = this.responder) === null || _this$responder25 === void 0 ? void 0 : _this$responder25.moveCursorInDirection(\"forward\");\n      }\n    },\n    control: {\n      d(event) {\n        var _this$delegate23;\n        (_this$delegate23 = this.delegate) === null || _this$delegate23 === void 0 || _this$delegate23.inputControllerWillPerformTyping();\n        return this.deleteInDirection(\"forward\", event);\n      },\n      h(event) {\n        var _this$delegate24;\n        (_this$delegate24 = this.delegate) === null || _this$delegate24 === void 0 || _this$delegate24.inputControllerWillPerformTyping();\n        return this.deleteInDirection(\"backward\", event);\n      },\n      o(event) {\n        var _this$delegate25, _this$responder26;\n        event.preventDefault();\n        (_this$delegate25 = this.delegate) === null || _this$delegate25 === void 0 || _this$delegate25.inputControllerWillPerformTyping();\n        (_this$responder26 = this.responder) === null || _this$responder26 === void 0 || _this$responder26.insertString(\"\\n\", {\n          updatePosition: false\n        });\n        return this.requestRender();\n      }\n    },\n    shift: {\n      return(event) {\n        var _this$delegate26, _this$responder27;\n        (_this$delegate26 = this.delegate) === null || _this$delegate26 === void 0 || _this$delegate26.inputControllerWillPerformTyping();\n        (_this$responder27 = this.responder) === null || _this$responder27 === void 0 || _this$responder27.insertString(\"\\n\");\n        this.requestRender();\n        event.preventDefault();\n      },\n      tab(event) {\n        var _this$responder28;\n        if ((_this$responder28 = this.responder) !== null && _this$responder28 !== void 0 && _this$responder28.canDecreaseNestingLevel()) {\n          var _this$responder29;\n          (_this$responder29 = this.responder) === null || _this$responder29 === void 0 || _this$responder29.decreaseNestingLevel();\n          this.requestRender();\n          event.preventDefault();\n        }\n      },\n      left(event) {\n        if (this.selectionIsInCursorTarget()) {\n          event.preventDefault();\n          return this.expandSelectionInDirection(\"backward\");\n        }\n      },\n      right(event) {\n        if (this.selectionIsInCursorTarget()) {\n          event.preventDefault();\n          return this.expandSelectionInDirection(\"forward\");\n        }\n      }\n    },\n    alt: {\n      backspace(event) {\n        var _this$delegate27;\n        this.setInputSummary({\n          preferDocument: false\n        });\n        return (_this$delegate27 = this.delegate) === null || _this$delegate27 === void 0 ? void 0 : _this$delegate27.inputControllerWillPerformTyping();\n      }\n    },\n    meta: {\n      backspace(event) {\n        var _this$delegate28;\n        this.setInputSummary({\n          preferDocument: false\n        });\n        return (_this$delegate28 = this.delegate) === null || _this$delegate28 === void 0 ? void 0 : _this$delegate28.inputControllerWillPerformTyping();\n      }\n    }\n  });\n  Level0InputController.proxyMethod(\"responder?.getSelectedRange\");\n  Level0InputController.proxyMethod(\"responder?.setSelectedRange\");\n  Level0InputController.proxyMethod(\"responder?.expandSelectionInDirection\");\n  Level0InputController.proxyMethod(\"responder?.selectionIsInCursorTarget\");\n  Level0InputController.proxyMethod(\"responder?.selectionIsExpanded\");\n  const extensionForFile = file => {\n    var _file$type;\n    return (_file$type = file.type) === null || _file$type === void 0 || (_file$type = _file$type.match(/\\/(\\w+)$/)) === null || _file$type === void 0 ? void 0 : _file$type[1];\n  };\n  const hasStringCodePointAt = !!((_$codePointAt = (_ = \" \").codePointAt) !== null && _$codePointAt !== void 0 && _$codePointAt.call(_, 0));\n  const stringFromKeyEvent = function (event) {\n    if (event.key && hasStringCodePointAt && event.key.codePointAt(0) === event.keyCode) {\n      return event.key;\n    } else {\n      let code;\n      if (event.which === null) {\n        code = event.keyCode;\n      } else if (event.which !== 0 && event.charCode !== 0) {\n        code = event.charCode;\n      }\n      if (code != null && keyNames[code] !== \"escape\") {\n        return UTF16String.fromCodepoints([code]).toString();\n      }\n    }\n  };\n  const pasteEventIsCrippledSafariHTMLPaste = function (event) {\n    const paste = event.clipboardData;\n    if (paste) {\n      if (paste.types.includes(\"text/html\")) {\n        // Answer is yes if there's any possibility of Paste and Match Style in Safari,\n        // which is nearly impossible to detect confidently: https://bugs.webkit.org/show_bug.cgi?id=174165\n        for (const type of paste.types) {\n          const hasPasteboardFlavor = /^CorePasteboardFlavorType/.test(type);\n          const hasReadableDynamicData = /^dyn\\./.test(type) && paste.getData(type);\n          const mightBePasteAndMatchStyle = hasPasteboardFlavor || hasReadableDynamicData;\n          if (mightBePasteAndMatchStyle) {\n            return true;\n          }\n        }\n        return false;\n      } else {\n        const isExternalHTMLPaste = paste.types.includes(\"com.apple.webarchive\");\n        const isExternalRichTextPaste = paste.types.includes(\"com.apple.flat-rtfd\");\n        return isExternalHTMLPaste || isExternalRichTextPaste;\n      }\n    }\n  };\n  class CompositionInput extends BasicObject {\n    constructor(inputController) {\n      super(...arguments);\n      this.inputController = inputController;\n      this.responder = this.inputController.responder;\n      this.delegate = this.inputController.delegate;\n      this.inputSummary = this.inputController.inputSummary;\n      this.data = {};\n    }\n    start(data) {\n      this.data.start = data;\n      if (this.isSignificant()) {\n        var _this$responder5;\n        if (this.inputSummary.eventName === \"keypress\" && this.inputSummary.textAdded) {\n          var _this$responder4;\n          (_this$responder4 = this.responder) === null || _this$responder4 === void 0 || _this$responder4.deleteInDirection(\"left\");\n        }\n        if (!this.selectionIsExpanded()) {\n          this.insertPlaceholder();\n          this.requestRender();\n        }\n        this.range = (_this$responder5 = this.responder) === null || _this$responder5 === void 0 ? void 0 : _this$responder5.getSelectedRange();\n      }\n    }\n    update(data) {\n      this.data.update = data;\n      if (this.isSignificant()) {\n        const range = this.selectPlaceholder();\n        if (range) {\n          this.forgetPlaceholder();\n          this.range = range;\n        }\n      }\n    }\n    end(data) {\n      this.data.end = data;\n      if (this.isSignificant()) {\n        this.forgetPlaceholder();\n        if (this.canApplyToDocument()) {\n          var _this$delegate2, _this$responder6, _this$responder7, _this$responder8;\n          this.setInputSummary({\n            preferDocument: true,\n            didInput: false\n          });\n          (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || _this$delegate2.inputControllerWillPerformTyping();\n          (_this$responder6 = this.responder) === null || _this$responder6 === void 0 || _this$responder6.setSelectedRange(this.range);\n          (_this$responder7 = this.responder) === null || _this$responder7 === void 0 || _this$responder7.insertString(this.data.end);\n          return (_this$responder8 = this.responder) === null || _this$responder8 === void 0 ? void 0 : _this$responder8.setSelectedRange(this.range[0] + this.data.end.length);\n        } else if (this.data.start != null || this.data.update != null) {\n          this.requestReparse();\n          return this.inputController.reset();\n        }\n      } else {\n        return this.inputController.reset();\n      }\n    }\n    getEndData() {\n      return this.data.end;\n    }\n    isEnded() {\n      return this.getEndData() != null;\n    }\n    isSignificant() {\n      if (browser.composesExistingText) {\n        return this.inputSummary.didInput;\n      } else {\n        return true;\n      }\n    }\n\n    // Private\n\n    canApplyToDocument() {\n      var _this$data$start, _this$data$end;\n      return ((_this$data$start = this.data.start) === null || _this$data$start === void 0 ? void 0 : _this$data$start.length) === 0 && ((_this$data$end = this.data.end) === null || _this$data$end === void 0 ? void 0 : _this$data$end.length) > 0 && this.range;\n    }\n  }\n  CompositionInput.proxyMethod(\"inputController.setInputSummary\");\n  CompositionInput.proxyMethod(\"inputController.requestRender\");\n  CompositionInput.proxyMethod(\"inputController.requestReparse\");\n  CompositionInput.proxyMethod(\"responder?.selectionIsExpanded\");\n  CompositionInput.proxyMethod(\"responder?.insertPlaceholder\");\n  CompositionInput.proxyMethod(\"responder?.selectPlaceholder\");\n  CompositionInput.proxyMethod(\"responder?.forgetPlaceholder\");\n\n  class Level2InputController extends InputController {\n    constructor() {\n      super(...arguments);\n      this.render = this.render.bind(this);\n    }\n    elementDidMutate() {\n      if (this.scheduledRender) {\n        if (this.composing) {\n          var _this$delegate, _this$delegate$inputC;\n          return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$inputC = _this$delegate.inputControllerDidAllowUnhandledInput) === null || _this$delegate$inputC === void 0 ? void 0 : _this$delegate$inputC.call(_this$delegate);\n        }\n      } else {\n        return this.reparse();\n      }\n    }\n    scheduleRender() {\n      return this.scheduledRender ? this.scheduledRender : this.scheduledRender = requestAnimationFrame(this.render);\n    }\n    render() {\n      var _this$afterRender;\n      cancelAnimationFrame(this.scheduledRender);\n      this.scheduledRender = null;\n      if (!this.composing) {\n        var _this$delegate2;\n        (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || _this$delegate2.render();\n      }\n      (_this$afterRender = this.afterRender) === null || _this$afterRender === void 0 || _this$afterRender.call(this);\n      this.afterRender = null;\n    }\n    reparse() {\n      var _this$delegate3;\n      return (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 ? void 0 : _this$delegate3.reparse();\n    }\n\n    // Responder helpers\n\n    insertString() {\n      var _this$delegate4;\n      let string = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : \"\";\n      let options = arguments.length > 1 ? arguments[1] : undefined;\n      (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.inputControllerWillPerformTyping();\n      return this.withTargetDOMRange(function () {\n        var _this$responder;\n        return (_this$responder = this.responder) === null || _this$responder === void 0 ? void 0 : _this$responder.insertString(string, options);\n      });\n    }\n    toggleAttributeIfSupported(attributeName) {\n      if (getAllAttributeNames().includes(attributeName)) {\n        var _this$delegate5;\n        (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || _this$delegate5.inputControllerWillPerformFormatting(attributeName);\n        return this.withTargetDOMRange(function () {\n          var _this$responder2;\n          return (_this$responder2 = this.responder) === null || _this$responder2 === void 0 ? void 0 : _this$responder2.toggleCurrentAttribute(attributeName);\n        });\n      }\n    }\n    activateAttributeIfSupported(attributeName, value) {\n      if (getAllAttributeNames().includes(attributeName)) {\n        var _this$delegate6;\n        (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || _this$delegate6.inputControllerWillPerformFormatting(attributeName);\n        return this.withTargetDOMRange(function () {\n          var _this$responder3;\n          return (_this$responder3 = this.responder) === null || _this$responder3 === void 0 ? void 0 : _this$responder3.setCurrentAttribute(attributeName, value);\n        });\n      }\n    }\n    deleteInDirection(direction) {\n      let {\n        recordUndoEntry\n      } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n        recordUndoEntry: true\n      };\n      if (recordUndoEntry) {\n        var _this$delegate7;\n        (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || _this$delegate7.inputControllerWillPerformTyping();\n      }\n      const perform = () => {\n        var _this$responder4;\n        return (_this$responder4 = this.responder) === null || _this$responder4 === void 0 ? void 0 : _this$responder4.deleteInDirection(direction);\n      };\n      const domRange = this.getTargetDOMRange({\n        minLength: this.composing ? 1 : 2\n      });\n      if (domRange) {\n        return this.withTargetDOMRange(domRange, perform);\n      } else {\n        return perform();\n      }\n    }\n\n    // Selection helpers\n\n    withTargetDOMRange(domRange, fn) {\n      if (typeof domRange === \"function\") {\n        fn = domRange;\n        domRange = this.getTargetDOMRange();\n      }\n      if (domRange) {\n        var _this$responder5;\n        return (_this$responder5 = this.responder) === null || _this$responder5 === void 0 ? void 0 : _this$responder5.withTargetDOMRange(domRange, fn.bind(this));\n      } else {\n        selectionChangeObserver.reset();\n        return fn.call(this);\n      }\n    }\n    getTargetDOMRange() {\n      var _this$event$getTarget, _this$event;\n      let {\n        minLength\n      } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {\n        minLength: 0\n      };\n      const targetRanges = (_this$event$getTarget = (_this$event = this.event).getTargetRanges) === null || _this$event$getTarget === void 0 ? void 0 : _this$event$getTarget.call(_this$event);\n      if (targetRanges) {\n        if (targetRanges.length) {\n          const domRange = staticRangeToRange(targetRanges[0]);\n          if (minLength === 0 || domRange.toString().length >= minLength) {\n            return domRange;\n          }\n        }\n      }\n    }\n    withEvent(event, fn) {\n      let result;\n      this.event = event;\n      try {\n        result = fn.call(this);\n      } finally {\n        this.event = null;\n      }\n      return result;\n    }\n  }\n  _defineProperty(Level2InputController, \"events\", {\n    keydown(event) {\n      if (keyEventIsKeyboardCommand(event)) {\n        var _this$delegate8;\n        const command = keyboardCommandFromKeyEvent(event);\n        if ((_this$delegate8 = this.delegate) !== null && _this$delegate8 !== void 0 && _this$delegate8.inputControllerDidReceiveKeyboardCommand(command)) {\n          event.preventDefault();\n        }\n      } else {\n        let name = event.key;\n        if (event.altKey) {\n          name += \"+Alt\";\n        }\n        if (event.shiftKey) {\n          name += \"+Shift\";\n        }\n        const handler = this.constructor.keys[name];\n        if (handler) {\n          return this.withEvent(event, handler);\n        }\n      }\n    },\n    // Handle paste event to work around beforeinput.insertFromPaste browser bugs.\n    // Safe to remove each condition once fixed upstream.\n    paste(event) {\n      var _event$clipboardData;\n      // https://bugs.webkit.org/show_bug.cgi?id=194921\n      let paste;\n      const href = (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.getData(\"URL\");\n      if (pasteEventHasFilesOnly(event)) {\n        event.preventDefault();\n        return this.attachFiles(event.clipboardData.files);\n\n        // https://bugs.chromium.org/p/chromium/issues/detail?id=934448\n      } else if (pasteEventHasPlainTextOnly(event)) {\n        var _this$delegate9, _this$responder6, _this$delegate10;\n        event.preventDefault();\n        paste = {\n          type: \"text/plain\",\n          string: event.clipboardData.getData(\"text/plain\")\n        };\n        (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || _this$delegate9.inputControllerWillPaste(paste);\n        (_this$responder6 = this.responder) === null || _this$responder6 === void 0 || _this$responder6.insertString(paste.string);\n        this.render();\n        return (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 ? void 0 : _this$delegate10.inputControllerDidPaste(paste);\n\n        // https://bugs.webkit.org/show_bug.cgi?id=196702\n      } else if (href) {\n        var _this$delegate11, _this$responder7, _this$delegate12;\n        event.preventDefault();\n        paste = {\n          type: \"text/html\",\n          html: this.createLinkHTML(href)\n        };\n        (_this$delegate11 = this.delegate) === null || _this$delegate11 === void 0 || _this$delegate11.inputControllerWillPaste(paste);\n        (_this$responder7 = this.responder) === null || _this$responder7 === void 0 || _this$responder7.insertHTML(paste.html);\n        this.render();\n        return (_this$delegate12 = this.delegate) === null || _this$delegate12 === void 0 ? void 0 : _this$delegate12.inputControllerDidPaste(paste);\n      }\n    },\n    beforeinput(event) {\n      const handler = this.constructor.inputTypes[event.inputType];\n      const immmediateRender = shouldRenderInmmediatelyToDealWithIOSDictation(event);\n      if (handler) {\n        this.withEvent(event, handler);\n        if (!immmediateRender) {\n          this.scheduleRender();\n        }\n      }\n      if (immmediateRender) {\n        this.render();\n      }\n    },\n    input(event) {\n      selectionChangeObserver.reset();\n    },\n    dragstart(event) {\n      var _this$responder8;\n      if ((_this$responder8 = this.responder) !== null && _this$responder8 !== void 0 && _this$responder8.selectionContainsAttachments()) {\n        var _this$responder9;\n        event.dataTransfer.setData(\"application/x-trix-dragging\", true);\n        this.dragging = {\n          range: (_this$responder9 = this.responder) === null || _this$responder9 === void 0 ? void 0 : _this$responder9.getSelectedRange(),\n          point: pointFromEvent(event)\n        };\n      }\n    },\n    dragenter(event) {\n      if (dragEventHasFiles(event)) {\n        event.preventDefault();\n      }\n    },\n    dragover(event) {\n      if (this.dragging) {\n        event.preventDefault();\n        const point = pointFromEvent(event);\n        if (!objectsAreEqual(point, this.dragging.point)) {\n          var _this$responder10;\n          this.dragging.point = point;\n          return (_this$responder10 = this.responder) === null || _this$responder10 === void 0 ? void 0 : _this$responder10.setLocationRangeFromPointRange(point);\n        }\n      } else if (dragEventHasFiles(event)) {\n        event.preventDefault();\n      }\n    },\n    drop(event) {\n      if (this.dragging) {\n        var _this$delegate13, _this$responder11;\n        event.preventDefault();\n        (_this$delegate13 = this.delegate) === null || _this$delegate13 === void 0 || _this$delegate13.inputControllerWillMoveText();\n        (_this$responder11 = this.responder) === null || _this$responder11 === void 0 || _this$responder11.moveTextFromRange(this.dragging.range);\n        this.dragging = null;\n        return this.scheduleRender();\n      } else if (dragEventHasFiles(event)) {\n        var _this$responder12;\n        event.preventDefault();\n        const point = pointFromEvent(event);\n        (_this$responder12 = this.responder) === null || _this$responder12 === void 0 || _this$responder12.setLocationRangeFromPointRange(point);\n        return this.attachFiles(event.dataTransfer.files);\n      }\n    },\n    dragend() {\n      if (this.dragging) {\n        var _this$responder13;\n        (_this$responder13 = this.responder) === null || _this$responder13 === void 0 || _this$responder13.setSelectedRange(this.dragging.range);\n        this.dragging = null;\n      }\n    },\n    compositionend(event) {\n      if (this.composing) {\n        this.composing = false;\n        if (!browser$1.recentAndroid) this.scheduleRender();\n      }\n    }\n  });\n  _defineProperty(Level2InputController, \"keys\", {\n    ArrowLeft() {\n      var _this$responder14;\n      if ((_this$responder14 = this.responder) !== null && _this$responder14 !== void 0 && _this$responder14.shouldManageMovingCursorInDirection(\"backward\")) {\n        var _this$responder15;\n        this.event.preventDefault();\n        return (_this$responder15 = this.responder) === null || _this$responder15 === void 0 ? void 0 : _this$responder15.moveCursorInDirection(\"backward\");\n      }\n    },\n    ArrowRight() {\n      var _this$responder16;\n      if ((_this$responder16 = this.responder) !== null && _this$responder16 !== void 0 && _this$responder16.shouldManageMovingCursorInDirection(\"forward\")) {\n        var _this$responder17;\n        this.event.preventDefault();\n        return (_this$responder17 = this.responder) === null || _this$responder17 === void 0 ? void 0 : _this$responder17.moveCursorInDirection(\"forward\");\n      }\n    },\n    Backspace() {\n      var _this$responder18;\n      if ((_this$responder18 = this.responder) !== null && _this$responder18 !== void 0 && _this$responder18.shouldManageDeletingInDirection(\"backward\")) {\n        var _this$delegate14, _this$responder19;\n        this.event.preventDefault();\n        (_this$delegate14 = this.delegate) === null || _this$delegate14 === void 0 || _this$delegate14.inputControllerWillPerformTyping();\n        (_this$responder19 = this.responder) === null || _this$responder19 === void 0 || _this$responder19.deleteInDirection(\"backward\");\n        return this.render();\n      }\n    },\n    Tab() {\n      var _this$responder20;\n      if ((_this$responder20 = this.responder) !== null && _this$responder20 !== void 0 && _this$responder20.canIncreaseNestingLevel()) {\n        var _this$responder21;\n        this.event.preventDefault();\n        (_this$responder21 = this.responder) === null || _this$responder21 === void 0 || _this$responder21.increaseNestingLevel();\n        return this.render();\n      }\n    },\n    \"Tab+Shift\"() {\n      var _this$responder22;\n      if ((_this$responder22 = this.responder) !== null && _this$responder22 !== void 0 && _this$responder22.canDecreaseNestingLevel()) {\n        var _this$responder23;\n        this.event.preventDefault();\n        (_this$responder23 = this.responder) === null || _this$responder23 === void 0 || _this$responder23.decreaseNestingLevel();\n        return this.render();\n      }\n    }\n  });\n  _defineProperty(Level2InputController, \"inputTypes\", {\n    deleteByComposition() {\n      return this.deleteInDirection(\"backward\", {\n        recordUndoEntry: false\n      });\n    },\n    deleteByCut() {\n      return this.deleteInDirection(\"backward\");\n    },\n    deleteByDrag() {\n      this.event.preventDefault();\n      return this.withTargetDOMRange(function () {\n        var _this$responder24;\n        this.deleteByDragRange = (_this$responder24 = this.responder) === null || _this$responder24 === void 0 ? void 0 : _this$responder24.getSelectedRange();\n      });\n    },\n    deleteCompositionText() {\n      return this.deleteInDirection(\"backward\", {\n        recordUndoEntry: false\n      });\n    },\n    deleteContent() {\n      return this.deleteInDirection(\"backward\");\n    },\n    deleteContentBackward() {\n      return this.deleteInDirection(\"backward\");\n    },\n    deleteContentForward() {\n      return this.deleteInDirection(\"forward\");\n    },\n    deleteEntireSoftLine() {\n      return this.deleteInDirection(\"forward\");\n    },\n    deleteHardLineBackward() {\n      return this.deleteInDirection(\"backward\");\n    },\n    deleteHardLineForward() {\n      return this.deleteInDirection(\"forward\");\n    },\n    deleteSoftLineBackward() {\n      return this.deleteInDirection(\"backward\");\n    },\n    deleteSoftLineForward() {\n      return this.deleteInDirection(\"forward\");\n    },\n    deleteWordBackward() {\n      return this.deleteInDirection(\"backward\");\n    },\n    deleteWordForward() {\n      return this.deleteInDirection(\"forward\");\n    },\n    formatBackColor() {\n      return this.activateAttributeIfSupported(\"backgroundColor\", this.event.data);\n    },\n    formatBold() {\n      return this.toggleAttributeIfSupported(\"bold\");\n    },\n    formatFontColor() {\n      return this.activateAttributeIfSupported(\"color\", this.event.data);\n    },\n    formatFontName() {\n      return this.activateAttributeIfSupported(\"font\", this.event.data);\n    },\n    formatIndent() {\n      var _this$responder25;\n      if ((_this$responder25 = this.responder) !== null && _this$responder25 !== void 0 && _this$responder25.canIncreaseNestingLevel()) {\n        return this.withTargetDOMRange(function () {\n          var _this$responder26;\n          return (_this$responder26 = this.responder) === null || _this$responder26 === void 0 ? void 0 : _this$responder26.increaseNestingLevel();\n        });\n      }\n    },\n    formatItalic() {\n      return this.toggleAttributeIfSupported(\"italic\");\n    },\n    formatJustifyCenter() {\n      return this.toggleAttributeIfSupported(\"justifyCenter\");\n    },\n    formatJustifyFull() {\n      return this.toggleAttributeIfSupported(\"justifyFull\");\n    },\n    formatJustifyLeft() {\n      return this.toggleAttributeIfSupported(\"justifyLeft\");\n    },\n    formatJustifyRight() {\n      return this.toggleAttributeIfSupported(\"justifyRight\");\n    },\n    formatOutdent() {\n      var _this$responder27;\n      if ((_this$responder27 = this.responder) !== null && _this$responder27 !== void 0 && _this$responder27.canDecreaseNestingLevel()) {\n        return this.withTargetDOMRange(function () {\n          var _this$responder28;\n          return (_this$responder28 = this.responder) === null || _this$responder28 === void 0 ? void 0 : _this$responder28.decreaseNestingLevel();\n        });\n      }\n    },\n    formatRemove() {\n      this.withTargetDOMRange(function () {\n        for (const attributeName in (_this$responder29 = this.responder) === null || _this$responder29 === void 0 ? void 0 : _this$responder29.getCurrentAttributes()) {\n          var _this$responder29, _this$responder30;\n          (_this$responder30 = this.responder) === null || _this$responder30 === void 0 || _this$responder30.removeCurrentAttribute(attributeName);\n        }\n      });\n    },\n    formatSetBlockTextDirection() {\n      return this.activateAttributeIfSupported(\"blockDir\", this.event.data);\n    },\n    formatSetInlineTextDirection() {\n      return this.activateAttributeIfSupported(\"textDir\", this.event.data);\n    },\n    formatStrikeThrough() {\n      return this.toggleAttributeIfSupported(\"strike\");\n    },\n    formatSubscript() {\n      return this.toggleAttributeIfSupported(\"sub\");\n    },\n    formatSuperscript() {\n      return this.toggleAttributeIfSupported(\"sup\");\n    },\n    formatUnderline() {\n      return this.toggleAttributeIfSupported(\"underline\");\n    },\n    historyRedo() {\n      var _this$delegate15;\n      return (_this$delegate15 = this.delegate) === null || _this$delegate15 === void 0 ? void 0 : _this$delegate15.inputControllerWillPerformRedo();\n    },\n    historyUndo() {\n      var _this$delegate16;\n      return (_this$delegate16 = this.delegate) === null || _this$delegate16 === void 0 ? void 0 : _this$delegate16.inputControllerWillPerformUndo();\n    },\n    insertCompositionText() {\n      this.composing = true;\n      return this.insertString(this.event.data);\n    },\n    insertFromComposition() {\n      this.composing = false;\n      return this.insertString(this.event.data);\n    },\n    insertFromDrop() {\n      const range = this.deleteByDragRange;\n      if (range) {\n        var _this$delegate17;\n        this.deleteByDragRange = null;\n        (_this$delegate17 = this.delegate) === null || _this$delegate17 === void 0 || _this$delegate17.inputControllerWillMoveText();\n        return this.withTargetDOMRange(function () {\n          var _this$responder31;\n          return (_this$responder31 = this.responder) === null || _this$responder31 === void 0 ? void 0 : _this$responder31.moveTextFromRange(range);\n        });\n      }\n    },\n    insertFromPaste() {\n      const {\n        dataTransfer\n      } = this.event;\n      const paste = {\n        dataTransfer\n      };\n      const href = dataTransfer.getData(\"URL\");\n      const html = dataTransfer.getData(\"text/html\");\n      if (href) {\n        var _this$delegate18;\n        let string;\n        this.event.preventDefault();\n        paste.type = \"text/html\";\n        const name = dataTransfer.getData(\"public.url-name\");\n        if (name) {\n          string = squishBreakableWhitespace(name).trim();\n        } else {\n          string = href;\n        }\n        paste.html = this.createLinkHTML(href, string);\n        (_this$delegate18 = this.delegate) === null || _this$delegate18 === void 0 || _this$delegate18.inputControllerWillPaste(paste);\n        this.withTargetDOMRange(function () {\n          var _this$responder32;\n          return (_this$responder32 = this.responder) === null || _this$responder32 === void 0 ? void 0 : _this$responder32.insertHTML(paste.html);\n        });\n        this.afterRender = () => {\n          var _this$delegate19;\n          return (_this$delegate19 = this.delegate) === null || _this$delegate19 === void 0 ? void 0 : _this$delegate19.inputControllerDidPaste(paste);\n        };\n      } else if (dataTransferIsPlainText(dataTransfer)) {\n        var _this$delegate20;\n        paste.type = \"text/plain\";\n        paste.string = dataTransfer.getData(\"text/plain\");\n        (_this$delegate20 = this.delegate) === null || _this$delegate20 === void 0 || _this$delegate20.inputControllerWillPaste(paste);\n        this.withTargetDOMRange(function () {\n          var _this$responder33;\n          return (_this$responder33 = this.responder) === null || _this$responder33 === void 0 ? void 0 : _this$responder33.insertString(paste.string);\n        });\n        this.afterRender = () => {\n          var _this$delegate21;\n          return (_this$delegate21 = this.delegate) === null || _this$delegate21 === void 0 ? void 0 : _this$delegate21.inputControllerDidPaste(paste);\n        };\n      } else if (processableFilePaste(this.event)) {\n        var _this$delegate22;\n        paste.type = \"File\";\n        paste.file = dataTransfer.files[0];\n        (_this$delegate22 = this.delegate) === null || _this$delegate22 === void 0 || _this$delegate22.inputControllerWillPaste(paste);\n        this.withTargetDOMRange(function () {\n          var _this$responder34;\n          return (_this$responder34 = this.responder) === null || _this$responder34 === void 0 ? void 0 : _this$responder34.insertFile(paste.file);\n        });\n        this.afterRender = () => {\n          var _this$delegate23;\n          return (_this$delegate23 = this.delegate) === null || _this$delegate23 === void 0 ? void 0 : _this$delegate23.inputControllerDidPaste(paste);\n        };\n      } else if (html) {\n        var _this$delegate24;\n        this.event.preventDefault();\n        paste.type = \"text/html\";\n        paste.html = html;\n        (_this$delegate24 = this.delegate) === null || _this$delegate24 === void 0 || _this$delegate24.inputControllerWillPaste(paste);\n        this.withTargetDOMRange(function () {\n          var _this$responder35;\n          return (_this$responder35 = this.responder) === null || _this$responder35 === void 0 ? void 0 : _this$responder35.insertHTML(paste.html);\n        });\n        this.afterRender = () => {\n          var _this$delegate25;\n          return (_this$delegate25 = this.delegate) === null || _this$delegate25 === void 0 ? void 0 : _this$delegate25.inputControllerDidPaste(paste);\n        };\n      }\n    },\n    insertFromYank() {\n      return this.insertString(this.event.data);\n    },\n    insertLineBreak() {\n      return this.insertString(\"\\n\");\n    },\n    insertLink() {\n      return this.activateAttributeIfSupported(\"href\", this.event.data);\n    },\n    insertOrderedList() {\n      return this.toggleAttributeIfSupported(\"number\");\n    },\n    insertParagraph() {\n      var _this$delegate26;\n      (_this$delegate26 = this.delegate) === null || _this$delegate26 === void 0 || _this$delegate26.inputControllerWillPerformTyping();\n      return this.withTargetDOMRange(function () {\n        var _this$responder36;\n        return (_this$responder36 = this.responder) === null || _this$responder36 === void 0 ? void 0 : _this$responder36.insertLineBreak();\n      });\n    },\n    insertReplacementText() {\n      const replacement = this.event.dataTransfer.getData(\"text/plain\");\n      const domRange = this.event.getTargetRanges()[0];\n      this.withTargetDOMRange(domRange, () => {\n        this.insertString(replacement, {\n          updatePosition: false\n        });\n      });\n    },\n    insertText() {\n      var _this$event$dataTrans;\n      return this.insertString(this.event.data || ((_this$event$dataTrans = this.event.dataTransfer) === null || _this$event$dataTrans === void 0 ? void 0 : _this$event$dataTrans.getData(\"text/plain\")));\n    },\n    insertTranspose() {\n      return this.insertString(this.event.data);\n    },\n    insertUnorderedList() {\n      return this.toggleAttributeIfSupported(\"bullet\");\n    }\n  });\n  const staticRangeToRange = function (staticRange) {\n    const range = document.createRange();\n    range.setStart(staticRange.startContainer, staticRange.startOffset);\n    range.setEnd(staticRange.endContainer, staticRange.endOffset);\n    return range;\n  };\n\n  // Event helpers\n\n  const dragEventHasFiles = event => {\n    var _event$dataTransfer;\n    return Array.from(((_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.types) || []).includes(\"Files\");\n  };\n  const processableFilePaste = event => {\n    var _event$dataTransfer$f;\n    // Paste events that only have files are handled by the paste event handler,\n    // to work around Safari not supporting beforeinput.insertFromPaste for files.\n\n    // MS Office text pastes include a file with a screenshot of the text, but we should\n    // handle them as text pastes.\n    return ((_event$dataTransfer$f = event.dataTransfer.files) === null || _event$dataTransfer$f === void 0 ? void 0 : _event$dataTransfer$f[0]) && !pasteEventHasFilesOnly(event) && !dataTransferIsMsOfficePaste(event);\n  };\n  const pasteEventHasFilesOnly = function (event) {\n    const clipboard = event.clipboardData;\n    if (clipboard) {\n      const fileTypes = Array.from(clipboard.types).filter(type => type.match(/file/i)); // \"Files\", \"application/x-moz-file\"\n      return fileTypes.length === clipboard.types.length && clipboard.files.length >= 1;\n    }\n  };\n  const pasteEventHasPlainTextOnly = function (event) {\n    const clipboard = event.clipboardData;\n    if (clipboard) {\n      return clipboard.types.includes(\"text/plain\") && clipboard.types.length === 1;\n    }\n  };\n  const keyboardCommandFromKeyEvent = function (event) {\n    const command = [];\n    if (event.altKey) {\n      command.push(\"alt\");\n    }\n    if (event.shiftKey) {\n      command.push(\"shift\");\n    }\n    command.push(event.key);\n    return command;\n  };\n  const pointFromEvent = event => ({\n    x: event.clientX,\n    y: event.clientY\n  });\n\n  const attributeButtonSelector = \"[data-trix-attribute]\";\n  const actionButtonSelector = \"[data-trix-action]\";\n  const toolbarButtonSelector = \"\".concat(attributeButtonSelector, \", \").concat(actionButtonSelector);\n  const dialogSelector = \"[data-trix-dialog]\";\n  const activeDialogSelector = \"\".concat(dialogSelector, \"[data-trix-active]\");\n  const dialogButtonSelector = \"\".concat(dialogSelector, \" [data-trix-method]\");\n  const dialogInputSelector = \"\".concat(dialogSelector, \" [data-trix-input]\");\n  const getInputForDialog = (element, attributeName) => {\n    if (!attributeName) {\n      attributeName = getAttributeName(element);\n    }\n    return element.querySelector(\"[data-trix-input][name='\".concat(attributeName, \"']\"));\n  };\n  const getActionName = element => element.getAttribute(\"data-trix-action\");\n  const getAttributeName = element => {\n    return element.getAttribute(\"data-trix-attribute\") || element.getAttribute(\"data-trix-dialog-attribute\");\n  };\n  const getDialogName = element => element.getAttribute(\"data-trix-dialog\");\n  class ToolbarController extends BasicObject {\n    constructor(element) {\n      super(element);\n      this.didClickActionButton = this.didClickActionButton.bind(this);\n      this.didClickAttributeButton = this.didClickAttributeButton.bind(this);\n      this.didClickDialogButton = this.didClickDialogButton.bind(this);\n      this.didKeyDownDialogInput = this.didKeyDownDialogInput.bind(this);\n      this.element = element;\n      this.attributes = {};\n      this.actions = {};\n      this.resetDialogInputs();\n      handleEvent(\"mousedown\", {\n        onElement: this.element,\n        matchingSelector: actionButtonSelector,\n        withCallback: this.didClickActionButton\n      });\n      handleEvent(\"mousedown\", {\n        onElement: this.element,\n        matchingSelector: attributeButtonSelector,\n        withCallback: this.didClickAttributeButton\n      });\n      handleEvent(\"click\", {\n        onElement: this.element,\n        matchingSelector: toolbarButtonSelector,\n        preventDefault: true\n      });\n      handleEvent(\"click\", {\n        onElement: this.element,\n        matchingSelector: dialogButtonSelector,\n        withCallback: this.didClickDialogButton\n      });\n      handleEvent(\"keydown\", {\n        onElement: this.element,\n        matchingSelector: dialogInputSelector,\n        withCallback: this.didKeyDownDialogInput\n      });\n    }\n\n    // Event handlers\n\n    didClickActionButton(event, element) {\n      var _this$delegate;\n      (_this$delegate = this.delegate) === null || _this$delegate === void 0 || _this$delegate.toolbarDidClickButton();\n      event.preventDefault();\n      const actionName = getActionName(element);\n      if (this.getDialog(actionName)) {\n        return this.toggleDialog(actionName);\n      } else {\n        var _this$delegate2;\n        return (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 ? void 0 : _this$delegate2.toolbarDidInvokeAction(actionName, element);\n      }\n    }\n    didClickAttributeButton(event, element) {\n      var _this$delegate3;\n      (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || _this$delegate3.toolbarDidClickButton();\n      event.preventDefault();\n      const attributeName = getAttributeName(element);\n      if (this.getDialog(attributeName)) {\n        this.toggleDialog(attributeName);\n      } else {\n        var _this$delegate4;\n        (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.toolbarDidToggleAttribute(attributeName);\n      }\n      return this.refreshAttributeButtons();\n    }\n    didClickDialogButton(event, element) {\n      const dialogElement = findClosestElementFromNode(element, {\n        matchingSelector: dialogSelector\n      });\n      const method = element.getAttribute(\"data-trix-method\");\n      return this[method].call(this, dialogElement);\n    }\n    didKeyDownDialogInput(event, element) {\n      if (event.keyCode === 13) {\n        // Enter key\n        event.preventDefault();\n        const attribute = element.getAttribute(\"name\");\n        const dialog = this.getDialog(attribute);\n        this.setAttribute(dialog);\n      }\n      if (event.keyCode === 27) {\n        // Escape key\n        event.preventDefault();\n        return this.hideDialog();\n      }\n    }\n\n    // Action buttons\n\n    updateActions(actions) {\n      this.actions = actions;\n      return this.refreshActionButtons();\n    }\n    refreshActionButtons() {\n      return this.eachActionButton((element, actionName) => {\n        element.disabled = this.actions[actionName] === false;\n      });\n    }\n    eachActionButton(callback) {\n      return Array.from(this.element.querySelectorAll(actionButtonSelector)).map(element => callback(element, getActionName(element)));\n    }\n\n    // Attribute buttons\n\n    updateAttributes(attributes) {\n      this.attributes = attributes;\n      return this.refreshAttributeButtons();\n    }\n    refreshAttributeButtons() {\n      return this.eachAttributeButton((element, attributeName) => {\n        element.disabled = this.attributes[attributeName] === false;\n        if (this.attributes[attributeName] || this.dialogIsVisible(attributeName)) {\n          element.setAttribute(\"data-trix-active\", \"\");\n          return element.classList.add(\"trix-active\");\n        } else {\n          element.removeAttribute(\"data-trix-active\");\n          return element.classList.remove(\"trix-active\");\n        }\n      });\n    }\n    eachAttributeButton(callback) {\n      return Array.from(this.element.querySelectorAll(attributeButtonSelector)).map(element => callback(element, getAttributeName(element)));\n    }\n    applyKeyboardCommand(keys) {\n      const keyString = JSON.stringify(keys.sort());\n      for (const button of Array.from(this.element.querySelectorAll(\"[data-trix-key]\"))) {\n        const buttonKeys = button.getAttribute(\"data-trix-key\").split(\"+\");\n        const buttonKeyString = JSON.stringify(buttonKeys.sort());\n        if (buttonKeyString === keyString) {\n          triggerEvent(\"mousedown\", {\n            onElement: button\n          });\n          return true;\n        }\n      }\n      return false;\n    }\n\n    // Dialogs\n\n    dialogIsVisible(dialogName) {\n      const element = this.getDialog(dialogName);\n      if (element) {\n        return element.hasAttribute(\"data-trix-active\");\n      }\n    }\n    toggleDialog(dialogName) {\n      if (this.dialogIsVisible(dialogName)) {\n        return this.hideDialog();\n      } else {\n        return this.showDialog(dialogName);\n      }\n    }\n    showDialog(dialogName) {\n      var _this$delegate5, _this$delegate6;\n      this.hideDialog();\n      (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || _this$delegate5.toolbarWillShowDialog();\n      const element = this.getDialog(dialogName);\n      element.setAttribute(\"data-trix-active\", \"\");\n      element.classList.add(\"trix-active\");\n      Array.from(element.querySelectorAll(\"input[disabled]\")).forEach(disabledInput => {\n        disabledInput.removeAttribute(\"disabled\");\n      });\n      const attributeName = getAttributeName(element);\n      if (attributeName) {\n        const input = getInputForDialog(element, dialogName);\n        if (input) {\n          input.value = this.attributes[attributeName] || \"\";\n          input.select();\n        }\n      }\n      return (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 ? void 0 : _this$delegate6.toolbarDidShowDialog(dialogName);\n    }\n    setAttribute(dialogElement) {\n      var _this$delegate7;\n      const attributeName = getAttributeName(dialogElement);\n      const input = getInputForDialog(dialogElement, attributeName);\n      if (input.willValidate) {\n        input.setCustomValidity(\"\");\n        if (!input.checkValidity() || !this.isSafeAttribute(input)) {\n          input.setCustomValidity(\"Invalid value\");\n          input.setAttribute(\"data-trix-validate\", \"\");\n          input.classList.add(\"trix-validate\");\n          return input.focus();\n        }\n      }\n      (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || _this$delegate7.toolbarDidUpdateAttribute(attributeName, input.value);\n      return this.hideDialog();\n    }\n    isSafeAttribute(input) {\n      if (input.hasAttribute(\"data-trix-validate-href\")) {\n        return purify.isValidAttribute(\"a\", \"href\", input.value);\n      } else {\n        return true;\n      }\n    }\n    removeAttribute(dialogElement) {\n      var _this$delegate8;\n      const attributeName = getAttributeName(dialogElement);\n      (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || _this$delegate8.toolbarDidRemoveAttribute(attributeName);\n      return this.hideDialog();\n    }\n    hideDialog() {\n      const element = this.element.querySelector(activeDialogSelector);\n      if (element) {\n        var _this$delegate9;\n        element.removeAttribute(\"data-trix-active\");\n        element.classList.remove(\"trix-active\");\n        this.resetDialogInputs();\n        return (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 ? void 0 : _this$delegate9.toolbarDidHideDialog(getDialogName(element));\n      }\n    }\n    resetDialogInputs() {\n      Array.from(this.element.querySelectorAll(dialogInputSelector)).forEach(input => {\n        input.setAttribute(\"disabled\", \"disabled\");\n        input.removeAttribute(\"data-trix-validate\");\n        input.classList.remove(\"trix-validate\");\n      });\n    }\n    getDialog(dialogName) {\n      return this.element.querySelector(\"[data-trix-dialog=\".concat(dialogName, \"]\"));\n    }\n  }\n\n  const snapshotsAreEqual = (a, b) => rangesAreEqual(a.selectedRange, b.selectedRange) && a.document.isEqualTo(b.document);\n  class EditorController extends Controller {\n    constructor(_ref) {\n      let {\n        editorElement,\n        document,\n        html\n      } = _ref;\n      super(...arguments);\n      this.editorElement = editorElement;\n      this.selectionManager = new SelectionManager(this.editorElement);\n      this.selectionManager.delegate = this;\n      this.composition = new Composition();\n      this.composition.delegate = this;\n      this.attachmentManager = new AttachmentManager(this.composition.getAttachments());\n      this.attachmentManager.delegate = this;\n      this.inputController = input.getLevel() === 2 ? new Level2InputController(this.editorElement) : new Level0InputController(this.editorElement);\n      this.inputController.delegate = this;\n      this.inputController.responder = this.composition;\n      this.compositionController = new CompositionController(this.editorElement, this.composition);\n      this.compositionController.delegate = this;\n      this.toolbarController = new ToolbarController(this.editorElement.toolbarElement);\n      this.toolbarController.delegate = this;\n      this.editor = new Editor(this.composition, this.selectionManager, this.editorElement);\n      if (document) {\n        this.editor.loadDocument(document);\n      } else {\n        this.editor.loadHTML(html);\n      }\n    }\n    registerSelectionManager() {\n      return selectionChangeObserver.registerSelectionManager(this.selectionManager);\n    }\n    unregisterSelectionManager() {\n      return selectionChangeObserver.unregisterSelectionManager(this.selectionManager);\n    }\n    render() {\n      return this.compositionController.render();\n    }\n    reparse() {\n      return this.composition.replaceHTML(this.editorElement.innerHTML);\n    }\n\n    // Composition delegate\n\n    compositionDidChangeDocument(document) {\n      this.notifyEditorElement(\"document-change\");\n      if (!this.handlingInput) {\n        return this.render();\n      }\n    }\n    compositionDidChangeCurrentAttributes(currentAttributes) {\n      this.currentAttributes = currentAttributes;\n      this.toolbarController.updateAttributes(this.currentAttributes);\n      this.updateCurrentActions();\n      return this.notifyEditorElement(\"attributes-change\", {\n        attributes: this.currentAttributes\n      });\n    }\n    compositionDidPerformInsertionAtRange(range) {\n      if (this.pasting) {\n        this.pastedRange = range;\n      }\n    }\n    compositionShouldAcceptFile(file) {\n      return this.notifyEditorElement(\"file-accept\", {\n        file\n      });\n    }\n    compositionDidAddAttachment(attachment) {\n      const managedAttachment = this.attachmentManager.manageAttachment(attachment);\n      return this.notifyEditorElement(\"attachment-add\", {\n        attachment: managedAttachment\n      });\n    }\n    compositionDidEditAttachment(attachment) {\n      this.compositionController.rerenderViewForObject(attachment);\n      const managedAttachment = this.attachmentManager.manageAttachment(attachment);\n      this.notifyEditorElement(\"attachment-edit\", {\n        attachment: managedAttachment\n      });\n      return this.notifyEditorElement(\"change\");\n    }\n    compositionDidChangeAttachmentPreviewURL(attachment) {\n      this.compositionController.invalidateViewForObject(attachment);\n      return this.notifyEditorElement(\"change\");\n    }\n    compositionDidRemoveAttachment(attachment) {\n      const managedAttachment = this.attachmentManager.unmanageAttachment(attachment);\n      return this.notifyEditorElement(\"attachment-remove\", {\n        attachment: managedAttachment\n      });\n    }\n    compositionDidStartEditingAttachment(attachment, options) {\n      this.attachmentLocationRange = this.composition.document.getLocationRangeOfAttachment(attachment);\n      this.compositionController.installAttachmentEditorForAttachment(attachment, options);\n      return this.selectionManager.setLocationRange(this.attachmentLocationRange);\n    }\n    compositionDidStopEditingAttachment(attachment) {\n      this.compositionController.uninstallAttachmentEditor();\n      this.attachmentLocationRange = null;\n    }\n    compositionDidRequestChangingSelectionToLocationRange(locationRange) {\n      if (this.loadingSnapshot && !this.isFocused()) return;\n      this.requestedLocationRange = locationRange;\n      this.compositionRevisionWhenLocationRangeRequested = this.composition.revision;\n      if (!this.handlingInput) {\n        return this.render();\n      }\n    }\n    compositionWillLoadSnapshot() {\n      this.loadingSnapshot = true;\n    }\n    compositionDidLoadSnapshot() {\n      this.compositionController.refreshViewCache();\n      this.render();\n      this.loadingSnapshot = false;\n    }\n    getSelectionManager() {\n      return this.selectionManager;\n    }\n\n    // Attachment manager delegate\n\n    attachmentManagerDidRequestRemovalOfAttachment(attachment) {\n      return this.removeAttachment(attachment);\n    }\n\n    // Document controller delegate\n\n    compositionControllerWillSyncDocumentView() {\n      this.inputController.editorWillSyncDocumentView();\n      this.selectionManager.lock();\n      return this.selectionManager.clearSelection();\n    }\n    compositionControllerDidSyncDocumentView() {\n      this.inputController.editorDidSyncDocumentView();\n      this.selectionManager.unlock();\n      this.updateCurrentActions();\n      return this.notifyEditorElement(\"sync\");\n    }\n    compositionControllerDidRender() {\n      if (this.requestedLocationRange) {\n        if (this.compositionRevisionWhenLocationRangeRequested === this.composition.revision) {\n          this.selectionManager.setLocationRange(this.requestedLocationRange);\n        }\n        this.requestedLocationRange = null;\n        this.compositionRevisionWhenLocationRangeRequested = null;\n      }\n      if (this.renderedCompositionRevision !== this.composition.revision) {\n        this.runEditorFilters();\n        this.composition.updateCurrentAttributes();\n        this.notifyEditorElement(\"render\");\n      }\n      this.renderedCompositionRevision = this.composition.revision;\n    }\n    compositionControllerDidFocus() {\n      if (this.isFocusedInvisibly()) {\n        this.setLocationRange({\n          index: 0,\n          offset: 0\n        });\n      }\n      this.toolbarController.hideDialog();\n      return this.notifyEditorElement(\"focus\");\n    }\n    compositionControllerDidBlur() {\n      return this.notifyEditorElement(\"blur\");\n    }\n    compositionControllerDidSelectAttachment(attachment, options) {\n      this.toolbarController.hideDialog();\n      return this.composition.editAttachment(attachment, options);\n    }\n    compositionControllerDidRequestDeselectingAttachment(attachment) {\n      const locationRange = this.attachmentLocationRange || this.composition.document.getLocationRangeOfAttachment(attachment);\n      return this.selectionManager.setLocationRange(locationRange[1]);\n    }\n    compositionControllerWillUpdateAttachment(attachment) {\n      return this.editor.recordUndoEntry(\"Edit Attachment\", {\n        context: attachment.id,\n        consolidatable: true\n      });\n    }\n    compositionControllerDidRequestRemovalOfAttachment(attachment) {\n      return this.removeAttachment(attachment);\n    }\n\n    // Input controller delegate\n\n    inputControllerWillHandleInput() {\n      this.handlingInput = true;\n      this.requestedRender = false;\n    }\n    inputControllerDidRequestRender() {\n      this.requestedRender = true;\n    }\n    inputControllerDidHandleInput() {\n      this.handlingInput = false;\n      if (this.requestedRender) {\n        this.requestedRender = false;\n        return this.render();\n      }\n    }\n    inputControllerDidAllowUnhandledInput() {\n      return this.notifyEditorElement(\"change\");\n    }\n    inputControllerDidRequestReparse() {\n      return this.reparse();\n    }\n    inputControllerWillPerformTyping() {\n      return this.recordTypingUndoEntry();\n    }\n    inputControllerWillPerformFormatting(attributeName) {\n      return this.recordFormattingUndoEntry(attributeName);\n    }\n    inputControllerWillCutText() {\n      return this.editor.recordUndoEntry(\"Cut\");\n    }\n    inputControllerWillPaste(paste) {\n      this.editor.recordUndoEntry(\"Paste\");\n      this.pasting = true;\n      return this.notifyEditorElement(\"before-paste\", {\n        paste\n      });\n    }\n    inputControllerDidPaste(paste) {\n      paste.range = this.pastedRange;\n      this.pastedRange = null;\n      this.pasting = null;\n      return this.notifyEditorElement(\"paste\", {\n        paste\n      });\n    }\n    inputControllerWillMoveText() {\n      return this.editor.recordUndoEntry(\"Move\");\n    }\n    inputControllerWillAttachFiles() {\n      return this.editor.recordUndoEntry(\"Drop Files\");\n    }\n    inputControllerWillPerformUndo() {\n      return this.editor.undo();\n    }\n    inputControllerWillPerformRedo() {\n      return this.editor.redo();\n    }\n    inputControllerDidReceiveKeyboardCommand(keys) {\n      return this.toolbarController.applyKeyboardCommand(keys);\n    }\n    inputControllerDidStartDrag() {\n      this.locationRangeBeforeDrag = this.selectionManager.getLocationRange();\n    }\n    inputControllerDidReceiveDragOverPoint(point) {\n      return this.selectionManager.setLocationRangeFromPointRange(point);\n    }\n    inputControllerDidCancelDrag() {\n      this.selectionManager.setLocationRange(this.locationRangeBeforeDrag);\n      this.locationRangeBeforeDrag = null;\n    }\n\n    // Selection manager delegate\n\n    locationRangeDidChange(locationRange) {\n      this.composition.updateCurrentAttributes();\n      this.updateCurrentActions();\n      if (this.attachmentLocationRange && !rangesAreEqual(this.attachmentLocationRange, locationRange)) {\n        this.composition.stopEditingAttachment();\n      }\n      return this.notifyEditorElement(\"selection-change\");\n    }\n\n    // Toolbar controller delegate\n\n    toolbarDidClickButton() {\n      if (!this.getLocationRange()) {\n        return this.setLocationRange({\n          index: 0,\n          offset: 0\n        });\n      }\n    }\n    toolbarDidInvokeAction(actionName, invokingElement) {\n      return this.invokeAction(actionName, invokingElement);\n    }\n    toolbarDidToggleAttribute(attributeName) {\n      this.recordFormattingUndoEntry(attributeName);\n      this.composition.toggleCurrentAttribute(attributeName);\n      this.render();\n      if (!this.selectionFrozen) {\n        return this.editorElement.focus();\n      }\n    }\n    toolbarDidUpdateAttribute(attributeName, value) {\n      this.recordFormattingUndoEntry(attributeName);\n      this.composition.setCurrentAttribute(attributeName, value);\n      this.render();\n      if (!this.selectionFrozen) {\n        return this.editorElement.focus();\n      }\n    }\n    toolbarDidRemoveAttribute(attributeName) {\n      this.recordFormattingUndoEntry(attributeName);\n      this.composition.removeCurrentAttribute(attributeName);\n      this.render();\n      if (!this.selectionFrozen) {\n        return this.editorElement.focus();\n      }\n    }\n    toolbarWillShowDialog(dialogElement) {\n      this.composition.expandSelectionForEditing();\n      return this.freezeSelection();\n    }\n    toolbarDidShowDialog(dialogName) {\n      return this.notifyEditorElement(\"toolbar-dialog-show\", {\n        dialogName\n      });\n    }\n    toolbarDidHideDialog(dialogName) {\n      this.thawSelection();\n      this.editorElement.focus();\n      return this.notifyEditorElement(\"toolbar-dialog-hide\", {\n        dialogName\n      });\n    }\n\n    // Selection\n\n    freezeSelection() {\n      if (!this.selectionFrozen) {\n        this.selectionManager.lock();\n        this.composition.freezeSelection();\n        this.selectionFrozen = true;\n        return this.render();\n      }\n    }\n    thawSelection() {\n      if (this.selectionFrozen) {\n        this.composition.thawSelection();\n        this.selectionManager.unlock();\n        this.selectionFrozen = false;\n        return this.render();\n      }\n    }\n    canInvokeAction(actionName) {\n      if (this.actionIsExternal(actionName)) {\n        return true;\n      } else {\n        var _this$actions$actionN;\n        return !!((_this$actions$actionN = this.actions[actionName]) !== null && _this$actions$actionN !== void 0 && (_this$actions$actionN = _this$actions$actionN.test) !== null && _this$actions$actionN !== void 0 && _this$actions$actionN.call(this));\n      }\n    }\n    invokeAction(actionName, invokingElement) {\n      if (this.actionIsExternal(actionName)) {\n        return this.notifyEditorElement(\"action-invoke\", {\n          actionName,\n          invokingElement\n        });\n      } else {\n        var _this$actions$actionN2;\n        return (_this$actions$actionN2 = this.actions[actionName]) === null || _this$actions$actionN2 === void 0 || (_this$actions$actionN2 = _this$actions$actionN2.perform) === null || _this$actions$actionN2 === void 0 ? void 0 : _this$actions$actionN2.call(this);\n      }\n    }\n    actionIsExternal(actionName) {\n      return /^x-./.test(actionName);\n    }\n    getCurrentActions() {\n      const result = {};\n      for (const actionName in this.actions) {\n        result[actionName] = this.canInvokeAction(actionName);\n      }\n      return result;\n    }\n    updateCurrentActions() {\n      const currentActions = this.getCurrentActions();\n      if (!objectsAreEqual(currentActions, this.currentActions)) {\n        this.currentActions = currentActions;\n        this.toolbarController.updateActions(this.currentActions);\n        return this.notifyEditorElement(\"actions-change\", {\n          actions: this.currentActions\n        });\n      }\n    }\n\n    // Editor filters\n\n    runEditorFilters() {\n      let snapshot = this.composition.getSnapshot();\n      Array.from(this.editor.filters).forEach(filter => {\n        const {\n          document,\n          selectedRange\n        } = snapshot;\n        snapshot = filter.call(this.editor, snapshot) || {};\n        if (!snapshot.document) {\n          snapshot.document = document;\n        }\n        if (!snapshot.selectedRange) {\n          snapshot.selectedRange = selectedRange;\n        }\n      });\n      if (!snapshotsAreEqual(snapshot, this.composition.getSnapshot())) {\n        return this.composition.loadSnapshot(snapshot);\n      }\n    }\n\n    // Private\n\n    updateInputElement() {\n      const element = this.compositionController.getSerializableElement();\n      const value = serializeToContentType(element, \"text/html\");\n      return this.editorElement.setFormValue(value);\n    }\n    notifyEditorElement(message, data) {\n      switch (message) {\n        case \"document-change\":\n          this.documentChangedSinceLastRender = true;\n          break;\n        case \"render\":\n          if (this.documentChangedSinceLastRender) {\n            this.documentChangedSinceLastRender = false;\n            this.notifyEditorElement(\"change\");\n          }\n          break;\n        case \"change\":\n        case \"attachment-add\":\n        case \"attachment-edit\":\n        case \"attachment-remove\":\n          this.updateInputElement();\n          break;\n      }\n      return this.editorElement.notify(message, data);\n    }\n    removeAttachment(attachment) {\n      this.editor.recordUndoEntry(\"Delete Attachment\");\n      this.composition.removeAttachment(attachment);\n      return this.render();\n    }\n    recordFormattingUndoEntry(attributeName) {\n      const blockConfig = getBlockConfig(attributeName);\n      const locationRange = this.selectionManager.getLocationRange();\n      if (blockConfig || !rangeIsCollapsed(locationRange)) {\n        return this.editor.recordUndoEntry(\"Formatting\", {\n          context: this.getUndoContext(),\n          consolidatable: true\n        });\n      }\n    }\n    recordTypingUndoEntry() {\n      return this.editor.recordUndoEntry(\"Typing\", {\n        context: this.getUndoContext(this.currentAttributes),\n        consolidatable: true\n      });\n    }\n    getUndoContext() {\n      for (var _len = arguments.length, context = new Array(_len), _key = 0; _key < _len; _key++) {\n        context[_key] = arguments[_key];\n      }\n      return [this.getLocationContext(), this.getTimeContext(), ...Array.from(context)];\n    }\n    getLocationContext() {\n      const locationRange = this.selectionManager.getLocationRange();\n      if (rangeIsCollapsed(locationRange)) {\n        return locationRange[0].index;\n      } else {\n        return locationRange;\n      }\n    }\n    getTimeContext() {\n      if (undo.interval > 0) {\n        return Math.floor(new Date().getTime() / undo.interval);\n      } else {\n        return 0;\n      }\n    }\n    isFocused() {\n      var _this$editorElement$o;\n      return this.editorElement === ((_this$editorElement$o = this.editorElement.ownerDocument) === null || _this$editorElement$o === void 0 ? void 0 : _this$editorElement$o.activeElement);\n    }\n\n    // Detect \"Cursor disappears sporadically\" Firefox bug.\n    // - https://bugzilla.mozilla.org/show_bug.cgi?id=226301\n    isFocusedInvisibly() {\n      return this.isFocused() && !this.getLocationRange();\n    }\n    get actions() {\n      return this.constructor.actions;\n    }\n  }\n  _defineProperty(EditorController, \"actions\", {\n    undo: {\n      test() {\n        return this.editor.canUndo();\n      },\n      perform() {\n        return this.editor.undo();\n      }\n    },\n    redo: {\n      test() {\n        return this.editor.canRedo();\n      },\n      perform() {\n        return this.editor.redo();\n      }\n    },\n    link: {\n      test() {\n        return this.editor.canActivateAttribute(\"href\");\n      }\n    },\n    increaseNestingLevel: {\n      test() {\n        return this.editor.canIncreaseNestingLevel();\n      },\n      perform() {\n        return this.editor.increaseNestingLevel() && this.render();\n      }\n    },\n    decreaseNestingLevel: {\n      test() {\n        return this.editor.canDecreaseNestingLevel();\n      },\n      perform() {\n        return this.editor.decreaseNestingLevel() && this.render();\n      }\n    },\n    attachFiles: {\n      test() {\n        return true;\n      },\n      perform() {\n        return input.pickFiles(this.editor.insertFiles);\n      }\n    }\n  });\n  EditorController.proxyMethod(\"getSelectionManager().setLocationRange\");\n  EditorController.proxyMethod(\"getSelectionManager().getLocationRange\");\n\n  var controllers = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    AttachmentEditorController: AttachmentEditorController,\n    CompositionController: CompositionController,\n    Controller: Controller,\n    EditorController: EditorController,\n    InputController: InputController,\n    Level0InputController: Level0InputController,\n    Level2InputController: Level2InputController,\n    ToolbarController: ToolbarController\n  });\n\n  var observers = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    MutationObserver: MutationObserver,\n    SelectionChangeObserver: SelectionChangeObserver\n  });\n\n  var operations = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    FileVerificationOperation: FileVerificationOperation,\n    ImagePreloadOperation: ImagePreloadOperation\n  });\n\n  installDefaultCSSForTagName(\"trix-toolbar\", \"%t {\\n  display: block;\\n}\\n\\n%t {\\n  white-space: nowrap;\\n}\\n\\n%t [data-trix-dialog] {\\n  display: none;\\n}\\n\\n%t [data-trix-dialog][data-trix-active] {\\n  display: block;\\n}\\n\\n%t [data-trix-dialog] [data-trix-validate]:invalid {\\n  background-color: #ffdddd;\\n}\");\n  class TrixToolbarElement extends HTMLElement {\n    // Element lifecycle\n\n    connectedCallback() {\n      if (this.innerHTML === \"\") {\n        this.innerHTML = toolbar.getDefaultHTML();\n      }\n    }\n\n    // Properties\n\n    get editorElements() {\n      if (this.id) {\n        var _this$ownerDocument;\n        const nodeList = (_this$ownerDocument = this.ownerDocument) === null || _this$ownerDocument === void 0 ? void 0 : _this$ownerDocument.querySelectorAll(\"trix-editor[toolbar=\\\"\".concat(this.id, \"\\\"]\"));\n        return Array.from(nodeList);\n      } else {\n        return [];\n      }\n    }\n    get editorElement() {\n      const [editorElement] = this.editorElements;\n      return editorElement;\n    }\n  }\n\n  let id = 0;\n\n  // Contenteditable support helpers\n\n  const autofocus = function (element) {\n    if (!document.querySelector(\":focus\")) {\n      if (element.hasAttribute(\"autofocus\") && document.querySelector(\"[autofocus]\") === element) {\n        return element.focus();\n      }\n    }\n  };\n  const makeEditable = function (element) {\n    if (element.hasAttribute(\"contenteditable\")) {\n      return;\n    }\n    element.toggleAttribute(\"contenteditable\", !element.disabled);\n    return handleEventOnce(\"focus\", {\n      onElement: element,\n      withCallback() {\n        return configureContentEditable(element);\n      }\n    });\n  };\n  const configureContentEditable = function (element) {\n    disableObjectResizing(element);\n    return setDefaultParagraphSeparator(element);\n  };\n  const disableObjectResizing = function (element) {\n    var _document$queryComman, _document;\n    if ((_document$queryComman = (_document = document).queryCommandSupported) !== null && _document$queryComman !== void 0 && _document$queryComman.call(_document, \"enableObjectResizing\")) {\n      document.execCommand(\"enableObjectResizing\", false, false);\n      return handleEvent(\"mscontrolselect\", {\n        onElement: element,\n        preventDefault: true\n      });\n    }\n  };\n  const setDefaultParagraphSeparator = function (element) {\n    var _document$queryComman2, _document2;\n    if ((_document$queryComman2 = (_document2 = document).queryCommandSupported) !== null && _document$queryComman2 !== void 0 && _document$queryComman2.call(_document2, \"DefaultParagraphSeparator\")) {\n      const {\n        tagName\n      } = attributes.default;\n      if ([\"div\", \"p\"].includes(tagName)) {\n        return document.execCommand(\"DefaultParagraphSeparator\", false, tagName);\n      }\n    }\n  };\n\n  // Accessibility helpers\n\n  const addAccessibilityRole = function (element) {\n    if (element.hasAttribute(\"role\")) {\n      return;\n    }\n    return element.setAttribute(\"role\", \"textbox\");\n  };\n  const ensureAriaLabel = function (element) {\n    if (element.hasAttribute(\"aria-label\") || element.hasAttribute(\"aria-labelledby\")) {\n      return;\n    }\n    const update = function () {\n      const texts = Array.from(element.labels).map(label => {\n        if (!label.contains(element)) return label.textContent;\n      }).filter(text => text);\n      const text = texts.join(\" \");\n      if (text) {\n        return element.setAttribute(\"aria-label\", text);\n      } else {\n        return element.removeAttribute(\"aria-label\");\n      }\n    };\n    update();\n    return handleEvent(\"focus\", {\n      onElement: element,\n      withCallback: update\n    });\n  };\n\n  // Style\n\n  const cursorTargetStyles = function () {\n    if (browser$1.forcesObjectResizing) {\n      return {\n        display: \"inline\",\n        width: \"auto\"\n      };\n    } else {\n      return {\n        display: \"inline-block\",\n        width: \"1px\"\n      };\n    }\n  }();\n  installDefaultCSSForTagName(\"trix-editor\", \"%t {\\n    display: block;\\n}\\n\\n%t:empty::before {\\n    content: attr(placeholder);\\n    color: graytext;\\n    cursor: text;\\n    pointer-events: none;\\n    white-space: pre-line;\\n}\\n\\n%t a[contenteditable=false] {\\n    cursor: text;\\n}\\n\\n%t img {\\n    max-width: 100%;\\n    height: auto;\\n}\\n\\n%t \".concat(attachmentSelector, \" figcaption textarea {\\n    resize: none;\\n}\\n\\n%t \").concat(attachmentSelector, \" figcaption textarea.trix-autoresize-clone {\\n    position: absolute;\\n    left: -9999px;\\n    max-height: 0px;\\n}\\n\\n%t \").concat(attachmentSelector, \" figcaption[data-trix-placeholder]:empty::before {\\n    content: attr(data-trix-placeholder);\\n    color: graytext;\\n}\\n\\n%t [data-trix-cursor-target] {\\n    display: \").concat(cursorTargetStyles.display, \" !important;\\n    width: \").concat(cursorTargetStyles.width, \" !important;\\n    padding: 0 !important;\\n    margin: 0 !important;\\n    border: none !important;\\n}\\n\\n%t [data-trix-cursor-target=left] {\\n    vertical-align: top !important;\\n    margin-left: -1px !important;\\n}\\n\\n%t [data-trix-cursor-target=right] {\\n    vertical-align: bottom !important;\\n    margin-right: -1px !important;\\n}\"));\n  var _internals = /*#__PURE__*/new WeakMap();\n  var _formDisabled = /*#__PURE__*/new WeakMap();\n  var _validate = /*#__PURE__*/new WeakSet();\n  class ElementInternalsDelegate {\n    constructor(element) {\n      _classPrivateMethodInitSpec(this, _validate);\n      _defineProperty(this, \"value\", \"\");\n      _classPrivateFieldInitSpec(this, _internals, {\n        writable: true,\n        value: void 0\n      });\n      _classPrivateFieldInitSpec(this, _formDisabled, {\n        writable: true,\n        value: void 0\n      });\n      this.element = element;\n      _classPrivateFieldSet(this, _internals, element.attachInternals());\n      _classPrivateFieldSet(this, _formDisabled, false);\n    }\n    connectedCallback() {\n      _classPrivateMethodGet(this, _validate, _validate2).call(this);\n    }\n    disconnectedCallback() {}\n    get form() {\n      return _classPrivateFieldGet(this, _internals).form;\n    }\n    get name() {\n      return this.element.getAttribute(\"name\");\n    }\n    set name(value) {\n      this.element.setAttribute(\"name\", value);\n    }\n    get labels() {\n      return _classPrivateFieldGet(this, _internals).labels;\n    }\n    get disabled() {\n      return _classPrivateFieldGet(this, _formDisabled) || this.element.hasAttribute(\"disabled\");\n    }\n    set disabled(value) {\n      this.element.toggleAttribute(\"disabled\", value);\n    }\n    get required() {\n      return this.element.hasAttribute(\"required\");\n    }\n    set required(value) {\n      this.element.toggleAttribute(\"required\", value);\n      _classPrivateMethodGet(this, _validate, _validate2).call(this);\n    }\n    get validity() {\n      return _classPrivateFieldGet(this, _internals).validity;\n    }\n    get validationMessage() {\n      return _classPrivateFieldGet(this, _internals).validationMessage;\n    }\n    get willValidate() {\n      return _classPrivateFieldGet(this, _internals).willValidate;\n    }\n    formDisabledCallback(disabled) {\n      _classPrivateFieldSet(this, _formDisabled, disabled);\n    }\n    setFormValue(value) {\n      this.value = value;\n      _classPrivateMethodGet(this, _validate, _validate2).call(this);\n      _classPrivateFieldGet(this, _internals).setFormValue(this.element.disabled ? undefined : this.value);\n    }\n    checkValidity() {\n      return _classPrivateFieldGet(this, _internals).checkValidity();\n    }\n    reportValidity() {\n      return _classPrivateFieldGet(this, _internals).reportValidity();\n    }\n    setCustomValidity(validationMessage) {\n      _classPrivateMethodGet(this, _validate, _validate2).call(this, validationMessage);\n    }\n  }\n  function _validate2() {\n    let customValidationMessage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : \"\";\n    const {\n      required,\n      value\n    } = this.element;\n    const valueMissing = required && !value;\n    const customError = !!customValidationMessage;\n    const input = makeElement(\"input\", {\n      required\n    });\n    const validationMessage = customValidationMessage || input.validationMessage;\n    _classPrivateFieldGet(this, _internals).setValidity({\n      valueMissing,\n      customError\n    }, validationMessage);\n  }\n  var _focusHandler = /*#__PURE__*/new WeakMap();\n  var _resetBubbled = /*#__PURE__*/new WeakMap();\n  var _clickBubbled = /*#__PURE__*/new WeakMap();\n  class LegacyDelegate {\n    constructor(element) {\n      _classPrivateFieldInitSpec(this, _focusHandler, {\n        writable: true,\n        value: void 0\n      });\n      _classPrivateFieldInitSpec(this, _resetBubbled, {\n        writable: true,\n        value: event => {\n          if (event.defaultPrevented) return;\n          if (event.target !== this.element.form) return;\n          this.element.reset();\n        }\n      });\n      _classPrivateFieldInitSpec(this, _clickBubbled, {\n        writable: true,\n        value: event => {\n          if (event.defaultPrevented) return;\n          if (this.element.contains(event.target)) return;\n          const label = findClosestElementFromNode(event.target, {\n            matchingSelector: \"label\"\n          });\n          if (!label) return;\n          if (!Array.from(this.labels).includes(label)) return;\n          this.element.focus();\n        }\n      });\n      this.element = element;\n    }\n    connectedCallback() {\n      _classPrivateFieldSet(this, _focusHandler, ensureAriaLabel(this.element));\n      window.addEventListener(\"reset\", _classPrivateFieldGet(this, _resetBubbled), false);\n      window.addEventListener(\"click\", _classPrivateFieldGet(this, _clickBubbled), false);\n    }\n    disconnectedCallback() {\n      var _classPrivateFieldGet2;\n      (_classPrivateFieldGet2 = _classPrivateFieldGet(this, _focusHandler)) === null || _classPrivateFieldGet2 === void 0 || _classPrivateFieldGet2.destroy();\n      window.removeEventListener(\"reset\", _classPrivateFieldGet(this, _resetBubbled), false);\n      window.removeEventListener(\"click\", _classPrivateFieldGet(this, _clickBubbled), false);\n    }\n    get labels() {\n      const labels = [];\n      if (this.element.id && this.element.ownerDocument) {\n        labels.push(...Array.from(this.element.ownerDocument.querySelectorAll(\"label[for='\".concat(this.element.id, \"']\")) || []));\n      }\n      const label = findClosestElementFromNode(this.element, {\n        matchingSelector: \"label\"\n      });\n      if (label) {\n        if ([this.element, null].includes(label.control)) {\n          labels.push(label);\n        }\n      }\n      return labels;\n    }\n    get form() {\n      console.warn(\"This browser does not support the .form property for trix-editor elements.\");\n      return null;\n    }\n    get name() {\n      console.warn(\"This browser does not support the .name property for trix-editor elements.\");\n      return null;\n    }\n    set name(value) {\n      console.warn(\"This browser does not support the .name property for trix-editor elements.\");\n    }\n    get disabled() {\n      console.warn(\"This browser does not support the [disabled] attribute for trix-editor elements.\");\n      return false;\n    }\n    set disabled(value) {\n      console.warn(\"This browser does not support the [disabled] attribute for trix-editor elements.\");\n    }\n    get required() {\n      console.warn(\"This browser does not support the [required] attribute for trix-editor elements.\");\n      return false;\n    }\n    set required(value) {\n      console.warn(\"This browser does not support the [required] attribute for trix-editor elements.\");\n    }\n    get validity() {\n      console.warn(\"This browser does not support the validity property for trix-editor elements.\");\n      return null;\n    }\n    get validationMessage() {\n      console.warn(\"This browser does not support the validationMessage property for trix-editor elements.\");\n      return \"\";\n    }\n    get willValidate() {\n      console.warn(\"This browser does not support the willValidate property for trix-editor elements.\");\n      return false;\n    }\n    formDisabledCallback(value) {}\n    setFormValue(value) {}\n    checkValidity() {\n      console.warn(\"This browser does not support checkValidity() for trix-editor elements.\");\n      return true;\n    }\n    reportValidity() {\n      console.warn(\"This browser does not support reportValidity() for trix-editor elements.\");\n      return true;\n    }\n    setCustomValidity(validationMessage) {\n      console.warn(\"This browser does not support setCustomValidity(validationMessage) for trix-editor elements.\");\n    }\n  }\n  var _delegate = /*#__PURE__*/new WeakMap();\n  class TrixEditorElement extends HTMLElement {\n    constructor() {\n      super();\n      _classPrivateFieldInitSpec(this, _delegate, {\n        writable: true,\n        value: void 0\n      });\n      this.willCreateInput = true;\n      _classPrivateFieldSet(this, _delegate, this.constructor.formAssociated ? new ElementInternalsDelegate(this) : new LegacyDelegate(this));\n    }\n\n    // Properties\n\n    get trixId() {\n      if (this.hasAttribute(\"trix-id\")) {\n        return this.getAttribute(\"trix-id\");\n      } else {\n        this.setAttribute(\"trix-id\", ++id);\n        return this.trixId;\n      }\n    }\n    get labels() {\n      return _classPrivateFieldGet(this, _delegate).labels;\n    }\n    get disabled() {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        return inputElement.disabled;\n      } else {\n        return _classPrivateFieldGet(this, _delegate).disabled;\n      }\n    }\n    set disabled(value) {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        inputElement.disabled = value;\n      }\n      _classPrivateFieldGet(this, _delegate).disabled = value;\n    }\n    get required() {\n      return _classPrivateFieldGet(this, _delegate).required;\n    }\n    set required(value) {\n      _classPrivateFieldGet(this, _delegate).required = value;\n    }\n    get validity() {\n      return _classPrivateFieldGet(this, _delegate).validity;\n    }\n    get validationMessage() {\n      return _classPrivateFieldGet(this, _delegate).validationMessage;\n    }\n    get willValidate() {\n      return _classPrivateFieldGet(this, _delegate).willValidate;\n    }\n    get type() {\n      return this.localName;\n    }\n    get toolbarElement() {\n      if (this.hasAttribute(\"toolbar\")) {\n        var _this$ownerDocument;\n        return (_this$ownerDocument = this.ownerDocument) === null || _this$ownerDocument === void 0 ? void 0 : _this$ownerDocument.getElementById(this.getAttribute(\"toolbar\"));\n      } else if (this.parentNode) {\n        const toolbarId = \"trix-toolbar-\".concat(this.trixId);\n        this.setAttribute(\"toolbar\", toolbarId);\n        this.internalToolbar = makeElement(\"trix-toolbar\", {\n          id: toolbarId\n        });\n        this.parentNode.insertBefore(this.internalToolbar, this);\n        return this.internalToolbar;\n      } else {\n        return undefined;\n      }\n    }\n    get form() {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        return inputElement.form;\n      } else {\n        return _classPrivateFieldGet(this, _delegate).form;\n      }\n    }\n    get inputElement() {\n      if (this.hasAttribute(\"input\")) {\n        var _this$ownerDocument2;\n        return (_this$ownerDocument2 = this.ownerDocument) === null || _this$ownerDocument2 === void 0 ? void 0 : _this$ownerDocument2.getElementById(this.getAttribute(\"input\"));\n      } else {\n        return undefined;\n      }\n    }\n    get editor() {\n      var _this$editorControlle;\n      return (_this$editorControlle = this.editorController) === null || _this$editorControlle === void 0 ? void 0 : _this$editorControlle.editor;\n    }\n    get name() {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        return inputElement.name;\n      } else {\n        return _classPrivateFieldGet(this, _delegate).name;\n      }\n    }\n    set name(value) {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        inputElement.name = value;\n      } else {\n        _classPrivateFieldGet(this, _delegate).name = value;\n      }\n    }\n    get value() {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        return inputElement.value;\n      } else {\n        return _classPrivateFieldGet(this, _delegate).value;\n      }\n    }\n    set value(defaultValue) {\n      var _this$editor;\n      this.defaultValue = defaultValue;\n      (_this$editor = this.editor) === null || _this$editor === void 0 || _this$editor.loadHTML(this.defaultValue);\n    }\n\n    // Element callbacks\n\n    attributeChangedCallback(name, oldValue, newValue) {\n      if (name === \"connected\" && this.isConnected && oldValue != null && oldValue !== newValue) {\n        requestAnimationFrame(() => this.reconnect());\n      }\n    }\n\n    // Controller delegate methods\n\n    notify(message, data) {\n      if (this.editorController) {\n        return triggerEvent(\"trix-\".concat(message), {\n          onElement: this,\n          attributes: data\n        });\n      }\n    }\n    setFormValue(value) {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        inputElement.value = value;\n      }\n      _classPrivateFieldGet(this, _delegate).setFormValue(value);\n    }\n\n    // Element lifecycle\n\n    connectedCallback() {\n      if (!this.hasAttribute(\"data-trix-internal\")) {\n        makeEditable(this);\n        addAccessibilityRole(this);\n        if (!this.editorController) {\n          triggerEvent(\"trix-before-initialize\", {\n            onElement: this\n          });\n          this.defaultValue = this.inputElement ? this.inputElement.value : this.innerHTML;\n          if (!this.hasAttribute(\"input\") && this.parentNode && this.willCreateInput) {\n            const inputId = \"trix-input-\".concat(this.trixId);\n            this.setAttribute(\"input\", inputId);\n            const element = makeElement(\"input\", {\n              type: \"hidden\",\n              id: inputId\n            });\n            this.parentNode.insertBefore(element, this.nextElementSibling);\n          }\n          this.editorController = new EditorController({\n            editorElement: this,\n            html: this.defaultValue\n          });\n          requestAnimationFrame(() => triggerEvent(\"trix-initialize\", {\n            onElement: this\n          }));\n        }\n        this.editorController.registerSelectionManager();\n        _classPrivateFieldGet(this, _delegate).connectedCallback();\n        this.toggleAttribute(\"connected\", true);\n        autofocus(this);\n      }\n    }\n    disconnectedCallback() {\n      var _this$editorControlle2;\n      (_this$editorControlle2 = this.editorController) === null || _this$editorControlle2 === void 0 || _this$editorControlle2.unregisterSelectionManager();\n      _classPrivateFieldGet(this, _delegate).disconnectedCallback();\n      this.toggleAttribute(\"connected\", false);\n    }\n    reconnect() {\n      this.removeInternalToolbar();\n      this.disconnectedCallback();\n      this.connectedCallback();\n    }\n    removeInternalToolbar() {\n      var _this$internalToolbar;\n      (_this$internalToolbar = this.internalToolbar) === null || _this$internalToolbar === void 0 || _this$internalToolbar.remove();\n      this.internalToolbar = null;\n    }\n\n    // Form support\n\n    checkValidity() {\n      return _classPrivateFieldGet(this, _delegate).checkValidity();\n    }\n    reportValidity() {\n      return _classPrivateFieldGet(this, _delegate).reportValidity();\n    }\n    setCustomValidity(validationMessage) {\n      _classPrivateFieldGet(this, _delegate).setCustomValidity(validationMessage);\n    }\n    formDisabledCallback(disabled) {\n      const {\n        inputElement\n      } = this;\n      if (inputElement) {\n        inputElement.disabled = disabled;\n      }\n      this.toggleAttribute(\"contenteditable\", !disabled);\n      _classPrivateFieldGet(this, _delegate).formDisabledCallback(disabled);\n    }\n    formResetCallback() {\n      this.reset();\n    }\n    reset() {\n      this.value = this.defaultValue;\n    }\n  }\n  _defineProperty(TrixEditorElement, \"formAssociated\", \"ElementInternals\" in window);\n  _defineProperty(TrixEditorElement, \"observedAttributes\", [\"connected\"]);\n\n  var elements = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    TrixEditorElement: TrixEditorElement,\n    TrixToolbarElement: TrixToolbarElement\n  });\n\n  var filters = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    Filter: Filter,\n    attachmentGalleryFilter: attachmentGalleryFilter\n  });\n\n  const Trix = {\n    VERSION: version,\n    config,\n    core,\n    models,\n    views,\n    controllers,\n    observers,\n    operations,\n    elements,\n    filters\n  };\n\n  // Expose models under the Trix constant for compatibility with v1\n  Object.assign(Trix, models);\n  function start() {\n    if (!customElements.get(\"trix-toolbar\")) {\n      customElements.define(\"trix-toolbar\", TrixToolbarElement);\n    }\n    if (!customElements.get(\"trix-editor\")) {\n      customElements.define(\"trix-editor\", TrixEditorElement);\n    }\n  }\n  window.Trix = Trix;\n  setTimeout(start, 0);\n\n  return Trix;\n\n}));\n"
  },
  {
    "path": "action_text-trix/app/assets/stylesheets/trix.css",
    "content": "@charset \"UTF-8\";\ntrix-editor {\n  border: 1px solid #bbb;\n  border-radius: 3px;\n  margin: 0;\n  padding: 0.4em 0.6em;\n  min-height: 5em;\n  outline: none;\n}\n\ntrix-toolbar * {\n  box-sizing: border-box;\n}\ntrix-toolbar .trix-button-row {\n  display: flex;\n  flex-wrap: nowrap;\n  justify-content: space-between;\n  overflow-x: auto;\n}\ntrix-toolbar .trix-button-group {\n  display: flex;\n  margin-bottom: 10px;\n  border: 1px solid #bbb;\n  border-top-color: #ccc;\n  border-bottom-color: #888;\n  border-radius: 3px;\n}\ntrix-toolbar .trix-button-group:not(:first-child) {\n  margin-left: 1.5vw;\n}\n@media (max-width: 768px) {\n  trix-toolbar .trix-button-group:not(:first-child) {\n    margin-left: 0;\n  }\n}\ntrix-toolbar .trix-button-group-spacer {\n  flex-grow: 1;\n}\n@media (max-width: 768px) {\n  trix-toolbar .trix-button-group-spacer {\n    display: none;\n  }\n}\ntrix-toolbar .trix-button {\n  position: relative;\n  float: left;\n  color: rgba(0, 0, 0, 0.6);\n  font-size: 0.75em;\n  font-weight: 600;\n  white-space: nowrap;\n  padding: 0 0.5em;\n  margin: 0;\n  outline: none;\n  border: none;\n  border-bottom: 1px solid #ddd;\n  border-radius: 0;\n  background: transparent;\n}\ntrix-toolbar .trix-button:not(:first-child) {\n  border-left: 1px solid #ccc;\n}\ntrix-toolbar .trix-button.trix-active {\n  background: #cbeefa;\n  color: rgb(0, 0, 0);\n}\ntrix-toolbar .trix-button:not(:disabled) {\n  cursor: pointer;\n}\ntrix-toolbar .trix-button:disabled {\n  color: rgba(0, 0, 0, 0.125);\n}\n@media (max-width: 768px) {\n  trix-toolbar .trix-button {\n    letter-spacing: -0.01em;\n    padding: 0 0.3em;\n  }\n}\ntrix-toolbar .trix-button--icon {\n  font-size: inherit;\n  width: 2.6em;\n  height: 1.6em;\n  max-width: calc(0.8em + 4vw);\n  text-indent: -9999px;\n}\n@media (max-width: 768px) {\n  trix-toolbar .trix-button--icon {\n    height: 2em;\n    max-width: calc(0.8em + 3.5vw);\n  }\n}\ntrix-toolbar .trix-button--icon::before {\n  display: inline-block;\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  opacity: 0.6;\n  content: \"\";\n  background-position: center;\n  background-repeat: no-repeat;\n  background-size: contain;\n}\n@media (max-width: 768px) {\n  trix-toolbar .trix-button--icon::before {\n    right: 6%;\n    left: 6%;\n  }\n}\ntrix-toolbar .trix-button--icon.trix-active::before {\n  opacity: 1;\n}\ntrix-toolbar .trix-button--icon:disabled::before {\n  opacity: 0.125;\n}\ntrix-toolbar .trix-button--icon-attach::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.5%2018V7.5c0-2.25%203-2.25%203%200V18c0%204.125-6%204.125-6%200V7.5c0-6.375%209-6.375%209%200V18%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-miterlimit%3D%2210%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E\");\n  top: 8%;\n  bottom: 4%;\n}\ntrix-toolbar .trix-button--icon-bold::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6.522%2019.242a.5.5%200%200%201-.5-.5V5.35a.5.5%200%200%201%20.5-.5h5.783c1.347%200%202.46.345%203.24.982.783.64%201.216%201.562%201.216%202.683%200%201.13-.587%202.129-1.476%202.71a.35.35%200%200%200%20.049.613c1.259.56%202.101%201.742%202.101%203.22%200%201.282-.483%202.334-1.363%203.063-.876.726-2.132%201.12-3.66%201.12h-5.89ZM9.27%207.347v3.362h1.97c.766%200%201.347-.17%201.733-.464.38-.291.587-.716.587-1.27%200-.53-.183-.928-.513-1.198-.334-.273-.838-.43-1.505-.43H9.27Zm0%205.606v3.791h2.389c.832%200%201.448-.177%201.853-.497.399-.315.614-.786.614-1.423%200-.62-.22-1.077-.63-1.385-.418-.313-1.053-.486-1.905-.486H9.27Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-italic::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M9%205h6.5v2h-2.23l-2.31%2010H13v2H6v-2h2.461l2.306-10H9V5Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-link::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M18.948%205.258a4.337%204.337%200%200%200-6.108%200L11.217%206.87a.993.993%200%200%200%200%201.41c.392.39%201.027.39%201.418%200l1.623-1.613a2.323%202.323%200%200%201%203.271%200%202.29%202.29%200%200%201%200%203.251l-2.393%202.38a3.021%203.021%200%200%201-4.255%200l-.05-.049a1.007%201.007%200%200%200-1.418%200%20.993.993%200%200%200%200%201.41l.05.049a5.036%205.036%200%200%200%207.091%200l2.394-2.38a4.275%204.275%200%200%200%200-6.072Zm-13.683%2013.6a4.337%204.337%200%200%200%206.108%200l1.262-1.255a.993.993%200%200%200%200-1.41%201.007%201.007%200%200%200-1.418%200L9.954%2017.45a2.323%202.323%200%200%201-3.27%200%202.29%202.29%200%200%201%200-3.251l2.344-2.331a2.579%202.579%200%200%201%203.631%200c.392.39%201.027.39%201.419%200a.993.993%200%200%200%200-1.41%204.593%204.593%200%200%200-6.468%200l-2.345%202.33a4.275%204.275%200%200%200%200%206.072Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-strike::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6%2014.986c.088%202.647%202.246%204.258%205.635%204.258%203.496%200%205.713-1.728%205.713-4.463%200-.275-.02-.536-.062-.781h-3.461c.398.293.573.654.573%201.123%200%201.035-1.074%201.787-2.646%201.787-1.563%200-2.773-.762-2.91-1.924H6ZM6.432%2010h3.763c-.632-.314-.914-.715-.914-1.273%200-1.045.977-1.739%202.432-1.739%201.475%200%202.52.723%202.617%201.914h2.764c-.05-2.548-2.11-4.238-5.39-4.238-3.145%200-5.392%201.719-5.392%204.316%200%20.363.04.703.12%201.02ZM4%2011a1%201%200%201%200%200%202h15a1%201%200%201%200%200-2H4Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-quote::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M4.581%208.471c.44-.5%201.056-.834%201.758-.995C8.074%207.17%209.201%207.822%2010%208.752c1.354%201.578%201.33%203.555.394%205.277-.941%201.731-2.788%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.121-.49.16-.764.294-.286.567-.566.791-.835.222-.266.413-.54.524-.815.113-.28.156-.597.026-.908-.128-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.674-2.7c0-.905.283-1.59.72-2.088Zm9.419%200c.44-.5%201.055-.834%201.758-.995%201.734-.306%202.862.346%203.66%201.276%201.355%201.578%201.33%203.555.395%205.277-.941%201.731-2.789%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.122-.49.16-.764.294-.286.567-.566.791-.835.222-.266.412-.54.523-.815.114-.28.157-.597.026-.908-.127-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.672-2.701c0-.905.283-1.59.72-2.088Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-heading-1::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.5%207.5v-3h-12v3H14v13h3v-13h4.5ZM9%2013.5h3.5v-3h-10v3H6v7h3v-7Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-code::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3.293%2011.293a1%201%200%200%200%200%201.414l4%204a1%201%200%201%200%201.414-1.414L5.414%2012l3.293-3.293a1%201%200%200%200-1.414-1.414l-4%204Zm13.414%205.414%204-4a1%201%200%200%200%200-1.414l-4-4a1%201%200%201%200-1.414%201.414L18.586%2012l-3.293%203.293a1%201%200%200%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-bullet-list::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%207.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203ZM8%206a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-2.5-5a1.5%201.5%200%201%201-3%200%201.5%201.5%200%200%201%203%200ZM5%2019.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-number-list::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%204h2v4H4V5H3V4Zm5%202a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-3.5-7H6v1l-1.5%202H6v1H3v-1l1.667-2H3v-1h2.5ZM3%2017v-1h3v4H3v-1h2v-.5H4v-1h1V17H3Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-undo::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%2014a1%201%200%200%200%201%201h6a1%201%200%201%200%200-2H6.257c2.247-2.764%205.151-3.668%207.579-3.264%202.589.432%204.739%202.356%205.174%205.405a1%201%200%200%200%201.98-.283c-.564-3.95-3.415-6.526-6.825-7.095C11.084%207.25%207.63%208.377%205%2011.39V8a1%201%200%200%200-2%200v6Zm2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-redo::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21%2014a1%201%200%200%201-1%201h-6a1%201%200%201%201%200-2h3.743c-2.247-2.764-5.151-3.668-7.579-3.264-2.589.432-4.739%202.356-5.174%205.405a1%201%200%200%201-1.98-.283c.564-3.95%203.415-6.526%206.826-7.095%203.08-.513%206.534.614%209.164%203.626V8a1%201%200%201%201%202%200v6Zm-2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-decrease-nesting-level::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-3.707-5.707a1%201%200%200%200%200%201.414l2%202a1%201%200%201%200%201.414-1.414L4.414%2012l1.293-1.293a1%201%200%200%200-1.414-1.414l-2%202Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-button--icon-increase-nesting-level::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-2.293-2.293%202-2a1%201%200%200%200%200-1.414l-2-2a1%201%200%201%200-1.414%201.414L3.586%2012l-1.293%201.293a1%201%200%201%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\");\n}\ntrix-toolbar .trix-dialogs {\n  position: relative;\n}\ntrix-toolbar .trix-dialog {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  font-size: 0.75em;\n  padding: 15px 10px;\n  background: #fff;\n  box-shadow: 0 0.3em 1em #ccc;\n  border-top: 2px solid #888;\n  border-radius: 5px;\n  z-index: 5;\n}\ntrix-toolbar .trix-input--dialog {\n  font-size: inherit;\n  font-weight: normal;\n  padding: 0.5em 0.8em;\n  margin: 0 10px 0 0;\n  border-radius: 3px;\n  border: 1px solid #bbb;\n  background-color: #fff;\n  box-shadow: none;\n  outline: none;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n}\ntrix-toolbar .trix-input--dialog.validate:invalid {\n  box-shadow: #F00 0px 0px 1.5px 1px;\n}\ntrix-toolbar .trix-button--dialog {\n  font-size: inherit;\n  padding: 0.5em;\n  border-bottom: none;\n}\ntrix-toolbar .trix-dialog--link {\n  max-width: 600px;\n}\ntrix-toolbar .trix-dialog__link-fields {\n  display: flex;\n  align-items: baseline;\n}\ntrix-toolbar .trix-dialog__link-fields .trix-input {\n  flex: 1;\n}\ntrix-toolbar .trix-dialog__link-fields .trix-button-group {\n  flex: 0 0 content;\n  margin: 0;\n}\n\ntrix-editor [data-trix-mutable]:not(.attachment__caption-editor) {\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n\ntrix-editor [data-trix-mutable] ::-moz-selection, trix-editor [data-trix-mutable]::-moz-selection,\ntrix-editor [data-trix-cursor-target]::-moz-selection {\n  background: none;\n}\ntrix-editor [data-trix-mutable] ::selection, trix-editor [data-trix-mutable]::selection,\ntrix-editor [data-trix-cursor-target]::selection {\n  background: none;\n}\n\ntrix-editor [data-trix-mutable].attachment__caption-editor:focus::-moz-selection {\n  background: highlight;\n}\ntrix-editor [data-trix-mutable].attachment__caption-editor:focus::selection {\n  background: highlight;\n}\n\ntrix-editor [data-trix-mutable].attachment.attachment--file {\n  box-shadow: 0 0 0 2px highlight;\n  border-color: transparent;\n}\ntrix-editor [data-trix-mutable].attachment img {\n  box-shadow: 0 0 0 2px highlight;\n}\ntrix-editor .attachment {\n  position: relative;\n}\ntrix-editor .attachment:hover {\n  cursor: default;\n}\ntrix-editor .attachment--preview .attachment__caption:hover {\n  cursor: text;\n}\ntrix-editor .attachment__progress {\n  position: absolute;\n  z-index: 1;\n  height: 20px;\n  top: calc(50% - 10px);\n  left: 5%;\n  width: 90%;\n  opacity: 0.9;\n  transition: opacity 200ms ease-in;\n}\ntrix-editor .attachment__progress[value=\"100\"] {\n  opacity: 0;\n}\ntrix-editor .attachment__caption-editor {\n  display: inline-block;\n  width: 100%;\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  font-family: inherit;\n  line-height: inherit;\n  color: inherit;\n  text-align: center;\n  vertical-align: top;\n  border: none;\n  outline: none;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n}\ntrix-editor .attachment__toolbar {\n  position: absolute;\n  z-index: 1;\n  top: -0.9em;\n  left: 0;\n  width: 100%;\n  text-align: center;\n}\ntrix-editor .trix-button-group {\n  display: inline-flex;\n}\ntrix-editor .trix-button {\n  position: relative;\n  float: left;\n  color: #666;\n  white-space: nowrap;\n  font-size: 80%;\n  padding: 0 0.8em;\n  margin: 0;\n  outline: none;\n  border: none;\n  border-radius: 0;\n  background: transparent;\n}\ntrix-editor .trix-button:not(:first-child) {\n  border-left: 1px solid #ccc;\n}\ntrix-editor .trix-button.trix-active {\n  background: #cbeefa;\n}\ntrix-editor .trix-button:not(:disabled) {\n  cursor: pointer;\n}\ntrix-editor .trix-button--remove {\n  text-indent: -9999px;\n  display: inline-block;\n  padding: 0;\n  outline: none;\n  width: 1.8em;\n  height: 1.8em;\n  line-height: 1.8em;\n  border-radius: 50%;\n  background-color: #fff;\n  border: 2px solid highlight;\n  box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25);\n}\ntrix-editor .trix-button--remove::before {\n  display: inline-block;\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  opacity: 0.7;\n  content: \"\";\n  background-image: url(\"data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E\");\n  background-position: center;\n  background-repeat: no-repeat;\n  background-size: 90%;\n}\ntrix-editor .trix-button--remove:hover {\n  border-color: #333;\n}\ntrix-editor .trix-button--remove:hover::before {\n  opacity: 1;\n}\ntrix-editor .attachment__metadata-container {\n  position: relative;\n}\ntrix-editor .attachment__metadata {\n  position: absolute;\n  left: 50%;\n  top: 2em;\n  transform: translate(-50%, 0);\n  max-width: 90%;\n  padding: 0.1em 0.6em;\n  font-size: 0.8em;\n  color: #fff;\n  background-color: rgba(0, 0, 0, 0.7);\n  border-radius: 3px;\n}\ntrix-editor .attachment__metadata .attachment__name {\n  display: inline-block;\n  max-width: 100%;\n  vertical-align: bottom;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\ntrix-editor .attachment__metadata .attachment__size {\n  margin-left: 0.2em;\n  white-space: nowrap;\n}\n\n.trix-content {\n  line-height: 1.5;\n  overflow-wrap: break-word;\n  word-break: break-word;\n}\n.trix-content * {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n.trix-content h1 {\n  font-size: 1.2em;\n  line-height: 1.2;\n}\n.trix-content blockquote {\n  border: 0 solid #ccc;\n  border-left-width: 0.3em;\n  margin-left: 0.3em;\n  padding-left: 0.6em;\n}\n.trix-content [dir=rtl] blockquote,\n.trix-content blockquote[dir=rtl] {\n  border-width: 0;\n  border-right-width: 0.3em;\n  margin-right: 0.3em;\n  padding-right: 0.6em;\n}\n.trix-content li {\n  margin-left: 1em;\n}\n.trix-content [dir=rtl] li {\n  margin-right: 1em;\n}\n.trix-content pre {\n  display: inline-block;\n  width: 100%;\n  vertical-align: top;\n  font-family: monospace;\n  font-size: 0.9em;\n  padding: 0.5em;\n  white-space: pre;\n  background-color: #eee;\n  overflow-x: auto;\n}\n.trix-content img {\n  max-width: 100%;\n  height: auto;\n}\n.trix-content .attachment {\n  display: inline-block;\n  position: relative;\n  max-width: 100%;\n}\n.trix-content .attachment a {\n  color: inherit;\n  text-decoration: none;\n}\n.trix-content .attachment a:hover, .trix-content .attachment a:visited:hover {\n  color: inherit;\n}\n.trix-content .attachment__caption {\n  text-align: center;\n}\n.trix-content .attachment__caption .attachment__name + .attachment__size::before {\n  content: \" •\";\n}\n.trix-content .attachment--preview {\n  width: 100%;\n  text-align: center;\n}\n.trix-content .attachment--preview .attachment__caption {\n  color: #666;\n  font-size: 0.9em;\n  line-height: 1.2;\n}\n.trix-content .attachment--file {\n  color: #333;\n  line-height: 1;\n  margin: 0 2px 2px 2px;\n  padding: 0.4em 1em;\n  border: 1px solid #bbb;\n  border-radius: 5px;\n}\n.trix-content .attachment-gallery {\n  display: flex;\n  flex-wrap: wrap;\n  position: relative;\n}\n.trix-content .attachment-gallery .attachment {\n  flex: 1 0 33%;\n  padding: 0 0.5em;\n  max-width: 33%;\n}\n.trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment {\n  flex-basis: 50%;\n  max-width: 50%;\n}"
  },
  {
    "path": "action_text-trix/bin/rails",
    "content": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails gems\n# installed from the root of your application.\n\nENGINE_ROOT = File.expand_path('..', __dir__)\nENGINE_PATH = File.expand_path('../lib/action_text/trix/engine.rb', __dir__)\nAPP_PATH = File.expand_path('../test/dummy/config/application', __dir__)\n\n# Set up gems listed in the Gemfile.\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)\nrequire \"bundler/setup\" if File.exist?(ENV[\"BUNDLE_GEMFILE\"])\n\nrequire \"rails/all\"\nrequire \"rails/test_unit/railtie\"\nrequire \"rails/engine/commands\"\n"
  },
  {
    "path": "action_text-trix/lib/action_text/trix/engine.rb",
    "content": "module Trix\n  class Engine < ::Rails::Engine\n    initializer \"trix.asset\" do |app|\n      if app.config.respond_to?(:assets)\n        app.config.assets.precompile += %w[ trix.js trix.css ]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "action_text-trix/lib/action_text/trix/version.rb",
    "content": "module Trix\n  VERSION = \"2.1.17\"\nend\n"
  },
  {
    "path": "action_text-trix/lib/action_text/trix.rb",
    "content": "require_relative \"trix/version\"\nrequire_relative \"trix/engine\"\n"
  },
  {
    "path": "action_text-trix/test/application_system_test_case.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"test_helper\"\n\nclass ApplicationSystemTestCase < ActionDispatch::SystemTestCase\n  driven_by :cuprite, using: :chrome, options: {\n    js_errors: true,\n    headless: ENV[\"HEADLESS\"] != \"0\"\n  }\nend\n\nCapybara.server = :puma, { Silent: true }\n"
  },
  {
    "path": "action_text-trix/test/dummy/Rakefile",
    "content": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative \"config/application\"\n\nRails.application.load_tasks\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/assets/images/.keep",
    "content": ""
  },
  {
    "path": "action_text-trix/test/dummy/app/assets/stylesheets/actiontext.css",
    "content": "/*\n * Default Trix editor styles. See Action Text overwrites below.\n*/\n\ntrix-editor {\n  border: 1px solid #bbb;\n  border-radius: 3px;\n  margin: 0;\n  padding: 0.4em 0.6em;\n  min-height: 5em;\n  outline: none; }\n\ntrix-toolbar * {\n  box-sizing: border-box; }\n\ntrix-toolbar .trix-button-row {\n  display: flex;\n  flex-wrap: nowrap;\n  justify-content: space-between;\n  overflow-x: auto; }\n\ntrix-toolbar .trix-button-group {\n  display: flex;\n  margin-bottom: 10px;\n  border: 1px solid #bbb;\n  border-top-color: #ccc;\n  border-bottom-color: #888;\n  border-radius: 3px; }\n  trix-toolbar .trix-button-group:not(:first-child) {\n    margin-left: 1.5vw; }\n    @media (max-width: 768px) {\n      trix-toolbar .trix-button-group:not(:first-child) {\n        margin-left: 0; } }\n\ntrix-toolbar .trix-button-group-spacer {\n  flex-grow: 1; }\n  @media (max-width: 768px) {\n    trix-toolbar .trix-button-group-spacer {\n      display: none; } }\n\ntrix-toolbar .trix-button {\n  position: relative;\n  float: left;\n  color: rgba(0, 0, 0, 0.6);\n  font-size: 0.75em;\n  font-weight: 600;\n  white-space: nowrap;\n  padding: 0 0.5em;\n  margin: 0;\n  outline: none;\n  border: none;\n  border-bottom: 1px solid #ddd;\n  border-radius: 0;\n  background: transparent; }\n  trix-toolbar .trix-button:not(:first-child) {\n    border-left: 1px solid #ccc; }\n  trix-toolbar .trix-button.trix-active {\n    background: #cbeefa;\n    color: black; }\n  trix-toolbar .trix-button:not(:disabled) {\n    cursor: pointer; }\n  trix-toolbar .trix-button:disabled {\n    color: rgba(0, 0, 0, 0.125); }\n  @media (max-width: 768px) {\n    trix-toolbar .trix-button {\n      letter-spacing: -0.01em;\n      padding: 0 0.3em; } }\n\ntrix-toolbar .trix-button--icon {\n  font-size: inherit;\n  width: 2.6em;\n  height: 1.6em;\n  max-width: calc(0.8em + 4vw);\n  text-indent: -9999px; }\n  @media (max-width: 768px) {\n    trix-toolbar .trix-button--icon {\n      height: 2em;\n      max-width: calc(0.8em + 3.5vw); } }\n  trix-toolbar .trix-button--icon::before {\n    display: inline-block;\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    opacity: 0.6;\n    content: \"\";\n    background-position: center;\n    background-repeat: no-repeat;\n    background-size: contain; }\n    @media (max-width: 768px) {\n      trix-toolbar .trix-button--icon::before {\n        right: 6%;\n        left: 6%; } }\n  trix-toolbar .trix-button--icon.trix-active::before {\n    opacity: 1; }\n  trix-toolbar .trix-button--icon:disabled::before {\n    opacity: 0.125; }\n\ntrix-toolbar .trix-button--icon-attach::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.5%2018V7.5c0-2.25%203-2.25%203%200V18c0%204.125-6%204.125-6%200V7.5c0-6.375%209-6.375%209%200V18%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-miterlimit%3D%2210%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E\");\n  top: 8%;\n  bottom: 4%; }\n\ntrix-toolbar .trix-button--icon-bold::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6.522%2019.242a.5.5%200%200%201-.5-.5V5.35a.5.5%200%200%201%20.5-.5h5.783c1.347%200%202.46.345%203.24.982.783.64%201.216%201.562%201.216%202.683%200%201.13-.587%202.129-1.476%202.71a.35.35%200%200%200%20.049.613c1.259.56%202.101%201.742%202.101%203.22%200%201.282-.483%202.334-1.363%203.063-.876.726-2.132%201.12-3.66%201.12h-5.89ZM9.27%207.347v3.362h1.97c.766%200%201.347-.17%201.733-.464.38-.291.587-.716.587-1.27%200-.53-.183-.928-.513-1.198-.334-.273-.838-.43-1.505-.43H9.27Zm0%205.606v3.791h2.389c.832%200%201.448-.177%201.853-.497.399-.315.614-.786.614-1.423%200-.62-.22-1.077-.63-1.385-.418-.313-1.053-.486-1.905-.486H9.27Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-italic::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M9%205h6.5v2h-2.23l-2.31%2010H13v2H6v-2h2.461l2.306-10H9V5Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-link::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M18.948%205.258a4.337%204.337%200%200%200-6.108%200L11.217%206.87a.993.993%200%200%200%200%201.41c.392.39%201.027.39%201.418%200l1.623-1.613a2.323%202.323%200%200%201%203.271%200%202.29%202.29%200%200%201%200%203.251l-2.393%202.38a3.021%203.021%200%200%201-4.255%200l-.05-.049a1.007%201.007%200%200%200-1.418%200%20.993.993%200%200%200%200%201.41l.05.049a5.036%205.036%200%200%200%207.091%200l2.394-2.38a4.275%204.275%200%200%200%200-6.072Zm-13.683%2013.6a4.337%204.337%200%200%200%206.108%200l1.262-1.255a.993.993%200%200%200%200-1.41%201.007%201.007%200%200%200-1.418%200L9.954%2017.45a2.323%202.323%200%200%201-3.27%200%202.29%202.29%200%200%201%200-3.251l2.344-2.331a2.579%202.579%200%200%201%203.631%200c.392.39%201.027.39%201.419%200a.993.993%200%200%200%200-1.41%204.593%204.593%200%200%200-6.468%200l-2.345%202.33a4.275%204.275%200%200%200%200%206.072Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-strike::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6%2014.986c.088%202.647%202.246%204.258%205.635%204.258%203.496%200%205.713-1.728%205.713-4.463%200-.275-.02-.536-.062-.781h-3.461c.398.293.573.654.573%201.123%200%201.035-1.074%201.787-2.646%201.787-1.563%200-2.773-.762-2.91-1.924H6ZM6.432%2010h3.763c-.632-.314-.914-.715-.914-1.273%200-1.045.977-1.739%202.432-1.739%201.475%200%202.52.723%202.617%201.914h2.764c-.05-2.548-2.11-4.238-5.39-4.238-3.145%200-5.392%201.719-5.392%204.316%200%20.363.04.703.12%201.02ZM4%2011a1%201%200%201%200%200%202h15a1%201%200%201%200%200-2H4Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-quote::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M4.581%208.471c.44-.5%201.056-.834%201.758-.995C8.074%207.17%209.201%207.822%2010%208.752c1.354%201.578%201.33%203.555.394%205.277-.941%201.731-2.788%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.121-.49.16-.764.294-.286.567-.566.791-.835.222-.266.413-.54.524-.815.113-.28.156-.597.026-.908-.128-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.674-2.7c0-.905.283-1.59.72-2.088Zm9.419%200c.44-.5%201.055-.834%201.758-.995%201.734-.306%202.862.346%203.66%201.276%201.355%201.578%201.33%203.555.395%205.277-.941%201.731-2.789%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.122-.49.16-.764.294-.286.567-.566.791-.835.222-.266.412-.54.523-.815.114-.28.157-.597.026-.908-.127-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.672-2.701c0-.905.283-1.59.72-2.088Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-heading-1::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.5%207.5v-3h-12v3H14v13h3v-13h4.5ZM9%2013.5h3.5v-3h-10v3H6v7h3v-7Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-code::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3.293%2011.293a1%201%200%200%200%200%201.414l4%204a1%201%200%201%200%201.414-1.414L5.414%2012l3.293-3.293a1%201%200%200%200-1.414-1.414l-4%204Zm13.414%205.414%204-4a1%201%200%200%200%200-1.414l-4-4a1%201%200%201%200-1.414%201.414L18.586%2012l-3.293%203.293a1%201%200%200%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-bullet-list::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%207.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203ZM8%206a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-2.5-5a1.5%201.5%200%201%201-3%200%201.5%201.5%200%200%201%203%200ZM5%2019.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-number-list::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%204h2v4H4V5H3V4Zm5%202a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-3.5-7H6v1l-1.5%202H6v1H3v-1l1.667-2H3v-1h2.5ZM3%2017v-1h3v4H3v-1h2v-.5H4v-1h1V17H3Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-undo::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%2014a1%201%200%200%200%201%201h6a1%201%200%201%200%200-2H6.257c2.247-2.764%205.151-3.668%207.579-3.264%202.589.432%204.739%202.356%205.174%205.405a1%201%200%200%200%201.98-.283c-.564-3.95-3.415-6.526-6.825-7.095C11.084%207.25%207.63%208.377%205%2011.39V8a1%201%200%200%200-2%200v6Zm2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-redo::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21%2014a1%201%200%200%201-1%201h-6a1%201%200%201%201%200-2h3.743c-2.247-2.764-5.151-3.668-7.579-3.264-2.589.432-4.739%202.356-5.174%205.405a1%201%200%200%201-1.98-.283c.564-3.95%203.415-6.526%206.826-7.095%203.08-.513%206.534.614%209.164%203.626V8a1%201%200%201%201%202%200v6Zm-2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-decrease-nesting-level::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-3.707-5.707a1%201%200%200%200%200%201.414l2%202a1%201%200%201%200%201.414-1.414L4.414%2012l1.293-1.293a1%201%200%200%200-1.414-1.414l-2%202Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-button--icon-increase-nesting-level::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-2.293-2.293%202-2a1%201%200%200%200%200-1.414l-2-2a1%201%200%201%200-1.414%201.414L3.586%2012l-1.293%201.293a1%201%200%201%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E\"); }\n\ntrix-toolbar .trix-dialogs {\n  position: relative; }\n\ntrix-toolbar .trix-dialog {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  font-size: 0.75em;\n  padding: 15px 10px;\n  background: #fff;\n  box-shadow: 0 0.3em 1em #ccc;\n  border-top: 2px solid #888;\n  border-radius: 5px;\n  z-index: 5; }\n\ntrix-toolbar .trix-input--dialog {\n  font-size: inherit;\n  font-weight: normal;\n  padding: 0.5em 0.8em;\n  margin: 0 10px 0 0;\n  border-radius: 3px;\n  border: 1px solid #bbb;\n  background-color: #fff;\n  box-shadow: none;\n  outline: none;\n  -webkit-appearance: none;\n  -moz-appearance: none; }\n  trix-toolbar .trix-input--dialog.validate:invalid {\n    box-shadow: #F00 0px 0px 1.5px 1px; }\n\ntrix-toolbar .trix-button--dialog {\n  font-size: inherit;\n  padding: 0.5em;\n  border-bottom: none; }\n\ntrix-toolbar .trix-dialog--link {\n  max-width: 600px; }\n\ntrix-toolbar .trix-dialog__link-fields {\n  display: flex;\n  align-items: baseline; }\n  trix-toolbar .trix-dialog__link-fields .trix-input {\n    flex: 1; }\n  trix-toolbar .trix-dialog__link-fields .trix-button-group {\n    flex: 0 0 content;\n    margin: 0; }\n\ntrix-editor [data-trix-mutable]:not(.attachment__caption-editor) {\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none; }\n\ntrix-editor [data-trix-mutable]::-moz-selection,\ntrix-editor [data-trix-cursor-target]::-moz-selection, trix-editor [data-trix-mutable] ::-moz-selection {\n  background: none; }\n\ntrix-editor [data-trix-mutable]::selection,\ntrix-editor [data-trix-cursor-target]::selection, trix-editor [data-trix-mutable] ::selection {\n  background: none; }\n\ntrix-editor .attachment__caption-editor:focus[data-trix-mutable]::-moz-selection {\n  background: highlight; }\n\ntrix-editor .attachment__caption-editor:focus[data-trix-mutable]::selection {\n  background: highlight; }\n\ntrix-editor [data-trix-mutable].attachment.attachment--file {\n  box-shadow: 0 0 0 2px highlight;\n  border-color: transparent; }\n\ntrix-editor [data-trix-mutable].attachment img {\n  box-shadow: 0 0 0 2px highlight; }\n\ntrix-editor .attachment {\n  position: relative; }\n  trix-editor .attachment:hover {\n    cursor: default; }\n\ntrix-editor .attachment--preview .attachment__caption:hover {\n  cursor: text; }\n\ntrix-editor .attachment__progress {\n  position: absolute;\n  z-index: 1;\n  height: 20px;\n  top: calc(50% - 10px);\n  left: 5%;\n  width: 90%;\n  opacity: 0.9;\n  transition: opacity 200ms ease-in; }\n  trix-editor .attachment__progress[value=\"100\"] {\n    opacity: 0; }\n\ntrix-editor .attachment__caption-editor {\n  display: inline-block;\n  width: 100%;\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  font-family: inherit;\n  line-height: inherit;\n  color: inherit;\n  text-align: center;\n  vertical-align: top;\n  border: none;\n  outline: none;\n  -webkit-appearance: none;\n  -moz-appearance: none; }\n\ntrix-editor .attachment__toolbar {\n  position: absolute;\n  z-index: 1;\n  top: -0.9em;\n  left: 0;\n  width: 100%;\n  text-align: center; }\n\ntrix-editor .trix-button-group {\n  display: inline-flex; }\n\ntrix-editor .trix-button {\n  position: relative;\n  float: left;\n  color: #666;\n  white-space: nowrap;\n  font-size: 80%;\n  padding: 0 0.8em;\n  margin: 0;\n  outline: none;\n  border: none;\n  border-radius: 0;\n  background: transparent; }\n  trix-editor .trix-button:not(:first-child) {\n    border-left: 1px solid #ccc; }\n  trix-editor .trix-button.trix-active {\n    background: #cbeefa; }\n  trix-editor .trix-button:not(:disabled) {\n    cursor: pointer; }\n\ntrix-editor .trix-button--remove {\n  text-indent: -9999px;\n  display: inline-block;\n  padding: 0;\n  outline: none;\n  width: 1.8em;\n  height: 1.8em;\n  line-height: 1.8em;\n  border-radius: 50%;\n  background-color: #fff;\n  border: 2px solid highlight;\n  box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25); }\n  trix-editor .trix-button--remove::before {\n    display: inline-block;\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    opacity: 0.7;\n    content: \"\";\n    background-image: url(\"data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E\");\n    background-position: center;\n    background-repeat: no-repeat;\n    background-size: 90%; }\n  trix-editor .trix-button--remove:hover {\n    border-color: #333; }\n    trix-editor .trix-button--remove:hover::before {\n      opacity: 1; }\n\ntrix-editor .attachment__metadata-container {\n  position: relative; }\n\ntrix-editor .attachment__metadata {\n  position: absolute;\n  left: 50%;\n  top: 2em;\n  transform: translate(-50%, 0);\n  max-width: 90%;\n  padding: 0.1em 0.6em;\n  font-size: 0.8em;\n  color: #fff;\n  background-color: rgba(0, 0, 0, 0.7);\n  border-radius: 3px; }\n  trix-editor .attachment__metadata .attachment__name {\n    display: inline-block;\n    max-width: 100%;\n    vertical-align: bottom;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap; }\n  trix-editor .attachment__metadata .attachment__size {\n    margin-left: 0.2em;\n    white-space: nowrap; }\n\n.trix-content {\n  line-height: 1.5;\n  overflow-wrap: break-word;\n  word-break: break-word; }\n  .trix-content * {\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0; }\n  .trix-content h1 {\n    font-size: 1.2em;\n    line-height: 1.2; }\n  .trix-content blockquote {\n    border: 0 solid #ccc;\n    border-left-width: 0.3em;\n    margin-left: 0.3em;\n    padding-left: 0.6em; }\n  .trix-content [dir=rtl] blockquote,\n  .trix-content blockquote[dir=rtl] {\n    border-width: 0;\n    border-right-width: 0.3em;\n    margin-right: 0.3em;\n    padding-right: 0.6em; }\n  .trix-content li {\n    margin-left: 1em; }\n  .trix-content [dir=rtl] li {\n    margin-right: 1em; }\n  .trix-content pre {\n    display: inline-block;\n    width: 100%;\n    vertical-align: top;\n    font-family: monospace;\n    font-size: 0.9em;\n    padding: 0.5em;\n    white-space: pre;\n    background-color: #eee;\n    overflow-x: auto; }\n  .trix-content img {\n    max-width: 100%;\n    height: auto; }\n  .trix-content .attachment {\n    display: inline-block;\n    position: relative;\n    max-width: 100%; }\n    .trix-content .attachment a {\n      color: inherit;\n      text-decoration: none; }\n      .trix-content .attachment a:hover, .trix-content .attachment a:visited:hover {\n        color: inherit; }\n  .trix-content .attachment__caption {\n    text-align: center; }\n    .trix-content .attachment__caption .attachment__name + .attachment__size::before {\n      content: ' \\2022 '; }\n  .trix-content .attachment--preview {\n    width: 100%;\n    text-align: center; }\n    .trix-content .attachment--preview .attachment__caption {\n      color: #666;\n      font-size: 0.9em;\n      line-height: 1.2; }\n  .trix-content .attachment--file {\n    color: #333;\n    line-height: 1;\n    margin: 0 2px 2px 2px;\n    padding: 0.4em 1em;\n    border: 1px solid #bbb;\n    border-radius: 5px; }\n  .trix-content .attachment-gallery {\n    display: flex;\n    flex-wrap: wrap;\n    position: relative; }\n    .trix-content .attachment-gallery .attachment {\n      flex: 1 0 33%;\n      padding: 0 0.5em;\n      max-width: 33%; }\n    .trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment {\n      flex-basis: 50%;\n      max-width: 50%; }\n\n/*\n * We need to override trix.css’s image gallery styles to accommodate the\n * <action-text-attachment> element we wrap around attachments. Otherwise,\n * images in galleries will be squished by the max-width: 33%; rule.\n*/\n.trix-content .attachment-gallery > action-text-attachment,\n.trix-content .attachment-gallery > .attachment {\n  flex: 1 0 33%;\n  padding: 0 0.5em;\n  max-width: 33%;\n}\n\n.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,\n.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,\n.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {\n  flex-basis: 50%;\n  max-width: 50%;\n}\n\n.trix-content action-text-attachment .attachment {\n  padding: 0 !important;\n  max-width: 100% !important;\n}\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/assets/stylesheets/application.css",
    "content": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below.\n *\n * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,\n * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.\n *\n * You're free to add application-wide styles to this file and they'll appear at the bottom of the\n * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS\n * files in this directory. Styles in this file should be added after the last require_* statement.\n * It is generally better to create a new file per style scope.\n *\n *= require_tree .\n *= require_self\n */\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\n  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.\n  allow_browser versions: :modern\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/controllers/concerns/.keep",
    "content": ""
  },
  {
    "path": "action_text-trix/test/dummy/app/controllers/messages_controller.rb",
    "content": "class MessagesController < ApplicationController\n  def index\n    @messages = Message.all\n  end\n\n  def new\n    @message = Message.new\n  end\n\n  def create\n    @message = Message.create!(message_params)\n\n    redirect_to action: :index\n  end\n\n  private\n    def message_params\n      if params.respond_to?(:expect)\n        params.expect(message: [ :subject, :content ])\n      else\n        params.require(:message).permit(:subject, :content)\n      end\n    end\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/javascript/application.js",
    "content": "// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"trix\"\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/models/application_record.rb",
    "content": "class ApplicationRecord < ActiveRecord::Base\n  primary_abstract_class\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/models/concerns/.keep",
    "content": ""
  },
  {
    "path": "action_text-trix/test/dummy/app/models/message.rb",
    "content": "class Message < ApplicationRecord\n  has_rich_text :content\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/active_storage/blobs/_blob.html.erb",
    "content": "<figure class=\"attachment attachment--<%= blob.representable? ? \"preview\" : \"file\" %> attachment--<%= blob.filename.extension %>\">\n  <% if blob.representable? %>\n    <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>\n  <% end %>\n\n  <figcaption class=\"attachment__caption\">\n    <% if caption = blob.try(:caption) %>\n      <%= caption %>\n    <% else %>\n      <span class=\"attachment__name\"><%= blob.filename %></span>\n      <span class=\"attachment__size\"><%= number_to_human_size blob.byte_size %></span>\n    <% end %>\n  </figcaption>\n</figure>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/layouts/action_text/contents/_content.html.erb",
    "content": "<div class=\"trix-content\">\n  <%= yield -%>\n</div>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title><%= content_for(:title) || \"Dummy\" %></title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <%= csrf_meta_tags %>\n    <%= csp_meta_tag %>\n\n    <%= yield :head %>\n\n    <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>\n    <%#= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %>\n\n    <link rel=\"icon\" href=\"/icon.png\" type=\"image/png\">\n    <link rel=\"icon\" href=\"/icon.svg\" type=\"image/svg+xml\">\n    <link rel=\"apple-touch-icon\" href=\"/icon.png\">\n\n    <%# Includes all stylesheet files in app/assets/stylesheets %>\n    <%= stylesheet_link_tag :app %>\n    <%= javascript_importmap_tags %>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/layouts/mailer.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n    <style>\n      /* Email styles need to be inline */\n    </style>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/layouts/mailer.text.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/messages/_form.html.erb",
    "content": "<%= form_with(model: message) do |form| %>\n  <%= form.label :subject %> <br>\n  <%= form.text_field :subject %> <br>\n\n  <%= form.label :content %> <br>\n  <%= form.rich_text_area :content %>\n\n  <%= form.submit %>\n<% end %>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/messages/index.html.erb",
    "content": "<h1>Messages</h1>\n\n<table>\n  <thead>\n    <tr>\n      <th>Subject</th>\n      <th>Content</th>\n    </tr>\n  </thead>\n\n  <tbody>\n    <% @messages.each do |message| %>\n      <tr>\n        <td><%= message.subject %></td>\n        <td><%= message.content %></td>\n      </tr>\n    <% end %>\n  </tbody>\n</table>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/messages/new.html.erb",
    "content": "<h1>New Message</h1>\n\n<%= render \"form\", message: @message %>\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/pwa/manifest.json.erb",
    "content": "{\n  \"name\": \"Dummy\",\n  \"icons\": [\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"Dummy.\",\n  \"theme_color\": \"red\",\n  \"background_color\": \"red\"\n}\n"
  },
  {
    "path": "action_text-trix/test/dummy/app/views/pwa/service-worker.js",
    "content": "// Add a service worker for processing Web Push notifications:\n//\n// self.addEventListener(\"push\", async (event) => {\n//   const { title, options } = await event.data.json()\n//   event.waitUntil(self.registration.showNotification(title, options))\n// })\n//\n// self.addEventListener(\"notificationclick\", function(event) {\n//   event.notification.close()\n//   event.waitUntil(\n//     clients.matchAll({ type: \"window\" }).then((clientList) => {\n//       for (let i = 0; i < clientList.length; i++) {\n//         let client = clientList[i]\n//         let clientPath = (new URL(client.url)).pathname\n//\n//         if (clientPath == event.notification.data.path && \"focus\" in client) {\n//           return client.focus()\n//         }\n//       }\n//\n//       if (clients.openWindow) {\n//         return clients.openWindow(event.notification.data.path)\n//       }\n//     })\n//   )\n// })\n"
  },
  {
    "path": "action_text-trix/test/dummy/bin/dev",
    "content": "#!/usr/bin/env ruby\nexec \"./bin/rails\", \"server\", *ARGV\n"
  },
  {
    "path": "action_text-trix/test/dummy/bin/importmap",
    "content": "#!/usr/bin/env ruby\n\nrequire_relative \"../config/application\"\nrequire \"importmap/commands\"\n"
  },
  {
    "path": "action_text-trix/test/dummy/bin/rails",
    "content": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequire \"rails/commands\"\n"
  },
  {
    "path": "action_text-trix/test/dummy/bin/rake",
    "content": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
  },
  {
    "path": "action_text-trix/test/dummy/bin/setup",
    "content": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\nAPP_ROOT = File.expand_path(\"..\", __dir__)\n\ndef system!(*args)\n  system(*args, exception: true)\nend\n\nFileUtils.chdir APP_ROOT do\n  # This script is a way to set up or update your development environment automatically.\n  # This script is idempotent, so that you can run it at any time and get an expectable outcome.\n  # Add necessary setup steps to this file.\n\n  puts \"== Installing dependencies ==\"\n  system(\"bundle check\") || system!(\"bundle install\")\n\n  # puts \"\\n== Copying sample files ==\"\n  # unless File.exist?(\"config/database.yml\")\n  #   FileUtils.cp \"config/database.yml.sample\", \"config/database.yml\"\n  # end\n\n  puts \"\\n== Preparing database ==\"\n  system! \"bin/rails db:prepare\"\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! \"bin/rails log:clear tmp:clear\"\n\n  unless ARGV.include?(\"--skip-server\")\n    puts \"\\n== Starting development server ==\"\n    STDOUT.flush # flush the output before exec(2) so that it displays\n    exec \"bin/dev\"\n  end\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/application.rb",
    "content": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule Dummy\n  class Application < Rails::Application\n    config.load_defaults Rails::VERSION::STRING.to_f\n\n    # For compatibility with applications that use this config\n    config.action_controller.include_all_helpers = false\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US & Canada)\"\n    # config.eager_load_paths << Rails.root.join(\"extras\")\n\n    config.active_storage.variant_processor = :disabled\n  end\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/boot.rb",
    "content": "# Set up gems listed in the Gemfile.\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../../../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" if File.exist?(ENV[\"BUNDLE_GEMFILE\"])\n$LOAD_PATH.unshift File.expand_path(\"../../../lib\", __dir__)\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/cable.yml",
    "content": "development:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n  channel_prefix: dummy_production\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/database.yml",
    "content": "# SQLite. Versions 3.8.0 and up are supported.\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem \"sqlite3\"\n#\ndefault: &default\n  adapter: sqlite3\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %>\n  timeout: 5000\n\ndevelopment:\n  <<: *default\n  database: storage/development.sqlite3\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  <<: *default\n  database: storage/test.sqlite3\n\n\n# SQLite3 write its data on the local filesystem, as such it requires\n# persistent disks. If you are deploying to a managed service, you should\n# make sure it provides disk persistence, as many don't.\n#\n# Similarly, if you deploy your application as a Docker container, you must\n# ensure the database is located in a persisted volume.\nproduction:\n  <<: *default\n  # database: path/to/persistent/storage/production.sqlite3\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/environment.rb",
    "content": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/environments/development.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Make code changes take effect immediately without server restart.\n  config.enable_reloading = true\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable server timing.\n  config.server_timing = true\n\n  # Enable/disable Action Controller caching. By default Action Controller caching is disabled.\n  # Run rails dev:cache to toggle Action Controller caching.\n  if Rails.root.join(\"tmp/caching-dev.txt\").exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n    config.public_file_server.headers = { \"cache-control\" => \"public, max-age=#{2.days.to_i}\" }\n  else\n    config.action_controller.perform_caching = false\n  end\n\n  # Change to :null_store to avoid any caching.\n  config.cache_store = :memory_store\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  config.action_mailer.raise_delivery_errors = false\n\n  # Make template changes take effect immediately.\n  config.action_mailer.perform_caching = false\n\n  # Set localhost to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"localhost\", port: 3000 }\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Append comments with runtime information tags to SQL queries in logs.\n  config.active_record.query_log_tags_enabled = true\n\n  # Highlight code that enqueued background job in logs.\n  config.active_job.verbose_enqueue_logs = true\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Uncomment if you wish to allow Action Cable access from any origin.\n  # config.action_cable.disable_request_forgery_protection = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/environments/production.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" => \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: ->(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!)\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  # config.cache_store = :mem_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  # config.active_job.queue_adapter = :resque\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"example.com\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  # Enable DNS rebinding protection and other `Host` header attacks.\n  # config.hosts = [\n  #   \"example.com\",     # Allow requests from example.com\n  #   /.*\\.example\\.com/ # Allow requests from subdomains like `www.example.com`\n  # ]\n  #\n  # Skip DNS rebinding protection for the default health check endpoint.\n  # config.host_authorization = { exclude: ->(request) { request.path == \"/up\" } }\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/environments/test.rb",
    "content": "# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # While tests run files are not watched, reloading is not necessary.\n  config.enable_reloading = false\n\n  # Eager loading loads your entire application. When running a single test locally,\n  # this is usually not necessary, and can slow down your test suite. However, it's\n  # recommended that you enable it in continuous integration systems to ensure eager\n  # loading is working properly before deploying your code.\n  config.eager_load = ENV[\"CI\"].present?\n\n  # Configure public file server for tests with cache-control for performance.\n  config.public_file_server.headers = { \"cache-control\" => \"public, max-age=3600\" }\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n  config.cache_store = :null_store\n\n  # Render exception templates for rescuable exceptions and raise for other exceptions.\n  config.action_dispatch.show_exceptions = :rescuable\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  config.active_storage.service = :test\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/importmap.rb",
    "content": "# Pin npm packages by running ./bin/importmap\n\npin \"application\"\n\npin \"trix\"\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/initializers/assets.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire all your assets.\nRails.application.config.assets.version = \"1.0\"\n\n# Add additional assets to the asset load path.\n# Rails.application.config.assets.paths << Emoji.images_path\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/initializers/content_security_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See the Securing Rails Applications Guide for more information:\n# https://guides.rubyonrails.org/security.html#content-security-policy-header\n\n# Rails.application.configure do\n#   config.content_security_policy do |policy|\n#     policy.default_src :self, :https\n#     policy.font_src    :self, :https, :data\n#     policy.img_src     :self, :https, :data\n#     policy.object_src  :none\n#     policy.script_src  :self, :https\n#     policy.style_src   :self, :https\n#     # Specify URI for violation reports\n#     # policy.report_uri \"/csp-violation-report-endpoint\"\n#   end\n#\n#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.\n#   config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }\n#   config.content_security_policy_nonce_directives = %w(script-src style-src)\n#\n#   # Report violations without enforcing the policy.\n#   # config.content_security_policy_report_only = true\n# end\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/initializers/filter_parameter_logging.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.\n# Use this to limit dissemination of sensitive information.\n# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.\nRails.application.config.filter_parameters += [\n  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc\n]\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, \"\\\\1en\"\n#   inflect.singular /^(ox)en/i, \"\\\\1\"\n#   inflect.irregular \"person\", \"people\"\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym \"RESTful\"\n# end\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/locales/en.yml",
    "content": "# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If you want to use locales other than\n# English, add the necessary files in this directory.\n#\n# To use the locales, use `I18n.t`:\n#\n#     I18n.t \"hello\"\n#\n# In views, this is aliased to just `t`:\n#\n#     <%= t(\"hello\") %>\n#\n# To use a different locale, set it with `I18n.locale`:\n#\n#     I18n.locale = :es\n#\n# This would use the information in config/locales/es.yml.\n#\n# To learn more about the API, please read the Rails Internationalization guide\n# at https://guides.rubyonrails.org/i18n.html.\n#\n# Be aware that YAML interprets the following case-insensitive strings as\n# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings\n# must be quoted to be interpreted as strings. For example:\n#\n#     en:\n#       \"yes\": yup\n#       enabled: \"ON\"\n\nen:\n  hello: \"Hello world\"\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/puma.rb",
    "content": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n#\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# You can control the number of workers using ENV[\"WEB_CONCURRENCY\"]. You\n# should only set this value when you want to run 2 or more workers. The\n# default is already 1.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Specify the PID file. Defaults to tmp/pids/server.pid in development.\n# In other environments, only set the PID file if requested.\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/routes.rb",
    "content": "Rails.application.routes.draw do\n  resources :messages\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/config/storage.yml",
    "content": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join(\"storage\") %>\n\n# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)\n# amazon:\n#   service: S3\n#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>\n#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>\n#   region: us-east-1\n#   bucket: your_own_bucket-<%= Rails.env %>\n\n# Remember not to checkin your GCS keyfile to a repository\n# google:\n#   service: GCS\n#   project: your_project\n#   credentials: <%= Rails.root.join(\"path/to/gcs.keyfile\") %>\n#   bucket: your_own_bucket-<%= Rails.env %>\n\n# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)\n# microsoft:\n#   service: AzureStorage\n#   storage_account_name: your_account_name\n#   storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>\n#   container: your_container_name-<%= Rails.env %>\n\n# mirror:\n#   service: Mirror\n#   primary: local\n#   mirrors: [ amazon, google, microsoft ]\n"
  },
  {
    "path": "action_text-trix/test/dummy/config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.application\nRails.application.load_server\n"
  },
  {
    "path": "action_text-trix/test/dummy/db/migrate/20250926170812_create_active_storage_tables.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20170806125915)\nclass CreateActiveStorageTables < ActiveRecord::Migration[7.0]\n  def change\n    # Use Active Record's configured type for primary and foreign keys\n    primary_key_type, foreign_key_type = primary_and_foreign_key_types\n\n    create_table :active_storage_blobs, id: primary_key_type do |t|\n      t.string   :key,          null: false\n      t.string   :filename,     null: false\n      t.string   :content_type\n      t.text     :metadata\n      t.string   :service_name, null: false\n      t.bigint   :byte_size,    null: false\n      t.string   :checksum\n\n      if connection.supports_datetime_with_precision?\n        t.datetime :created_at, precision: 6, null: false\n      else\n        t.datetime :created_at, null: false\n      end\n\n      t.index [ :key ], unique: true\n    end\n\n    create_table :active_storage_attachments, id: primary_key_type do |t|\n      t.string     :name,     null: false\n      t.references :record,   null: false, polymorphic: true, index: false, type: foreign_key_type\n      t.references :blob,     null: false, type: foreign_key_type\n\n      if connection.supports_datetime_with_precision?\n        t.datetime :created_at, precision: 6, null: false\n      else\n        t.datetime :created_at, null: false\n      end\n\n      t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n\n    create_table :active_storage_variant_records, id: primary_key_type do |t|\n      t.belongs_to :blob, null: false, index: false, type: foreign_key_type\n      t.string :variation_digest, null: false\n\n      t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\n\n  private\n    def primary_and_foreign_key_types\n      config = Rails.configuration.generators\n      setting = config.options[config.orm][:primary_key_type]\n      primary_key_type = setting || :primary_key\n      foreign_key_type = setting || :bigint\n      [ primary_key_type, foreign_key_type ]\n    end\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/db/migrate/20250926170813_create_action_text_tables.action_text.rb",
    "content": "# This migration comes from action_text (originally 20180528164100)\nclass CreateActionTextTables < ActiveRecord::Migration[6.0]\n  def change\n    # Use Active Record's configured type for primary and foreign keys\n    primary_key_type, foreign_key_type = primary_and_foreign_key_types\n\n    create_table :action_text_rich_texts, id: primary_key_type do |t|\n      t.string     :name, null: false\n      t.text       :body, size: :long\n      t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type\n\n      t.timestamps\n\n      t.index [ :record_type, :record_id, :name ], name: \"index_action_text_rich_texts_uniqueness\", unique: true\n    end\n  end\n\n  private\n    def primary_and_foreign_key_types\n      config = Rails.configuration.generators\n      setting = config.options[config.orm][:primary_key_type]\n      primary_key_type = setting || :primary_key\n      foreign_key_type = setting || :bigint\n      [ primary_key_type, foreign_key_type ]\n    end\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/db/migrate/20250926170921_create_messages.rb",
    "content": "class CreateMessages < ActiveRecord::Migration[7.2]\n  def change\n    create_table :messages do |t|\n      t.text :subject\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/db/schema.rb",
    "content": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[7.2].define(version: 2025_09_26_170921) do\n  create_table \"action_text_rich_texts\", force: :cascade do |t|\n    t.text \"body\"\n    t.datetime \"created_at\", null: false\n    t.string \"name\", null: false\n    t.bigint \"record_id\", null: false\n    t.string \"record_type\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"record_type\", \"record_id\", \"name\"], name: \"index_action_text_rich_texts_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_attachments\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.string \"name\", null: false\n    t.bigint \"record_id\", null: false\n    t.string \"record_type\", null: false\n    t.index [\"blob_id\"], name: \"index_active_storage_attachments_on_blob_id\"\n    t.index [\"record_type\", \"record_id\", \"name\", \"blob_id\"], name: \"index_active_storage_attachments_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_blobs\", force: :cascade do |t|\n    t.bigint \"byte_size\", null: false\n    t.string \"checksum\"\n    t.string \"content_type\"\n    t.datetime \"created_at\", null: false\n    t.string \"filename\", null: false\n    t.string \"key\", null: false\n    t.text \"metadata\"\n    t.string \"service_name\", null: false\n    t.index [\"key\"], name: \"index_active_storage_blobs_on_key\", unique: true\n  end\n\n  create_table \"active_storage_variant_records\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.string \"variation_digest\", null: false\n    t.index [\"blob_id\", \"variation_digest\"], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n  end\n\n  create_table \"messages\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.text \"subject\"\n    t.datetime \"updated_at\", null: false\n  end\n\n  add_foreign_key \"active_storage_attachments\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"active_storage_variant_records\", \"active_storage_blobs\", column: \"blob_id\"\nend\n"
  },
  {
    "path": "action_text-trix/test/dummy/public/400.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The server cannot process the request due to a client error (400 Bad Request)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100vh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/400.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z\" fill=\"#f0eff0\"/><path d=\"m123.606 85.4445c3.212 1.0523 5.538 4.2089 5.538 8.0301 0 6.1472-4.209 9.5254-11.298 9.5254h-15.617v-34.0033h14.565c7.089 0 11.353 3.1566 11.353 9.2484 0 3.6551-2.049 6.3134-4.541 7.1994zm-12.904-2.9905h5.095c2.603 0 3.988-.9968 3.988-3.1013 0-2.1044-1.385-3.0459-3.988-3.0459h-5.095zm0 6.6456v6.5902h5.981c2.492 0 3.877-1.3291 3.877-3.2674 0-2.049-1.385-3.3228-3.877-3.3228zm43.786 13.9004h-8.362v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.083-2.769-9.083-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.483 1.3845v-1.606c0-1.606-.942-2.9905-3.046-2.9905-1.606 0-2.548.7199-2.935 1.8275h-8.197c.72-4.8181 4.985-8.6393 11.409-8.6393 7.088 0 11.131 3.7659 11.131 10.2453zm-8.362-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm27.996 6.9779v-1.994c-1.163 1.329-3.599 2.548-6.147 2.548-7.199 0-11.131-5.8151-11.131-13.0145s3.932-13.0143 11.131-13.0143c2.548 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.664-1.3291-2.159-2.326-3.821-2.326-2.99 0-4.763 2.4368-4.763 5.6488s1.773 5.5934 4.763 5.5934c1.717 0 3.157-.9415 3.821-2.326zm35.471-2.049h-3.101v11.2421h-8.806v-34.0033h15.285c7.31 0 12.35 4.1535 12.35 11.5744 0 5.1503-2.603 8.6947-6.757 10.2453l7.975 12.1836h-9.858zm-3.101-15.2849v8.1962h5.538c3.156 0 4.596-1.606 4.596-4.0981s-1.44-4.0981-4.596-4.0981zm36.957 17.8323h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm30.98 27.5234v-10.799c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.9259-11.132-13.0145 0-7.144 3.932-13.0143 11.132-13.0143 2.547 0 4.984 1.2184 6.147 2.5475v-1.9937h8.695v33.726zm0-17.9981v-6.5902c-.665-1.3291-2.105-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.661 0 3.156-.9415 3.821-2.326zm36.789-15.7279v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.996 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm19.084 16.2263h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.963 5.095 11.963 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm13.428 11.0206h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.762-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm27.538-.8861v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.993-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.871 0-9.193-2.769-9.193-9.0819z\" fill=\"#d30001\"/></svg>\n      </header>\n      <article>\n        <p><strong>The server cannot process the request due to a client error.</strong> Please check the request and try again. If you’re the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "action_text-trix/test/dummy/public/404.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The page you were looking for doesn’t exist (404 Not found)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100vh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/404.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm165.328-35.41581-45.689 100.02991h26.224v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.184v-31.901l50.285-103.27391z\" fill=\"#f0eff0\"/><path d=\"m157.758 68.9967v34.0033h-7.199l-14.233-19.8814v19.8814h-8.584v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm13.184 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm37.027 8.5839h-8.806v-34.0033h23.924v7.6978h-15.118v6.7564h13.9v7.5316h-13.9zm41.876-12.4605c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm35.337-12.4605v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.997 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm4.076 24.921v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.156.9969-3.6 2.7136v15.0634zm44.113 0v-1.994c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.8151-11.132-13.0145s3.932-13.0143 11.132-13.0143c2.547 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.665-1.3291-2.16-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.717 0 3.156-.9415 3.821-2.326z\" fill=\"#d30001\"/></svg>\n      </header>\n      <article>\n        <p><strong>The page you were looking for doesn’t exist.</strong> You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "action_text-trix/test/dummy/public/406-unsupported-browser.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>Your browser is not supported (406 Not Acceptable)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100vh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/406-unsupported-browser.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm202.906 9.7326h-41.093c-2.433-7.2994-7.84-12.4361-17.302-12.4361-16.221 0-25.413 17.5728-25.954 34.8752v1.3517c5.137-7.0291 16.221-12.4361 30.82-12.4361 33.524 0 54.881 24.0612 54.881 53.7998 0 33.253-23.791 58.396-61.64 58.396-21.628 0-39.741-10.003-50.825-27.576-9.733-14.599-13.788-32.442-13.788-54.3406 0-51.9072 24.331-89.485807 66.236-89.485807 32.712 0 53.258 18.654107 58.665 47.851907zm-82.727 66.2355c0 13.247 9.463 22.439 22.71 22.439 12.977 0 22.439-9.192 22.439-22.439 0-13.517-9.462-22.7091-22.439-22.7091-13.247 0-22.71 9.1921-22.71 22.7091z\" fill=\"#f0eff0\"/><path d=\"m100.761 68.9967v34.0033h-7.1991l-14.2326-19.8814v19.8814h-8.5839v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm13.185 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.02-25.4194h9.083l12.958 34.0033h-9.027l-2.436-6.5902h-12.35l-2.381 6.5902h-8.806zm4.431 10.5222-3.489 9.5807h6.978zm17.44 11.0206c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm25.676 0c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm42.013 3.7658h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm23.4 16.7244v10.799h-8.694v-33.726h8.694v1.9937c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8703 11.131 13.0143 0 7.0886-3.932 13.0145-11.131 13.0145-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.16 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.717 0-3.157.9969-3.822 2.326zm21.892 7.1994v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.458 8.5839h-8.363v-1.274c-.83.831-3.322 1.717-5.981 1.717-4.928 0-9.082-2.769-9.082-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.045-2.9905-1.606 0-2.548.7199-2.936 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.553-1.0522-2.049-1.7167-3.655-1.7167-1.716 0-3.433.7199-3.433 2.3813 0 1.7168 1.717 2.4367 3.433 2.4367 1.606 0 3.102-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.694v-35.997h8.694v13.0697c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8149 11.131 13.0143s-3.932 13.0145-11.131 13.0145c-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.662 0-3.157.9969-3.822 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997z\" fill=\"#d30001\"/></svg>\n      </header>\n      <article>\n        <p><strong>Your browser is not supported.</strong><br> Please upgrade your browser to continue.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "action_text-trix/test/dummy/public/422.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The change you wanted was rejected (422 Unprocessable Entity)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100vh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/422.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm130.453 51.63681c0-8.9215-6.218-15.4099-15.681-15.4099-10.273 0-15.95 7.5698-16.491 16.4913h-44.608c3.244-30.8199 25.683-55.421707 61.099-55.421707 36.498 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.869 36.7676-31.901 52.7186l-27.305 27.035h59.747v37.308h-120.306v-27.846l57.044-56.7736c11.084-11.8954 18.925-20.0059 18.925-29.7385zm140.455 0c0-8.9215-6.218-15.4099-15.68-15.4099-10.274 0-15.951 7.5698-16.492 16.4913h-44.608c3.245-30.8199 25.684-55.421707 61.1-55.421707 36.497 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.87 36.7676-31.902 52.7186l-27.305 27.035h59.747v37.308h-120.305v-27.846l57.043-56.7736c11.085-11.8954 18.925-20.0059 18.925-29.7385z\" fill=\"#f0eff0\"/><path d=\"m19.3936 103.554c-8.9715 0-14.84183-5.0952-14.84183-14.4544v-20.1029h8.86083v19.3276c0 4.8181 2.2706 7.3102 5.981 7.3102 3.6551 0 5.9257-2.4921 5.9257-7.3102v-19.3276h8.8608v20.1583c0 9.3038-5.8149 14.399-14.7865 14.399zm18.734-.554v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm36.8584-1.994v10.799h-8.6946v-33.726h8.6946v1.9937c1.163-1.3291 3.5997-2.5475 6.1472-2.5475 7.1994 0 11.1314 5.8703 11.1314 13.0143 0 7.0886-3.932 13.0145-11.1314 13.0145-2.5475 0-4.9842-1.219-6.1472-2.548zm0-13.7893v6.5902c.6646 1.3845 2.1599 2.326 3.8213 2.326 2.9905 0 4.7626-2.3814 4.7626-5.5934s-1.7721-5.6488-4.7626-5.6488c-1.7168 0-3.1567.9969-3.8213 2.326zm36.789-9.2485v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.6949v-24.921h8.6949v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm10.352 0c0-7.6978 5.095-13.0143 12.571-13.0143 6.701 0 10.855 3.9874 11.574 9.8023h-8.417c-.222-1.4953-1.385-2.6029-3.157-2.6029-2.437 0-3.987 2.2706-3.987 5.8149s1.55 5.7595 3.987 5.7595c1.772 0 2.935-1.0522 3.157-2.5475h8.417c-.719 5.7596-4.873 9.8025-11.574 9.8025-7.476 0-12.571-5.3167-12.571-13.0145zm42.013 3.7658h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.544-3.5997zm13.428 11.0206h8.473c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.938-1.5506l-4.874-.9969c-4.153-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.74-8.2518zm24.269 0h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm47.918 7.6978h-8.363v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.695v-35.997h8.695v13.0697c1.163-1.3291 3.6-2.5475 6.147-2.5475 7.2 0 11.132 5.8149 11.132 13.0143s-3.932 13.0145-11.132 13.0145c-2.547 0-4.984-1.219-6.147-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.821 2.326 2.991 0 4.763-2.3814 4.763-5.5934s-1.772-5.6488-4.763-5.6488c-1.661 0-3.156.9969-3.821 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm25.461-15.2849h24.311v7.6424h-15.561v5.3165h14.232v7.4763h-14.232v5.8703h15.561v7.6978h-24.311zm27.942 34.0033v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm29.991-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm26.161-16.3371v24.921h-8.694v-24.921zm.61-6.7564c0 2.8244-2.271 4.652-4.929 4.652s-4.929-1.8276-4.929-4.652c0-2.8797 2.271-4.7073 4.929-4.7073s4.929 1.8276 4.929 4.7073zm5.382 23.0935v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm29.22 17.3889h-8.584l3.655-9.414-9.303-24.312h9.026l4.763 14.1773 4.652-14.1773h8.639z\" fill=\"#d30001\"/></svg>\n      </header>\n      <article>\n        <p><strong>The change you wanted was rejected.</strong> Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "action_text-trix/test/dummy/public/500.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>We’re sorry, but something went wrong (500 Internal Server Error)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100vh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/500.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m101.23 93.8427c-8.1103 0-15.4098 3.7849-19.7354 8.3813h-36.2269v-99.21891h103.8143v37.03791h-68.3984v24.8722c5.1366-2.7035 15.1396-5.9477 24.6014-5.9477 35.146 0 56.233 22.7094 56.233 55.4215 0 34.605-23.791 57.315-60.558 57.315-37.8492 0-61.64-22.169-63.8028-55.963h42.9857c1.0814 10.814 9.1919 19.195 21.6281 19.195 11.355 0 19.465-8.381 19.465-20.547 0-11.625-7.299-20.5463-20.006-20.5463zm138.833 77.8613c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z\" fill=\"#f0eff0\"/><path d=\"m23.1377 68.9967v34.0033h-8.9162v-34.0033zm4.3157 34.0033v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm29.9913-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.5839v6.8671h5.2058v6.7564h-5.2058v8.307c0 1.9383.9415 2.769 2.6583 2.769.9414 0 1.9937-.2216 2.769-.5538v7.3654c-.9969.443-2.8798.775-4.8181.775-5.8703 0-9.1931-2.769-9.1931-9.0819zm32.3666-.1108h8.0301c-.8861 5.7597-5.2057 9.2487-11.6852 9.2487-7.6424 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.3165-13.0143 12.5159-13.0143 7.6424 0 11.9621 5.095 11.9621 12.5159v2.1598h-16.1156c.2769 2.9905 1.8275 4.5965 4.3196 4.5965 1.7722 0 3.1567-.7753 3.6551-2.4921zm-3.8212-10.0237c-2.0491 0-3.4336 1.2737-3.9874 3.5997h7.5317c-.1107-2.0491-1.3845-3.5997-3.5443-3.5997zm31.4299-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.599-.7753-2.382 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.381.443zm2.949 25.0318v-24.921h8.694v2.1598c1.385-1.5506 3.821-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.64v-14.2327c0-2.049-1.052-3.5443-3.267-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm50.371 0h-8.363v-1.274c-.83.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.433.7199-3.433 2.3813 0 1.7168 1.716 2.4367 3.433 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742-29.0191v35.997h-8.694v-35.997zm13.036 25.9178h9.248c.72 2.326 2.714 3.489 5.483 3.489 2.713 0 4.596-1.163 4.596-3.2674 0-1.6061-1.052-2.326-3.212-2.8244l-6.534-1.3845c-4.985-1.1076-8.751-3.7105-8.751-9.47 0-6.6456 5.538-11.0206 13.07-11.0206 8.307 0 13.014 4.5411 13.956 10.4114h-8.695c-.72-1.8829-2.27-3.3228-5.205-3.3228-2.548 0-4.265 1.1076-4.265 2.9905 0 1.4953 1.052 2.326 2.825 2.7137l6.645 1.5506c5.815 1.3845 9.027 4.5412 9.027 9.8023 0 6.9778-5.87 10.9654-13.291 10.9654-8.141 0-13.679-3.9322-14.897-10.6332zm46.509 1.3845h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm31.431-6.3134v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.382.443zm18.288 25.0318h-7.809l-9.47-24.921h8.861l4.763 14.288 4.652-14.288h8.528zm25.614-8.6947h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997zm31.43-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443zm13.703-8.9715h24.312v7.6424h-15.562v5.3165h14.232v7.4763h-14.232v5.8703h15.562v7.6978h-24.312zm44.667 8.9715v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm19.673 0v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm28.082-12.5713v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443z\" fill=\"#d30001\"/></svg>\n      </header>\n      <article>\n        <p><strong>We’re sorry, but something went wrong.</strong><br> If you’re the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "action_text-trix/test/fixtures/action_text/rich_texts.yml",
    "content": "# one:\n#   record: name_of_fixture (ClassOfFixture)\n#   name: content\n#   body: <p>In a <i>million</i> stars!</p>\n"
  },
  {
    "path": "action_text-trix/test/system/action_text_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"application_system_test_case\"\n\nclass ActionTextTest < ApplicationSystemTestCase\n  test \"accepts rich text content\" do\n    visit new_message_url\n    fill_in_rich_text_area with: \"Hello, world!\"\n    click_button \"Create Message\"\n\n    assert_element class: \"trix-content\", text: \"Hello, world!\"\n  end\nend\n"
  },
  {
    "path": "action_text-trix/test/test_helper.rb",
    "content": "# Configure Rails Environment\nENV[\"RAILS_ENV\"] = \"test\"\n\nrequire_relative \"../test/dummy/config/environment\"\nActiveRecord::Migrator.migrations_paths = [ File.expand_path(\"../test/dummy/db/migrate\", __dir__) ]\nActiveRecord::Migrator.migrations_paths << File.expand_path(\"../db/migrate\", __dir__)\nrequire \"rails/test_help\"\n\n# Load fixtures from the engine\nif ActiveSupport::TestCase.respond_to?(:fixture_paths=)\n  ActiveSupport::TestCase.fixture_paths = [ File.expand_path(\"fixtures\", __dir__) ]\n  ActionDispatch::IntegrationTest.fixture_paths = ActiveSupport::TestCase.fixture_paths\n  ActiveSupport::TestCase.file_fixture_path = File.expand_path(\"fixtures\", __dir__) + \"/files\"\n  ActiveSupport::TestCase.fixtures :all\nend\n"
  },
  {
    "path": "assets/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>Trix</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <meta name=\"csp-nonce\" content=\"topsecret\">\n    <link rel=\"icon\" href=\"data:,\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"trix.css\">\n    <style type=\"text/css\">\n      * {\n        box-sizing: border-box;\n      }\n\n      main {\n        margin: 20px auto;\n        max-width: 700px;\n      }\n\n      trix-editor:invalid {\n        border: solid 1px red;\n      }\n\n      #output {\n        margin: 1rem 0 0;\n      }\n\n      #output textarea {\n        width: 100%;\n        height: 6rem;\n        resize: vertical;\n        font-family: monospace;\n        border-radius: 5px;\n        border: solid 1px #444;\n        padding: 10px;\n      }\n    </style>\n    <script type=\"module\" src=\"trix.esm.js\"></script>\n    <script type=\"module\" src=\"inspector.js\"></script>\n    <script type=\"module\">\n      Trix.config.attachments.preview.caption.name = true\n      Trix.config.attachments.preview.caption.size = false\n\n      document.addEventListener(\"trix-initialize\", function(event) {\n        Trix.Inspector.install(event.target);\n      });\n\n      document.addEventListener(\"trix-attachment-add\", function(event) {\n        var attachment = event.attachment;\n        if (attachment.file) {\n          var xhr = new XMLHttpRequest;\n          xhr.open(\"POST\", \"/attachments\", true);\n\n          xhr.upload.onprogress = function(event) {\n            var progress = event.loaded / event.total * 100;\n            attachment.setUploadProgress(progress);\n          };\n\n          xhr.onload = function() {\n            if (xhr.status === 201) {\n              setTimeout(function() {\n                var url = xhr.responseText;\n                attachment.setAttributes({ url: url, href: url });\n              }, 30)\n            }\n          };\n\n          attachment.setUploadProgress(10);\n\n          setTimeout(function() {\n            xhr.send(attachment.file);\n          }, 30)\n        }\n      });\n    </script>\n  </head>\n  <body>\n    <main>\n      <trix-editor autofocus class=\"trix-content\" input=\"input\"></trix-editor>\n      <details id=\"output\">\n        <summary>Output</summary>\n        <textarea readonly id=\"input\"></textarea>\n      </details>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "assets/test.html",
    "content": "<!DOCTYPE html>\n<meta charset=\"utf-8\">\n<title>Test Suite</title>\n<link rel=\"icon\" href=\"data:,\">\n<link rel=\"stylesheet\" href=\"https://code.jquery.com/qunit/qunit-2.19.1.css\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"trix.css\">\n<body>\n  <div id=\"qunit\"></div>\n  <div id=\"qunit-fixture\"></div>\n  <script src=\"https://code.jquery.com/qunit/qunit-2.19.1.js\"></script>\n  <script type=\"module\" src=\"test.js\"></script>\n</body>\n"
  },
  {
    "path": "assets/trix/images/README.md",
    "content": "# Trix Icons\n\nTrix's toolbar uses [Material Design Icons by Google][1], which are licensed under the [Creative Commons Attribution 4.0 International License (CC-BY 4.0)][2]. Some icons have been modified.\n\n[1]: https://github.com/google/material-design-icons\n[2]: https://github.com/google/material-design-icons/blob/master/LICENSE\n"
  },
  {
    "path": "assets/trix/stylesheets/attachments.scss",
    "content": "@import \"./icons\";\n@import \"./selection\";\n\ntrix-editor {\n  [data-trix-mutable],\n  [data-trix-cursor-target] {\n    @extend %invisible-selection;\n  }\n\n  [data-trix-mutable] {\n    * {\n      @extend %invisible-selection;\n    }\n\n    &:not(.attachment__caption-editor) {\n      @extend %disable-selection;\n    }\n\n    &.attachment__caption-editor:focus {\n      @extend %visible-selection;\n    }\n\n    &.attachment {\n      &.attachment--file {\n        box-shadow: 0 0 0 2px highlight;\n        border-color: transparent;\n      }\n\n      img {\n        box-shadow: 0 0 0 2px highlight;\n      }\n    }\n  }\n\n  .attachment {\n    position: relative;\n\n    &:hover {\n      cursor: default;\n    }\n  }\n\n  .attachment--preview {\n    .attachment__caption:hover {\n      cursor: text;\n    }\n  }\n\n  .attachment__progress {\n    position: absolute;\n    z-index: 1;\n    height: 20px;\n    top: calc(50% - 10px);\n    left: 5%;\n    width: 90%;\n    opacity: 0.9;\n    transition: opacity 200ms ease-in;\n\n    &[value=\"100\"] {\n      opacity: 0;\n    }\n  }\n\n  .attachment__caption-editor {\n    display: inline-block;\n    width: 100%;\n    margin: 0;\n    padding: 0;\n    font-size: inherit;\n    font-family: inherit;\n    line-height: inherit;\n    color: inherit;\n    text-align: center;\n    vertical-align: top;\n    border: none;\n    outline: none;\n    -webkit-appearance: none;\n    -moz-appearance: none;\n  }\n\n  .attachment__toolbar {\n    position: absolute;\n    z-index: 1;\n    top: -0.9em;\n    left: 0;\n    width: 100%;\n    text-align: center;\n  }\n\n  .trix-button-group {\n    display: inline-flex;\n  }\n\n  .trix-button {\n    position: relative;\n    float: left; // Collapse whitespace between elements\n    color: #666;\n    white-space: nowrap;\n    font-size: 80%;\n    padding: 0 0.8em;\n    margin: 0;\n    outline: none;\n    border: none;\n    border-radius: 0;\n    background: transparent;\n\n    &:not(:first-child) {\n      border-left: 1px solid #ccc;\n    }\n\n    &.trix-active {\n      background: #cbeefa;\n    }\n\n    &:not(:disabled) {\n      cursor: pointer;\n    }\n  }\n\n  .trix-button--remove {\n    text-indent: -9999px;\n    display: inline-block;\n    padding: 0;\n    outline: none;\n    width: 1.8em;\n    height: 1.8em;\n    line-height: 1.8em;\n    border-radius: 50%;\n    background-color: #fff;\n    border: 2px solid highlight;\n    box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25);\n\n    &::before {\n      display: inline-block;\n      position: absolute;\n      top: 0;\n      right: 0;\n      bottom: 0;\n      left: 0;\n      opacity: 0.7;\n      content: \"\";\n      background-image: $icon-remove;\n      background-position: center;\n      background-repeat: no-repeat;\n      background-size: 90%;\n    }\n\n    &:hover {\n      border-color: #333;\n\n      &::before {\n        opacity: 1;\n      }\n    }\n  }\n\n  .attachment__metadata-container {\n    position: relative;\n  }\n\n  .attachment__metadata {\n    position: absolute;\n    left: 50%;\n    top: 2em;\n    transform: translate(-50%, 0);\n    max-width: 90%;\n    padding: 0.1em 0.6em;\n    font-size: 0.8em;\n    color: #fff;\n    background-color: rgba(0, 0, 0, 0.7);\n    border-radius: 3px;\n\n    .attachment__name {\n      display: inline-block;\n      max-width: 100%;\n      vertical-align: bottom;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n    }\n\n    .attachment__size {\n      margin-left: 0.2em;\n      white-space: nowrap;\n    }\n  }\n}\n"
  },
  {
    "path": "assets/trix/stylesheets/content.scss",
    "content": "$quote-border-width: 0.3em;\n$quote-margin-start: 0.3em;\n$quote-padding-start: 0.6em;\n\n.trix-content {\n  line-height: 1.5;\n  overflow-wrap: break-word;\n  word-break: break-word;\n\n  * {\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0;\n  }\n\n  h1 {\n    font-size: 1.2em;\n    line-height: 1.2;\n  }\n\n  blockquote {\n    border: 0 solid #ccc;\n    border-left-width: $quote-border-width;\n    margin-left: $quote-margin-start;\n    padding-left: $quote-padding-start;\n  }\n\n  [dir=rtl] blockquote,\n  blockquote[dir=rtl] {\n    border-width: 0;\n    border-right-width: $quote-border-width;\n    margin-right: $quote-margin-start;\n    padding-right: $quote-padding-start;\n  }\n\n  li {\n    margin-left: 1em;\n  }\n\n  [dir=rtl] li {\n    margin-right: 1em;\n  }\n\n  pre {\n    display: inline-block;\n    width: 100%;\n    vertical-align: top;\n    font-family: monospace;\n    font-size: 0.9em;\n    padding: 0.5em;\n    white-space: pre;\n    background-color: #eee;\n    overflow-x: auto;\n  }\n\n  img {\n    max-width: 100%;\n    height: auto;\n  }\n\n  .attachment {\n    display: inline-block;\n    position: relative;\n    max-width: 100%;\n\n    a {\n      color: inherit;\n      text-decoration: none;\n\n      &:hover,\n      &:visited:hover {\n        color: inherit;\n      }\n    }\n  }\n\n  .attachment__caption {\n    text-align: center;\n\n    .attachment__name + .attachment__size {\n      &::before {\n        content: ' \\2022 ';\n      }\n    }\n  }\n\n  .attachment--preview {\n    width: 100%;\n    text-align: center;\n\n    .attachment__caption {\n      color: #666;\n      font-size: 0.9em;\n      line-height: 1.2;\n    }\n  }\n\n  .attachment--file {\n    color: #333;\n    line-height: 1;\n    margin: 0 2px 2px 2px;\n    padding: 0.4em 1em;\n    border: 1px solid #bbb;\n    border-radius: 5px;\n  }\n\n  .attachment-gallery {\n    display: flex;\n    flex-wrap: wrap;\n    position: relative;\n\n    .attachment {\n      flex: 1 0 33%;\n      padding: 0 0.5em;\n      max-width: 33%;\n    }\n\n    &.attachment-gallery--2,\n    &.attachment-gallery--4 {\n      .attachment {\n        flex-basis: 50%;\n        max-width: 50%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "assets/trix/stylesheets/editor.scss",
    "content": "trix-editor {\n  border: 1px solid #bbb;\n  border-radius: 3px;\n  margin: 0;\n  padding: 0.4em 0.6em;\n  min-height: 5em;\n  outline: none;\n}\n"
  },
  {
    "path": "assets/trix/stylesheets/icons.scss",
    "content": "$icon-attach: svg('trix/images/attach.svg');\n$icon-bold: svg('trix/images/bold.svg');\n$icon-bullets: svg('trix/images/bullets.svg');\n$icon-code: svg('trix/images/code.svg');\n$icon-heading-1: svg('trix/images/heading_1.svg');\n$icon-italic: svg('trix/images/italic.svg');\n$icon-link: svg('trix/images/link.svg');\n$icon-nesting-level-decrease: svg('trix/images/nesting_level_decrease.svg');\n$icon-nesting-level-increase: svg('trix/images/nesting_level_increase.svg');\n$icon-numbers: svg('trix/images/numbers.svg');\n$icon-quote: svg('trix/images/quote.svg');\n$icon-redo: svg('trix/images/redo.svg');\n$icon-remove: svg('trix/images/remove.svg');\n$icon-strike: svg('trix/images/strike.svg');\n$icon-undo: svg('trix/images/undo.svg');\n"
  },
  {
    "path": "assets/trix/stylesheets/media-queries.scss",
    "content": "$phone-width: 768px;\n\n@mixin phone {\n  @media (max-width: #{$phone-width}) {\n    @content;\n  }\n}\n"
  },
  {
    "path": "assets/trix/stylesheets/selection.scss",
    "content": "%disable-selection {\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n\n%invisible-selection {\n  &::-moz-selection { background: none; }\n  &::selection { background: none; }\n}\n\n%visible-selection {\n  &::-moz-selection { background: highlight; }\n  &::selection { background: highlight; }\n}\n"
  },
  {
    "path": "assets/trix/stylesheets/toolbar.scss",
    "content": "@import \"./media-queries\";\n@import \"./icons\";\n\n$font-size-normal: 0.75em;\n$opacity-normal: 0.6;\n$opacity-disabled: 0.125;\n$opacity-active: 1;\n\ntrix-toolbar {\n  * {\n    box-sizing: border-box;\n  }\n\n  .trix-button-row {\n    display: flex;\n    flex-wrap: nowrap;\n    justify-content: space-between;\n    overflow-x: auto;\n  }\n\n  .trix-button-group {\n    display: flex;\n    margin-bottom: 10px;\n    border: 1px solid #bbb;\n    border-top-color: #ccc;\n    border-bottom-color: #888;\n    border-radius: 3px;\n\n    &:not(:first-child) {\n      margin-left: 1.5vw;\n\n      @include phone {\n        margin-left: 0;\n      }\n    }\n  }\n\n  .trix-button-group-spacer {\n    flex-grow: 1;\n\n    @include phone {\n      display: none;\n    }\n  }\n\n  .trix-button {\n    position: relative;\n    float: left; // Collapse whitespace between elements\n    color: rgba(0,0,0, $opacity-normal);\n    font-size: $font-size-normal;\n    font-weight: 600;\n    white-space: nowrap;\n    padding: 0 0.5em;\n    margin: 0;\n    outline: none;\n    border: none;\n    border-bottom: 1px solid #ddd;\n    border-radius: 0;\n    background: transparent;\n\n    &:not(:first-child) {\n      border-left: 1px solid #ccc;\n    }\n\n    &.trix-active {\n      background: #cbeefa;\n      color: rgba(0,0,0, $opacity-active);\n    }\n\n    &:not(:disabled) {\n      cursor: pointer;\n    }\n\n    &:disabled {\n      color: rgba(0,0,0, $opacity-disabled);\n    }\n\n    @include phone {\n      letter-spacing: -0.01em;\n      padding: 0 0.3em;\n    }\n  }\n\n  .trix-button--icon {\n    font-size: inherit;\n    width: 2.6em;\n    height: 1.6em;\n    max-width: calc(0.8em + 4vw);\n    text-indent: -9999px;\n\n    @include phone {\n      height: 2em;\n      max-width: calc(0.8em + 3.5vw);\n    }\n\n    &::before {\n      display: inline-block;\n      position: absolute;\n      top: 0;\n      right: 0;\n      bottom: 0;\n      left: 0;\n      opacity: $opacity-normal;\n      content: \"\";\n      background-position: center;\n      background-repeat: no-repeat;\n      background-size: contain;\n\n      @include phone {\n        right: 6%;\n        left: 6%;\n      }\n    }\n\n    &.trix-active::before {\n      opacity: $opacity-active;\n    }\n\n    &:disabled::before {\n      opacity: $opacity-disabled;\n    }\n  }\n\n  .trix-button--icon-attach::before { background-image: $icon-attach; top: 8%; bottom: 4%; }\n  .trix-button--icon-bold::before { background-image: $icon-bold; }\n  .trix-button--icon-italic::before { background-image: $icon-italic; }\n  .trix-button--icon-link::before { background-image: $icon-link; }\n  .trix-button--icon-strike::before { background-image: $icon-strike; }\n  .trix-button--icon-quote::before { background-image: $icon-quote; }\n  .trix-button--icon-heading-1::before { background-image: $icon-heading-1; }\n  .trix-button--icon-code::before { background-image: $icon-code; }\n  .trix-button--icon-bullet-list::before { background-image: $icon-bullets; }\n  .trix-button--icon-number-list::before { background-image: $icon-numbers; }\n  .trix-button--icon-undo::before { background-image: $icon-undo; }\n  .trix-button--icon-redo::before { background-image: $icon-redo; }\n  .trix-button--icon-decrease-nesting-level::before { background-image: $icon-nesting-level-decrease; }\n  .trix-button--icon-increase-nesting-level::before { background-image: $icon-nesting-level-increase; }\n\n  .trix-dialogs {\n    position: relative;\n  }\n\n  .trix-dialog {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    font-size: $font-size-normal;\n    padding: 15px 10px;\n    background: #fff;\n    box-shadow: 0 0.3em 1em #ccc;\n    border-top: 2px solid #888;\n    border-radius: 5px;\n    z-index: 5;\n  }\n\n  .trix-input--dialog {\n    font-size: inherit;\n    font-weight: normal;\n    padding: 0.5em 0.8em;\n    margin: 0 10px 0 0;\n    border-radius: 3px;\n    border: 1px solid #bbb;\n    background-color: #fff;\n    box-shadow: none;\n    outline: none;\n    -webkit-appearance: none;\n    -moz-appearance: none;\n\n    &.validate:invalid {\n      box-shadow: #F00 0px 0px 1.5px 1px;\n    }\n  }\n\n  .trix-button--dialog {\n    font-size: inherit;\n    padding: 0.5em;\n    border-bottom: none;\n  }\n\n  .trix-dialog--link {\n    max-width: 600px;\n  }\n\n  .trix-dialog__link-fields {\n    display: flex;\n    align-items: baseline;\n\n    .trix-input {\n      flex: 1;\n    }\n\n    .trix-button-group {\n      flex: 0 0 content;\n      margin: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "assets/trix.scss",
    "content": "@import \"trix/stylesheets/editor\";\n@import \"trix/stylesheets/toolbar\";\n@import \"trix/stylesheets/attachments\";\n@import \"trix/stylesheets/content\";\n"
  },
  {
    "path": "babel.config.json",
    "content": "{\n  \"presets\": [\n    [\"@babel/preset-env\",\n      {\n        \"targets\": {\n          \"chrome\": \"80\",\n          \"safari\": \"12.1\",\n          \"edge\": \"80\",\n          \"firefox\": \"88\"\n        }\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "bin/ci",
    "content": "#!/usr/bin/env bash\nset -e\n\nif [ -n \"$CI\" ]; then\n  echo \"GITHUB_WORKFLOW: $GITHUB_WORKFLOW\"\n  echo \"GITHUB_RUN_NUMBER: $GITHUB_RUN_NUMBER\"\n  echo \"GITHUB_RUN_ID: $GITHUB_RUN_ID\"\n  echo \"GITHUB_ACTOR: $GITHUB_ACTOR\"\n  echo \"GITHUB_EVENT_NAME: $GITHUB_EVENT_NAME\"\n  echo \"GITHUB_SHA: $GITHUB_SHA\"\n  echo \"GITHUB_REF: $GITHUB_REF\"\n  echo \"GITHUB_HEAD_REF: $GITHUB_HEAD_REF\"\n  echo \"GITHUB_BASE_REF: $GITHUB_BASE_REF\"\nfi\n\nyarn test\n"
  },
  {
    "path": "bin/sass-build",
    "content": "#!/usr/bin/env node\n\nconst path = require(\"path\")\nconst fs = require(\"fs\")\nconst sass = require(\"sass\")\nconst { optimize } = require(\"svgo\")\nconst chokidar = require(\"chokidar\")\n\nconst args = process.argv.slice(2)\nif (args.length < 2) {\n  console.error(\"Usage: bin/sass-build <inputFile> <outputFile> (--watch)\")\n  process.exit(1)\n}\nconst inputFile = path.resolve(args[0])\nconst outputFiles = args.slice(1).map(outputPath => path.resolve(outputPath))\nconst watchMode = args.includes(\"--watch\")\n\nconst basePath = path.dirname(inputFile)\n\nconst functions = {\n  \"svg($file)\": (args) => {\n    const fileName = args[0].assertString().text\n    const filePath = path.resolve(basePath, fileName)\n\n    let svgContent = fs.readFileSync(filePath, \"utf8\")\n    svgContent = optimize(svgContent, { multipass: true, datauri: \"enc\" })\n\n    return new sass.SassString(`url(\"${svgContent.data}\")`, { quotes: false })\n  }\n}\n\nfunction compile() {\n  try {\n    const result = sass.compile(inputFile, { functions })\n\n    outputFiles.forEach(outputFile => fs.writeFileSync(outputFile, result.css, \"utf8\"))\n  } catch (error) {\n    console.error(\"Error compiling SCSS:\", error.message)\n  }\n}\ncompile()\n\nif (watchMode) {\n  console.log(`Watching for SASS file changes under ${basePath}...`)\n  chokidar.watch(basePath).on(\"change\", (filePath) => {\n    if (!filePath.endsWith(\".scss\")) return\n    console.log(`[${new Date().toLocaleTimeString()}] ${filePath} changed. Recompiling...`)\n    compile()\n  })\n}\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env bash\nset -eo pipefail\n\n# Use binstubs. Work from the root dir.\napp_root=\"$( cd \"$(dirname \"$0\")/..\"; pwd )\"\n\n# Prefer bin/ executables\nexport PATH=\"$app_root/bin:$PATH\"\n\nif [ \"$1\" = \"-v\" ]; then\n  exec 3>&1\nelse\n  exec 3>/dev/null\n  exec 4>&1\n  trap 'echo \"Setup failed - run \\`bin/setup -v\\` to see the error output\" >&4' ERR\nfi\n\nbrew_install_missing() {\n  if which brew > /dev/null; then\n    if ! which \"$1\" > /dev/null; then\n      echo \" -- Installing Homebrew package: $@\"\n      brew reinstall \"$@\"\n    fi\n  else\n    return 1\n  fi\n}\n\nabort() {\n  echo \"$@\"\n  return 2\n}\n\necho \"--- Installing Ruby gems\"\n{\n  if which rbenv > /dev/null; then\n    rbenv install --skip-existing\n  else\n    if ! which ruby > /dev/null; then\n      brew_install_missing ruby || abort \"Can't find or install Ruby. Install it from https://www.ruby-lang.org or with https://github.com/rbenv/rbenv\"\n    fi\n  fi\n  gem list -i bundler >/dev/null 2>&1 || gem install bundler\n  bundle check || bundle install\n} >&3 2>&1\n\necho \"--- Installing npm modules\"\n{\n  if ! which npm > /dev/null; then\n    brew_install_missing \"npm\" || abort \"Can't find or install npm. Install it from https://nodejs.org\"\n  fi\n  npm install\n} >&3 2>&1\n\nif [ -d \"$HOME/.pow\" ]; then\n  echo \"--- Setting up Pow\"\n  { ln -nfs \"$app_root\" \"$HOME/.pow/trix\"\n    mkdir -p tmp\n    touch tmp/restart.txt\n  } >&3 2>&1\nfi\n\necho\necho \"Done!\"\nif [ -L \"$HOME/.pow/trix\" ]; then\n  echo \" * Open http://trix.dev to develop in-browser\"\nelse\n  echo \" * Run \\`bin/rackup\\` to develop in-browser\"\nfi\necho \" * Run \\`bin/blade build\\` to build Trix\"\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"trix\",\n  \"version\": \"2.1.17\",\n  \"description\": \"A rich text editor for everyday writing\",\n  \"main\": \"dist/trix.umd.min.js\",\n  \"module\": \"dist/trix.esm.min.js\",\n  \"style\": \"dist/trix.css\",\n  \"files\": [\n    \"dist/*.css\",\n    \"dist/*.js\",\n    \"dist/*.map\",\n    \"src/{inspector,trix}/*.js\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/basecamp/trix.git\"\n  },\n  \"keywords\": [\n    \"rich text\",\n    \"wysiwyg\",\n    \"editor\"\n  ],\n  \"author\": \"37signals, LLC\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/basecamp/trix/issues\"\n  },\n  \"homepage\": \"https://trix-editor.org/\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.16.0\",\n    \"@babel/preset-env\": \"^7.16.4\",\n    \"@rollup/plugin-babel\": \"^5.3.0\",\n    \"@rollup/plugin-commonjs\": \"^22.0.2\",\n    \"@rollup/plugin-json\": \"^4.1.0\",\n    \"@rollup/plugin-node-resolve\": \"^13.3.0\",\n    \"@web/dev-server\": \"^0.1.34\",\n    \"@web/test-runner\": \"^0.20.2\",\n    \"@web/test-runner-playwright\": \"^0.11.1\",\n    \"@web/test-runner-webdriver\": \"^0.9.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"chokidar\": \"^4.0.2\",\n    \"concurrently\": \"^7.4.0\",\n    \"eslint\": \"^7.32.0\",\n    \"esm\": \"^3.2.25\",\n    \"idiomorph\": \"^0.7.3\",\n    \"qunit\": \"2.19.1\",\n    \"rangy\": \"^1.3.0\",\n    \"rollup\": \"^2.56.3\",\n    \"rollup-plugin-includepaths\": \"^0.2.4\",\n    \"rollup-plugin-terser\": \"^7.0.2\",\n    \"sass\": \"^1.83.0\",\n    \"source-map\": \"^0.7.6\",\n    \"svgo\": \"^2.8.0\",\n    \"webdriverio\": \"^7.19.5\"\n  },\n  \"resolutions\": {\n    \"webdriverio\": \"^7.19.5\"\n  },\n  \"scripts\": {\n    \"build-css\": \"bin/sass-build assets/trix.scss dist/trix.css action_text-trix/app/assets/stylesheets/trix.css\",\n    \"build-js\": \"rollup -c\",\n    \"build-assets\": \"cp -f assets/*.html dist/\",\n    \"build-ruby\": \"rake -C action_text-trix sync\",\n    \"build\": \"yarn run build-js && yarn run build-css && yarn run build-assets && yarn run build-ruby\",\n    \"watch\": \"rollup -c -w\",\n    \"lint\": \"eslint .\",\n    \"pretest\": \"yarn run lint && yarn run build\",\n    \"test\": \"web-test-runner\",\n    \"test:watch\": \"web-test-runner --watch\",\n    \"version\": \"yarn build && git add action_text-trix\",\n    \"prerelease\": \"yarn version && yarn test\",\n    \"release-npm\": \"npm adduser && npm publish\",\n    \"release-ruby\": \"rake -C action_text-trix release\",\n    \"release\": \"yarn run release-npm && yarn run release-ruby\",\n    \"postrelease\": \"git push && git push --tags\",\n    \"dev\": \"web-dev-server --app-index index.html  --root-dir dist --node-resolve --open\",\n    \"start\": \"yarn build-assets && concurrently --kill-others --names js,css,dev-server 'yarn watch' 'yarn build-css --watch' 'yarn dev'\"\n  },\n  \"dependencies\": {\n    \"dompurify\": \"^3.2.5\"\n  },\n  \"engines\": {\n    \"node\": \">= 18\"\n  }\n}\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import json from \"@rollup/plugin-json\"\nimport includePaths from \"rollup-plugin-includepaths\"\nimport commonjs from \"@rollup/plugin-commonjs\"\nimport { babel } from \"@rollup/plugin-babel\"\nimport nodeResolve from \"@rollup/plugin-node-resolve\"\nimport { terser } from \"rollup-plugin-terser\"\n\nimport { version } from \"./package.json\"\n\nconst year = new Date().getFullYear()\nconst banner = `/*\\nTrix ${version}\\nCopyright © ${year} 37signals, LLC\\n */`\n\nconst plugins = [\n  json(),\n  includePaths({\n    paths: [ \"src\" ],\n    extensions: [ \".js\" ]\n  }),\n  nodeResolve({ extensions: [ \".js\" ] }),\n  commonjs({\n    extensions: [ \".js\" ]\n  }),\n  babel({ babelHelpers: \"bundled\" }),\n]\n\nconst defaultConfig = {\n  context: \"window\",\n  treeshake: false,\n  plugins: plugins,\n  watch: {\n    include: \"src/**\"\n  }\n}\n\nconst terserConfig = terser({\n  mangle: true,\n  compress: true,\n  format: {\n    comments: function (node, comment) {\n      const text = comment.value\n      const type = comment.type\n      if (type == \"comment2\") {\n        // multiline comment\n        return /@license|Copyright/.test(text)\n      }\n    },\n  },\n})\n\nconst compressedConfig = Object.assign({}, defaultConfig, { plugins: plugins.concat(terserConfig) })\n\nexport default [\n  {\n    input: \"src/trix/trix.js\",\n    output: [\n      {\n        name: \"Trix\",\n        file: \"dist/trix.umd.js\",\n        format: \"umd\",\n        banner\n      },\n      {\n        file: \"dist/trix.esm.js\",\n        format: \"es\",\n        banner\n      },\n      {\n        name: \"Trix\",\n        file: \"action_text-trix/app/assets/javascripts/trix.js\",\n        format: \"umd\",\n        banner\n      },\n    ],\n    ...defaultConfig,\n  },\n  {\n    input: \"src/trix/trix.js\",\n    output: [\n      {\n        name: \"Trix\",\n        file: \"dist/trix.umd.min.js\",\n        format: \"umd\",\n        banner,\n        sourcemap: true\n      },\n      {\n        file: \"dist/trix.esm.min.js\",\n        format: \"es\",\n        banner,\n        sourcemap: true\n      }\n    ],\n    ...compressedConfig,\n  },\n  {\n    input: \"src/test/test.js\",\n    output: {\n      name: \"TrixTests\",\n      file: \"dist/test.js\",\n      format: \"es\",\n      sourcemap: true,\n      banner\n    },\n    ...defaultConfig,\n  },\n  {\n    input: \"src/inspector/inspector.js\",\n    output: {\n      name: \"TrixInspector\",\n      file: \"dist/inspector.js\",\n      format: \"es\",\n      sourcemap: true,\n      banner\n    },\n    ...defaultConfig,\n  }\n]\n"
  },
  {
    "path": "src/inspector/control_element.js",
    "content": "const KEY_EVENTS = \"keydown keypress input\".split(\" \")\nconst COMPOSITION_EVENTS = \"compositionstart compositionupdate compositionend textInput\".split(\" \")\nconst OBSERVER_OPTIONS = {\n  attributes: true,\n  childList: true,\n  characterData: true,\n  characterDataOldValue: true,\n  subtree: true,\n}\n\nexport default class ControlElement {\n  constructor(editorElement) {\n    this.didMutate = this.didMutate.bind(this)\n    this.editorElement = editorElement\n    this.install()\n  }\n\n  install() {\n    this.createElement()\n    this.logInputEvents()\n    this.logMutations()\n  }\n\n  uninstall() {\n    this.observer.disconnect()\n    this.element.parentNode.removeChild(this.element)\n  }\n\n  createElement() {\n    this.element = document.createElement(\"div\")\n    this.element.setAttribute(\"contenteditable\", \"\")\n    this.element.style.width = getComputedStyle(this.editorElement).width\n    this.element.style.minHeight = \"50px\"\n    this.element.style.border = \"1px solid green\"\n    this.editorElement.parentNode.insertBefore(this.element, this.editorElement.nextSibling)\n  }\n\n  logInputEvents() {\n    KEY_EVENTS.forEach((eventName) => {\n      this.element.addEventListener(eventName, (event) => console.log(`${event.type}: keyCode = ${event.keyCode}`))\n    })\n\n    COMPOSITION_EVENTS.forEach((eventName) => {\n      this.element.addEventListener(eventName, (event) =>\n        console.log(`${event.type}: data = ${JSON.stringify(event.data)}`)\n      )\n    })\n  }\n\n  logMutations() {\n    this.observer = new window.MutationObserver(this.didMutate)\n    this.observer.observe(this.element, OBSERVER_OPTIONS)\n  }\n\n  didMutate(mutations) {\n    console.log(`Mutations (${mutations.length}):`)\n    for (let index = 0; index < mutations.length; index++) {\n      const mutation = mutations[index]\n      console.log(` ${index + 1}. ${mutation.type}:`)\n      switch (mutation.type) {\n        case \"characterData\":\n          console.log(`  oldValue = ${JSON.stringify(mutation.oldValue)}, newValue = ${JSON.stringify(mutation.target.data)}`)\n          break\n        case \"childList\":\n          Array.from(mutation.addedNodes).forEach((node) => {\n            console.log(`  node added ${inspectNode(node)}`)\n          })\n\n          Array.from(mutation.removedNodes).forEach((node) => {\n            console.log(`  node removed ${inspectNode(node)}`)\n          })\n      }\n    }\n  }\n}\n\nconst inspectNode = function(node) {\n  if (node.data) {\n    return JSON.stringify(node.data)\n  } else {\n    return JSON.stringify(node.outerHTML)\n  }\n}\n"
  },
  {
    "path": "src/inspector/debugger.js",
    "content": "/* eslint-disable\n    id-length,\n    no-empty,\n*/\n\n// This file is not included in the main Trix bundle and\n// should be explicitly required to enable the debugger.\n\nconst DEBUG_METHODS = {\n  AttachmentEditorController: [\n    \"didClickRemoveButton\",\n    \"uninstall\",\n  ],\n\n  \"Trix.CompositionController\": [\n    \"didClickAttachment\"\n  ],\n\n  EditorController: [\n    \"setEditor\",\n    \"loadDocument\",\n  ],\n\n  \"Trix.Level0InputController\": [\n    \"elementDidMutate\",\n    \"events.keydown\",\n    \"events.keypress\",\n    \"events.dragstart\",\n    \"events.dragover\",\n    \"events.dragend\",\n    \"events.drop\",\n    \"events.cut\",\n    \"events.paste\",\n    \"events.compositionstart\",\n    \"events.compositionend\",\n  ],\n\n  \"Trix.Level2InputController\": [\n    \"elementDidMutate\",\n    \"events.beforeinput\",\n    \"events.input\",\n    \"events.compositionend\",\n  ],\n\n  \"Trix.ToolbarController\": [\n    \"didClickActionButton\",\n    \"didClickAttributeButton\",\n    \"didClickDialogButton\",\n    \"didKeyDownDialogInput\",\n  ]\n}\n\nimport { findClosestElementFromNode } from \"trix/core/helpers\"\n\nlet errorListeners = []\n\nTrix.Debugger = {\n  addErrorListener(listener) {\n    if (!errorListeners.includes(listener)) {\n      errorListeners.push(listener)\n    }\n  },\n\n  removeErrorListener(listener) {\n    errorListeners = errorListeners.filter((l) => l !== listener)\n  },\n}\n\nconst installMethodDebugger = function(className, methodName) {\n  const [ objectName, ...constructorNames ] = className.split(\".\")\n\n  const parts = methodName.split(\".\")\n  const propertyNames = parts.slice(0, parts.length - 1)\n  methodName = parts[parts.length - 1]\n\n  let object = this[objectName]\n  constructorNames.forEach((constructorName) => {\n    object = object[constructorName]\n  })\n  object = object.prototype\n  propertyNames.forEach((propertyName) => {\n    object = object[propertyName]\n  })\n\n  if (typeof object?.[methodName] === \"function\") {\n    object[methodName] = wrapFunctionWithErrorHandler(object[methodName])\n  } else {\n    throw new Error(\"Can't install on non-function\")\n  }\n}\n\nconst wrapFunctionWithErrorHandler = function(fn) {\n  const trixDebugWrapper = function() {\n    try {\n      return fn.apply(this, arguments)\n    } catch (error) {\n      reportError(error)\n      throw error\n    }\n  }\n  return trixDebugWrapper\n}\n\nconst reportError = function(error) {\n  Trix.Debugger.lastError = error\n\n  console.error(\"Trix error!\")\n  console.log(error.stack)\n\n  const { activeElement } = document\n  const editorElement = findClosestElementFromNode(activeElement, { matchingSelector: \"trix-editor\" })\n\n  if (editorElement) {\n    notifyErrorListeners(error, editorElement)\n  } else {\n    console.warn(\"Can't find <trix-editor> element. document.activeElement =\", activeElement)\n  }\n}\n\nconst notifyErrorListeners = (error, element) => {\n  errorListeners.forEach((listener) => {\n    try {\n      listener(error, element)\n    } catch (error1) {}\n  })\n}\n\n(function() {\n  console.groupCollapsed(\"Trix debugger\")\n\n  for (const className in DEBUG_METHODS) {\n    const methodNames = DEBUG_METHODS[className]\n\n    methodNames.forEach((methodName) => {\n      try {\n        installMethodDebugger(className, methodName)\n        console.log(`✓ ${className}#${methodName}`)\n      } catch (error) {\n        console.warn(`✗ ${className}#${methodName}:`, error.message)\n      }\n    })\n  }\n\n  console.groupEnd()\n})()\n"
  },
  {
    "path": "src/inspector/element.js",
    "content": "/* eslint-disable\n    id-length,\n*/\nimport { installDefaultCSSForTagName } from \"trix/core/helpers\"\n\ninstallDefaultCSSForTagName(\"trix-inspector\", `\\\n%t {\n  display: block;\n}\n\n%t {\n  position: fixed;\n  background: #fff;\n  border: 1px solid #444;\n  border-radius: 5px;\n  padding: 10px;\n  font-family: sans-serif;\n  font-size: 12px;\n  overflow: auto;\n  word-wrap: break-word;\n}\n\n%t details {\n  margin-bottom: 10px;\n}\n\n%t summary:focus {\n  outline: none;\n}\n\n%t details .panel {\n  padding: 10px;\n}\n\n%t .performance .metrics {\n  margin: 0 0 5px 5px;\n}\n\n%t .selection .characters {\n  margin-top: 10px;\n}\n\n%t .selection .character {\n  display: inline-block;\n  font-size: 8px;\n  font-family: courier, monospace;\n  line-height: 10px;\n  vertical-align: middle;\n  text-align: center;\n  width: 10px;\n  height: 10px;\n  margin: 0 1px 1px 0;\n  border: 1px solid #333;\n  border-radius: 1px;\n  background: #676666;\n  color: #fff;\n}\n\n%t .selection .character.selected {\n  background: yellow;\n  color: #000;\n}\\\n`)\n\nexport default class TrixInspector extends HTMLElement {\n  connectedCallback() {\n    this.editorElement = document.querySelector(`trix-editor[trix-id='${this.dataset.trixId}']`)\n    this.views = this.createViews()\n\n    this.views.forEach((view) => {\n      view.render()\n      this.appendChild(view.element)\n    })\n\n    this.reposition()\n\n    this.resizeHandler = this.reposition.bind(this)\n    addEventListener(\"resize\", this.resizeHandler)\n  }\n\n  disconnectedCallback() {\n    removeEventListener(\"resize\", this.resizeHandler)\n  }\n\n  createViews() {\n    const views = Trix.Inspector.views.map((View) => new View(this.editorElement))\n\n    return views.sort((a, b) => a.title.toLowerCase() > b.title.toLowerCase())\n  }\n\n  reposition() {\n    const { top, right } = this.editorElement.getBoundingClientRect()\n\n    this.style.top = `${top}px`\n    this.style.left = `${right + 10}px`\n    this.style.maxWidth = `${window.innerWidth - right - 40}px`\n    this.style.maxHeight = `${window.innerHeight - top - 30}px`\n  }\n}\n\nwindow.customElements.define(\"trix-inspector\", TrixInspector)\n"
  },
  {
    "path": "src/inspector/global.js",
    "content": "window.Trix.Inspector = {\n  views: [],\n\n  registerView(constructor) {\n    return this.views.push(constructor)\n  },\n\n  install(editorElement) {\n    this.editorElement = editorElement\n    const element = document.createElement(\"trix-inspector\")\n    element.dataset.trixId = this.editorElement.trixId\n    return document.body.appendChild(element)\n  },\n}\n"
  },
  {
    "path": "src/inspector/inspector.js",
    "content": "import \"inspector/element\"\nimport \"inspector/global\"\nimport \"inspector/templates\"\nimport \"inspector/control_element\"\nimport \"inspector/views/debug_view\"\nimport \"inspector/views/document_view\"\nimport \"inspector/views/performance_view\"\nimport \"inspector/views/render_view\"\nimport \"inspector/views/selection_view\"\nimport \"inspector/views/undo_view\"\n"
  },
  {
    "path": "src/inspector/templates/debug.js",
    "content": "if (!window.JST) window.JST = {}\n\nwindow.JST[\"trix/inspector/templates/debug\"] = function() {\n  return `<p>\n  <label>\n    <input type=\"checkbox\" name=\"viewCaching\" checked=\"${this.compositionController.isViewCachingEnabled()}\">\n    Cache views between renders\n  </label>\n</p>\n\n<p>\n  <button data-action=\"render\">Force Render</button> <button data-action=\"parse\">Parse current HTML</button>\n</p>\n\n<p>\n  <label>\n    <input type=\"checkbox\" name=\"controlElement\">\n    Show <code>contenteditable</code> control element\n  </label>\n</p>` }\n"
  },
  {
    "path": "src/inspector/templates/document.js",
    "content": "if (!window.JST) window.JST = {}\n\nwindow.JST[\"trix/inspector/templates/document\"] = function() {\n  const details = this.document.getBlocks().map((block, index) => {\n    const { text } = block\n    const pieces = text.pieceList.toArray()\n\n    return `<details class=\"block\">\n      <summary class=\"title\">\n        Block ${block.id}, Index: ${index}\n      </summary>\n      <div class=\"attributes\">\n        Attributes: ${JSON.stringify(block.attributes)}\n      </div>\n\n      <div class=\"htmlAttributes\">\n        HTML Attributes: ${JSON.stringify(block.htmlAttributes)}\n      </div>\n\n      <div class=\"text\">\n        <div class=\"title\">\n          Text: ${text.id}, Pieces: ${pieces.length}, Length: ${text.getLength()}\n        </div>\n        <div class=\"pieces\">\n          ${piecePartials(pieces).join(\"\\n\")}\n        </div>\n      </div>\n    </details>`\n  })\n\n  return details.join(\"\\n\")\n}\n\nconst piecePartials = (pieces) =>\n  pieces.map((piece, index) =>`<div class=\"piece\">\n      <div class=\"title\">\n        Piece ${piece.id}, Index: ${index}\n      </div>\n      <div class=\"attributes\">\n        Attributes: ${JSON.stringify(piece.attributes)}\n      </div>\n      <div class=\"content\">\n        ${JSON.stringify(piece.toString())}\n      </div>\n    </div>`)\n"
  },
  {
    "path": "src/inspector/templates/performance.js",
    "content": "if (!window.JST) window.JST = {}\n\nwindow.JST[\"trix/inspector/templates/performance\"] = function() {\n  return Object.keys(this.data).map((name) => {\n      const data = this.data[name]\n      return dataMetrics(name, data, this.round)\n  }).join(\"\\n\")\n}\n\nconst dataMetrics = function(name, data, round) {\n  let item = `<strong>${name}</strong> (${data.calls})<br>`\n\n  if (data.calls > 0) {\n    item += `<div class=\"metrics\">\n        Mean: ${round(data.mean)}ms<br>\n        Max: ${round(data.max)}ms<br>\n        Last: ${round(data.last)}ms\n      </div>`\n\n    return item\n  }\n}\n"
  },
  {
    "path": "src/inspector/templates/render.js",
    "content": "if (!window.JST) window.JST = {}\n\nwindow.JST[\"trix/inspector/templates/render\"] = () => `Syncs: ${this.syncCount}`\n"
  },
  {
    "path": "src/inspector/templates/selection.js",
    "content": "if (!window.JST) window.JST = {}\n\nwindow.JST[\"trix/inspector/templates/selection\"] = function() {\n  return `Location range: [${this.locationRange[0].index}:${this.locationRange[0].offset}, ${this.locationRange[1].index}:${this.locationRange[1].offset}]\n    ${charSpans(this.characters).join(\"\\n\")}`\n}\n\nconst charSpans = (characters) =>\n  Array.from(characters).map(\n    (char) => `<span class=\"character ${char.selected ? \"selected\" : undefined}\">${char.string}</span>`\n  )\n"
  },
  {
    "path": "src/inspector/templates/undo.js",
    "content": "if (!window.JST) window.JST = {}\n\nwindow.JST[\"trix/inspector/templates/undo\"] = () =>\n  `<h4>Undo stack</h4>\n    <ol class=\"undo-entries\">\n      ${entryList(this.undoEntries)}\n    </ol>\n    <h4>Redo stack</h4>\n    <ol class=\"redo-entries\">\n      ${entryList(this.redoEntries)}\n    </ol>`\n\nconst entryList = (entries) =>\n  entries.map((entry) =>\n    `<li>${entry.description} ${JSON.stringify({\n      selectedRange: entry.snapshot.selectedRange,\n      context: entry.context,\n    })}</li>`)\n"
  },
  {
    "path": "src/inspector/templates.js",
    "content": "import \"inspector/templates/debug\"\nimport \"inspector/templates/document\"\nimport \"inspector/templates/performance\"\nimport \"inspector/templates/render\"\nimport \"inspector/templates/selection\"\nimport \"inspector/templates/undo\"\n"
  },
  {
    "path": "src/inspector/view.js",
    "content": "import { handleEvent } from \"trix/core/helpers\"\n\nexport default class View {\n  constructor(editorElement) {\n    this.editorElement = editorElement\n    this.editorController = this.editorElement.editorController\n    this.editor = this.editorElement.editor\n    this.compositionController = this.editorController.compositionController\n    this.composition = this.editorController.composition\n\n    this.element = document.createElement(\"details\")\n    if (this.getSetting(\"open\") === \"true\") {\n      this.element.open = true\n    }\n    this.element.classList.add(this.constructor.template)\n\n    this.titleElement = document.createElement(\"summary\")\n    this.element.appendChild(this.titleElement)\n\n    this.panelElement = document.createElement(\"div\")\n    this.panelElement.classList.add(\"panel\")\n    this.element.appendChild(this.panelElement)\n\n    this.element.addEventListener(\"toggle\", (event) => {\n      if (event.target === this.element) {\n        return this.didToggle()\n      }\n    })\n\n    if (this.events) {\n      this.installEventHandlers()\n    }\n  }\n\n  installEventHandlers() {\n    for (const eventName in this.events) {\n      const handler = this.events[eventName]\n      const callback = (event) => {\n        requestAnimationFrame(() => {\n          handler.call(this, event)\n        })\n      }\n\n      handleEvent(eventName, { onElement: this.editorElement, withCallback: callback })\n    }\n  }\n\n  didToggle(event) {\n    this.saveSetting(\"open\", this.isOpen())\n    return this.render()\n  }\n\n  isOpen() {\n    return this.element.hasAttribute(\"open\")\n  }\n\n  getTitle() {\n    return this.title || \"\"\n  }\n\n  render() {\n    this.renderTitle()\n    if (this.isOpen()) {\n      this.panelElement.innerHTML = window.JST[`trix/inspector/templates/${this.constructor.template}`].apply(this)\n    }\n  }\n\n  renderTitle() {\n    this.titleElement.innerHTML = this.getTitle()\n  }\n\n  getSetting(key) {\n    key = this.getSettingsKey(key)\n    return window.sessionStorage?.[key]\n  }\n\n  saveSetting(key, value) {\n    key = this.getSettingsKey(key)\n    if (window.sessionStorage) {\n      window.sessionStorage[key] = value\n    }\n  }\n\n  getSettingsKey(key) {\n    return `trix/inspector/${this.template}/${key}`\n  }\n\n  get title() {\n    return this.constructor.title\n  }\n\n  get template() {\n    return this.constructor.template\n  }\n\n  get events() {\n    return this.constructor.events\n  }\n}\n"
  },
  {
    "path": "src/inspector/views/debug_view.js",
    "content": "import View from \"inspector/view\"\n\nimport { handleEvent } from \"trix/core/helpers\"\n\nclass DebugView extends View {\n  static title = \"Debug\"\n  static template = \"debug\"\n\n  constructor() {\n    super(...arguments)\n    this.didToggleViewCaching = this.didToggleViewCaching.bind(this)\n    this.didClickRenderButton = this.didClickRenderButton.bind(this)\n    this.didClickParseButton = this.didClickParseButton.bind(this)\n    this.didToggleControlElement = this.didToggleControlElement.bind(this)\n\n    handleEvent(\"change\", {\n      onElement: this.element,\n      matchingSelector: \"input[name=viewCaching]\",\n      withCallback: this.didToggleViewCaching,\n    })\n    handleEvent(\"click\", {\n      onElement: this.element,\n      matchingSelector: \"button[data-action=render]\",\n      withCallback: this.didClickRenderButton,\n    })\n    handleEvent(\"click\", {\n      onElement: this.element,\n      matchingSelector: \"button[data-action=parse]\",\n      withCallback: this.didClickParseButton,\n    })\n    handleEvent(\"change\", {\n      onElement: this.element,\n      matchingSelector: \"input[name=controlElement]\",\n      withCallback: this.didToggleControlElement,\n    })\n  }\n\n  didToggleViewCaching({ target }) {\n    if (target.checked) {\n      return this.compositionController.enableViewCaching()\n    } else {\n      return this.compositionController.disableViewCaching()\n    }\n  }\n\n  didClickRenderButton() {\n    return this.editorController.render()\n  }\n\n  didClickParseButton() {\n    return this.editorController.reparse()\n  }\n\n  didToggleControlElement({ target }) {\n    if (target.checked) {\n      this.control = new Trix.Inspector.ControlElement(this.editorElement)\n    } else {\n      this.control?.uninstall()\n      this.control = null\n    }\n  }\n}\n\nTrix.Inspector.registerView(DebugView)\n"
  },
  {
    "path": "src/inspector/views/document_view.js",
    "content": "import View from \"inspector/view\"\n\nclass DocumentView extends View {\n  static title = \"Document\"\n  static template = \"document\"\n  static events = {\n    \"trix-change\": function() {\n      return this.render()\n    },\n  }\n\n  render() {\n    this.document = this.editor.getDocument()\n    return super.render(...arguments)\n  }\n}\n\nTrix.Inspector.registerView(DocumentView)\n"
  },
  {
    "path": "src/inspector/views/performance_view.js",
    "content": "import View from \"inspector/view\"\nconst now = window.performance?.now ? () => performance.now() : () => new Date().getTime()\n\nclass PerformanceView extends View {\n  static title = \"Performance\"\n  static template = \"performance\"\n\n  constructor() {\n    super(...arguments)\n    this.documentView = this.compositionController.documentView\n\n    this.data = {}\n    this.track(\"documentView.render\")\n    this.track(\"documentView.sync\")\n    this.track(\"documentView.garbageCollectCachedViews\")\n    this.track(\"composition.replaceHTML\")\n\n    this.render()\n  }\n\n  track(methodPath) {\n    this.data[methodPath] = { calls: 0, total: 0, mean: 0, max: 0, last: 0 }\n    const parts = methodPath.split(\".\")\n    const propertyNames = parts.slice(0, parts.length - 1)\n    const methodName = parts[parts.length - 1]\n\n    let object = this\n\n    propertyNames.forEach((propertyName) => {\n      object = object[propertyName]\n    })\n\n    const original = object[methodName]\n\n    object[methodName] = function() {\n      const started = now()\n      const result = original.apply(object, arguments)\n      const timing = now() - started\n      this.record(methodPath, timing)\n      return result\n    }.bind(this)\n  }\n\n  record(methodPath, timing) {\n    const data = this.data[methodPath]\n    data.calls += 1\n    data.total += timing\n    data.mean = data.total / data.calls\n    if (timing > data.max) {\n      data.max = timing\n    }\n    data.last = timing\n    return this.render()\n  }\n\n  round(ms) {\n    return Math.round(ms * 1000) / 1000\n  }\n}\n\nTrix.Inspector.registerView(PerformanceView)\n"
  },
  {
    "path": "src/inspector/views/render_view.js",
    "content": "import View from \"inspector/view\"\n\nexport default class RenderView extends View {\n  static title = \"Renders\"\n  static template = \"render\"\n  static events = {\n    \"trix-render\": function() {\n      this.renderCount++\n      return this.render()\n    },\n\n    \"trix-sync\": function() {\n      this.syncCount++\n      return this.render()\n    },\n  }\n\n  constructor() {\n    super(...arguments)\n    this.renderCount = 0\n    this.syncCount = 0\n  }\n\n  getTitle() {\n    return `${this.title} (${this.renderCount})`\n  }\n}\n\nTrix.Inspector.registerView(RenderView)\n"
  },
  {
    "path": "src/inspector/views/selection_view.js",
    "content": "import View from \"inspector/view\"\nimport UTF16String from \"trix/core/utilities/utf16_string\"\n\nclass SelectionView extends View {\n  static title = \"Selection\"\n  static template = \"selection\"\n  static events = {\n    \"trix-selection-change\": function() {\n      return this.render()\n    },\n    \"trix-render\": function() {\n      return this.render()\n    },\n  }\n\n  render() {\n    this.document = this.editor.getDocument()\n    this.range = this.editor.getSelectedRange()\n    this.locationRange = this.composition.getLocationRange()\n    this.characters = this.getCharacters()\n    return super.render(...arguments)\n  }\n\n  getCharacters() {\n    const chars = []\n    const utf16string = UTF16String.box(this.document.toString())\n    const rangeIsExpanded = this.range[0] !== this.range[1]\n    let position = 0\n    while (position < utf16string.length) {\n      let string = utf16string.charAt(position).toString()\n      if (string === \"\\n\") {\n        string = \"⏎\"\n      }\n      const selected = rangeIsExpanded && position >= this.range[0] && position < this.range[1]\n      chars.push({ string, selected })\n      position++\n    }\n    return chars\n  }\n\n  getTitle() {\n    return `${this.title} (${this.range.join()})`\n  }\n}\n\nTrix.Inspector.registerView(SelectionView)\n"
  },
  {
    "path": "src/inspector/views/undo_view.js",
    "content": "import View from \"inspector/view\"\n\nclass UndoView extends View {\n  static title = \"Undo\"\n  static template = \"undo\"\n  static events = {\n    \"trix-change\": function() {\n      return this.render()\n    },\n  }\n\n  render() {\n    this.undoEntries = this.editor.undoManager.undoEntries\n    this.redoEntries = this.editor.undoManager.redoEntries\n    return super.render(...arguments)\n  }\n}\n\nTrix.Inspector.registerView(UndoView)\n"
  },
  {
    "path": "src/inspector/watchdog/deserializer.js",
    "content": "export default class Deserializer {\n  constructor(document, snapshot) {\n    this.document = document\n    this.snapshot = snapshot\n    this.tree = this.snapshot.tree\n    this.selection = this.snapshot.selection\n    this.deserializeTree()\n    this.deserializeSelection()\n  }\n\n  deserializeTree() {\n    this.nodes = {}\n    this.element = this.deserializeNode(this.tree)\n  }\n\n  deserializeNode(serializedNode) {\n    let node\n    switch (serializedNode.name) {\n      case \"#text\":\n        node = this.deserializeTextNode(serializedNode)\n        break\n      case \"#comment\":\n        node = this.deserializeComment(serializedNode)\n        break\n      default:\n        node = this.deserializeElement(serializedNode)\n        break\n    }\n\n    this.nodes[serializedNode.id] = node\n    return node\n  }\n\n  deserializeTextNode({ value }) {\n    return this.document.createTextNode(value)\n  }\n\n  deserializeComment({ value }) {\n    return this.document.createComment(value)\n  }\n\n  deserializeChildren(serializedNode) {\n    const children = serializedNode.children ? Array.from(serializedNode.children) : []\n    return children.map((child) => this.deserializeNode(child))\n  }\n\n  deserializeElement(serializedNode) {\n    const node = this.document.createElement(serializedNode.name)\n    const object = serializedNode.attributes ? serializedNode.attributes : {}\n    for (const name in object) {\n      const value = object[name]\n      node.setAttribute(name, value)\n    }\n    while (node.lastChild) {\n      node.removeChild(node.lastChild)\n    }\n    this.deserializeChildren(serializedNode).forEach((childNode) => {\n      node.appendChild(childNode)\n    })\n    return node\n  }\n\n  deserializeSelection() {\n    if (!this.selection) return\n\n    const { start, end } = this.selection\n    const startContainer = this.nodes[start.id]\n    const endContainer = this.nodes[end.id]\n\n    this.range = this.document.createRange()\n    this.range.setStart(startContainer, start.offset)\n    this.range.setEnd(endContainer, end.offset)\n    return this.range\n  }\n\n  getElement() {\n    return this.element\n  }\n\n  getRange() {\n    return this.range\n  }\n}\n"
  },
  {
    "path": "src/inspector/watchdog/player.js",
    "content": "import \"inspector/watchdog/recording\"\n\nexport default class Player {\n  constructor(recording) {\n    this.tick = this.tick.bind(this)\n    this.recording = recording\n    this.playing = false\n    this.index = -1\n    this.length = this.recording.getFrameCount()\n  }\n\n  play() {\n    if (this.playing) return\n    if (this.hasEnded()) {\n      this.index = -1\n    }\n    this.playing = true\n    this.delegate?.playerDidStartPlaying?.()\n    return this.tick()\n  }\n\n  tick() {\n    if (this.hasEnded()) {\n      return this.stop()\n    } else {\n      this.seek(this.index + 1)\n      const duration = this.getTimeToNextFrame()\n      this.timeout = setTimeout(this.tick, duration)\n    }\n  }\n\n  seek(index) {\n    const previousIndex = this.index\n\n    if (index < 0) {\n      this.index = 0\n    } else if (index >= this.length) {\n      this.index = this.length - 1\n    } else {\n      this.index = index\n    }\n\n    if (this.index !== previousIndex) {\n      return this.delegate?.playerDidSeekToIndex?.(index)\n    }\n  }\n\n  stop() {\n    if (!this.playing) return\n    clearTimeout(this.timeout)\n    this.timeout = null\n    this.playing = false\n    return this.delegate?.playerDidStopPlaying?.()\n  }\n\n  isPlaying() {\n    return this.playing\n  }\n\n  hasEnded() {\n    return this.index >= this.length - 1\n  }\n\n  getSnapshot() {\n    return this.recording.getSnapshotAtFrameIndex(this.index)\n  }\n\n  getEvents() {\n    return this.recording.getEventsUpToFrameIndex(this.index)\n  }\n\n  getTimeToNextFrame() {\n    const current = this.recording.getTimestampAtFrameIndex(this.index)\n    const next = this.recording.getTimestampAtFrameIndex(this.index + 1)\n    const duration = current && next ? next - current : 0\n    return Math.min(duration, 500)\n  }\n}\n"
  },
  {
    "path": "src/inspector/watchdog/player_controller.js",
    "content": "import Player from \"inspector/watchdog/player\"\nimport PlayerView from \"inspector/watchdog/player_view\"\n\nexport default class PlayerController {\n  constructor(element, recording) {\n    this.element = element\n    this.recording = recording\n    this.player = new Player(this.recording)\n    this.player.delegate = this\n\n    this.view = new PlayerView(this.element)\n    this.view.delegate = this\n\n    this.view.setLength(this.player.length)\n    this.player.seek(0)\n  }\n\n  play() {\n    return this.player.play()\n  }\n\n  stop() {\n    return this.player.stop()\n  }\n\n  playerViewDidClickPlayButton() {\n    if (this.player.isPlaying()) {\n      return this.player.stop()\n    } else {\n      return this.player.play()\n    }\n  }\n\n  playerViewDidChangeSliderValue(value) {\n    return this.player.seek(value)\n  }\n\n  playerDidSeekToIndex(index) {\n    this.view.setIndex(index)\n\n    const snapshot = this.player.getSnapshot(index)\n    if (snapshot) {\n      this.view.renderSnapshot(snapshot)\n    }\n\n    const events = this.player.getEvents(index)\n    if (events) {\n      return this.view.renderEvents(events)\n    }\n  }\n\n  playerDidStartPlaying() {\n    return this.view.playerDidStartPlaying()\n  }\n\n  playerDidStopPlaying() {\n    return this.view.playerDidStopPlaying()\n  }\n}\n"
  },
  {
    "path": "src/inspector/watchdog/player_element.js",
    "content": "import { installDefaultCSSForTagName } from \"trix/core/helpers\"\n\nimport Recording from \"inspector/watchdog/recording\"\nimport PlayerController from \"inspector/watchdog/player_controller\"\n\ninstallDefaultCSSForTagName(\"trix-watchdog-player\", `\\\n%t > div { display: -webkit-flex; display: flex; font-size: 14px; margin: 10px 0 }\n%t > div > button { width: 65px }\n%t > div > input { width: 100%; -webkit-align-self: stretch; align-self: stretch; margin: 0 20px }\n%t > div > span { display: inline-block; text-align: center; width: 110px }\\\n`)\n\nclass PlayerElement extends HTMLElement {\n  static get observedAttributes() { return [ \"src\" ] }\n\n  connectedCallback() {\n    const url = this.getAttribute(\"src\")\n    if (url) {\n      return this.fetchRecordingAtURL(url)\n    }\n  }\n\n  attributeChangedCallback(attributeName, oldValue, newValue) {\n    if (attributeName === \"src\") {\n      return this.fetchRecordingAtURL(newValue)\n    }\n  }\n\n  fetchRecordingAtURL(url) {\n    this.activeRequest?.abort()\n    this.activeRequest = new XMLHttpRequest()\n    this.activeRequest.open(\"GET\", url)\n    this.activeRequest.send()\n\n    this.activeRequest.onload = () => {\n      const json = this.activeRequest.responseText\n      this.activeRequest = null\n      const recording = Recording.fromJSON(JSON.parse(json))\n      return this.loadRecording(recording)\n    }\n  }\n\n  loadRecording(recording) {\n    this.controller = new PlayerController(this, recording)\n  }\n}\n\nwindow.customElements.define(\"trix-watchdog-player\", PlayerElement)\n"
  },
  {
    "path": "src/inspector/watchdog/player_view.js",
    "content": "import Deserializer from \"inspector/watchdog/deserializer\"\nimport View from \"../view\"\n\nconst clear = (element) => {\n  while (element.lastChild) {\n    element.removeChild(element.lastChild)\n  }\n}\n\nconst render = (element, ...contents) => {\n  clear(element)\n  contents.forEach((content) => element.appendChild(content))\n}\n\nconst select = (document, range) => {\n  if (!range) return\n  const selection = window.getSelection()\n  selection.removeAllRanges()\n  selection.addRange(range)\n}\n\nexport default class PlayerView extends View {\n  static documentClassName = \"trix-watchdog-player\"\n  static playingClassName = \"trix-watchdog-player-playing\"\n\n  constructor(element) {\n    super(...arguments)\n    this.frameDidLoadDefaultDocument = this.frameDidLoadDefaultDocument.bind(this)\n    this.frameDidLoadStylesheet = this.frameDidLoadStylesheet.bind(this)\n    this.frameDidLoseFocus = this.frameDidLoseFocus.bind(this)\n    this.didClickPlayButton = this.didClickPlayButton.bind(this)\n    this.didChangeSliderValue = this.didChangeSliderValue.bind(this)\n    this.updateFrame = this.updateFrame.bind(this)\n\n    this.element = element\n    this.frame = document.createElement(\"iframe\")\n    this.frame.style.border = \"none\"\n    this.frame.style.width = \"100%\"\n    this.frame.onload = this.frameDidLoadDefaultDocument\n    this.frame.onblur = this.frameDidLoseFocus\n\n    const controlsContainer = document.createElement(\"div\")\n\n    this.playButton = document.createElement(\"button\")\n    this.playButton.textContent = \"Play\"\n    this.playButton.onclick = this.didClickPlayButton\n\n    this.slider = document.createElement(\"input\")\n    this.slider.type = \"range\"\n    this.slider.oninput = this.didChangeSliderValue\n\n    this.indexLabel = document.createElement(\"span\")\n\n    const logContainer = document.createElement(\"div\")\n\n    this.log = document.createElement(\"textarea\")\n    this.log.setAttribute(\"readonly\", \"\")\n    this.log.rows = 4\n\n    render(controlsContainer, this.playButton, this.slider, this.indexLabel)\n    render(logContainer, this.log)\n    render(this.element, this.frame, controlsContainer, logContainer)\n    this.setIndex(0)\n  }\n\n  renderSnapshot(snapshot) {\n    if (this.body) {\n      const { element, range } = this.deserializeSnapshot(snapshot)\n      render(this.body, element)\n      select(this.document, range)\n      return this.updateFrame()\n    } else {\n      this.snapshot = snapshot\n    }\n  }\n\n  renderEvents(events) {\n    const renderedEvents = events.slice().reverse().map((event, index) => {\n      return this.renderEvent(event, index)\n    })\n    this.log.innerText = renderedEvents.join(\"\\n\")\n  }\n\n  setIndex(index) {\n    this.slider.value = index\n    this.indexLabel.textContent = `Frame ${index}`\n  }\n\n  setLength(length) {\n    this.slider.max = length - 1\n  }\n\n  playerDidStartPlaying() {\n    this.element.classList.add(this.constructor.playingClassName)\n    this.playButton.textContent = \"Pause\"\n  }\n\n  playerDidStopPlaying() {\n    this.element.classList.remove(this.constructor.playingClassName)\n    this.playButton.textContent = \"Play\"\n  }\n\n  frameDidLoadDefaultDocument() {\n    this.document = this.frame.contentDocument\n    this.document.documentElement.classList.add(this.constructor.documentClassName)\n\n    this.document.head.innerHTML = document.head.innerHTML\n\n    Array.from(this.document.head.querySelectorAll(\"link[rel=stylesheet]\")).forEach((stylesheet) => {\n      stylesheet.onload = this.frameDidLoadStylesheet\n    })\n\n    this.body = this.document.body\n    this.body.style.cssText = \"margin: 0; padding: 0\"\n    this.body.onkeydown = (event) => event.preventDefault()\n\n    if (this.snapshot) {\n      this.renderSnapshot(this.snapshot)\n      this.snapshot = null\n    }\n  }\n\n  frameDidLoadStylesheet() {\n    return this.updateFrame()\n  }\n\n  frameDidLoseFocus() {\n    if (this.element.classList.contains(this.constructor.playingClassName)) {\n      return requestAnimationFrame(this.updateFrame)\n    }\n  }\n\n  didClickPlayButton() {\n    return this.delegate?.playerViewDidClickPlayButton?.()\n  }\n\n  didChangeSliderValue() {\n    const value = parseInt(this.slider.value, 10)\n    return this.delegate?.playerViewDidChangeSliderValue?.(value)\n  }\n\n  renderEvent(event, index) {\n    let description, key\n\n    switch (event.type) {\n      case \"input\":\n        description = \"Browser input event received\"\n        break\n      case \"keypress\":\n        key = event.character || event.charCode || event.keyCode\n        description = `Key pressed: ${JSON.stringify(key)}`\n        break\n      case \"log\":\n        description = event.message\n        break\n      case \"snapshot\":\n        description = \"DOM update\"\n    }\n\n    return `[${index}] ${description}`\n  }\n\n  deserializeSnapshot(snapshot) {\n    const deserializer = new Deserializer(this.document, snapshot)\n    return {\n      element: deserializer.getElement(),\n      range: deserializer.getRange(),\n    }\n  }\n\n  updateFrame() {\n    this.frame.style.height = 0\n    this.frame.style.height = this.body.scrollHeight + \"px\"\n    this.frame.focus()\n    return this.frame.contentWindow.focus()\n  }\n}\n"
  },
  {
    "path": "src/inspector/watchdog/recorder.js",
    "content": "import Recording from \"inspector/watchdog/recording\"\nimport Serializer from \"inspector/watchdog/serializer\"\n\nexport default class Recorder {\n  constructor(element, { snapshotLimit } = {}) {\n    this.recordSnapshotDuringNextAnimationFrame = this.recordSnapshotDuringNextAnimationFrame.bind(this)\n    this.handleEvent = this.handleEvent.bind(this)\n    this.element = element\n    this.snapshotLimit = snapshotLimit\n    this.recording = new Recording()\n  }\n\n  start() {\n    if (this.started) return\n\n    this.installMutationObserver()\n    this.installEventListeners()\n    this.recordSnapshot()\n    this.started = true\n  }\n\n  stop() {\n    if (!this.started) return\n\n    this.uninstallMutationObserver()\n    this.uninstallEventListeners()\n    this.started = false\n  }\n\n  log(message) {\n    return this.recording.recordEvent({ type: \"log\", message })\n  }\n\n  installMutationObserver() {\n    this.mutationObserver = new MutationObserver(this.recordSnapshotDuringNextAnimationFrame)\n    return this.mutationObserver.observe(this.element, {\n      attributes: true,\n      characterData: true,\n      childList: true,\n      subtree: true,\n    })\n  }\n\n  uninstallMutationObserver() {\n    this.mutationObserver.disconnect()\n    this.mutationObserver = null\n  }\n\n  recordSnapshotDuringNextAnimationFrame() {\n    if (!this.animationFrameRequest) {\n      this.animationFrameRequest = requestAnimationFrame(() => {\n        this.animationFrameRequest = null\n        return this.recordSnapshot()\n      })\n    }\n    return this.animationFrameRequest\n  }\n\n  installEventListeners() {\n    this.element.addEventListener(\"input\", this.handleEvent, true)\n    this.element.addEventListener(\"keypress\", this.handleEvent, true)\n    return document.addEventListener(\"selectionchange\", this.handleEvent, true)\n  }\n\n  uninstallEventListeners() {\n    this.element.removeEventListener(\"input\", this.handleEvent, true)\n    this.element.removeEventListener(\"keypress\", this.handleEvent, true)\n    return document.removeEventListener(\"selectionchange\", this.handleEvent, true)\n  }\n\n  handleEvent(event) {\n    switch (event.type) {\n      case \"input\":\n        return this.recordInputEvent(event)\n      case \"keypress\":\n        return this.recordKeypressEvent(event)\n      case \"selectionchange\":\n        return this.recordSnapshotDuringNextAnimationFrame()\n    }\n  }\n\n  recordInputEvent(event) {\n    return this.recording.recordEvent({ type: \"input\" })\n  }\n\n  recordKeypressEvent(event) {\n    return this.recording.recordEvent({\n      type: \"keypress\",\n      altKey: event.altKey,\n      ctrlKey: event.ctrlKey,\n      metaKey: event.metaKey,\n      shiftKey: event.shiftKey,\n      keyCode: event.keyCode,\n      charCode: event.charCode,\n      character: characterFromKeyboardEvent(event),\n    })\n  }\n\n  recordSnapshot() {\n    this.recording.recordSnapshot(this.getSnapshot())\n    if (this.snapshotLimit != null) {\n      return this.recording.truncateToSnapshotCount(this.snapshotLimit)\n    }\n  }\n\n  getSnapshot() {\n    const serializer = new Serializer(this.element)\n    return serializer.getSnapshot()\n  }\n}\n\nconst characterFromKeyboardEvent = function(event) {\n  if (event.which === null) {\n    return String.fromCharCode(event.keyCode)\n  } else if (event.which !== 0 && event.charCode !== 0) {\n    return String.fromCharCode(event.charCode)\n  }\n}\n"
  },
  {
    "path": "src/inspector/watchdog/recording.js",
    "content": "export default class Recording {\n  static fromJSON({ snapshots, frames }) {\n    return new this(snapshots, frames)\n  }\n\n  constructor(snapshots = [], frames = []) {\n    this.snapshots = snapshots\n    this.frames = frames\n  }\n\n  recordSnapshot(snapshot) {\n    const snapshotJSON = JSON.stringify(snapshot)\n    if (snapshotJSON !== this.lastSnapshotJSON) {\n      this.lastSnapshotJSON = snapshotJSON\n      this.snapshots.push(snapshot)\n      return this.recordEvent({ type: \"snapshot\" })\n    }\n  }\n\n  recordEvent(event) {\n    const frame = [ this.getTimestamp(), this.snapshots.length - 1, event ]\n    return this.frames.push(frame)\n  }\n\n  getSnapshotAtIndex(index) {\n    if (index >= 0) {\n      return this.snapshots[index]\n    }\n  }\n\n  getSnapshotAtFrameIndex(frameIndex) {\n    const snapshotIndex = this.getSnapshotIndexAtFrameIndex(frameIndex)\n    return this.getSnapshotAtIndex(snapshotIndex)\n  }\n\n  getTimestampAtFrameIndex(index) {\n    return this.frames[index]?.[0]\n  }\n\n  getSnapshotIndexAtFrameIndex(index) {\n    return this.frames[index]?.[1]\n  }\n\n  getEventAtFrameIndex(index) {\n    return this.frames[index]?.[2]\n  }\n\n  getEventsUpToFrameIndex(index) {\n    return this.frames.slice(0, index + 1).map((frame) => frame[2])\n  }\n\n  getFrameCount() {\n    return this.frames.length\n  }\n\n  getTimestamp() {\n    return new Date().getTime()\n  }\n\n  truncateToSnapshotCount(snapshotCount) {\n    const offset = this.snapshots.length - snapshotCount\n    if (offset < 0) return\n\n    const { frames } = this\n    this.frames = frames.map(([ timestamp, index, event ]) => {\n      if (index >= offset) {\n        return [ timestamp, index - offset, event ]\n      }\n    }).filter(frame => frame)\n\n    this.snapshots = this.snapshots.slice(offset)\n  }\n\n  toJSON() {\n    return { snapshots: this.snapshots, frames: this.frames }\n  }\n}\n"
  },
  {
    "path": "src/inspector/watchdog/serializer.js",
    "content": "export default class Serializer {\n  constructor(element) {\n    this.element = element\n    this.id = 0\n    this.serializeTree()\n    this.serializeSelection()\n  }\n\n  serializeTree() {\n    this.ids = new Map()\n    this.tree = this.serializeNode(this.element)\n  }\n\n  serializeNode(node) {\n    const object = { id: ++this.id, name: node.nodeName }\n    this.ids.set(node, object.id)\n\n    switch (node.nodeType) {\n      case Node.ELEMENT_NODE:\n        this.serializeElementToObject(node, object)\n        this.serializeElementChildrenToObject(node, object)\n        break\n\n      case Node.TEXT_NODE:\n      case Node.COMMENT_NODE:\n        this.serializeNodeValueToObject(node, object)\n        break\n    }\n\n    return object\n  }\n\n  serializeElementToObject(node, object) {\n    const attributes = {}\n    let hasAttributes = false\n\n    Array.from(node.attributes).forEach(({ name }) => {\n      if (node.hasAttribute(name)) {\n        let value = node.getAttribute(name)\n        if (name === \"src\" && value.slice(0, 5) === \"data:\") {\n          value = \"data:\"\n        }\n        attributes[name] = value\n        hasAttributes = true\n      }\n    })\n\n    if (hasAttributes) {\n      object.attributes = attributes\n    }\n  }\n\n  serializeElementChildrenToObject(node, object) {\n    if (node.childNodes.length) {\n      object.children = Array.from(node.childNodes).map((childNode) => this.serializeNode(childNode))\n    }\n  }\n\n  serializeNodeValueToObject(node, object) {\n    object.value = node.nodeValue\n  }\n\n  serializeSelection() {\n    const selection = window.getSelection()\n    if (selection.rangeCount <= 0) return\n\n    const range = selection.getRangeAt(0)\n    const startId = this.ids.get(range?.startContainer)\n    const endId = this.ids.get(range?.endContainer)\n\n    if (startId && endId) {\n      this.selection = {\n        start: { id: startId, offset: range.startOffset },\n        end: { id: endId, offset: range.endOffset },\n      }\n    }\n  }\n\n  getSnapshot() {\n    return { tree: this.tree, selection: this.selection }\n  }\n}\n"
  },
  {
    "path": "src/inspector/watchdog.js",
    "content": "import \"inspector/watchdog/recorder\"\nimport \"inspector/watchdog/player_element\"\n\nTrix.Watchdog = {}\n"
  },
  {
    "path": "src/test/system/accessibility_test.js",
    "content": "import { assert, insertImageAttachment, skipIf, test, testGroup, triggerEvent } from \"test/test_helper\"\nimport { delay } from \"test/test_helpers/timing_helpers\"\nimport TrixEditorElement from \"trix/elements/trix_editor_element\"\n\ntestGroup(\"Accessibility attributes\", { template: \"editor_default_aria_label\" }, () => {\n  test(\"sets the role to textbox\", () => {\n    const editor = document.getElementById(\"editor-without-labels\")\n    assert.equal(editor.getAttribute(\"role\"), \"textbox\")\n  })\n\n  test(\"reads img[alt] from Attachment attributes\", async () => {\n    const element = getEditorElement()\n    element.addEventListener(\"trix-attachment-add\", (event) => event.attachment.setAttributes({ alt: \"some alt text\" }))\n\n    insertImageAttachment()\n    await delay(20)\n\n    const image = element.querySelector(\"img\")\n    assert.equal(\"some alt text\", image.getAttribute(\"alt\"), \"sets [alt] from Attachment attribute\")\n  })\n\n  skipIf(TrixEditorElement.formAssociated, \"does not set aria-label when the element has no <label> elements\", () => {\n    const editor = document.getElementById(\"editor-without-labels\")\n    assert.equal(editor.hasAttribute(\"aria-label\"), false)\n  })\n\n  skipIf(TrixEditorElement.formAssociated, \"does not override aria-label when the element declares it\", () => {\n    const editor = document.getElementById(\"editor-with-aria-label\")\n    assert.equal(editor.getAttribute(\"aria-label\"), \"ARIA Label text\")\n  })\n\n  skipIf(TrixEditorElement.formAssociated, \"does not set aria-label when the element declares aria-labelledby\", () => {\n    const editor = document.getElementById(\"editor-with-aria-labelledby\")\n    assert.equal(editor.hasAttribute(\"aria-label\"), false)\n    assert.equal(editor.getAttribute(\"aria-labelledby\"), \"aria-labelledby-id\")\n  })\n\n  skipIf(TrixEditorElement.formAssociated, \"assigns aria-label to the text of the element's <label> elements\", () => {\n    const editor = document.getElementById(\"editor-with-labels\")\n    assert.equal(editor.getAttribute(\"aria-label\"), \"Label 1 Label 2 Label 3\")\n  })\n\n  skipIf(TrixEditorElement.formAssociated, \"updates the aria-label on focus\", () => {\n    const editor = document.getElementById(\"editor-with-modified-label\")\n    const label = document.getElementById(\"modified-label\")\n\n    label.innerHTML = \"<span>New Value</span>\"\n    triggerEvent(editor, \"focus\")\n    assert.equal(editor.getAttribute(\"aria-label\"), \"New Value\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/attachment_caption_test.js",
    "content": "import * as config from \"trix/config\"\nimport { assert, insertImageAttachment, test, testGroup } from \"test/test_helper\"\n\ntestGroup(\"Attachment captions\", { template: \"editor_empty\" }, () => {\n  test(\"default caption includes file name and size\", () => {\n    insertImageAttachment()\n    const element = getCaptionElement()\n    assert.notOk(element.hasAttribute(\"data-trix-placeholder\"))\n    assert.equal(element.textContent, \"image.gif 35 Bytes\")\n  })\n\n  test(\"caption excludes file name when configured\", () => {\n    withPreviewCaptionConfig({ name: false, size: true }, () => {\n      insertImageAttachment()\n      const element = getCaptionElement()\n      assert.notOk(element.hasAttribute(\"data-trix-placeholder\"))\n      assert.equal(element.textContent, \"35 Bytes\")\n    })\n  })\n\n  test(\"caption excludes file size when configured\", () => {\n    withPreviewCaptionConfig({ name: true, size: false }, () => {\n      insertImageAttachment()\n      const element = getCaptionElement()\n      assert.notOk(element.hasAttribute(\"data-trix-placeholder\"))\n      assert.equal(element.textContent, \"image.gif\")\n    })\n  })\n\n  test(\"caption is empty when configured\", () => {\n    withPreviewCaptionConfig({ name: false, size: false }, () => {\n      insertImageAttachment()\n      const element = getCaptionElement()\n      assert.ok(element.hasAttribute(\"data-trix-placeholder\"))\n      assert.equal(element.getAttribute(\"data-trix-placeholder\"), config.lang.captionPlaceholder)\n      assert.equal(element.textContent, \"\")\n    })\n  })\n})\n\nconst withPreviewCaptionConfig = (captionConfig, fn) => {\n  if (!captionConfig) captionConfig = {}\n  const { caption } = config.attachments.preview\n  config.attachments.preview.caption = captionConfig\n  try {\n    return fn()\n  } finally {\n    config.attachments.preview.caption = caption\n  }\n}\n\nconst getCaptionElement = () => getEditorElement().querySelector(\"figcaption\")\n"
  },
  {
    "path": "src/test/system/attachment_gallery_test.js",
    "content": "import {\n  assert,\n  clickToolbarButton,\n  createImageAttachment,\n  expectDocument,\n  insertAttachments,\n  pressKey,\n  test,\n  testGroup,\n  typeCharacters,\n} from \"test/test_helper\"\nimport { OBJECT_REPLACEMENT_CHARACTER } from \"trix/constants\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\nconst ORC = OBJECT_REPLACEMENT_CHARACTER\n\ntestGroup(\"Attachment galleries\", { template: \"editor_empty\" }, () => {\n  test(\"inserting more than one image attachment creates a gallery block\", () => {\n    insertAttachments(createImageAttachments(2))\n    assert.blockAttributes([ 0, 2 ], [ \"attachmentGallery\" ])\n    expectDocument(`${ORC}${ORC}\\n`)\n  })\n\n  test(\"gallery formatting is removed from blocks containing less than two image attachments\", async () => {\n    insertAttachments(createImageAttachments(2))\n    assert.blockAttributes([ 0, 2 ], [ \"attachmentGallery\" ])\n    getEditor().setSelectedRange([ 1, 2 ])\n\n    await pressKey(\"backspace\")\n    await nextFrame()\n\n    assert.blockAttributes([ 0, 2 ], [])\n    expectDocument(`${ORC}\\n`)\n  })\n\n  test(\"typing in an attachment gallery block splits it\", async () => {\n    insertAttachments(createImageAttachments(4))\n    getEditor().setSelectedRange(2)\n\n    await typeCharacters(\"a\")\n    await nextFrame()\n\n    assert.blockAttributes([ 0, 2 ], [ \"attachmentGallery\" ])\n    assert.blockAttributes([ 3, 4 ], [])\n    assert.blockAttributes([ 5, 7 ], [ \"attachmentGallery\" ])\n    expectDocument(`${ORC}${ORC}\\na\\n${ORC}${ORC}\\n`)\n  })\n\n  test(\"inserting a gallery in a formatted block\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    await typeCharacters(\"abc\")\n\n    insertAttachments(createImageAttachments(2))\n    await nextFrame()\n\n    assert.blockAttributes([ 0, 3 ], [ \"quote\" ])\n    assert.blockAttributes([ 4, 6 ], [ \"attachmentGallery\" ])\n    expectDocument(`abc\\n${ORC}${ORC}\\n`)\n  })\n})\n\nconst createImageAttachments = function (num) {\n  if (num == null) {\n    num = 1\n  }\n  const attachments = []\n  while (attachments.length < num) {\n    attachments.push(createImageAttachment())\n  }\n  return attachments\n}\n"
  },
  {
    "path": "src/test/system/attachment_test.js",
    "content": "import * as config from \"trix/config\"\nimport { OBJECT_REPLACEMENT_CHARACTER } from \"trix/constants\"\n\nimport {\n  assert,\n  clickElement,\n  clickToolbarButton,\n  createFile,\n  dragToCoordinates,\n  expectDocument,\n  moveCursor,\n  pressKey,\n  test,\n  testGroup,\n  triggerEvent,\n  typeCharacters,\n} from \"test/test_helper\"\nimport { delay, nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"Attachments\", { template: \"editor_with_image\" }, () => {\n  test(\"moving an image by drag and drop\", async () => {\n    await typeCharacters(\"!\")\n\n    const coordinates = await moveCursor({ direction: \"right\", times: 1 })\n    const img = document.activeElement.querySelector(\"img\")\n    triggerEvent(img, \"mousedown\")\n\n    await nextFrame()\n    await dragToCoordinates(coordinates)\n\n    expectDocument(`!a${OBJECT_REPLACEMENT_CHARACTER}b\\n`)\n  })\n\n  test(\"removing an image\", async () => {\n    await delay(20)\n    await clickElement(getFigure())\n\n    const closeButton = getFigure().querySelector(\"[data-trix-action=remove]\")\n    await clickElement(closeButton)\n\n    expectDocument(\"ab\\n\")\n  })\n\n  test(\"intercepting attachment toolbar creation\", async () => {\n    function insertToolbarButton(event) {\n      const { toolbar, attachment } = event\n\n      assert.ok(toolbar.matches(\".attachment__toolbar\"))\n      assert.equal(attachment.getContentType(), \"image\")\n\n      toolbar.querySelector(\".trix-button-group\").insertAdjacentHTML(\"beforeend\", \"<button class=\\\"new-button\\\">👍</button>\")\n    }\n\n    getEditorElement().addEventListener(\"trix-attachment-before-toolbar\", insertToolbarButton, { once: true })\n\n    await clickElement(getFigure())\n\n    assert.ok(getEditorElement().querySelector(\".trix-button-group .new-button\"))\n  })\n\n  test(\"editing an image caption\", async () => {\n    await delay(20)\n\n    await clickElement(findElement(\"figure\"))\n    await clickElement(findElement(\"figcaption\"))\n\n    await nextFrame()\n\n    const textarea = findElement(\"textarea\")\n    assert.ok(textarea)\n\n    textarea.focus()\n    textarea.value = \"my\"\n    triggerEvent(textarea, \"input\")\n\n    await nextFrame()\n    textarea.value = \"\"\n\n    await nextFrame()\n    textarea.value = \"my caption\"\n    triggerEvent(textarea, \"input\")\n\n    await pressKey(\"return\")\n    assert.notOk(findElement(\"textarea\"))\n    assert.textAttributes([ 2, 3 ], { caption: \"my caption\" })\n    assert.locationRange({ index: 0, offset: 3 })\n    expectDocument(`ab${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n  })\n\n  test(\"editing an attachment caption with no filename\", async () => {\n    await delay(20)\n\n    let captionElement = findElement(\"figcaption\")\n    assert.ok(captionElement.clientHeight > 0)\n    assert.equal(captionElement.getAttribute(\"data-trix-placeholder\"), config.lang.captionPlaceholder)\n\n    await clickElement(findElement(\"figure\"))\n\n    captionElement = findElement(\"figcaption\")\n    assert.ok(captionElement.clientHeight > 0)\n    assert.equal(captionElement.getAttribute(\"data-trix-placeholder\"), config.lang.captionPlaceholder)\n  })\n\n  test(\"updating an attachment's href attribute while editing its caption\", async () => {\n    const attachment = getEditorController().attachmentManager.getAttachments()[0]\n\n    await delay(20)\n\n    await clickElement(findElement(\"figure\"))\n    await clickElement(findElement(\"figcaption\"))\n\n    await nextFrame()\n\n    let textarea = findElement(\"textarea\")\n    assert.ok(textarea)\n    textarea.focus()\n    textarea.value = \"my caption\"\n    triggerEvent(textarea, \"input\")\n    attachment.setAttributes({ href: \"https://example.com\" })\n\n    await nextFrame()\n\n    textarea = findElement(\"textarea\")\n    assert.ok(document.activeElement === textarea)\n    assert.equal(textarea.value, \"my caption\")\n\n    await pressKey(\"return\")\n\n    assert.notOk(findElement(\"textarea\"))\n    assert.textAttributes([ 2, 3 ], { caption: \"my caption\" })\n    assert.locationRange({ index: 0, offset: 3 })\n\n    expectDocument(`ab${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n  })\n\n  testGroup(\"File insertion\", { template: \"editor_empty\" }, () => {\n    test(\"inserting a file in a formatted block\", async () => {\n      await clickToolbarButton({ attribute: \"bullet\" })\n      await clickToolbarButton({ attribute: \"bold\" })\n\n      getComposition().insertFile(createFile())\n\n      assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n      assert.textAttributes([ 0, 1 ], {})\n      expectDocument(`${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n    })\n\n    test(\"inserting a files in a formatted block\", async () => {\n      await clickToolbarButton({ attribute: \"quote\" })\n      await clickToolbarButton({ attribute: \"italic\" })\n\n      getComposition().insertFiles([ createFile(), createFile() ])\n\n      assert.blockAttributes([ 0, 2 ], [ \"quote\" ])\n      assert.textAttributes([ 0, 1 ], {})\n      assert.textAttributes([ 1, 2 ], {})\n      expectDocument(`${OBJECT_REPLACEMENT_CHARACTER}${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n    })\n\n    test(\"setting previewURL makes an Attachment previewable\", async () => {\n      getEditorElement().addEventListener(\"trix-attachment-add\", async ({ attachment }) => {\n        attachment.setAttribute(\"previewable\", true)\n        attachment.setPreviewURL(\"/loading.png\")\n\n        await delay(50)\n\n        attachment.setPreviewURL(\"/loaded.png\")\n      }, { once: true })\n\n      getComposition().insertFile(createFile())\n\n      const loadingImg = getEditorElement().querySelector(\"img\")\n\n      assert.ok(/loading.png$/.test(loadingImg.src), \"sets previewURL to loading image\")\n\n      await delay(50)\n\n      const loadedImg = getEditorElement().querySelector(\"img\")\n\n      assert.ok(/loaded.png$/.test(loadedImg.src), \"sets previewURL to loaded image\")\n    })\n  })\n})\n\nconst getFigure = () => findElement(\"figure\")\n\nconst findElement = (selector) => getEditorElement().querySelector(selector)\n"
  },
  {
    "path": "src/test/system/basic_input_test.js",
    "content": "import * as config from \"trix/config\"\nimport {\n  assert,\n  dragToCoordinates,\n  expandSelection,\n  expectDocument,\n  insertNode,\n  moveCursor,\n  pressKey,\n  selectAll,\n  test,\n  testGroup,\n  testIf,\n  triggerEvent,\n  typeCharacters,\n} from \"test/test_helper\"\n\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"Basic input\", { template: \"editor_empty\" }, () => {\n  test(\"typing\", async () => {\n    await typeCharacters(\"abc\")\n    expectDocument(\"abc\\n\")\n  })\n\n  test(\"backspacing\", async () => {\n    await typeCharacters(\"abc\\b\")\n    expectDocument(\"ab\\n\")\n  })\n\n  test(\"pressing delete\", async () => {\n    await typeCharacters(\"ab\")\n    await moveCursor(\"left\")\n    await pressKey(\"delete\")\n    expectDocument(\"a\\n\")\n  })\n\n  test(\"pressing return\", async () => {\n    await typeCharacters(\"ab\")\n    await pressKey(\"return\")\n    await typeCharacters(\"c\")\n\n    expectDocument(\"ab\\nc\\n\")\n  })\n\n  test(\"pressing escape in Safari\", async () => {\n    await typeCharacters(\"a\")\n\n    if (triggerEvent(document.activeElement, \"keydown\", { charCode: 0, keyCode: 27, which: 27, key: \"Escape\", code: \"Escape\" })) {\n      triggerEvent(document.activeElement, \"keypress\", { charCode: 27, keyCode: 27, which: 27, key: \"Escape\", code: \"Escape\" })\n    }\n\n    await nextFrame()\n    expectDocument(\"a\\n\")\n  })\n\n  test(\"pressing escape in Firefox\", async () => {\n    await typeCharacters(\"a\")\n    if (triggerEvent(document.activeElement, \"keydown\", { charCode: 0, keyCode: 27, which: 27, key: \"Escape\", code: \"Escape\" })) {\n      triggerEvent(document.activeElement, \"keypress\", { charCode: 0, keyCode: 27, which: 0, key: \"Escape\", code: \"Escape\" })\n    }\n    await nextFrame()\n    expectDocument(\"a\\n\")\n  })\n\n  test(\"pressing escape in Chrome\", async () => {\n    await typeCharacters(\"a\")\n    triggerEvent(document.activeElement, \"keydown\", {\n      charCode: 0,\n      keyCode: 27,\n      which: 27,\n      key: \"Escape\",\n      code: \"Escape\",\n    })\n    await nextFrame()\n    expectDocument(\"a\\n\")\n  })\n\n  test(\"cursor left\", async () => {\n    await typeCharacters(\"ac\")\n    await moveCursor(\"left\")\n    await typeCharacters(\"b\")\n\n    expectDocument(\"abc\\n\")\n  })\n\n  test(\"replace entire document\", async () => {\n    await typeCharacters(\"abc\")\n    await selectAll()\n    await typeCharacters(\"d\")\n\n    expectDocument(\"d\\n\")\n  })\n\n  test(\"remove entire document\", async () => {\n    await typeCharacters(\"abc\")\n    await selectAll()\n    await typeCharacters(\"\\b\")\n\n    expectDocument(\"\\n\")\n  })\n\n  test(\"drag text\", async () => {\n    await typeCharacters(\"abc\")\n    const coordinates = await moveCursor({ direction: \"left\", times: 2 })\n    await nextFrame()\n\n    await moveCursor(\"right\")\n    await expandSelection(\"right\")\n    await dragToCoordinates(coordinates)\n\n    await expectDocument(\"acb\\n\")\n  })\n\n  testIf(config.input.getLevel() === 0, \"inserting newline after cursor (control + o)\", async () => {\n    await typeCharacters(\"ab\")\n    await moveCursor(\"left\")\n\n    triggerEvent(document.activeElement, \"keydown\", { charCode: 0, keyCode: 79, which: 79, ctrlKey: true })\n    await nextFrame()\n\n    assert.locationRange({ index: 0, offset: 1 })\n    expectDocument(\"a\\nb\\n\")\n   })\n\n  testIf(config.input.getLevel() === 0, \"inserting ó with control + alt + o (AltGr)\", async () => {\n    await typeCharacters(\"ab\")\n    await moveCursor(\"left\")\n\n    if (triggerEvent(document.activeElement, \"keydown\", { charCode: 0, keyCode: 79, which: 79, altKey: true, ctrlKey: true })) {\n      triggerEvent(document.activeElement, \"keypress\", { charCode: 243, keyCode: 243, which: 243, altKey: true, ctrlKey: true })\n      insertNode(document.createTextNode(\"ó\"))\n    }\n\n    await nextFrame()\n    assert.locationRange({ index: 0, offset: 2 })\n    expectDocument(\"aób\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/block_formatting_test.js",
    "content": "import Text from \"trix/models/text\"\nimport Block from \"trix/models/block\"\nimport Document from \"trix/models/document\"\n\nimport {\n  assert,\n  clickToolbarButton,\n  expandSelection,\n  expectDocument,\n  isToolbarButtonActive,\n  isToolbarButtonDisabled,\n  moveCursor,\n  pressKey,\n  replaceDocument,\n  selectAll,\n  test,\n  testGroup,\n  typeCharacters,\n} from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"Block formatting\", { template: \"editor_empty\" }, () => {\n  test(\"applying block attributes\", async () => {\n    await typeCharacters(\"abc\")\n    await clickToolbarButton({ attribute: \"quote\" })\n\n    assert.blockAttributes([ 0, 4 ], [ \"quote\" ])\n    assert.ok(isToolbarButtonActive({ attribute: \"quote\" }))\n\n    await clickToolbarButton({ attribute: \"code\" })\n    assert.blockAttributes([ 0, 4 ], [ \"quote\", \"code\" ])\n    assert.ok(isToolbarButtonActive({ attribute: \"code\" }))\n\n    await clickToolbarButton({ attribute: \"code\" })\n    assert.blockAttributes([ 0, 4 ], [ \"quote\" ])\n    assert.notOk(isToolbarButtonActive({ attribute: \"code\" }))\n    assert.ok(isToolbarButtonActive({ attribute: \"quote\" }))\n  })\n\n  test(\"applying block attributes to text after newline\", async () => {\n    await typeCharacters(\"a\\nbc\")\n    await clickToolbarButton({ attribute: \"quote\" })\n\n    assert.blockAttributes([ 0, 2 ], [])\n    assert.blockAttributes([ 2, 4 ], [ \"quote\" ])\n  })\n\n  test(\"applying block attributes to text between newlines\", async () => {\n    await typeCharacters(\"ab\\ndef\\nghi\\nj\\n\")\n    await moveCursor({ direction: \"left\", times: 2 })\n    await expandSelection({ direction: \"left\", times: 5 })\n    await clickToolbarButton({ attribute: \"quote\" })\n\n    assert.blockAttributes([ 0, 3 ], [])\n    assert.blockAttributes([ 3, 11 ], [ \"quote\" ])\n    assert.blockAttributes([ 11, 13 ], [])\n  })\n\n  test(\"applying bullets to text with newlines\", async () => {\n    await typeCharacters(\"abc\\ndef\\nghi\\njkl\\nmno\\n\")\n    await moveCursor({ direction: \"left\", times: 2 })\n    await expandSelection({ direction: \"left\", times: 15 })\n    await clickToolbarButton({ attribute: \"bullet\" })\n\n    assert.blockAttributes([ 0, 4 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 4, 8 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 8, 12 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 12, 16 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 16, 20 ], [ \"bulletList\", \"bullet\" ])\n  })\n\n  test(\"applying block attributes to adjacent unformatted blocks consolidates them\", async () => {\n    const document = new Document([\n      new Block(Text.textForStringWithAttributes(\"1\"), [ \"bulletList\", \"bullet\" ]),\n      new Block(Text.textForStringWithAttributes(\"a\"), []),\n      new Block(Text.textForStringWithAttributes(\"b\"), []),\n      new Block(Text.textForStringWithAttributes(\"c\"), []),\n      new Block(Text.textForStringWithAttributes(\"2\"), [ \"bulletList\", \"bullet\" ]),\n      new Block(Text.textForStringWithAttributes(\"3\"), [ \"bulletList\", \"bullet\" ]),\n    ])\n\n    replaceDocument(document)\n    getEditorController().setLocationRange([\n      { index: 0, offset: 0 },\n      { index: 5, offset: 1 },\n    ])\n\n    await nextFrame()\n    await clickToolbarButton({ attribute: \"quote\" })\n\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\", \"quote\" ])\n    assert.blockAttributes([ 2, 8 ], [ \"quote\" ])\n    assert.blockAttributes([ 8, 10 ], [ \"bulletList\", \"bullet\", \"quote\" ])\n    assert.blockAttributes([ 10, 12 ], [ \"bulletList\", \"bullet\", \"quote\" ])\n  })\n\n  test(\"breaking out of the end of a block\", async () => {\n    await typeCharacters(\"abc\")\n    await clickToolbarButton({ attribute: \"quote\" })\n    await typeCharacters(\"\\n\\n\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ])\n    assert.equal(block.toString(), \"abc\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [])\n    assert.equal(block.toString(), \"\\n\")\n\n    assert.locationRange({ index: 1, offset: 0 })\n  })\n\n  test(\"breaking out of the middle of a block before character\", async () => {\n    // * = cursor\n    //\n    // ab\n    // *c\n    //\n    await typeCharacters(\"abc\")\n    await clickToolbarButton({ attribute: \"quote\" })\n    await moveCursor(\"left\")\n    await typeCharacters(\"\\n\\n\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ])\n    assert.equal(block.toString(), \"ab\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [])\n    assert.equal(block.toString(), \"\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ])\n    assert.equal(block.toString(), \"c\\n\")\n\n    assert.locationRange({ index: 2, offset: 0 })\n  })\n\n  test(\"breaking out of the middle of a block before newline\", async () => {\n    // * = cursor\n    //\n    // ab\n    // *\n    // c\n    //\n    await typeCharacters(\"abc\")\n    await clickToolbarButton({ attribute: \"quote\" })\n    await moveCursor(\"left\")\n    await typeCharacters(\"\\n\")\n    await moveCursor(\"left\")\n    await typeCharacters(\"\\n\\n\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ])\n    assert.equal(block.toString(), \"ab\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [])\n    assert.equal(block.toString(), \"\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ])\n    assert.equal(block.toString(), \"c\\n\")\n  })\n\n  test(\"breaking out of a formatted block with adjacent non-formatted blocks\", async () => {\n    // * = cursor\n    //\n    // a\n    // b*\n    // c\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"a\"), []),\n      new Block(Text.textForStringWithAttributes(\"b\"), [ \"quote\" ]),\n      new Block(Text.textForStringWithAttributes(\"c\"), []),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(3)\n\n    await typeCharacters(\"\\n\\n\")\n\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 4)\n    assert.blockAttributes([ 0, 1 ], [])\n    assert.blockAttributes([ 2, 3 ], [ \"quote\" ])\n    assert.blockAttributes([ 4, 5 ], [])\n    assert.blockAttributes([ 5, 6 ], [])\n    expectDocument(\"a\\nb\\n\\nc\\n\")\n  })\n\n  test(\"breaking out a block after newline at offset 0\", async () => {\n    // * = cursor\n    //\n    //\n    // *a\n    //\n    await typeCharacters(\"a\")\n    await clickToolbarButton({ attribute: \"quote\" })\n    await moveCursor(\"left\")\n    await typeCharacters(\"\\n\\n\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [])\n    assert.equal(block.toString(), \"\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ])\n    assert.equal(block.toString(), \"a\\n\")\n    assert.locationRange({ index: 1, offset: 0 })\n  })\n\n  test(\"deleting the only non-block-break character in a block\", async () => {\n    await typeCharacters(\"ab\")\n    await clickToolbarButton({ attribute: \"quote\" })\n    await typeCharacters(\"\\b\\b\")\n    assert.blockAttributes([ 0, 1 ], [ \"quote\" ])\n  })\n\n  test(\"backspacing a quote\", async () => {\n    await nextFrame()\n    await clickToolbarButton({ attribute: \"quote\" })\n    assert.blockAttributes([ 0, 1 ], [ \"quote\" ])\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 0, 1 ], [])\n  })\n\n  test(\"backspacing a nested quote\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    assert.blockAttributes([ 0, 1 ], [ \"quote\", \"quote\" ])\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 0, 1 ], [ \"quote\" ])\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 0, 1 ], [])\n  })\n\n  test(\"backspacing a list item\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 0, 0 ], [])\n  })\n\n  test(\"backspacing a nested list item\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\\n\")\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ])\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\"a\\n\\n\")\n  })\n\n  test(\"backspacing a list item inside a quote\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    await clickToolbarButton({ attribute: \"bullet\" })\n    assert.blockAttributes([ 0, 1 ], [ \"quote\", \"bulletList\", \"bullet\" ])\n\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 0, 1 ], [ \"quote\" ])\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 0, 1 ], [])\n  })\n\n  test(\"backspacing selected nested list items\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\\n\")\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    await typeCharacters(\"b\")\n    getSelectionManager().setLocationRange([\n      { index: 0, offset: 0 },\n      { index: 1, offset: 1 },\n    ])\n    await pressKey(\"backspace\")\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\"\\n\")\n  })\n\n  test(\"backspace selection spanning formatted blocks\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    await typeCharacters(\"ab\\n\\n\")\n    await clickToolbarButton({ attribute: \"code\" })\n    await typeCharacters(\"cd\")\n    getSelectionManager().setLocationRange([\n      { index: 0, offset: 1 },\n      { index: 1, offset: 1 },\n    ])\n    getComposition().deleteInDirection(\"backward\")\n    assert.blockAttributes([ 0, 2 ], [ \"quote\" ])\n    expectDocument(\"ad\\n\")\n  })\n\n  test(\"backspace selection spanning and entire formatted block and a formatted block\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    await typeCharacters(\"ab\\n\\n\")\n    await clickToolbarButton({ attribute: \"code\" })\n    await typeCharacters(\"cd\")\n    getSelectionManager().setLocationRange([\n      { index: 0, offset: 0 },\n      { index: 1, offset: 1 },\n    ])\n    getComposition().deleteInDirection(\"backward\")\n    assert.blockAttributes([ 0, 2 ], [ \"code\" ])\n    expectDocument(\"d\\n\")\n  })\n\n  test(\"increasing list level\", async () => {\n    assert.ok(isToolbarButtonDisabled({ action: \"increaseNestingLevel\" }))\n    assert.ok(isToolbarButtonDisabled({ action: \"decreaseNestingLevel\" }))\n    await clickToolbarButton({ attribute: \"bullet\" })\n    assert.ok(isToolbarButtonDisabled({ action: \"increaseNestingLevel\" }))\n    assert.notOk(isToolbarButtonDisabled({ action: \"decreaseNestingLevel\" }))\n    await typeCharacters(\"a\\n\")\n    assert.notOk(isToolbarButtonDisabled({ action: \"increaseNestingLevel\" }))\n    assert.notOk(isToolbarButtonDisabled({ action: \"decreaseNestingLevel\" }))\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    await typeCharacters(\"b\")\n    assert.ok(isToolbarButtonDisabled({ action: \"increaseNestingLevel\" }))\n    assert.notOk(isToolbarButtonDisabled({ action: \"decreaseNestingLevel\" }))\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 4 ], [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ])\n  })\n\n  test(\"changing list type\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    await clickToolbarButton({ attribute: \"number\" })\n    assert.blockAttributes([ 0, 1 ], [ \"numberList\", \"number\" ])\n  })\n\n  test(\"adding bullet to heading block\", async () => {\n    await clickToolbarButton({ attribute: \"heading1\" })\n    await clickToolbarButton({ attribute: \"bullet\" })\n\n    assert.ok(isToolbarButtonActive({ attribute: \"heading1\" }))\n    assert.blockAttributes([ 1, 2 ], [])\n  })\n\n  test(\"removing bullet from heading block\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await clickToolbarButton({ attribute: \"heading1\" })\n    assert.ok(isToolbarButtonDisabled({ attribute: \"bullet\" }))\n  })\n\n  test(\"breaking out of heading in list\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await clickToolbarButton({ attribute: \"heading1\" })\n    assert.ok(isToolbarButtonActive({ attribute: \"heading1\" }))\n    await typeCharacters(\"abc\")\n    await typeCharacters(\"\\n\")\n\n    assert.ok(isToolbarButtonActive({ attribute: \"bullet\" }))\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 4 ], [ \"bulletList\", \"bullet\", \"heading1\" ])\n    assert.blockAttributes([ 4, 5 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\"abc\\n\\n\")\n  })\n\n  test(\"breaking out of middle of heading block\", async () => {\n    await clickToolbarButton({ attribute: \"heading1\" })\n    await typeCharacters(\"abc\")\n    assert.ok(isToolbarButtonActive({ attribute: \"heading1\" }))\n    await moveCursor({ direction: \"left\", times: 1 })\n    await typeCharacters(\"\\n\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 3 ], [ \"heading1\" ])\n    assert.blockAttributes([ 3, 4 ], [ \"heading1\" ])\n    expectDocument(\"ab\\nc\\n\")\n  })\n\n  test(\"breaking out of middle of heading block with preceding blocks\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"a\"), [ \"heading1\" ]),\n      new Block(Text.textForStringWithAttributes(\"b\"), []),\n      new Block(Text.textForStringWithAttributes(\"cd\"), [ \"heading1\" ]),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(5)\n    assert.ok(isToolbarButtonActive({ attribute: \"heading1\" }))\n\n    await typeCharacters(\"\\n\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 4)\n    assert.blockAttributes([ 0, 1 ], [ \"heading1\" ])\n    assert.blockAttributes([ 2, 3 ], [])\n    assert.blockAttributes([ 4, 5 ], [ \"heading1\" ])\n    assert.blockAttributes([ 6, 7 ], [ \"heading1\" ])\n    expectDocument(\"a\\nb\\nc\\nd\\n\")\n  })\n\n  test(\"breaking out of end of heading block with preceding blocks\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"a\"), [ \"heading1\" ]),\n      new Block(Text.textForStringWithAttributes(\"b\"), []),\n      new Block(Text.textForStringWithAttributes(\"cd\"), [ \"heading1\" ]),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(6)\n    assert.ok(isToolbarButtonActive({ attribute: \"heading1\" }))\n\n    await typeCharacters(\"\\n\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 4)\n    assert.blockAttributes([ 0, 1 ], [ \"heading1\" ])\n    assert.blockAttributes([ 2, 3 ], [])\n    assert.blockAttributes([ 4, 6 ], [ \"heading1\" ])\n    assert.blockAttributes([ 7, 8 ], [])\n    expectDocument(\"a\\nb\\ncd\\n\\n\")\n\n  })\n\n  test(\"inserting newline before heading\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"\\n\"), []),\n      new Block(Text.textForStringWithAttributes(\"abc\"), [ \"heading1\" ]),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(0)\n\n    await typeCharacters(\"\\n\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [])\n    assert.equal(block.toString(), \"\\n\\n\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"heading1\" ])\n    assert.equal(block.toString(), \"abc\\n\")\n  })\n\n  test(\"inserting multiple newlines before heading\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"\\n\"), []),\n      new Block(Text.textForStringWithAttributes(\"abc\"), [ \"heading1\" ]),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(0)\n\n    await typeCharacters(\"\\n\\n\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [])\n    assert.equal(block.toString(), \"\\n\\n\\n\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"heading1\" ])\n    assert.equal(block.toString(), \"abc\\n\")\n  })\n\n  test(\"inserting multiple newlines before formatted block\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"\\n\"), []),\n      new Block(Text.textForStringWithAttributes(\"abc\"), [ \"quote\" ]),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(1)\n\n    await typeCharacters(\"\\n\\n\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 1 ], [])\n    assert.blockAttributes([ 2, 3 ], [])\n    assert.blockAttributes([ 4, 6 ], [ \"quote\" ])\n    assert.locationRange({ index: 0, offset: 3 })\n    expectDocument(\"\\n\\n\\n\\nabc\\n\")\n  })\n\n  test(\"inserting newline after heading with text in following block\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"ab\"), [ \"heading1\" ]),\n      new Block(Text.textForStringWithAttributes(\"cd\"), []),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(2)\n\n    await typeCharacters(\"\\n\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n    assert.blockAttributes([ 0, 2 ], [ \"heading1\" ])\n    assert.blockAttributes([ 3, 4 ], [])\n    assert.blockAttributes([ 5, 6 ], [])\n    expectDocument(\"ab\\n\\ncd\\n\")\n  })\n\n  test(\"backspacing a newline in an empty block with adjacent formatted blocks\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"abc\"), [ \"heading1\" ]),\n      new Block(),\n      new Block(Text.textForStringWithAttributes(\"d\"), [ \"heading1\" ]),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(4)\n\n    await pressKey(\"backspace\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 1 ], [ \"heading1\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"heading1\" ])\n    expectDocument(\"abc\\nd\\n\")\n  })\n\n  test(\"backspacing a newline at beginning of non-formatted block\", async () => {\n    let document = new Document([\n      new Block(Text.textForStringWithAttributes(\"ab\"), [ \"heading1\" ]),\n      new Block(Text.textForStringWithAttributes(\"\\ncd\"), []),\n    ])\n\n    replaceDocument(document)\n    getEditor().setSelectedRange(3)\n\n    await pressKey(\"backspace\")\n    document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 2 ], [ \"heading1\" ])\n    assert.blockAttributes([ 3, 5 ], [])\n    expectDocument(\"ab\\ncd\\n\")\n  })\n\n  test(\"inserting newline after single character header\", async () => {\n    await clickToolbarButton({ attribute: \"heading1\" })\n    await typeCharacters(\"a\")\n    await typeCharacters(\"\\n\")\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 1 ], [ \"heading1\" ])\n    expectDocument(\"a\\n\\n\")\n  })\n\n  test(\"terminal attributes are only added once\", async () => {\n    replaceDocument(\n      new Document([\n        new Block(Text.textForStringWithAttributes(\"a\"), []),\n        new Block(Text.textForStringWithAttributes(\"b\"), [ \"heading1\" ]),\n        new Block(Text.textForStringWithAttributes(\"c\"), []),\n      ])\n    )\n\n    await selectAll()\n    await clickToolbarButton({ attribute: \"heading1\" })\n    assert.equal(getDocument().getBlockCount(), 3)\n    assert.blockAttributes([ 0, 1 ], [ \"heading1\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"heading1\" ])\n    assert.blockAttributes([ 4, 5 ], [ \"heading1\" ])\n    expectDocument(\"a\\nb\\nc\\n\")\n  })\n\n  test(\"terminal attributes replace existing terminal attributes\", async () => {\n    replaceDocument(\n      new Document([\n        new Block(Text.textForStringWithAttributes(\"a\"), []),\n        new Block(Text.textForStringWithAttributes(\"b\"), [ \"heading1\" ]),\n        new Block(Text.textForStringWithAttributes(\"c\"), []),\n      ])\n    )\n\n    await selectAll()\n    await clickToolbarButton({ attribute: \"code\" })\n    assert.equal(getDocument().getBlockCount(), 3)\n    assert.blockAttributes([ 0, 1 ], [ \"code\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"code\" ])\n    assert.blockAttributes([ 4, 5 ], [ \"code\" ])\n    expectDocument(\"a\\nb\\nc\\n\")\n  })\n\n  test(\"code blocks preserve newlines\", async () => {\n    await typeCharacters(\"a\\nb\")\n    await selectAll()\n    clickToolbarButton({ attribute: \"code\" })\n    assert.equal(getDocument().getBlockCount(), 1)\n    assert.blockAttributes([ 0, 3 ], [ \"code\" ])\n    expectDocument(\"a\\nb\\n\")\n  })\n\n  test(\"code blocks are not indentable\", async () => {\n    await clickToolbarButton({ attribute: \"code\" })\n    assert.notOk(isToolbarButtonActive({ action: \"increaseNestingLevel\" }))\n  })\n\n  test(\"code blocks are terminal\", async () => {\n    await clickToolbarButton({ attribute: \"code\" })\n    assert.ok(isToolbarButtonDisabled({ attribute: \"quote\" }))\n    assert.ok(isToolbarButtonDisabled({ attribute: \"heading1\" }))\n    assert.ok(isToolbarButtonDisabled({ attribute: \"bullet\" }))\n    assert.ok(isToolbarButtonDisabled({ attribute: \"number\" }))\n    assert.notOk(isToolbarButtonDisabled({ attribute: \"code\" }))\n    assert.notOk(isToolbarButtonDisabled({ attribute: \"bold\" }))\n    assert.notOk(isToolbarButtonDisabled({ attribute: \"italic\" }))\n  })\n\n  test(\"unindenting a code block inside a bullet\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await clickToolbarButton({ attribute: \"code\" })\n    await typeCharacters(\"a\")\n    await clickToolbarButton({ action: \"decreaseNestingLevel\" })\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 1)\n    assert.blockAttributes([ 0, 1 ], [ \"code\" ])\n    expectDocument(\"a\\n\")\n  })\n\n  test(\"indenting a heading inside a bullet\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\")\n    await typeCharacters(\"\\n\")\n    await clickToolbarButton({ attribute: \"heading1\" })\n    await typeCharacters(\"b\")\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\", \"heading1\" ])\n    expectDocument(\"a\\nb\\n\")\n  })\n\n  test(\"indenting a quote inside a bullet\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await clickToolbarButton({ attribute: \"quote\" })\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 1)\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\", \"quote\", \"quote\" ])\n    expectDocument(\"\\n\")\n  })\n\n  test(\"list indentation constraints consider the list type\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\\n\\n\")\n    await clickToolbarButton({ attribute: \"number\" })\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"numberList\", \"number\" ])\n    expectDocument(\"a\\n\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/caching_test.js",
    "content": "import { assert, clickToolbarButton, moveCursor, test, testGroup, typeCharacters } from \"test/test_helper\"\n\ntestGroup(\"View caching\", { template: \"editor_empty\" }, () => {\n  test(\"reparsing and rendering identical texts\", async () => {\n    await typeCharacters(\"a\\nb\\na\")\n    await moveCursor({ direction: \"left\", times: 2 })\n    await clickToolbarButton({ attribute: \"quote\" })\n\n    const html = getEditorElement().innerHTML\n    getEditorController().reparse()\n    getEditorController().render()\n    assert.equal(getEditorElement().innerHTML, html)\n  })\n\n  test(\"reparsing and rendering identical blocks\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\\na\")\n\n    const html = getEditorElement().innerHTML\n    getEditorController().reparse()\n    getEditorController().render()\n    assert.equal(getEditorElement().innerHTML, html)\n  })\n})\n"
  },
  {
    "path": "src/test/system/canceled_input_test.js",
    "content": "import { expectDocument, pressKey, test, testGroup, typeCharacters } from \"test/test_helper\"\n\nconst testOptions = {\n  template: \"editor_empty\",\n  setup() {\n    let handler\n    addEventListener(\"keydown\", cancel, true)\n    addEventListener(\n      \"trix-before-initialize\",\n      handler = function ({ target }) {\n        removeEventListener(\"trix-before-initialize\", handler)\n        target.addEventListener(\"keydown\", cancel)\n      }\n    )\n  },\n  teardown() {\n    removeEventListener(\"keydown\", cancel, true)\n  },\n}\n\nlet cancelingInCapturingPhase = false\nlet cancelingAtTarget = false\n\nconst cancel = (event) => {\n  switch (event.eventPhase) {\n    case Event.prototype.CAPTURING_PHASE:\n      if (cancelingInCapturingPhase) {\n        event.preventDefault()\n      }\n      break\n    case Event.prototype.AT_TARGET:\n      if (cancelingAtTarget) {\n        event.preventDefault()\n      }\n      break\n  }\n}\n\ntestGroup(\"Canceled input\", testOptions, () => {\n  test(\"ignoring canceled input events in capturing phase\", async () => {\n    await typeCharacters(\"a\")\n    cancelingInCapturingPhase = true\n    await pressKey(\"backspace\")\n    await pressKey(\"return\")\n    cancelingInCapturingPhase = false\n    await typeCharacters(\"b\")\n\n    expectDocument(\"ab\\n\")\n  })\n\n  test(\"ignoring canceled input events at target\", async () => {\n    await typeCharacters(\"a\")\n    cancelingAtTarget = true\n    await pressKey(\"backspace\")\n    await pressKey(\"return\")\n    cancelingAtTarget = false\n    await typeCharacters(\"b\")\n    expectDocument(\"ab\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/composition_input_test.js",
    "content": "import * as config from \"trix/config\"\n\nimport {\n  assert,\n  clickToolbarButton,\n  endComposition,\n  expectDocument,\n  insertNode,\n  pressKey,\n  selectNode,\n  startComposition,\n  test,\n  testGroup,\n  testIf,\n  triggerEvent,\n  triggerInputEvent,\n  typeCharacters,\n  updateComposition,\n} from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"Composition input\", { template: \"editor_empty\" }, () => {\n  test(\"composing\", async () => {\n    await startComposition(\"a\")\n    await updateComposition(\"ab\")\n    await endComposition(\"abc\")\n\n    expectDocument(\"abc\\n\")\n  })\n\n  test(\"typing and composing\", async () => {\n    await typeCharacters(\"a\")\n    await startComposition(\"b\")\n    await updateComposition(\"bc\")\n    await endComposition(\"bcd\")\n    await typeCharacters(\"e\")\n\n    expectDocument(\"abcde\\n\")\n  })\n\n  test(\"composition input is serialized\", async () => {\n    await startComposition(\"´\")\n    await endComposition(\"é\")\n\n    assert.equal(getEditorElement().value, \"<div>é</div>\")\n    expectDocument(\"é\\n\")\n  })\n\n  test(\"pressing after a canceled composition\", async () => {\n    await typeCharacters(\"ab\")\n    triggerEvent(document.activeElement, \"compositionend\", { data: \"ab\" })\n    await pressKey(\"return\")\n\n    expectDocument(\"ab\\n\\n\")\n  })\n\n  test(\"composing formatted text\", async () => {\n    await typeCharacters(\"abc\")\n    await clickToolbarButton({ attribute: \"bold\" })\n    await startComposition(\"d\")\n    await updateComposition(\"de\")\n    await endComposition(\"def\")\n\n    assert.textAttributes([ 0, 3 ], {})\n    assert.textAttributes([ 3, 6 ], { bold: true })\n    expectDocument(\"abcdef\\n\")\n  })\n\n  test(\"composing away from formatted text\", async () => {\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"abc\")\n    await clickToolbarButton({ attribute: \"bold\" })\n    await startComposition(\"d\")\n    await updateComposition(\"de\")\n    await endComposition(\"def\")\n\n    assert.textAttributes([ 0, 3 ], { bold: true })\n    assert.textAttributes([ 3, 6 ], {})\n    expectDocument(\"abcdef\\n\")\n  })\n\n  test(\"composing another language using a QWERTY keyboard\", async () => {\n    const element = getEditorElement()\n    const keyCodes = { x: 120, i: 105 }\n\n    triggerEvent(element, \"keypress\", { charCode: keyCodes.x, keyCode: keyCodes.x, which: keyCodes.x })\n    await startComposition(\"x\")\n    triggerEvent(element, \"keypress\", { charCode: keyCodes.i, keyCode: keyCodes.i, which: keyCodes.i })\n    await updateComposition(\"xi\")\n    await endComposition(\"喜\")\n\n    expectDocument(\"喜\\n\")\n  })\n\n  // Simulates the sequence of events when pressing backspace through a word on Android\n  testIf(config.input.getLevel() === 0, \"backspacing through a composition\", async () => {\n    const element = getEditorElement()\n    element.editor.insertString(\"a cat\")\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionupdate\", { data: \"ca\" })\n    triggerEvent(element, \"input\")\n    await removeCharacters(-1)\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionupdate\", { data: \"c\" })\n    triggerEvent(element, \"input\")\n    triggerEvent(element, \"compositionend\", { data: \"c\" })\n    await removeCharacters(-1)\n    await pressKey(\"backspace\")\n\n    expectDocument(\"a \\n\")\n  })\n\n  // Simulates the sequence of events when pressing backspace at the end of a\n  // word and updating it on Android (running older versions of System WebView)\n  testIf(config.input.getLevel() === 0, \"updating a composition\", async () => {\n    const element = getEditorElement()\n    element.editor.insertString(\"cat\")\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionstart\", { data: \"cat\" })\n    triggerEvent(element, \"compositionupdate\", { data: \"cat\" })\n    triggerEvent(element, \"input\")\n    await removeCharacters(-1)\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionupdate\", { data: \"car\" })\n    triggerEvent(element, \"input\")\n    triggerEvent(element, \"compositionend\", { data: \"car\" })\n    await insertNode(document.createTextNode(\"r\"))\n\n    expectDocument(\"car\\n\")\n  })\n\n  // Simulates the sequence of events when typing on Android and then tapping elsewhere\n  testIf(config.input.getLevel() === 0, \"leaving a composition\", async () => {\n    const element = getEditorElement()\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionstart\", { data: \"\" })\n    triggerInputEvent(element, \"beforeinput\", { inputType: \"insertCompositionText\", data: \"c\" })\n    triggerEvent(element, \"compositionupdate\", { data: \"c\" })\n    triggerEvent(element, \"input\")\n    const node = document.createTextNode(\"c\")\n    insertNode(node)\n    selectNode(node)\n\n    await nextFrame()\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerInputEvent(element, \"beforeinput\", { inputType: \"insertCompositionText\", data: \"ca\" })\n    triggerEvent(element, \"compositionupdate\", { data: \"ca\" })\n    triggerEvent(element, \"input\")\n    node.data = \"ca\"\n\n    await nextFrame()\n    triggerEvent(element, \"compositionend\", { data: \"\" })\n\n    await nextFrame()\n    expectDocument(\"ca\\n\")\n  })\n\n  testIf(config.browser.composesExistingText, \"composition events from cursor movement are ignored\", async () => {\n    const element = getEditorElement()\n    element.editor.insertString(\"ab \")\n\n    element.editor.setSelectedRange(0)\n    triggerEvent(element, \"compositionstart\", { data: \"\" })\n    triggerEvent(element, \"compositionupdate\", { data: \"ab\" })\n\n    await nextFrame()\n    element.editor.setSelectedRange(1)\n    triggerEvent(element, \"compositionupdate\", { data: \"ab\" })\n\n    await nextFrame()\n\n    element.editor.setSelectedRange(2)\n    triggerEvent(element, \"compositionupdate\", { data: \"ab\" })\n\n    await nextFrame()\n    element.editor.setSelectedRange(3)\n    triggerEvent(element, \"compositionend\", { data: \"ab\" })\n\n    await nextFrame()\n    expectDocument(\"ab \\n\")\n  })\n\n  // Simulates compositions in Firefox where the final composition data is\n  // dispatched as both compositionupdate and compositionend.\n  testIf(config.input.getLevel() === 0, \"composition ending with same data as last update\", async () => {\n    const element = getEditorElement()\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionstart\", { data: \"\" })\n    triggerEvent(element, \"compositionupdate\", { data: \"´\" })\n    const node = document.createTextNode(\"´\")\n    insertNode(node)\n    selectNode(node)\n\n    await nextFrame()\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionupdate\", { data: \"é\" })\n    triggerEvent(element, \"input\")\n    node.data = \"é\"\n\n    await nextFrame()\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    triggerEvent(element, \"compositionupdate\", { data: \"éé\" })\n    triggerEvent(element, \"input\")\n    node.data = \"éé\"\n\n    await nextFrame()\n    triggerEvent(element, \"compositionend\", { data: \"éé\" })\n\n    await nextFrame()\n    assert.locationRange({ index: 0, offset: 2 })\n    expectDocument(\"éé\\n\")\n  })\n})\n\nconst removeCharacters = async (direction) => {\n  const selection = rangy.getSelection()\n  const range = selection.getRangeAt(0)\n  range.moveStart(\"character\", direction)\n  range.deleteContents()\n  await nextFrame()\n}\n"
  },
  {
    "path": "src/test/system/cursor_movement_test.js",
    "content": "import {\n  assert,\n  createFile,\n  expandSelection,\n  insertFile,\n  insertString,\n  moveCursor,\n  test,\n  testGroup,\n} from \"test/test_helper\"\n\ntestGroup(\"Cursor movement\", { template: \"editor_empty\" }, () => {\n  test(\"move cursor around attachment\", async () => {\n    insertFile(createFile())\n    assert.locationRange({ index: 0, offset: 1 })\n\n    await moveCursor(\"left\")\n    assert.locationRange({ index: 0, offset: 0 }, { index: 0, offset: 1 })\n\n    await moveCursor(\"left\")\n    assert.locationRange({ index: 0, offset: 0 })\n\n    await moveCursor(\"right\")\n    assert.locationRange({ index: 0, offset: 0 }, { index: 0, offset: 1 })\n\n    await moveCursor(\"right\")\n    assert.locationRange({ index: 0, offset: 1 })\n  })\n\n  test(\"move cursor around attachment and text\", async () => {\n    insertString(\"a\")\n    insertFile(createFile())\n    insertString(\"b\")\n    assert.locationRange({ index: 0, offset: 3 })\n\n    await moveCursor(\"left\")\n    assert.locationRange({ index: 0, offset: 2 })\n\n    await moveCursor(\"left\")\n    assert.locationRange({ index: 0, offset: 1 }, { index: 0, offset: 2 })\n\n    await moveCursor(\"left\")\n    assert.locationRange({ index: 0, offset: 1 })\n\n    await moveCursor(\"left\")\n    assert.locationRange({ index: 0, offset: 0 })\n  })\n\n  test(\"expand selection over attachment\", async () => {\n    insertFile(createFile())\n    assert.locationRange({ index: 0, offset: 1 })\n\n    await expandSelection(\"left\")\n    assert.locationRange({ index: 0, offset: 0 }, { index: 0, offset: 1 })\n\n    await moveCursor(\"left\")\n    assert.locationRange({ index: 0, offset: 0 })\n\n    await expandSelection(\"right\")\n    assert.locationRange({ index: 0, offset: 0 }, { index: 0, offset: 1 })\n  })\n\n  test(\"expand selection over attachment and text\", async () => {\n    insertString(\"a\")\n    insertFile(createFile())\n    insertString(\"b\")\n    assert.locationRange({ index: 0, offset: 3 })\n\n    await expandSelection(\"left\")\n    assert.locationRange({ index: 0, offset: 2 }, { index: 0, offset: 3 })\n\n    await expandSelection(\"left\")\n    assert.locationRange({ index: 0, offset: 1 }, { index: 0, offset: 3 })\n\n    await expandSelection(\"left\")\n    assert.locationRange({ index: 0, offset: 0 }, { index: 0, offset: 3 })\n  })\n})\n"
  },
  {
    "path": "src/test/system/custom_element_test.js",
    "content": "import { makeElement, rangesAreEqual } from \"trix/core/helpers\"\nimport TrixEditorElement from \"trix/elements/trix_editor_element\"\n\nimport {\n  TEST_IMAGE_URL,\n  assert,\n  clickElement,\n  clickToolbarButton,\n  createFile,\n  expectDocument,\n  insertImageAttachment,\n  moveCursor,\n  pasteContent,\n  setFixtureHTML,\n  test,\n  testGroup,\n  testIf,\n  triggerEvent,\n  typeCharacters,\n  typeInToolbarDialog,\n} from \"test/test_helper\"\nimport { delay, nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"Custom element API\", { template: \"editor_empty\" }, () => {\n  test(\"element triggers trix-initialize on first connect\", async () => {\n    const container = document.getElementById(\"trix-container\")\n    container.innerHTML = \"\"\n\n    let initializeEventCount = 0\n    const element = document.createElement(\"trix-editor\")\n    element.addEventListener(\"trix-initialize\", () => initializeEventCount++)\n\n    container.appendChild(element)\n    await nextFrame()\n\n    container.removeChild(element)\n    await nextFrame()\n\n    container.appendChild(element)\n    await delay(60)\n\n    assert.equal(initializeEventCount, 1)\n  })\n\n  test(\"files are accepted by default\", () => {\n    getComposition().insertFile(createFile())\n    assert.equal(getComposition().getAttachments().length, 1)\n  })\n\n  test(\"rejecting a file by canceling the trix-file-accept event\", () => {\n    getEditorElement().addEventListener(\"trix-file-accept\", (event) => event.preventDefault())\n    getComposition().insertFile(createFile())\n    assert.equal(getComposition().getAttachments().length, 0)\n  })\n\n  test(\"element triggers attachment events\", () => {\n    const file = createFile()\n    const element = getEditorElement()\n    const composition = getComposition()\n    let attachment = null\n    const events = []\n\n    element.addEventListener(\"trix-file-accept\", function (event) {\n      events.push(event.type)\n      assert.ok(file === event.file)\n    })\n\n    element.addEventListener(\"trix-attachment-add\", function (event) {\n      events.push(event.type)\n      attachment = event.attachment\n    })\n\n    composition.insertFile(file)\n    assert.deepEqual(events, [ \"trix-file-accept\", \"trix-attachment-add\" ])\n\n    element.addEventListener(\"trix-attachment-remove\", function (event) {\n      events.push(event.type)\n      assert.ok(attachment === event.attachment)\n    })\n\n    attachment.remove()\n    assert.deepEqual(events, [ \"trix-file-accept\", \"trix-attachment-add\", \"trix-attachment-remove\" ])\n  })\n\n  test(\"element triggers trix-change when an attachment is edited\", () => {\n    const file = createFile()\n    const element = getEditorElement()\n    const composition = getComposition()\n    let attachment = null\n    const events = []\n\n    element.addEventListener(\"trix-attachment-add\", (event) => attachment = event.attachment)\n\n    composition.insertFile(file)\n\n    element.addEventListener(\"trix-attachment-edit\", (event) => events.push(event.type))\n\n    element.addEventListener(\"trix-change\", (event) => events.push(event.type))\n\n    attachment.setAttributes({ width: 9876 })\n    assert.deepEqual(events, [ \"trix-attachment-edit\", \"trix-change\" ])\n  })\n\n  test(\"editing the document in a trix-attachment-add handler doesn't trigger trix-attachment-add again\", () => {\n    const element = getEditorElement()\n    const composition = getComposition()\n    let eventCount = 0\n\n    element.addEventListener(\"trix-attachment-add\", () => {\n      if (eventCount++ === 0) {\n        element.editor.setSelectedRange([ 0, 1 ])\n        element.editor.activateAttribute(\"bold\")\n      }\n    })\n\n    composition.insertFile(createFile())\n    assert.equal(eventCount, 1)\n  })\n\n  test(\"element triggers trix-change events when the document changes\", async () => {\n    const element = getEditorElement()\n    let eventCount = 0\n    element.addEventListener(\"trix-change\", (event) => eventCount++)\n\n    await typeCharacters(\"a\")\n    assert.equal(eventCount, 1)\n\n    await moveCursor(\"left\")\n    assert.equal(eventCount, 1)\n\n    await typeCharacters(\"bcd\")\n    assert.equal(eventCount, 4)\n\n    await clickToolbarButton({ action: \"undo\" })\n    assert.equal(eventCount, 5)\n  })\n\n  test(\"invoking internal actions does not dispatch a trix-action-invoke event\", async () => {\n    let event = null\n\n    addEventListener(\"trix-action-invoke\", (ev) => event = ev, { once: true })\n    await clickToolbarButton({ action: \"link\" })\n\n    assert.equal(null, event)\n  })\n\n  test(\"invoking external actions dispatches a trix-action-invoke event\", async () => {\n    let event = null\n    const editor = getEditorElement()\n    editor.toolbarElement.insertAdjacentHTML(\"beforeend\", `\n      <button id=\"test-action\" type=\"button\" data-trix-action=\"x-test\"></button>\n    `)\n\n    addEventListener(\"trix-action-invoke\", (ev) => event = ev, { once: true })\n    await clickToolbarButton({ action: \"x-test\" })\n\n    assert.equal(editor, event.target)\n    assert.equal(\"x-test\", event.actionName)\n    assert.equal(document.getElementById(\"test-action\"), event.invokingElement)\n  })\n\n  test(\"element triggers trix-change event after toggling attributes\", async () => {\n    const element = getEditorElement()\n    const { editor } = element\n\n    const afterChangeEvent = (edit) => {\n      return new Promise((resolve) => {\n        let handler\n        element.addEventListener(\n          \"trix-change\",\n          handler = function (event) {\n            element.removeEventListener(\"trix-change\", handler)\n            resolve(event)\n          }\n        )\n        edit()\n      })\n    }\n\n    await typeCharacters(\"hello\")\n\n    let edit = () => editor.activateAttribute(\"quote\")\n    await afterChangeEvent(edit)\n    assert.ok(editor.attributeIsActive(\"quote\"))\n\n    edit = () => editor.deactivateAttribute(\"quote\")\n    await afterChangeEvent(edit)\n    assert.notOk(editor.attributeIsActive(\"quote\"))\n\n    editor.setSelectedRange([ 0, 5 ])\n\n    edit = () => editor.activateAttribute(\"bold\")\n    await afterChangeEvent(edit)\n    assert.ok(editor.attributeIsActive(\"bold\"))\n\n    edit = () => editor.deactivateAttribute(\"bold\")\n    await afterChangeEvent(edit)\n    assert.notOk(editor.attributeIsActive(\"bold\"))\n  })\n\n  test(\"disabled attributes aren't considered active\", async () => {\n    const { editor } = getEditorElement()\n    editor.activateAttribute(\"heading1\")\n    assert.notOk(editor.attributeIsActive(\"code\"))\n    assert.notOk(editor.attributeIsActive(\"quote\"))\n  })\n\n  test(\"element triggers trix-selection-change events when the location range changes\", async () => {\n    const element = getEditorElement()\n    let eventCount = 0\n\n    element.addEventListener(\"trix-selection-change\", (event) => eventCount++)\n    await nextFrame()\n\n    await typeCharacters(\"a\")\n    assert.equal(eventCount, 1)\n\n    await moveCursor(\"left\")\n    assert.equal(eventCount, 2)\n  })\n\n  test(\"only triggers trix-selection-change events on the active element\", () => {\n    const elementA = getEditorElement()\n    const elementB = document.createElement(\"trix-editor\")\n    elementA.parentNode.insertBefore(elementB, elementA.nextSibling)\n\n    return new Promise((resolve) => {\n      elementB.addEventListener(\"trix-initialize\", () => {\n        elementA.editor.insertString(\"a\")\n        elementB.editor.insertString(\"b\")\n        rangy.getSelection().removeAllRanges()\n\n        let eventCountA = 0\n        let eventCountB = 0\n        elementA.addEventListener(\"trix-selection-change\", (event) => eventCountA++)\n        elementB.addEventListener(\"trix-selection-change\", (event) => eventCountB++)\n\n        elementA.editor.setSelectedRange(0)\n        assert.equal(eventCountA, 1)\n        assert.equal(eventCountB, 0)\n\n        elementB.editor.setSelectedRange(0)\n        assert.equal(eventCountA, 1)\n        assert.equal(eventCountB, 1)\n\n        elementA.editor.setSelectedRange(1)\n        assert.equal(eventCountA, 2)\n        assert.equal(eventCountB, 1)\n        resolve()\n      })\n    })\n  })\n\n  test(\"element triggers toolbar dialog events\", async () => {\n    const element = getEditorElement()\n    const events = []\n\n    element.addEventListener(\"trix-toolbar-dialog-show\", (event) => events.push(event.type))\n    element.addEventListener(\"trix-toolbar-dialog-hide\", (event) => events.push(event.type))\n    await nextFrame()\n\n    await clickToolbarButton({ action: \"link\" })\n    await typeInToolbarDialog(\"http://example.com\", { attribute: \"href\" })\n    await nextFrame()\n\n    assert.deepEqual(events, [ \"trix-toolbar-dialog-show\", \"trix-toolbar-dialog-hide\" ])\n  })\n\n  test(\"element triggers before-paste event with paste data\", async () => {\n    const element = getEditorElement()\n    let eventCount = 0\n    let paste = null\n\n    element.addEventListener(\"trix-before-paste\", function (event) {\n      eventCount++\n      paste = event.paste\n    })\n\n    await typeCharacters(\"\")\n    await pasteContent(\"text/html\", \"<strong>hello</strong>\")\n\n    assert.equal(eventCount, 1)\n    assert.equal(paste.type, \"text/html\")\n    assert.equal(paste.html, \"<strong>hello</strong>\")\n\n    expectDocument(\"hello\\n\")\n  })\n\n  test(\"element triggers before-paste event with mutable paste data\", async () => {\n    const element = getEditorElement()\n    let eventCount = 0\n    let paste = null\n\n    element.addEventListener(\"trix-before-paste\", function (event) {\n      eventCount++\n      paste = event.paste\n      paste.html = \"<strong>greetings</strong>\"\n    })\n\n    await typeCharacters(\"\")\n    await pasteContent(\"text/html\", \"<strong>hello</strong>\")\n\n    assert.equal(eventCount, 1)\n    assert.equal(paste.type, \"text/html\")\n    expectDocument(\"greetings\\n\")\n  })\n\n  test(\"element triggers paste event with position range\", async () => {\n    const element = getEditorElement()\n    let eventCount = 0\n    let paste = null\n\n    element.addEventListener(\"trix-paste\", function (event) {\n      eventCount++\n      paste = event.paste\n    })\n\n    await typeCharacters(\"\")\n    await pasteContent(\"text/html\", \"<strong>hello</strong>\")\n\n    assert.equal(eventCount, 1)\n    assert.equal(paste.type, \"text/html\")\n    assert.ok(rangesAreEqual([ 0, 5 ], paste.range))\n  })\n\n  test(\"element triggers attribute change events\", async () => {\n    const element = getEditorElement()\n    let eventCount = 0\n    let attributes = null\n\n    element.addEventListener(\"trix-attributes-change\", function (event) {\n      eventCount++\n      attributes = event.attributes\n    })\n\n    await typeCharacters(\"\")\n    assert.equal(eventCount, 0)\n\n    await clickToolbarButton({ attribute: \"bold\" })\n\n    assert.equal(eventCount, 1)\n    assert.deepEqual({ bold: true }, attributes)\n  })\n\n  test(\"element triggers action change events\", async () => {\n    const element = getEditorElement()\n    let eventCount = 0\n    let actions = null\n\n    element.addEventListener(\"trix-actions-change\", function (event) {\n      eventCount++\n      actions = event.actions\n    })\n\n    await typeCharacters(\"\")\n    assert.equal(eventCount, 0)\n\n    await clickToolbarButton({ attribute: \"bullet\" })\n\n    assert.equal(eventCount, 1)\n    assert.equal(actions.decreaseNestingLevel, true)\n    assert.equal(actions.increaseNestingLevel, false)\n  })\n\n  test(\"element triggers custom focus and blur events\", async () => {\n    const element = getEditorElement()\n\n    let focusEventCount = 0\n    let blurEventCount = 0\n    element.addEventListener(\"trix-focus\", () => focusEventCount++)\n    element.addEventListener(\"trix-blur\", () => blurEventCount++)\n\n    triggerEvent(element, \"blur\")\n    await delay(10)\n\n    assert.equal(blurEventCount, 1)\n    assert.equal(focusEventCount, 0)\n\n    triggerEvent(element, \"focus\")\n    await delay(10)\n\n    assert.equal(blurEventCount, 1)\n    assert.equal(focusEventCount, 1)\n\n    insertImageAttachment()\n    await delay(20)\n\n    await clickElement(element.querySelector(\"figure\"))\n\n    const textarea = element.querySelector(\"textarea\")\n    textarea.focus()\n    await nextFrame()\n\n    assert.equal(document.activeElement, textarea)\n    assert.equal(blurEventCount, 1)\n    assert.equal(focusEventCount, 1)\n  })\n\n  // Selenium doesn't seem to focus windows properly in some browsers (FF 47 on OS X)\n  // so skip this test when unfocused pending a better solution.\n  testIf(document.hasFocus(), \"element triggers custom focus event when autofocusing\", () => {\n    const element = document.createElement(\"trix-editor\")\n    element.setAttribute(\"autofocus\", \"\")\n\n    let focusEventCount = 0\n    element.addEventListener(\"trix-focus\", () => focusEventCount++)\n\n    const container = document.getElementById(\"trix-container\")\n    container.innerHTML = \"\"\n    container.appendChild(element)\n\n    return new Promise((resolve) => {\n      element.addEventListener(\"trix-initialize\", () => {\n        assert.equal(focusEventCount, 1)\n        resolve()\n      })\n    })\n  })\n\n  test(\"element serializes HTML after attribute changes\", async () => {\n    const element = getEditorElement()\n    let serializedHTML = element.value\n\n    await typeCharacters(\"a\")\n    assert.notEqual(serializedHTML, element.value)\n    serializedHTML = element.value\n\n    await clickToolbarButton({ attribute: \"quote\" })\n    assert.notEqual(serializedHTML, element.value)\n    serializedHTML = element.value\n\n    await clickToolbarButton({ attribute: \"quote\" })\n    assert.notEqual(serializedHTML, element.value)\n  })\n\n  test(\"element serializes HTML after attachment attribute changes\", async () => {\n    const element = getEditorElement()\n    const attributes = { url: \"test_helpers/fixtures/logo.png\", contentType: \"image/png\" }\n\n    const promise = new Promise((resolve) => {\n      element.addEventListener(\"trix-attachment-add\", async (event) => {\n        const { attachment } = event\n        await nextFrame()\n\n        let serializedHTML = element.value\n        attachment.setAttributes(attributes)\n        assert.notEqual(serializedHTML, element.value)\n\n        serializedHTML = element.value\n        assert.ok(serializedHTML.indexOf(TEST_IMAGE_URL) < 0, \"serialized HTML contains previous attachment attributes\")\n        assert.ok(\n          serializedHTML.indexOf(attributes.url) > 0,\n          \"serialized HTML doesn't contain current attachment attributes\"\n        )\n\n        attachment.remove()\n        await nextFrame()\n        resolve()\n      })\n    })\n\n\n    await nextFrame()\n    insertImageAttachment()\n\n    return promise\n  })\n\n  test(\"editor resets to its original value on form reset\", async () => {\n    const element = getEditorElement()\n    const { form } = element.inputElement\n\n    await typeCharacters(\"hello\")\n    form.reset()\n    expectDocument(\"\\n\")\n  })\n\n  test(\"editor resets to last-set value on form reset\", async () => {\n    const element = getEditorElement()\n    const { form } = element.inputElement\n\n    element.value = \"hi\"\n    await typeCharacters(\"hello\")\n    form.reset()\n    expectDocument(\"hi\\n\")\n  })\n\n  test(\"editor respects preventDefault on form reset\", async () => {\n    const element = getEditorElement()\n    const { form } = element.inputElement\n    const preventDefault = (event) => event.preventDefault()\n\n    await typeCharacters(\"hello\")\n\n    form.addEventListener(\"reset\", preventDefault, false)\n    form.reset()\n    form.removeEventListener(\"reset\", preventDefault, false)\n    expectDocument(\"hello\\n\")\n  })\n})\n\ntestGroup(\"<label> support\", { template: \"editor_with_labels\" }, () => {\n  test(\"associates all label elements\", () => {\n    const labels = [ document.getElementById(\"label-1\"), document.getElementById(\"label-3\") ]\n    assert.deepEqual(Array.from(getEditorElement().labels), labels)\n  })\n\n  test(\"focuses when <label> clicked\", () => {\n    document.getElementById(\"label-1\").click()\n    assert.equal(getEditorElement(), document.activeElement)\n  })\n\n  test(\"focuses when <label> descendant clicked\", () => {\n    document.getElementById(\"label-1\").querySelector(\"span\").click()\n    assert.equal(getEditorElement(), document.activeElement)\n  })\n\n  test(\"does not focus when <label> controls another element\", () => {\n    const label = document.getElementById(\"label-2\")\n    assert.notEqual(getEditorElement(), label.control)\n    label.click()\n    assert.notEqual(getEditorElement(), document.activeElement)\n  })\n})\n\ntestGroup(\"form property references its <form>\", { template: \"editors_with_forms\", container: \"div\" }, () => {\n  test(\"accesses its ancestor form\", () => {\n    const form = document.getElementById(\"ancestor-form\")\n    const editor = document.getElementById(\"editor-with-ancestor-form\")\n    assert.equal(editor.form, form)\n  })\n\n  test(\"transitively accesses its related <input> element's <form>\", () => {\n    const form = document.getElementById(\"input-form\")\n    const editor = document.getElementById(\"editor-with-input-form\")\n    assert.equal(editor.form, form)\n  })\n\n  test(\"returns null when there is no associated <form>\", () => {\n    const editor = document.getElementById(\"editor-with-no-form\")\n    assert.equal(editor.form, null)\n  })\n\n  test(\"editor resets to its original value on element reset\", async () => {\n    const element = getEditorElement()\n\n    await typeCharacters(\"hello\")\n    element.reset()\n    expectDocument(\"\\n\")\n  })\n\n  test(\"element returns empty string when value is missing\", () => {\n    const element = getEditorElement()\n\n    assert.equal(element.value, \"\")\n  })\n\n  test(\"editor returns its type\", () => {\n    const element = getEditorElement()\n\n    assert.equal(\"trix-editor\", element.type)\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"adds [disabled] attribute based on .disabled property\", () => {\n    const editor = document.getElementById(\"editor-with-ancestor-form\")\n\n    editor.disabled = true\n\n    assert.equal(editor.hasAttribute(\"disabled\"), true, \"adds [disabled] attribute\")\n\n    editor.disabled = false\n\n    assert.equal(editor.hasAttribute(\"disabled\"), false, \"removes [disabled] attribute\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"removes [contenteditable] and disables input when editor element has [disabled]\", () => {\n    const editor = document.getElementById(\"editor-with-no-form\")\n\n    editor.setAttribute(\"disabled\", \"\")\n\n    assert.equal(editor.matches(\":disabled\"), true, \"sets :disabled CSS pseudostate\")\n    assert.equal(editor.inputElement.disabled, true, \"disables input\")\n    assert.equal(editor.disabled, true, \"exposes [disabled] attribute as .disabled property\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), false, \"removes [contenteditable] attribute\")\n\n    editor.removeAttribute(\"disabled\")\n\n    assert.equal(editor.matches(\":disabled\"), false, \"removes sets :disabled pseudostate\")\n    assert.equal(editor.inputElement.disabled, false, \"enabled input\")\n    assert.equal(editor.disabled, false, \"updates .disabled property\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), true, \"adds [contenteditable] attribute\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"removes [contenteditable] and disables input when editor element is :disabled\", () => {\n    const editor = document.getElementById(\"editor-within-fieldset\")\n    const fieldset = document.getElementById(\"fieldset\")\n\n    fieldset.disabled = true\n\n    assert.equal(editor.matches(\":disabled\"), true, \"sets :disabled CSS pseudostate\")\n    assert.equal(editor.inputElement.disabled, true, \"disables input\")\n    assert.equal(editor.disabled, true, \"infers disabled state from ancestor\")\n    assert.equal(editor.hasAttribute(\"disabled\"), false, \"does not set [disabled] attribute\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), false, \"removes [contenteditable] attribute\")\n\n    fieldset.disabled = false\n\n    assert.equal(editor.matches(\":disabled\"), false, \"removes sets :disabled pseudostate\")\n    assert.equal(editor.inputElement.disabled, false, \"enabled input\")\n    assert.equal(editor.disabled, false, \"updates .disabled property\")\n    assert.equal(editor.hasAttribute(\"disabled\"), false, \"does not set [disabled] attribute\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), true, \"adds [contenteditable] attribute\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"does not receive focus when :disabled\", () => {\n    const activeEditor = document.getElementById(\"editor-with-input-form\")\n    const editor = document.getElementById(\"editor-within-fieldset\")\n\n    activeEditor.focus()\n    editor.disabled = true\n    editor.focus()\n\n    assert.equal(activeEditor, document.activeElement, \"disabled editor does not receive focus\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"disabled editor does not encode its value when the form is submitted\", () => {\n    const editor = document.getElementById(\"editor-with-ancestor-form\")\n    const form = editor.form\n\n    editor.inputElement.value = \"Hello world\"\n    editor.disabled = true\n\n    assert.deepEqual({}, Object.fromEntries(new FormData(form).entries()), \"does not write to FormData\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"validates with [required] attribute as invalid\", () => {\n    const editor = document.getElementById(\"editor-with-ancestor-form\")\n    const form = editor.form\n    const invalidInput = makeElement(\"input\", { required: true })\n    let invalidEvent, submitEvent = null\n\n    editor.addEventListener(\"invalid\", event => invalidEvent = event, { once: true })\n    form.addEventListener(\"submit\", event => submitEvent = event, { once: true })\n\n    editor.required = true\n    form.requestSubmit()\n\n    // assert.equal(document.activeElement, editor, \"editor receives focus\")\n    assert.equal(editor.required, true, \".required property retrurns true\")\n    assert.equal(editor.validity.valid, false, \"validity.valid is false\")\n    assert.equal(editor.validationMessage, invalidInput.validationMessage, \"sets .validationMessage\")\n    assert.equal(invalidEvent.target, editor, \"dispatches 'invalid' event on editor\")\n    assert.equal(submitEvent, null, \"does not dispatch a 'submit' event\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"does not validate with [disabled] attribute\", () => {\n    const editor = document.getElementById(\"editor-with-ancestor-form\")\n    let invalidEvent = null\n\n    editor.disabled = true\n    editor.required = true\n    editor.addEventListener(\"invalid\", event => invalidEvent = event, { once: true })\n    editor.reportValidity()\n\n    assert.equal(invalidEvent, null, \"does not dispatch an 'invalid' event\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"re-validates when the value changes\", async () => {\n    const editor = document.getElementById(\"editor-with-ancestor-form\")\n    editor.required = true\n    editor.focus()\n\n    assert.equal(editor.validity.valid, false, \"validity.valid is initially false\")\n\n    await typeCharacters(\"a\")\n\n    assert.equal(editor.validity.valid, true, \"validity.valid is true after re-validating\")\n    assert.equal(editor.validity.valueMissing, false, \"validity.valueMissing is false\")\n    assert.equal(editor.validationMessage, \"\", \"clears the validationMessage\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"accepts a customError validation message\", () => {\n    const editor = document.getElementById(\"editor-with-ancestor-form\")\n\n    editor.setCustomValidity(\"A custom validation message\")\n\n    assert.equal(editor.validity.valid, false)\n    assert.equal(editor.validity.customError, true)\n    assert.equal(editor.validationMessage, \"A custom validation message\")\n  })\n})\n\nconst configureInputAssociated = ({ target }) => target.willCreateInput = false\n\ntestGroup(\"TrixEditorElement.willCreateInput = false\", {\n  setup: () => addEventListener(\"trix-before-initialize\", configureInputAssociated),\n  teardown: () => removeEventListener(\"trix-before-initialize\", configureInputAssociated)\n}, () => {\n  testIf(TrixEditorElement.formAssociated, \"does not create an <input> on connect\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\")\n\n    const container = document.getElementById(\"trix-container\")\n    const editor = getEditorElement()\n\n    assert.equal(container.querySelectorAll(\"input[type=hidden]\").length, 0)\n    assert.equal(editor.hasAttribute(\"input\"), false)\n    assert.equal(editor.inputElement, null)\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"associates an <input> element when [input] attribute is set\", async () => {\n    await setFixtureHTML(`\n      <trix-editor input=\"input\"></trix-editor>\n      <input id=\"input\">\n    `)\n\n    const editor = getEditorElement()\n    const inputElement = document.getElementById(\"input\")\n\n    assert.equal(editor.getAttribute(\"input\"), inputElement.id)\n    assert.strictEqual(editor.inputElement, inputElement)\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"accesses its ancestor form\", async () => {\n    await setFixtureHTML(\"<form><trix-editor></trix-editor></form>\", \"div\")\n\n    const form = document.querySelector(\"form\")\n    const editor = getEditorElement()\n\n    assert.equal(editor.form, form)\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"accesses its related <form> through the [form] attribute\", async () => {\n    await setFixtureHTML(`\n      <form id=\"form\"></form>\n      <trix-editor form=\"form\"></trix-editor>\n    `, \"div\")\n\n    const form = document.getElementById(\"form\")\n    const editor = getEditorElement()\n\n    assert.equal(editor.form, form)\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"returns null when there is no associated <form>\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\", \"div\")\n\n    const editor = getEditorElement()\n\n    assert.equal(editor.form, null)\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"reads and writes [name] attribute through .name property\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\")\n\n    const editor = getEditorElement()\n\n    assert.equal(editor.name, null)\n    assert.equal(editor.hasAttribute(\"name\"), false, \"has no [name] when .name is null\")\n\n    editor.name = \"content\"\n\n    assert.equal(editor.name, \"content\")\n    assert.equal(editor.getAttribute(\"name\"), \"content\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"makes its sanitized value available to its <form>\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\")\n\n    const editor = getEditorElement()\n    const empty = new FormData(editor.form)\n\n    assert.deepEqual(Array.from(empty.values()), [], \"does not serialize to FormData without [name]\")\n\n    editor.name = \"content\"\n    editor.value = \"<div>hello</div><script>alert('hacked!')</script>\"\n\n    const formData = new FormData(editor.form)\n\n    assert.deepEqual(formData.get(\"content\"), \"<div>hello</div>\", \"serializes sanitized value to FormData\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"editor resets to its original value on element reset\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\")\n\n    const element = getEditorElement()\n\n    element.editor.loadHTML(\"<div>hello</div\")\n    element.reset()\n\n    assert.equal(element.value, \"\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"element returns empty string when value is missing\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\")\n\n    const element = getEditorElement()\n\n    assert.equal(element.value, \"\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"adds [disabled] attribute based on .disabled property\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\")\n\n    const editor = getEditorElement()\n\n    editor.disabled = true\n\n    assert.equal(editor.hasAttribute(\"disabled\"), true, \"adds [disabled] attribute\")\n\n    editor.disabled = false\n\n    assert.equal(editor.hasAttribute(\"disabled\"), false, \"removes [disabled] attribute\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"removes [contenteditable] and when editor element has [disabled]\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\")\n\n    const editor = getEditorElement()\n\n    editor.setAttribute(\"disabled\", \"\")\n\n    assert.equal(editor.matches(\":disabled\"), true, \"sets :disabled CSS pseudostate\")\n    assert.equal(editor.disabled, true, \"exposes [disabled] attribute as .disabled property\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), false, \"removes [contenteditable] attribute\")\n\n    editor.removeAttribute(\"disabled\")\n\n    assert.equal(editor.matches(\":disabled\"), false, \"removes sets :disabled pseudostate\")\n    assert.equal(editor.disabled, false, \"updates .disabled property\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), true, \"adds [contenteditable] attribute\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"removes [contenteditable] when editor element is :disabled\", async () => {\n    await setFixtureHTML(\"<trix-editor></trix-editor>\", \"fieldset\")\n\n    const editor = getEditorElement()\n    const fieldset = editor.closest(\"fieldset\")\n\n    fieldset.disabled = true\n\n    assert.equal(editor.matches(\":disabled\"), true, \"sets :disabled CSS pseudostate\")\n    assert.equal(editor.disabled, true, \"infers disabled state from ancestor\")\n    assert.equal(editor.hasAttribute(\"disabled\"), false, \"does not set [disabled] attribute\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), false, \"removes [contenteditable] attribute\")\n\n    fieldset.disabled = false\n\n    assert.equal(editor.matches(\":disabled\"), false, \"removes sets :disabled pseudostate\")\n    assert.equal(editor.disabled, false, \"updates .disabled property\")\n    assert.equal(editor.hasAttribute(\"disabled\"), false, \"does not set [disabled] attribute\")\n    assert.equal(editor.hasAttribute(\"contenteditable\"), true, \"adds [contenteditable] attribute\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"does not receive focus when :disabled\", async () => {\n    await setFixtureHTML(`\n      <trix-editor id=\"active\"></trix-editor>\n      <trix-editor id=\"disabled\" disabled></trix-editor>\n    `)\n\n    const activeEditor = document.getElementById(\"active\")\n    const disabledEditor = document.getElementById(\"disabled\")\n\n    activeEditor.focus()\n\n    assert.equal(activeEditor, document.activeElement)\n\n    disabledEditor.focus()\n\n    assert.equal(activeEditor, document.activeElement, \"disabled editor does not receive focus\")\n  })\n\n  testIf(TrixEditorElement.formAssociated, \"disabled editor does not encode its value when the form is submitted\", async () => {\n    await setFixtureHTML(`\n      <trix-editor name=\"ignored\" disabled></trix-editor>\n    `)\n\n    const editor = getEditorElement()\n\n    editor.value = \"Hello world\"\n\n    assert.deepEqual(Array.from(new FormData(editor.form).values()), [], \"does not write to FormData\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/html_loading_test.js",
    "content": "import { TEST_IMAGE_URL, assert, expectDocument, test, testGroup } from \"test/test_helper\"\nimport { OBJECT_REPLACEMENT_CHARACTER } from \"trix/constants\"\nimport { delay } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"HTML loading\", () => {\n  testGroup(\"inline elements\", { template: \"editor_with_styled_content\" }, () => {\n    const cases = {\n      \"BR before block element styled otherwise\": {\n        html: `a<br><figure class=\"attachment\"><img src=\"${TEST_IMAGE_URL}\"></figure>`,\n        expectedDocument: `a\\n${OBJECT_REPLACEMENT_CHARACTER}\\n`,\n      },\n\n      \"BR in text before block element styled otherwise\": {\n        html: `<div>a<br>b<figure class=\"attachment\"><img src=\"${TEST_IMAGE_URL}\"></figure></div>`,\n        expectedDocument: `a\\nb${OBJECT_REPLACEMENT_CHARACTER}\\n`,\n      },\n    }\n\n    for (const name in cases) {\n      const details = cases[name]\n      test(name, () => {\n        getEditor().loadHTML(details.html)\n        expectDocument(details.expectedDocument)\n      })\n    }\n  })\n\n  testGroup(\"bold elements\", { template: \"editor_with_bold_styles\" }, () => {\n    test(\"<strong> with font-weight: 500\", () => {\n      getEditor().loadHTML(\"<strong>a</strong>\")\n      assert.textAttributes([ 0, 1 ], { bold: true })\n      expectDocument(\"a\\n\")\n    })\n\n    test(\"<span> with font-weight: 600\", () => {\n      getEditor().loadHTML(\"<span>a</span>\")\n      assert.textAttributes([ 0, 1 ], { bold: true })\n      expectDocument(\"a\\n\")\n    })\n\n    test(\"<article> with font-weight: bold\", () => {\n      getEditor().loadHTML(\"<article>a</article>\")\n      assert.textAttributes([ 0, 1 ], { bold: true })\n      expectDocument(\"a\\n\")\n    })\n  })\n\n  testGroup(\"styled block elements\", { template: \"editor_with_block_styles\" }, () => {\n    test(\"<em> in <blockquote> with font-style: italic\", () => {\n      getEditor().loadHTML(\"<blockquote>a<em>b</em></blockquote>\")\n      assert.textAttributes([ 0, 1 ], {})\n      assert.textAttributes([ 1, 2 ], { italic: true })\n      assert.blockAttributes([ 0, 2 ], [ \"quote\" ])\n      expectDocument(\"ab\\n\")\n    })\n\n    test(\"<strong> in <li> with font-weight: bold\", () => {\n      getEditor().loadHTML(\"<ul><li>a<strong>b</strong></li></ul>\")\n      assert.textAttributes([ 0, 1 ], {})\n      assert.textAttributes([ 1, 2 ], { bold: true })\n      assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n      expectDocument(\"ab\\n\")\n    })\n\n    test(\"newline in <li> with font-weight: bold\", () => {\n      getEditor().loadHTML(\"<ul><li>a<br>b</li></ul>\")\n      assert.textAttributes([ 0, 2 ], {})\n      assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n      expectDocument(\"a\\nb\\n\")\n    })\n  })\n\n  testGroup(\"in a table\", { template: \"editor_in_table\" }, () => {\n    test(\"block elements\", () => {\n      getEditor().loadHTML(\"<h1>a</h1><blockquote>b</blockquote>\")\n      assert.blockAttributes([ 0, 2 ], [ \"heading1\" ])\n      assert.blockAttributes([ 2, 4 ], [ \"quote\" ])\n      expectDocument(\"a\\nb\\n\")\n    })\n  })\n\n  testGroup(\"images\", { template: \"editor_empty\" }, () => {\n    test(\"without dimensions\", async () => {\n      getEditor().loadHTML(`<img src=\"${TEST_IMAGE_URL}\">`)\n      await delay(50)\n\n      const attachment = getDocument().getAttachments()[0]\n      const image = getEditorElement().querySelector(\"img\")\n      assert.equal(attachment.getWidth(), 1)\n      assert.equal(attachment.getHeight(), 1)\n      assert.equal(image.getAttribute(\"width\"), \"1\")\n      assert.equal(image.getAttribute(\"height\"), \"1\")\n      expectDocument(`${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n    })\n\n    test(\"with dimensions\", async () => {\n      getEditor().loadHTML(`<img src=\"${TEST_IMAGE_URL}\" width=\"10\" height=\"20\">`)\n      await delay(50)\n\n      const attachment = getDocument().getAttachments()[0]\n      const image = getEditorElement().querySelector(\"img\")\n      assert.equal(attachment.getWidth(), 10)\n      assert.equal(attachment.getHeight(), 20)\n      assert.equal(image.getAttribute(\"width\"), \"10\")\n      assert.equal(image.getAttribute(\"height\"), \"20\")\n      expectDocument(`${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n    })\n  })\n\n  testGroup(\"text after closing tag\", { template: \"editor_empty\" }, () => {\n    test(\"parses text as separate block\", () => {\n      getEditor().loadHTML(\"<h1>a</h1>b\")\n      assert.blockAttributes([ 0, 2 ], [ \"heading1\" ])\n      assert.blockAttributes([ 2, 4 ], [])\n      expectDocument(\"a\\nb\\n\")\n    })\n  })\n})\n"
  },
  {
    "path": "src/test/system/html_reparsing_test.js",
    "content": "import { assert, expectDocument, test, testGroup } from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"HTML Reparsing\", { template: \"editor_empty\" }, () => {\n  test(\"mutation resulting in identical blocks\", async () => {\n    const element = getEditorElement()\n    element.editor.loadHTML(\"<ul><li>a</li><li>b</li></ul>\")\n    await nextFrame()\n    element.querySelector(\"li\").textContent = \"b\"\n    await nextFrame()\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\" ])\n    assert.equal(element.value, \"<ul><li>b</li><li>b</li></ul>\")\n    expectDocument(\"b\\nb\\n\")\n  })\n\n  test(\"mutation resulting in identical pieces\", async () => {\n    const element = getEditorElement()\n    element.editor.loadHTML(\"<div><strong>a</strong> <strong>b</strong></div>\")\n    await nextFrame()\n    element.querySelector(\"strong\").textContent = \"b\"\n    await nextFrame()\n    assert.textAttributes([ 0, 1 ], { bold: true })\n    assert.textAttributes([ 2, 3 ], { bold: true })\n    assert.equal(element.value, \"<div><strong>b</strong> <strong>b</strong></div>\")\n    expectDocument(\"b b\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/html_replacement_test.js",
    "content": "import * as config from \"trix/config\"\n\nimport { assert, expectDocument, testGroup, testIf, triggerEvent } from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\nconst test = function() {\n  testIf(config.input.getLevel() === 0, ...arguments)\n}\n\ntestGroup(\"Level 0 input: HTML replacement\", () =>\n  testGroup(\"deleting with command+backspace\", { template: \"editor_empty\" }, () => {\n    test(\"from the end of a line\", async () => {\n      getEditor().loadHTML(\"<div>a</div><blockquote>b</blockquote><div>c</div>\")\n      getSelectionManager().setLocationRange({ index: 1, offset: 1 })\n      await pressCommandBackspace({ replaceText: \"b\" })\n      assert.locationRange({ index: 1, offset: 0 })\n      assert.blockAttributes([ 0, 2 ], [])\n      assert.blockAttributes([ 2, 3 ], [ \"quote\" ])\n      assert.blockAttributes([ 3, 5 ], [])\n      expectDocument(\"a\\n\\nc\\n\")\n    })\n\n    test(\"in the first block\", async () => {\n      getEditor().loadHTML(\"<div>a</div><blockquote>b</blockquote>\")\n      getSelectionManager().setLocationRange({ index: 0, offset: 1 })\n      await pressCommandBackspace({ replaceText: \"a\" })\n      assert.locationRange({ index: 0, offset: 0 })\n      assert.blockAttributes([ 0, 1 ], [])\n      assert.blockAttributes([ 1, 3 ], [ \"quote\" ])\n      expectDocument(\"\\nb\\n\")\n    })\n\n    test(\"from the middle of a line\", async () => {\n      getEditor().loadHTML(\"<div>a</div><blockquote>bc</blockquote><div>d</div>\")\n      getSelectionManager().setLocationRange({ index: 1, offset: 1 })\n      await pressCommandBackspace({ replaceText: \"b\" })\n      assert.locationRange({ index: 1, offset: 0 })\n      assert.blockAttributes([ 0, 2 ], [])\n      assert.blockAttributes([ 2, 4 ], [ \"quote\" ])\n      assert.blockAttributes([ 4, 6 ], [])\n      expectDocument(\"a\\nc\\nd\\n\")\n    })\n\n    test(\"from the middle of a line in a multi-line block\", async () => {\n      getEditor().loadHTML(\"<div>a</div><blockquote>bc<br>d</blockquote><div>e</div>\")\n      getSelectionManager().setLocationRange({ index: 1, offset: 1 })\n      await pressCommandBackspace({ replaceText: \"b\" })\n      assert.locationRange({ index: 1, offset: 0 })\n      assert.blockAttributes([ 0, 2 ], [])\n      assert.blockAttributes([ 2, 6 ], [ \"quote\" ])\n      expectDocument(\"a\\nc\\nd\\ne\\n\")\n    })\n\n    test(\"from the end of a list item\", async () => {\n      getEditor().loadHTML(\"<ul><li>a</li><li>b</li></ul>\")\n      getSelectionManager().setLocationRange({ index: 1, offset: 1 })\n      await pressCommandBackspace({ replaceText: \"b\" })\n      assert.locationRange({ index: 1, offset: 0 })\n      assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n      assert.blockAttributes([ 2, 4 ], [ \"bulletList\", \"bullet\" ])\n      expectDocument(\"a\\n\\n\")\n    })\n\n    test(\"a character that is its text node's only data\", async () => {\n      getEditor().loadHTML(\"<div>a<br>b<br><strong>c</strong></div>\")\n      getSelectionManager().setLocationRange({ index: 0, offset: 3 })\n      await pressCommandBackspace({ replaceText: \"b\" })\n      assert.locationRange({ index: 0, offset: 2 })\n      expectDocument(\"a\\n\\nc\\n\")\n    })\n\n    test(\"a formatted word\", async () => {\n      getEditor().loadHTML(\"<div>a<strong>bc</strong></div>\")\n      getSelectionManager().setLocationRange({ index: 0, offset: 4 })\n      await pressCommandBackspace({ replaceElementWithText: \"bc\" })\n      assert.locationRange({ index: 0, offset: 1 })\n      expectDocument(\"a\\n\")\n    })\n  })\n)\n\nconst pressCommandBackspace = async ({ replaceText, replaceElementWithText }) => {\n  let previousSibling\n  triggerEvent(document.activeElement, \"keydown\", { charCode: 0, keyCode: 8, which: 8, metaKey: true })\n  const range = rangy.getSelection().getRangeAt(0)\n\n  if (replaceElementWithText) {\n    const element = getElementWithText(replaceElementWithText)\n    previousSibling = element.previousSibling\n    element.parentNode.removeChild(element)\n    range.collapseAfter(previousSibling)\n  } else {\n    range.findText(replaceText, { direction: \"backward\" })\n    range.splitBoundaries()\n\n    const node = range.getNodes()[0]\n    previousSibling = node.previousSibling\n    const { nextSibling, parentNode } = node\n\n    if (previousSibling?.nodeType === Node.COMMENT_NODE) {\n      parentNode.removeChild(previousSibling)\n    }\n\n    node.data = \"\"\n    parentNode.removeChild(node)\n\n    if (!parentNode.hasChildNodes()) {\n      parentNode.appendChild(document.createElement(\"br\"))\n    }\n\n    range.collapseBefore(nextSibling ? nextSibling : parentNode.firstChild)\n  }\n\n  range.select()\n  await nextFrame()\n}\n\nconst getElementWithText = function (text) {\n  for (const element of Array.from(document.activeElement.querySelectorAll(\"*\"))) {\n    if (element.innerText === text) {\n      return element\n    }\n  }\n  return null\n}\n"
  },
  {
    "path": "src/test/system/installation_process_test.js",
    "content": "import EditorController from \"trix/controllers/editor_controller\"\n\nimport { assert, setFixtureHTML, test, testGroup } from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"Installation process\", { template: \"editor_html\" }, () => {\n  test(\"element.editorController\", () => {\n    assert.ok(getEditorController() instanceof EditorController)\n  })\n\n  test(\"creates a contenteditable element\", () => assert.ok(getEditorElement()))\n\n  test(\"loads the initial document\", () => {\n    assert.equal(getEditorElement().textContent, \"Hello world\")\n  })\n\n  test(\"sets value property\", async () => {\n    await nextFrame()\n    assert.equal(getEditorElement().value, \"<div>Hello world</div>\")\n  })\n})\n\ntestGroup(\"Installation process without specified elements\", { template: \"editor_empty\" }, () =>\n  test(\"creates identified toolbar and input elements\", () => {\n    const editorElement = getEditorElement()\n\n    const toolbarId = editorElement.getAttribute(\"toolbar\")\n    assert.ok(/trix-toolbar-\\d+/.test(toolbarId), `toolbar id not assert.ok ${JSON.stringify(toolbarId)}`)\n    const toolbarElement = document.getElementById(toolbarId)\n    assert.ok(toolbarElement, \"toolbar element not assert.ok\")\n    assert.equal(editorElement.toolbarElement, toolbarElement)\n\n    const inputId = editorElement.getAttribute(\"input\")\n    assert.ok(/trix-input-\\d+/.test(inputId), `input id not assert.ok ${JSON.stringify(inputId)}`)\n    const inputElement = document.getElementById(inputId)\n    assert.ok(inputElement, \"input element not assert.ok\")\n    assert.equal(editorElement.inputElement, inputElement)\n  })\n)\n\ntestGroup(\"Installation process with specified elements\", { template: \"editor_with_toolbar_and_input\" }, () => {\n  test(\"uses specified elements\", () => {\n    const editorElement = getEditorElement()\n    assert.equal(editorElement.toolbarElement, document.getElementById(\"my_toolbar\"))\n    assert.equal(editorElement.inputElement, document.getElementById(\"my_input\"))\n    assert.equal(editorElement.value, \"<div>Hello world</div>\")\n  })\n\n  test(\"trix-toolbar can reference editorElements and editorElement\", () => {\n    const editorElement = getEditorElement()\n    const toolbarElement = editorElement.toolbarElement\n\n    assert.equal(toolbarElement, document.getElementById(\"my_toolbar\"))\n    assert.deepEqual(Array.from(toolbarElement.editorElements), [ editorElement ])\n    assert.equal(toolbarElement.editorElement, editorElement)\n  })\n\n  test(\"can be cloned\", async () => {\n    const originalElement = document.getElementById(\"my_editor\")\n    const clonedElement = originalElement.cloneNode(true)\n\n    const { parentElement } = originalElement\n    parentElement.removeChild(originalElement)\n    parentElement.appendChild(clonedElement)\n\n    await nextFrame()\n\n    const editorElement = getEditorElement()\n    assert.equal(editorElement.toolbarElement, document.getElementById(\"my_toolbar\"))\n    assert.equal(editorElement.inputElement, document.getElementById(\"my_input\"))\n    assert.equal(editorElement.value, \"<div>Hello world</div>\")\n  })\n})\n\ntestGroup(\"Installation process with content and without specified elements\", () => {\n  test(\"loads the trix-editor element's innerHTML on boot\", async () => {\n    await setFixtureHTML(\"<trix-editor><div>Hello world</div></trix-editor>\")\n\n    const editorElement = getEditorElement()\n\n    assert.equal(1, editorElement.childElementCount, \"sanitzes HTML\")\n    assert.equal(editorElement.value, \"<div>Hello world</div>\")\n    assert.equal(editorElement.value, editorElement.inputElement.value)\n  })\n\n  test(\"sanitizes the trix-editor element's innerHTML on boot\", async () => {\n    await setFixtureHTML(\"<trix-editor><script>alert('xss')</script></trix-editor>\")\n\n    const editorElement = getEditorElement()\n\n    assert.equal(0, editorElement.querySelectorAll(\"script\").length, \"sanitzes HTML\")\n    assert.equal(editorElement.value, \"\")\n    assert.equal(editorElement.value, editorElement.inputElement.value)\n  })\n})\n\ntestGroup(\"Installation process with content and input\", () => {\n  test(\"prioritzes loading its initial state from the input over the content\", async () => {\n    await setFixtureHTML(`\n      <trix-editor input=\"input\"><div>from editor</div></trix-editor>\n      <textarea id=\"input\"><div>from input</div></textarea>\n    `)\n\n    const editorElement = getEditorElement()\n\n    assert.equal(editorElement.value, \"<div>from input</div>\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/level_2_input_test.js",
    "content": "import * as config from \"trix/config\"\nimport { OBJECT_REPLACEMENT_CHARACTER } from \"trix/constants\"\n\nimport {\n  assert,\n  clickToolbarButton,\n  expectDocument,\n  insertNode,\n  insertString,\n  isToolbarButtonActive,\n  selectNode,\n  testGroup,\n  testIf,\n  triggerEvent,\n  triggerInputEvent,\n  typeCharacters,\n} from \"test/test_helper\"\nimport { delay, nextFrame } from \"../test_helpers/timing_helpers\"\n\nconst test = function() {\n  testIf(config.input.getLevel() === 2, ...arguments)\n}\n\nconst testOptions = {\n  template: \"editor_empty\",\n  setup() {\n    addEventListener(\"beforeinput\", recordInputEvent, true)\n    addEventListener(\"input\", recordInputEvent, true)\n  },\n  teardown() {\n    removeEventListener(\"beforeinput\", recordInputEvent, true)\n    removeEventListener(\"input\", recordInputEvent, true)\n  },\n}\n\nlet inputEvents = []\n\nconst recordInputEvent = function (event) {\n  // Not all browsers dispatch \"beforeinput\" event when calling execCommand() so\n  // we manually dispatch a synthetic one. If a second one arrives, ignore it.\n  if (event.type === \"beforeinput\" && inputEvents.length === 1 && inputEvents[0].type === \"beforeinput\") {\n    event.stopImmediatePropagation()\n  } else {\n    const { type, inputType, data } = event\n    inputEvents.push({ type, inputType, data })\n  }\n}\n\n// Borrowed from https://github.com/web-platform-tests/wpt/blob/master/input-events/input-events-exec-command.html\nconst performInputTypeUsingExecCommand = async (command, { inputType, data }) => {\n  inputEvents = []\n\n  await nextFrame()\n\n  const isInsertParagraph = inputType === \"insertParagraph\"\n\n  triggerInputEvent(document.activeElement, \"beforeinput\", { inputType, data })\n\n  // See `shouldRenderInmmediatelyToDealWithIOSDictation` to deal with iOS 18+ dictation bug.\n  if (!isInsertParagraph) {\n    document.execCommand(command, false, data)\n    assert.equal(inputEvents[1].type, \"input\")\n  }\n\n  assert.equal(inputEvents.length, isInsertParagraph ? 1 : 2)\n  assert.equal(inputEvents[0].type, \"beforeinput\")\n  assert.equal(inputEvents[0].inputType, inputType)\n  assert.equal(inputEvents[0].data, data)\n\n  await nextFrame()\n  await nextFrame()\n}\n\ntestGroup(\"Level 2 Input\", testOptions, () => {\n  test(\"insertText\", async () => {\n    await performInputTypeUsingExecCommand(\"insertText\", { inputType: \"insertText\", data: \"abc\" })\n    expectDocument(\"abc\\n\")\n  })\n\n  test(\"insertOrderedList\", async () => {\n    insertString(\"a\\nb\")\n    await performInputTypeUsingExecCommand(\"insertOrderedList\", { inputType: \"insertOrderedList\" })\n    assert.blockAttributes([ 0, 2 ], [])\n    assert.blockAttributes([ 2, 4 ], [ \"numberList\", \"number\" ])\n    assert.ok(isToolbarButtonActive({ attribute: \"number\" }))\n    expectDocument(\"a\\nb\\n\")\n  })\n\n  test(\"insertUnorderedList\", async () => {\n    insertString(\"a\\nb\")\n    await performInputTypeUsingExecCommand(\"insertUnorderedList\", { inputType: \"insertUnorderedList\" })\n    assert.blockAttributes([ 0, 2 ], [])\n    assert.blockAttributes([ 2, 4 ], [ \"bulletList\", \"bullet\" ])\n    assert.ok(isToolbarButtonActive({ attribute: \"bullet\" }))\n    expectDocument(\"a\\nb\\n\")\n  })\n\n  test(\"insertLineBreak\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    insertString(\"abc\")\n    await performInputTypeUsingExecCommand(\"insertLineBreak\", { inputType: \"insertLineBreak\" })\n    await performInputTypeUsingExecCommand(\"insertLineBreak\", { inputType: \"insertLineBreak\" })\n    assert.blockAttributes([ 0, 6 ], [ \"quote\" ])\n    expectDocument(\"abc\\n\\n\\n\")\n  })\n\n  test(\"insertParagraph\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    insertString(\"abc\")\n    await performInputTypeUsingExecCommand(\"insertParagraph\", { inputType: \"insertParagraph\" })\n    await performInputTypeUsingExecCommand(\"insertParagraph\", { inputType: \"insertParagraph\" })\n\n    assert.blockAttributes([ 0, 4 ], [ \"quote\" ])\n    assert.blockAttributes([ 4, 5 ], [])\n    expectDocument(\"abc\\n\\n\")\n  })\n\n  test(\"formatBold\", async () => {\n    insertString(\"abc\")\n    getComposition().setSelectedRange([ 1, 2 ])\n    await performInputTypeUsingExecCommand(\"bold\", { inputType: \"formatBold\" })\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 2 ], { bold: true })\n    assert.textAttributes([ 2, 3 ], {})\n    expectDocument(\"abc\\n\")\n  })\n\n  test(\"formatItalic\", async () => {\n    insertString(\"abc\")\n    getComposition().setSelectedRange([ 1, 2 ])\n    await performInputTypeUsingExecCommand(\"italic\", { inputType: \"formatItalic\" })\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 2 ], { italic: true })\n    assert.textAttributes([ 2, 3 ], {})\n    expectDocument(\"abc\\n\")\n  })\n\n  test(\"formatStrikeThrough\", async () => {\n    insertString(\"abc\")\n    getComposition().setSelectedRange([ 1, 2 ])\n    await performInputTypeUsingExecCommand(\"strikeThrough\", { inputType: \"formatStrikeThrough\" })\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 2 ], { strike: true })\n    assert.textAttributes([ 2, 3 ], {})\n    expectDocument(\"abc\\n\")\n  })\n\n  // https://input-inspector.now.sh/profiles/hVXS1cHYFvc2EfdRyTWQ\n  test(\"correcting a misspelled word\", async () => {\n    insertString(\"onr\")\n    getComposition().setSelectedRange([ 0, 3 ])\n    await nextFrame()\n\n    const inputType = \"insertReplacementText\"\n    const dataTransfer = createDataTransfer({ \"text/plain\": \"one\" })\n\n    const targetRange = document.createRange()\n    const textNode = getEditorElement().firstElementChild.lastChild\n    targetRange.setStart(textNode, 0)\n    targetRange.setEnd(textNode, 3)\n\n    const event = createEvent(\"beforeinput\", { inputType, dataTransfer, getTargetRanges: () => [ targetRange ] })\n    document.activeElement.dispatchEvent(event)\n\n    await nextFrame()\n    expectDocument(\"one\\n\")\n  })\n\n  // https://input-inspector.now.sh/profiles/XsZVwKtFxakwnsNs0qnX\n  test(\"correcting a misspelled word in Safari\", async () => {\n    insertString(\"onr\")\n    getComposition().setSelectedRange([ 0, 3 ])\n    await nextFrame()\n\n    const inputType = \"insertText\"\n    const dataTransfer = createDataTransfer({ \"text/plain\": \"one\", \"text/html\": \"one\" })\n    const event = createEvent(\"beforeinput\", { inputType, dataTransfer })\n    document.activeElement.dispatchEvent(event)\n\n    await nextFrame()\n    expectDocument(\"one\\n\")\n  })\n\n  // https://input-inspector.now.sh/profiles/yZlsrfG93QMzp2oyr0BE\n  test(\"deleting the last character in a composed word on Android\", async () => {\n    insertString(\"c\")\n    const element = getEditorElement()\n    const textNode = element.firstChild.lastChild\n    await selectNode(textNode)\n    triggerInputEvent(element, \"beforeinput\", { inputType: \"insertCompositionText\", data: \"\" })\n    triggerEvent(element, \"compositionend\", { data: \"\" })\n    await nextFrame()\n    expectDocument(\"\\n\")\n  })\n\n  test(\"pasting a file\", async () => {\n    const file = await createFile()\n    const clipboardData = createDataTransfer({ Files: [ file ] })\n    const dataTransfer = createDataTransfer({ Files: [ file ] })\n    await paste({ clipboardData, dataTransfer })\n\n    const attachments = getDocument().getAttachments()\n    assert.equal(attachments.length, 1)\n    assert.equal(attachments[0].getFilename(), file.name)\n    expectDocument(`${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n  })\n\n  // \"insertFromPaste InputEvent missing pasted files in dataTransfer\"\n  // - https://bugs.webkit.org/show_bug.cgi?id=194921\n  test(\"pasting a file in Safari\", async () => {\n    const file = await createFile()\n\n    const clipboardData = createDataTransfer({ Files: [ file ] })\n    const dataTransfer = createDataTransfer({ \"text/html\": `<img src=\"blob:${location.origin}/531de8\">` })\n\n    await paste({ clipboardData, dataTransfer })\n    const attachments = getDocument().getAttachments()\n    assert.equal(attachments.length, 1)\n    assert.equal(attachments[0].getFilename(), file.name)\n    expectDocument(`${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n  })\n\n  // \"insertFromPaste InputEvent missing text/uri-list in dataTransfer for pasted links\"\n  // - https://bugs.webkit.org/show_bug.cgi?id=196702\n  test(\"pasting a link in Safari\", async () => {\n    await createFile()\n    const url = \"https://bugs.webkit.org\"\n    const text = \"WebKit Bugzilla\"\n    const clipboardData = createDataTransfer({ URL: url, \"text/uri-list\": url, \"text/plain\": text })\n    const dataTransfer = createDataTransfer({ \"text/html\": `<a href=\"${url}\">${text}</a>`, \"text/plain\": text })\n    await paste({ clipboardData, dataTransfer })\n    assert.textAttributes([ 0, url.length ], { href: url })\n    expectDocument(`${url}\\n`)\n  })\n\n  // Pastes from MS Word include an image of the copied text 🙃\n  // https://input-inspector.now.sh/profiles/QWDITsV60dpEVl1SOZg8\n  test(\"pasting text from MS Word\", async () => {\n    const file = await createFile()\n    const dataTransfer = createDataTransfer({\n      \"text/html\": `<html xmlns:o=\"urn:schemas-microsoft-com:office:office\"\n        xmlns:w=\"urn:schemas-microsoft-com:office:word\"\n        xmlns:m=\"http://schemas.microsoft.com/office/2004/12/omml\"\n        xmlns=\"http://www.w3.org/TR/REC-html40\">\n        <body>\n          <span class=\"MsoNormal\">abc</span>\n        </body>\n      </html>`,\n      \"text/plain\": \"abc\",\n      Files: [ file ],\n    })\n\n    await paste({ dataTransfer })\n    const attachments = getDocument().getAttachments()\n    assert.equal(attachments.length, 0)\n    expectDocument(\"abc\\n\")\n  })\n\n  // \"beforeinput\" event is not fired for Paste and Match Style operations\n  // - https://bugs.chromium.org/p/chromium/issues/detail?id=934448\n  test(\"Paste and Match Style in Chrome\", async () => {\n    await typeCharacters(\"a\\n\\n\")\n    const clipboardData = createDataTransfer({ \"text/plain\": \"b\\n\\nc\" })\n    const pasteEvent = createEvent(\"paste\", { clipboardData })\n\n    if (document.activeElement.dispatchEvent(pasteEvent)) {\n      const node = document.createElement(\"div\")\n      node.innerHTML = \"<div>b</div><div><br></div><div>c</div>\"\n      await insertNode(node)\n    } else {\n      await nextFrame()\n    }\n    expectDocument(\"a\\n\\nb\\n\\nc\\n\")\n  })\n\n  // Firefox provides element-level target ranges (startContainer: <li>)\n  // rather than text-node-level ranges for deleteContentBackward. When the\n  // container is an element and offset is 0, the location mapper must count\n  // the block-start comment before breaking, or the block index will be wrong\n  // and the bullet list structure gets corrupted.\n  // - https://github.com/basecamp/trix/issues/1259\n  test(\"deleteContentBackward with element-level target range in a bullet list (Firefox)\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    insertString(\"foo bar\")\n    getComposition().setSelectedRange([ 0, 3 ])\n    await nextFrame()\n\n    // Simulate Firefox's target range: startContainer is the <li> element\n    // (not the text node), startOffset 0, endContainer is the text node, endOffset 3\n    const element = getEditorElement()\n    const li = element.querySelector(\"li\")\n    const textNode = li.lastChild\n\n    const targetRange = document.createRange()\n    targetRange.setStart(li, 0)\n    targetRange.setEnd(textNode, 3)\n\n    const event = createEvent(\"beforeinput\", {\n      inputType: \"deleteContentBackward\",\n      getTargetRanges: () => [ targetRange ],\n    })\n    document.activeElement.dispatchEvent(event)\n\n    await nextFrame()\n    await nextFrame()\n\n    assert.blockAttributes([ 0, 5 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\" bar\\n\")\n  })\n})\n\nconst createFile = () => {\n  return new Promise((resolve) => {\n    const canvas = document.createElement(\"canvas\")\n    canvas.toBlob((file) => {\n      file.name = \"image.png\"\n      resolve(file)\n    })\n  })\n}\n\nconst createDataTransfer = function (data = {}) {\n  return {\n    types: Object.keys(data),\n    files: data.Files || [],\n    getData: (type) => data[type],\n  }\n}\n\nconst createEvent = function (type, properties = {}) {\n  const event = document.createEvent(\"Events\")\n  event.initEvent(type, true, true)\n  for (const key in properties) {\n    const value = properties[key]\n    Object.defineProperty(event, key, { value })\n  }\n  return event\n}\n\nconst paste = async (param = {}) => {\n  const { dataTransfer, clipboardData } = param\n  const pasteEvent = createEvent(\"paste\", { clipboardData: clipboardData || dataTransfer })\n  const inputEvent = createEvent(\"beforeinput\", { inputType: \"insertFromPaste\", dataTransfer })\n  if (document.activeElement.dispatchEvent(pasteEvent)) {\n    document.activeElement.dispatchEvent(inputEvent)\n  }\n\n  await delay(60)\n}\n"
  },
  {
    "path": "src/test/system/list_formatting_test.js",
    "content": "import * as config from \"trix/config\"\n\nimport {\n  assert,\n  clickToolbarButton,\n  expectDocument,\n  moveCursor,\n  pressKey,\n  test,\n  testGroup,\n  testIf,\n  triggerEvent,\n  typeCharacters,\n} from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"List formatting\", { template: \"editor_empty\" }, () => {\n  test(\"creating a new list item\", async () => {\n    await typeCharacters(\"a\")\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"\\n\")\n    assert.locationRange({ index: 1, offset: 0 })\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\" ])\n  })\n\n  test(\"breaking out of a list\", async () => {\n    await typeCharacters(\"a\")\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"\\n\\n\")\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [])\n    expectDocument(\"a\\n\\n\")\n  })\n\n  test(\"pressing return at the beginning of a non-empty list item\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\\nb\")\n    await moveCursor(\"left\")\n    await pressKey(\"return\")\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 3, 5 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\"a\\n\\nb\\n\")\n  })\n\n  test(\"pressing tab increases nesting level, tab+shift decreases nesting level\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\")\n    await pressKey(\"return\")\n    await pressKey(\"tab\")\n    await typeCharacters(\"b\")\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ])\n    await nextFrame()\n    // press shift tab\n    triggerEvent(document.activeElement, \"keydown\", {\n      key: \"Tab\",\n      charCode: 0,\n      keyCode: 9,\n      which: 9,\n      shiftKey: true,\n    })\n    assert.blockAttributes([ 0, 1 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\"a\\nb\\n\")\n  })\n\n  testIf(config.input.getLevel() === 0, \"pressing shift-return at the end of a list item\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\")\n    const pressShiftReturn = triggerEvent(document.activeElement, \"keydown\", {\n      charCode: 0,\n      keyCode: 13,\n      which: 13,\n      shiftKey: true,\n    })\n    assert.notOk(pressShiftReturn) // Assert defaultPrevented\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\"a\\n\\n\")\n  })\n\n  test(\"pressing delete at the beginning of a non-empty nested list item\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\\n\")\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    await typeCharacters(\"b\\n\")\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    await typeCharacters(\"c\")\n    getSelectionManager().setLocationRange({ index: 1, offset: 0 })\n    getComposition().deleteInDirection(\"backward\")\n    getEditorController().render()\n    await nextFrame()\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 3, 4 ], [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ])\n    expectDocument(\"ab\\nc\\n\")\n  })\n\n  test(\"decreasing list item's level decreases its nested items level too\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"a\\n\")\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    await typeCharacters(\"b\\n\")\n    await clickToolbarButton({ action: \"increaseNestingLevel\" })\n    await typeCharacters(\"c\")\n    getSelectionManager().setLocationRange({ index: 1, offset: 1 })\n\n    for (let n = 0; n < 3; n++) {\n      getComposition().deleteInDirection(\"backward\")\n      getEditorController().render()\n    }\n\n    assert.blockAttributes([ 0, 2 ], [ \"bulletList\", \"bullet\" ])\n    assert.blockAttributes([ 2, 3 ], [])\n    assert.blockAttributes([ 3, 5 ], [ \"bulletList\", \"bullet\" ])\n    expectDocument(\"a\\n\\nc\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/morphing_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\nimport { Idiomorph } from \"idiomorph\"\n\nfunction renderWithMorph(event) {\n  event.render = (editorElement, documentFragment) => {\n    Idiomorph.morph(editorElement, documentFragment, { morphStyle: \"innerHTML\" })\n  }\n}\n\ntestGroup(\"morphing editor content\", {\n  template: \"editor_empty\",\n  beforeSetup: () => addEventListener(\"trix-before-render\", renderWithMorph),\n  afterTeardown: () => removeEventListener(\"trix-before-render\", renderWithMorph)\n}, () => {\n  test(\"renders changed elements with morphing\", () => {\n    const element = getEditorElement()\n\n    element.editor.loadHTML(\"<div>hello</div>\")\n\n    const before = element.firstElementChild\n    assert.equal(before.textContent, \"hello\")\n\n    element.editor.loadHTML(\"<div>goodbye</div>\")\n\n    const after = element.firstElementChild\n    assert.equal(after.textContent, \"goodbye\")\n    assert.strictEqual(after, before, \"preserves element across renders\")\n  })\n\n  test(\"renders with morphing when ancestor elements are morphed\", () => {\n    const element = getEditorElement()\n\n    Idiomorph.morph(\n      element.parentElement,\n      \"<trix-editor><div>hello</div></trix-editor>\",\n      { morphStyle: \"innerHTML\" }\n    )\n\n    const before = element.firstElementChild\n    assert.equal(before.textContent, \"hello\")\n\n    Idiomorph.morph(\n      element.parentElement,\n      \"<trix-editor><div>goodbye</div></trix-editor>\",\n      { morphStyle: \"innerHTML\" }\n    )\n\n    const after = element.firstElementChild\n    assert.equal(after.textContent, \"goodbye\")\n    assert.strictEqual(after, before, \"preserves element across renders\")\n  })\n})\n\ntestGroup(\"morphing with internal toolbar\", { template: \"editor_empty\" }, () => {\n  test(\"removing the 'connected' attribute will reset the editor and recreate toolbar\", async () => {\n    const element = getEditorElement()\n\n    assert.ok(element.hasAttribute(\"connected\"))\n\n    const originalToolbar = element.toolbarElement\n    element.toolbarElement.remove()\n    element.removeAttribute(\"toolbar\")\n    element.removeAttribute(\"connected\")\n    await nextFrame()\n\n    assert.ok(element.hasAttribute(\"connected\"))\n    assert.ok(element.toolbarElement)\n    assert.notEqual(originalToolbar, element.toolbarElement)\n  })\n})\n\ntestGroup(\"morphing with external toolbar\", { template: \"editor_with_toolbar_and_input\" }, () => {\n  test(\"removing the 'connected' attribute will reset the editor leave the toolbar untouched\", async () => {\n    const element = getEditorElement()\n\n    assert.ok(element.hasAttribute(\"connected\"))\n\n    const originalToolbar = element.toolbarElement\n    element.removeAttribute(\"connected\")\n    await nextFrame()\n\n    assert.ok(element.hasAttribute(\"connected\"))\n    assert.ok(element.toolbarElement)\n    assert.equal(originalToolbar, element.toolbarElement)\n  })\n})\n"
  },
  {
    "path": "src/test/system/mutation_input_test.js",
    "content": "import * as config from \"trix/config\"\n\nimport {\n  TEST_IMAGE_URL,\n  assert,\n  clickToolbarButton,\n  expectDocument,\n  insertNode,\n  isToolbarButtonActive,\n  testGroup,\n  testIf,\n  triggerEvent,\n  typeCharacters,\n} from \"test/test_helper\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\nconst test = function() {\n  testIf(config.input.getLevel() === 0, ...arguments)\n}\n\ntestGroup(\"Mutation input\", { template: \"editor_empty\" }, () => {\n  test(\"deleting a newline\", async () => {\n    const element = getEditorElement()\n    element.editor.insertString(\"a\\n\\nb\")\n\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 229, which: 229 })\n    const br = element.querySelectorAll(\"br\")[1]\n    br.parentNode.removeChild(br)\n    await nextFrame()\n    expectDocument(\"a\\nb\\n\")\n  })\n\n  test(\"typing a space in formatted text at the end of a block\", async () => {\n    const element = getEditorElement()\n\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"a\")\n    // Press space key\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 32, which: 32 })\n    triggerEvent(element, \"keypress\", { charCode: 32, keyCode: 32, which: 32 })\n\n    const boldElement = element.querySelector(\"strong\")\n    boldElement.appendChild(document.createTextNode(\" \"))\n    boldElement.appendChild(document.createElement(\"br\"))\n\n    await nextFrame()\n\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    assert.textAttributes([ 0, 2 ], { bold: true })\n    expectDocument(\"a \\n\")\n  })\n\n  test(\"typing formatted text after a newline at the end of block\", async () => {\n    const element = getEditorElement()\n    element.editor.insertHTML(\"<ul><li>a</li><li><br></li></ul>\")\n    element.editor.setSelectedRange(3)\n\n    await clickToolbarButton({ attribute: \"bold\" })\n\n    // Press B key\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 66, which: 66 })\n    triggerEvent(element, \"keypress\", { charCode: 98, keyCode: 98, which: 98 })\n\n    const node = document.createTextNode(\"b\")\n    const extraBR = element.querySelectorAll(\"br\")[1]\n    extraBR.parentNode.insertBefore(node, extraBR)\n    extraBR.parentNode.removeChild(extraBR)\n\n    await nextFrame()\n\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 3, 4 ], { bold: true })\n    expectDocument(\"a\\n\\nb\\n\")\n  })\n\n  test(\"typing an emoji after a newline at the end of block\", async () => {\n    const element = getEditorElement()\n\n    await typeCharacters(\"\\n\")\n\n    // Tap 👏🏻 on iOS\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 0, which: 0, key: \"👏🏻\" })\n    triggerEvent(element, \"keypress\", { charCode: 128079, keyCode: 128079, which: 128079, key: \"👏🏻\" })\n\n    const node = document.createTextNode(\"👏🏻\")\n    const extraBR = element.querySelectorAll(\"br\")[1]\n    extraBR.parentNode.insertBefore(node, extraBR)\n    extraBR.parentNode.removeChild(extraBR)\n\n    await nextFrame()\n    expectDocument(\"\\n👏🏻\\n\")\n  })\n\n  test(\"backspacing an attachment at the beginning of an otherwise empty document\", async () => {\n    const element = getEditorElement()\n    element.editor.loadHTML(`<img src=\"${TEST_IMAGE_URL}\" width=\"10\" height=\"10\">`)\n\n    await nextFrame()\n\n    element.editor.setSelectedRange([ 0, 1 ])\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 8, which: 8 })\n\n    element.firstElementChild.innerHTML = \"<br>\"\n\n    await nextFrame()\n\n    assert.locationRange({ index: 0, offset: 0 })\n    expectDocument(\"\\n\")\n  })\n\n  test(\"backspacing a block comment node\", async (expectDocument) => {\n    const element = getEditorElement()\n    element.editor.loadHTML(\"<blockquote>a</blockquote><div>b</div>\")\n\n    await nextFrame()\n\n    element.editor.setSelectedRange(2)\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 8, which: 8 })\n    const commentNode = element.lastChild.firstChild\n    commentNode.parentNode.removeChild(commentNode)\n\n    await nextFrame()\n\n    assert.locationRange({ index: 0, offset: 1 })\n    expectDocument(\"ab\\n\")\n  })\n\n  test(\"typing formatted text with autocapitalization on\", async () => {\n    const element = getEditorElement()\n\n    await clickToolbarButton({ attribute: \"bold\" })\n    // Type \"b\", autocapitalize to \"B\"\n    triggerEvent(element, \"keydown\", { charCode: 0, keyCode: 66, which: 66 })\n    triggerEvent(element, \"keypress\", { charCode: 98, keyCode: 98, which: 98 })\n    triggerEvent(element, \"textInput\", { data: \"B\" })\n\n    await insertNode(document.createTextNode(\"B\"))\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    assert.textAttributes([ 0, 1 ], { bold: true })\n    expectDocument(\"B\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/pasting_test.js",
    "content": "import * as config from \"trix/config\"\nimport { OBJECT_REPLACEMENT_CHARACTER } from \"trix/constants\"\n\nimport {\n  TEST_IMAGE_URL,\n  assert,\n  clickToolbarButton,\n  createFile,\n  expandSelection,\n  expectDocument,\n  moveCursor,\n  pasteContent,\n  pressKey,\n  test,\n  testGroup,\n  testIf,\n  triggerEvent,\n  typeCharacters,\n} from \"test/test_helper\"\nimport { delay, nextFrame } from \"../test_helpers/timing_helpers\"\n\ntestGroup(\"Pasting\", { template: \"editor_empty\" }, () => {\n  test(\"paste plain text\", async () => {\n    await typeCharacters(\"abc\")\n    await moveCursor(\"left\")\n    await pasteContent(\"text/plain\", \"!\")\n    expectDocument(\"ab!c\\n\")\n  })\n\n  test(\"paste simple html\", async () => {\n    await typeCharacters(\"abc\")\n    await moveCursor(\"left\")\n    await pasteContent(\"text/html\", \"&lt;\")\n    expectDocument(\"ab<c\\n\")\n  })\n\n  test(\"paste complex html\", async () => {\n    await typeCharacters(\"abc\")\n    await moveCursor(\"left\")\n    await pasteContent(\"text/html\", \"<div>Hello world<br></div><div>This is a test</div>\")\n    expectDocument(\"abHello world\\nThis is a test\\nc\\n\")\n  })\n\n  test(\"paste html in expanded selection\", async () => {\n    await typeCharacters(\"abc\")\n    await moveCursor(\"left\")\n    await expandSelection({ direction: \"left\", times: 2 })\n    await pasteContent(\"text/html\", \"<strong>x</strong>\")\n    assert.selectedRange(1)\n    expectDocument(\"xc\\n\")\n  })\n\n  test(\"paste plain text with CRLF \", async () => {\n    await pasteContent(\"text/plain\", \"a\\r\\nb\\r\\nc\")\n    expectDocument(\"a\\nb\\nc\\n\")\n  })\n\n  test(\"paste html with CRLF \", async () => {\n    await pasteContent(\"text/html\", \"<div>a<br></div>\\r\\n<div>b<br></div>\\r\\n<div>c<br></div>\")\n    expectDocument(\"a\\nb\\nc\\n\")\n  })\n\n  test(\"paste plain text with CR\", async () => {\n    await pasteContent(\"text/plain\", \"a\\rb\\rc\")\n    expectDocument(\"a\\nb\\nc\\n\")\n  })\n\n  test(\"paste html with CR\", async () => {\n    await pasteContent(\"text/html\", \"<div>a<br></div>\\r<div>b<br></div>\\r<div>c<br></div>\")\n    expectDocument(\"a\\nb\\nc\\n\")\n  })\n\n  test(\"paste unsafe html\", async () => {\n    window.unsanitized = []\n    const pasteData = {\n      \"text/plain\": \"x\",\n      \"text/html\": `\\\n        <img onload=\"window.unsanitized.push('img.onload');\" src=\"${TEST_IMAGE_URL}\">\n        <img onerror=\"window.unsanitized.push('img.onerror');\" src=\"data:image/gif;base64,TOTALLYBOGUS\">\n        <form><math><mtext></form><form><mglyph><style></math><img src onerror=\"window.unsanitized.push('img.onerror');\">\n        <script>\n          window.unsanitized.push('script tag');\n        </script>`,\n    }\n\n    await pasteContent(pasteData)\n    await delay(20)\n    assert.deepEqual(window.unsanitized, [])\n    delete window.unsanitized\n  })\n\n  test(\"paste unsafe html with noscript\", async () => {\n    window.unsanitized = []\n    const pasteData = {\n      \"text/plain\": \"x\",\n      \"text/html\": `\\\n        <div><noscript><div class=\"123</noscript>456<img src=1 onerror=window.unsanitized.push(1)//\"></div></noscript></div>\n      `\n    }\n\n    await pasteContent(pasteData)\n    await delay(20)\n    assert.deepEqual(window.unsanitized, [])\n    delete window.unsanitized\n  })\n\n  test(\"paste data-trix-attachment unsafe html\", async () => {\n    window.unsanitized = []\n    const pasteData = {\n      \"text/plain\": \"x\",\n      \"text/html\": `\\\n      copy<div data-trix-attachment=\"{&quot;contentType&quot;:&quot;text/anything&quot;,&quot;content&quot;:&quot;&lt;img src=1 onerror=window.unsanitized.push(1)&gt;HELLO123&quot;}\"></div>me\n      `,\n    }\n\n    await pasteContent(pasteData)\n    await delay(20)\n    assert.deepEqual(window.unsanitized, [])\n    delete window.unsanitized\n  })\n\n  test(\"paste data-trix-attachment unsafe html div overload\", async () => {\n    window.unsanitized = []\n    const pasteData = {\n      \"text/plain\": \"x\",\n      \"text/html\": `\\\n      <div data-trix-attachment=\"{&quot;contentType&quot;:&quot;text/html5&quot;,&quot;content&quot;:&quot;<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><a><svg><desc><svg><image><a><desc><svg><image></image></svg></desc></a></image><style><a data-trix-caca='</style><img src=x onerror=window.unsanitized.push(1)>'></a></style></svg></desc></svg></a>&quot;}\"></div>\n      `,\n    }\n\n    await pasteContent(pasteData)\n    await delay(20)\n    assert.deepEqual(window.unsanitized, [])\n    delete window.unsanitized\n  })\n\n  test(\"paste data-trix-attachment encoded mathml\", async () => {\n    window.unsanitized = []\n    const pasteData = {\n      \"text/plain\": \"x\",\n      \"text/html\": `\\\n      copy<div data-trix-attachment=\"{&quot;contentType&quot;:&quot;text/html5&quot;,&quot;content&quot;:&quot;&lt;math&gt;&lt;mtext&gt;&lt;table&gt;&lt;mglyph&gt;&lt;style&gt;&lt;img src=x onerror=window.unsanitized.push(1)&gt;&lt;/style&gt;XSS POC&quot;}\"></div>me\n      `,\n    }\n\n    await pasteContent(pasteData)\n    await delay(20)\n    assert.deepEqual(window.unsanitized, [])\n    delete window.unsanitized\n  })\n\n  test(\"paste data-trix-attachment encoded embed\", async () => {\n    window.unsanitized = []\n    const pasteData = {\n      \"text/plain\": \"x\",\n      \"text/html\": `\\\n      copy<div data-trix-attachment=\"{&quot;contentType&quot;:&quot;text/html5&quot;,&quot;content&quot;:&quot;&lt;embed src='window.unsanitized.push(1)'&gt;XSS POC&quot;}\"></div>me\n      `,\n    }\n\n    await pasteContent(pasteData)\n    await delay(20)\n    assert.deepEqual(window.unsanitized, [])\n    delete window.unsanitized\n  })\n\n  test(\"paste data-trix-attachment with serialized-attributes XSS\", async () => {\n    const pasteData = {\n      \"text/plain\": \"x\",\n      \"text/html\": `\\\n      copy<div data-trix-attachment=\"{&quot;contentType&quot;:&quot;text/html&quot;,&quot;content&quot;:&quot;&lt;img src=\\\\&quot;x\\\\&quot; data-trix-serialized-attributes=\\\\&quot;{&amp;quot;onerror&amp;quot;:&amp;quot;alert(1)&amp;quot;}\\\\&quot;&gt;&quot;}\"></div>me\n      `,\n    }\n\n    await pasteContent(pasteData)\n    await delay(20)\n    const div = document.createElement(\"div\")\n    div.innerHTML = getEditorElement().value\n    const img = div.querySelector(\"img\")\n    assert.ok(img, \"serialized HTML should contain an img element\")\n    assert.notOk(img.hasAttribute(\"onerror\"), \"img should not have an onerror attribute\")\n  })\n\n  test(\"prefers plain text when html lacks formatting\", async () => {\n    const pasteData = {\n      \"text/html\": \"<meta charset='utf-8'>a\\nb\",\n      \"text/plain\": \"a\\nb\",\n    }\n\n    await pasteContent(pasteData)\n    expectDocument(\"a\\nb\\n\")\n  })\n\n  test(\"prefers formatted html\", async () => {\n    const pasteData = {\n      \"text/html\": \"<meta charset='utf-8'>a\\n<strong>b</strong>\",\n      \"text/plain\": \"a\\nb\",\n    }\n\n    await pasteContent(pasteData)\n    expectDocument(\"a b\\n\")\n  })\n\n  test(\"paste URL\", async () => {\n    await typeCharacters(\"a\")\n    await pasteContent(\"URL\", \"http://example.com\")\n    assert.textAttributes([ 1, 18 ], { href: \"http://example.com\" })\n    expectDocument(\"ahttp://example.com\\n\")\n  })\n\n  test(\"paste URL with name\", async () => {\n    const pasteData = {\n      URL: \"http://example.com\",\n      \"public.url-name\": \"Example\",\n      \"text/plain\": \"http://example.com\",\n    }\n\n    await pasteContent(pasteData)\n    assert.textAttributes([ 0, 7 ], { href: \"http://example.com\" })\n    expectDocument(\"Example\\n\")\n  })\n\n  test(\"paste JavaScript URL\", async () => {\n    const pasteData = { URL: \"javascript:alert('XSS')\" }\n    await pasteContent(pasteData)\n    assert.textAttributes([ 0, 23 ], {})\n    expectDocument(\"javascript:alert('XSS')\\n\")\n  })\n\n  test(\"paste URL with name containing extraneous whitespace\", async () => {\n    const pasteData = {\n      URL: \"http://example.com\",\n      \"public.url-name\": \"   Example from \\n link  around\\n\\nnested \\nelements \",\n      \"text/plain\": \"http://example.com\",\n    }\n\n    await pasteContent(pasteData)\n    assert.textAttributes([ 0, 40 ], { href: \"http://example.com\" })\n    expectDocument(\"Example from link around nested elements\\n\")\n  })\n\n  test(\"paste complex html into formatted block\", async () => {\n    await typeCharacters(\"abc\")\n    await clickToolbarButton({ attribute: \"quote\" })\n    await pasteContent(\"text/html\", \"<div>Hello world<br></div><pre>This is a test</pre>\")\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 2)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ], assert.equal(block.toString(), \"abcHello world\\n\"))\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"quote\", \"code\" ])\n    assert.equal(block.toString(), \"This is a test\\n\")\n  })\n\n  test(\"paste list into list\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"abc\\n\")\n    await pasteContent(\"text/html\", \"<ul><li>one</li><li>two</li></ul>\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"abc\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"one\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"two\\n\")\n  })\n\n  test(\"paste list into quote\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    await typeCharacters(\"abc\")\n    await pasteContent(\"text/html\", \"<ul><li>one</li><li>two</li></ul>\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"quote\" ])\n    assert.equal(block.toString(), \"abc\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"quote\", \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"one\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"quote\", \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"two\\n\")\n  })\n\n  test(\"paste list into quoted list\", async () => {\n    await clickToolbarButton({ attribute: \"quote\" })\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"abc\\n\")\n    await pasteContent(\"text/html\", \"<ul><li>one</li><li>two</li></ul>\")\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"quote\", \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"abc\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"quote\", \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"one\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"quote\", \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"two\\n\")\n  })\n\n  test(\"paste nested list into empty list item\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"y\\nzz\")\n\n    getSelectionManager().setLocationRange({ index: 0, offset: 1 })\n\n    await nextFrame()\n\n    await pressKey(\"backspace\")\n    await pasteContent(\"text/html\", \"<ul><li>a<ul><li>b</li></ul></li></ul>\")\n\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"a\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"b\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"zz\\n\")\n  })\n\n  test(\"paste nested list over list item contents\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"y\\nzz\")\n    getSelectionManager().setLocationRange({ index: 0, offset: 1 })\n    await nextFrame()\n    await expandSelection(\"left\")\n    await pasteContent(\"text/html\", \"<ul><li>a<ul><li>b</li></ul></li></ul>\")\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"a\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"b\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"zz\\n\")\n  })\n\n  test(\"paste list into empty block before list\", async () => {\n    await clickToolbarButton({ attribute: \"bullet\" })\n    await typeCharacters(\"c\")\n    await moveCursor(\"left\")\n    await pressKey(\"return\")\n    getSelectionManager().setLocationRange({ index: 0, offset: 0 })\n\n    await nextFrame()\n    await pasteContent(\"text/html\", \"<ul><li>a</li><li>b</li></ul>\")\n    const document = getDocument()\n    assert.equal(document.getBlockCount(), 3)\n\n    let block = document.getBlockAtIndex(0)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"a\\n\")\n\n    block = document.getBlockAtIndex(1)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"b\\n\")\n\n    block = document.getBlockAtIndex(2)\n    assert.deepEqual(block.getAttributes(), [ \"bulletList\", \"bullet\" ])\n    assert.equal(block.toString(), \"c\\n\")\n  })\n\n  test(\"paste file\", async () => {\n    await typeCharacters(\"a\")\n    await pasteContent(\"Files\", createFile())\n    await expectDocument(`a${OBJECT_REPLACEMENT_CHARACTER}\\n`)\n  })\n\n  testIf(config.input.getLevel() === 0, \"paste event with no clipboardData\", async () => {\n    await typeCharacters(\"a\")\n    triggerEvent(document.activeElement, \"paste\")\n    document.activeElement.insertAdjacentHTML(\"beforeend\", \"<span>bc</span>\")\n    await nextFrame()\n    expectDocument(\"abc\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/text_formatting_test.js",
    "content": "import * as config from \"trix/config\"\nimport { makeElement } from \"trix/core/helpers\"\nimport Text from \"trix/models/text\"\n\nimport {\n  assert,\n  clickElement,\n  clickToolbarButton,\n  clickToolbarDialogButton,\n  collapseSelection,\n  expandSelection,\n  expectDocument,\n  fixtures,\n  insertString,\n  insertText,\n  isToolbarButtonActive,\n  isToolbarButtonDisabled,\n  isToolbarDialogActive,\n  moveCursor,\n  pressKey,\n  test,\n  testGroup,\n  testIf,\n  typeCharacters,\n  typeInToolbarDialog,\n  typeToolbarKeyCommand,\n} from \"test/test_helper\"\n\ntestGroup(\"Text formatting\", { template: \"editor_empty\" }, () => {\n  test(\"applying attributes to text\", async () => {\n    await typeCharacters(\"abc\")\n    await expandSelection(\"left\")\n    await clickToolbarButton({ attribute: \"bold\" })\n    assert.textAttributes([ 0, 2 ], {})\n    assert.textAttributes([ 2, 3 ], { bold: true })\n    assert.textAttributes([ 3, 4 ], { blockBreak: true })\n  })\n\n  test(\"applying a link to text\", async () => {\n    await typeCharacters(\"abc\")\n    await moveCursor(\"left\")\n    await expandSelection(\"left\")\n    await clickToolbarButton({ attribute: \"href\" })\n    assert.ok(isToolbarDialogActive({ attribute: \"href\" }))\n    await typeInToolbarDialog(\"http://example.com\", { attribute: \"href\" })\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 2 ], { href: \"http://example.com\" })\n    assert.textAttributes([ 2, 3 ], {})\n  })\n\n  test(\"inserting a link\", async () => {\n    await typeCharacters(\"a\")\n    await clickToolbarButton({ attribute: \"href\" })\n    assert.ok(isToolbarDialogActive({ attribute: \"href\" }))\n    await typeInToolbarDialog(\"http://example.com\", { attribute: \"href\" })\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 19 ], { href: \"http://example.com\" })\n    expectDocument(\"ahttp://example.com\\n\")\n  })\n\n  test(\"inserting a javascript: link is forbidden\", async () => {\n    await typeCharacters(\"XSS\")\n    await moveCursor(\"left\")\n    await expandSelection(\"left\")\n    await clickToolbarButton({ attribute: \"href\" })\n    assert.ok(isToolbarDialogActive({ attribute: \"href\" }))\n    await typeInToolbarDialog(\"javascript:alert('XSS')\", { attribute: \"href\" })\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 2 ], { frozen: true })\n    assert.textAttributes([ 2, 3 ], {})\n  })\n\n  test(\"editing a link\", async () => {\n    insertString(\"a\")\n    const text = Text.textForStringWithAttributes(\"bc\", { href: \"http://example.com\" })\n    insertText(text)\n    insertString(\"d\")\n    await moveCursor({ direction: \"left\", times: 2 })\n    await clickToolbarButton({ attribute: \"href\" })\n    assert.ok(isToolbarDialogActive({ attribute: \"href\" }))\n    assert.locationRange({ index: 0, offset: 1 }, { index: 0, offset: 3 })\n    await typeInToolbarDialog(\"http://example.org\", { attribute: \"href\" })\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 3 ], { href: \"http://example.org\" })\n    assert.textAttributes([ 3, 4 ], {})\n  })\n\n  test(\"removing a link\", async () => {\n    const text = Text.textForStringWithAttributes(\"ab\", { href: \"http://example.com\" })\n    insertText(text)\n    assert.textAttributes([ 0, 2 ], { href: \"http://example.com\" })\n    await expandSelection({ direction: \"left\", times: 2 })\n    await clickToolbarButton({ attribute: \"href\" })\n    await clickToolbarDialogButton({ method: \"removeAttribute\" })\n    await assert.textAttributes([ 0, 2 ], {})\n  })\n\n  test(\"selecting an attachment disables text formatting\", async () => {\n    const text = fixtures[\"file attachment\"].document.getBlockAtIndex(0).getTextWithoutBlockBreak()\n    insertText(text)\n    await typeCharacters(\"a\")\n    assert.notOk(isToolbarButtonDisabled({ attribute: \"bold\" }))\n    await expandSelection(\"left\")\n    assert.notOk(isToolbarButtonDisabled({ attribute: \"bold\" }))\n    await expandSelection(\"left\")\n    assert.ok(isToolbarButtonDisabled({ attribute: \"bold\" }))\n  })\n\n  test(\"selecting an attachment deactivates toolbar dialog\", async () => {\n    const text = fixtures[\"file attachment\"].document.getBlockAtIndex(0).getTextWithoutBlockBreak()\n    insertText(text)\n    await clickToolbarButton({ attribute: \"href\" })\n    assert.ok(isToolbarDialogActive({ attribute: \"href\" }))\n    await clickElement(getEditorElement().querySelector(\"figure\"))\n    assert.notOk(isToolbarDialogActive({ attribute: \"href\" }))\n    assert.ok(isToolbarButtonDisabled({ attribute: \"href\" }))\n  })\n\n  test(\"typing over a selected attachment does not apply disabled formatting attributes\", async () => {\n    const text = fixtures[\"file attachment\"].document.getBlockAtIndex(0).getTextWithoutBlockBreak()\n    insertText(text)\n    await expandSelection(\"left\")\n    assert.ok(isToolbarButtonDisabled({ attribute: \"bold\" }))\n    await typeCharacters(\"a\")\n    assert.textAttributes([ 0, 1 ], {})\n    expectDocument(\"a\\n\")\n  })\n\n  test(\"applying a link to an attachment with a host-provided href\", async () => {\n    const text = fixtures[\"file attachment\"].document.getBlockAtIndex(0).getTextWithoutBlockBreak()\n    insertText(text)\n    await typeCharacters(\"a\")\n    assert.notOk(isToolbarButtonDisabled({ attribute: \"href\" }))\n    await expandSelection(\"left\")\n    assert.notOk(isToolbarButtonDisabled({ attribute: \"href\" }))\n    await expandSelection(\"left\")\n    assert.ok(isToolbarButtonDisabled({ attribute: \"href\" }))\n  })\n\n  test(\"typing after a link\", async () => {\n    await typeCharacters(\"ab\")\n    await expandSelection({ direction: \"left\", times: 2 })\n    await clickToolbarButton({ attribute: \"href\" })\n    await typeInToolbarDialog(\"http://example.com\", { attribute: \"href\" })\n    await collapseSelection(\"right\")\n    assert.locationRange({ index: 0, offset: 2 })\n    await typeCharacters(\"c\")\n    assert.textAttributes([ 0, 2 ], { href: \"http://example.com\" })\n    assert.textAttributes([ 2, 3 ], {})\n    await moveCursor(\"left\")\n    assert.notOk(isToolbarButtonActive({ attribute: \"href\" }))\n    await moveCursor(\"left\")\n    assert.ok(isToolbarButtonActive({ attribute: \"href\" }))\n  })\n\n  test(\"applying formatting and then typing\", async () => {\n    await typeCharacters(\"a\")\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"bcd\")\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"e\")\n    assert.textAttributes([ 0, 1 ], {})\n    assert.textAttributes([ 1, 4 ], { bold: true })\n    assert.textAttributes([ 4, 5 ], {})\n  })\n\n  test(\"applying formatting and then moving the cursor away\", async () => {\n    await typeCharacters(\"abc\")\n    await moveCursor(\"left\")\n    assert.notOk(isToolbarButtonActive({ attribute: \"bold\" }))\n    await clickToolbarButton({ attribute: \"bold\" })\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    await moveCursor(\"right\")\n    assert.notOk(isToolbarButtonActive({ attribute: \"bold\" }))\n    await moveCursor(\"left\")\n    assert.notOk(isToolbarButtonActive({ attribute: \"bold\" }))\n    assert.textAttributes([ 0, 3 ], {})\n    assert.textAttributes([ 3, 4 ], { blockBreak: true })\n  })\n\n  test(\"applying formatting to an unfocused editor\", async () => {\n    const input = makeElement(\"input\", { type: \"text\" })\n    document.body.appendChild(input)\n    input.focus()\n\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"a\")\n    assert.textAttributes([ 0, 1 ], { bold: true })\n    document.body.removeChild(input)\n  })\n\n  test(\"editing formatted text\", async () => {\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"ab\")\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"c\")\n    assert.notOk(isToolbarButtonActive({ attribute: \"bold\" }))\n    await moveCursor(\"left\")\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    await moveCursor(\"left\")\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    await typeCharacters(\"Z\")\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    assert.textAttributes([ 0, 3 ], { bold: true })\n    assert.textAttributes([ 3, 4 ], {})\n    assert.textAttributes([ 4, 5 ], { blockBreak: true })\n    await moveCursor(\"right\")\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n    await moveCursor(\"right\")\n    assert.notOk(isToolbarButtonActive({ attribute: \"bold\" }))\n  })\n\n  testIf(config.input.getLevel() === 0, \"key command activates toolbar button\", async () => {\n    await typeToolbarKeyCommand({ attribute: \"bold\" })\n    assert.ok(isToolbarButtonActive({ attribute: \"bold\" }))\n  })\n\n  test(\"backspacing newline after text\", async () => {\n    await typeCharacters(\"a\\n\")\n    await pressKey(\"backspace\")\n    expectDocument(\"a\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/system/undo_test.js",
    "content": "import {\n  assert,\n  clickToolbarButton,\n  expandSelection,\n  moveCursor,\n  test,\n  testGroup,\n  typeCharacters,\n} from \"test/test_helper\"\n\ntestGroup(\"Undo/Redo\", { template: \"editor_empty\" }, async () => {\n  test(\"typing and undoing\", async () => {\n    const first = getDocument().copy()\n    await typeCharacters(\"abc\")\n    assert.notOk(getDocument().isEqualTo(first))\n    await clickToolbarButton({ action: \"undo\" })\n    assert.ok(getDocument().isEqualTo(first))\n  })\n\n  test(\"typing, formatting, typing, and undoing\", async () => {\n    const first = getDocument().copy()\n    await typeCharacters(\"abc\")\n    const second = getDocument().copy()\n    await clickToolbarButton({ attribute: \"bold\" })\n    await typeCharacters(\"def\")\n    const third = getDocument().copy()\n    await clickToolbarButton({ action: \"undo\" })\n    assert.ok(getDocument().isEqualTo(second))\n    await clickToolbarButton({ action: \"undo\" })\n    assert.ok(getDocument().isEqualTo(first))\n    await clickToolbarButton({ action: \"redo\" })\n    assert.ok(getDocument().isEqualTo(second))\n    await clickToolbarButton({ action: \"redo\" })\n    assert.ok(getDocument().isEqualTo(third))\n  })\n\n  test(\"formatting changes are batched by location range\", async () => {\n    await typeCharacters(\"abc\")\n    const first = getDocument().copy()\n    await expandSelection(\"left\")\n    await clickToolbarButton({ attribute: \"bold\" })\n    await clickToolbarButton({ attribute: \"italic\" })\n    const second = getDocument().copy()\n    await moveCursor(\"left\")\n    await expandSelection(\"left\")\n    await clickToolbarButton({ attribute: \"italic\" })\n    const third = getDocument().copy()\n    await clickToolbarButton({ action: \"undo\" })\n    assert.ok(getDocument().isEqualTo(second))\n    await clickToolbarButton({ action: \"undo\" })\n    assert.ok(getDocument().isEqualTo(first))\n    await clickToolbarButton({ action: \"redo\" })\n    assert.ok(getDocument().isEqualTo(second))\n    await clickToolbarButton({ action: \"redo\" })\n    assert.ok(getDocument().isEqualTo(third))\n  })\n\n  test(\"block formatting are undoable\", async () => {\n    await typeCharacters(\"abc\")\n    const first = getDocument().copy()\n    await clickToolbarButton({ attribute: \"heading1\" })\n    const second = getDocument().copy()\n    await clickToolbarButton({ action: \"undo\" })\n    assert.ok(getDocument().isEqualTo(first))\n    clickToolbarButton({ action: \"redo\" })\n    assert.ok(getDocument().isEqualTo(second))\n  })\n})\n"
  },
  {
    "path": "src/test/system.js",
    "content": "import \"test/system/accessibility_test\"\nimport \"test/system/attachment_caption_test\"\nimport \"test/system/attachment_gallery_test\"\nimport \"test/system/attachment_test\"\nimport \"test/system/basic_input_test\"\nimport \"test/system/block_formatting_test\"\nimport \"test/system/caching_test\"\nimport \"test/system/canceled_input_test\"\nimport \"test/system/composition_input_test\"\nimport \"test/system/cursor_movement_test\"\nimport \"test/system/custom_element_test\"\nimport \"test/system/html_loading_test\"\nimport \"test/system/html_reparsing_test\"\nimport \"test/system/html_replacement_test\"\nimport \"test/system/installation_process_test\"\nimport \"test/system/level_2_input_test\"\nimport \"test/system/list_formatting_test\"\nimport \"test/system/morphing_test\"\nimport \"test/system/mutation_input_test\"\nimport \"test/system/pasting_test\"\nimport \"test/system/text_formatting_test\"\nimport \"test/system/undo_test\"\n"
  },
  {
    "path": "src/test/test.js",
    "content": "/* eslint-disable\n*/\nimport Trix from \"trix/trix\"\n\nimport \"trix/core/helpers/global\"\nimport \"test/test_helper\"\n\nimport \"test/unit\"\nimport \"test/system\"\n"
  },
  {
    "path": "src/test/test_helper.js",
    "content": "import Trix from \"trix/trix\"\n\nexport * from \"trix/core/helpers/functions\"\nexport * from \"trix/core/helpers/global\"\nexport * from \"./test_helpers/event_helpers\"\nexport * from \"./test_helpers/assertions\"\nexport * from \"./test_helpers/test_helpers\"\nexport * from \"./test_helpers/test_stubs\"\nexport * from \"./test_helpers/functions\"\nexport * from \"./test_helpers/fixtures/fixtures\"\nexport * from \"./test_helpers/event_helpers\"\nexport * from \"./test_helpers/input_helpers\"\nexport * from \"./test_helpers/editor_helpers\"\nexport * from \"./test_helpers/toolbar_helpers\"\nexport * from \"./test_helpers/selection_helpers\"\n\nwindow.Trix = Trix\nTrix.config.undo.interval = 0\n\nQUnit.config.hidepassed = true\nQUnit.config.testTimeout = 20000\n\ndocument.head.insertAdjacentHTML(\n  \"beforeend\",\n  `<style type=\"text/css\">\n    #trix-container { height: 150px; }\n    trix-toolbar { margin-bottom: 10px; }\n    trix-toolbar button { border: 1px solid #ccc; background: #fff; }\n    trix-toolbar button.active { background: #d3e6fd; }\n    trix-toolbar button:disabled { color: #ccc; }\n    #qunit { position: relative !important; }\n  </style>`\n)\n"
  },
  {
    "path": "src/test/test_helpers/assertions.js",
    "content": "import DocumentView from \"trix/views/document_view\"\nimport { normalizeRange } from \"trix/core/helpers\"\n\nconst { assert } = QUnit\n\nassert.locationRange = function (start, end) {\n  const expectedLocationRange = normalizeRange([ start, end ])\n  const actualLocationRange = getEditorController().getLocationRange()\n  this.deepEqual(actualLocationRange, expectedLocationRange)\n}\n\nassert.selectedRange = function (range) {\n  const expectedRange = normalizeRange(range)\n  const actualRange = getEditor().getSelectedRange()\n  this.deepEqual(actualRange, expectedRange)\n}\n\nassert.textAttributes = function (range, attributes) {\n  const document = window.getDocument().getDocumentAtRange(range)\n  const blocks = document.getBlocks()\n  if (blocks.length !== 1) {\n    throw `range ${JSON.stringify(range)} spans more than one block`\n  }\n\n  const locationRange = window.getDocument().locationRangeFromRange(range)\n  const textIndex = locationRange[0].index\n  const textRange = [ locationRange[0].offset, locationRange[1].offset ]\n  const text = window.getDocument().getTextAtIndex(textIndex).getTextAtRange(textRange)\n  const pieces = text.getPieces()\n  if (pieces.length !== 1) {\n    throw `range ${JSON.stringify(range)} must only span one piece`\n  }\n\n  const piece = pieces[0]\n  this.deepEqual(piece.getAttributes(), attributes)\n}\n\nassert.blockAttributes = function (range, attributes) {\n  const document = window.getDocument().getDocumentAtRange(range)\n  const blocks = document.getBlocks()\n  if (blocks.length !== 1) {\n    throw `range ${JSON.stringify(range)} spans more than one block`\n  }\n\n  const block = blocks[0]\n  this.deepEqual(block.getAttributes(), attributes)\n}\n\nassert.documentHTMLEqual = function (trixDocument, html) {\n  this.equal(getHTML(trixDocument), html)\n}\n\nconst getHTML = (trixDocument) => DocumentView.render(trixDocument).innerHTML\n\nexport const expectDocument = (expectedDocumentValue, element) => {\n  if (!element) element = getEditorElement()\n  assert.equal(element.editor.getDocument().toString(), expectedDocumentValue)\n}\n\nexport { assert, getHTML }\n"
  },
  {
    "path": "src/test/test_helpers/editor_helpers.js",
    "content": "import { TEST_IMAGE_URL } from \"test/test_helpers/fixtures/test_image_url\"\nimport Attachment from \"trix/models/attachment\"\n\nexport const insertString = function (string) {\n  getComposition().insertString(string)\n  render()\n}\n\nexport const insertText = function (text) {\n  getComposition().insertText(text)\n  render()\n}\n\nexport const insertDocument = function (document) {\n  getComposition().insertDocument(document)\n  render()\n}\n\nexport const insertFile = function (file) {\n  getComposition().insertFile(file)\n  render()\n}\n\nexport const insertAttachment = function (attachment) {\n  getComposition().insertAttachment(attachment)\n  render()\n}\n\nexport const insertAttachments = function (attachments) {\n  getComposition().insertAttachments(attachments)\n  render()\n}\n\nexport const insertImageAttachment = function (attributes) {\n  const attachment = createImageAttachment(attributes)\n  return insertAttachment(attachment)\n}\n\nexport const createImageAttachment = function (attributes) {\n  if (!attributes) {\n    attributes = {\n      url: TEST_IMAGE_URL,\n      width: 10,\n      height: 10,\n      filename: \"image.gif\",\n      filesize: 35,\n      contentType: \"image/gif\",\n    }\n  }\n\n  return new Attachment(attributes)\n}\n\nexport const replaceDocument = function (document) {\n  getComposition().setDocument(document)\n  render()\n}\n\n\nconst render = () => getEditorController().render()\n"
  },
  {
    "path": "src/test/test_helpers/event_helpers.js",
    "content": "export const createEvent = function (type, properties = {}) {\n  const event = document.createEvent(\"Events\")\n  event.initEvent(type, true, true)\n  for (const key in properties) {\n    const value = properties[key]\n    event[key] = value\n  }\n  return event\n}\n\nexport const triggerEvent = (element, type, properties) => element.dispatchEvent(createEvent(type, properties))\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_default_aria_label.js",
    "content": "export default () =>\n  `<trix-editor id=\"editor-without-labels\"></trix-editor>\n\n  <label for=\"editor-with-aria-label\"><span>Label text</span></label>\n  <trix-editor id=\"editor-with-aria-label\" aria-label=\"ARIA Label text\"></trix-editor>\n\n  <span id=\"aria-labelledby-id\">ARIA Labelledby</span>\n  <label for=\"editor-with-aria-labelledby\"><span>Label text</span></label>\n  <trix-editor id=\"editor-with-aria-labelledby\" aria-labelledby=\"aria-labelledby-id\"></trix-editor>\n\n  <label for=\"editor-with-labels\"><span>Label 1</span></label>\n  <label for=\"editor-with-labels\"><span>Label 2</span></label>\n  <label for=\"editor-with-labels\"><span>Label 3</span></label>\n  <label>\n    <span>Label 4</span>\n    <trix-editor id=\"editor-with-labels\"></trix-editor>\n  </label>\n\n  <label id=\"modified-label\" for=\"editor-with-modified-label\">Original Value</label>\n  <trix-editor id=\"editor-with-modified-label\"></trix-editor>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_empty.js",
    "content": "export default () => \"<trix-editor autofocus placeholder=\\\"Say hello...\\\"></trix-editor>\"\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_html.js",
    "content": "export default () =>\n  `<input id=\"my_input\" type=\"hidden\" value=\"&lt;div&gt;Hello world&lt;/div&gt;\">\n  <trix-editor input=\"my_input\" autofocus placeholder=\"Say hello...\"></trix-editor>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_in_table.js",
    "content": "export default () =>\n  `<table>\n    <tr>\n      <td>\n        <trix-editor></trix-editor>\n      </td>\n    </tr>\n  </table>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_with_block_styles.js",
    "content": "export default () =>\n  `<style type=\"text/css\">\n    blockquote { font-style: italic; }\n    li { font-weight: bold; }\n  </style>\n\n  <trix-editor class=\"trix-content\"></trix-editor>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_with_bold_styles.js",
    "content": "export default () =>\n  `<style type=\"text/css\">\n    strong { font-weight: 500; }\n    span { font-weight: 600; }\n    article { font-weight: bold; }\n  </style>\n\n  <trix-editor class=\"trix-content\"></trix-editor>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_with_image.js",
    "content": "import { TEST_IMAGE_URL } from \"./test_image_url\"\n\nexport default () =>\n  `<trix-editor input=\"my_input\" autofocus placeholder=\"Say hello...\"></trix-editor>\n  <input id=\"my_input\" type=\"hidden\" value=\"ab&lt;img src=&quot;${TEST_IMAGE_URL}&quot; width=&quot;10&quot; height=&quot;10&quot;&gt;\">`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_with_labels.js",
    "content": "export default () =>\n  `<label id=\"label-1\" for=\"editor\"><span>Label 1</span></label>\n   <label id=\"label-2\">Label 2</label>\n   <trix-editor id=\"editor\"></trix-editor>\n   <label id=\"label-3\" for=\"editor\">Label 3</label>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_with_styled_content.js",
    "content": "export default () =>\n  `<style type=\"text/css\">\n    .trix-content figure.attachment {\n      display: inline-block;\n    }\n  </style>\n\n  <trix-editor class=\"trix-content\"></trix-editor>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editor_with_toolbar_and_input.js",
    "content": "export default () =>\n  `<ul id=\"my_editor\">\n    <li><trix-toolbar id=\"my_toolbar\"></trix-toolbar></li>\n    <li><trix-editor toolbar=\"my_toolbar\" input=\"my_input\" autofocus placeholder=\"Say hello...\"></trix-editor></li>\n    <li><input id=\"my_input\" type=\"hidden\" value=\"&lt;div&gt;Hello world&lt;/div&gt;\"></li>\n  </ul>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/editors_with_forms.js",
    "content": "export default () =>\n  `<form id=\"ancestor-form\">\n    <trix-editor id=\"editor-with-ancestor-form\" name=\"editor-with-ancestor-form\"></trix-editor>\n  </form>\n\n  <form id=\"input-form\">\n    <input type=\"hidden\" id=\"hidden-input\">\n  </form>\n  <trix-editor id=\"editor-with-input-form\" input=\"hidden-input\"></trix-editor>\n\n  <trix-editor id=\"editor-with-no-form\"></trix-editor>\n  <fieldset id=\"fieldset\"><trix-editor id=\"editor-within-fieldset\"></fieldset>`\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/fixtures.js",
    "content": "import * as config from \"trix/config\"\nimport { ZERO_WIDTH_SPACE } from \"trix/constants\"\nimport { makeElement } from \"trix/core/helpers\"\n\nimport Text from \"trix/models/text\"\nimport Block from \"trix/models/block\"\nimport Attachment from \"trix/models/attachment\"\nimport Document from \"trix/models/document\"\nimport StringPiece from \"trix/models/string_piece\"\n\nimport editorDefaultAriaLabel from \"./editor_default_aria_label\"\nimport editorEmpty from \"./editor_empty\"\nimport editorHtml from \"./editor_html\"\nimport editorInTable from \"./editor_in_table\"\nimport editorWithBlockStyles from \"./editor_with_block_styles\"\nimport editorWithBoldStyles from \"./editor_with_bold_styles\"\nimport editorWithImage from \"./editor_with_image\"\nimport editorWithLabels from \"./editor_with_labels\"\nimport editorWithStyledContent from \"./editor_with_styled_content\"\nimport editorWithToolbarAndInput from \"./editor_with_toolbar_and_input\"\nimport editorsWithForms from \"./editors_with_forms\"\nimport { TEST_IMAGE_URL } from \"./test_image_url\"\n\nexport const fixtureTemplates = {\n  \"editor_default_aria_label\": editorDefaultAriaLabel,\n  \"editor_empty\": editorEmpty,\n  \"editor_html\": editorHtml,\n  \"editor_in_table\": editorInTable,\n  \"editor_with_block_styles\": editorWithBlockStyles,\n  \"editor_with_bold_styles\": editorWithBoldStyles,\n  \"editor_with_image\": editorWithImage,\n  \"editor_with_labels\": editorWithLabels,\n  \"editor_with_styled_content\": editorWithStyledContent,\n  \"editor_with_toolbar_and_input\": editorWithToolbarAndInput,\n  \"editors_with_forms\": editorsWithForms,\n}\n\nexport { TEST_IMAGE_URL }\n\nconst { css } = config\n\nconst createDocument = function (...parts) {\n  const blocks = parts.map((part) => {\n    const [ string, textAttributes, blockAttributes, htmlAttributes = {} ] = Array.from(part)\n    const text = Text.textForStringWithAttributes(string, textAttributes)\n    return new Block(text, blockAttributes, htmlAttributes)\n  })\n\n  return new Document(blocks)\n}\n\nexport const createCursorTarget = (name) =>\n  makeElement({\n    tagName: \"span\",\n    textContent: ZERO_WIDTH_SPACE,\n    data: {\n      trixCursorTarget: name,\n      trixSerialize: false,\n    },\n  })\n\nconst cursorTargetLeft = createCursorTarget(\"left\").outerHTML\nconst cursorTargetRight = createCursorTarget(\"right\").outerHTML\n\nconst blockComment = \"<!--block-->\"\n\nconst removeWhitespace = (string) => string.replace(/\\s/g, \"\")\n\nexport const fixtures = {\n  \"bold text\": {\n    document: createDocument([ \"abc\", { bold: true } ]),\n    html: `<div>${blockComment}<strong>abc</strong></div>`,\n    serializedHTML: \"<div><strong>abc</strong></div>\",\n  },\n\n  \"bold, italic text\": {\n    document: createDocument([ \"abc\", { bold: true, italic: true } ]),\n    html: `<div>${blockComment}<strong><em>abc</em></strong></div>`,\n  },\n\n  \"text with newline\": {\n    document: createDocument([ \"ab\\nc\" ]),\n    html: `<div>${blockComment}ab<br>c</div>`,\n  },\n\n  \"text with link\": {\n    document: createDocument([ \"abc\", { href: \"http://example.com\" } ]),\n    html: `<div>${blockComment}<a href=\"http://example.com\">abc</a></div>`,\n  },\n\n  \"text with link and formatting\": {\n    document: createDocument([ \"abc\", { italic: true, href: \"http://example.com\" } ]),\n    html: `<div>${blockComment}<a href=\"http://example.com\"><em>abc</em></a></div>`,\n  },\n\n  \"partially formatted link\": {\n    document: new Document([\n      new Block(\n        new Text([\n          new StringPiece(\"ab\", { href: \"http://example.com\" }),\n          new StringPiece(\"c\", { href: \"http://example.com\", italic: true }),\n        ])\n      ),\n    ]),\n    html: `<div>${blockComment}<a href=\"http://example.com\">ab<em>c</em></a></div>`,\n  },\n\n  \"spaces 1\": {\n    document: createDocument([ \" a\" ]),\n    html: `<div>${blockComment}&nbsp;a</div>`,\n  },\n\n  \"spaces 2\": {\n    document: createDocument([ \"  a\" ]),\n    html: `<div>${blockComment}&nbsp; a</div>`,\n  },\n\n  \"spaces 3\": {\n    document: createDocument([ \"   a\" ]),\n    html: `<div>${blockComment}&nbsp; &nbsp;a</div>`,\n  },\n\n  \"spaces 4\": {\n    document: createDocument([ \" a \" ]),\n    html: `<div>${blockComment}&nbsp;a&nbsp;</div>`,\n  },\n\n  \"spaces 5\": {\n    document: createDocument([ \"a  b\" ]),\n    html: `<div>${blockComment}a&nbsp; b</div>`,\n  },\n\n  \"spaces 6\": {\n    document: createDocument([ \"a   b\" ]),\n    html: `<div>${blockComment}a &nbsp; b</div>`,\n  },\n\n  \"spaces 7\": {\n    document: createDocument([ \"a    b\" ]),\n    html: `<div>${blockComment}a&nbsp; &nbsp; b</div>`,\n  },\n\n  \"spaces 8\": {\n    document: createDocument([ \"a b \" ]),\n    html: `<div>${blockComment}a b&nbsp;</div>`,\n  },\n\n  \"spaces 9\": {\n    document: createDocument([ \"a b c\" ]),\n    html: `<div>${blockComment}a b c</div>`,\n  },\n\n  \"spaces 10\": {\n    document: createDocument([ \"a \" ]),\n    html: `<div>${blockComment}a&nbsp;</div>`,\n  },\n\n  \"spaces 11\": {\n    document: createDocument([ \"a  \" ]),\n    html: `<div>${blockComment}a &nbsp;</div>`,\n  },\n\n  \"spaces and formatting\": {\n    document: new Document([\n      new Block(\n        new Text([\n          new StringPiece(\" a \"),\n          new StringPiece(\"b\", { href: \"http://b.com\" }),\n          new StringPiece(\" \"),\n          new StringPiece(\"c\", { bold: true }),\n          new StringPiece(\" d\"),\n          new StringPiece(\" e \", { italic: true }),\n          new StringPiece(\" f  \"),\n        ])\n      ),\n    ]),\n    html: `<div>${blockComment}&nbsp;a <a href=\"http://b.com\">b</a> <strong>c</strong> d<em> e </em>&nbsp;f &nbsp;</div>`,\n  },\n\n  \"quote formatted block\": {\n    document: createDocument([ \"abc\", {}, [ \"quote\" ] ]),\n    html: `<blockquote>${blockComment}abc</blockquote>`,\n  },\n\n  \"code formatted block\": {\n    document: createDocument([ \"123\", {}, [ \"code\" ] ]),\n    html: `<pre>${blockComment}123</pre>`,\n  },\n\n  \"code with newline\": {\n    document: createDocument([ \"12\\n3\", {}, [ \"code\" ] ]),\n    html: `<pre>${blockComment}12\\n3</pre>`,\n  },\n\n  \"code with custom language\": {\n    document: createDocument([ \"puts \\\"Hello world!\\\"\", {}, [ \"code\" ], { \"language\": \"ruby\" } ]),\n    html: `<pre language=\"ruby\">${blockComment}puts \"Hello world!\"</pre>`,\n    serializedHTML: \"<pre language=\\\"ruby\\\">puts \\\"Hello world!\\\"</pre>\"\n  },\n\n  \"multiple blocks with block comments in their text\": {\n    document: createDocument([ `a${blockComment}b`, {}, [ \"quote\" ] ], [ `${blockComment}c`, {}, [ \"code\" ] ]),\n    html: `<blockquote>${blockComment}a&lt;!--block--&gt;b</blockquote><pre>${blockComment}&lt;!--block--&gt;c</pre>`,\n    serializedHTML: \"<blockquote>a&lt;!--block--&gt;b</blockquote><pre>&lt;!--block--&gt;c</pre>\",\n  },\n\n  \"unordered list with one item\": {\n    document: createDocument([ \"a\", {}, [ \"bulletList\", \"bullet\" ] ]),\n    html: `<ul><li>${blockComment}a</li></ul>`,\n  },\n\n  \"unordered list with bold text\": {\n    document: createDocument([ \"a\", { bold: true }, [ \"bulletList\", \"bullet\" ] ]),\n    html: `<ul><li>${blockComment}<strong>a</strong></li></ul>`,\n  },\n\n  \"unordered list with partially formatted text\": {\n    document: new Document([\n      new Block(new Text([ new StringPiece(\"a\"), new StringPiece(\"b\", { italic: true }) ]), [ \"bulletList\", \"bullet\" ]),\n    ]),\n    html: `<ul><li>${blockComment}a<em>b</em></li></ul>`,\n  },\n\n  \"unordered list with two items\": {\n    document: createDocument([ \"a\", {}, [ \"bulletList\", \"bullet\" ] ], [ \"b\", {}, [ \"bulletList\", \"bullet\" ] ]),\n    html: `<ul><li>${blockComment}a</li><li>${blockComment}b</li></ul>`,\n  },\n\n  \"unordered list surrounded by unformatted blocks\": {\n    document: createDocument([ \"a\" ], [ \"b\", {}, [ \"bulletList\", \"bullet\" ] ], [ \"c\" ]),\n    html: `<div>${blockComment}a</div><ul><li>${blockComment}b</li></ul><div>${blockComment}c</div>`,\n  },\n\n  \"ordered list\": {\n    document: createDocument([ \"a\", {}, [ \"numberList\", \"number\" ] ]),\n    html: `<ol><li>${blockComment}a</li></ol>`,\n  },\n\n  \"ordered list and an unordered list\": {\n    document: createDocument([ \"a\", {}, [ \"bulletList\", \"bullet\" ] ], [ \"b\", {}, [ \"numberList\", \"number\" ] ]),\n    html: `<ul><li>${blockComment}a</li></ul><ol><li>${blockComment}b</li></ol>`,\n  },\n\n  \"empty block with attributes\": {\n    document: createDocument([ \"\", {}, [ \"quote\" ] ]),\n    html: `<blockquote>${blockComment}<br></blockquote>`,\n  },\n\n  \"image attachment\": (() => {\n    const attrs = {\n      url: TEST_IMAGE_URL,\n      filename: \"example.png\",\n      filesize: 98203,\n      contentType: \"image/png\",\n      width: 1,\n      height: 1,\n    }\n    const attachment = new Attachment(attrs)\n    const text = Text.textForAttachmentWithAttributes(attachment)\n\n    const image = makeElement(\"img\", { src: attrs.url, \"data-trix-mutable\": true, width: 1, height: 1 })\n    image.dataset.trixStoreKey = [ \"imageElement\", attachment.id, image.src, image.width, image.height ].join(\"/\")\n\n    const caption = makeElement({ tagName: \"figcaption\", className: css.attachmentCaption })\n    caption.innerHTML = `<span class=\"${css.attachmentName}\">${attrs.filename}</span> <span class=\"${css.attachmentSize}\">95.9 KB</span>`\n\n    const figure = makeElement({\n      tagName: \"figure\",\n      className: \"attachment attachment--preview attachment--png\",\n      editable: false,\n      data: {\n        trixAttachment: JSON.stringify(attachment),\n        trixContentType: \"image/png\",\n        trixId: attachment.id,\n      },\n    })\n\n    figure.setAttribute(\"contenteditable\", false)\n    figure.appendChild(image)\n    figure.appendChild(caption)\n\n    const serializedFigure = figure.cloneNode(true)\n\n    ;[ \"data-trix-id\", \"data-trix-mutable\", \"data-trix-store-key\", \"contenteditable\" ].forEach((attribute) => {\n      serializedFigure.removeAttribute(attribute)\n\n      Array.from(serializedFigure.querySelectorAll(`[${attribute}]`)).forEach((element) => {\n        element.removeAttribute(attribute)\n      })\n    })\n\n    return {\n      html: `<div>${blockComment}${cursorTargetLeft}${figure.outerHTML}${cursorTargetRight}</div>`,\n      serializedHTML: `<div>${serializedFigure.outerHTML}</div>`,\n      document: new Document([ new Block(text) ]),\n    }\n  })(),\n\n  \"text with newlines and image attachment\": (() => {\n    const stringText = Text.textForStringWithAttributes(\"a\\nb\")\n\n    const attrs = {\n      url: TEST_IMAGE_URL,\n      filename: \"example.png\",\n      filesize: 98203,\n      contentType: \"image/png\",\n      width: 1,\n      height: 1,\n    }\n    const attachment = new Attachment(attrs)\n    const attachmentText = Text.textForAttachmentWithAttributes(attachment)\n\n    const image = makeElement(\"img\", { src: attrs.url, \"data-trix-mutable\": true, width: 1, height: 1 })\n    image.dataset.trixStoreKey = [ \"imageElement\", attachment.id, image.src, image.width, image.height ].join(\"/\")\n\n    const caption = makeElement({ tagName: \"figcaption\", className: css.attachmentCaption })\n    caption.innerHTML = `<span class=\"${css.attachmentName}\">${attrs.filename}</span> <span class=\"${css.attachmentSize}\">95.9 KB</span>`\n\n    const figure = makeElement({\n      tagName: \"figure\",\n      className: \"attachment attachment--preview attachment--png\",\n      editable: false,\n      data: {\n        trixAttachment: JSON.stringify(attachment),\n        trixContentType: \"image/png\",\n        trixId: attachment.id,\n      },\n    })\n\n    figure.appendChild(image)\n    figure.appendChild(caption)\n\n    const serializedFigure = figure.cloneNode(true)\n\n    ;[ \"data-trix-id\", \"data-trix-mutable\", \"data-trix-store-key\", \"contenteditable\" ].forEach((attribute) => {\n      serializedFigure.removeAttribute(attribute)\n\n      Array.from(serializedFigure.querySelectorAll(`[${attribute}]`)).forEach((element) => {\n        element.removeAttribute(attribute)\n      })\n    })\n\n    const text = stringText.appendText(attachmentText)\n\n    return {\n      html: `<div>${blockComment}a<br>b${cursorTargetLeft}${figure.outerHTML}${cursorTargetRight}</div>`,\n      serializedHTML: `<div>a<br>b${serializedFigure.outerHTML}</div>`,\n      document: new Document([ new Block(text) ]),\n    }\n  })(),\n\n  \"image attachment with edited caption\": (() => {\n    const attrs = {\n      url: TEST_IMAGE_URL,\n      filename: \"example.png\",\n      filesize: 123,\n      contentType: \"image/png\",\n      width: 1,\n      height: 1,\n    }\n    const attachment = new Attachment(attrs)\n    const textAttrs = { caption: \"Example\" }\n    const text = Text.textForAttachmentWithAttributes(attachment, textAttrs)\n\n    const image = makeElement(\"img\", { src: attrs.url, \"data-trix-mutable\": true, width: 1, height: 1 })\n    image.dataset.trixStoreKey = [ \"imageElement\", attachment.id, image.src, image.width, image.height ].join(\"/\")\n\n    const caption = makeElement({\n      tagName: \"figcaption\",\n      className: `${css.attachmentCaption} ${css.attachmentCaption}--edited`,\n      textContent: \"Example\",\n    })\n\n    const figure = makeElement({\n      tagName: \"figure\",\n      className: \"attachment attachment--preview attachment--png\",\n      editable: false,\n      data: {\n        trixAttachment: JSON.stringify(attachment),\n        trixContentType: \"image/png\",\n        trixId: attachment.id,\n        trixAttributes: JSON.stringify(textAttrs),\n      },\n    })\n\n    figure.appendChild(image)\n    figure.appendChild(caption)\n\n    return {\n      html: `<div>${blockComment}${cursorTargetLeft}${figure.outerHTML}${cursorTargetRight}</div>`,\n      document: new Document([ new Block(text) ]),\n    }\n  })(),\n\n  \"file attachment\": (() => {\n    const attrs = {\n      href: \"http://example.com/example.pdf\",\n      filename: \"example.pdf\",\n      filesize: 34038769,\n      contentType: \"application/pdf\",\n    }\n    const attachment = new Attachment(attrs)\n    const text = Text.textForAttachmentWithAttributes(attachment)\n\n    const figure = makeElement({\n      tagName: \"figure\",\n      className: \"attachment attachment--file attachment--pdf\",\n      editable: false,\n      data: {\n        trixAttachment: JSON.stringify(attachment),\n        trixContentType: \"application/pdf\",\n        trixId: attachment.id,\n      },\n    })\n\n    const caption = `<figcaption class=\"${css.attachmentCaption}\"><span class=\"${css.attachmentName}\">${attrs.filename}</span> <span class=\"${css.attachmentSize}\">32.46 MB</span></figcaption>`\n    figure.innerHTML = caption\n\n    const link = makeElement({ tagName: \"a\", editable: false, attributes: { href: attrs.href, tabindex: -1 } })\n    Array.from(figure.childNodes).forEach((node) => {\n      link.appendChild(node)\n    })\n    figure.appendChild(link)\n\n    return {\n      html: `<div>${blockComment}${cursorTargetLeft}${figure.outerHTML}${cursorTargetRight}</div>`,\n      document: new Document([ new Block(text) ]),\n    }\n  })(),\n\n  \"pending file attachment\": (() => {\n    const attrs = { filename: \"example.pdf\", filesize: 34038769, contentType: \"application/pdf\" }\n    const attachment = new Attachment(attrs)\n    attachment.file = {}\n    const text = Text.textForAttachmentWithAttributes(attachment)\n\n    const figure = makeElement({\n      tagName: \"figure\",\n      className: \"attachment attachment--file attachment--pdf\",\n      editable: false,\n      data: {\n        trixAttachment: JSON.stringify(attachment),\n        trixContentType: \"application/pdf\",\n        trixId: attachment.id,\n        trixSerialize: false,\n      },\n    })\n\n    const progress = makeElement({\n      tagName: \"progress\",\n      attributes: {\n        class: \"attachment__progress\",\n        value: 0,\n        max: 100,\n      },\n      data: {\n        trixMutable: true,\n        trixStoreKey: [ \"progressElement\", attachment.id ].join(\"/\"),\n      },\n    })\n\n    const caption = `<figcaption class=\"${css.attachmentCaption}\"><span class=\"${css.attachmentName}\">${attrs.filename}</span> <span class=\"${css.attachmentSize}\">32.46 MB</span></figcaption>`\n    figure.innerHTML = caption + progress.outerHTML\n\n    return {\n      html: `<div>${blockComment}${cursorTargetLeft}${figure.outerHTML}${cursorTargetRight}</div>`,\n      document: new Document([ new Block(text) ]),\n    }\n  })(),\n\n  \"content attachment\": (() => {\n    const content =\n      \"<blockquote class=\\\"twitter-tweet\\\"><p>ruby-build 20150413 is out, with definitions for 2.2.2, 2.1.6, and 2.0.0-p645 to address recent security issues: <a href=\\\"https://t.co/YEwV6NtRD8\\\">https://t.co/YEwV6NtRD8</a></p>&mdash; Sam Stephenson (@sstephenson) <a href=\\\"https://twitter.com/sstephenson/status/587715996783218688\\\">April 13, 2015</a></blockquote>\"\n    const href = \"https://twitter.com/sstephenson/status/587715996783218688\"\n    const contentType = \"embed/twitter\"\n\n    const attachment = new Attachment({ content, contentType, href })\n    const text = Text.textForAttachmentWithAttributes(attachment)\n\n    const figure = makeElement({\n      tagName: \"figure\",\n      className: \"attachment attachment--content\",\n      editable: false,\n      data: {\n        trixAttachment: JSON.stringify(attachment),\n        trixContentType: contentType,\n        trixId: attachment.id,\n      },\n    })\n\n    figure.innerHTML = content\n\n    const caption = makeElement({ tagName: \"figcaption\", className: css.attachmentCaption })\n    figure.appendChild(caption)\n\n    return {\n      html: `<div>${blockComment}${cursorTargetLeft}${figure.outerHTML}${cursorTargetRight}</div>`,\n      document: new Document([ new Block(text) ]),\n    }\n  })(),\n\n  \"nested quote and code formatted block\": {\n    document: createDocument([ \"ab3\", {}, [ \"quote\", \"code\" ] ]),\n    html: `<blockquote><pre>${blockComment}ab3</pre></blockquote>`,\n  },\n\n  \"nested code and quote formatted block\": {\n    document: createDocument([ \"ab3\", {}, [ \"code\", \"quote\" ] ]),\n    html: `<pre><blockquote>${blockComment}ab3</blockquote></pre>`,\n  },\n\n  \"nested code blocks in quote\": {\n    document: createDocument(\n      [ \"a\\n\", {}, [ \"quote\" ] ],\n      [ \"b\", {}, [ \"quote\", \"code\" ] ],\n      [ \"\\nc\\n\", {}, [ \"quote\" ] ],\n      [ \"d\", {}, [ \"quote\", \"code\" ] ]\n    ),\n    html: removeWhitespace(\n      `<blockquote>\n        ${blockComment}\n        a\n        <br>\n        <br>\n        <pre>\n          ${blockComment}\n          b\n        </pre>\n        ${blockComment}\n        <br>\n        c\n        <br>\n        <br>\n        <pre>\n          ${blockComment}\n          d\n        </pre>\n      </blockquote>`\n    ),\n    serializedHTML: removeWhitespace(\n      `<blockquote>\n        a\n        <br>\n        <br>\n        <pre>\n          b\n        </pre>\n        <br>\n        c\n        <br>\n        <br>\n        <pre>\n          d\n        </pre>\n      </blockquote>`\n    ),\n  },\n\n  \"nested code, quote, and list in quote\": {\n    document: createDocument(\n      [ \"a\\n\", {}, [ \"quote\" ] ],\n      [ \"b\", {}, [ \"quote\", \"code\" ] ],\n      [ \"\\nc\\n\", {}, [ \"quote\" ] ],\n      [ \"d\", {}, [ \"quote\", \"quote\" ] ],\n      [ \"\\ne\\n\", {}, [ \"quote\" ] ],\n      [ \"f\", {}, [ \"quote\", \"bulletList\", \"bullet\" ] ]\n    ),\n    html: removeWhitespace(\n      `<blockquote>\n        ${blockComment}\n        a\n        <br>\n        <br>\n        <pre>\n          ${blockComment}\n          b\n        </pre>\n        ${blockComment}\n        <br>\n        c\n        <br>\n        <br>\n        <blockquote>\n          ${blockComment}\n          d\n        </blockquote>\n        ${blockComment}\n        <br>\n        e\n        <br>\n        <br>\n        <ul>\n          <li>\n            ${blockComment}\n            f\n          </li>\n        </ul>\n      </blockquote>`\n  ),\n    serializedHTML: removeWhitespace(\n      `<blockquote>\n        a\n        <br>\n        <br>\n        <pre>\n          b\n        </pre>\n        <br>\n        c\n        <br>\n        <br>\n        <blockquote>\n          d\n        </blockquote>\n        <br>\n        e\n        <br>\n        <br>\n        <ul>\n          <li>\n            f\n          </li>\n        </ul>\n      </blockquote>`\n    ),\n  },\n\n  \"nested quotes at different nesting levels\": {\n    document: createDocument(\n      [ \"a\", {}, [ \"quote\", \"quote\", \"quote\" ] ],\n      [ \"b\", {}, [ \"quote\", \"quote\" ] ],\n      [ \"c\", {}, [ \"quote\" ] ],\n      [ \"d\", {}, [ \"quote\", \"quote\" ] ]\n    ),\n    html: removeWhitespace(\n      `<blockquote>\n        <blockquote>\n          <blockquote>\n            ${blockComment}\n            a\n          </blockquote>\n          ${blockComment}\n          b\n        </blockquote>\n        ${blockComment}\n        c\n        <blockquote>\n          ${blockComment}\n          d\n        </blockquote>\n      </blockquote>`\n    ),\n    serializedHTML: removeWhitespace(\n      `<blockquote>\n        <blockquote>\n          <blockquote>\n            a\n          </blockquote>\n          b\n        </blockquote>\n        c\n        <blockquote>\n          d\n        </blockquote>\n      </blockquote>`\n    ),\n  },\n\n  \"nested quote and list\": {\n    document: createDocument([ \"ab3\", {}, [ \"quote\", \"bulletList\", \"bullet\" ] ]),\n    html: `<blockquote><ul><li>${blockComment}ab3</li></ul></blockquote>`,\n  },\n\n  \"nested list and quote\": {\n    document: createDocument([ \"ab3\", {}, [ \"bulletList\", \"bullet\", \"quote\" ] ]),\n    html: `<ul><li><blockquote>${blockComment}ab3</blockquote></li></ul>`,\n  },\n\n  \"nested lists and quotes\": {\n    document: createDocument(\n      [ \"a\", {}, [ \"bulletList\", \"bullet\", \"quote\" ] ],\n      [ \"b\", {}, [ \"bulletList\", \"bullet\", \"quote\" ] ]\n    ),\n    html: `<ul><li><blockquote>${blockComment}a</blockquote></li><li><blockquote>${blockComment}b</blockquote></li></ul>`,\n  },\n\n  \"nested quote and list with two items\": {\n    document: createDocument(\n      [ \"a\", {}, [ \"quote\", \"bulletList\", \"bullet\" ] ],\n      [ \"b\", {}, [ \"quote\", \"bulletList\", \"bullet\" ] ]\n    ),\n    html: `<blockquote><ul><li>${blockComment}a</li><li>${blockComment}b</li></ul></blockquote>`,\n  },\n\n  \"nested unordered lists\": {\n    document: createDocument(\n      [ \"a\", {}, [ \"bulletList\", \"bullet\" ] ],\n      [ \"b\", {}, [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ] ],\n      [ \"c\", {}, [ \"bulletList\", \"bullet\", \"bulletList\", \"bullet\" ] ]\n    ),\n    html: `<ul><li>${blockComment}a<ul><li>${blockComment}b</li><li>${blockComment}c</li></ul></li></ul>`,\n  },\n\n  \"nested lists\": {\n    document: createDocument(\n      [ \"a\", {}, [ \"numberList\", \"number\" ] ],\n      [ \"b\", {}, [ \"numberList\", \"number\", \"bulletList\", \"bullet\" ] ],\n      [ \"c\", {}, [ \"numberList\", \"number\", \"bulletList\", \"bullet\" ] ]\n    ),\n    html: `<ol><li>${blockComment}a<ul><li>${blockComment}b</li><li>${blockComment}c</li></ul></li></ol>`,\n  },\n\n  \"blocks beginning with newlines\": {\n    document: createDocument([ \"\\na\", {}, [ \"quote\" ] ], [ \"\\nb\", {}, [] ], [ \"\\nc\", {}, [ \"quote\" ] ]),\n    html: `<blockquote>${blockComment}<br>a</blockquote><div>${blockComment}<br>b</div><blockquote>${blockComment}<br>c</blockquote>`,\n  },\n\n  \"blocks beginning with formatted text\": {\n    document: createDocument(\n      [ \"a\", { bold: true }, [ \"quote\" ] ],\n      [ \"b\", { italic: true }, [] ],\n      [ \"c\", { bold: true }, [ \"quote\" ] ]\n    ),\n    html: `<blockquote>${blockComment}<strong>a</strong></blockquote><div>${blockComment}<em>b</em></div><blockquote>${blockComment}<strong>c</strong></blockquote>`,\n  },\n\n  \"text with newlines before block\": {\n    document: createDocument([ \"a\\nb\" ], [ \"c\", {}, [ \"quote\" ] ]),\n    html: `<div>${blockComment}a<br>b</div><blockquote>${blockComment}c</blockquote>`,\n  },\n\n  \"empty heading block\": {\n    document: createDocument([ \"\", {}, [ \"heading1\" ] ]),\n    html: `<h1>${blockComment}<br></h1>`,\n  },\n\n  \"two adjacent headings\": {\n    document: createDocument([ \"a\", {}, [ \"heading1\" ] ], [ \"b\", {}, [ \"heading1\" ] ]),\n    html: `<h1>${blockComment}a</h1><h1>${blockComment}b</h1>`,\n  },\n\n  \"heading in ordered list\": {\n    document: createDocument([ \"a\", {}, [ \"numberList\", \"number\", \"heading1\" ] ]),\n    html: `<ol><li><h1>${blockComment}a</h1></li></ol>`,\n  },\n\n  \"headings with formatted text\": {\n    document: createDocument([ \"a\", { bold: true }, [ \"heading1\" ] ], [ \"b\", { italic: true, bold: true }, [ \"heading1\" ] ]),\n    html: `<h1>${blockComment}<strong>a</strong></h1><h1>${blockComment}<strong><em>b</em></strong></h1>`,\n  },\n\n  \"bidrectional text\": {\n    document: createDocument(\n      [ \"a\" ],\n      [ \"ل\", {}, [ \"quote\" ] ],\n      [ \"b\", {}, [ \"bulletList\", \"bullet\" ] ],\n      [ \"ל\", {}, [ \"bulletList\", \"bullet\" ] ],\n      [ \"\", {}, [ \"bulletList\", \"bullet\" ] ],\n      [ \"cید\" ],\n      [ \"\\n گ\" ]\n    ),\n    html: `<div>${blockComment}a</div>\\\n<blockquote dir=\"rtl\">${blockComment}ل</blockquote>\\\n<ul><li>${blockComment}b</li></ul>\\\n<ul dir=\"rtl\"><li>${blockComment}ל</li><li>${blockComment}<br></li></ul>\\\n<div>${blockComment}cید</div>\\\n<div dir=\"rtl\">${blockComment}<br>&nbsp;گ</div>\\\n`,\n    serializedHTML:\n      \"\\\n<div>a</div>\\\n<blockquote dir=\\\"rtl\\\">ل</blockquote>\\\n<ul><li>b</li></ul>\\\n<ul dir=\\\"rtl\\\"><li>ל</li><li><br></li></ul>\\\n<div>cید</div>\\\n<div dir=\\\"rtl\\\"><br>&nbsp;گ</div>\\\n\",\n  },\n}\n\nexport const eachFixture = (callback) => {\n  for (const name in fixtures) {\n    const details = fixtures[name]\n    callback(name, details)\n  }\n}\n"
  },
  {
    "path": "src/test/test_helpers/fixtures/test_image_url.js",
    "content": "export const TEST_IMAGE_URL = \"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=\"\n"
  },
  {
    "path": "src/test/test_helpers/functions.js",
    "content": "export const extend = function (properties) {\n  for (const key in properties) {\n    const value = properties[key]\n    this[key] = value\n  }\n  return this\n}\n\nexport const after = (delay, callback) => setTimeout(callback, delay)\n"
  },
  {
    "path": "src/test/test_helpers/input_helpers.js",
    "content": "import * as config from \"trix/config\"\nimport { delay, nextFrame } from \"./timing_helpers\"\nimport { triggerEvent } from \"./event_helpers\"\nimport {\n  collapseSelection,\n  createDOMRangeFromPoint,\n  deleteSelection,\n  insertNode,\n  selectNode,\n  selectionIsCollapsed,\n} from \"./selection_helpers\"\n\nconst keyCodes = {}\n\nObject.keys(config.keyNames).forEach(code => {\n  const name = config.keyNames[code]\n  keyCodes[name] = code\n})\n\nconst isIE = /Windows.*Trident/.test(navigator.userAgent)\n\nexport const triggerInputEvent = function (element, type, properties = {}) {\n  if (config.input.getLevel() === 2) {\n    let ranges\n    if (properties.ranges) {\n      ({ ranges } = properties)\n      delete properties.ranges\n    } else {\n      ranges = []\n      const selection = window.getSelection()\n      if (selection.rangeCount > 0) {\n        ranges.push(selection.getRangeAt(0).cloneRange())\n      }\n    }\n    properties.getTargetRanges = () => ranges\n    triggerEvent(element, type, properties)\n  }\n}\n\nexport const pasteContent = async (contentType, value) => {\n  let data\n\n  if (typeof contentType === \"object\") {\n    data = contentType\n  } else {\n    data = { [contentType]: value }\n  }\n\n  const testClipboardData = {\n    getData: (type) => data[type],\n    types: Object.keys(data),\n    items: Object.values(data),\n  }\n\n  if (testClipboardData.types.includes(\"Files\")) {\n    testClipboardData.files = testClipboardData.items\n  }\n\n  triggerInputEvent(document.activeElement, \"beforeinput\", {\n    inputType: \"insertFromPaste\",\n    dataTransfer: testClipboardData,\n  })\n\n  triggerEvent(document.activeElement, \"paste\", { testClipboardData })\n\n  await nextFrame()\n}\n\nexport const createFile = function (properties = {}) {\n  const file = {\n    getAsFile() {\n      return {}\n    },\n  }\n  for (const key in properties) {\n    const value = properties[key]\n    file[key] = value\n  }\n  return file\n}\n\nexport const typeCharacters = async (string) => {\n  const characters = Array.isArray(string) ? string : string.split(\"\")\n\n  const typeNextCharacter = async () => {\n    await delay(10)\n    const character = characters.shift()\n    if (character == null) return\n\n    switch (character) {\n      case \"\\n\":\n        await pressKey(\"return\")\n        await typeNextCharacter()\n        break\n      case \"\\b\":\n        await pressKey(\"backspace\")\n        await typeNextCharacter()\n        break\n      default:\n        await typeCharacterInElement(character, document.activeElement)\n        await typeNextCharacter()\n    }\n  }\n\n  await typeNextCharacter()\n  await delay(10)\n}\n\nexport const pressKey = async (keyName) => {\n  const element = document.activeElement\n  const code = keyCodes[keyName]\n  const properties = { which: code, keyCode: code, charCode: 0, key: capitalize(keyName) }\n\n  if (!triggerEvent(element, \"keydown\", properties)) return\n\n  await simulateKeypress(keyName)\n  await nextFrame()\n\n  triggerEvent(element, \"keyup\", properties)\n  await nextFrame()\n}\n\nexport const startComposition = async (data) => {\n  const element = document.activeElement\n  triggerEvent(element, \"compositionstart\", { data: \"\" })\n  triggerInputEvent(element, \"beforeinput\", { inputType: \"insertCompositionText\", data })\n  triggerEvent(element, \"compositionupdate\", { data })\n  triggerEvent(element, \"input\")\n\n  const node = document.createTextNode(data)\n  insertNode(node)\n  await selectNode(node)\n}\n\nexport const updateComposition = async (data) => {\n  const element = document.activeElement\n  triggerInputEvent(element, \"beforeinput\", { inputType: \"insertCompositionText\", data })\n  triggerEvent(element, \"compositionupdate\", { data })\n  triggerEvent(element, \"input\")\n\n  const node = document.createTextNode(data)\n  insertNode(node)\n  await selectNode(node)\n}\n\nexport const endComposition = async (data) => {\n  const element = document.activeElement\n  triggerInputEvent(element, \"beforeinput\", { inputType: \"insertCompositionText\", data })\n  triggerEvent(element, \"compositionupdate\", { data })\n\n  const node = document.createTextNode(data)\n  insertNode(node)\n  selectNode(node)\n  await collapseSelection(\"right\")\n\n  triggerEvent(element, \"input\")\n  triggerEvent(element, \"compositionend\", { data })\n\n  await nextFrame()\n}\n\nexport const clickElement = async (element) => {\n  if (triggerEvent(element, \"mousedown\")) {\n    await nextFrame()\n\n    if (triggerEvent(element, \"mouseup\")) {\n      await nextFrame()\n      triggerEvent(element, \"click\")\n      await nextFrame()\n    }\n  }\n}\n\nexport const dragToCoordinates = async (coordinates) => {\n  const element = document.activeElement\n\n  // IE only allows writing \"text\" to DataTransfer\n  // https://msdn.microsoft.com/en-us/library/ms536744(v=vs.85).aspx\n  const dataTransfer = {\n    files: [],\n    data: {},\n    getData(format) {\n      if (isIE && format.toLowerCase() !== \"text\") {\n        throw new Error(\"Invalid argument.\")\n      } else {\n        this.data[format]\n        return true\n      }\n    },\n    setData(format, data) {\n      if (isIE && format.toLowerCase() !== \"text\") {\n        throw new Error(\"Unexpected call to method or property access.\")\n      } else {\n        this.data[format] = data\n      }\n    },\n  }\n\n  triggerEvent(element, \"mousemove\")\n\n  const dragstartData = { dataTransfer }\n  triggerEvent(element, \"dragstart\", dragstartData)\n  triggerInputEvent(element, \"beforeinput\", { inputType: \"deleteByDrag\" })\n\n  const dropData = { dataTransfer }\n  for (const key in coordinates) {\n    const value = coordinates[key]\n    dropData[key] = value\n  }\n  triggerEvent(element, \"drop\", dropData)\n\n  const { clientX, clientY } = coordinates\n  const domRange = createDOMRangeFromPoint(clientX, clientY)\n  triggerInputEvent(element, \"beforeinput\", { inputType: \"insertFromDrop\", ranges: [ domRange ] })\n\n  await nextFrame()\n}\n\nexport const mouseDownOnElementAndMove = async (element, distance) => {\n  const coordinates = getElementCoordinates(element)\n  triggerEvent(element, \"mousedown\", coordinates)\n\n  const destination = (offset) => ({\n    clientX: coordinates.clientX + offset,\n    clientY: coordinates.clientY + offset,\n  })\n\n  const dragSpeed = 20\n  await delay(dragSpeed)\n\n  let offset = 0\n  const drag = async () => {\n    if (++offset <= distance) {\n      triggerEvent(element, \"mousemove\", destination(offset))\n      await delay(dragSpeed)\n      drag()\n    } else {\n      triggerEvent(element, \"mouseup\", destination(distance))\n      await delay(dragSpeed)\n    }\n  }\n\n  drag()\n}\n\nconst typeCharacterInElement = async (character, element) => {\n  const charCode = character.charCodeAt(0)\n  const keyCode = character.toUpperCase().charCodeAt(0)\n\n  if (!triggerEvent(element, \"keydown\", { keyCode, charCode: 0 })) return\n\n  await nextFrame()\n\n  if (!triggerEvent(element, \"keypress\", { keyCode: charCode, charCode })) return\n\n  triggerInputEvent(element, \"beforeinput\", { inputType: \"insertText\", data: character })\n\n  await insertCharacter(character)\n  triggerEvent(element, \"input\")\n  await nextFrame()\n\n  triggerEvent(element, \"keyup\", { keyCode, charCode: 0 })\n}\n\nconst insertCharacter = async (character) => {\n  const node = document.createTextNode(character)\n  await insertNode(node)\n}\n\nconst simulateKeypress = async (keyName) => {\n  switch (keyName) {\n    case \"backspace\":\n      await deleteInDirection(\"left\")\n      break\n    case \"delete\":\n      await deleteInDirection(\"right\")\n      break\n    case \"return\":\n      triggerInputEvent(document.activeElement, \"beforeinput\", { inputType: \"insertParagraph\" })\n      break\n  }\n}\n\nconst deleteInDirection = async (direction) => {\n  if (selectionIsCollapsed()) {\n    getComposition().expandSelectionInDirection(direction === \"left\" ? \"backward\" : \"forward\")\n    await nextFrame()\n\n    const inputType = direction === \"left\" ? \"deleteContentBackward\" : \"deleteContentForward\"\n    triggerInputEvent(document.activeElement, \"beforeinput\", { inputType })\n\n    await nextFrame()\n    deleteSelection()\n  } else {\n    triggerInputEvent(document.activeElement, \"beforeinput\", { inputType: \"deleteContentBackward\" })\n    deleteSelection()\n  }\n}\n\nconst getElementCoordinates = function (element) {\n  const rect = element.getBoundingClientRect()\n  return {\n    clientX: rect.left + rect.width / 2,\n    clientY: rect.top + rect.height / 2,\n  }\n}\n\nconst capitalize = (string) => string.charAt(0).toUpperCase() + string.slice(1)\n"
  },
  {
    "path": "src/test/test_helpers/selection_helpers.js",
    "content": "import * as config from \"trix/config\"\n\nimport { triggerEvent } from \"event_helpers\"\nimport { selectionChangeObserver } from \"trix/observers/selection_change_observer\"\n\nimport rangy from \"rangy\"\nimport \"rangy/lib/rangy-textrange\"\nimport { nextFrame } from \"./timing_helpers\"\n\nwindow.rangy = rangy\n\nconst keyCodes = {}\nfor (const code in config.keyNames) {\n  const name = config.keyNames[code]\n  keyCodes[name] = code\n}\n\nconst keys = {\n  left: \"ArrowLeft\",\n  right: \"ArrowRight\",\n}\n\nexport const moveCursor = async (options) => {\n  let direction, times\n  if (typeof options === \"string\") {\n    direction = options\n  } else {\n    times = options.times\n    direction = options.direction\n  }\n\n  if (!times) times = 1\n\n  const move = async () => {\n    await nextFrame()\n\n    if (triggerEvent(document.activeElement, \"keydown\", { keyCode: keyCodes[direction], key: keys[direction] })) {\n      const selection = rangy.getSelection()\n      selection.move(\"character\", direction === \"right\" ? 1 : -1)\n      selectionChangeObserver.update()\n    }\n\n    if (--times === 0) {\n      await nextFrame()\n      return getCursorCoordinates()\n    } else {\n      return move()\n    }\n  }\n  return await move()\n}\n\nexport const expandSelection = async (options) => {\n  await nextFrame()\n\n  let direction, times\n  if (typeof options === \"string\") {\n    direction = options\n  } else {\n    ({ direction } = options)\n    times = options.times\n  }\n\n  if (!times) times = 1\n\n  const expand = async () => {\n    await nextFrame()\n\n    if (triggerEvent(document.activeElement, \"keydown\", { keyCode: keyCodes[direction], key: keys[direction], shiftKey: true })) {\n      getComposition().expandSelectionInDirection(direction === \"left\" ? \"backward\" : \"forward\")\n    }\n\n    if (--times === 0) {\n      await nextFrame()\n    } else {\n      return await expand()\n    }\n  }\n\n  return await expand()\n}\n\nexport const collapseSelection = async (direction) => {\n  const selection = rangy.getSelection()\n  if (direction === \"left\") {\n    selection.collapseToStart()\n  } else {\n    selection.collapseToEnd()\n  }\n  selectionChangeObserver.update()\n  await nextFrame()\n}\n\nexport const selectAll = async () => {\n  rangy.getSelection().selectAllChildren(document.activeElement)\n  selectionChangeObserver.update()\n  await nextFrame()\n}\n\nexport const deleteSelection = () => {\n  const selection = rangy.getSelection()\n  selection.getRangeAt(0).deleteContents()\n  selectionChangeObserver.update()\n}\n\nexport const selectionIsCollapsed = () => rangy.getSelection().isCollapsed\n\nexport const insertNode = async (node) => {\n  const selection = rangy.getSelection()\n  const range = selection.getRangeAt(0)\n  range.splitBoundaries()\n  range.insertNode(node)\n  range.setStartAfter(node)\n  range.deleteContents()\n  selection.setSingleRange(range)\n  selectionChangeObserver.update()\n\n  await nextFrame()\n}\n\nexport const selectNode = async (node) => {\n  const selection = rangy.getSelection()\n  selection.selectAllChildren(node)\n  selectionChangeObserver.update()\n}\n\nexport const createDOMRangeFromPoint = function (px, py) {\n  if (document.caretPositionFromPoint) {\n    const { offsetNode, offset } = document.caretPositionFromPoint(px, py)\n    const domRange = document.createRange()\n    domRange.setStart(offsetNode, offset)\n    return domRange\n  } else if (document.caretRangeFromPoint) {\n    return document.caretRangeFromPoint(px, py)\n  }\n}\n\nconst getCursorCoordinates = () => {\n  const rect = window.getSelection().getRangeAt(0).getClientRects()[0]\n  if (rect) {\n    return {\n      clientX: rect.left,\n      clientY: rect.top + rect.height / 2,\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/test_helpers/test_helpers.js",
    "content": "import { fixtureTemplates } from \"test/test_helpers/fixtures/fixtures\"\nimport { removeNode } from \"trix/core/helpers\"\n\nexport const setFixtureHTML = function (html, container = \"form\") {\n  let element = document.getElementById(\"trix-container\")\n  if (element != null) removeNode(element)\n\n  element = document.createElement(container)\n  element.id = \"trix-container\"\n  element.innerHTML = html\n\n  document.body.insertAdjacentElement(\"afterbegin\", element)\n\n  return waitForTrixInit()\n}\n\nexport const testGroup = function (name, options, callback) {\n  let container, beforeSetup, setup, teardown, afterTeardown, template\n  if (callback != null) {\n    ({ container, template, beforeSetup, setup, teardown, afterTeardown } = options)\n  } else {\n    callback = options\n  }\n\n  const before = () => {\n    if (beforeSetup) beforeSetup()\n  }\n\n  const beforeEach = async () => {\n    // Ensure window is active on CI so focus and blur events are natively dispatched\n    window.focus()\n\n    if (template != null) {\n      await setFixtureHTML(fixtureTemplates[template](), container)\n    }\n\n    if (setup) setup()\n  }\n\n  const afterEach = () => {\n    if (template != null) setFixtureHTML(\"\")\n    return teardown?.()\n  }\n\n  const after = () => {\n    if (afterTeardown) afterTeardown()\n  }\n\n  if (callback != null) {\n    return QUnit.module(name, function (hooks) {\n      hooks.before(before)\n      hooks.beforeEach(beforeEach)\n      hooks.afterEach(afterEach)\n      hooks.after(after)\n      callback()\n    })\n  } else {\n    return QUnit.module(name, { before, beforeEach, afterEach, after })\n  }\n}\n\nexport const skipIf = function (condition, ...args) {\n  testIf(!condition, ...args)\n}\n\nexport const testIf = function (condition, ...args) {\n  if (condition) {\n    test(...Array.from(args || []))\n  } else {\n    skip(...Array.from(args || []))\n  }\n}\n\nexport const { skip, test } = QUnit\n\nconst waitForTrixInit = async () => {\n  return new Promise((resolve) => {\n    addEventListener(\"trix-initialize\", ({ target }) => {\n      if (target.hasAttribute(\"autofocus\")) target.editor.setSelectedRange(0)\n      resolve(target)\n    }, { once: true })\n  })\n}\n\n\n// const isAsync = (func) => func.constructor.name === \"AsyncFunction\"\n"
  },
  {
    "path": "src/test/test_helpers/test_stubs.js",
    "content": "import { normalizeRange, rangeIsCollapsed } from \"trix/core/helpers\"\n\nexport class TestCompositionDelegate {\n  compositionDidRequestChangingSelectionToLocationRange() {\n    return this.getSelectionManager().setLocationRange(...arguments)\n  }\n\n  getSelectionManager() {\n    if (!this.selectionManager) this.selectionManager = new TestSelectionManager()\n    return this.selectionManager\n  }\n}\n\nexport class TestSelectionManager {\n  constructor() {\n    this.setLocationRange({ index: 0, offset: 0 })\n  }\n\n  getLocationRange() {\n    return this.locationRange\n  }\n\n  setLocationRange(locationRange) {\n    this.locationRange = normalizeRange(locationRange)\n  }\n\n  preserveSelection(block) {\n    const locationRange = this.getLocationRange()\n    block()\n    this.locationRange = locationRange\n  }\n\n  setLocationRangeFromPoint(point) {}\n\n  locationIsCursorTarget() {\n    return false\n  }\n\n  selectionIsExpanded() {\n    return !rangeIsCollapsed(this.getLocationRange())\n  }\n}\n"
  },
  {
    "path": "src/test/test_helpers/timing_helpers.js",
    "content": "export function nextFrame() {\n  return new Promise(requestAnimationFrame)\n}\n\nexport function nextIdle() {\n  return new Promise(window.requestIdleCallback || setTimeout)\n}\n\nexport function delay(ms = 1) {\n  return new Promise(resolve => setTimeout(resolve, ms))\n}\n"
  },
  {
    "path": "src/test/test_helpers/toolbar_helpers.js",
    "content": "import { triggerEvent } from \"./event_helpers\"\nimport { selectionChangeObserver } from \"trix/observers/selection_change_observer\"\nimport { delay, nextFrame } from \"./timing_helpers\"\n\nexport const clickToolbarButton = async (selector) => {\n  selectionChangeObserver.update()\n  const button = getToolbarButton(selector)\n  triggerEvent(button, \"mousedown\")\n  await delay(5)\n}\n\nexport const typeToolbarKeyCommand = async (selector) => {\n  const button = getToolbarButton(selector)\n  const { trixKey } = button.dataset\n  if (trixKey) {\n    const keyCode = trixKey.toUpperCase().charCodeAt(0)\n    triggerEvent(getEditorElement(), \"keydown\", { keyCode, charCode: 0, metaKey: true, ctrlKey: true })\n  }\n  await nextFrame()\n}\n\nexport const clickToolbarDialogButton = async ({ method }) => {\n  const button = getToolbarElement().querySelector(`[data-trix-dialog] [data-trix-method='${method}']`)\n  triggerEvent(button, \"click\")\n  await nextFrame()\n}\n\nexport const isToolbarButtonActive = function (selector) {\n  const button = getToolbarButton(selector)\n  return button.hasAttribute(\"data-trix-active\") && button.classList.contains(\"trix-active\")\n}\n\nexport const isToolbarButtonDisabled = (selector) => getToolbarButton(selector).disabled\n\nexport const typeInToolbarDialog = async (string, { attribute }) => {\n  const dialog = getToolbarDialog({ attribute })\n  const input = dialog.querySelector(`[data-trix-input][name='${attribute}']`)\n  const button = dialog.querySelector(\"[data-trix-method='setAttribute']\")\n  input.value = string\n  triggerEvent(button, \"click\")\n  await nextFrame()\n}\n\nexport const isToolbarDialogActive = function (selector) {\n  const dialog = getToolbarDialog(selector)\n  return dialog.hasAttribute(\"data-trix-active\") && dialog.classList.contains(\"trix-active\")\n}\n\nconst getToolbarButton = ({ attribute, action }) =>\n  getToolbarElement().querySelector(`[data-trix-attribute='${attribute}'], [data-trix-action='${action}']`)\n\nconst getToolbarDialog = ({ attribute, action }) => getToolbarElement().querySelector(`[data-trix-dialog='${attribute}']`)\n"
  },
  {
    "path": "src/test/unit/attachment_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\nimport Attachment from \"trix/models/attachment\"\n\ntestGroup(\"Attachment\", () => {\n  const previewableTypes = \"image image/gif image/png image/jpg image/webp\".split(\" \")\n  const nonPreviewableTypes = \"image/tiff application/foo\".split(\" \")\n\n  const createAttachment = (attributes) => new Attachment(attributes)\n\n  previewableTypes.forEach((contentType) => {\n    test(`${contentType} content type is previewable`, () => {\n      assert.ok(createAttachment({ contentType }).isPreviewable())\n    })\n  })\n\n  nonPreviewableTypes.forEach((contentType) => {\n    test(`${contentType} content type is NOT previewable`, () => {\n      assert.notOk(createAttachment({ contentType }).isPreviewable())\n    })\n  })\n\n  test(\"'previewable' attribute determines previewability\", () => {\n    let attrs = { previewable: true, contentType: nonPreviewableTypes[0] }\n    assert.ok(createAttachment(attrs).isPreviewable())\n\n    attrs = { previewable: false, contentType: previewableTypes[0] }\n    assert.notOk(createAttachment(attrs).isPreviewable())\n  })\n})\n"
  },
  {
    "path": "src/test/unit/bidi_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\nimport { getDirection } from \"trix/core/helpers\"\n\ntestGroup(\"BIDI\", () => {\n  test(\"detects text direction\", () => {\n    assert.equal(getDirection(\"abc\"), \"ltr\")\n    assert.equal(getDirection(\"אבג\"), \"rtl\")\n  })\n})\n"
  },
  {
    "path": "src/test/unit/block_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\n\nimport Text from \"trix/models/text\"\nimport Block from \"trix/models/block\"\n\ntestGroup(\"Block\", () => {\n  test(\"consolidating blocks creates text with one blockBreak piece\", () => {\n    const blockA = new Block(Text.textForStringWithAttributes(\"a\"))\n    const blockB = new Block(Text.textForStringWithAttributes(\"b\"))\n    const consolidatedBlock = blockA.consolidateWith(blockB)\n    const pieces = consolidatedBlock.text.getPieces()\n\n    assert.equal(pieces.length, 2, JSON.stringify(pieces))\n    assert.deepEqual(pieces[0].getAttributes(), {})\n    assert.deepEqual(pieces[1].getAttributes(), { blockBreak: true })\n    assert.equal(consolidatedBlock.toString(), \"a\\nb\\n\")\n  })\n\n  test(\"consolidating empty blocks creates text with one blockBreak piece\", () => {\n    const consolidatedBlock = new Block().consolidateWith(new Block())\n    const pieces = consolidatedBlock.text.getPieces()\n\n    assert.equal(pieces.length, 2, JSON.stringify(pieces))\n    assert.deepEqual(pieces[0].getAttributes(), {})\n    assert.deepEqual(pieces[1].getAttributes(), { blockBreak: true })\n    assert.equal(consolidatedBlock.toString(), \"\\n\\n\")\n  })\n})\n"
  },
  {
    "path": "src/test/unit/composition_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\n\nimport Composition from \"trix/models/composition\"\nimport { TestCompositionDelegate } from \"test/test_helpers/test_stubs\"\n\nlet composition = null\nconst setup = () => {\n  composition = new Composition()\n  composition.delegate = new TestCompositionDelegate()\n}\n\ntestGroup(\"Composition\", { setup }, () =>\n  test(\"deleteInDirection respects UTF-16 character boundaries\", () => {\n    composition.insertString(\"abc😭\")\n    composition.deleteInDirection(\"backward\")\n    composition.insertString(\"d\")\n    assert.equal(composition.document.toString(), \"abcd\\n\")\n  })\n)\n"
  },
  {
    "path": "src/test/unit/document_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\n\nimport Text from \"trix/models/text\"\nimport Block from \"trix/models/block\"\nimport Attachment from \"trix/models/attachment\"\nimport Document from \"trix/models/document\"\nimport HTMLParser from \"trix/models/html_parser\"\n\ntestGroup(\"Document\", () => {\n  const createDocumentWithAttachment = function (attachment) {\n    const text = Text.textForAttachmentWithAttributes(attachment)\n    return new Document([ new Block(text) ])\n  }\n\n  test(\"documents with different attachments are not equal\", () => {\n    const aDoc = createDocumentWithAttachment(new Attachment({ url: \"a\" }))\n    const bDoc = createDocumentWithAttachment(new Attachment({ url: \"b\" }))\n    assert.notOk(aDoc.isEqualTo(bDoc))\n  })\n\n  test(\"getStringAtRange does not leak trailing block breaks\", () => {\n    const document = Document.fromString(\"Hey\")\n    assert.equal(document.getStringAtRange([ 0, 0 ]), \"\")\n    assert.equal(document.getStringAtRange([ 0, 1 ]), \"H\")\n    assert.equal(document.getStringAtRange([ 0, 2 ]), \"He\")\n    assert.equal(document.getStringAtRange([ 0, 3 ]), \"Hey\")\n    assert.equal(document.getStringAtRange([ 0, 4 ]), \"Hey\\n\")\n  })\n\n  test(\"findRangesForTextAttribute\", () => {\n    const document = HTMLParser.parse(\n      `\n      <div>Hello <strong>world, <em>this</em> is</strong> a <strong>test</strong>.<br></div>\n    `\n    ).getDocument()\n    assert.deepEqual(document.findRangesForTextAttribute(\"bold\"), [\n      [ 6, 20 ],\n      [ 23, 27 ],\n    ])\n    assert.deepEqual(document.findRangesForTextAttribute(\"italic\"), [ [ 13, 17 ] ])\n    assert.deepEqual(document.findRangesForTextAttribute(\"href\"), [])\n  })\n\n  test(\"findRangesForTextAttribute withValue\", () => {\n    const document = HTMLParser.parse(\n      `\n      <div>Hello <a href=\"http://google.com/\">world, <em>this</em> is</a> a <a href=\"http://basecamp.com/\">test</a>.<br></div>\n    `\n    ).getDocument()\n    assert.deepEqual(document.findRangesForTextAttribute(\"href\"), [\n      [ 6, 20 ],\n      [ 23, 27 ],\n    ])\n    assert.deepEqual(document.findRangesForTextAttribute(\"href\", { withValue: \"http://google.com/\" }), [ [ 6, 20 ] ])\n    assert.deepEqual(document.findRangesForTextAttribute(\"href\", { withValue: \"http://basecamp.com/\" }), [ [ 23, 27 ] ])\n    assert.deepEqual(document.findRangesForTextAttribute(\"href\", { withValue: \"http://amazon.com/\" }), [])\n  })\n})\n"
  },
  {
    "path": "src/test/unit/document_view_test.js",
    "content": "import { assert, eachFixture, test, testGroup } from \"test/test_helper\"\n\ntestGroup(\"DocumentView\", () => {\n  eachFixture((name, details) => {\n    test(name, () => {\n      assert.documentHTMLEqual(details.document, details.html)\n    })\n  })\n})\n"
  },
  {
    "path": "src/test/unit/helpers/custom_elements_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\nimport { installDefaultCSSForTagName, makeElement } from \"trix/core/helpers\"\n\nlet meta = null\n\nconst insertMeta = (attributes) => {\n  meta = makeElement(\"meta\", attributes)\n  document.head.append(meta)\n}\n\nconst installDefaultCSS = (tagName, css) => {\n  installDefaultCSSForTagName(tagName, css)\n\n  return document.querySelector(`style[data-tag-name=${tagName}]`)\n}\n\nconst teardown = () => meta.remove()\n\ntestGroup(\"Helpers: Custom Elements\", { teardown }, () => {\n  test(\"reads from meta[name=csp-nonce][content]\", () => {\n    insertMeta({ name: \"csp-nonce\", content: \"abc123\" })\n\n    const style = installDefaultCSS(\"trix-element\", \"trix-element { display: block; }\")\n\n    assert.equal(style.getAttribute(\"nonce\"), \"abc123\")\n    assert.equal(style.innerHTML, \"trix-element { display: block; }\")\n  })\n\n  test(\"reads from meta[name=trix-csp-nonce][content]\", () => {\n    insertMeta({ name: \"trix-csp-nonce\", content: \"abc123\" })\n\n    const style = installDefaultCSS(\"trix-element\", \"trix-element { display: block; }\")\n\n    assert.equal(style.getAttribute(\"nonce\"), \"abc123\")\n    assert.equal(style.innerHTML, \"trix-element { display: block; }\")\n  })\n\n  test(\"reads from meta[name=csp-nonce][nonce]\", () => {\n    insertMeta({ name: \"csp-nonce\", nonce: \"abc123\" })\n\n    const style = installDefaultCSS(\"trix-element\", \"trix-element { display: block; }\")\n\n    assert.equal(style.getAttribute(\"nonce\"), \"abc123\")\n    assert.equal(style.innerHTML, \"trix-element { display: block; }\")\n  })\n\n  test(\"reads from meta[name=trix-csp-nonce][nonce]\", () => {\n    insertMeta({ name: \"trix-csp-nonce\", nonce: \"abc123\" })\n\n    const style = installDefaultCSS(\"trix-element\", \"trix-element { display: block; }\")\n\n    assert.equal(style.getAttribute(\"nonce\"), \"abc123\")\n    assert.equal(style.innerHTML, \"trix-element { display: block; }\")\n  })\n})\n"
  },
  {
    "path": "src/test/unit/html_parser_test.js",
    "content": "import {\n  TEST_IMAGE_URL,\n  assert,\n  createCursorTarget,\n  eachFixture,\n  fixtures,\n  getHTML,\n  test,\n  testGroup,\n} from \"test/test_helper\"\n\nimport * as config from \"trix/config\"\nimport HTMLParser from \"trix/models/html_parser\"\nimport { delay } from \"../test_helpers/timing_helpers\"\n\nconst cursorTargetLeft = createCursorTarget(\"left\").outerHTML\nconst cursorTargetRight = createCursorTarget(\"right\").outerHTML\n\ntestGroup(\"HTMLParser\", () => {\n  eachFixture((name, { html, document }) => {\n    test(name, () => {\n      const parsedDocument = HTMLParser.parse(html).getDocument()\n      assert.documentHTMLEqual(parsedDocument.copyUsingObjectsFromDocument(document), html)\n    })\n  })\n\n  eachFixture((name, { html, serializedHTML, document }) => {\n    if (serializedHTML) {\n      test(`${name} (serialized)`, () => {\n        const parsedDocument = HTMLParser.parse(serializedHTML).getDocument()\n        assert.documentHTMLEqual(parsedDocument.copyUsingObjectsFromDocument(document), html)\n      })\n    }\n  })\n\n  testGroup(\"nested line breaks\", () => {\n    const cases = {\n      \"<div>a<div>b</div>c</div>\": \"<div><!--block-->a<br>b<br>c</div>\",\n      \"<div>a<div><div><div>b</div></div></div>c</div>\": \"<div><!--block-->a<br>b<br>c</div>\",\n      \"<blockquote>a<div>b</div>c</blockquote>\": \"<blockquote><!--block-->a<br>b<br>c</blockquote>\",\n    }\n    // TODO:\n    // \"<div><div>a</div><div>b</div>c</div>\": \"<div><!--block-->a<br>b<br>c</div>\"\n    // \"<blockquote><div>a</div><div>b</div><div>c</div></blockquote>\": \"<blockquote><!--block-->a<br>b<br>c</blockquote>\"\n    // \"<blockquote><div>a<br></div><div><br></div><div>b<br></div></blockquote>\": \"<blockquote><!--block-->a<br><br>b</blockquote>\"\n\n    for (const [ html, expectedHTML ] of Object.entries(cases)) {\n      test(html, () => {\n        assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n      })\n    }\n  })\n\n  test(\"parses absolute image URLs\", () => {\n    const src = `${getOrigin()}/test_helpers/fixtures/logo.png`\n    const pattern = new RegExp(`src=\"${src}\"`)\n    const html = `<img src=\"${src}\">`\n\n    const finalHTML = getHTML(HTMLParser.parse(html).getDocument())\n    assert.ok(pattern.test(finalHTML), `${pattern} not found in ${JSON.stringify(finalHTML)}`)\n  })\n\n  test(\"parses relative image URLs\", () => {\n    const src = \"/test_helpers/fixtures/logo.png\"\n    const pattern = new RegExp(`src=\"${src}\"`)\n    const html = `<img src=\"${src}\">`\n\n    const finalHTML = getHTML(HTMLParser.parse(html).getDocument())\n    assert.ok(pattern.test(finalHTML), `${pattern} not found in ${JSON.stringify(finalHTML)}`)\n  })\n\n  test(\"parses unfamiliar html\", () => {\n    const html =\n      \"<meta charset=\\\"UTF-8\\\"><span style=\\\"font-style: italic\\\">abc</span><span>d</span><section style=\\\"margin:0\\\"><blink>123</blink><a href=\\\"http://example.com\\\">45<b>6</b></a>x<br />y</section><p style=\\\"margin:0\\\">9</p>\"\n    const expectedHTML =\n      \"<div><!--block--><em>abc</em>d</div><div><!--block-->123<a href=\\\"http://example.com\\\">45<strong>6</strong></a>x<br>y</div><div><!--block-->9</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"ignores leading whitespace before <meta> tag\", () => {\n    const html = \" \\n <meta charset=\\\"UTF-8\\\"><pre>abc</pre>\"\n    const expectedHTML = \"<pre><!--block-->abc</pre>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"ignores content after </html>\", () => {\n    const html = `\n      <html xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" xmlns:m=\"http://schemas.microsoft.com/office/2004/12/omml\" xmlns=\"http://www.w3.org/TR/REC-html40\">\n      <head>\n      <meta http-equiv=Content-Type content=\"text/html; charset=utf-8\">\n      <meta name=ProgId content=Word.Document>\n      </head>\n\n      <body lang=EN-US link=blue vlink=\"#954F72\" style='tab-interval:.5in'>\n      <!--StartFragment--><span lang=EN style='font-size:12.0pt;font-family:\n      \"Arial\",sans-serif;mso-fareast-font-family:\"Times New Roman\";mso-ansi-language:\n      EN;mso-fareast-language:EN-US;mso-bidi-language:AR-SA'>abc</span><!--EndFragment-->\n      </body>\n\n      </html>\n      TAxelFCg��K��`\n\n    const expectedHTML = \"<div><!--block-->abc</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses incorrectly nested list html\", () => {\n    const html = \"<ul><li>a</li><ul><li>b</li><ol><li>1</li><li>2</li><ol></ul></ul>\"\n    const expectedHTML =\n      \"<ul><li><!--block-->a<ul><li><!--block-->b<ol><li><!--block-->1</li><li><!--block-->2</li></ol></li></ul></li></ul>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"ignores whitespace between block elements\", () => {\n    const html = \"<div>a</div> \\n <div>b</div>     <article>c</article>  \\n\\n <section>d</section> \"\n    const expectedHTML =\n      \"<div><!--block-->a</div><div><!--block-->b</div><div><!--block-->c</div><div><!--block-->d</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"ingores whitespace between nested block elements\", () => {\n    const html = \"<ul> <li>a</li> \\n  <li>b</li>  </ul><div>  <div> \\n <blockquote>c</blockquote>\\n </div>  \\n</div>\"\n    const expectedHTML = \"<ul><li><!--block-->a</li><li><!--block-->b</li></ul><blockquote><!--block-->c</blockquote>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"ignores inline whitespace that can't be displayed\", () => {\n    const html = \" a  \\n b    <span>c\\n</span><span>d  \\ne </span> f <span style=\\\"white-space: pre\\\">  g\\n\\n h  </span>\"\n    const expectedHTML = \"<div><!--block-->a b c d e f &nbsp; g<br><br>&nbsp;h &nbsp;</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses significant whitespace in empty inline elements\", () => {\n    const html = \"a<span style='mso-spacerun:yes'> </span>b<span style='mso-spacerun:yes'>  </span>c\"\n    const expectedHTML = \"<div><!--block-->a b c</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses block elements with leading breakable whitespace\", () => {\n    const html =\n      \"<blockquote> <span>a</span> <blockquote>\\n <strong>b</strong> <pre> <span>c</span></pre></blockquote></blockquote>\"\n    const expectedHTML =\n      \"<blockquote><!--block-->a<blockquote><!--block--><strong>b</strong><pre><!--block--> c</pre></blockquote></blockquote>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses block elements with leading non-breaking whitespace\", () => {\n    const html = \"<blockquote>&nbsp;<span>a</span></blockquote>\"\n    const expectedHTML = \"<blockquote><!--block-->&nbsp;a</blockquote>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"converts newlines to spaces\", () => {\n    const html = \"<div>a\\nb \\nc \\n d \\n\\ne</div><pre>1\\n2</pre>\"\n    const expectedHTML = \"<div><!--block-->a b c d e</div><pre><!--block-->1\\n2</pre>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses entire HTML document\", () => {\n    const html =\n      \"<html><head><style>.bold {font-weight: bold}</style></head><body><span class=\\\"bold\\\">abc</span></body></html>\"\n    const expectedHTML = \"<div><!--block--><strong>abc</strong></div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses inline element following block element\", () => {\n    const html = \"<blockquote>abc</blockquote><strong>123</strong>\"\n    const expectedHTML = \"<blockquote><!--block-->abc</blockquote><div><!--block--><strong>123</strong></div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses text nodes following block elements\", () => {\n    const html = \"<ul><li>a</li></ul>b<blockquote>c</blockquote>d\"\n    const expectedHTML =\n      \"<ul><li><!--block-->a</li></ul><div><!--block-->b</div><blockquote><!--block-->c</blockquote><div><!--block-->d</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses whitespace-only text nodes without a containing block element\", () => {\n    const html = \"a <strong>b</strong> <em>c</em>\"\n    const expectedHTML = \"<div><!--block-->a <strong>b</strong> <em>c</em></div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses spaces around cursor targets\", () => {\n    const html = `<div>a ${cursorTargetLeft}<span>b</span>${cursorTargetRight} c</div>`\n    const expectedHTML = \"<div><!--block-->a b c</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses spanned text elements that don't have a parser function\", () => {\n    assert.notOk(config.textAttributes.strike.parser)\n    const html = \"<del>a <strong>b</strong></del>\"\n    const expectedHTML = \"<div><!--block--><del>a </del><strong><del>b</del></strong></div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"translates tables into plain text\", () => {\n    const html = \"<table><tr><td>a</td><td>b</td></tr><tr><td>1</td><td><p>2</p></td></tr><table>\"\n    const expectedHTML = \"<div><!--block-->a | b<br>1 | 2</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"allows customizing table separater\", () => {\n    withParserConfig({ tableCellSeparator: \"*\", tableRowSeparator: \"-\" }, () => {\n      const html = \"<table><tr><td>a</td><td>b</td></tr><tr><td>1</td><td><p>2</p></td></tr><table>\"\n      const expectedHTML = \"<div><!--block-->a*b-1*2</div>\"\n      assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n    })\n  })\n\n  test(\"includes empty cells when translating tables into plain text\", () => {\n    const html = \"<table><tr><td> </td><td></td></tr><tr><td>1</td><td><p>2</p></td></tr><table>\"\n    const expectedHTML = \"<div><!--block-->&nbsp;|&nbsp;<br>1 | 2</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"allows removing empty table cells from translated tables\", () => {\n    withParserConfig({ removeBlankTableCells: true }, () => {\n      const html = \"<table><tr><td> </td><td>\\n</td></tr><tr><td>1</td><td><p>2</p></td></tr><table>\"\n      const expectedHTML = \"<div><!--block-->1 | 2</div>\"\n      assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n    })\n  })\n\n  test(\"translates block element margins to newlines\", () => {\n    const html =\n      \"<p style=\\\"margin: 0 0 1em 0\\\">a</p><p style=\\\"margin: 0\\\">b</p><article style=\\\"margin: 1em 0 0 0\\\">c</article>\"\n    const expectedHTML = \"<div><!--block-->a<br><br></div><div><!--block-->b</div><div><!--block--><br>c</div>\"\n    const document = HTMLParser.parse(html).getDocument()\n    assert.documentHTMLEqual(document, expectedHTML)\n  })\n\n  test(\"skips translating empty block element margins to newlines\", () => {\n    const html =\n      \"<p style=\\\"margin: 0 0 1em 0\\\">a</p><p style=\\\"margin: 0 0 1em 0\\\"><span></span></p><p style=\\\"margin: 0\\\">b</p>\"\n    const expectedHTML = \"<div><!--block-->a<br><br></div><div><!--block--><br></div><div><!--block-->b</div>\"\n    const document = HTMLParser.parse(html).getDocument()\n    assert.documentHTMLEqual(document, expectedHTML)\n  })\n\n  test(\"ignores text nodes in script elements\", () => {\n    const html = \"<div>a<script>alert(\\\"b\\\")</script></div>\"\n    const expectedHTML = \"<div><!--block-->a</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"ignores iframe elements\", () => {\n    const html = \"<div>a<iframe src=\\\"data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk7PC9zY3JpcHQ+\\\">b</iframe></div>\"\n    const expectedHTML = \"<div><!--block-->a</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"sanitizes unsafe html\", async () => {\n    window.unsanitized = []\n    HTMLParser.parse(`\n      <img onload=\"window.unsanitized.push('img.onload');\" src=\"${TEST_IMAGE_URL}\">\n      <img onerror=\"window.unsanitized.push('img.onerror');\" src=\"data:image/gif;base64,TOTALLYBOGUS\">\n      <script>\n        window.unsanitized.push('script tag');\n      </script>`)\n\n    await delay(20)\n    assert.deepEqual(window.unsanitized, [])\n    delete window.unsanitized\n  })\n\n  test(\"forbids href attributes with javascript: protocol\", () => {\n    const html =\n      \"<a href=\\\"javascript:alert()\\\">a</a> <a href=\\\" javascript: alert()\\\">b</a> <a href=\\\"JavaScript:alert()\\\">c</a>\"\n    const expectedHTML = \"<div><!--block-->a b c</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"forbids javascript: protocol in attachment href\", () => {\n    const html = \"<figure data-trix-attachment=\\\"{&quot;href&quot;:&quot;javascript:alert(1)&quot;,&quot;contentType&quot;:&quot;application/pdf&quot;,&quot;filename&quot;:&quot;report.pdf&quot;}\\\"></figure>\"\n    const finalHTML = getHTML(HTMLParser.parse(html).getDocument())\n    assert.notOk(finalHTML.includes(\"href=\\\"javascript:\"), \"javascript: protocol found in href attribute: \" + JSON.stringify(finalHTML))\n  })\n\n  test(\"forbids data:text/html protocol in attachment href\", () => {\n    const html = \"<figure data-trix-attachment=\\\"{&quot;href&quot;:&quot;data:text/html,&lt;script&gt;alert(1)&lt;/script&gt;&quot;,&quot;contentType&quot;:&quot;application/pdf&quot;,&quot;filename&quot;:&quot;report.pdf&quot;}\\\"></figure>\"\n    const finalHTML = getHTML(HTMLParser.parse(html).getDocument())\n    assert.notOk(finalHTML.includes(\"href=\\\"data:text/html\"), \"data:text/html protocol found in href attribute: \" + JSON.stringify(finalHTML))\n  })\n\n  test(\"ignores attachment elements with malformed JSON\", () => {\n    const html =\n      \"<div>a</div><div data-trix-attachment data-trix-attributes></div>\" +\n      \"<div data-trix-attachment=\\\"\\\" data-trix-attributes=\\\"\\\"></div>\" +\n      \"<div data-trix-attachment=\\\"{&quot;x:}\\\" data-trix-attributes=\\\"{&quot;x:}\\\"></div>\" +\n      \"<div>b</div>\"\n    const expectedHTML = \"<div><!--block-->a</div><div><!--block--><br></div><div><!--block-->b</div>\"\n    assert.documentHTMLEqual(HTMLParser.parse(html).getDocument(), expectedHTML)\n  })\n\n  test(\"parses attachment caption from large html string\", () => {\n    let { html } = fixtures[\"image attachment with edited caption\"]\n\n    for (let i = 1; i <= 30; i++) {\n      html += fixtures[\"image attachment\"].html\n    }\n\n    for (let n = 1; n <= 3; n++) {\n      const attachmentPiece = HTMLParser.parse(html).getDocument().getAttachmentPieces()[0]\n      assert.equal(attachmentPiece.getCaption(), \"Example\")\n    }\n  })\n\n  test(\"parses foreground color when configured\", () => {\n    const attrConfig = { foregroundColor: { styleProperty: \"color\" } }\n\n    withTextAttributeConfig(attrConfig, () => {\n      const html = \"<span style=\\\"color: rgb(60, 179, 113);\\\">green</span>\"\n      const expectedHTML = \"<div><!--block--><span style=\\\"color: rgb(60, 179, 113);\\\">green</span></div>\"\n      const document = HTMLParser.parse(html).getDocument()\n      assert.documentHTMLEqual(document, expectedHTML)\n    })\n  })\n\n  test(\"parses background color when configured\", () => {\n    const attrConfig = { backgroundColor: { styleProperty: \"backgroundColor\" } }\n\n    withTextAttributeConfig(attrConfig, () => {\n      const html = \"<span style=\\\"background-color: yellow;\\\">on yellow</span>\"\n      const expectedHTML = \"<div><!--block--><span style=\\\"background-color: yellow;\\\">on yellow</span></div>\"\n      const document = HTMLParser.parse(html).getDocument()\n      assert.documentHTMLEqual(document, expectedHTML)\n    })\n  })\n\n  test(\"parses configured foreground color on formatted text\", () => {\n    const attrConfig = { foregroundColor: { styleProperty: \"color\" } }\n\n    withTextAttributeConfig(attrConfig, () => {\n      const html = \"<strong style=\\\"color: rgb(60, 179, 113);\\\">GREEN</strong>\"\n      const expectedHTML = \"<div><!--block--><strong style=\\\"color: rgb(60, 179, 113);\\\">GREEN</strong></div>\"\n      const document = HTMLParser.parse(html).getDocument()\n      assert.documentHTMLEqual(document, expectedHTML)\n    })\n  })\n\n  test(\"parses foreground color using configured parser function\", () => {\n    const attrConfig = {\n      foregroundColor: {\n        styleProperty: \"color\",\n        parser(element) {\n          const { color } = element.style\n          if (color === \"rgb(60, 179, 113)\") {\n            return color\n          }\n        },\n      },\n    }\n\n    withTextAttributeConfig(attrConfig, () => {\n      const html = \"<span style=\\\"color: rgb(60, 179, 113);\\\">green</span><span style=\\\"color: yellow;\\\">not yellow</span>\"\n      const expectedHTML = \"<div><!--block--><span style=\\\"color: rgb(60, 179, 113);\\\">green</span>not yellow</div>\"\n      const document = HTMLParser.parse(html).getDocument()\n      assert.documentHTMLEqual(document, expectedHTML)\n    })\n  })\n})\n\nconst withParserConfig = (attrConfig = {}, fn) => {\n  withConfig(\"parser\", attrConfig, fn)\n}\n\nconst withTextAttributeConfig = (attrConfig = {}, fn) => {\n  withConfig(\"textAttributes\", attrConfig, fn)\n}\n\nconst withConfig = (section, newConfig = {}, fn) => {\n  const originalConfig = Object.assign({}, config[section])\n  const copy = (section, properties) => {\n    for (const [ key, value ] of Object.entries(properties)) {\n      if (value) {\n        config[section][key] = value\n      } else {\n        delete config[section][key]\n      }\n    }\n  }\n\n  try {\n    copy(section, newConfig)\n    fn()\n  } finally {\n    copy(section, originalConfig)\n  }\n}\n\nconst getOrigin = () => {\n  const { protocol, hostname, port } = window.location\n  return `${protocol}//${hostname}${port ? `:${port}` : \"\"}`\n}\n"
  },
  {
    "path": "src/test/unit/html_sanitizer_test.js",
    "content": "import {\n  assert,\n  test,\n  testGroup,\n} from \"test/test_helper\"\n\nimport { HTMLSanitizer } from \"../../trix/models\"\nimport * as config from \"../../trix/config\"\n\ntestGroup(\"HTMLSanitizer\", () => {\n  test(\"strips custom tags\", () => {\n    const html = \"<custom-tag></custom-tag>\"\n    const expectedHTML = \"\"\n    const document = HTMLSanitizer.sanitize(html).body.innerHTML\n    assert.equal(document, expectedHTML)\n  })\n\n  test(\"strips data-trix-serialized-attributes\", () => {\n    const html = \"<div data-trix-serialized-attributes=\\\"{}\\\">content</div>\"\n    const sanitized = HTMLSanitizer.sanitize(html).body.innerHTML\n    assert.notOk(sanitized.includes(\"data-trix-serialized-attributes\"))\n  })\n\n  test(\"preserves other data-trix-* attributes\", () => {\n    const html = \"<div data-trix-attachment=\\\"{}\\\">content</div>\"\n    const sanitized = HTMLSanitizer.sanitize(html).body.innerHTML\n    assert.ok(sanitized.includes(\"data-trix-attachment\"))\n  })\n\n  test(\"keeps custom tags configured for DOMPurify\", () => {\n    const config = {\n      ADD_TAGS: [ \"custom-tag\" ],\n      RETURN_DOM: true,\n    }\n    withDOMPurifyConfig(config, () => {\n      const html = \"<custom-tag></custom-tag>\"\n      const expectedHTML = \"<custom-tag></custom-tag>\"\n      const document = HTMLSanitizer.sanitize(html).body.innerHTML\n      assert.equal(document, expectedHTML)\n    })\n  })\n\n})\n\nconst withDOMPurifyConfig = (attrConfig = {}, fn) => {\n  withConfig(\"dompurify\", attrConfig, fn)\n}\n\nconst withConfig = (section, newConfig = {}, fn) => {\n  const originalConfig = Object.assign({}, config[section])\n  const copy = (section, properties) => {\n    for (const [ key, value ] of Object.entries(properties)) {\n      if (value) {\n        config[section][key] = value\n      } else {\n        delete config[section][key]\n      }\n    }\n  }\n\n  try {\n    copy(section, newConfig)\n    fn()\n  } finally {\n    copy(section, originalConfig)\n  }\n}\n"
  },
  {
    "path": "src/test/unit/location_mapper_test.js",
    "content": "import { TEST_IMAGE_URL, assert, test, testGroup } from \"test/test_helper\"\n\nimport DocumentView from \"trix/views/document_view\"\nimport Document from \"trix/models/document\"\nimport LocationMapper from \"trix/models/location_mapper\"\n\ntestGroup(\"LocationMapper\", () => {\n  test(\"findLocationFromContainerAndOffset\", () => {\n    setDocument([\n      // <trix-editor>\n      // 0 <div>\n      //     0 <!--block-->\n      //     1 <strong>\n      //         0 a\n      //         1 <br>\n      //       </strong>\n      //     2 <br>\n      //   </div>\n      // 1 <blockquote>\n      //     0 <!--block-->\n      //     1 b😭cd\n      //     2 <span data-trix-cursor-target>\n      //         0 (zero-width space)\n      //       </span>\n      //     3 <a href=\"data:image/png,\" data-trix-attachment=\"\" ...>\n      //         0 <figure ...>...</figure>\n      //       </a>\n      //     4 <span data-trix-cursor-target>\n      //         0 (zero-width space)\n      //       </span>\n      //     5 e\n      //   </blockquote>\n      // </trix-editor>\n      {\n        text: [\n          { type: \"string\", attributes: { bold: true }, string: \"a\\n\" },\n          { type: \"string\", attributes: { blockBreak: true }, string: \"\\n\" },\n        ],\n        attributes: [],\n      },\n      {\n        text: [\n          { type: \"string\", attributes: {}, string: \"b😭cd\" },\n          {\n            type: \"attachment\",\n            attributes: {},\n            attachment: {\n              contentType: \"image/png\",\n              filename: \"x.png\",\n              filesize: 0,\n              height: 13,\n              href: TEST_IMAGE_URL,\n              identifier: \"1\",\n              url: TEST_IMAGE_URL,\n              width: 15,\n            },\n          },\n          { type: \"string\", attributes: {}, string: \"e\" },\n          { type: \"string\", attributes: { blockBreak: true }, string: \"\\n\" },\n        ],\n        attributes: [ \"quote\" ],\n      },\n    ])\n\n    const assertions = [\n      { location: [ 0, 0 ], container: [], offset: 0 },\n      { location: [ 0, 0 ], container: [ 0 ], offset: 0 },\n      { location: [ 0, 0 ], container: [ 0 ], offset: 1 },\n      { location: [ 0, 0 ], container: [ 0, 1 ], offset: 0 },\n      { location: [ 0, 0 ], container: [ 0, 1, 0 ], offset: 0 },\n      { location: [ 0, 1 ], container: [ 0, 1, 0 ], offset: 1 },\n      { location: [ 0, 1 ], container: [ 0, 1 ], offset: 1 },\n      { location: [ 0, 2 ], container: [ 0, 1 ], offset: 2 },\n      { location: [ 0, 2 ], container: [ 0 ], offset: 2 },\n      { location: [ 0, 3 ], container: [], offset: 1 },\n      { location: [ 0, 3 ], container: [ 1 ], offset: 0 },\n      { location: [ 1, 0 ], container: [ 1 ], offset: 1 },\n      { location: [ 1, 0 ], container: [ 1, 1 ], offset: 0 },\n      { location: [ 1, 1 ], container: [ 1, 1 ], offset: 1 },\n      { location: [ 1, 2 ], container: [ 1, 1 ], offset: 2 },\n      { location: [ 1, 3 ], container: [ 1, 1 ], offset: 3 },\n      { location: [ 1, 4 ], container: [ 1, 1 ], offset: 4 },\n      { location: [ 1, 5 ], container: [ 1, 1 ], offset: 5 },\n      { location: [ 1, 6 ], container: [ 1, 1 ], offset: 6 },\n      { location: [ 1, 5 ], container: [ 1 ], offset: 2 },\n      { location: [ 1, 5 ], container: [ 1, 2 ], offset: 0 },\n      { location: [ 1, 5 ], container: [ 1, 2 ], offset: 1 },\n      { location: [ 1, 5 ], container: [ 1 ], offset: 3 },\n      { location: [ 1, 5 ], container: [ 1, 3 ], offset: 0 },\n      { location: [ 1, 5 ], container: [ 1, 3 ], offset: 1 },\n      { location: [ 1, 6 ], container: [ 1 ], offset: 4 },\n      { location: [ 1, 6 ], container: [ 1, 4 ], offset: 0 },\n      { location: [ 1, 6 ], container: [ 1, 4 ], offset: 1 },\n      { location: [ 1, 6 ], container: [ 1 ], offset: 5 },\n      { location: [ 1, 6 ], container: [ 1, 5 ], offset: 0 },\n      { location: [ 1, 7 ], container: [ 1, 5 ], offset: 1 },\n      { location: [ 1, 7 ], container: [], offset: 2 },\n    ]\n\n    for (const assertion of assertions) {\n      const path = assertion.container\n      const container = findContainer(path)\n      const { offset } = assertion\n\n      const expectedLocation = { index: assertion.location[0], offset: assertion.location[1] }\n      const actualLocation = mapper.findLocationFromContainerAndOffset(container, offset)\n\n      assert.equal(\n        format(actualLocation),\n        format(expectedLocation),\n        `${describe(container)} at [${path.join(\", \")}], offset ${offset} = ${format(expectedLocation)}`\n      )\n    }\n  })\n\n  test(\"findContainerAndOffsetFromLocation: (0/0)\", () => {\n    setDocument([\n      // <trix-editor>\n      // 0 <ul>\n      //     0 <li>\n      //         0 <!--block-->\n      //         1 <br>\n      //       </li>\n      //   </ul>\n      // </trix-editor>\n      {\n        text: [ { type: \"string\", attributes: { blockBreak: true }, string: \"\\n\" } ],\n        attributes: [ \"bulletList\", \"bullet\" ],\n      },\n    ])\n\n    const location = { index: 0, offset: 0 }\n    const container = findContainer([ 0, 0 ])\n    const offset = 1\n\n    assert.deepEqual(mapper.findContainerAndOffsetFromLocation(location), [ container, offset ])\n  })\n\n  test(\"findContainerAndOffsetFromLocation after newline in formatted text\", () => {\n    setDocument([\n      // <trix-editor>\n      // 0 <div>\n      //     0 <!--block-->\n      //     0 <strong>\n      //         0 a\n      //         1 <br>\n      //       </strong>\n      //   </div>\n      // </trix-editor>\n      {\n        text: [\n          { type: \"string\", attributes: { bold: true }, string: \"a\\n\" },\n          { type: \"string\", attributes: { blockBreak: true }, string: \"\\n\" },\n        ],\n        attributes: [],\n      },\n    ])\n\n    const location = { index: 0, offset: 2 }\n    const container = findContainer([ 0 ])\n    const offset = 2\n\n    assert.deepEqual(mapper.findContainerAndOffsetFromLocation(location), [ container, offset ])\n  })\n\n  test(\"findContainerAndOffsetFromLocation after nested block\", () => {\n    setDocument([\n      // <trix-editor>\n      //   <blockquote>\n      //     <ul>\n      //       <li>\n      //         <!--block-->\n      //         a\n      //       </li>\n      //     </ul>\n      //     <!--block-->\n      //     <br>\n      //   </blockquote>\n      // </trix-editor>\n      {\n        text: [\n          { type: \"string\", attributes: {}, string: \"a\" },\n          { type: \"string\", attributes: { blockBreak: true }, string: \"\\n\" },\n        ],\n        attributes: [ \"quote\", \"bulletList\", \"bullet\" ],\n      },\n      {\n        text: [ { type: \"string\", attributes: { blockBreak: true }, string: \"\\n\" } ],\n        attributes: [ \"quote\" ],\n      },\n    ])\n\n    const location = { index: 1, offset: 0 }\n    const container = findContainer([ 0 ])\n    const offset = 2\n\n    assert.deepEqual(mapper.findContainerAndOffsetFromLocation(location), [ container, offset ])\n  })\n})\n\n// ---\nlet document = null\nlet element = null\nlet mapper = null\n\nconst setDocument = (json) => {\n  document = Document.fromJSON(json)\n  element = DocumentView.render(document)\n  mapper = new LocationMapper(element)\n}\n\nconst findContainer = (path) => {\n  let el = element\n  for (const index of path) {\n    el = el.childNodes[index]\n  }\n  return el\n}\n\nconst format = ({ index, offset }) => `${index}/${offset}`\n\nconst describe = (node) => {\n  if (node.nodeType === Node.TEXT_NODE) {\n    return `text node ${JSON.stringify(node.textContent)}`\n  } else {\n    return `container <${node.tagName.toLowerCase()}>`\n  }\n}\n"
  },
  {
    "path": "src/test/unit/mutation_observer_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\n\nimport MutationObserver from \"trix/observers/mutation_observer\"\nimport { nextFrame } from \"../test_helpers/timing_helpers\"\n\nlet observer = null\nlet element = null\nlet summaries = []\n\nconst install = function (html) {\n  element = document.createElement(\"div\")\n  if (html) {\n    element.innerHTML = html\n  }\n  observer = new MutationObserver(element)\n  observer.delegate = {\n    elementDidMutate(summary) {\n      summaries.push(summary)\n    },\n  }\n}\n\nconst uninstall = () => {\n  observer?.stop()\n  observer = null\n  element = null\n  summaries = []\n}\n\nconst observerTest = (name, options = {}, callback) => {\n  test(name, async () => {\n    install(options.html)\n    await callback()\n    uninstall()\n  })\n}\n\ntestGroup(\"MutationObserver\", () => {\n  observerTest(\"add character\", { html: \"a\" }, async () => {\n    element.firstChild.data += \"b\"\n    await nextFrame()\n\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textAdded: \"b\" })\n  })\n\n  observerTest(\"remove character\", { html: \"ab\" }, async () => {\n    element.firstChild.data = \"a\"\n    await nextFrame()\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textDeleted: \"b\" })\n  })\n\n  observerTest(\"replace character\", { html: \"ab\" }, async () => {\n    element.firstChild.data = \"ac\"\n    await nextFrame()\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textAdded: \"c\", textDeleted: \"b\" })\n  })\n\n  observerTest(\"add <br>\", { html: \"a\" }, async () => {\n    element.appendChild(document.createElement(\"br\"))\n    await nextFrame()\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textAdded: \"\\n\" })\n  })\n\n  observerTest(\"remove <br>\", { html: \"a<br>\" }, async () => {\n    element.removeChild(element.lastChild)\n    await nextFrame()\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textDeleted: \"\\n\" })\n  })\n\n  observerTest(\"remove block comment\", { html: \"<div><!--block-->a</div>\" }, async () => {\n    element.firstChild.removeChild(element.firstChild.firstChild)\n    await nextFrame()\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textDeleted: \"\\n\" })\n  })\n\n  observerTest(\"remove formatted element\", { html: \"a<strong>b</strong>\" }, async () => {\n    element.removeChild(element.lastChild)\n    await nextFrame()\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textDeleted: \"b\" })\n  })\n\n  observerTest(\"remove nested formatted elements\", { html: \"a<strong>b<em>c</em></strong>\" }, async () => {\n    element.removeChild(element.lastChild)\n    await nextFrame()\n    assert.equal(summaries.length, 1)\n    assert.deepEqual(summaries[0], { textDeleted: \"bc\" })\n  })\n})\n"
  },
  {
    "path": "src/test/unit/serialization_test.js",
    "content": "import { serializeToContentType } from \"trix/core/serialization\"\nimport { assert, eachFixture, test, testGroup } from \"test/test_helper\"\n\ntestGroup(\"serializeToContentType\", () => {\n  eachFixture((name, details) => {\n    if (details.serializedHTML) {\n      test(name, () => {\n        assert.equal(serializeToContentType(details.document, \"text/html\"), details.serializedHTML)\n      })\n    }\n  })\n})\n"
  },
  {
    "path": "src/test/unit/string_change_summary_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\n\nimport { summarizeStringChange } from \"trix/core/helpers\"\n\ntestGroup(\"summarizeStringChange\", () => {\n  const assertions = {\n    \"no change\": {\n      oldString: \"abc\",\n      newString: \"abc\",\n      change: { added: \"\", removed: \"\" },\n    },\n    \"adding a character\": {\n      oldString: \"\",\n      newString: \"a\",\n      change: { added: \"a\", removed: \"\" },\n    },\n    \"appending a character\": {\n      oldString: \"ab\",\n      newString: \"abc\",\n      change: { added: \"c\", removed: \"\" },\n    },\n    \"appending a multibyte character\": {\n      oldString: \"a💩\",\n      newString: \"a💩💩\",\n      change: { added: \"💩\", removed: \"\" },\n    },\n    \"prepending a character\": {\n      oldString: \"bc\",\n      newString: \"abc\",\n      change: { added: \"a\", removed: \"\" },\n    },\n    \"inserting a character\": {\n      oldString: \"ac\",\n      newString: \"abc\",\n      change: { added: \"b\", removed: \"\" },\n    },\n    \"inserting a string\": {\n      oldString: \"ac\",\n      newString: \"aZZZc\",\n      change: { added: \"ZZZ\", removed: \"\" },\n    },\n    \"replacing a character\": {\n      oldString: \"abc\",\n      newString: \"aZc\",\n      change: { added: \"Z\", removed: \"b\" },\n    },\n    \"replacing a character with a string\": {\n      oldString: \"abc\",\n      newString: \"aXYc\",\n      change: { added: \"XY\", removed: \"b\" },\n    },\n    \"replacing a string with a character\": {\n      oldString: \"abcde\",\n      newString: \"aXe\",\n      change: { added: \"X\", removed: \"bcd\" },\n    },\n    \"replacing a string with a string\": {\n      oldString: \"abcde\",\n      newString: \"aBCDe\",\n      change: { added: \"BCD\", removed: \"bcd\" },\n    },\n    \"removing a character\": {\n      oldString: \"abc\",\n      newString: \"ac\",\n      change: { added: \"\", removed: \"b\" },\n    },\n  }\n\n  for (const name in assertions) {\n    test(name, () => {\n      const details = assertions[name]\n      const { oldString, newString, change } = details\n      assert.deepEqual(summarizeStringChange(oldString, newString), change)\n    })\n  }\n})\n"
  },
  {
    "path": "src/test/unit/text_test.js",
    "content": "import { assert, test, testGroup } from \"test/test_helper\"\n\nimport Text from \"trix/models/text\"\nimport StringPiece from \"trix/models/string_piece\"\n\ntestGroup(\"Text\", () =>\n  testGroup(\"#removeTextAtRange\", () => {\n    test(\"removes text with range in single piece\", () => {\n      const text = new Text([ new StringPiece(\"abc\") ])\n      const pieces = text.removeTextAtRange([ 0, 1 ]).getPieces()\n      assert.equal(pieces.length, 1)\n      assert.equal(pieces[0].toString(), \"bc\")\n      assert.deepEqual(pieces[0].getAttributes(), {})\n    })\n\n    test(\"removes text with range spanning pieces\", () => {\n      const text = new Text([ new StringPiece(\"abc\"), new StringPiece(\"123\", { bold: true }) ])\n      const pieces = text.removeTextAtRange([ 2, 4 ]).getPieces()\n      assert.equal(pieces.length, 2)\n      assert.equal(pieces[0].toString(), \"ab\")\n      assert.deepEqual(pieces[0].getAttributes(), {})\n      assert.equal(pieces[1].toString(), \"23\")\n      assert.deepEqual(pieces[1].getAttributes(), { bold: true })\n    })\n  })\n)\n"
  },
  {
    "path": "src/test/unit.js",
    "content": "import \"test/unit/attachment_test\"\nimport \"test/unit/bidi_test\"\nimport \"test/unit/block_test\"\nimport \"test/unit/composition_test\"\nimport \"test/unit/document_test\"\nimport \"test/unit/document_view_test\"\nimport \"test/unit/helpers/custom_elements_test\"\nimport \"test/unit/html_parser_test\"\nimport \"test/unit/html_sanitizer_test\"\nimport \"test/unit/location_mapper_test\"\nimport \"test/unit/mutation_observer_test\"\nimport \"test/unit/serialization_test\"\nimport \"test/unit/string_change_summary_test\"\nimport \"test/unit/text_test\"\n"
  },
  {
    "path": "src/trix/config/attachments.js",
    "content": "export const attachmentSelector = \"[data-trix-attachment]\"\n\nconst attachments = {\n  preview: {\n    presentation: \"gallery\",\n    caption: {\n      name: true,\n      size: true,\n    },\n  },\n  file: {\n    caption: {\n      size: true,\n    },\n  },\n}\nexport default attachments\n"
  },
  {
    "path": "src/trix/config/block_attributes.js",
    "content": "const attributes = {\n  default: {\n    tagName: \"div\",\n    parse: false,\n  },\n  quote: {\n    tagName: \"blockquote\",\n    nestable: true,\n  },\n  heading1: {\n    tagName: \"h1\",\n    terminal: true,\n    breakOnReturn: true,\n    group: false,\n  },\n  code: {\n    tagName: \"pre\",\n    terminal: true,\n    htmlAttributes: [ \"language\" ],\n    text: {\n      plaintext: true,\n    },\n  },\n  bulletList: {\n    tagName: \"ul\",\n    parse: false,\n  },\n  bullet: {\n    tagName: \"li\",\n    listAttribute: \"bulletList\",\n    group: false,\n    nestable: true,\n    test(element) {\n      return tagName(element.parentNode) === attributes[this.listAttribute].tagName\n    },\n  },\n  numberList: {\n    tagName: \"ol\",\n    parse: false,\n  },\n  number: {\n    tagName: \"li\",\n    listAttribute: \"numberList\",\n    group: false,\n    nestable: true,\n    test(element) {\n      return tagName(element.parentNode) === attributes[this.listAttribute].tagName\n    },\n  },\n  attachmentGallery: {\n    tagName: \"div\",\n    exclusive: true,\n    terminal: true,\n    parse: false,\n    group: false,\n  },\n}\n\nconst tagName = (element) => element?.tagName?.toLowerCase()\n\nexport default attributes\n"
  },
  {
    "path": "src/trix/config/browser.js",
    "content": "const androidVersionMatch = navigator.userAgent.match(/android\\s([0-9]+.*Chrome)/i)\nconst androidVersion = androidVersionMatch && parseInt(androidVersionMatch[1])\n\nexport default {\n  // Android emits composition events when moving the cursor through existing text\n  // Introduced in Chrome 65: https://bugs.chromium.org/p/chromium/issues/detail?id=764439#c9\n  composesExistingText: /Android.*Chrome/.test(navigator.userAgent),\n\n  // Android 13, especially on Samsung keyboards, emits extra compositionend and beforeinput events\n  // that can make the input handler lose the current selection or enter an infinite input -> render -> input\n  // loop.\n  recentAndroid: androidVersion && androidVersion > 12,\n  samsungAndroid: androidVersion && navigator.userAgent.match(/Android.*SM-/),\n\n  // IE 11 activates resizing handles on editable elements that have \"layout\"\n  forcesObjectResizing: /Trident.*rv:11/.test(navigator.userAgent),\n  // https://www.w3.org/TR/input-events-1/ + https://www.w3.org/TR/input-events-2/\n  supportsInputEvents: typeof InputEvent !== \"undefined\" &&\n    [ \"data\", \"getTargetRanges\", \"inputType\" ].every(prop => prop in InputEvent.prototype),\n}\n"
  },
  {
    "path": "src/trix/config/css.js",
    "content": "export default {\n  attachment: \"attachment\",\n  attachmentCaption: \"attachment__caption\",\n  attachmentCaptionEditor: \"attachment__caption-editor\",\n  attachmentMetadata: \"attachment__metadata\",\n  attachmentMetadataContainer: \"attachment__metadata-container\",\n  attachmentName: \"attachment__name\",\n  attachmentProgress: \"attachment__progress\",\n  attachmentSize: \"attachment__size\",\n  attachmentToolbar: \"attachment__toolbar\",\n  attachmentGallery: \"attachment-gallery\",\n}\n"
  },
  {
    "path": "src/trix/config/dompurify.js",
    "content": "export default {\n  ADD_ATTR: [ \"language\" ],\n  SAFE_FOR_XML: false,\n  RETURN_DOM: true\n}\n"
  },
  {
    "path": "src/trix/config/file_size_formatting.js",
    "content": "/* eslint-disable\n    no-case-declarations,\n*/\nimport lang from \"trix/config/lang\"\n\nconst sizes = [ lang.bytes, lang.KB, lang.MB, lang.GB, lang.TB, lang.PB ]\n\nexport default {\n  prefix: \"IEC\",\n  precision: 2,\n\n  formatter(number) {\n    switch (number) {\n      case 0:\n        return `0 ${lang.bytes}`\n      case 1:\n        return `1 ${lang.byte}`\n      default:\n        let base\n\n        if (this.prefix === \"SI\") {\n          base = 1000\n        } else if (this.prefix === \"IEC\") {\n          base = 1024\n        }\n\n        const exp = Math.floor(Math.log(number) / Math.log(base))\n        const humanSize = number / Math.pow(base, exp)\n        const string = humanSize.toFixed(this.precision)\n        const withoutInsignificantZeros = string.replace(/0*$/, \"\").replace(/\\.$/, \"\")\n        return `${withoutInsignificantZeros} ${sizes[exp]}`\n    }\n  },\n}\n"
  },
  {
    "path": "src/trix/config/index.js",
    "content": "export { default as attachments } from \"./attachments\"\nexport { default as blockAttributes } from \"./block_attributes\"\nexport { default as browser } from \"./browser\"\nexport { default as css } from \"./css\"\nexport { default as dompurify } from \"./dompurify\"\nexport { default as fileSize } from \"./file_size_formatting\"\nexport { default as input } from \"./input\"\nexport { default as keyNames } from \"./key_names\"\nexport { default as lang } from \"./lang\"\nexport { default as parser } from \"./parser\"\nexport { default as textAttributes } from \"./text_attributes\"\nexport { default as toolbar } from \"./toolbar\"\nexport { default as undo } from \"./undo\"\n"
  },
  {
    "path": "src/trix/config/input.js",
    "content": "import browser from \"trix/config/browser\"\nimport { makeElement, removeNode } from \"trix/core/helpers/dom\"\n\nconst input = {\n  level2Enabled: true,\n\n  getLevel() {\n    if (this.level2Enabled && browser.supportsInputEvents) {\n      return 2\n    } else {\n      return 0\n    }\n  },\n  pickFiles(callback) {\n    const input = makeElement(\"input\", { type: \"file\", multiple: true, hidden: true, id: this.fileInputId })\n\n    input.addEventListener(\"change\", () => {\n      callback(input.files)\n      removeNode(input)\n    })\n\n    removeNode(document.getElementById(this.fileInputId))\n    document.body.appendChild(input)\n    input.click()\n  }\n}\n\nexport default input\n"
  },
  {
    "path": "src/trix/config/key_names.js",
    "content": "export default {\n  8: \"backspace\",\n  9: \"tab\",\n  13: \"return\",\n  27: \"escape\",\n  37: \"left\",\n  39: \"right\",\n  46: \"delete\",\n  68: \"d\",\n  72: \"h\",\n  79: \"o\",\n}\n"
  },
  {
    "path": "src/trix/config/lang.js",
    "content": "export default {\n  attachFiles: \"Attach Files\",\n  bold: \"Bold\",\n  bullets: \"Bullets\",\n  byte: \"Byte\",\n  bytes: \"Bytes\",\n  captionPlaceholder: \"Add a caption…\",\n  code: \"Code\",\n  heading1: \"Heading\",\n  indent: \"Increase Level\",\n  italic: \"Italic\",\n  link: \"Link\",\n  numbers: \"Numbers\",\n  outdent: \"Decrease Level\",\n  quote: \"Quote\",\n  redo: \"Redo\",\n  remove: \"Remove\",\n  strike: \"Strikethrough\",\n  undo: \"Undo\",\n  unlink: \"Unlink\",\n  url: \"URL\",\n  urlPlaceholder: \"Enter a URL…\",\n  GB: \"GB\",\n  KB: \"KB\",\n  MB: \"MB\",\n  PB: \"PB\",\n  TB: \"TB\",\n}\n"
  },
  {
    "path": "src/trix/config/parser.js",
    "content": "export default {\n  removeBlankTableCells: false,\n  tableCellSeparator: \" | \",\n  tableRowSeparator: \"\\n\",\n}\n"
  },
  {
    "path": "src/trix/config/text_attributes.js",
    "content": "import { attachmentSelector } from \"trix/config/attachments\"\n\nexport default {\n  bold: {\n    tagName: \"strong\",\n    inheritable: true,\n    parser(element) {\n      const style = window.getComputedStyle(element)\n      return style.fontWeight === \"bold\" || style.fontWeight >= 600\n    },\n  },\n  italic: {\n    tagName: \"em\",\n    inheritable: true,\n    parser(element) {\n      const style = window.getComputedStyle(element)\n      return style.fontStyle === \"italic\"\n    },\n  },\n  href: {\n    groupTagName: \"a\",\n    parser(element) {\n      const matchingSelector = `a:not(${attachmentSelector})`\n      const link = element.closest(matchingSelector)\n      if (link) {\n        return link.getAttribute(\"href\")\n      }\n    },\n  },\n  strike: {\n    tagName: \"del\",\n    inheritable: true,\n  },\n  frozen: {\n    style: { backgroundColor: \"highlight\" },\n  },\n}\n"
  },
  {
    "path": "src/trix/config/toolbar.js",
    "content": "import lang from \"trix/config/lang\"\n\nexport default {\n  getDefaultHTML() {\n    return `<div class=\"trix-button-row\">\n      <span class=\"trix-button-group trix-button-group--text-tools\" data-trix-button-group=\"text-tools\">\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bold\" data-trix-attribute=\"bold\" data-trix-key=\"b\" title=\"${lang.bold}\" tabindex=\"-1\">${lang.bold}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-italic\" data-trix-attribute=\"italic\" data-trix-key=\"i\" title=\"${lang.italic}\" tabindex=\"-1\">${lang.italic}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-strike\" data-trix-attribute=\"strike\" title=\"${lang.strike}\" tabindex=\"-1\">${lang.strike}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-link\" data-trix-attribute=\"href\" data-trix-action=\"link\" data-trix-key=\"k\" title=\"${lang.link}\" tabindex=\"-1\">${lang.link}</button>\n      </span>\n\n      <span class=\"trix-button-group trix-button-group--block-tools\" data-trix-button-group=\"block-tools\">\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-heading-1\" data-trix-attribute=\"heading1\" title=\"${lang.heading1}\" tabindex=\"-1\">${lang.heading1}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-quote\" data-trix-attribute=\"quote\" title=\"${lang.quote}\" tabindex=\"-1\">${lang.quote}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-code\" data-trix-attribute=\"code\" title=\"${lang.code}\" tabindex=\"-1\">${lang.code}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bullet-list\" data-trix-attribute=\"bullet\" title=\"${lang.bullets}\" tabindex=\"-1\">${lang.bullets}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-number-list\" data-trix-attribute=\"number\" title=\"${lang.numbers}\" tabindex=\"-1\">${lang.numbers}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-decrease-nesting-level\" data-trix-action=\"decreaseNestingLevel\" title=\"${lang.outdent}\" tabindex=\"-1\">${lang.outdent}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-increase-nesting-level\" data-trix-action=\"increaseNestingLevel\" title=\"${lang.indent}\" tabindex=\"-1\">${lang.indent}</button>\n      </span>\n\n      <span class=\"trix-button-group trix-button-group--file-tools\" data-trix-button-group=\"file-tools\">\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-attach\" data-trix-action=\"attachFiles\" title=\"${lang.attachFiles}\" tabindex=\"-1\">${lang.attachFiles}</button>\n      </span>\n\n      <span class=\"trix-button-group-spacer\"></span>\n\n      <span class=\"trix-button-group trix-button-group--history-tools\" data-trix-button-group=\"history-tools\">\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-undo\" data-trix-action=\"undo\" data-trix-key=\"z\" title=\"${lang.undo}\" tabindex=\"-1\">${lang.undo}</button>\n        <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-redo\" data-trix-action=\"redo\" data-trix-key=\"shift+z\" title=\"${lang.redo}\" tabindex=\"-1\">${lang.redo}</button>\n      </span>\n    </div>\n\n    <div class=\"trix-dialogs\" data-trix-dialogs>\n      <div class=\"trix-dialog trix-dialog--link\" data-trix-dialog=\"href\" data-trix-dialog-attribute=\"href\">\n        <div class=\"trix-dialog__link-fields\">\n          <input type=\"url\" name=\"href\" class=\"trix-input trix-input--dialog\" placeholder=\"${lang.urlPlaceholder}\" aria-label=\"${lang.url}\" data-trix-validate-href required data-trix-input>\n          <div class=\"trix-button-group\">\n            <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"${lang.link}\" data-trix-method=\"setAttribute\">\n            <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"${lang.unlink}\" data-trix-method=\"removeAttribute\">\n          </div>\n        </div>\n      </div>\n    </div>`\n  },\n}\n"
  },
  {
    "path": "src/trix/config/undo.js",
    "content": "const undo = { interval: 5000 }\nexport default undo\n"
  },
  {
    "path": "src/trix/constants.js",
    "content": "export const ZERO_WIDTH_SPACE = \"\\uFEFF\"\nexport const NON_BREAKING_SPACE = \"\\u00A0\"\nexport const OBJECT_REPLACEMENT_CHARACTER = \"\\uFFFC\"\n"
  },
  {
    "path": "src/trix/controllers/attachment_editor_controller.js",
    "content": "import { removeNode } from \"trix/core/helpers\"\n\nimport * as config from \"trix/config\"\nimport BasicObject from \"trix/core/basic_object\"\n\nimport { defer, handleEvent, makeElement, tagName, triggerEvent } from \"trix/core/helpers\"\nconst { lang, css, keyNames } = config\n\nconst undoable = function(fn) {\n  return function() {\n    const commands = fn.apply(this, arguments)\n    commands.do()\n    if (!this.undos) {\n      this.undos = []\n    }\n    this.undos.push(commands.undo)\n  }\n}\n\nexport default class AttachmentEditorController extends BasicObject {\n  constructor(attachmentPiece, element, container, options = {}) {\n    super(...arguments)\n    this.didClickToolbar = this.didClickToolbar.bind(this)\n    this.didClickActionButton = this.didClickActionButton.bind(this)\n    this.didKeyDownCaption = this.didKeyDownCaption.bind(this)\n    this.didInputCaption = this.didInputCaption.bind(this)\n    this.didChangeCaption = this.didChangeCaption.bind(this)\n    this.didBlurCaption = this.didBlurCaption.bind(this)\n    this.attachmentPiece = attachmentPiece\n    this.element = element\n    this.container = container\n    this.options = options\n    this.attachment = this.attachmentPiece.attachment\n    if (tagName(this.element) === \"a\") {\n      this.element = this.element.firstChild\n    }\n    this.install()\n  }\n\n  install() {\n    this.makeElementMutable()\n    this.addToolbar()\n    if (this.attachment.isPreviewable()) {\n      this.installCaptionEditor()\n    }\n  }\n\n  uninstall() {\n    let undo = this.undos.pop()\n    this.savePendingCaption()\n    while (undo) {\n      undo()\n      undo = this.undos.pop()\n    }\n    this.delegate?.didUninstallAttachmentEditor(this)\n  }\n\n  // Private\n\n  savePendingCaption() {\n    if (this.pendingCaption != null) {\n      const caption = this.pendingCaption\n      this.pendingCaption = null\n      if (caption) {\n        this.delegate?.attachmentEditorDidRequestUpdatingAttributesForAttachment?.({ caption }, this.attachment)\n      } else {\n        this.delegate?.attachmentEditorDidRequestRemovingAttributeForAttachment?.(\"caption\", this.attachment)\n      }\n    }\n  }\n\n  // Installing and uninstalling\n\n  makeElementMutable = undoable(() => {\n    return {\n      do: () => {\n        this.element.dataset.trixMutable = true\n      },\n      undo: () => delete this.element.dataset.trixMutable,\n    }\n  })\n\n  addToolbar = undoable(() => {\n    // <div class=\"#{css.attachmentMetadataContainer}\" data-trix-mutable=\"true\">\n    //   <div class=\"trix-button-row\">\n    //     <span class=\"trix-button-group trix-button-group--actions\">\n    //       <button type=\"button\" class=\"trix-button trix-button--remove\" title=\"#{lang.remove}\" data-trix-action=\"remove\">#{lang.remove}</button>\n    //     </span>\n    //   </div>\n    // </div>\n    const element = makeElement({\n      tagName: \"div\",\n      className: css.attachmentToolbar,\n      data: { trixMutable: true },\n      childNodes: makeElement({\n        tagName: \"div\",\n        className: \"trix-button-row\",\n        childNodes: makeElement({\n          tagName: \"span\",\n          className: \"trix-button-group trix-button-group--actions\",\n          childNodes: makeElement({\n            tagName: \"button\",\n            className: \"trix-button trix-button--remove\",\n            textContent: lang.remove,\n            attributes: { title: lang.remove },\n            data: { trixAction: \"remove\" },\n          }),\n        }),\n      }),\n    })\n\n    if (this.attachment.isPreviewable()) {\n      // <div class=\"#{css.attachmentMetadataContainer}\">\n      //   <span class=\"#{css.attachmentMetadata}\">\n      //     <span class=\"#{css.attachmentName}\" title=\"#{name}\">#{name}</span>\n      //     <span class=\"#{css.attachmentSize}\">#{size}</span>\n      //   </span>\n      // </div>\n      element.appendChild(\n        makeElement({\n          tagName: \"div\",\n          className: css.attachmentMetadataContainer,\n          childNodes: makeElement({\n            tagName: \"span\",\n            className: css.attachmentMetadata,\n            childNodes: [\n              makeElement({\n                tagName: \"span\",\n                className: css.attachmentName,\n                textContent: this.attachment.getFilename(),\n                attributes: { title: this.attachment.getFilename() },\n              }),\n              makeElement({\n                tagName: \"span\",\n                className: css.attachmentSize,\n                textContent: this.attachment.getFormattedFilesize(),\n              }),\n            ],\n          }),\n        })\n      )\n    }\n\n    handleEvent(\"click\", { onElement: element, withCallback: this.didClickToolbar })\n    handleEvent(\"click\", {\n      onElement: element,\n      matchingSelector: \"[data-trix-action]\",\n      withCallback: this.didClickActionButton,\n    })\n\n    triggerEvent(\"trix-attachment-before-toolbar\", { onElement: this.element, attributes: { toolbar: element, attachment: this.attachment } })\n\n    return {\n      do: () => this.element.appendChild(element),\n      undo: () => removeNode(element),\n    }\n  })\n\n  installCaptionEditor = undoable(() => {\n    const textarea = makeElement({\n      tagName: \"textarea\",\n      className: css.attachmentCaptionEditor,\n      attributes: { placeholder: lang.captionPlaceholder },\n      data: { trixMutable: true },\n    })\n    textarea.value = this.attachmentPiece.getCaption()\n\n    const textareaClone = textarea.cloneNode()\n    textareaClone.classList.add(\"trix-autoresize-clone\")\n    textareaClone.tabIndex = -1\n\n    const autoresize = function() {\n      textareaClone.value = textarea.value\n      textarea.style.height = textareaClone.scrollHeight + \"px\"\n    }\n\n    handleEvent(\"input\", { onElement: textarea, withCallback: autoresize })\n    handleEvent(\"input\", { onElement: textarea, withCallback: this.didInputCaption })\n    handleEvent(\"keydown\", { onElement: textarea, withCallback: this.didKeyDownCaption })\n    handleEvent(\"change\", { onElement: textarea, withCallback: this.didChangeCaption })\n    handleEvent(\"blur\", { onElement: textarea, withCallback: this.didBlurCaption })\n\n    const figcaption = this.element.querySelector(\"figcaption\")\n    const editingFigcaption = figcaption.cloneNode()\n\n    return {\n      do: () => {\n        figcaption.style.display = \"none\"\n        editingFigcaption.appendChild(textarea)\n        editingFigcaption.appendChild(textareaClone)\n        editingFigcaption.classList.add(`${css.attachmentCaption}--editing`)\n        figcaption.parentElement.insertBefore(editingFigcaption, figcaption)\n        autoresize()\n        if (this.options.editCaption) {\n          return defer(() => textarea.focus())\n        }\n      },\n      undo() {\n        removeNode(editingFigcaption)\n        figcaption.style.display = null\n      },\n    }\n  })\n\n  // Event handlers\n\n  didClickToolbar(event) {\n    event.preventDefault()\n    return event.stopPropagation()\n  }\n\n  didClickActionButton(event) {\n    const action = event.target.getAttribute(\"data-trix-action\")\n    switch (action) {\n      case \"remove\":\n        return this.delegate?.attachmentEditorDidRequestRemovalOfAttachment(this.attachment)\n    }\n  }\n\n  didKeyDownCaption(event) {\n    if (keyNames[event.keyCode] === \"return\") {\n      event.preventDefault()\n      this.savePendingCaption()\n      return this.delegate?.attachmentEditorDidRequestDeselectingAttachment?.(this.attachment)\n    }\n  }\n\n  didInputCaption(event) {\n    this.pendingCaption = event.target.value.replace(/\\s/g, \" \").trim()\n  }\n\n  didChangeCaption(event) {\n    return this.savePendingCaption()\n  }\n\n  didBlurCaption(event) {\n    return this.savePendingCaption()\n  }\n}\n"
  },
  {
    "path": "src/trix/controllers/composition_controller.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\nimport DocumentView from \"trix/views/document_view\"\nimport AttachmentEditorController from \"trix/controllers/attachment_editor_controller\"\n\nimport { defer, findClosestElementFromNode, handleEvent, innerElementIsActive } from \"trix/core/helpers\"\nimport { attachmentSelector } from \"trix/config/attachments\"\n\nexport default class CompositionController extends BasicObject {\n  constructor(element, composition) {\n    super(...arguments)\n    this.didFocus = this.didFocus.bind(this)\n    this.didBlur = this.didBlur.bind(this)\n    this.didClickAttachment = this.didClickAttachment.bind(this)\n\n    this.element = element\n    this.composition = composition\n    this.documentView = new DocumentView(this.composition.document, { element: this.element })\n\n    handleEvent(\"focus\", { onElement: this.element, withCallback: this.didFocus })\n    handleEvent(\"blur\", { onElement: this.element, withCallback: this.didBlur })\n    handleEvent(\"click\", {\n      onElement: this.element,\n      matchingSelector: \"a[contenteditable=false]\",\n      preventDefault: true,\n    })\n    handleEvent(\"mousedown\", {\n      onElement: this.element,\n      matchingSelector: attachmentSelector,\n      withCallback: this.didClickAttachment,\n    })\n    handleEvent(\"click\", { onElement: this.element, matchingSelector: `a${attachmentSelector}`, preventDefault: true })\n  }\n\n  didFocus(event) {\n    const perform = () => {\n      if (!this.focused) {\n        this.focused = true\n        return this.delegate?.compositionControllerDidFocus?.()\n      }\n    }\n\n    return this.blurPromise?.then(perform) || perform()\n  }\n\n  didBlur(event) {\n    this.blurPromise = new Promise((resolve) => {\n      return defer(() => {\n        if (!innerElementIsActive(this.element)) {\n          this.focused = null\n          this.delegate?.compositionControllerDidBlur?.()\n        }\n        this.blurPromise = null\n        return resolve()\n      })\n    })\n  }\n\n  didClickAttachment(event, target) {\n    const attachment = this.findAttachmentForElement(target)\n    const editCaption = !!findClosestElementFromNode(event.target, { matchingSelector: \"figcaption\" })\n    return this.delegate?.compositionControllerDidSelectAttachment?.(attachment, { editCaption })\n  }\n\n  getSerializableElement() {\n    if (this.isEditingAttachment()) {\n      return this.documentView.shadowElement\n    } else {\n      return this.element\n    }\n  }\n\n  render() {\n    if (this.revision !== this.composition.revision) {\n      this.documentView.setDocument(this.composition.document)\n      this.documentView.render()\n      this.revision = this.composition.revision\n    }\n\n    if (this.canSyncDocumentView() && !this.documentView.isSynced()) {\n      this.delegate?.compositionControllerWillSyncDocumentView?.()\n      this.documentView.sync()\n      this.delegate?.compositionControllerDidSyncDocumentView?.()\n    }\n\n    return this.delegate?.compositionControllerDidRender?.()\n  }\n\n  rerenderViewForObject(object) {\n    this.invalidateViewForObject(object)\n    return this.render()\n  }\n\n  invalidateViewForObject(object) {\n    return this.documentView.invalidateViewForObject(object)\n  }\n\n  isViewCachingEnabled() {\n    return this.documentView.isViewCachingEnabled()\n  }\n\n  enableViewCaching() {\n    return this.documentView.enableViewCaching()\n  }\n\n  disableViewCaching() {\n    return this.documentView.disableViewCaching()\n  }\n\n  refreshViewCache() {\n    return this.documentView.garbageCollectCachedViews()\n  }\n\n  // Attachment editor management\n\n  isEditingAttachment() {\n    return !!this.attachmentEditor\n  }\n\n  installAttachmentEditorForAttachment(attachment, options) {\n    if (this.attachmentEditor?.attachment === attachment) return\n    const element = this.documentView.findElementForObject(attachment)\n    if (!element) return\n\n    this.uninstallAttachmentEditor()\n    const attachmentPiece = this.composition.document.getAttachmentPieceForAttachment(attachment)\n    this.attachmentEditor = new AttachmentEditorController(attachmentPiece, element, this.element, options)\n    this.attachmentEditor.delegate = this\n  }\n\n  uninstallAttachmentEditor() {\n    return this.attachmentEditor?.uninstall()\n  }\n\n  // Attachment controller delegate\n\n  didUninstallAttachmentEditor() {\n    this.attachmentEditor = null\n    return this.render()\n  }\n\n  attachmentEditorDidRequestUpdatingAttributesForAttachment(attributes, attachment) {\n    this.delegate?.compositionControllerWillUpdateAttachment?.(attachment)\n    return this.composition.updateAttributesForAttachment(attributes, attachment)\n  }\n\n  attachmentEditorDidRequestRemovingAttributeForAttachment(attribute, attachment) {\n    this.delegate?.compositionControllerWillUpdateAttachment?.(attachment)\n    return this.composition.removeAttributeForAttachment(attribute, attachment)\n  }\n\n  attachmentEditorDidRequestRemovalOfAttachment(attachment) {\n    return this.delegate?.compositionControllerDidRequestRemovalOfAttachment?.(attachment)\n  }\n\n  attachmentEditorDidRequestDeselectingAttachment(attachment) {\n    return this.delegate?.compositionControllerDidRequestDeselectingAttachment?.(attachment)\n  }\n\n  // Private\n\n  canSyncDocumentView() {\n    return !this.isEditingAttachment()\n  }\n\n  findAttachmentForElement(element) {\n    return this.composition.document.getAttachmentById(parseInt(element.dataset.trixId, 10))\n  }\n}\n"
  },
  {
    "path": "src/trix/controllers/controller.js",
    "content": "import \"trix/views/object_view\"\nimport BasicObject from \"trix/core/basic_object\"\n\nexport default class Controller extends BasicObject {}\n"
  },
  {
    "path": "src/trix/controllers/editor_controller.js",
    "content": "/* eslint-disable\n    id-length,\n*/\nimport * as config from \"trix/config\"\n\nimport { serializeToContentType } from \"trix/core/serialization\"\n\nimport Controller from \"trix/controllers/controller\"\nimport Level0InputController from \"trix/controllers/level_0_input_controller\"\nimport Level2InputController from \"trix/controllers/level_2_input_controller\"\nimport CompositionController from \"trix/controllers/composition_controller\"\nimport ToolbarController from \"trix/controllers/toolbar_controller\"\nimport Composition from \"trix/models/composition\"\nimport Editor from \"trix/models/editor\"\nimport AttachmentManager from \"trix/models/attachment_manager\"\nimport SelectionManager from \"trix/models/selection_manager\"\n\nimport { getBlockConfig, objectsAreEqual, rangeIsCollapsed, rangesAreEqual } from \"trix/core/helpers\"\nimport { selectionChangeObserver } from \"trix/observers/selection_change_observer\"\n\nconst snapshotsAreEqual = (a, b) => rangesAreEqual(a.selectedRange, b.selectedRange) && a.document.isEqualTo(b.document)\n\nexport default class EditorController extends Controller {\n  static actions = {\n    undo: {\n      test() {\n        return this.editor.canUndo()\n      },\n      perform() {\n        return this.editor.undo()\n      },\n    },\n    redo: {\n      test() {\n        return this.editor.canRedo()\n      },\n      perform() {\n        return this.editor.redo()\n      },\n    },\n    link: {\n      test() {\n        return this.editor.canActivateAttribute(\"href\")\n      },\n    },\n    increaseNestingLevel: {\n      test() {\n        return this.editor.canIncreaseNestingLevel()\n      },\n      perform() {\n        return this.editor.increaseNestingLevel() && this.render()\n      },\n    },\n    decreaseNestingLevel: {\n      test() {\n        return this.editor.canDecreaseNestingLevel()\n      },\n      perform() {\n        return this.editor.decreaseNestingLevel() && this.render()\n      },\n    },\n    attachFiles: {\n      test() {\n        return true\n      },\n      perform() {\n        return config.input.pickFiles(this.editor.insertFiles)\n      },\n    },\n  }\n\n  constructor({ editorElement, document, html }) {\n    super(...arguments)\n    this.editorElement = editorElement\n    this.selectionManager = new SelectionManager(this.editorElement)\n    this.selectionManager.delegate = this\n\n    this.composition = new Composition()\n    this.composition.delegate = this\n\n    this.attachmentManager = new AttachmentManager(this.composition.getAttachments())\n    this.attachmentManager.delegate = this\n\n    this.inputController =\n      config.input.getLevel() === 2\n        ? new Level2InputController(this.editorElement)\n        : new Level0InputController(this.editorElement)\n\n    this.inputController.delegate = this\n    this.inputController.responder = this.composition\n\n    this.compositionController = new CompositionController(this.editorElement, this.composition)\n    this.compositionController.delegate = this\n\n    this.toolbarController = new ToolbarController(this.editorElement.toolbarElement)\n    this.toolbarController.delegate = this\n\n    this.editor = new Editor(this.composition, this.selectionManager, this.editorElement)\n    if (document) {\n      this.editor.loadDocument(document)\n    } else {\n      this.editor.loadHTML(html)\n    }\n  }\n\n  registerSelectionManager() {\n    return selectionChangeObserver.registerSelectionManager(this.selectionManager)\n  }\n\n  unregisterSelectionManager() {\n    return selectionChangeObserver.unregisterSelectionManager(this.selectionManager)\n  }\n\n  render() {\n    return this.compositionController.render()\n  }\n\n  reparse() {\n    return this.composition.replaceHTML(this.editorElement.innerHTML)\n  }\n\n  // Composition delegate\n\n  compositionDidChangeDocument(document) {\n    this.notifyEditorElement(\"document-change\")\n    if (!this.handlingInput) {\n      return this.render()\n    }\n  }\n\n  compositionDidChangeCurrentAttributes(currentAttributes) {\n    this.currentAttributes = currentAttributes\n    this.toolbarController.updateAttributes(this.currentAttributes)\n    this.updateCurrentActions()\n    return this.notifyEditorElement(\"attributes-change\", { attributes: this.currentAttributes })\n  }\n\n  compositionDidPerformInsertionAtRange(range) {\n    if (this.pasting) {\n      this.pastedRange = range\n    }\n  }\n\n  compositionShouldAcceptFile(file) {\n    return this.notifyEditorElement(\"file-accept\", { file })\n  }\n\n  compositionDidAddAttachment(attachment) {\n    const managedAttachment = this.attachmentManager.manageAttachment(attachment)\n    return this.notifyEditorElement(\"attachment-add\", { attachment: managedAttachment })\n  }\n\n  compositionDidEditAttachment(attachment) {\n    this.compositionController.rerenderViewForObject(attachment)\n    const managedAttachment = this.attachmentManager.manageAttachment(attachment)\n    this.notifyEditorElement(\"attachment-edit\", { attachment: managedAttachment })\n    return this.notifyEditorElement(\"change\")\n  }\n\n  compositionDidChangeAttachmentPreviewURL(attachment) {\n    this.compositionController.invalidateViewForObject(attachment)\n    return this.notifyEditorElement(\"change\")\n  }\n\n  compositionDidRemoveAttachment(attachment) {\n    const managedAttachment = this.attachmentManager.unmanageAttachment(attachment)\n    return this.notifyEditorElement(\"attachment-remove\", { attachment: managedAttachment })\n  }\n\n  compositionDidStartEditingAttachment(attachment, options) {\n    this.attachmentLocationRange = this.composition.document.getLocationRangeOfAttachment(attachment)\n    this.compositionController.installAttachmentEditorForAttachment(attachment, options)\n    return this.selectionManager.setLocationRange(this.attachmentLocationRange)\n  }\n\n  compositionDidStopEditingAttachment(attachment) {\n    this.compositionController.uninstallAttachmentEditor()\n    this.attachmentLocationRange = null\n  }\n\n  compositionDidRequestChangingSelectionToLocationRange(locationRange) {\n    if (this.loadingSnapshot && !this.isFocused()) return\n    this.requestedLocationRange = locationRange\n    this.compositionRevisionWhenLocationRangeRequested = this.composition.revision\n    if (!this.handlingInput) {\n      return this.render()\n    }\n  }\n\n  compositionWillLoadSnapshot() {\n    this.loadingSnapshot = true\n  }\n\n  compositionDidLoadSnapshot() {\n    this.compositionController.refreshViewCache()\n    this.render()\n    this.loadingSnapshot = false\n  }\n\n  getSelectionManager() {\n    return this.selectionManager\n  }\n\n  // Attachment manager delegate\n\n  attachmentManagerDidRequestRemovalOfAttachment(attachment) {\n    return this.removeAttachment(attachment)\n  }\n\n  // Document controller delegate\n\n  compositionControllerWillSyncDocumentView() {\n    this.inputController.editorWillSyncDocumentView()\n    this.selectionManager.lock()\n    return this.selectionManager.clearSelection()\n  }\n\n  compositionControllerDidSyncDocumentView() {\n    this.inputController.editorDidSyncDocumentView()\n    this.selectionManager.unlock()\n    this.updateCurrentActions()\n    return this.notifyEditorElement(\"sync\")\n  }\n\n  compositionControllerDidRender() {\n    if (this.requestedLocationRange) {\n      if (this.compositionRevisionWhenLocationRangeRequested === this.composition.revision) {\n        this.selectionManager.setLocationRange(this.requestedLocationRange)\n      }\n      this.requestedLocationRange = null\n      this.compositionRevisionWhenLocationRangeRequested = null\n    }\n\n    if (this.renderedCompositionRevision !== this.composition.revision) {\n      this.runEditorFilters()\n      this.composition.updateCurrentAttributes()\n      this.notifyEditorElement(\"render\")\n    }\n\n    this.renderedCompositionRevision = this.composition.revision\n  }\n\n  compositionControllerDidFocus() {\n    if (this.isFocusedInvisibly()) {\n      this.setLocationRange({ index: 0, offset: 0 })\n    }\n    this.toolbarController.hideDialog()\n    return this.notifyEditorElement(\"focus\")\n  }\n\n  compositionControllerDidBlur() {\n    return this.notifyEditorElement(\"blur\")\n  }\n\n  compositionControllerDidSelectAttachment(attachment, options) {\n    this.toolbarController.hideDialog()\n    return this.composition.editAttachment(attachment, options)\n  }\n\n  compositionControllerDidRequestDeselectingAttachment(attachment) {\n    const locationRange = this.attachmentLocationRange || this.composition.document.getLocationRangeOfAttachment(attachment)\n    return this.selectionManager.setLocationRange(locationRange[1])\n  }\n\n  compositionControllerWillUpdateAttachment(attachment) {\n    return this.editor.recordUndoEntry(\"Edit Attachment\", { context: attachment.id, consolidatable: true })\n  }\n\n  compositionControllerDidRequestRemovalOfAttachment(attachment) {\n    return this.removeAttachment(attachment)\n  }\n\n  // Input controller delegate\n\n  inputControllerWillHandleInput() {\n    this.handlingInput = true\n    this.requestedRender = false\n  }\n\n  inputControllerDidRequestRender() {\n    this.requestedRender = true\n  }\n\n  inputControllerDidHandleInput() {\n    this.handlingInput = false\n    if (this.requestedRender) {\n      this.requestedRender = false\n      return this.render()\n    }\n  }\n\n  inputControllerDidAllowUnhandledInput() {\n    return this.notifyEditorElement(\"change\")\n  }\n\n  inputControllerDidRequestReparse() {\n    return this.reparse()\n  }\n\n  inputControllerWillPerformTyping() {\n    return this.recordTypingUndoEntry()\n  }\n\n  inputControllerWillPerformFormatting(attributeName) {\n    return this.recordFormattingUndoEntry(attributeName)\n  }\n\n  inputControllerWillCutText() {\n    return this.editor.recordUndoEntry(\"Cut\")\n  }\n\n  inputControllerWillPaste(paste) {\n    this.editor.recordUndoEntry(\"Paste\")\n    this.pasting = true\n    return this.notifyEditorElement(\"before-paste\", { paste })\n  }\n\n  inputControllerDidPaste(paste) {\n    paste.range = this.pastedRange\n    this.pastedRange = null\n    this.pasting = null\n    return this.notifyEditorElement(\"paste\", { paste })\n  }\n\n  inputControllerWillMoveText() {\n    return this.editor.recordUndoEntry(\"Move\")\n  }\n\n  inputControllerWillAttachFiles() {\n    return this.editor.recordUndoEntry(\"Drop Files\")\n  }\n\n  inputControllerWillPerformUndo() {\n    return this.editor.undo()\n  }\n\n  inputControllerWillPerformRedo() {\n    return this.editor.redo()\n  }\n\n  inputControllerDidReceiveKeyboardCommand(keys) {\n    return this.toolbarController.applyKeyboardCommand(keys)\n  }\n\n  inputControllerDidStartDrag() {\n    this.locationRangeBeforeDrag = this.selectionManager.getLocationRange()\n  }\n\n  inputControllerDidReceiveDragOverPoint(point) {\n    return this.selectionManager.setLocationRangeFromPointRange(point)\n  }\n\n  inputControllerDidCancelDrag() {\n    this.selectionManager.setLocationRange(this.locationRangeBeforeDrag)\n    this.locationRangeBeforeDrag = null\n  }\n\n  // Selection manager delegate\n\n  locationRangeDidChange(locationRange) {\n    this.composition.updateCurrentAttributes()\n    this.updateCurrentActions()\n    if (this.attachmentLocationRange && !rangesAreEqual(this.attachmentLocationRange, locationRange)) {\n      this.composition.stopEditingAttachment()\n    }\n    return this.notifyEditorElement(\"selection-change\")\n  }\n\n  // Toolbar controller delegate\n\n  toolbarDidClickButton() {\n    if (!this.getLocationRange()) {\n      return this.setLocationRange({ index: 0, offset: 0 })\n    }\n  }\n\n  toolbarDidInvokeAction(actionName, invokingElement) {\n    return this.invokeAction(actionName, invokingElement)\n  }\n\n  toolbarDidToggleAttribute(attributeName) {\n    this.recordFormattingUndoEntry(attributeName)\n    this.composition.toggleCurrentAttribute(attributeName)\n    this.render()\n    if (!this.selectionFrozen) {\n      return this.editorElement.focus()\n    }\n  }\n\n  toolbarDidUpdateAttribute(attributeName, value) {\n    this.recordFormattingUndoEntry(attributeName)\n    this.composition.setCurrentAttribute(attributeName, value)\n    this.render()\n    if (!this.selectionFrozen) {\n      return this.editorElement.focus()\n    }\n  }\n\n  toolbarDidRemoveAttribute(attributeName) {\n    this.recordFormattingUndoEntry(attributeName)\n    this.composition.removeCurrentAttribute(attributeName)\n    this.render()\n    if (!this.selectionFrozen) {\n      return this.editorElement.focus()\n    }\n  }\n\n  toolbarWillShowDialog(dialogElement) {\n    this.composition.expandSelectionForEditing()\n    return this.freezeSelection()\n  }\n\n  toolbarDidShowDialog(dialogName) {\n    return this.notifyEditorElement(\"toolbar-dialog-show\", { dialogName })\n  }\n\n  toolbarDidHideDialog(dialogName) {\n    this.thawSelection()\n    this.editorElement.focus()\n    return this.notifyEditorElement(\"toolbar-dialog-hide\", { dialogName })\n  }\n\n  // Selection\n\n  freezeSelection() {\n    if (!this.selectionFrozen) {\n      this.selectionManager.lock()\n      this.composition.freezeSelection()\n      this.selectionFrozen = true\n      return this.render()\n    }\n  }\n\n  thawSelection() {\n    if (this.selectionFrozen) {\n      this.composition.thawSelection()\n      this.selectionManager.unlock()\n      this.selectionFrozen = false\n      return this.render()\n    }\n  }\n\n  canInvokeAction(actionName) {\n    if (this.actionIsExternal(actionName)) {\n      return true\n    } else {\n      return !!this.actions[actionName]?.test?.call(this)\n    }\n  }\n\n  invokeAction(actionName, invokingElement) {\n    if (this.actionIsExternal(actionName)) {\n      return this.notifyEditorElement(\"action-invoke\", { actionName, invokingElement })\n    } else {\n      return this.actions[actionName]?.perform?.call(this)\n    }\n  }\n\n  actionIsExternal(actionName) {\n    return /^x-./.test(actionName)\n  }\n\n  getCurrentActions() {\n    const result = {}\n    for (const actionName in this.actions) {\n      result[actionName] = this.canInvokeAction(actionName)\n    }\n    return result\n  }\n\n  updateCurrentActions() {\n    const currentActions = this.getCurrentActions()\n    if (!objectsAreEqual(currentActions, this.currentActions)) {\n      this.currentActions = currentActions\n      this.toolbarController.updateActions(this.currentActions)\n      return this.notifyEditorElement(\"actions-change\", { actions: this.currentActions })\n    }\n  }\n\n  // Editor filters\n\n  runEditorFilters() {\n    let snapshot = this.composition.getSnapshot()\n\n    Array.from(this.editor.filters).forEach((filter) => {\n      const { document, selectedRange } = snapshot\n      snapshot = filter.call(this.editor, snapshot) || {}\n      if (!snapshot.document) {\n        snapshot.document = document\n      }\n      if (!snapshot.selectedRange) {\n        snapshot.selectedRange = selectedRange\n      }\n    })\n\n    if (!snapshotsAreEqual(snapshot, this.composition.getSnapshot())) {\n      return this.composition.loadSnapshot(snapshot)\n    }\n  }\n\n  // Private\n\n  updateInputElement() {\n    const element = this.compositionController.getSerializableElement()\n    const value = serializeToContentType(element, \"text/html\")\n    return this.editorElement.setFormValue(value)\n  }\n\n  notifyEditorElement(message, data) {\n    switch (message) {\n      case \"document-change\":\n        this.documentChangedSinceLastRender = true\n        break\n      case \"render\":\n        if (this.documentChangedSinceLastRender) {\n          this.documentChangedSinceLastRender = false\n          this.notifyEditorElement(\"change\")\n        }\n        break\n      case \"change\":\n      case \"attachment-add\":\n      case \"attachment-edit\":\n      case \"attachment-remove\":\n        this.updateInputElement()\n        break\n    }\n\n    return this.editorElement.notify(message, data)\n  }\n\n  removeAttachment(attachment) {\n    this.editor.recordUndoEntry(\"Delete Attachment\")\n    this.composition.removeAttachment(attachment)\n    return this.render()\n  }\n\n  recordFormattingUndoEntry(attributeName) {\n    const blockConfig = getBlockConfig(attributeName)\n    const locationRange = this.selectionManager.getLocationRange()\n    if (blockConfig || !rangeIsCollapsed(locationRange)) {\n      return this.editor.recordUndoEntry(\"Formatting\", { context: this.getUndoContext(), consolidatable: true })\n    }\n  }\n\n  recordTypingUndoEntry() {\n    return this.editor.recordUndoEntry(\"Typing\", {\n      context: this.getUndoContext(this.currentAttributes),\n      consolidatable: true,\n    })\n  }\n\n  getUndoContext(...context) {\n    return [ this.getLocationContext(), this.getTimeContext(), ...Array.from(context) ]\n  }\n\n  getLocationContext() {\n    const locationRange = this.selectionManager.getLocationRange()\n    if (rangeIsCollapsed(locationRange)) {\n      return locationRange[0].index\n    } else {\n      return locationRange\n    }\n  }\n\n  getTimeContext() {\n    if (config.undo.interval > 0) {\n      return Math.floor(new Date().getTime() / config.undo.interval)\n    } else {\n      return 0\n    }\n  }\n\n  isFocused() {\n    return this.editorElement === this.editorElement.ownerDocument?.activeElement\n  }\n\n  // Detect \"Cursor disappears sporadically\" Firefox bug.\n  // - https://bugzilla.mozilla.org/show_bug.cgi?id=226301\n  isFocusedInvisibly() {\n    return this.isFocused() && !this.getLocationRange()\n  }\n\n  get actions() {\n    return this.constructor.actions\n  }\n}\n\nEditorController.proxyMethod(\"getSelectionManager().setLocationRange\")\nEditorController.proxyMethod(\"getSelectionManager().getLocationRange\")\n"
  },
  {
    "path": "src/trix/controllers/index.js",
    "content": "export { default as AttachmentEditorController } from \"./attachment_editor_controller\"\nexport { default as CompositionController } from \"./composition_controller\"\nexport { default as Controller } from \"./controller\"\nexport { default as EditorController } from \"./editor_controller\"\nexport { default as InputController } from \"./input_controller\"\nexport { default as Level0InputController } from \"./level_0_input_controller\"\nexport { default as Level2InputController } from \"./level_2_input_controller\"\nexport { default as ToolbarController } from \"./toolbar_controller\"\n"
  },
  {
    "path": "src/trix/controllers/input_controller.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\nimport MutationObserver from \"trix/observers/mutation_observer\"\nimport FileVerificationOperation from \"trix/operations/file_verification_operation\"\nimport FlakyAndroidKeyboardDetector from \"../models/flaky_android_keyboard_detector\"\n\nimport { handleEvent, innerElementIsActive } from \"trix/core/helpers\"\n\nexport default class InputController extends BasicObject {\n\n  static events = {}\n\n  constructor(element) {\n    super(...arguments)\n    this.element = element\n    this.mutationObserver = new MutationObserver(this.element)\n    this.mutationObserver.delegate = this\n    this.flakyKeyboardDetector = new FlakyAndroidKeyboardDetector(this.element)\n    for (const eventName in this.constructor.events) {\n      handleEvent(eventName, { onElement: this.element, withCallback: this.handlerFor(eventName) })\n    }\n  }\n\n  elementDidMutate(mutationSummary) {}\n\n  editorWillSyncDocumentView() {\n    return this.mutationObserver.stop()\n  }\n\n  editorDidSyncDocumentView() {\n    return this.mutationObserver.start()\n  }\n\n  requestRender() {\n    return this.delegate?.inputControllerDidRequestRender?.()\n  }\n\n  requestReparse() {\n    this.delegate?.inputControllerDidRequestReparse?.()\n    return this.requestRender()\n  }\n\n  attachFiles(files) {\n    const operations = Array.from(files).map((file) => new FileVerificationOperation(file))\n    return Promise.all(operations).then((files) => {\n      this.handleInput(function() {\n        this.delegate?.inputControllerWillAttachFiles()\n        this.responder?.insertFiles(files)\n        return this.requestRender()\n      })\n    })\n  }\n\n  // Private\n\n  handlerFor(eventName) {\n    return (event) => {\n      if (!event.defaultPrevented) {\n        this.handleInput(() => {\n          if (!innerElementIsActive(this.element)) {\n            if (this.flakyKeyboardDetector.shouldIgnore(event)) return\n\n            this.eventName = eventName\n            this.constructor.events[eventName].call(this, event)\n          }\n        })\n      }\n    }\n  }\n\n  handleInput(callback) {\n    try {\n      this.delegate?.inputControllerWillHandleInput()\n      callback.call(this)\n    } finally {\n      this.delegate?.inputControllerDidHandleInput()\n    }\n  }\n\n  createLinkHTML(href, text) {\n    const link = document.createElement(\"a\")\n    link.href = href\n    link.textContent = text ? text : href\n    return link.outerHTML\n  }\n}\n\n"
  },
  {
    "path": "src/trix/controllers/level_0_input_controller.js",
    "content": "import * as config from \"trix/config\"\nimport UTF16String from \"trix/core/utilities/utf16_string\"\nimport BasicObject from \"trix/core/basic_object\"\nimport InputController from \"trix/controllers/input_controller\"\nimport DocumentView from \"trix/views/document_view\"\nimport Document from \"trix/models/document\"\n\nimport {\n  dataTransferIsPlainText,\n  dataTransferIsWritable,\n  keyEventIsKeyboardCommand,\n  makeElement,\n  objectsAreEqual,\n  removeNode,\n  squishBreakableWhitespace,\n} from \"trix/core/helpers\"\n\nimport { selectionChangeObserver } from \"trix/observers/selection_change_observer\"\n\nconst { browser, keyNames } = config\nlet pastedFileCount = 0\n\nexport default class Level0InputController extends InputController {\n\n  static events = {\n    keydown(event) {\n      if (!this.isComposing()) {\n        this.resetInputSummary()\n      }\n      this.inputSummary.didInput = true\n\n      const keyName = keyNames[event.keyCode]\n      if (keyName) {\n        let context = this.keys\n\n        ;[ \"ctrl\", \"alt\", \"shift\", \"meta\" ].forEach((modifier) => {\n          if (event[`${modifier}Key`]) {\n            if (modifier === \"ctrl\") {\n              modifier = \"control\"\n            }\n            context = context?.[modifier]\n          }\n        })\n\n        if (context?.[keyName] != null) {\n          this.setInputSummary({ keyName })\n          selectionChangeObserver.reset()\n          context[keyName].call(this, event)\n        }\n      }\n\n      if (keyEventIsKeyboardCommand(event)) {\n        const character = String.fromCharCode(event.keyCode).toLowerCase()\n        if (character) {\n          const keys = [ \"alt\", \"shift\" ].map((modifier) => {\n            if (event[`${modifier}Key`]) {\n              return modifier\n            }\n          }).filter(key => key)\n          keys.push(character)\n          if (this.delegate?.inputControllerDidReceiveKeyboardCommand(keys)) {\n            event.preventDefault()\n          }\n        }\n      }\n    },\n\n    keypress(event) {\n      if (this.inputSummary.eventName != null) return\n      if (event.metaKey) return\n      if (event.ctrlKey && !event.altKey) return\n\n      const string = stringFromKeyEvent(event)\n      if (string) {\n        this.delegate?.inputControllerWillPerformTyping()\n        this.responder?.insertString(string)\n        return this.setInputSummary({ textAdded: string, didDelete: this.selectionIsExpanded() })\n      }\n    },\n\n    textInput(event) {\n      // Handle autocapitalization\n      const { data } = event\n      const { textAdded } = this.inputSummary\n      if (textAdded && textAdded !== data && textAdded.toUpperCase() === data) {\n        const range = this.getSelectedRange()\n        this.setSelectedRange([ range[0], range[1] + textAdded.length ])\n        this.responder?.insertString(data)\n        this.setInputSummary({ textAdded: data })\n        return this.setSelectedRange(range)\n      }\n    },\n\n    dragenter(event) {\n      event.preventDefault()\n    },\n\n    dragstart(event) {\n      this.serializeSelectionToDataTransfer(event.dataTransfer)\n      this.draggedRange = this.getSelectedRange()\n      return this.delegate?.inputControllerDidStartDrag?.()\n    },\n\n    dragover(event) {\n      if (this.draggedRange || this.canAcceptDataTransfer(event.dataTransfer)) {\n        event.preventDefault()\n        const draggingPoint = { x: event.clientX, y: event.clientY }\n        if (!objectsAreEqual(draggingPoint, this.draggingPoint)) {\n          this.draggingPoint = draggingPoint\n          return this.delegate?.inputControllerDidReceiveDragOverPoint?.(this.draggingPoint)\n        }\n      }\n    },\n\n    dragend(event) {\n      this.delegate?.inputControllerDidCancelDrag?.()\n      this.draggedRange = null\n      this.draggingPoint = null\n    },\n\n    drop(event) {\n      event.preventDefault()\n      const files = event.dataTransfer?.files\n      const documentJSON = event.dataTransfer.getData(\"application/x-trix-document\")\n\n      const point = { x: event.clientX, y: event.clientY }\n      this.responder?.setLocationRangeFromPointRange(point)\n\n      if (files?.length) {\n        this.attachFiles(files)\n      } else if (this.draggedRange) {\n        this.delegate?.inputControllerWillMoveText()\n        this.responder?.moveTextFromRange(this.draggedRange)\n        this.draggedRange = null\n        this.requestRender()\n      } else if (documentJSON) {\n        const document = Document.fromJSONString(documentJSON)\n        this.responder?.insertDocument(document)\n        this.requestRender()\n      }\n\n      this.draggedRange = null\n      this.draggingPoint = null\n    },\n\n    cut(event) {\n      if (this.responder?.selectionIsExpanded()) {\n        if (this.serializeSelectionToDataTransfer(event.clipboardData)) {\n          event.preventDefault()\n        }\n\n        this.delegate?.inputControllerWillCutText()\n        this.deleteInDirection(\"backward\")\n        if (event.defaultPrevented) {\n          return this.requestRender()\n        }\n      }\n    },\n\n    copy(event) {\n      if (this.responder?.selectionIsExpanded()) {\n        if (this.serializeSelectionToDataTransfer(event.clipboardData)) {\n          event.preventDefault()\n        }\n      }\n    },\n\n    paste(event) {\n      const clipboard = event.clipboardData || event.testClipboardData\n      const paste = { clipboard }\n\n      if (!clipboard || pasteEventIsCrippledSafariHTMLPaste(event)) {\n        this.getPastedHTMLUsingHiddenElement((html) => {\n          paste.type = \"text/html\"\n          paste.html = html\n          this.delegate?.inputControllerWillPaste(paste)\n          this.responder?.insertHTML(paste.html)\n          this.requestRender()\n          return this.delegate?.inputControllerDidPaste(paste)\n        })\n        return\n      }\n\n      const href = clipboard.getData(\"URL\")\n      const html = clipboard.getData(\"text/html\")\n      const name = clipboard.getData(\"public.url-name\")\n\n      if (href) {\n        let string\n        paste.type = \"text/html\"\n        if (name) {\n          string = squishBreakableWhitespace(name).trim()\n        } else {\n          string = href\n        }\n        paste.html = this.createLinkHTML(href, string)\n        this.delegate?.inputControllerWillPaste(paste)\n        this.setInputSummary({ textAdded: string, didDelete: this.selectionIsExpanded() })\n        this.responder?.insertHTML(paste.html)\n        this.requestRender()\n        this.delegate?.inputControllerDidPaste(paste)\n      } else if (dataTransferIsPlainText(clipboard)) {\n        paste.type = \"text/plain\"\n        paste.string = clipboard.getData(\"text/plain\")\n        this.delegate?.inputControllerWillPaste(paste)\n        this.setInputSummary({ textAdded: paste.string, didDelete: this.selectionIsExpanded() })\n        this.responder?.insertString(paste.string)\n        this.requestRender()\n        this.delegate?.inputControllerDidPaste(paste)\n      } else if (html) {\n        paste.type = \"text/html\"\n        paste.html = html\n        this.delegate?.inputControllerWillPaste(paste)\n        this.responder?.insertHTML(paste.html)\n        this.requestRender()\n        this.delegate?.inputControllerDidPaste(paste)\n      } else if (Array.from(clipboard.types).includes(\"Files\")) {\n        const file = clipboard.items?.[0]?.getAsFile?.()\n        if (file) {\n          const extension = extensionForFile(file)\n          if (!file.name && extension) {\n            file.name = `pasted-file-${++pastedFileCount}.${extension}`\n          }\n          paste.type = \"File\"\n          paste.file = file\n          this.delegate?.inputControllerWillAttachFiles()\n          this.responder?.insertFile(paste.file)\n          this.requestRender()\n          this.delegate?.inputControllerDidPaste(paste)\n        }\n      }\n\n      event.preventDefault()\n    },\n\n    compositionstart(event) {\n      return this.getCompositionInput().start(event.data)\n    },\n\n    compositionupdate(event) {\n      return this.getCompositionInput().update(event.data)\n    },\n\n    compositionend(event) {\n      return this.getCompositionInput().end(event.data)\n    },\n\n    beforeinput(event) {\n      this.inputSummary.didInput = true\n    },\n\n    input(event) {\n      this.inputSummary.didInput = true\n      return event.stopPropagation()\n    },\n  }\n\n  static keys = {\n    backspace(event) {\n      this.delegate?.inputControllerWillPerformTyping()\n      return this.deleteInDirection(\"backward\", event)\n    },\n\n    delete(event) {\n      this.delegate?.inputControllerWillPerformTyping()\n      return this.deleteInDirection(\"forward\", event)\n    },\n\n    return(event) {\n      this.setInputSummary({ preferDocument: true })\n      this.delegate?.inputControllerWillPerformTyping()\n      return this.responder?.insertLineBreak()\n    },\n\n    tab(event) {\n      if (this.responder?.canIncreaseNestingLevel()) {\n        this.responder?.increaseNestingLevel()\n        this.requestRender()\n        event.preventDefault()\n      }\n    },\n\n    left(event) {\n      if (this.selectionIsInCursorTarget()) {\n        event.preventDefault()\n        return this.responder?.moveCursorInDirection(\"backward\")\n      }\n    },\n\n    right(event) {\n      if (this.selectionIsInCursorTarget()) {\n        event.preventDefault()\n        return this.responder?.moveCursorInDirection(\"forward\")\n      }\n    },\n\n    control: {\n      d(event) {\n        this.delegate?.inputControllerWillPerformTyping()\n        return this.deleteInDirection(\"forward\", event)\n      },\n\n      h(event) {\n        this.delegate?.inputControllerWillPerformTyping()\n        return this.deleteInDirection(\"backward\", event)\n      },\n\n      o(event) {\n        event.preventDefault()\n        this.delegate?.inputControllerWillPerformTyping()\n        this.responder?.insertString(\"\\n\", { updatePosition: false })\n        return this.requestRender()\n      },\n    },\n\n    shift: {\n      return(event) {\n        this.delegate?.inputControllerWillPerformTyping()\n        this.responder?.insertString(\"\\n\")\n        this.requestRender()\n        event.preventDefault()\n      },\n\n      tab(event) {\n        if (this.responder?.canDecreaseNestingLevel()) {\n          this.responder?.decreaseNestingLevel()\n          this.requestRender()\n          event.preventDefault()\n        }\n      },\n\n      left(event) {\n        if (this.selectionIsInCursorTarget()) {\n          event.preventDefault()\n          return this.expandSelectionInDirection(\"backward\")\n        }\n      },\n\n      right(event) {\n        if (this.selectionIsInCursorTarget()) {\n          event.preventDefault()\n          return this.expandSelectionInDirection(\"forward\")\n        }\n      },\n    },\n\n    alt: {\n      backspace(event) {\n        this.setInputSummary({ preferDocument: false })\n        return this.delegate?.inputControllerWillPerformTyping()\n      },\n    },\n\n    meta: {\n      backspace(event) {\n        this.setInputSummary({ preferDocument: false })\n        return this.delegate?.inputControllerWillPerformTyping()\n      },\n    },\n  }\n\n  constructor() {\n    super(...arguments)\n    this.resetInputSummary()\n  }\n\n  setInputSummary(summary = {}) {\n    this.inputSummary.eventName = this.eventName\n    for (const key in summary) {\n      const value = summary[key]\n      this.inputSummary[key] = value\n    }\n    return this.inputSummary\n  }\n\n  resetInputSummary() {\n    this.inputSummary = {}\n  }\n\n  reset() {\n    this.resetInputSummary()\n    return selectionChangeObserver.reset()\n  }\n\n  // Mutation observer delegate\n\n  elementDidMutate(mutationSummary) {\n    if (this.isComposing()) {\n      return this.delegate?.inputControllerDidAllowUnhandledInput?.()\n    } else {\n      return this.handleInput(function() {\n        if (this.mutationIsSignificant(mutationSummary)) {\n          if (this.mutationIsExpected(mutationSummary)) {\n            this.requestRender()\n          } else {\n            this.requestReparse()\n          }\n        }\n        return this.reset()\n      })\n    }\n  }\n\n  mutationIsExpected({ textAdded, textDeleted }) {\n    if (this.inputSummary.preferDocument) {\n      return true\n    }\n\n    const mutationAdditionMatchesSummary =\n      textAdded != null ? textAdded === this.inputSummary.textAdded : !this.inputSummary.textAdded\n    const mutationDeletionMatchesSummary =\n      textDeleted != null ? this.inputSummary.didDelete : !this.inputSummary.didDelete\n\n    const unexpectedNewlineAddition = [ \"\\n\", \" \\n\" ].includes(textAdded) && !mutationAdditionMatchesSummary\n    const unexpectedNewlineDeletion = textDeleted === \"\\n\" && !mutationDeletionMatchesSummary\n    const singleUnexpectedNewline =\n      unexpectedNewlineAddition && !unexpectedNewlineDeletion ||\n      unexpectedNewlineDeletion && !unexpectedNewlineAddition\n\n    if (singleUnexpectedNewline) {\n      const range = this.getSelectedRange()\n      if (range) {\n        const offset = unexpectedNewlineAddition ? textAdded.replace(/\\n$/, \"\").length || -1 : textAdded?.length || 1\n        if (this.responder?.positionIsBlockBreak(range[1] + offset)) {\n          return true\n        }\n      }\n    }\n\n    return mutationAdditionMatchesSummary && mutationDeletionMatchesSummary\n  }\n\n  mutationIsSignificant(mutationSummary) {\n    const textChanged = Object.keys(mutationSummary).length > 0\n    const composedEmptyString = this.compositionInput?.getEndData() === \"\"\n    return textChanged || !composedEmptyString\n  }\n\n  // Private\n\n  getCompositionInput() {\n    if (this.isComposing()) {\n      return this.compositionInput\n    } else {\n      this.compositionInput = new CompositionInput(this)\n    }\n  }\n\n  isComposing() {\n    return this.compositionInput && !this.compositionInput.isEnded()\n  }\n\n  deleteInDirection(direction, event) {\n    if (this.responder?.deleteInDirection(direction) === false) {\n      if (event) {\n        event.preventDefault()\n        return this.requestRender()\n      }\n    } else {\n      return this.setInputSummary({ didDelete: true })\n    }\n  }\n\n  serializeSelectionToDataTransfer(dataTransfer) {\n    if (!dataTransferIsWritable(dataTransfer)) return\n    const document = this.responder?.getSelectedDocument().toSerializableDocument()\n\n    dataTransfer.setData(\"application/x-trix-document\", JSON.stringify(document))\n    dataTransfer.setData(\"text/html\", DocumentView.render(document).innerHTML)\n    dataTransfer.setData(\"text/plain\", document.toString().replace(/\\n$/, \"\"))\n    return true\n  }\n\n  canAcceptDataTransfer(dataTransfer) {\n    const types = {}\n    Array.from(dataTransfer?.types || []).forEach((type) => {\n      types[type] = true\n    })\n    return types.Files || types[\"application/x-trix-document\"] || types[\"text/html\"] || types[\"text/plain\"]\n  }\n\n  getPastedHTMLUsingHiddenElement(callback) {\n    const selectedRange = this.getSelectedRange()\n\n    const style = {\n      position: \"absolute\",\n      left: `${window.pageXOffset}px`,\n      top: `${window.pageYOffset}px`,\n      opacity: 0,\n    }\n\n    const element = makeElement({ style, tagName: \"div\", editable: true })\n    document.body.appendChild(element)\n    element.focus()\n\n    return requestAnimationFrame(() => {\n      const html = element.innerHTML\n      removeNode(element)\n      this.setSelectedRange(selectedRange)\n      return callback(html)\n    })\n  }\n}\n\nLevel0InputController.proxyMethod(\"responder?.getSelectedRange\")\nLevel0InputController.proxyMethod(\"responder?.setSelectedRange\")\nLevel0InputController.proxyMethod(\"responder?.expandSelectionInDirection\")\nLevel0InputController.proxyMethod(\"responder?.selectionIsInCursorTarget\")\nLevel0InputController.proxyMethod(\"responder?.selectionIsExpanded\")\n\nconst extensionForFile = (file) => file.type?.match(/\\/(\\w+)$/)?.[1]\n\nconst hasStringCodePointAt = !!\" \".codePointAt?.(0)\n\nconst stringFromKeyEvent = function(event) {\n  if (event.key && hasStringCodePointAt && event.key.codePointAt(0) === event.keyCode) {\n    return event.key\n  } else {\n    let code\n    if (event.which === null) {\n      code = event.keyCode\n    } else if (event.which !== 0 && event.charCode !== 0) {\n      code = event.charCode\n    }\n\n    if (code != null && keyNames[code] !== \"escape\") {\n      return UTF16String.fromCodepoints([ code ]).toString()\n    }\n  }\n}\n\nconst pasteEventIsCrippledSafariHTMLPaste = function(event) {\n  const paste = event.clipboardData\n  if (paste) {\n    if (paste.types.includes(\"text/html\")) {\n      // Answer is yes if there's any possibility of Paste and Match Style in Safari,\n      // which is nearly impossible to detect confidently: https://bugs.webkit.org/show_bug.cgi?id=174165\n      for (const type of paste.types) {\n        const hasPasteboardFlavor = /^CorePasteboardFlavorType/.test(type)\n        const hasReadableDynamicData = /^dyn\\./.test(type) && paste.getData(type)\n        const mightBePasteAndMatchStyle = hasPasteboardFlavor || hasReadableDynamicData\n        if (mightBePasteAndMatchStyle) {\n          return true\n        }\n      }\n      return false\n    } else {\n      const isExternalHTMLPaste = paste.types.includes(\"com.apple.webarchive\")\n      const isExternalRichTextPaste = paste.types.includes(\"com.apple.flat-rtfd\")\n      return isExternalHTMLPaste || isExternalRichTextPaste\n    }\n  }\n}\n\nclass CompositionInput extends BasicObject {\n  constructor(inputController) {\n    super(...arguments)\n    this.inputController = inputController\n    this.responder = this.inputController.responder\n    this.delegate = this.inputController.delegate\n    this.inputSummary = this.inputController.inputSummary\n    this.data = {}\n  }\n\n  start(data) {\n    this.data.start = data\n\n    if (this.isSignificant()) {\n      if (this.inputSummary.eventName === \"keypress\" && this.inputSummary.textAdded) {\n        this.responder?.deleteInDirection(\"left\")\n      }\n\n      if (!this.selectionIsExpanded()) {\n        this.insertPlaceholder()\n        this.requestRender()\n      }\n\n      this.range = this.responder?.getSelectedRange()\n    }\n  }\n\n  update(data) {\n    this.data.update = data\n\n    if (this.isSignificant()) {\n      const range = this.selectPlaceholder()\n      if (range) {\n        this.forgetPlaceholder()\n        this.range = range\n      }\n    }\n  }\n\n  end(data) {\n    this.data.end = data\n\n    if (this.isSignificant()) {\n      this.forgetPlaceholder()\n\n      if (this.canApplyToDocument()) {\n        this.setInputSummary({ preferDocument: true, didInput: false })\n        this.delegate?.inputControllerWillPerformTyping()\n        this.responder?.setSelectedRange(this.range)\n        this.responder?.insertString(this.data.end)\n        return this.responder?.setSelectedRange(this.range[0] + this.data.end.length)\n      } else if (this.data.start != null || this.data.update != null) {\n        this.requestReparse()\n        return this.inputController.reset()\n      }\n    } else {\n      return this.inputController.reset()\n    }\n  }\n\n  getEndData() {\n    return this.data.end\n  }\n\n  isEnded() {\n    return this.getEndData() != null\n  }\n\n  isSignificant() {\n    if (browser.composesExistingText) {\n      return this.inputSummary.didInput\n    } else {\n      return true\n    }\n  }\n\n  // Private\n\n  canApplyToDocument() {\n    return this.data.start?.length === 0 && this.data.end?.length > 0 && this.range\n  }\n}\n\nCompositionInput.proxyMethod(\"inputController.setInputSummary\")\nCompositionInput.proxyMethod(\"inputController.requestRender\")\nCompositionInput.proxyMethod(\"inputController.requestReparse\")\nCompositionInput.proxyMethod(\"responder?.selectionIsExpanded\")\nCompositionInput.proxyMethod(\"responder?.insertPlaceholder\")\nCompositionInput.proxyMethod(\"responder?.selectPlaceholder\")\nCompositionInput.proxyMethod(\"responder?.forgetPlaceholder\")\n"
  },
  {
    "path": "src/trix/controllers/level_2_input_controller.js",
    "content": "import { getAllAttributeNames, shouldRenderInmmediatelyToDealWithIOSDictation, squishBreakableWhitespace } from \"trix/core/helpers\"\nimport InputController from \"trix/controllers/input_controller\"\nimport * as config from \"trix/config\"\n\nimport { dataTransferIsMsOfficePaste, dataTransferIsPlainText, keyEventIsKeyboardCommand, objectsAreEqual } from \"trix/core/helpers\"\n\nimport { selectionChangeObserver } from \"trix/observers/selection_change_observer\"\n\nexport default class Level2InputController extends InputController {\n  constructor(...args) {\n    super(...args)\n    this.render = this.render.bind(this)\n  }\n\n  static events = {\n    keydown(event) {\n      if (keyEventIsKeyboardCommand(event)) {\n        const command = keyboardCommandFromKeyEvent(event)\n        if (this.delegate?.inputControllerDidReceiveKeyboardCommand(command)) {\n          event.preventDefault()\n        }\n      } else {\n        let name = event.key\n        if (event.altKey) {\n          name += \"+Alt\"\n        }\n        if (event.shiftKey) {\n          name += \"+Shift\"\n        }\n        const handler = this.constructor.keys[name]\n        if (handler) {\n          return this.withEvent(event, handler)\n        }\n      }\n    },\n\n    // Handle paste event to work around beforeinput.insertFromPaste browser bugs.\n    // Safe to remove each condition once fixed upstream.\n    paste(event) {\n      // https://bugs.webkit.org/show_bug.cgi?id=194921\n      let paste\n      const href = event.clipboardData?.getData(\"URL\")\n      if (pasteEventHasFilesOnly(event)) {\n        event.preventDefault()\n        return this.attachFiles(event.clipboardData.files)\n\n        // https://bugs.chromium.org/p/chromium/issues/detail?id=934448\n      } else if (pasteEventHasPlainTextOnly(event)) {\n        event.preventDefault()\n        paste = {\n          type: \"text/plain\",\n          string: event.clipboardData.getData(\"text/plain\"),\n        }\n        this.delegate?.inputControllerWillPaste(paste)\n        this.responder?.insertString(paste.string)\n        this.render()\n        return this.delegate?.inputControllerDidPaste(paste)\n\n        // https://bugs.webkit.org/show_bug.cgi?id=196702\n      } else if (href) {\n        event.preventDefault()\n        paste = {\n          type: \"text/html\",\n          html: this.createLinkHTML(href),\n        }\n        this.delegate?.inputControllerWillPaste(paste)\n        this.responder?.insertHTML(paste.html)\n        this.render()\n        return this.delegate?.inputControllerDidPaste(paste)\n      }\n    },\n\n    beforeinput(event) {\n      const handler = this.constructor.inputTypes[event.inputType]\n\n      const immmediateRender = shouldRenderInmmediatelyToDealWithIOSDictation(event)\n\n      if (handler) {\n        this.withEvent(event, handler)\n\n        if (!immmediateRender) {\n          this.scheduleRender()\n        }\n      }\n\n      if (immmediateRender) {\n        this.render()\n      }\n    },\n\n    input(event) {\n      selectionChangeObserver.reset()\n    },\n\n    dragstart(event) {\n      if (this.responder?.selectionContainsAttachments()) {\n        event.dataTransfer.setData(\"application/x-trix-dragging\", true)\n\n        this.dragging = {\n          range: this.responder?.getSelectedRange(),\n          point: pointFromEvent(event),\n        }\n      }\n    },\n\n    dragenter(event) {\n      if (dragEventHasFiles(event)) {\n        event.preventDefault()\n      }\n    },\n\n    dragover(event) {\n      if (this.dragging) {\n        event.preventDefault()\n        const point = pointFromEvent(event)\n        if (!objectsAreEqual(point, this.dragging.point)) {\n          this.dragging.point = point\n          return this.responder?.setLocationRangeFromPointRange(point)\n        }\n      } else if (dragEventHasFiles(event)) {\n        event.preventDefault()\n      }\n    },\n\n    drop(event) {\n      if (this.dragging) {\n        event.preventDefault()\n        this.delegate?.inputControllerWillMoveText()\n        this.responder?.moveTextFromRange(this.dragging.range)\n        this.dragging = null\n        return this.scheduleRender()\n      } else if (dragEventHasFiles(event)) {\n        event.preventDefault()\n        const point = pointFromEvent(event)\n        this.responder?.setLocationRangeFromPointRange(point)\n        return this.attachFiles(event.dataTransfer.files)\n      }\n    },\n\n    dragend() {\n      if (this.dragging) {\n        this.responder?.setSelectedRange(this.dragging.range)\n        this.dragging = null\n      }\n    },\n\n    compositionend(event) {\n      if (this.composing) {\n        this.composing = false\n        if (!config.browser.recentAndroid) this.scheduleRender()\n      }\n    },\n  }\n\n  static keys = {\n    ArrowLeft() {\n      if (this.responder?.shouldManageMovingCursorInDirection(\"backward\")) {\n        this.event.preventDefault()\n        return this.responder?.moveCursorInDirection(\"backward\")\n      }\n    },\n\n    ArrowRight() {\n      if (this.responder?.shouldManageMovingCursorInDirection(\"forward\")) {\n        this.event.preventDefault()\n        return this.responder?.moveCursorInDirection(\"forward\")\n      }\n    },\n\n    Backspace() {\n      if (this.responder?.shouldManageDeletingInDirection(\"backward\")) {\n        this.event.preventDefault()\n        this.delegate?.inputControllerWillPerformTyping()\n        this.responder?.deleteInDirection(\"backward\")\n        return this.render()\n      }\n    },\n\n    Tab() {\n      if (this.responder?.canIncreaseNestingLevel()) {\n        this.event.preventDefault()\n        this.responder?.increaseNestingLevel()\n        return this.render()\n      }\n    },\n\n    \"Tab+Shift\"() {\n      if (this.responder?.canDecreaseNestingLevel()) {\n        this.event.preventDefault()\n        this.responder?.decreaseNestingLevel()\n        return this.render()\n      }\n    },\n  }\n\n  static inputTypes = {\n    deleteByComposition() {\n      return this.deleteInDirection(\"backward\", { recordUndoEntry: false })\n    },\n\n    deleteByCut() {\n      return this.deleteInDirection(\"backward\")\n    },\n\n    deleteByDrag() {\n      this.event.preventDefault()\n      return this.withTargetDOMRange(function() {\n        this.deleteByDragRange = this.responder?.getSelectedRange()\n      })\n    },\n\n    deleteCompositionText() {\n      return this.deleteInDirection(\"backward\", { recordUndoEntry: false })\n    },\n\n    deleteContent() {\n      return this.deleteInDirection(\"backward\")\n    },\n\n    deleteContentBackward() {\n      return this.deleteInDirection(\"backward\")\n    },\n\n    deleteContentForward() {\n      return this.deleteInDirection(\"forward\")\n    },\n\n    deleteEntireSoftLine() {\n      return this.deleteInDirection(\"forward\")\n    },\n\n    deleteHardLineBackward() {\n      return this.deleteInDirection(\"backward\")\n    },\n\n    deleteHardLineForward() {\n      return this.deleteInDirection(\"forward\")\n    },\n\n    deleteSoftLineBackward() {\n      return this.deleteInDirection(\"backward\")\n    },\n\n    deleteSoftLineForward() {\n      return this.deleteInDirection(\"forward\")\n    },\n\n    deleteWordBackward() {\n      return this.deleteInDirection(\"backward\")\n    },\n\n    deleteWordForward() {\n      return this.deleteInDirection(\"forward\")\n    },\n\n    formatBackColor() {\n      return this.activateAttributeIfSupported(\"backgroundColor\", this.event.data)\n    },\n\n    formatBold() {\n      return this.toggleAttributeIfSupported(\"bold\")\n    },\n\n    formatFontColor() {\n      return this.activateAttributeIfSupported(\"color\", this.event.data)\n    },\n\n    formatFontName() {\n      return this.activateAttributeIfSupported(\"font\", this.event.data)\n    },\n\n    formatIndent() {\n      if (this.responder?.canIncreaseNestingLevel()) {\n        return this.withTargetDOMRange(function() {\n          return this.responder?.increaseNestingLevel()\n        })\n      }\n    },\n\n    formatItalic() {\n      return this.toggleAttributeIfSupported(\"italic\")\n    },\n\n    formatJustifyCenter() {\n      return this.toggleAttributeIfSupported(\"justifyCenter\")\n    },\n\n    formatJustifyFull() {\n      return this.toggleAttributeIfSupported(\"justifyFull\")\n    },\n\n    formatJustifyLeft() {\n      return this.toggleAttributeIfSupported(\"justifyLeft\")\n    },\n\n    formatJustifyRight() {\n      return this.toggleAttributeIfSupported(\"justifyRight\")\n    },\n\n    formatOutdent() {\n      if (this.responder?.canDecreaseNestingLevel()) {\n        return this.withTargetDOMRange(function() {\n          return this.responder?.decreaseNestingLevel()\n        })\n      }\n    },\n\n    formatRemove() {\n      this.withTargetDOMRange(function() {\n        for (const attributeName in this.responder?.getCurrentAttributes()) {\n          this.responder?.removeCurrentAttribute(attributeName)\n        }\n      })\n    },\n\n    formatSetBlockTextDirection() {\n      return this.activateAttributeIfSupported(\"blockDir\", this.event.data)\n    },\n\n    formatSetInlineTextDirection() {\n      return this.activateAttributeIfSupported(\"textDir\", this.event.data)\n    },\n\n    formatStrikeThrough() {\n      return this.toggleAttributeIfSupported(\"strike\")\n    },\n\n    formatSubscript() {\n      return this.toggleAttributeIfSupported(\"sub\")\n    },\n\n    formatSuperscript() {\n      return this.toggleAttributeIfSupported(\"sup\")\n    },\n\n    formatUnderline() {\n      return this.toggleAttributeIfSupported(\"underline\")\n    },\n\n    historyRedo() {\n      return this.delegate?.inputControllerWillPerformRedo()\n    },\n\n    historyUndo() {\n      return this.delegate?.inputControllerWillPerformUndo()\n    },\n\n    insertCompositionText() {\n      this.composing = true\n      return this.insertString(this.event.data)\n    },\n\n    insertFromComposition() {\n      this.composing = false\n      return this.insertString(this.event.data)\n    },\n\n    insertFromDrop() {\n      const range = this.deleteByDragRange\n      if (range) {\n        this.deleteByDragRange = null\n        this.delegate?.inputControllerWillMoveText()\n        return this.withTargetDOMRange(function() {\n          return this.responder?.moveTextFromRange(range)\n        })\n      }\n    },\n\n    insertFromPaste() {\n      const { dataTransfer } = this.event\n      const paste = { dataTransfer }\n\n      const href = dataTransfer.getData(\"URL\")\n      const html = dataTransfer.getData(\"text/html\")\n\n      if (href) {\n        let string\n        this.event.preventDefault()\n        paste.type = \"text/html\"\n        const name = dataTransfer.getData(\"public.url-name\")\n        if (name) {\n          string = squishBreakableWhitespace(name).trim()\n        } else {\n          string = href\n        }\n        paste.html = this.createLinkHTML(href, string)\n        this.delegate?.inputControllerWillPaste(paste)\n        this.withTargetDOMRange(function() {\n          return this.responder?.insertHTML(paste.html)\n        })\n        this.afterRender = () => {\n          return this.delegate?.inputControllerDidPaste(paste)\n        }\n      } else if (dataTransferIsPlainText(dataTransfer)) {\n        paste.type = \"text/plain\"\n        paste.string = dataTransfer.getData(\"text/plain\")\n        this.delegate?.inputControllerWillPaste(paste)\n        this.withTargetDOMRange(function() {\n          return this.responder?.insertString(paste.string)\n        })\n\n        this.afterRender = () => {\n          return this.delegate?.inputControllerDidPaste(paste)\n        }\n      } else if (processableFilePaste(this.event)) {\n        paste.type = \"File\"\n        paste.file = dataTransfer.files[0]\n        this.delegate?.inputControllerWillPaste(paste)\n        this.withTargetDOMRange(function() {\n          return this.responder?.insertFile(paste.file)\n        })\n\n        this.afterRender = () => {\n          return this.delegate?.inputControllerDidPaste(paste)\n        }\n      } else if (html) {\n        this.event.preventDefault()\n        paste.type = \"text/html\"\n        paste.html = html\n        this.delegate?.inputControllerWillPaste(paste)\n        this.withTargetDOMRange(function() {\n          return this.responder?.insertHTML(paste.html)\n        })\n        this.afterRender = () => {\n          return this.delegate?.inputControllerDidPaste(paste)\n        }\n      }\n    },\n\n    insertFromYank() {\n      return this.insertString(this.event.data)\n    },\n\n    insertLineBreak() {\n      return this.insertString(\"\\n\")\n    },\n\n    insertLink() {\n      return this.activateAttributeIfSupported(\"href\", this.event.data)\n    },\n\n    insertOrderedList() {\n      return this.toggleAttributeIfSupported(\"number\")\n    },\n\n    insertParagraph() {\n      this.delegate?.inputControllerWillPerformTyping()\n      return this.withTargetDOMRange(function() {\n        return this.responder?.insertLineBreak()\n      })\n    },\n\n    insertReplacementText() {\n      const replacement = this.event.dataTransfer.getData(\"text/plain\")\n      const domRange = this.event.getTargetRanges()[0]\n\n      this.withTargetDOMRange(domRange, () => {\n        this.insertString(replacement, { updatePosition: false })\n      })\n    },\n\n    insertText() {\n      return this.insertString(this.event.data || this.event.dataTransfer?.getData(\"text/plain\"))\n    },\n\n    insertTranspose() {\n      return this.insertString(this.event.data)\n    },\n\n    insertUnorderedList() {\n      return this.toggleAttributeIfSupported(\"bullet\")\n    },\n  }\n\n  elementDidMutate() {\n    if (this.scheduledRender) {\n      if (this.composing) {\n        return this.delegate?.inputControllerDidAllowUnhandledInput?.()\n      }\n    } else {\n      return this.reparse()\n    }\n  }\n\n  scheduleRender() {\n    return this.scheduledRender ? this.scheduledRender : this.scheduledRender = requestAnimationFrame(this.render)\n  }\n\n  render() {\n    cancelAnimationFrame(this.scheduledRender)\n    this.scheduledRender = null\n    if (!this.composing) {\n      this.delegate?.render()\n    }\n    this.afterRender?.()\n    this.afterRender = null\n  }\n\n  reparse() {\n    return this.delegate?.reparse()\n  }\n\n  // Responder helpers\n\n  insertString(string = \"\", options) {\n    this.delegate?.inputControllerWillPerformTyping()\n    return this.withTargetDOMRange(function() {\n      return this.responder?.insertString(string, options)\n    })\n  }\n\n  toggleAttributeIfSupported(attributeName) {\n    if (getAllAttributeNames().includes(attributeName)) {\n      this.delegate?.inputControllerWillPerformFormatting(attributeName)\n      return this.withTargetDOMRange(function() {\n        return this.responder?.toggleCurrentAttribute(attributeName)\n      })\n    }\n  }\n\n  activateAttributeIfSupported(attributeName, value) {\n    if (getAllAttributeNames().includes(attributeName)) {\n      this.delegate?.inputControllerWillPerformFormatting(attributeName)\n      return this.withTargetDOMRange(function() {\n        return this.responder?.setCurrentAttribute(attributeName, value)\n      })\n    }\n  }\n\n  deleteInDirection(direction, { recordUndoEntry } = { recordUndoEntry: true }) {\n    if (recordUndoEntry) {\n      this.delegate?.inputControllerWillPerformTyping()\n    }\n    const perform = () => this.responder?.deleteInDirection(direction)\n    const domRange = this.getTargetDOMRange({ minLength: this.composing ? 1 : 2 })\n    if (domRange) {\n      return this.withTargetDOMRange(domRange, perform)\n    } else {\n      return perform()\n    }\n  }\n\n  // Selection helpers\n\n  withTargetDOMRange(domRange, fn) {\n    if (typeof domRange === \"function\") {\n      fn = domRange\n      domRange = this.getTargetDOMRange()\n    }\n    if (domRange) {\n      return this.responder?.withTargetDOMRange(domRange, fn.bind(this))\n    } else {\n      selectionChangeObserver.reset()\n      return fn.call(this)\n    }\n  }\n\n  getTargetDOMRange({ minLength } = { minLength: 0 }) {\n    const targetRanges = this.event.getTargetRanges?.()\n    if (targetRanges) {\n      if (targetRanges.length) {\n        const domRange = staticRangeToRange(targetRanges[0])\n        if (minLength === 0 || domRange.toString().length >= minLength) {\n          return domRange\n        }\n      }\n    }\n  }\n\n  withEvent(event, fn) {\n    let result\n    this.event = event\n    try {\n      result = fn.call(this)\n    } finally {\n      this.event = null\n    }\n    return result\n  }\n}\n\nconst staticRangeToRange = function(staticRange) {\n  const range = document.createRange()\n  range.setStart(staticRange.startContainer, staticRange.startOffset)\n  range.setEnd(staticRange.endContainer, staticRange.endOffset)\n  return range\n}\n\n// Event helpers\n\nconst dragEventHasFiles = (event) => Array.from(event.dataTransfer?.types || []).includes(\"Files\")\n\nconst processableFilePaste = (event) => {\n  // Paste events that only have files are handled by the paste event handler,\n  // to work around Safari not supporting beforeinput.insertFromPaste for files.\n\n  // MS Office text pastes include a file with a screenshot of the text, but we should\n  // handle them as text pastes.\n  return event.dataTransfer.files?.[0] && !pasteEventHasFilesOnly(event) && !dataTransferIsMsOfficePaste(event)\n}\n\nconst pasteEventHasFilesOnly = function(event) {\n  const clipboard = event.clipboardData\n  if (clipboard) {\n    const fileTypes = Array.from(clipboard.types).filter((type) => type.match(/file/i)) // \"Files\", \"application/x-moz-file\"\n    return fileTypes.length === clipboard.types.length && clipboard.files.length >= 1\n  }\n}\n\nconst pasteEventHasPlainTextOnly = function(event) {\n  const clipboard = event.clipboardData\n  if (clipboard) {\n    return clipboard.types.includes(\"text/plain\") && clipboard.types.length === 1\n  }\n}\n\nconst keyboardCommandFromKeyEvent = function(event) {\n  const command = []\n  if (event.altKey) {\n    command.push(\"alt\")\n  }\n  if (event.shiftKey) {\n    command.push(\"shift\")\n  }\n  command.push(event.key)\n  return command\n}\n\nconst pointFromEvent = (event) => ({\n  x: event.clientX,\n  y: event.clientY,\n})\n"
  },
  {
    "path": "src/trix/controllers/toolbar_controller.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nimport { findClosestElementFromNode, handleEvent, triggerEvent } from \"trix/core/helpers\"\n\nimport DOMPurify from \"dompurify\"\n\nconst attributeButtonSelector = \"[data-trix-attribute]\"\nconst actionButtonSelector = \"[data-trix-action]\"\nconst toolbarButtonSelector = `${attributeButtonSelector}, ${actionButtonSelector}`\nconst dialogSelector = \"[data-trix-dialog]\"\nconst activeDialogSelector = `${dialogSelector}[data-trix-active]`\nconst dialogButtonSelector = `${dialogSelector} [data-trix-method]`\nconst dialogInputSelector = `${dialogSelector} [data-trix-input]`\nconst getInputForDialog = (element, attributeName) => {\n  if (!attributeName) { attributeName = getAttributeName(element) }\n  return element.querySelector(`[data-trix-input][name='${attributeName}']`)\n}\nconst getActionName = (element) => element.getAttribute(\"data-trix-action\")\nconst getAttributeName = (element) => {\n  return element.getAttribute(\"data-trix-attribute\") || element.getAttribute(\"data-trix-dialog-attribute\")\n}\nconst getDialogName = (element) => element.getAttribute(\"data-trix-dialog\")\n\nexport default class ToolbarController extends BasicObject {\n  constructor(element) {\n    super(element)\n    this.didClickActionButton = this.didClickActionButton.bind(this)\n    this.didClickAttributeButton = this.didClickAttributeButton.bind(this)\n    this.didClickDialogButton = this.didClickDialogButton.bind(this)\n    this.didKeyDownDialogInput = this.didKeyDownDialogInput.bind(this)\n    this.element = element\n    this.attributes = {}\n    this.actions = {}\n    this.resetDialogInputs()\n\n    handleEvent(\"mousedown\", {\n      onElement: this.element,\n      matchingSelector: actionButtonSelector,\n      withCallback: this.didClickActionButton,\n    })\n    handleEvent(\"mousedown\", {\n      onElement: this.element,\n      matchingSelector: attributeButtonSelector,\n      withCallback: this.didClickAttributeButton,\n    })\n    handleEvent(\"click\", { onElement: this.element, matchingSelector: toolbarButtonSelector, preventDefault: true })\n    handleEvent(\"click\", {\n      onElement: this.element,\n      matchingSelector: dialogButtonSelector,\n      withCallback: this.didClickDialogButton,\n    })\n    handleEvent(\"keydown\", {\n      onElement: this.element,\n      matchingSelector: dialogInputSelector,\n      withCallback: this.didKeyDownDialogInput,\n    })\n  }\n\n  // Event handlers\n\n  didClickActionButton(event, element) {\n    this.delegate?.toolbarDidClickButton()\n    event.preventDefault()\n    const actionName = getActionName(element)\n\n    if (this.getDialog(actionName)) {\n      return this.toggleDialog(actionName)\n    } else {\n      return this.delegate?.toolbarDidInvokeAction(actionName, element)\n    }\n  }\n\n  didClickAttributeButton(event, element) {\n    this.delegate?.toolbarDidClickButton()\n    event.preventDefault()\n    const attributeName = getAttributeName(element)\n\n    if (this.getDialog(attributeName)) {\n      this.toggleDialog(attributeName)\n    } else {\n      this.delegate?.toolbarDidToggleAttribute(attributeName)\n    }\n\n    return this.refreshAttributeButtons()\n  }\n\n  didClickDialogButton(event, element) {\n    const dialogElement = findClosestElementFromNode(element, { matchingSelector: dialogSelector })\n    const method = element.getAttribute(\"data-trix-method\")\n    return this[method].call(this, dialogElement)\n  }\n\n  didKeyDownDialogInput(event, element) {\n    if (event.keyCode === 13) {\n      // Enter key\n      event.preventDefault()\n      const attribute = element.getAttribute(\"name\")\n      const dialog = this.getDialog(attribute)\n      this.setAttribute(dialog)\n    }\n    if (event.keyCode === 27) {\n      // Escape key\n      event.preventDefault()\n      return this.hideDialog()\n    }\n  }\n\n  // Action buttons\n\n  updateActions(actions) {\n    this.actions = actions\n    return this.refreshActionButtons()\n  }\n\n  refreshActionButtons() {\n    return this.eachActionButton((element, actionName) => {\n      element.disabled = this.actions[actionName] === false\n    })\n  }\n\n  eachActionButton(callback) {\n    return Array.from(this.element.querySelectorAll(actionButtonSelector)).map((element) =>\n      callback(element, getActionName(element))\n    )\n  }\n\n  // Attribute buttons\n\n  updateAttributes(attributes) {\n    this.attributes = attributes\n    return this.refreshAttributeButtons()\n  }\n\n  refreshAttributeButtons() {\n    return this.eachAttributeButton((element, attributeName) => {\n      element.disabled = this.attributes[attributeName] === false\n      if (this.attributes[attributeName] || this.dialogIsVisible(attributeName)) {\n        element.setAttribute(\"data-trix-active\", \"\")\n        return element.classList.add(\"trix-active\")\n      } else {\n        element.removeAttribute(\"data-trix-active\")\n        return element.classList.remove(\"trix-active\")\n      }\n    })\n  }\n\n  eachAttributeButton(callback) {\n    return Array.from(this.element.querySelectorAll(attributeButtonSelector)).map((element) =>\n      callback(element, getAttributeName(element))\n    )\n  }\n\n  applyKeyboardCommand(keys) {\n    const keyString = JSON.stringify(keys.sort())\n    for (const button of Array.from(this.element.querySelectorAll(\"[data-trix-key]\"))) {\n      const buttonKeys = button.getAttribute(\"data-trix-key\").split(\"+\")\n      const buttonKeyString = JSON.stringify(buttonKeys.sort())\n      if (buttonKeyString === keyString) {\n        triggerEvent(\"mousedown\", { onElement: button })\n        return true\n      }\n    }\n    return false\n  }\n\n  // Dialogs\n\n  dialogIsVisible(dialogName) {\n    const element = this.getDialog(dialogName)\n    if (element) {\n      return element.hasAttribute(\"data-trix-active\")\n    }\n  }\n\n  toggleDialog(dialogName) {\n    if (this.dialogIsVisible(dialogName)) {\n      return this.hideDialog()\n    } else {\n      return this.showDialog(dialogName)\n    }\n  }\n\n  showDialog(dialogName) {\n    this.hideDialog()\n    this.delegate?.toolbarWillShowDialog()\n\n    const element = this.getDialog(dialogName)\n    element.setAttribute(\"data-trix-active\", \"\")\n    element.classList.add(\"trix-active\")\n\n    Array.from(element.querySelectorAll(\"input[disabled]\")).forEach((disabledInput) => {\n      disabledInput.removeAttribute(\"disabled\")\n    })\n\n    const attributeName = getAttributeName(element)\n    if (attributeName) {\n      const input = getInputForDialog(element, dialogName)\n      if (input) {\n        input.value = this.attributes[attributeName] || \"\"\n        input.select()\n      }\n    }\n\n    return this.delegate?.toolbarDidShowDialog(dialogName)\n  }\n\n  setAttribute(dialogElement) {\n    const attributeName = getAttributeName(dialogElement)\n    const input = getInputForDialog(dialogElement, attributeName)\n\n    if (input.willValidate) {\n      input.setCustomValidity(\"\")\n      if (!input.checkValidity() || !this.isSafeAttribute(input)) {\n        input.setCustomValidity(\"Invalid value\")\n        input.setAttribute(\"data-trix-validate\", \"\")\n        input.classList.add(\"trix-validate\")\n        return input.focus()\n      }\n    }\n    this.delegate?.toolbarDidUpdateAttribute(attributeName, input.value)\n    return this.hideDialog()\n  }\n\n  isSafeAttribute(input) {\n    if (input.hasAttribute(\"data-trix-validate-href\")) {\n      return DOMPurify.isValidAttribute(\"a\", \"href\", input.value)\n    } else {\n      return true\n    }\n  }\n\n  removeAttribute(dialogElement) {\n    const attributeName = getAttributeName(dialogElement)\n    this.delegate?.toolbarDidRemoveAttribute(attributeName)\n    return this.hideDialog()\n  }\n\n  hideDialog() {\n    const element = this.element.querySelector(activeDialogSelector)\n    if (element) {\n      element.removeAttribute(\"data-trix-active\")\n      element.classList.remove(\"trix-active\")\n      this.resetDialogInputs()\n      return this.delegate?.toolbarDidHideDialog(getDialogName(element))\n    }\n  }\n\n  resetDialogInputs() {\n    Array.from(this.element.querySelectorAll(dialogInputSelector)).forEach((input) => {\n      input.setAttribute(\"disabled\", \"disabled\")\n      input.removeAttribute(\"data-trix-validate\")\n      input.classList.remove(\"trix-validate\")\n    })\n  }\n\n  getDialog(dialogName) {\n    return this.element.querySelector(`[data-trix-dialog=${dialogName}]`)\n  }\n}\n"
  },
  {
    "path": "src/trix/core/basic_object.js",
    "content": "export default class BasicObject {\n  static proxyMethod(expression) {\n    const { name, toMethod, toProperty, optional } = parseProxyMethodExpression(expression)\n\n    this.prototype[name] = function() {\n      let subject\n      let object\n\n      if (toMethod) {\n        if (optional) {\n          object = this[toMethod]?.()\n        } else {\n          object = this[toMethod]()\n        }\n      } else if (toProperty) {\n        object = this[toProperty]\n      }\n\n      if (optional) {\n        subject = object?.[name]\n        if (subject) {\n          return apply.call(subject, object, arguments)\n        }\n      } else {\n        subject = object[name]\n        return apply.call(subject, object, arguments)\n      }\n    }\n  }\n}\n\nconst parseProxyMethodExpression = function(expression) {\n  const match = expression.match(proxyMethodExpressionPattern)\n  if (!match) {\n    throw new Error(`can't parse @proxyMethod expression: ${expression}`)\n  }\n\n  const args = { name: match[4] }\n\n  if (match[2] != null) {\n    args.toMethod = match[1]\n  } else {\n    args.toProperty = match[1]\n  }\n\n  if (match[3] != null) {\n    args.optional = true\n  }\n\n  return args\n}\n\nconst { apply } = Function.prototype\n\nconst proxyMethodExpressionPattern = new RegExp(\"\\\n^\\\n(.+?)\\\n(\\\\(\\\\))?\\\n(\\\\?)?\\\n\\\\.\\\n(.+?)\\\n$\\\n\")\n"
  },
  {
    "path": "src/trix/core/collections/element_store.js",
    "content": "export default class ElementStore {\n  constructor(elements) {\n    this.reset(elements)\n  }\n\n  add(element) {\n    const key = getKey(element)\n    this.elements[key] = element\n  }\n\n  remove(element) {\n    const key = getKey(element)\n    const value = this.elements[key]\n    if (value) {\n      delete this.elements[key]\n      return value\n    }\n  }\n\n  reset(elements = []) {\n    this.elements = {}\n    Array.from(elements).forEach((element) => {\n      this.add(element)\n    })\n    return elements\n  }\n}\n\nconst getKey = (element) => element.dataset.trixStoreKey\n"
  },
  {
    "path": "src/trix/core/collections/hash.js",
    "content": "import TrixObject from \"trix/core/object\" // Don't override window.Object\nimport { arraysAreEqual } from \"trix/core/helpers\"\n\nexport default class Hash extends TrixObject {\n  static fromCommonAttributesOfObjects(objects = []) {\n    if (!objects.length) {\n      return new this()\n    }\n    let hash = box(objects[0])\n    let keys = hash.getKeys()\n\n    objects.slice(1).forEach((object) => {\n      keys = hash.getKeysCommonToHash(box(object))\n      hash = hash.slice(keys)\n    })\n\n    return hash\n  }\n\n  static box(values) {\n    return box(values)\n  }\n\n  constructor(values = {}) {\n    super(...arguments)\n    this.values = copy(values)\n  }\n\n  add(key, value) {\n    return this.merge(object(key, value))\n  }\n\n  remove(key) {\n    return new Hash(copy(this.values, key))\n  }\n\n  get(key) {\n    return this.values[key]\n  }\n\n  has(key) {\n    return key in this.values\n  }\n\n  merge(values) {\n    return new Hash(merge(this.values, unbox(values)))\n  }\n\n  slice(keys) {\n    const values = {}\n\n    Array.from(keys).forEach((key) => {\n      if (this.has(key)) {\n        values[key] = this.values[key]\n      }\n    })\n\n    return new Hash(values)\n  }\n\n  getKeys() {\n    return Object.keys(this.values)\n  }\n\n  getKeysCommonToHash(hash) {\n    hash = box(hash)\n    return this.getKeys().filter((key) => this.values[key] === hash.values[key])\n  }\n\n  isEqualTo(values) {\n    return arraysAreEqual(this.toArray(), box(values).toArray())\n  }\n\n  isEmpty() {\n    return this.getKeys().length === 0\n  }\n\n  toArray() {\n    if (!this.array) {\n      const result = []\n      for (const key in this.values) {\n        const value = this.values[key]\n        result.push(result.push(key, value))\n      }\n      this.array = result.slice(0)\n    }\n\n    return this.array\n  }\n\n  toObject() {\n    return copy(this.values)\n  }\n\n  toJSON() {\n    return this.toObject()\n  }\n\n  contentsForInspection() {\n    return { values: JSON.stringify(this.values) }\n  }\n}\n\nconst object = function(key, value) {\n  const result = {}\n  result[key] = value\n  return result\n}\n\nconst merge = function(object, values) {\n  const result = copy(object)\n  for (const key in values) {\n    const value = values[key]\n    result[key] = value\n  }\n  return result\n}\n\nconst copy = function(object, keyToRemove) {\n  const result = {}\n  const sortedKeys = Object.keys(object).sort()\n\n  sortedKeys.forEach((key) => {\n    if (key !== keyToRemove) {\n      result[key] = object[key]\n    }\n  })\n\n  return result\n}\n\nconst box = function(object) {\n  if (object instanceof Hash) {\n    return object\n  } else {\n    return new Hash(object)\n  }\n}\n\nconst unbox = function(object) {\n  if (object instanceof Hash) {\n    return object.values\n  } else {\n    return object\n  }\n}\n"
  },
  {
    "path": "src/trix/core/collections/index.js",
    "content": "import \"./hash\"\nimport \"./object_group\"\nimport \"./object_map\"\nimport \"./element_store\"\n"
  },
  {
    "path": "src/trix/core/collections/object_group.js",
    "content": "export default class ObjectGroup {\n  static groupObjects(ungroupedObjects = [], { depth, asTree } = {}) {\n    let group\n    if (asTree) {\n      if (depth == null) {\n        depth = 0\n      }\n    }\n    const objects = []\n\n    Array.from(ungroupedObjects).forEach((object) => {\n      if (group) {\n        if (object.canBeGrouped?.(depth) && group[group.length - 1].canBeGroupedWith?.(object, depth)) {\n          group.push(object)\n          return\n        } else {\n          objects.push(new this(group, { depth, asTree }))\n          group = null\n        }\n      }\n\n      if (object.canBeGrouped?.(depth)) {\n        group = [ object ]\n      } else {\n        objects.push(object)\n      }\n    })\n\n    if (group) {\n      objects.push(new this(group, { depth, asTree }))\n    }\n    return objects\n  }\n\n  constructor(objects = [], { depth, asTree }) {\n    this.objects = objects\n    if (asTree) {\n      this.depth = depth\n      this.objects = this.constructor.groupObjects(this.objects, { asTree, depth: this.depth + 1 })\n    }\n  }\n\n  getObjects() {\n    return this.objects\n  }\n\n  getDepth() {\n    return this.depth\n  }\n\n  getCacheKey() {\n    const keys = [ \"objectGroup\" ]\n    Array.from(this.getObjects()).forEach((object) => {\n      keys.push(object.getCacheKey())\n    })\n    return keys.join(\"/\")\n  }\n}\n"
  },
  {
    "path": "src/trix/core/collections/object_map.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nexport default class ObjectMap extends BasicObject {\n  constructor(objects = []) {\n    super(...arguments)\n    this.objects = {}\n\n    Array.from(objects).forEach((object) => {\n      const hash = JSON.stringify(object)\n      if (this.objects[hash] == null) {\n        this.objects[hash] = object\n      }\n    })\n  }\n\n  find(object) {\n    const hash = JSON.stringify(object)\n    return this.objects[hash]\n  }\n}\n"
  },
  {
    "path": "src/trix/core/helpers/arrays.js",
    "content": "/* eslint-disable\n    id-length,\n*/\nexport const arraysAreEqual = function(a = [], b = []) {\n  if (a.length !== b.length) {\n    return false\n  }\n  for (let index = 0; index < a.length; index++) {\n    const value = a[index]\n    if (value !== b[index]) {\n      return false\n    }\n  }\n  return true\n}\n\nexport const arrayStartsWith = (a = [], b = []) => arraysAreEqual(a.slice(0, b.length), b)\n\nexport const spliceArray = function(array, ...args) {\n  const result = array.slice(0)\n  result.splice(...args)\n  return result\n}\n\nexport const summarizeArrayChange = function(oldArray = [], newArray = []) {\n  const added = []\n  const removed = []\n\n  const existingValues = new Set()\n\n  oldArray.forEach((value) => {\n    existingValues.add(value)\n  })\n\n  const currentValues = new Set()\n\n  newArray.forEach((value) => {\n    currentValues.add(value)\n    if (!existingValues.has(value)) {\n      added.push(value)\n    }\n  })\n\n  oldArray.forEach((value) => {\n    if (!currentValues.has(value)) {\n      removed.push(value)\n    }\n  })\n\n  return { added, removed }\n}\n"
  },
  {
    "path": "src/trix/core/helpers/bidi.js",
    "content": "import { makeElement } from \"trix/core/helpers/dom\"\n\n// https://github.com/mathiasbynens/unicode-2.1.8/blob/master/Bidi_Class/Right_To_Left/regex.js\nconst RTL_PATTERN =\n  /[\\u05BE\\u05C0\\u05C3\\u05D0-\\u05EA\\u05F0-\\u05F4\\u061B\\u061F\\u0621-\\u063A\\u0640-\\u064A\\u066D\\u0671-\\u06B7\\u06BA-\\u06BE\\u06C0-\\u06CE\\u06D0-\\u06D5\\u06E5\\u06E6\\u200F\\u202B\\u202E\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE72\\uFE74\\uFE76-\\uFEFC]/\n\nexport const getDirection = (function() {\n  const input = makeElement(\"input\", { dir: \"auto\", name: \"x\", dirName: \"x.dir\" })\n  const textArea = makeElement(\"textarea\", { dir: \"auto\", name: \"y\", dirName: \"y.dir\" })\n  const form = makeElement(\"form\")\n  form.appendChild(input)\n  form.appendChild(textArea)\n\n  const supportsDirName = (function() {\n    try {\n      return new FormData(form).has(textArea.dirName)\n    } catch (error) {\n      return false\n    }\n  })()\n\n  const supportsDirSelector = (function() {\n    try {\n      return input.matches(\":dir(ltr),:dir(rtl)\")\n    } catch (error) {\n      return false\n    }\n  })()\n\n  if (supportsDirName) {\n    return function(string) {\n      textArea.value = string\n      return new FormData(form).get(textArea.dirName)\n    }\n  } else if (supportsDirSelector) {\n    return function(string) {\n      input.value = string\n      if (input.matches(\":dir(rtl)\")) {\n        return \"rtl\"\n      } else {\n        return \"ltr\"\n      }\n    }\n  } else {\n    return function(string) {\n      const char = string.trim().charAt(0)\n      if (RTL_PATTERN.test(char)) {\n        return \"rtl\"\n      } else {\n        return \"ltr\"\n      }\n    }\n  }\n})()\n"
  },
  {
    "path": "src/trix/core/helpers/config.js",
    "content": "import * as config from \"trix/config\"\n\nlet allAttributeNames = null\nlet blockAttributeNames = null\nlet textAttributeNames = null\nlet listAttributeNames = null\n\nexport const getAllAttributeNames = () => {\n  if (!allAttributeNames) {\n    allAttributeNames = getTextAttributeNames().concat(getBlockAttributeNames())\n  }\n  return allAttributeNames\n}\n\nexport const getBlockConfig = (attributeName) => config.blockAttributes[attributeName]\n\nexport const getBlockAttributeNames = () => {\n  if (!blockAttributeNames) {\n    blockAttributeNames = Object.keys(config.blockAttributes)\n  }\n  return blockAttributeNames\n}\n\nexport const getTextConfig = (attributeName) => config.textAttributes[attributeName]\n\nexport const getTextAttributeNames = () => {\n  if (!textAttributeNames) {\n    textAttributeNames = Object.keys(config.textAttributes)\n  }\n  return textAttributeNames\n}\n\nexport const getListAttributeNames = () => {\n  if (!listAttributeNames) {\n    listAttributeNames = []\n    for (const key in config.blockAttributes) {\n      const { listAttribute } = config.blockAttributes[key]\n      if (listAttribute != null) {\n        listAttributeNames.push(listAttribute)\n      }\n    }\n  }\n  return listAttributeNames\n}\n"
  },
  {
    "path": "src/trix/core/helpers/custom_elements.js",
    "content": "/* eslint-disable\n*/\nexport const installDefaultCSSForTagName = function(tagName, defaultCSS) {\n  const styleElement = insertStyleElementForTagName(tagName)\n  styleElement.textContent = defaultCSS.replace(/%t/g, tagName)\n}\n\nconst insertStyleElementForTagName = function(tagName) {\n  const element = document.createElement(\"style\")\n  element.setAttribute(\"type\", \"text/css\")\n  element.setAttribute(\"data-tag-name\", tagName.toLowerCase())\n  const nonce = getCSPNonce()\n  if (nonce) {\n    element.setAttribute(\"nonce\", nonce)\n  }\n  document.head.insertBefore(element, document.head.firstChild)\n  return element\n}\n\nconst getCSPNonce = function() {\n  const element = getMetaElement(\"trix-csp-nonce\") || getMetaElement(\"csp-nonce\")\n  if (element) {\n    const { nonce, content } = element\n    return nonce == \"\" ? content : nonce\n  }\n}\n\nconst getMetaElement = (name) => document.head.querySelector(`meta[name=${name}]`)\n"
  },
  {
    "path": "src/trix/core/helpers/dom.js",
    "content": "import blockAttributes from \"trix/config/block_attributes\"\nimport { ZERO_WIDTH_SPACE } from \"trix/constants\"\nimport { extend } from \"./extend\"\nimport { attachmentSelector } from \"trix/config/attachments\"\n\nconst html = document.documentElement\nconst match = html.matches\n\nexport const handleEvent = function(eventName, { onElement, matchingSelector, withCallback, inPhase, preventDefault, times } = {}) {\n  const element = onElement ? onElement : html\n  const selector = matchingSelector\n  const useCapture = inPhase === \"capturing\"\n\n  const handler = function(event) {\n    if (times != null && --times === 0) {\n      handler.destroy()\n    }\n    const target = findClosestElementFromNode(event.target, { matchingSelector: selector })\n    if (target != null) {\n      withCallback?.call(target, event, target)\n      if (preventDefault) {\n        event.preventDefault()\n      }\n    }\n  }\n\n  handler.destroy = () => element.removeEventListener(eventName, handler, useCapture)\n\n  element.addEventListener(eventName, handler, useCapture)\n  return handler\n}\n\nexport const handleEventOnce = function(eventName, options = {}) {\n  options.times = 1\n  return handleEvent(eventName, options)\n}\n\nexport const createEvent = function(eventName, { bubbles, cancelable, attributes } = {}) {\n  bubbles = bubbles !== false\n  cancelable = cancelable !== false\n\n  const event = document.createEvent(\"Events\")\n  event.initEvent(eventName, bubbles, cancelable)\n  if (attributes != null) {\n    extend.call(event, attributes)\n  }\n  return event\n}\n\nexport const triggerEvent = function(eventName, { onElement, bubbles, cancelable, attributes } = {}) {\n  const element = onElement != null ? onElement : html\n  const event = createEvent(eventName, { bubbles, cancelable, attributes })\n  return element.dispatchEvent(event)\n}\n\nexport const elementMatchesSelector = function(element, selector) {\n  if (element?.nodeType === 1) {\n    return match.call(element, selector)\n  }\n}\n\nexport const findClosestElementFromNode = function(node, { matchingSelector, untilNode } = {}) {\n  while (node && node.nodeType !== Node.ELEMENT_NODE) {\n    node = node.parentNode\n  }\n  if (node == null) {\n    return\n  }\n\n  if (matchingSelector != null) {\n    if (node.closest && untilNode == null) {\n      return node.closest(matchingSelector)\n    } else {\n      while (node && node !== untilNode) {\n        if (elementMatchesSelector(node, matchingSelector)) {\n          return node\n        }\n        node = node.parentNode\n      }\n    }\n  } else {\n    return node\n  }\n}\n\nexport const findInnerElement = function(element) {\n  while (element?.firstElementChild) {\n    element = element.firstElementChild\n  }\n  return element\n}\n\nexport const innerElementIsActive = (element) =>\n  document.activeElement !== element && elementContainsNode(element, document.activeElement)\n\nexport const elementContainsNode = function(element, node) {\n  if (!element || !node) {\n    return\n  }\n  while (node) {\n    if (node === element) {\n      return true\n    }\n    node = node.parentNode\n  }\n}\n\nexport const findNodeFromContainerAndOffset = function(container, offset) {\n  if (!container) {\n    return\n  }\n  if (container.nodeType === Node.TEXT_NODE) {\n    return container\n  } else if (offset === 0) {\n    return container.firstChild != null ? container.firstChild : container\n  } else {\n    return container.childNodes.item(offset - 1)\n  }\n}\n\nexport const findElementFromContainerAndOffset = function(container, offset) {\n  const node = findNodeFromContainerAndOffset(container, offset)\n  return findClosestElementFromNode(node)\n}\n\nexport const findChildIndexOfNode = function(node) {\n  if (!node?.parentNode) {\n    return\n  }\n  let childIndex = 0\n  node = node.previousSibling\n  while (node) {\n    childIndex++\n    node = node.previousSibling\n  }\n  return childIndex\n}\n\nexport const removeNode = (node) => node?.parentNode?.removeChild(node)\n\nexport const walkTree = function(tree, { onlyNodesOfType, usingFilter, expandEntityReferences } = {}) {\n  const whatToShow = (() => {\n    switch (onlyNodesOfType) {\n      case \"element\":\n        return NodeFilter.SHOW_ELEMENT\n      case \"text\":\n        return NodeFilter.SHOW_TEXT\n      case \"comment\":\n        return NodeFilter.SHOW_COMMENT\n      default:\n        return NodeFilter.SHOW_ALL\n    }\n  })()\n\n  return document.createTreeWalker(\n    tree,\n    whatToShow,\n    usingFilter != null ? usingFilter : null,\n    expandEntityReferences === true\n  )\n}\n\nexport const tagName = (element) => element?.tagName?.toLowerCase()\n\nexport const makeElement = function(tag, options = {}) {\n  let key, value\n  if (typeof tag === \"object\") {\n    options = tag\n    tag = options.tagName\n  } else {\n    options = { attributes: options }\n  }\n\n  const element = document.createElement(tag)\n\n  if (options.editable != null) {\n    if (options.attributes == null) {\n      options.attributes = {}\n    }\n    options.attributes.contenteditable = options.editable\n  }\n\n  if (options.attributes) {\n    for (key in options.attributes) {\n      value = options.attributes[key]\n      element.setAttribute(key, value)\n    }\n  }\n\n  if (options.style) {\n    for (key in options.style) {\n      value = options.style[key]\n      element.style[key] = value\n    }\n  }\n\n  if (options.data) {\n    for (key in options.data) {\n      value = options.data[key]\n      element.dataset[key] = value\n    }\n  }\n\n  if (options.className) {\n    options.className.split(\" \").forEach((className) => {\n      element.classList.add(className)\n    })\n  }\n\n  if (options.textContent) {\n    element.textContent = options.textContent\n  }\n\n  if (options.childNodes) {\n    [].concat(options.childNodes).forEach((childNode) => {\n      element.appendChild(childNode)\n    })\n  }\n\n  return element\n}\n\nlet blockTagNames = undefined\n\nexport const getBlockTagNames = function() {\n  if (blockTagNames != null) {\n    return blockTagNames\n  }\n\n  blockTagNames = []\n  for (const key in blockAttributes) {\n    const attributes = blockAttributes[key]\n    if (attributes.tagName) {\n      blockTagNames.push(attributes.tagName)\n    }\n  }\n\n  return blockTagNames\n}\n\nexport const nodeIsBlockContainer = (node) => nodeIsBlockStartComment(node?.firstChild)\n\nexport const nodeProbablyIsBlockContainer = function(node) {\n  return getBlockTagNames().includes(tagName(node)) && !getBlockTagNames().includes(tagName(node.firstChild))\n}\n\nexport const nodeIsBlockStart = function(node, { strict } = { strict: true }) {\n  if (strict) {\n    return nodeIsBlockStartComment(node)\n  } else {\n    return (\n      nodeIsBlockStartComment(node) || !nodeIsBlockStartComment(node.firstChild) && nodeProbablyIsBlockContainer(node)\n    )\n  }\n}\n\nexport const nodeIsBlockStartComment = (node) => nodeIsCommentNode(node) && node?.data === \"block\"\n\nexport const nodeIsCommentNode = (node) => node?.nodeType === Node.COMMENT_NODE\n\nexport const nodeIsCursorTarget = function(node, { name } = {}) {\n  if (!node) {\n    return\n  }\n  if (nodeIsTextNode(node)) {\n    if (node.data === ZERO_WIDTH_SPACE) {\n      if (name) {\n        return node.parentNode.dataset.trixCursorTarget === name\n      } else {\n        return true\n      }\n    }\n  } else {\n    return nodeIsCursorTarget(node.firstChild)\n  }\n}\n\nexport const nodeIsAttachmentElement = (node) => elementMatchesSelector(node, attachmentSelector)\n\nexport const nodeIsEmptyTextNode = (node) => nodeIsTextNode(node) && node?.data === \"\"\n\nexport const nodeIsTextNode = (node) => node?.nodeType === Node.TEXT_NODE\n"
  },
  {
    "path": "src/trix/core/helpers/events.js",
    "content": "const testTransferData = { \"application/x-trix-feature-detection\": \"test\" }\n\nexport const dataTransferIsPlainText = function(dataTransfer) {\n  const text = dataTransfer.getData(\"text/plain\")\n  const html = dataTransfer.getData(\"text/html\")\n\n  if (text && html) {\n    const { body } = new DOMParser().parseFromString(html, \"text/html\")\n    if (body.textContent === text) {\n      return !body.querySelector(\"*\")\n    }\n  } else {\n    return text?.length\n  }\n}\n\nexport const dataTransferIsMsOfficePaste = ({ dataTransfer }) => {\n  return dataTransfer.types.includes(\"Files\") &&\n    dataTransfer.types.includes(\"text/html\") &&\n    dataTransfer.getData(\"text/html\").includes(\"urn:schemas-microsoft-com:office:office\")\n}\n\nexport const dataTransferIsWritable = function(dataTransfer) {\n  if (!dataTransfer?.setData) return false\n\n  for (const key in testTransferData) {\n    const value = testTransferData[key]\n\n    try {\n      dataTransfer.setData(key, value)\n      if (!dataTransfer.getData(key) === value) return false\n    } catch (error) {\n      return false\n    }\n  }\n  return true\n}\n\nexport const keyEventIsKeyboardCommand = (function() {\n  if (/Mac|^iP/.test(navigator.platform)) {\n    return (event) => event.metaKey\n  } else {\n    return (event) => event.ctrlKey\n  }\n})()\n\nexport function shouldRenderInmmediatelyToDealWithIOSDictation(inputEvent) {\n  if (/iPhone|iPad/.test(navigator.userAgent)) {\n    // Handle garbled content and duplicated newlines when using dictation on iOS 18+. Upon dictation completion, iOS sends\n    // the list of insertText / insertParagraph events in a quick sequence. If we don't render\n    // the editor synchronously, the internal range fails to update and results in garbled content or duplicated newlines.\n    //\n    // This workaround is necessary because iOS doesn't send composing events as expected while dictating:\n    // https://bugs.webkit.org/show_bug.cgi?id=261764\n    return !inputEvent.inputType || inputEvent.inputType === \"insertParagraph\"\n  } else {\n    return false\n  }\n}\n"
  },
  {
    "path": "src/trix/core/helpers/extend.js",
    "content": "export const extend = function(properties) {\n  for (const key in properties) {\n    const value = properties[key]\n    this[key] = value\n  }\n  return this\n}\n"
  },
  {
    "path": "src/trix/core/helpers/functions.js",
    "content": "export const defer = (fn) => setTimeout(fn, 1)\n"
  },
  {
    "path": "src/trix/core/helpers/global.js",
    "content": "// Explicitly require this file (not included in the main\n// Trix bundle) to install the following global helpers.\n\nthis.getEditorElement = () => document.querySelector(\"trix-editor\")\n\nthis.getToolbarElement = () => getEditorElement().toolbarElement\n\nthis.getEditorController = () => getEditorElement().editorController\n\nthis.getEditor = () => getEditorController().editor\n\nthis.getComposition = () => getEditorController().composition\n\nthis.getDocument = () => getComposition().document\n\nthis.getSelectionManager = () => getEditorController().selectionManager\n"
  },
  {
    "path": "src/trix/core/helpers/index.js",
    "content": "export * from \"./arrays\"\nexport * from \"./bidi\"\nexport * from \"./config\"\nexport * from \"./custom_elements\"\nexport * from \"./dom\"\nexport * from \"./events\"\nexport * from \"./extend\"\nexport * from \"./functions\"\nexport * from \"./objects\"\nexport * from \"./ranges\"\nexport * from \"./selection\"\nexport * from \"./strings\"\n"
  },
  {
    "path": "src/trix/core/helpers/objects.js",
    "content": "/* eslint-disable\n    id-length,\n*/\nexport const copyObject = function(object = {}) {\n  const result = {}\n  for (const key in object) {\n    const value = object[key]\n    result[key] = value\n  }\n  return result\n}\n\nexport const objectsAreEqual = function(a = {}, b = {}) {\n  if (Object.keys(a).length !== Object.keys(b).length) {\n    return false\n  }\n  for (const key in a) {\n    const value = a[key]\n    if (value !== b[key]) {\n      return false\n    }\n  }\n  return true\n}\n"
  },
  {
    "path": "src/trix/core/helpers/ranges.js",
    "content": "import { copyObject, objectsAreEqual } from \"trix/core/helpers/objects\"\n\nexport const normalizeRange = function(range) {\n  if (range == null) return\n\n  if (!Array.isArray(range)) {\n    range = [ range, range ]\n  }\n  return [ copyValue(range[0]), copyValue(range[1] != null ? range[1] : range[0]) ]\n}\n\nexport const rangeIsCollapsed = function(range) {\n  if (range == null) return\n\n  const [ start, end ] = normalizeRange(range)\n  return rangeValuesAreEqual(start, end)\n}\n\nexport const rangesAreEqual = function(leftRange, rightRange) {\n  if (leftRange == null || rightRange == null) return\n\n  const [ leftStart, leftEnd ] = normalizeRange(leftRange)\n  const [ rightStart, rightEnd ] = normalizeRange(rightRange)\n  return rangeValuesAreEqual(leftStart, rightStart) && rangeValuesAreEqual(leftEnd, rightEnd)\n}\n\nconst copyValue = function(value) {\n  if (typeof value === \"number\") {\n    return value\n  } else {\n    return copyObject(value)\n  }\n}\n\nconst rangeValuesAreEqual = function(left, right) {\n  if (typeof left === \"number\") {\n    return left === right\n  } else {\n    return objectsAreEqual(left, right)\n  }\n}\n"
  },
  {
    "path": "src/trix/core/helpers/selection.js",
    "content": "import { getDOMRange, getDOMSelection, setDOMRange } from \"trix/observers/selection_change_observer\"\nexport { getDOMSelection, getDOMRange, setDOMRange }\n"
  },
  {
    "path": "src/trix/core/helpers/strings.js",
    "content": "/* eslint-disable\n    id-length,\n    no-useless-escape,\n*/\nimport { NON_BREAKING_SPACE, ZERO_WIDTH_SPACE } from \"trix/constants\"\nimport UTF16String from \"trix/core/utilities/utf16_string\"\n\nexport const normalizeSpaces = (string) =>\n  string.replace(new RegExp(`${ZERO_WIDTH_SPACE}`, \"g\"), \"\").replace(new RegExp(`${NON_BREAKING_SPACE}`, \"g\"), \" \")\n\nexport const normalizeNewlines = (string) => string.replace(/\\r\\n?/g, \"\\n\")\n\nexport const breakableWhitespacePattern = new RegExp(`[^\\\\S${NON_BREAKING_SPACE}]`)\n\nexport const squishBreakableWhitespace = (string) =>\n  string\n    // Replace all breakable whitespace characters with a space\n    .replace(new RegExp(`${breakableWhitespacePattern.source}`, \"g\"), \" \")\n    // Replace two or more spaces with a single space\n    .replace(/\\ {2,}/g, \" \")\n\nexport const summarizeStringChange = function(oldString, newString) {\n  let added, removed\n  oldString = UTF16String.box(oldString)\n  newString = UTF16String.box(newString)\n\n  if (newString.length < oldString.length) {\n    [ removed, added ] = utf16StringDifferences(oldString, newString)\n  } else {\n    [ added, removed ] = utf16StringDifferences(newString, oldString)\n  }\n\n  return { added, removed }\n}\n\nconst utf16StringDifferences = function(a, b) {\n  if (a.isEqualTo(b)) {\n    return [ \"\", \"\" ]\n  }\n\n  const diffA = utf16StringDifference(a, b)\n  const { length } = diffA.utf16String\n\n  let diffB\n\n  if (length) {\n    const { offset } = diffA\n    const codepoints = a.codepoints.slice(0, offset).concat(a.codepoints.slice(offset + length))\n    diffB = utf16StringDifference(b, UTF16String.fromCodepoints(codepoints))\n  } else {\n    diffB = utf16StringDifference(b, a)\n  }\n\n  return [ diffA.utf16String.toString(), diffB.utf16String.toString() ]\n}\n\nconst utf16StringDifference = function(a, b) {\n  let leftIndex = 0\n  let rightIndexA = a.length\n  let rightIndexB = b.length\n\n  while (leftIndex < rightIndexA && a.charAt(leftIndex).isEqualTo(b.charAt(leftIndex))) {\n    leftIndex++\n  }\n\n  while (rightIndexA > leftIndex + 1 && a.charAt(rightIndexA - 1).isEqualTo(b.charAt(rightIndexB - 1))) {\n    rightIndexA--\n    rightIndexB--\n  }\n\n  return {\n    utf16String: a.slice(leftIndex, rightIndexA),\n    offset: leftIndex,\n  }\n}\n"
  },
  {
    "path": "src/trix/core/index.js",
    "content": "import \"./object\"\nimport \"./helpers\"\nimport \"./collections\"\nimport \"./utilities\"\nimport \"./serialization\"\n"
  },
  {
    "path": "src/trix/core/object.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\nimport UTF16String from \"trix/core/utilities/utf16_string\"\n\nlet id = 0\n\nexport default class TrixObject extends BasicObject {\n  static fromJSONString(jsonString) {\n    return this.fromJSON(JSON.parse(jsonString))\n  }\n\n  constructor() {\n    super(...arguments)\n    this.id = ++id\n  }\n\n  hasSameConstructorAs(object) {\n    return this.constructor === object?.constructor\n  }\n\n  isEqualTo(object) {\n    return this === object\n  }\n\n  inspect() {\n    const parts = []\n    const contents = this.contentsForInspection() || {}\n\n    for (const key in contents) {\n      const value = contents[key]\n      parts.push(`${key}=${value}`)\n    }\n\n    return `#<${this.constructor.name}:${this.id}${parts.length ? ` ${parts.join(\", \")}` : \"\"}>`\n  }\n\n  contentsForInspection() {}\n\n  toJSONString() {\n    return JSON.stringify(this)\n  }\n\n  toUTF16String() {\n    return UTF16String.box(this)\n  }\n\n  getCacheKey() {\n    return this.id.toString()\n  }\n}\n"
  },
  {
    "path": "src/trix/core/serialization.js",
    "content": "/* eslint-disable\n    no-empty,\n*/\nimport { removeNode } from \"trix/core/helpers\"\n\nimport DocumentView from \"trix/views/document_view\"\nimport Document from \"trix/models/document\"\nimport HTMLParser from \"trix/models/html_parser\"\n\nconst unserializableElementSelector = \"[data-trix-serialize=false]\"\nconst unserializableAttributeNames = [\n  \"contenteditable\",\n  \"data-trix-id\",\n  \"data-trix-store-key\",\n  \"data-trix-mutable\",\n  \"data-trix-placeholder\",\n  \"tabindex\",\n]\nconst serializedAttributesAttribute = \"data-trix-serialized-attributes\"\nconst serializedAttributesSelector = `[${serializedAttributesAttribute}]`\n\nconst blockCommentPattern = new RegExp(\"<!--block-->\", \"g\")\n\nconst serializers = {\n  \"application/json\": function(serializable) {\n    let document\n    if (serializable instanceof Document) {\n      document = serializable\n    } else if (serializable instanceof HTMLElement) {\n      document = HTMLParser.parse(serializable.innerHTML).getDocument()\n    } else {\n      throw new Error(\"unserializable object\")\n    }\n\n    return document.toSerializableDocument().toJSONString()\n  },\n\n  \"text/html\": function(serializable) {\n    let element\n    if (serializable instanceof Document) {\n      element = DocumentView.render(serializable)\n    } else if (serializable instanceof HTMLElement) {\n      element = serializable.cloneNode(true)\n    } else {\n      throw new Error(\"unserializable object\")\n    }\n\n    // Remove unserializable elements\n    Array.from(element.querySelectorAll(unserializableElementSelector)).forEach((el) => {\n      removeNode(el)\n    })\n\n    // Remove unserializable attributes\n    unserializableAttributeNames.forEach((attribute) => {\n      Array.from(element.querySelectorAll(`[${attribute}]`)).forEach((el) => {\n        el.removeAttribute(attribute)\n      })\n    })\n\n    // Rewrite elements with serialized attribute overrides\n    Array.from(element.querySelectorAll(serializedAttributesSelector)).forEach((el) => {\n      try {\n        const attributes = JSON.parse(el.getAttribute(serializedAttributesAttribute))\n        el.removeAttribute(serializedAttributesAttribute)\n        for (const name in attributes) {\n          const value = attributes[name]\n          el.setAttribute(name, value)\n        }\n      } catch (error) {}\n    })\n\n    return element.innerHTML.replace(blockCommentPattern, \"\")\n  },\n}\n\nconst deserializers = {\n  \"application/json\": function(string) {\n    return Document.fromJSONString(string)\n  },\n\n  \"text/html\": function(string) {\n    return HTMLParser.parse(string).getDocument()\n  },\n}\n\nexport const serializeToContentType = function(serializable, contentType) {\n  const serializer = serializers[contentType]\n  if (serializer) {\n    return serializer(serializable)\n  } else {\n    throw new Error(`unknown content type: ${contentType}`)\n  }\n}\n\nexport const deserializeFromContentType = function(string, contentType) {\n  const deserializer = deserializers[contentType]\n  if (deserializer) {\n    return deserializer(string)\n  } else {\n    throw new Error(`unknown content type: ${contentType}`)\n  }\n}\n"
  },
  {
    "path": "src/trix/core/utilities/index.js",
    "content": "import \"./operation\"\nimport \"./utf16_string\"\n"
  },
  {
    "path": "src/trix/core/utilities/operation.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nexport default class Operation extends BasicObject {\n  isPerforming() {\n    return this.performing === true\n  }\n\n  hasPerformed() {\n    return this.performed === true\n  }\n\n  hasSucceeded() {\n    return this.performed && this.succeeded\n  }\n\n  hasFailed() {\n    return this.performed && !this.succeeded\n  }\n\n  getPromise() {\n    if (!this.promise) {\n      this.promise = new Promise((resolve, reject) => {\n        this.performing = true\n        return this.perform((succeeded, result) => {\n          this.succeeded = succeeded\n          this.performing = false\n          this.performed = true\n\n          if (this.succeeded) {\n            resolve(result)\n          } else {\n            reject(result)\n          }\n        })\n      })\n    }\n\n    return this.promise\n  }\n\n  perform(callback) {\n    return callback(false)\n  }\n\n  release() {\n    this.promise?.cancel?.()\n    this.promise = null\n    this.performing = null\n    this.performed = null\n    this.succeeded = null\n  }\n}\n\nOperation.proxyMethod(\"getPromise().then\")\nOperation.proxyMethod(\"getPromise().catch\")\n"
  },
  {
    "path": "src/trix/core/utilities/utf16_string.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nexport default class UTF16String extends BasicObject {\n  static box(value = \"\") {\n    if (value instanceof this) {\n      return value\n    } else {\n      return this.fromUCS2String(value?.toString())\n    }\n  }\n\n  static fromUCS2String(ucs2String) {\n    return new this(ucs2String, ucs2decode(ucs2String))\n  }\n\n  static fromCodepoints(codepoints) {\n    return new this(ucs2encode(codepoints), codepoints)\n  }\n\n  constructor(ucs2String, codepoints) {\n    super(...arguments)\n    this.ucs2String = ucs2String\n    this.codepoints = codepoints\n    this.length = this.codepoints.length\n    this.ucs2Length = this.ucs2String.length\n  }\n\n  offsetToUCS2Offset(offset) {\n    return ucs2encode(this.codepoints.slice(0, Math.max(0, offset))).length\n  }\n\n  offsetFromUCS2Offset(ucs2Offset) {\n    return ucs2decode(this.ucs2String.slice(0, Math.max(0, ucs2Offset))).length\n  }\n\n  slice() {\n    return this.constructor.fromCodepoints(this.codepoints.slice(...arguments))\n  }\n\n  charAt(offset) {\n    return this.slice(offset, offset + 1)\n  }\n\n  isEqualTo(value) {\n    return this.constructor.box(value).ucs2String === this.ucs2String\n  }\n\n  toJSON() {\n    return this.ucs2String\n  }\n\n  getCacheKey() {\n    return this.ucs2String\n  }\n\n  toString() {\n    return this.ucs2String\n  }\n}\n\nconst hasArrayFrom = Array.from?.(\"\\ud83d\\udc7c\").length === 1\nconst hasStringCodePointAt = \" \".codePointAt?.(0) != null\nconst hasStringFromCodePoint = String.fromCodePoint?.(32, 128124) === \" \\ud83d\\udc7c\"\n\n// UCS-2 conversion helpers ported from Mathias Bynens' Punycode.js:\n// https://github.com/bestiejs/punycode.js#punycodeucs2\n\nlet ucs2decode, ucs2encode\n\n// Creates an array containing the numeric code points of each Unicode\n// character in the string. While JavaScript uses UCS-2 internally,\n// this function will convert a pair of surrogate halves (each of which\n// UCS-2 exposes as separate characters) into a single code point,\n// matching UTF-16.\nif (hasArrayFrom && hasStringCodePointAt) {\n  ucs2decode = (string) => Array.from(string).map((char) => char.codePointAt(0))\n} else {\n  ucs2decode = function(string) {\n    const output = []\n    let counter = 0\n    const { length } = string\n\n    while (counter < length) {\n      let value = string.charCodeAt(counter++)\n      if (0xd800 <= value && value <= 0xdbff && counter < length) {\n        // high surrogate, and there is a next character\n        const extra = string.charCodeAt(counter++)\n        if ((extra & 0xfc00) === 0xdc00) {\n          // low surrogate\n          value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000\n        } else {\n          // unmatched surrogate; only append this code unit, in case the\n          // next code unit is the high surrogate of a surrogate pair\n          counter--\n        }\n      }\n      output.push(value)\n    }\n\n    return output\n  }\n}\n\n// Creates a string based on an array of numeric code points.\nif (hasStringFromCodePoint) {\n  ucs2encode = (array) => String.fromCodePoint(...Array.from(array || []))\n} else {\n  ucs2encode = function(array) {\n    const characters = (() => {\n      const result = []\n\n      Array.from(array).forEach((value) => {\n        let output = \"\"\n        if (value > 0xffff) {\n          value -= 0x10000\n          output += String.fromCharCode(value >>> 10 & 0x3ff | 0xd800)\n          value = 0xdc00 | value & 0x3ff\n        }\n        result.push(output + String.fromCharCode(value))\n      })\n\n      return result\n    })()\n\n    return characters.join(\"\")\n  }\n}\n"
  },
  {
    "path": "src/trix/core/utilities.js",
    "content": "import \"trix/core/utilities/operation\"\nimport \"trix/core/utilities/utf16_string\"\n"
  },
  {
    "path": "src/trix/elements/index.js",
    "content": "export { default as TrixEditorElement } from \"trix_editor_element\"\nexport { default as TrixToolbarElement } from \"trix_toolbar_element\"\n"
  },
  {
    "path": "src/trix/elements/trix_editor_element.js",
    "content": "import * as config from \"trix/config\"\n\nimport {\n  findClosestElementFromNode,\n  handleEvent,\n  handleEventOnce,\n  installDefaultCSSForTagName,\n  makeElement,\n  triggerEvent,\n} from \"trix/core/helpers\"\n\nimport { attachmentSelector } from \"trix/config/attachments\"\nimport EditorController from \"trix/controllers/editor_controller\"\nimport \"trix/elements/trix_toolbar_element\"\n\nlet id = 0\n\n// Contenteditable support helpers\n\nconst autofocus = function(element) {\n  if (!document.querySelector(\":focus\")) {\n    if (element.hasAttribute(\"autofocus\") && document.querySelector(\"[autofocus]\") === element) {\n      return element.focus()\n    }\n  }\n}\n\nconst makeEditable = function(element) {\n  if (element.hasAttribute(\"contenteditable\")) {\n    return\n  }\n  element.toggleAttribute(\"contenteditable\", !element.disabled)\n  return handleEventOnce(\"focus\", {\n    onElement: element,\n    withCallback() {\n      return configureContentEditable(element)\n    },\n  })\n}\n\nconst configureContentEditable = function(element) {\n  disableObjectResizing(element)\n  return setDefaultParagraphSeparator(element)\n}\n\nconst disableObjectResizing = function(element) {\n  if (document.queryCommandSupported?.(\"enableObjectResizing\")) {\n    document.execCommand(\"enableObjectResizing\", false, false)\n    return handleEvent(\"mscontrolselect\", { onElement: element, preventDefault: true })\n  }\n}\n\nconst setDefaultParagraphSeparator = function(element) {\n  if (document.queryCommandSupported?.(\"DefaultParagraphSeparator\")) {\n    const { tagName } = config.blockAttributes.default\n    if ([ \"div\", \"p\" ].includes(tagName)) {\n      return document.execCommand(\"DefaultParagraphSeparator\", false, tagName)\n    }\n  }\n}\n\n// Accessibility helpers\n\nconst addAccessibilityRole = function(element) {\n  if (element.hasAttribute(\"role\")) {\n    return\n  }\n  return element.setAttribute(\"role\", \"textbox\")\n}\n\nconst ensureAriaLabel = function(element) {\n  if (element.hasAttribute(\"aria-label\") || element.hasAttribute(\"aria-labelledby\")) {\n    return\n  }\n\n  const update = function() {\n    const texts = Array.from(element.labels).map((label) => {\n      if (!label.contains(element)) return label.textContent\n    }).filter(text => text)\n\n    const text = texts.join(\" \")\n    if (text) {\n      return element.setAttribute(\"aria-label\", text)\n    } else {\n      return element.removeAttribute(\"aria-label\")\n    }\n  }\n  update()\n  return handleEvent(\"focus\", { onElement: element, withCallback: update })\n}\n\n// Style\n\nconst cursorTargetStyles = (function() {\n  if (config.browser.forcesObjectResizing) {\n    return {\n      display: \"inline\",\n      width: \"auto\",\n    }\n  } else {\n    return {\n      display: \"inline-block\",\n      width: \"1px\",\n    }\n  }\n})()\n\ninstallDefaultCSSForTagName(\"trix-editor\", `\\\n%t {\n    display: block;\n}\n\n%t:empty::before {\n    content: attr(placeholder);\n    color: graytext;\n    cursor: text;\n    pointer-events: none;\n    white-space: pre-line;\n}\n\n%t a[contenteditable=false] {\n    cursor: text;\n}\n\n%t img {\n    max-width: 100%;\n    height: auto;\n}\n\n%t ${attachmentSelector} figcaption textarea {\n    resize: none;\n}\n\n%t ${attachmentSelector} figcaption textarea.trix-autoresize-clone {\n    position: absolute;\n    left: -9999px;\n    max-height: 0px;\n}\n\n%t ${attachmentSelector} figcaption[data-trix-placeholder]:empty::before {\n    content: attr(data-trix-placeholder);\n    color: graytext;\n}\n\n%t [data-trix-cursor-target] {\n    display: ${cursorTargetStyles.display} !important;\n    width: ${cursorTargetStyles.width} !important;\n    padding: 0 !important;\n    margin: 0 !important;\n    border: none !important;\n}\n\n%t [data-trix-cursor-target=left] {\n    vertical-align: top !important;\n    margin-left: -1px !important;\n}\n\n%t [data-trix-cursor-target=right] {\n    vertical-align: bottom !important;\n    margin-right: -1px !important;\n}`)\n\nclass ElementInternalsDelegate {\n  value = \"\"\n  #internals\n  #formDisabled\n\n  constructor(element) {\n    this.element = element\n    this.#internals = element.attachInternals()\n    this.#formDisabled = false\n  }\n\n  connectedCallback() {\n    this.#validate()\n  }\n\n  disconnectedCallback() {\n  }\n\n  get form() {\n    return this.#internals.form\n  }\n\n  get name() {\n    return this.element.getAttribute(\"name\")\n  }\n\n  set name(value) {\n    this.element.setAttribute(\"name\", value)\n  }\n\n  get labels() {\n    return this.#internals.labels\n  }\n\n  get disabled() {\n    return this.#formDisabled || this.element.hasAttribute(\"disabled\")\n  }\n\n  set disabled(value) {\n    this.element.toggleAttribute(\"disabled\", value)\n  }\n\n  get required() {\n    return this.element.hasAttribute(\"required\")\n  }\n\n  set required(value) {\n    this.element.toggleAttribute(\"required\", value)\n    this.#validate()\n  }\n\n  get validity() {\n    return this.#internals.validity\n  }\n\n  get validationMessage() {\n    return this.#internals.validationMessage\n  }\n\n  get willValidate() {\n    return this.#internals.willValidate\n  }\n\n  formDisabledCallback(disabled) {\n    this.#formDisabled = disabled\n  }\n\n  setFormValue(value) {\n    this.value = value\n    this.#validate()\n    this.#internals.setFormValue(this.element.disabled ? undefined : this.value)\n  }\n\n  checkValidity() {\n    return this.#internals.checkValidity()\n  }\n\n  reportValidity() {\n    return this.#internals.reportValidity()\n  }\n\n  setCustomValidity(validationMessage) {\n    this.#validate(validationMessage)\n  }\n\n  #validate(customValidationMessage = \"\") {\n    const { required, value } = this.element\n    const valueMissing = required && !value\n    const customError = !!customValidationMessage\n    const input = makeElement(\"input\", { required })\n    const validationMessage = customValidationMessage || input.validationMessage\n\n    this.#internals.setValidity({ valueMissing, customError }, validationMessage)\n  }\n}\n\nclass LegacyDelegate {\n  #focusHandler\n\n  constructor(element) {\n    this.element = element\n  }\n\n  connectedCallback() {\n    this.#focusHandler = ensureAriaLabel(this.element)\n    window.addEventListener(\"reset\", this.#resetBubbled, false)\n    window.addEventListener(\"click\", this.#clickBubbled, false)\n  }\n\n  disconnectedCallback() {\n    this.#focusHandler?.destroy()\n    window.removeEventListener(\"reset\", this.#resetBubbled, false)\n    window.removeEventListener(\"click\", this.#clickBubbled, false)\n  }\n\n  get labels() {\n    const labels = []\n    if (this.element.id && this.element.ownerDocument) {\n      labels.push(...Array.from(this.element.ownerDocument.querySelectorAll(`label[for='${this.element.id}']`) || []))\n    }\n\n    const label = findClosestElementFromNode(this.element, { matchingSelector: \"label\" })\n    if (label) {\n      if ([ this.element, null ].includes(label.control)) {\n        labels.push(label)\n      }\n    }\n\n    return labels\n  }\n\n  get form() {\n    console.warn(\"This browser does not support the .form property for trix-editor elements.\")\n\n    return null\n  }\n\n  get name() {\n    console.warn(\"This browser does not support the .name property for trix-editor elements.\")\n\n    return null\n  }\n\n  set name(value) {\n    console.warn(\"This browser does not support the .name property for trix-editor elements.\")\n  }\n\n  get disabled() {\n    console.warn(\"This browser does not support the [disabled] attribute for trix-editor elements.\")\n\n    return false\n  }\n\n  set disabled(value) {\n    console.warn(\"This browser does not support the [disabled] attribute for trix-editor elements.\")\n  }\n\n  get required() {\n    console.warn(\"This browser does not support the [required] attribute for trix-editor elements.\")\n\n    return false\n  }\n\n  set required(value) {\n    console.warn(\"This browser does not support the [required] attribute for trix-editor elements.\")\n  }\n\n  get validity() {\n    console.warn(\"This browser does not support the validity property for trix-editor elements.\")\n    return null\n  }\n\n  get validationMessage() {\n    console.warn(\"This browser does not support the validationMessage property for trix-editor elements.\")\n\n    return \"\"\n  }\n\n  get willValidate() {\n    console.warn(\"This browser does not support the willValidate property for trix-editor elements.\")\n\n    return false\n  }\n\n  formDisabledCallback(value) {\n  }\n\n  setFormValue(value) {\n  }\n\n  checkValidity() {\n    console.warn(\"This browser does not support checkValidity() for trix-editor elements.\")\n\n    return true\n  }\n\n  reportValidity() {\n    console.warn(\"This browser does not support reportValidity() for trix-editor elements.\")\n\n    return true\n  }\n\n  setCustomValidity(validationMessage) {\n    console.warn(\"This browser does not support setCustomValidity(validationMessage) for trix-editor elements.\")\n  }\n\n  #resetBubbled = (event) => {\n    if (event.defaultPrevented) return\n    if (event.target !== this.element.form) return\n    this.element.reset()\n  }\n\n  #clickBubbled = (event) => {\n    if (event.defaultPrevented) return\n    if (this.element.contains(event.target)) return\n\n    const label = findClosestElementFromNode(event.target, { matchingSelector: \"label\" })\n    if (!label) return\n\n    if (!Array.from(this.labels).includes(label)) return\n\n    this.element.focus()\n  }\n}\n\nexport default class TrixEditorElement extends HTMLElement {\n  static formAssociated = \"ElementInternals\" in window\n\n  static observedAttributes = [ \"connected\" ]\n\n  #delegate\n\n  constructor() {\n    super()\n    this.willCreateInput = true\n    this.#delegate = this.constructor.formAssociated ?\n      new ElementInternalsDelegate(this) :\n      new LegacyDelegate(this)\n  }\n\n  // Properties\n\n  get trixId() {\n    if (this.hasAttribute(\"trix-id\")) {\n      return this.getAttribute(\"trix-id\")\n    } else {\n      this.setAttribute(\"trix-id\", ++id)\n      return this.trixId\n    }\n  }\n\n  get labels() {\n    return this.#delegate.labels\n  }\n\n  get disabled() {\n    const { inputElement } = this\n\n    if (inputElement) {\n      return inputElement.disabled\n    } else {\n      return this.#delegate.disabled\n    }\n  }\n\n  set disabled(value) {\n    const { inputElement } = this\n\n    if (inputElement) {\n      inputElement.disabled = value\n    }\n    this.#delegate.disabled = value\n  }\n\n  get required() {\n    return this.#delegate.required\n  }\n\n  set required(value) {\n    this.#delegate.required = value\n  }\n\n  get validity() {\n    return this.#delegate.validity\n  }\n\n  get validationMessage() {\n    return this.#delegate.validationMessage\n  }\n\n  get willValidate() {\n    return this.#delegate.willValidate\n  }\n\n  get type() {\n    return this.localName\n  }\n\n  get toolbarElement() {\n    if (this.hasAttribute(\"toolbar\")) {\n      return this.ownerDocument?.getElementById(this.getAttribute(\"toolbar\"))\n    } else if (this.parentNode) {\n      const toolbarId = `trix-toolbar-${this.trixId}`\n      this.setAttribute(\"toolbar\", toolbarId)\n      this.internalToolbar = makeElement(\"trix-toolbar\", { id: toolbarId })\n      this.parentNode.insertBefore(this.internalToolbar, this)\n      return this.internalToolbar\n    } else {\n      return undefined\n    }\n  }\n\n  get form() {\n    const { inputElement } = this\n\n    if (inputElement) {\n      return inputElement.form\n    } else {\n      return this.#delegate.form\n    }\n  }\n\n  get inputElement() {\n    if (this.hasAttribute(\"input\")) {\n      return this.ownerDocument?.getElementById(this.getAttribute(\"input\"))\n    } else {\n      return undefined\n    }\n  }\n\n  get editor() {\n    return this.editorController?.editor\n  }\n\n  get name() {\n    const { inputElement } = this\n\n    if (inputElement) {\n      return inputElement.name\n    } else {\n      return this.#delegate.name\n    }\n  }\n\n  set name(value) {\n    const { inputElement } = this\n\n    if (inputElement) {\n      inputElement.name = value\n    } else {\n      this.#delegate.name = value\n    }\n  }\n\n  get value() {\n    const { inputElement } = this\n\n    if (inputElement) {\n      return inputElement.value\n    } else {\n      return this.#delegate.value\n    }\n  }\n\n  set value(defaultValue) {\n    this.defaultValue = defaultValue\n    this.editor?.loadHTML(this.defaultValue)\n  }\n\n  // Element callbacks\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === \"connected\" && this.isConnected && oldValue != null && oldValue !== newValue) {\n      requestAnimationFrame(() => this.reconnect())\n    }\n  }\n\n  // Controller delegate methods\n\n  notify(message, data) {\n    if (this.editorController) {\n      return triggerEvent(`trix-${message}`, { onElement: this, attributes: data })\n    }\n  }\n\n  setFormValue(value) {\n    const { inputElement } = this\n\n    if (inputElement) {\n      inputElement.value = value\n    }\n    this.#delegate.setFormValue(value)\n  }\n\n  // Element lifecycle\n\n  connectedCallback() {\n    if (!this.hasAttribute(\"data-trix-internal\")) {\n      makeEditable(this)\n      addAccessibilityRole(this)\n\n      if (!this.editorController) {\n        triggerEvent(\"trix-before-initialize\", { onElement: this })\n        this.defaultValue = this.inputElement ? this.inputElement.value : this.innerHTML\n        if (!this.hasAttribute(\"input\") && this.parentNode && this.willCreateInput) {\n          const inputId = `trix-input-${this.trixId}`\n          this.setAttribute(\"input\", inputId)\n          const element = makeElement(\"input\", { type: \"hidden\", id: inputId })\n          this.parentNode.insertBefore(element, this.nextElementSibling)\n        }\n        this.editorController = new EditorController({\n          editorElement: this,\n          html: this.defaultValue\n        })\n        requestAnimationFrame(() => triggerEvent(\"trix-initialize\", { onElement: this }))\n      }\n      this.editorController.registerSelectionManager()\n      this.#delegate.connectedCallback()\n\n      this.toggleAttribute(\"connected\", true)\n      autofocus(this)\n    }\n  }\n\n  disconnectedCallback() {\n    this.editorController?.unregisterSelectionManager()\n    this.#delegate.disconnectedCallback()\n    this.toggleAttribute(\"connected\", false)\n  }\n\n  reconnect() {\n    this.removeInternalToolbar()\n    this.disconnectedCallback()\n    this.connectedCallback()\n  }\n\n  removeInternalToolbar() {\n    this.internalToolbar?.remove()\n    this.internalToolbar = null\n  }\n\n  // Form support\n\n  checkValidity() {\n    return this.#delegate.checkValidity()\n  }\n\n  reportValidity() {\n    return this.#delegate.reportValidity()\n  }\n\n  setCustomValidity(validationMessage) {\n    this.#delegate.setCustomValidity(validationMessage)\n  }\n\n  formDisabledCallback(disabled) {\n    const { inputElement } = this\n\n    if (inputElement) {\n      inputElement.disabled = disabled\n    }\n    this.toggleAttribute(\"contenteditable\", !disabled)\n    this.#delegate.formDisabledCallback(disabled)\n  }\n\n  formResetCallback() {\n    this.reset()\n  }\n\n  reset() {\n    this.value = this.defaultValue\n  }\n}\n"
  },
  {
    "path": "src/trix/elements/trix_toolbar_element.js",
    "content": "import * as config from \"trix/config\"\n\nimport { installDefaultCSSForTagName } from \"trix/core/helpers\"\n\ninstallDefaultCSSForTagName(\"trix-toolbar\", `\\\n%t {\n  display: block;\n}\n\n%t {\n  white-space: nowrap;\n}\n\n%t [data-trix-dialog] {\n  display: none;\n}\n\n%t [data-trix-dialog][data-trix-active] {\n  display: block;\n}\n\n%t [data-trix-dialog] [data-trix-validate]:invalid {\n  background-color: #ffdddd;\n}`)\n\nexport default class TrixToolbarElement extends HTMLElement {\n\n  // Element lifecycle\n\n  connectedCallback() {\n    if (this.innerHTML === \"\") {\n      this.innerHTML = config.toolbar.getDefaultHTML()\n    }\n  }\n\n  // Properties\n\n  get editorElements() {\n    if (this.id) {\n      const nodeList = this.ownerDocument?.querySelectorAll(`trix-editor[toolbar=\"${this.id}\"]`)\n\n      return Array.from(nodeList)\n    } else {\n      return []\n    }\n  }\n\n  get editorElement() {\n    const [ editorElement ] = this.editorElements\n\n    return editorElement\n  }\n}\n"
  },
  {
    "path": "src/trix/filters/attachment_gallery_filter.js",
    "content": "import Filter from \"./filter\"\n\nexport const attachmentGalleryFilter = function(snapshot) {\n  const filter = new Filter(snapshot)\n  filter.perform()\n  return filter.getSnapshot()\n}\n\n"
  },
  {
    "path": "src/trix/filters/filter.js",
    "content": "const BLOCK_ATTRIBUTE_NAME = \"attachmentGallery\"\nconst TEXT_ATTRIBUTE_NAME = \"presentation\"\nconst TEXT_ATTRIBUTE_VALUE = \"gallery\"\n\nexport default class Filter {\n  constructor(snapshot) {\n    this.document = snapshot.document\n    this.selectedRange = snapshot.selectedRange\n  }\n\n  perform() {\n    this.removeBlockAttribute()\n    return this.applyBlockAttribute()\n  }\n\n  getSnapshot() {\n    return { document: this.document, selectedRange: this.selectedRange }\n  }\n\n  // Private\n\n  removeBlockAttribute() {\n    return this.findRangesOfBlocks().map((range) => this.document = this.document.removeAttributeAtRange(BLOCK_ATTRIBUTE_NAME, range))\n  }\n\n  applyBlockAttribute() {\n    let offset = 0\n\n    this.findRangesOfPieces().forEach((range) => {\n      if (range[1] - range[0] > 1) {\n        range[0] += offset\n        range[1] += offset\n\n        if (this.document.getCharacterAtPosition(range[1]) !== \"\\n\") {\n          this.document = this.document.insertBlockBreakAtRange(range[1])\n          if (range[1] < this.selectedRange[1]) {\n            this.moveSelectedRangeForward()\n          }\n          range[1]++\n          offset++\n        }\n\n        if (range[0] !== 0) {\n          if (this.document.getCharacterAtPosition(range[0] - 1) !== \"\\n\") {\n            this.document = this.document.insertBlockBreakAtRange(range[0])\n            if (range[0] < this.selectedRange[0]) {\n              this.moveSelectedRangeForward()\n            }\n            range[0]++\n            offset++\n          }\n        }\n\n        this.document = this.document.applyBlockAttributeAtRange(BLOCK_ATTRIBUTE_NAME, true, range)\n      }\n    })\n  }\n\n  findRangesOfBlocks() {\n    return this.document.findRangesForBlockAttribute(BLOCK_ATTRIBUTE_NAME)\n  }\n\n  findRangesOfPieces() {\n    return this.document.findRangesForTextAttribute(TEXT_ATTRIBUTE_NAME, { withValue: TEXT_ATTRIBUTE_VALUE })\n  }\n\n  moveSelectedRangeForward() {\n    this.selectedRange[0] += 1\n    this.selectedRange[1] += 1\n  }\n}\n"
  },
  {
    "path": "src/trix/filters/index.js",
    "content": "export { default as Filter } from \"./filter\"\nexport { attachmentGalleryFilter } from \"./attachment_gallery_filter\"\n"
  },
  {
    "path": "src/trix/models/attachment.js",
    "content": "import * as config from \"trix/config\"\nimport TrixObject from \"trix/core/object\" // Don't override window.Object\nimport Hash from \"trix/core/collections/hash\"\nimport ImagePreloadOperation from \"trix/operations/image_preload_operation\"\n\nexport default class Attachment extends TrixObject {\n  static previewablePattern = /^image(\\/(gif|png|webp|jpe?g)|$)/\n\n  static attachmentForFile(file) {\n    const attributes = this.attributesForFile(file)\n    const attachment = new this(attributes)\n    attachment.setFile(file)\n    return attachment\n  }\n\n  static attributesForFile(file) {\n    return new Hash({\n      filename: file.name,\n      filesize: file.size,\n      contentType: file.type,\n    })\n  }\n\n  static fromJSON(attachmentJSON) {\n    return new this(attachmentJSON)\n  }\n\n  constructor(attributes = {}) {\n    super(attributes)\n    this.releaseFile = this.releaseFile.bind(this)\n    this.attributes = Hash.box(attributes)\n    this.didChangeAttributes()\n  }\n\n  setAttribute(attribute, value) {\n    this.setAttributes({ [attribute]: value })\n  }\n\n  getAttribute(attribute) {\n    return this.attributes.get(attribute)\n  }\n\n  hasAttribute(attribute) {\n    return this.attributes.has(attribute)\n  }\n\n  getAttributes() {\n    return this.attributes.toObject()\n  }\n\n  setAttributes(attributes = {}) {\n    const newAttributes = this.attributes.merge(attributes)\n    if (!this.attributes.isEqualTo(newAttributes)) {\n      this.attributes = newAttributes\n      this.didChangeAttributes()\n      this.previewDelegate?.attachmentDidChangeAttributes?.(this)\n      return this.delegate?.attachmentDidChangeAttributes?.(this)\n    }\n  }\n\n  didChangeAttributes() {\n    if (this.isPreviewable()) {\n      return this.preloadURL()\n    }\n  }\n\n  isPending() {\n    return this.file != null && !(this.getURL() || this.getHref())\n  }\n\n  isPreviewable() {\n    if (this.attributes.has(\"previewable\")) {\n      return this.attributes.get(\"previewable\")\n    } else {\n      return Attachment.previewablePattern.test(this.getContentType())\n    }\n  }\n\n  getType() {\n    if (this.hasContent()) {\n      return \"content\"\n    } else if (this.isPreviewable()) {\n      return \"preview\"\n    } else {\n      return \"file\"\n    }\n  }\n\n  getURL() {\n    return this.attributes.get(\"url\")\n  }\n\n  getHref() {\n    return this.attributes.get(\"href\")\n  }\n\n  getFilename() {\n    return this.attributes.get(\"filename\") || \"\"\n  }\n\n  getFilesize() {\n    return this.attributes.get(\"filesize\")\n  }\n\n  getFormattedFilesize() {\n    const filesize = this.attributes.get(\"filesize\")\n    if (typeof filesize === \"number\") {\n      return config.fileSize.formatter(filesize)\n    } else {\n      return \"\"\n    }\n  }\n\n  getExtension() {\n    return this.getFilename()\n      .match(/\\.(\\w+)$/)?.[1]\n      .toLowerCase()\n  }\n\n  getContentType() {\n    return this.attributes.get(\"contentType\")\n  }\n\n  hasContent() {\n    return this.attributes.has(\"content\")\n  }\n\n  getContent() {\n    return this.attributes.get(\"content\")\n  }\n\n  getWidth() {\n    return this.attributes.get(\"width\")\n  }\n\n  getHeight() {\n    return this.attributes.get(\"height\")\n  }\n\n  getFile() {\n    return this.file\n  }\n\n  setFile(file) {\n    this.file = file\n    if (this.isPreviewable()) {\n      return this.preloadFile()\n    }\n  }\n\n  releaseFile() {\n    this.releasePreloadedFile()\n    this.file = null\n  }\n\n  getUploadProgress() {\n    return this.uploadProgress != null ? this.uploadProgress : 0\n  }\n\n  setUploadProgress(value) {\n    if (this.uploadProgress !== value) {\n      this.uploadProgress = value\n      return this.uploadProgressDelegate?.attachmentDidChangeUploadProgress?.(this)\n    }\n  }\n\n  toJSON() {\n    return this.getAttributes()\n  }\n\n  getCacheKey() {\n    return [ super.getCacheKey(...arguments), this.attributes.getCacheKey(), this.getPreviewURL() ].join(\"/\")\n  }\n\n  // Previewable\n\n  getPreviewURL() {\n    return this.previewURL || this.preloadingURL\n  }\n\n  setPreviewURL(url) {\n    if (url !== this.getPreviewURL()) {\n      this.previewURL = url\n      this.previewDelegate?.attachmentDidChangeAttributes?.(this)\n      return this.delegate?.attachmentDidChangePreviewURL?.(this)\n    }\n  }\n\n  preloadURL() {\n    return this.preload(this.getURL(), this.releaseFile)\n  }\n\n  preloadFile() {\n    if (this.file) {\n      this.fileObjectURL = URL.createObjectURL(this.file)\n      return this.preload(this.fileObjectURL)\n    }\n  }\n\n  releasePreloadedFile() {\n    if (this.fileObjectURL) {\n      URL.revokeObjectURL(this.fileObjectURL)\n      this.fileObjectURL = null\n    }\n  }\n\n  preload(url, callback) {\n    if (url && url !== this.getPreviewURL()) {\n      this.preloadingURL = url\n      const operation = new ImagePreloadOperation(url)\n      return operation\n        .then(({ width, height }) => {\n          if (!this.getWidth() || !this.getHeight()) {\n            this.setAttributes({ width, height })\n          }\n          this.preloadingURL = null\n          this.setPreviewURL(url)\n          return callback?.()\n        })\n        .catch(() => {\n          this.preloadingURL = null\n          return callback?.()\n        })\n    }\n  }\n}\n"
  },
  {
    "path": "src/trix/models/attachment_manager.js",
    "content": "import ManagedAttachment from \"trix/models/managed_attachment\"\nimport BasicObject from \"trix/core/basic_object\"\n\nexport default class AttachmentManager extends BasicObject {\n  constructor(attachments = []) {\n    super(...arguments)\n    this.managedAttachments = {}\n    Array.from(attachments).forEach((attachment) => {\n      this.manageAttachment(attachment)\n    })\n  }\n\n  getAttachments() {\n    const result = []\n    for (const id in this.managedAttachments) {\n      const attachment = this.managedAttachments[id]\n      result.push(attachment)\n    }\n    return result\n  }\n\n  manageAttachment(attachment) {\n    if (!this.managedAttachments[attachment.id]) {\n      this.managedAttachments[attachment.id] = new ManagedAttachment(this, attachment)\n    }\n    return this.managedAttachments[attachment.id]\n  }\n\n  attachmentIsManaged(attachment) {\n    return attachment.id in this.managedAttachments\n  }\n\n  requestRemovalOfAttachment(attachment) {\n    if (this.attachmentIsManaged(attachment)) {\n      return this.delegate?.attachmentManagerDidRequestRemovalOfAttachment?.(attachment)\n    }\n  }\n\n  unmanageAttachment(attachment) {\n    const managedAttachment = this.managedAttachments[attachment.id]\n    delete this.managedAttachments[attachment.id]\n    return managedAttachment\n  }\n}\n"
  },
  {
    "path": "src/trix/models/attachment_piece.js",
    "content": "import { OBJECT_REPLACEMENT_CHARACTER } from \"trix/constants\"\n\nimport Attachment from \"trix/models/attachment\"\nimport Piece from \"trix/models/piece\"\n\nexport default class AttachmentPiece extends Piece {\n  static permittedAttributes = [ \"caption\", \"presentation\" ]\n\n  static fromJSON(pieceJSON) {\n    return new this(Attachment.fromJSON(pieceJSON.attachment), pieceJSON.attributes)\n  }\n\n  constructor(attachment) {\n    super(...arguments)\n    this.attachment = attachment\n    this.length = 1\n    this.ensureAttachmentExclusivelyHasAttribute(\"href\")\n    if (!this.attachment.hasContent()) {\n      this.removeProhibitedAttributes()\n    }\n  }\n\n  ensureAttachmentExclusivelyHasAttribute(attribute) {\n    if (this.hasAttribute(attribute)) {\n      if (!this.attachment.hasAttribute(attribute)) {\n        this.attachment.setAttributes(this.attributes.slice([ attribute ]))\n      }\n      this.attributes = this.attributes.remove(attribute)\n    }\n  }\n\n  removeProhibitedAttributes() {\n    const attributes = this.attributes.slice(AttachmentPiece.permittedAttributes)\n    if (!attributes.isEqualTo(this.attributes)) {\n      this.attributes = attributes\n    }\n  }\n\n  getValue() {\n    return this.attachment\n  }\n\n  isSerializable() {\n    return !this.attachment.isPending()\n  }\n\n  getCaption() {\n    return this.attributes.get(\"caption\") || \"\"\n  }\n\n  isEqualTo(piece) {\n    return super.isEqualTo(piece) && this.attachment.id === piece?.attachment?.id\n  }\n\n  toString() {\n    return OBJECT_REPLACEMENT_CHARACTER\n  }\n\n  toJSON() {\n    const json = super.toJSON(...arguments)\n    json.attachment = this.attachment\n    return json\n  }\n\n  getCacheKey() {\n    return [ super.getCacheKey(...arguments), this.attachment.getCacheKey() ].join(\"/\")\n  }\n\n  toConsole() {\n    return JSON.stringify(this.toString())\n  }\n}\n\nPiece.registerType(\"attachment\", AttachmentPiece)\n"
  },
  {
    "path": "src/trix/models/block.js",
    "content": "import TrixObject from \"trix/core/object\" // Don't override window.Object\nimport Text from \"trix/models/text\"\n\nimport {\n  arraysAreEqual,\n  getBlockConfig,\n  getListAttributeNames,\n  objectsAreEqual,\n  spliceArray,\n} from \"trix/core/helpers\"\n\nexport default class Block extends TrixObject {\n  static fromJSON(blockJSON) {\n    const text = Text.fromJSON(blockJSON.text)\n    return new this(text, blockJSON.attributes, blockJSON.htmlAttributes)\n  }\n\n  constructor(text, attributes, htmlAttributes) {\n    super(...arguments)\n    this.text = applyBlockBreakToText(text || new Text())\n    this.attributes = attributes || []\n    this.htmlAttributes = htmlAttributes || {}\n  }\n\n  isEmpty() {\n    return this.text.isBlockBreak()\n  }\n\n  isEqualTo(block) {\n    if (super.isEqualTo(block)) return true\n\n    return this.text.isEqualTo(block?.text) && arraysAreEqual(this.attributes, block?.attributes) && objectsAreEqual(this.htmlAttributes, block?.htmlAttributes)\n  }\n\n  copyWithText(text) {\n    return new Block(text, this.attributes, this.htmlAttributes)\n  }\n\n  copyWithoutText() {\n    return this.copyWithText(null)\n  }\n\n  copyWithAttributes(attributes) {\n    return new Block(this.text, attributes, this.htmlAttributes)\n  }\n\n  copyWithoutAttributes() {\n    return this.copyWithAttributes(null)\n  }\n\n  copyUsingObjectMap(objectMap) {\n    const mappedText = objectMap.find(this.text)\n    if (mappedText) {\n      return this.copyWithText(mappedText)\n    } else {\n      return this.copyWithText(this.text.copyUsingObjectMap(objectMap))\n    }\n  }\n\n  addAttribute(attribute) {\n    const attributes = this.attributes.concat(expandAttribute(attribute))\n    return this.copyWithAttributes(attributes)\n  }\n\n  addHTMLAttribute(attribute, value) {\n    const htmlAttributes = Object.assign({}, this.htmlAttributes, { [attribute]: value })\n    return new Block(this.text, this.attributes, htmlAttributes)\n  }\n\n  removeAttribute(attribute) {\n    const { listAttribute } = getBlockConfig(attribute)\n    const attributes = removeLastValue(removeLastValue(this.attributes, attribute), listAttribute)\n    return this.copyWithAttributes(attributes)\n  }\n\n  removeLastAttribute() {\n    return this.removeAttribute(this.getLastAttribute())\n  }\n\n  getLastAttribute() {\n    return getLastElement(this.attributes)\n  }\n\n  getAttributes() {\n    return this.attributes.slice(0)\n  }\n\n  getAttributeLevel() {\n    return this.attributes.length\n  }\n\n  getAttributeAtLevel(level) {\n    return this.attributes[level - 1]\n  }\n\n  hasAttribute(attributeName) {\n    return this.attributes.includes(attributeName)\n  }\n\n  hasAttributes() {\n    return this.getAttributeLevel() > 0\n  }\n\n  getLastNestableAttribute() {\n    return getLastElement(this.getNestableAttributes())\n  }\n\n  getNestableAttributes() {\n    return this.attributes.filter((attribute) => getBlockConfig(attribute).nestable)\n  }\n\n  getNestingLevel() {\n    return this.getNestableAttributes().length\n  }\n\n  decreaseNestingLevel() {\n    const attribute = this.getLastNestableAttribute()\n    if (attribute) {\n      return this.removeAttribute(attribute)\n    } else {\n      return this\n    }\n  }\n\n  increaseNestingLevel() {\n    const attribute = this.getLastNestableAttribute()\n    if (attribute) {\n      const index = this.attributes.lastIndexOf(attribute)\n      const attributes = spliceArray(this.attributes, index + 1, 0, ...expandAttribute(attribute))\n      return this.copyWithAttributes(attributes)\n    } else {\n      return this\n    }\n  }\n\n  getListItemAttributes() {\n    return this.attributes.filter((attribute) => getBlockConfig(attribute).listAttribute)\n  }\n\n  isListItem() {\n    return getBlockConfig(this.getLastAttribute())?.listAttribute\n  }\n\n  isTerminalBlock() {\n    return getBlockConfig(this.getLastAttribute())?.terminal\n  }\n\n  breaksOnReturn() {\n    return getBlockConfig(this.getLastAttribute())?.breakOnReturn\n  }\n\n  findLineBreakInDirectionFromPosition(direction, position) {\n    const string = this.toString()\n    let result\n    switch (direction) {\n      case \"forward\":\n        result = string.indexOf(\"\\n\", position)\n        break\n      case \"backward\":\n        result = string.slice(0, position).lastIndexOf(\"\\n\")\n    }\n\n    if (result !== -1) {\n      return result\n    }\n  }\n\n  contentsForInspection() {\n    return {\n      text: this.text.inspect(),\n      attributes: this.attributes,\n    }\n  }\n\n  toString() {\n    return this.text.toString()\n  }\n\n  toJSON() {\n    return {\n      text: this.text,\n      attributes: this.attributes,\n      htmlAttributes: this.htmlAttributes,\n    }\n  }\n\n  // BIDI\n\n  getDirection() {\n    return this.text.getDirection()\n  }\n\n  isRTL() {\n    return this.text.isRTL()\n  }\n\n  // Splittable\n\n  getLength() {\n    return this.text.getLength()\n  }\n\n  canBeConsolidatedWith(block) {\n    return !this.hasAttributes() && !block.hasAttributes() && this.getDirection() === block.getDirection()\n  }\n\n  consolidateWith(block) {\n    const newlineText = Text.textForStringWithAttributes(\"\\n\")\n    const text = this.getTextWithoutBlockBreak().appendText(newlineText)\n    return this.copyWithText(text.appendText(block.text))\n  }\n\n  splitAtOffset(offset) {\n    let left, right\n    if (offset === 0) {\n      left = null\n      right = this\n    } else if (offset === this.getLength()) {\n      left = this\n      right = null\n    } else {\n      left = this.copyWithText(this.text.getTextAtRange([ 0, offset ]))\n      right = this.copyWithText(this.text.getTextAtRange([ offset, this.getLength() ]))\n    }\n    return [ left, right ]\n  }\n\n  getBlockBreakPosition() {\n    return this.text.getLength() - 1\n  }\n\n  getTextWithoutBlockBreak() {\n    if (textEndsInBlockBreak(this.text)) {\n      return this.text.getTextAtRange([ 0, this.getBlockBreakPosition() ])\n    } else {\n      return this.text.copy()\n    }\n  }\n\n  // Grouping\n\n  canBeGrouped(depth) {\n    return this.attributes[depth]\n  }\n\n  canBeGroupedWith(otherBlock, depth) {\n    const otherAttributes = otherBlock.getAttributes()\n    const otherAttribute = otherAttributes[depth]\n    const attribute = this.attributes[depth]\n\n    return (\n      attribute === otherAttribute &&\n      !(getBlockConfig(attribute).group === false && !getListAttributeNames().includes(otherAttributes[depth + 1])) &&\n      (this.getDirection() === otherBlock.getDirection() || otherBlock.isEmpty())\n    )\n  }\n}\n\n// Block breaks\n\nconst applyBlockBreakToText = function(text) {\n  text = unmarkExistingInnerBlockBreaksInText(text)\n  text = addBlockBreakToText(text)\n  return text\n}\n\nconst unmarkExistingInnerBlockBreaksInText = function(text) {\n  let modified = false\n  const pieces = text.getPieces()\n\n  let innerPieces = pieces.slice(0, pieces.length - 1)\n  const lastPiece = pieces[pieces.length - 1]\n\n  if (!lastPiece) return text\n\n  innerPieces = innerPieces.map((piece) => {\n    if (piece.isBlockBreak()) {\n      modified = true\n      return unmarkBlockBreakPiece(piece)\n    } else {\n      return piece\n    }\n  })\n\n  if (modified) {\n    return new Text([ ...innerPieces, lastPiece ])\n  } else {\n    return text\n  }\n}\n\nconst blockBreakText = Text.textForStringWithAttributes(\"\\n\", { blockBreak: true })\n\nconst addBlockBreakToText = function(text) {\n  if (textEndsInBlockBreak(text)) {\n    return text\n  } else {\n    return text.appendText(blockBreakText)\n  }\n}\n\nconst textEndsInBlockBreak = function(text) {\n  const length = text.getLength()\n  if (length === 0) {\n    return false\n  }\n  const endText = text.getTextAtRange([ length - 1, length ])\n  return endText.isBlockBreak()\n}\n\nconst unmarkBlockBreakPiece = (piece) => piece.copyWithoutAttribute(\"blockBreak\")\n\n// Attributes\n\nconst expandAttribute = function(attribute) {\n  const { listAttribute } = getBlockConfig(attribute)\n  if (listAttribute) {\n    return [ listAttribute, attribute ]\n  } else {\n    return [ attribute ]\n  }\n}\n\n// Array helpers\n\nconst getLastElement = (array) => array.slice(-1)[0]\n\nconst removeLastValue = function(array, value) {\n  const index = array.lastIndexOf(value)\n  if (index === -1) {\n    return array\n  } else {\n    return spliceArray(array, index, 1)\n  }\n}\n"
  },
  {
    "path": "src/trix/models/composition.js",
    "content": "import * as config from \"trix/config\"\nimport { OBJECT_REPLACEMENT_CHARACTER } from \"trix/constants\"\n\nimport BasicObject from \"trix/core/basic_object\"\nimport Text from \"trix/models/text\"\nimport Block from \"trix/models/block\"\nimport Attachment from \"trix/models/attachment\"\nimport Document from \"trix/models/document\"\nimport HTMLParser from \"trix/models/html_parser\"\nimport LineBreakInsertion from \"trix/models/line_break_insertion\"\n\nimport {\n  arrayStartsWith,\n  extend,\n  getAllAttributeNames,\n  getBlockConfig,\n  getTextConfig,\n  normalizeRange,\n  objectsAreEqual,\n  rangeIsCollapsed,\n  rangesAreEqual,\n  summarizeArrayChange,\n} from \"trix/core/helpers\"\n\nconst PLACEHOLDER = \" \"\n\nexport default class Composition extends BasicObject {\n  constructor() {\n    super(...arguments)\n    this.document = new Document()\n    this.attachments = []\n    this.currentAttributes = {}\n    this.revision = 0\n  }\n\n  setDocument(document) {\n    if (!document.isEqualTo(this.document)) {\n      this.document = document\n      this.refreshAttachments()\n      this.revision++\n      return this.delegate?.compositionDidChangeDocument?.(document)\n    }\n  }\n\n  // Snapshots\n\n  getSnapshot() {\n    return {\n      document: this.document,\n      selectedRange: this.getSelectedRange(),\n    }\n  }\n\n  loadSnapshot({ document, selectedRange }) {\n    this.delegate?.compositionWillLoadSnapshot?.()\n    this.setDocument(document != null ? document : new Document())\n    this.setSelection(selectedRange != null ? selectedRange : [ 0, 0 ])\n    return this.delegate?.compositionDidLoadSnapshot?.()\n  }\n\n  // Responder protocol\n\n  insertText(text, { updatePosition } = { updatePosition: true }) {\n    const selectedRange = this.getSelectedRange()\n    this.setDocument(this.document.insertTextAtRange(text, selectedRange))\n\n    const startPosition = selectedRange[0]\n    const endPosition = startPosition + text.getLength()\n\n    if (updatePosition) {\n      this.setSelection(endPosition)\n    }\n    return this.notifyDelegateOfInsertionAtRange([ startPosition, endPosition ])\n  }\n\n  insertBlock(block = new Block()) {\n    const document = new Document([ block ])\n    return this.insertDocument(document)\n  }\n\n  insertDocument(document = new Document()) {\n    const selectedRange = this.getSelectedRange()\n    this.setDocument(this.document.insertDocumentAtRange(document, selectedRange))\n\n    const startPosition = selectedRange[0]\n    const endPosition = startPosition + document.getLength()\n\n    this.setSelection(endPosition)\n    return this.notifyDelegateOfInsertionAtRange([ startPosition, endPosition ])\n  }\n\n  insertString(string, options) {\n    const attributes = this.getCurrentTextAttributes()\n    const text = Text.textForStringWithAttributes(string, attributes)\n    return this.insertText(text, options)\n  }\n\n  insertBlockBreak() {\n    const selectedRange = this.getSelectedRange()\n    this.setDocument(this.document.insertBlockBreakAtRange(selectedRange))\n\n    const startPosition = selectedRange[0]\n    const endPosition = startPosition + 1\n\n    this.setSelection(endPosition)\n    return this.notifyDelegateOfInsertionAtRange([ startPosition, endPosition ])\n  }\n\n  insertLineBreak() {\n    const insertion = new LineBreakInsertion(this)\n\n    if (insertion.shouldDecreaseListLevel()) {\n      this.decreaseListLevel()\n      return this.setSelection(insertion.startPosition)\n    } else if (insertion.shouldPrependListItem()) {\n      const document = new Document([ insertion.block.copyWithoutText() ])\n      return this.insertDocument(document)\n    } else if (insertion.shouldInsertBlockBreak()) {\n      return this.insertBlockBreak()\n    } else if (insertion.shouldRemoveLastBlockAttribute()) {\n      return this.removeLastBlockAttribute()\n    } else if (insertion.shouldBreakFormattedBlock()) {\n      return this.breakFormattedBlock(insertion)\n    } else {\n      return this.insertString(\"\\n\")\n    }\n  }\n\n  insertHTML(html) {\n    const document = HTMLParser.parse(html, { purifyOptions: { SAFE_FOR_XML: true } }).getDocument()\n    const selectedRange = this.getSelectedRange()\n\n    this.setDocument(this.document.mergeDocumentAtRange(document, selectedRange))\n\n    const startPosition = selectedRange[0]\n    const endPosition = startPosition + document.getLength() - 1\n\n    this.setSelection(endPosition)\n    return this.notifyDelegateOfInsertionAtRange([ startPosition, endPosition ])\n  }\n\n  replaceHTML(html) {\n    const document = HTMLParser.parse(html).getDocument().copyUsingObjectsFromDocument(this.document)\n    const locationRange = this.getLocationRange({ strict: false })\n    const selectedRange = this.document.rangeFromLocationRange(locationRange)\n    this.setDocument(document)\n    return this.setSelection(selectedRange)\n  }\n\n  insertFile(file) {\n    return this.insertFiles([ file ])\n  }\n\n  insertFiles(files) {\n    const attachments = []\n\n    Array.from(files).forEach((file) => {\n      if (this.delegate?.compositionShouldAcceptFile(file)) {\n        const attachment = Attachment.attachmentForFile(file)\n        attachments.push(attachment)\n      }\n    })\n\n    return this.insertAttachments(attachments)\n  }\n\n  insertAttachment(attachment) {\n    return this.insertAttachments([ attachment ])\n  }\n\n  insertAttachments(attachments) {\n    let text = new Text()\n\n    Array.from(attachments).forEach((attachment) => {\n      const type = attachment.getType()\n      const presentation = config.attachments[type]?.presentation\n\n      const attributes = this.getCurrentTextAttributes()\n      if (presentation) {\n        attributes.presentation = presentation\n      }\n\n      const attachmentText = Text.textForAttachmentWithAttributes(attachment, attributes)\n      text = text.appendText(attachmentText)\n    })\n\n    return this.insertText(text)\n  }\n\n  shouldManageDeletingInDirection(direction) {\n    const locationRange = this.getLocationRange()\n    if (rangeIsCollapsed(locationRange)) {\n      if (direction === \"backward\" && locationRange[0].offset === 0) {\n        return true\n      }\n      if (this.shouldManageMovingCursorInDirection(direction)) {\n        return true\n      }\n    } else {\n      if (locationRange[0].index !== locationRange[1].index) {\n        return true\n      }\n    }\n    return false\n  }\n\n  deleteInDirection(direction, { length } = {}) {\n    let attachment, deletingIntoPreviousBlock, selectionSpansBlocks\n    const locationRange = this.getLocationRange()\n    let range = this.getSelectedRange()\n    const selectionIsCollapsed = rangeIsCollapsed(range)\n\n    if (selectionIsCollapsed) {\n      deletingIntoPreviousBlock = direction === \"backward\" && locationRange[0].offset === 0\n    } else {\n      selectionSpansBlocks = locationRange[0].index !== locationRange[1].index\n    }\n\n    if (deletingIntoPreviousBlock) {\n      if (this.canDecreaseBlockAttributeLevel()) {\n        const block = this.getBlock()\n\n        if (block.isListItem()) {\n          this.decreaseListLevel()\n        } else {\n          this.decreaseBlockAttributeLevel()\n        }\n\n        this.setSelection(range[0])\n        if (block.isEmpty()) {\n          return false\n        }\n      }\n    }\n\n    if (selectionIsCollapsed) {\n      range = this.getExpandedRangeInDirection(direction, { length })\n      if (direction === \"backward\") {\n        attachment = this.getAttachmentAtRange(range)\n      }\n    }\n\n    if (attachment) {\n      this.editAttachment(attachment)\n      return false\n    } else {\n      this.setDocument(this.document.removeTextAtRange(range))\n      this.setSelection(range[0])\n      if (deletingIntoPreviousBlock || selectionSpansBlocks) {\n        return false\n      }\n    }\n  }\n\n  moveTextFromRange(range) {\n    const [ position ] = Array.from(this.getSelectedRange())\n    this.setDocument(this.document.moveTextFromRangeToPosition(range, position))\n    return this.setSelection(position)\n  }\n\n  removeAttachment(attachment) {\n    const range = this.document.getRangeOfAttachment(attachment)\n    if (range) {\n      this.stopEditingAttachment()\n      this.setDocument(this.document.removeTextAtRange(range))\n      return this.setSelection(range[0])\n    }\n  }\n\n  removeLastBlockAttribute() {\n    const [ startPosition, endPosition ] = Array.from(this.getSelectedRange())\n    const block = this.document.getBlockAtPosition(endPosition)\n    this.removeCurrentAttribute(block.getLastAttribute())\n    return this.setSelection(startPosition)\n  }\n\n  insertPlaceholder() {\n    this.placeholderPosition = this.getPosition()\n    return this.insertString(PLACEHOLDER)\n  }\n\n  selectPlaceholder() {\n    if (this.placeholderPosition != null) {\n      this.setSelectedRange([ this.placeholderPosition, this.placeholderPosition + PLACEHOLDER.length ])\n      return this.getSelectedRange()\n    }\n  }\n\n  forgetPlaceholder() {\n    this.placeholderPosition = null\n  }\n\n  // Current attributes\n\n  hasCurrentAttribute(attributeName) {\n    const value = this.currentAttributes[attributeName]\n    return value != null && value !== false\n  }\n\n  toggleCurrentAttribute(attributeName) {\n    const value = !this.currentAttributes[attributeName]\n    if (value) {\n      return this.setCurrentAttribute(attributeName, value)\n    } else {\n      return this.removeCurrentAttribute(attributeName)\n    }\n  }\n\n  canSetCurrentAttribute(attributeName) {\n    if (getBlockConfig(attributeName)) {\n      return this.canSetCurrentBlockAttribute(attributeName)\n    } else {\n      return this.canSetCurrentTextAttribute(attributeName)\n    }\n  }\n\n  canSetCurrentTextAttribute(attributeName) {\n    const document = this.getSelectedDocument()\n    if (!document) return\n    for (const attachment of Array.from(document.getAttachments())) {\n      if (!attachment.hasContent()) {\n        return false\n      }\n    }\n    return true\n  }\n\n  canSetCurrentBlockAttribute(attributeName) {\n    const block = this.getBlock()\n    if (!block) return\n    return !block.isTerminalBlock()\n  }\n\n  setCurrentAttribute(attributeName, value) {\n    if (getBlockConfig(attributeName)) {\n      return this.setBlockAttribute(attributeName, value)\n    } else {\n      this.setTextAttribute(attributeName, value)\n      this.currentAttributes[attributeName] = value\n      return this.notifyDelegateOfCurrentAttributesChange()\n    }\n  }\n\n  setHTMLAtributeAtPosition(position, attributeName, value) {\n    const block = this.document.getBlockAtPosition(position)\n    const allowedHTMLAttributes = getBlockConfig(block.getLastAttribute())?.htmlAttributes\n\n    if (block && allowedHTMLAttributes?.includes(attributeName)) {\n      const newDocument = this.document.setHTMLAttributeAtPosition(position, attributeName, value)\n      this.setDocument(newDocument)\n    }\n  }\n\n  setTextAttribute(attributeName, value) {\n    const selectedRange = this.getSelectedRange()\n    if (!selectedRange) return\n\n    const [ startPosition, endPosition ] = Array.from(selectedRange)\n    if (startPosition === endPosition) {\n      if (attributeName === \"href\") {\n        const text = Text.textForStringWithAttributes(value, { href: value })\n        return this.insertText(text)\n      }\n    } else {\n      return this.setDocument(this.document.addAttributeAtRange(attributeName, value, selectedRange))\n    }\n  }\n\n  setBlockAttribute(attributeName, value) {\n    const selectedRange = this.getSelectedRange()\n    if (this.canSetCurrentAttribute(attributeName)) {\n      this.setDocument(this.document.applyBlockAttributeAtRange(attributeName, value, selectedRange))\n      return this.setSelection(selectedRange)\n    }\n  }\n\n  removeCurrentAttribute(attributeName) {\n    if (getBlockConfig(attributeName)) {\n      this.removeBlockAttribute(attributeName)\n      return this.updateCurrentAttributes()\n    } else {\n      this.removeTextAttribute(attributeName)\n      delete this.currentAttributes[attributeName]\n      return this.notifyDelegateOfCurrentAttributesChange()\n    }\n  }\n\n  removeTextAttribute(attributeName) {\n    const selectedRange = this.getSelectedRange()\n    if (!selectedRange) return\n    return this.setDocument(this.document.removeAttributeAtRange(attributeName, selectedRange))\n  }\n\n  removeBlockAttribute(attributeName) {\n    const selectedRange = this.getSelectedRange()\n    if (!selectedRange) return\n    return this.setDocument(this.document.removeAttributeAtRange(attributeName, selectedRange))\n  }\n\n  canDecreaseNestingLevel() {\n    return this.getBlock()?.getNestingLevel() > 0\n  }\n\n  canIncreaseNestingLevel() {\n    const block = this.getBlock()\n    if (!block) return\n    if (getBlockConfig(block.getLastNestableAttribute())?.listAttribute) {\n      const previousBlock = this.getPreviousBlock()\n      if (previousBlock) {\n        return arrayStartsWith(previousBlock.getListItemAttributes(), block.getListItemAttributes())\n      }\n    } else {\n      return block.getNestingLevel() > 0\n    }\n  }\n\n  decreaseNestingLevel() {\n    const block = this.getBlock()\n    if (!block) return\n    return this.setDocument(this.document.replaceBlock(block, block.decreaseNestingLevel()))\n  }\n\n  increaseNestingLevel() {\n    const block = this.getBlock()\n    if (!block) return\n    return this.setDocument(this.document.replaceBlock(block, block.increaseNestingLevel()))\n  }\n\n  canDecreaseBlockAttributeLevel() {\n    return this.getBlock()?.getAttributeLevel() > 0\n  }\n\n  decreaseBlockAttributeLevel() {\n    const attribute = this.getBlock()?.getLastAttribute()\n    if (attribute) {\n      return this.removeCurrentAttribute(attribute)\n    }\n  }\n\n  decreaseListLevel() {\n    let [ startPosition ] = Array.from(this.getSelectedRange())\n    const { index } = this.document.locationFromPosition(startPosition)\n    let endIndex = index\n    const attributeLevel = this.getBlock().getAttributeLevel()\n\n    let block = this.document.getBlockAtIndex(endIndex + 1)\n    while (block) {\n      if (!block.isListItem() || block.getAttributeLevel() <= attributeLevel) {\n        break\n      }\n      endIndex++\n      block = this.document.getBlockAtIndex(endIndex + 1)\n    }\n\n    startPosition = this.document.positionFromLocation({ index, offset: 0 })\n    const endPosition = this.document.positionFromLocation({ index: endIndex, offset: 0 })\n    return this.setDocument(this.document.removeLastListAttributeAtRange([ startPosition, endPosition ]))\n  }\n\n  updateCurrentAttributes() {\n    const selectedRange = this.getSelectedRange({ ignoreLock: true })\n    if (selectedRange) {\n      const currentAttributes = this.document.getCommonAttributesAtRange(selectedRange)\n\n      Array.from(getAllAttributeNames()).forEach((attributeName) => {\n        if (!currentAttributes[attributeName]) {\n          if (!this.canSetCurrentAttribute(attributeName)) {\n            currentAttributes[attributeName] = false\n          }\n        }\n      })\n\n      if (!objectsAreEqual(currentAttributes, this.currentAttributes)) {\n        this.currentAttributes = currentAttributes\n        return this.notifyDelegateOfCurrentAttributesChange()\n      }\n    }\n  }\n\n  getCurrentAttributes() {\n    return extend.call({}, this.currentAttributes)\n  }\n\n  getCurrentTextAttributes() {\n    const attributes = {}\n    for (const key in this.currentAttributes) {\n      const value = this.currentAttributes[key]\n      if (value !== false) {\n        if (getTextConfig(key)) {\n          attributes[key] = value\n        }\n      }\n    }\n    return attributes\n  }\n\n  // Selection freezing\n\n  freezeSelection() {\n    return this.setCurrentAttribute(\"frozen\", true)\n  }\n\n  thawSelection() {\n    return this.removeCurrentAttribute(\"frozen\")\n  }\n\n  hasFrozenSelection() {\n    return this.hasCurrentAttribute(\"frozen\")\n  }\n\n  setSelection(selectedRange) {\n    const locationRange = this.document.locationRangeFromRange(selectedRange)\n    return this.delegate?.compositionDidRequestChangingSelectionToLocationRange(locationRange)\n  }\n\n  getSelectedRange() {\n    const locationRange = this.getLocationRange()\n    if (locationRange) {\n      return this.document.rangeFromLocationRange(locationRange)\n    }\n  }\n\n  setSelectedRange(selectedRange) {\n    const locationRange = this.document.locationRangeFromRange(selectedRange)\n    return this.getSelectionManager().setLocationRange(locationRange)\n  }\n\n  getPosition() {\n    const locationRange = this.getLocationRange()\n    if (locationRange) {\n      return this.document.positionFromLocation(locationRange[0])\n    }\n  }\n\n  getLocationRange(options) {\n    if (this.targetLocationRange) {\n      return this.targetLocationRange\n    } else {\n      return this.getSelectionManager().getLocationRange(options) || normalizeRange({ index: 0, offset: 0 })\n    }\n  }\n\n  withTargetLocationRange(locationRange, fn) {\n    let result\n    this.targetLocationRange = locationRange\n    try {\n      result = fn()\n    } finally {\n      this.targetLocationRange = null\n    }\n    return result\n  }\n\n  withTargetRange(range, fn) {\n    const locationRange = this.document.locationRangeFromRange(range)\n    return this.withTargetLocationRange(locationRange, fn)\n  }\n\n  withTargetDOMRange(domRange, fn) {\n    const locationRange = this.createLocationRangeFromDOMRange(domRange, { strict: false })\n    return this.withTargetLocationRange(locationRange, fn)\n  }\n\n  getExpandedRangeInDirection(direction, { length } = {}) {\n    let [ startPosition, endPosition ] = Array.from(this.getSelectedRange())\n    if (direction === \"backward\") {\n      if (length) {\n        startPosition -= length\n      } else {\n        startPosition = this.translateUTF16PositionFromOffset(startPosition, -1)\n      }\n    } else {\n      if (length) {\n        endPosition += length\n      } else {\n        endPosition = this.translateUTF16PositionFromOffset(endPosition, 1)\n      }\n    }\n    return normalizeRange([ startPosition, endPosition ])\n  }\n\n  shouldManageMovingCursorInDirection(direction) {\n    if (this.editingAttachment) {\n      return true\n    }\n    const range = this.getExpandedRangeInDirection(direction)\n    return this.getAttachmentAtRange(range) != null\n  }\n\n  moveCursorInDirection(direction) {\n    let canEditAttachment, range\n    if (this.editingAttachment) {\n      range = this.document.getRangeOfAttachment(this.editingAttachment)\n    } else {\n      const selectedRange = this.getSelectedRange()\n      range = this.getExpandedRangeInDirection(direction)\n      canEditAttachment = !rangesAreEqual(selectedRange, range)\n    }\n\n    if (direction === \"backward\") {\n      this.setSelectedRange(range[0])\n    } else {\n      this.setSelectedRange(range[1])\n    }\n\n    if (canEditAttachment) {\n      const attachment = this.getAttachmentAtRange(range)\n      if (attachment) {\n        return this.editAttachment(attachment)\n      }\n    }\n  }\n\n  expandSelectionInDirection(direction, { length } = {}) {\n    const range = this.getExpandedRangeInDirection(direction, { length })\n    return this.setSelectedRange(range)\n  }\n\n  expandSelectionForEditing() {\n    if (this.hasCurrentAttribute(\"href\")) {\n      return this.expandSelectionAroundCommonAttribute(\"href\")\n    }\n  }\n\n  expandSelectionAroundCommonAttribute(attributeName) {\n    const position = this.getPosition()\n    const range = this.document.getRangeOfCommonAttributeAtPosition(attributeName, position)\n    return this.setSelectedRange(range)\n  }\n\n  selectionContainsAttachments() {\n    return this.getSelectedAttachments()?.length > 0\n  }\n\n  selectionIsInCursorTarget() {\n    return this.editingAttachment || this.positionIsCursorTarget(this.getPosition())\n  }\n\n  positionIsCursorTarget(position) {\n    const location = this.document.locationFromPosition(position)\n    if (location) {\n      return this.locationIsCursorTarget(location)\n    }\n  }\n\n  positionIsBlockBreak(position) {\n    return this.document.getPieceAtPosition(position)?.isBlockBreak()\n  }\n\n  getSelectedDocument() {\n    const selectedRange = this.getSelectedRange()\n    if (selectedRange) {\n      return this.document.getDocumentAtRange(selectedRange)\n    }\n  }\n\n  getSelectedAttachments() {\n    return this.getSelectedDocument()?.getAttachments()\n  }\n\n  // Attachments\n\n  getAttachments() {\n    return this.attachments.slice(0)\n  }\n\n  refreshAttachments() {\n    const attachments = this.document.getAttachments()\n    const { added, removed } = summarizeArrayChange(this.attachments, attachments)\n    this.attachments = attachments\n\n    Array.from(removed).forEach((attachment) => {\n      attachment.delegate = null\n      this.delegate?.compositionDidRemoveAttachment?.(attachment)\n    })\n\n    return (() => {\n      const result = []\n\n      Array.from(added).forEach((attachment) => {\n        attachment.delegate = this\n        result.push(this.delegate?.compositionDidAddAttachment?.(attachment))\n      })\n\n      return result\n    })()\n  }\n\n  // Attachment delegate\n\n  attachmentDidChangeAttributes(attachment) {\n    this.revision++\n    return this.delegate?.compositionDidEditAttachment?.(attachment)\n  }\n\n  attachmentDidChangePreviewURL(attachment) {\n    this.revision++\n    return this.delegate?.compositionDidChangeAttachmentPreviewURL?.(attachment)\n  }\n\n  // Attachment editing\n\n  editAttachment(attachment, options) {\n    if (attachment === this.editingAttachment) return\n    this.stopEditingAttachment()\n    this.editingAttachment = attachment\n    return this.delegate?.compositionDidStartEditingAttachment?.(this.editingAttachment, options)\n  }\n\n  stopEditingAttachment() {\n    if (!this.editingAttachment) return\n    this.delegate?.compositionDidStopEditingAttachment?.(this.editingAttachment)\n    this.editingAttachment = null\n  }\n\n  updateAttributesForAttachment(attributes, attachment) {\n    return this.setDocument(this.document.updateAttributesForAttachment(attributes, attachment))\n  }\n\n  removeAttributeForAttachment(attribute, attachment) {\n    return this.setDocument(this.document.removeAttributeForAttachment(attribute, attachment))\n  }\n\n  // Private\n\n  breakFormattedBlock(insertion) {\n    let { document } = insertion\n    const { block } = insertion\n    let position = insertion.startPosition\n    let range = [ position - 1, position ]\n\n    if (block.getBlockBreakPosition() === insertion.startLocation.offset) {\n      if (block.breaksOnReturn() && insertion.nextCharacter === \"\\n\") {\n        position += 1\n      } else {\n        document = document.removeTextAtRange(range)\n      }\n      range = [ position, position ]\n    } else if (insertion.nextCharacter === \"\\n\") {\n      if (insertion.previousCharacter === \"\\n\") {\n        range = [ position - 1, position + 1 ]\n      } else {\n        range = [ position, position + 1 ]\n        position += 1\n      }\n    } else if (insertion.startLocation.offset - 1 !== 0) {\n      position += 1\n    }\n\n    const newDocument = new Document([ block.removeLastAttribute().copyWithoutText() ])\n    this.setDocument(document.insertDocumentAtRange(newDocument, range))\n    return this.setSelection(position)\n  }\n\n  getPreviousBlock() {\n    const locationRange = this.getLocationRange()\n    if (locationRange) {\n      const { index } = locationRange[0]\n      if (index > 0) {\n        return this.document.getBlockAtIndex(index - 1)\n      }\n    }\n  }\n\n  getBlock() {\n    const locationRange = this.getLocationRange()\n    if (locationRange) {\n      return this.document.getBlockAtIndex(locationRange[0].index)\n    }\n  }\n\n  getAttachmentAtRange(range) {\n    const document = this.document.getDocumentAtRange(range)\n    if (document.toString() === `${OBJECT_REPLACEMENT_CHARACTER}\\n`) {\n      return document.getAttachments()[0]\n    }\n  }\n\n  notifyDelegateOfCurrentAttributesChange() {\n    return this.delegate?.compositionDidChangeCurrentAttributes?.(this.currentAttributes)\n  }\n\n  notifyDelegateOfInsertionAtRange(range) {\n    return this.delegate?.compositionDidPerformInsertionAtRange?.(range)\n  }\n\n  translateUTF16PositionFromOffset(position, offset) {\n    const utf16string = this.document.toUTF16String()\n    const utf16position = utf16string.offsetFromUCS2Offset(position)\n    return utf16string.offsetToUCS2Offset(utf16position + offset)\n  }\n}\n\nComposition.proxyMethod(\"getSelectionManager().getPointRange\")\nComposition.proxyMethod(\"getSelectionManager().setLocationRangeFromPointRange\")\nComposition.proxyMethod(\"getSelectionManager().createLocationRangeFromDOMRange\")\nComposition.proxyMethod(\"getSelectionManager().locationIsCursorTarget\")\nComposition.proxyMethod(\"getSelectionManager().selectionIsExpanded\")\nComposition.proxyMethod(\"delegate?.getSelectionManager\")\n"
  },
  {
    "path": "src/trix/models/document.js",
    "content": "import * as config from \"trix/config\"\nimport TrixObject from \"trix/core/object\" // Don't override window.Object\n\nimport Text from \"trix/models/text\"\nimport Block from \"trix/models/block\"\nimport SplittableList from \"trix/models/splittable_list\"\nimport Hash from \"trix/core/collections/hash\"\nimport ObjectMap from \"trix/core/collections/object_map\"\n\nimport { arraysAreEqual, getBlockConfig, normalizeRange, rangeIsCollapsed } from \"trix/core/helpers\"\n\nexport default class Document extends TrixObject {\n  static fromJSON(documentJSON) {\n    const blocks = Array.from(documentJSON).map((blockJSON) => Block.fromJSON(blockJSON))\n    return new this(blocks)\n  }\n\n  static fromString(string, textAttributes) {\n    const text = Text.textForStringWithAttributes(string, textAttributes)\n    return new this([ new Block(text) ])\n  }\n\n  constructor(blocks = []) {\n    super(...arguments)\n    if (blocks.length === 0) {\n      blocks = [ new Block() ]\n    }\n    this.blockList = SplittableList.box(blocks)\n  }\n\n  isEmpty() {\n    const block = this.getBlockAtIndex(0)\n    return this.blockList.length === 1 && block.isEmpty() && !block.hasAttributes()\n  }\n\n  copy(options = {}) {\n    const blocks = options.consolidateBlocks ? this.blockList.consolidate().toArray() : this.blockList.toArray()\n\n    return new this.constructor(blocks)\n  }\n\n  copyUsingObjectsFromDocument(sourceDocument) {\n    const objectMap = new ObjectMap(sourceDocument.getObjects())\n    return this.copyUsingObjectMap(objectMap)\n  }\n\n  copyUsingObjectMap(objectMap) {\n    const blocks = this.getBlocks().map((block) => {\n      const mappedBlock = objectMap.find(block)\n      return mappedBlock || block.copyUsingObjectMap(objectMap)\n    })\n    return new this.constructor(blocks)\n  }\n\n  copyWithBaseBlockAttributes(blockAttributes = []) {\n    const blocks = this.getBlocks().map((block) => {\n      const attributes = blockAttributes.concat(block.getAttributes())\n      return block.copyWithAttributes(attributes)\n    })\n\n    return new this.constructor(blocks)\n  }\n\n  replaceBlock(oldBlock, newBlock) {\n    const index = this.blockList.indexOf(oldBlock)\n    if (index === -1) {\n      return this\n    }\n    return new this.constructor(this.blockList.replaceObjectAtIndex(newBlock, index))\n  }\n\n  insertDocumentAtRange(document, range) {\n    const { blockList } = document\n    range = normalizeRange(range)\n    let [ position ] = range\n    const { index, offset } = this.locationFromPosition(position)\n\n    let result = this\n    const block = this.getBlockAtPosition(position)\n\n    if (rangeIsCollapsed(range) && block.isEmpty() && !block.hasAttributes()) {\n      result = new this.constructor(result.blockList.removeObjectAtIndex(index))\n    } else if (block.getBlockBreakPosition() === offset) {\n      position++\n    }\n\n    result = result.removeTextAtRange(range)\n    return new this.constructor(result.blockList.insertSplittableListAtPosition(blockList, position))\n  }\n\n  mergeDocumentAtRange(document, range) {\n    let formattedDocument, result\n    range = normalizeRange(range)\n    const [ startPosition ] = range\n    const startLocation = this.locationFromPosition(startPosition)\n    const blockAttributes = this.getBlockAtIndex(startLocation.index).getAttributes()\n    const baseBlockAttributes = document.getBaseBlockAttributes()\n    const trailingBlockAttributes = blockAttributes.slice(-baseBlockAttributes.length)\n\n    if (arraysAreEqual(baseBlockAttributes, trailingBlockAttributes)) {\n      const leadingBlockAttributes = blockAttributes.slice(0, -baseBlockAttributes.length)\n      formattedDocument = document.copyWithBaseBlockAttributes(leadingBlockAttributes)\n    } else {\n      formattedDocument = document.copy({ consolidateBlocks: true }).copyWithBaseBlockAttributes(blockAttributes)\n    }\n\n    const blockCount = formattedDocument.getBlockCount()\n    const firstBlock = formattedDocument.getBlockAtIndex(0)\n\n    if (arraysAreEqual(blockAttributes, firstBlock.getAttributes())) {\n      const firstText = firstBlock.getTextWithoutBlockBreak()\n      result = this.insertTextAtRange(firstText, range)\n\n      if (blockCount > 1) {\n        formattedDocument = new this.constructor(formattedDocument.getBlocks().slice(1))\n        const position = startPosition + firstText.getLength()\n        result = result.insertDocumentAtRange(formattedDocument, position)\n      }\n    } else {\n      result = this.insertDocumentAtRange(formattedDocument, range)\n    }\n\n    return result\n  }\n\n  insertTextAtRange(text, range) {\n    range = normalizeRange(range)\n    const [ startPosition ] = range\n    const { index, offset } = this.locationFromPosition(startPosition)\n\n    const document = this.removeTextAtRange(range)\n    return new this.constructor(\n      document.blockList.editObjectAtIndex(index, (block) =>\n        block.copyWithText(block.text.insertTextAtPosition(text, offset))\n      )\n    )\n  }\n\n  removeTextAtRange(range) {\n    let blocks\n    range = normalizeRange(range)\n    const [ leftPosition, rightPosition ] = range\n    if (rangeIsCollapsed(range)) {\n      return this\n    }\n    const [ leftLocation, rightLocation ] = Array.from(this.locationRangeFromRange(range))\n\n    const leftIndex = leftLocation.index\n    const leftOffset = leftLocation.offset\n    const leftBlock = this.getBlockAtIndex(leftIndex)\n\n    const rightIndex = rightLocation.index\n    const rightOffset = rightLocation.offset\n    const rightBlock = this.getBlockAtIndex(rightIndex)\n\n    const removeRightNewline =\n      rightPosition - leftPosition === 1 &&\n      leftBlock.getBlockBreakPosition() === leftOffset &&\n      rightBlock.getBlockBreakPosition() !== rightOffset &&\n      rightBlock.text.getStringAtPosition(rightOffset) === \"\\n\"\n\n    if (removeRightNewline) {\n      blocks = this.blockList.editObjectAtIndex(rightIndex, (block) =>\n        block.copyWithText(block.text.removeTextAtRange([ rightOffset, rightOffset + 1 ]))\n      )\n    } else {\n      let block\n      const leftText = leftBlock.text.getTextAtRange([ 0, leftOffset ])\n      const rightText = rightBlock.text.getTextAtRange([ rightOffset, rightBlock.getLength() ])\n      const text = leftText.appendText(rightText)\n\n      const removingLeftBlock = leftIndex !== rightIndex && leftOffset === 0\n      const useRightBlock = removingLeftBlock && leftBlock.getAttributeLevel() >= rightBlock.getAttributeLevel()\n\n      if (useRightBlock) {\n        block = rightBlock.copyWithText(text)\n      } else {\n        block = leftBlock.copyWithText(text)\n      }\n\n      const affectedBlockCount = rightIndex + 1 - leftIndex\n      blocks = this.blockList.splice(leftIndex, affectedBlockCount, block)\n    }\n\n    return new this.constructor(blocks)\n  }\n\n  moveTextFromRangeToPosition(range, position) {\n    let text\n    range = normalizeRange(range)\n    const [ startPosition, endPosition ] = range\n    if (startPosition <= position && position <= endPosition) {\n      return this\n    }\n\n    let document = this.getDocumentAtRange(range)\n    let result = this.removeTextAtRange(range)\n\n    const movingRightward = startPosition < position\n    if (movingRightward) {\n      position -= document.getLength()\n    }\n\n    const [ firstBlock, ...blocks ] = document.getBlocks()\n    if (blocks.length === 0) {\n      text = firstBlock.getTextWithoutBlockBreak()\n      if (movingRightward) {\n        position += 1\n      }\n    } else {\n      text = firstBlock.text\n    }\n\n    result = result.insertTextAtRange(text, position)\n    if (blocks.length === 0) {\n      return result\n    }\n\n    document = new this.constructor(blocks)\n    position += text.getLength()\n\n    return result.insertDocumentAtRange(document, position)\n  }\n\n  addAttributeAtRange(attribute, value, range) {\n    let { blockList } = this\n    this.eachBlockAtRange(\n      range,\n      (block, textRange, index) =>\n        blockList = blockList.editObjectAtIndex(index, function() {\n          if (getBlockConfig(attribute)) {\n            return block.addAttribute(attribute, value)\n          } else {\n            if (textRange[0] === textRange[1]) {\n              return block\n            } else {\n              return block.copyWithText(block.text.addAttributeAtRange(attribute, value, textRange))\n            }\n          }\n        })\n    )\n    return new this.constructor(blockList)\n  }\n\n  addAttribute(attribute, value) {\n    let { blockList } = this\n    this.eachBlock(\n      (block, index) => blockList = blockList.editObjectAtIndex(index, () => block.addAttribute(attribute, value))\n    )\n    return new this.constructor(blockList)\n  }\n\n  removeAttributeAtRange(attribute, range) {\n    let { blockList } = this\n    this.eachBlockAtRange(range, function(block, textRange, index) {\n      if (getBlockConfig(attribute)) {\n        blockList = blockList.editObjectAtIndex(index, () => block.removeAttribute(attribute))\n      } else if (textRange[0] !== textRange[1]) {\n        blockList = blockList.editObjectAtIndex(index, () =>\n          block.copyWithText(block.text.removeAttributeAtRange(attribute, textRange))\n        )\n      }\n    })\n    return new this.constructor(blockList)\n  }\n\n  updateAttributesForAttachment(attributes, attachment) {\n    const range = this.getRangeOfAttachment(attachment)\n    const [ startPosition ] = Array.from(range)\n    const { index } = this.locationFromPosition(startPosition)\n    const text = this.getTextAtIndex(index)\n\n    return new this.constructor(\n      this.blockList.editObjectAtIndex(index, (block) =>\n        block.copyWithText(text.updateAttributesForAttachment(attributes, attachment))\n      )\n    )\n  }\n\n  removeAttributeForAttachment(attribute, attachment) {\n    const range = this.getRangeOfAttachment(attachment)\n    return this.removeAttributeAtRange(attribute, range)\n  }\n\n  setHTMLAttributeAtPosition(position, name, value) {\n    const block = this.getBlockAtPosition(position)\n    const updatedBlock = block.addHTMLAttribute(name, value)\n    return this.replaceBlock(block, updatedBlock)\n  }\n\n  insertBlockBreakAtRange(range) {\n    let blocks\n    range = normalizeRange(range)\n    const [ startPosition ] = range\n    const { offset } = this.locationFromPosition(startPosition)\n\n    const document = this.removeTextAtRange(range)\n    if (offset === 0) {\n      blocks = [ new Block() ]\n    }\n    return new this.constructor(\n      document.blockList.insertSplittableListAtPosition(new SplittableList(blocks), startPosition)\n    )\n  }\n\n  applyBlockAttributeAtRange(attributeName, value, range) {\n    const expanded = this.expandRangeToLineBreaksAndSplitBlocks(range)\n    let document = expanded.document\n    range = expanded.range\n    const blockConfig = getBlockConfig(attributeName)\n\n    if (blockConfig.listAttribute) {\n      document = document.removeLastListAttributeAtRange(range, { exceptAttributeName: attributeName })\n      const converted = document.convertLineBreaksToBlockBreaksInRange(range)\n      document = converted.document\n      range = converted.range\n    } else if (blockConfig.exclusive) {\n      document = document.removeBlockAttributesAtRange(range)\n    } else if (blockConfig.terminal) {\n      document = document.removeLastTerminalAttributeAtRange(range)\n    } else {\n      document = document.consolidateBlocksAtRange(range)\n    }\n\n    return document.addAttributeAtRange(attributeName, value, range)\n  }\n\n  removeLastListAttributeAtRange(range, options = {}) {\n    let { blockList } = this\n    this.eachBlockAtRange(range, function(block, textRange, index) {\n      const lastAttributeName = block.getLastAttribute()\n      if (!lastAttributeName) {\n        return\n      }\n      if (!getBlockConfig(lastAttributeName).listAttribute) {\n        return\n      }\n      if (lastAttributeName === options.exceptAttributeName) {\n        return\n      }\n      blockList = blockList.editObjectAtIndex(index, () => block.removeAttribute(lastAttributeName))\n    })\n    return new this.constructor(blockList)\n  }\n\n  removeLastTerminalAttributeAtRange(range) {\n    let { blockList } = this\n    this.eachBlockAtRange(range, function(block, textRange, index) {\n      const lastAttributeName = block.getLastAttribute()\n      if (!lastAttributeName) {\n        return\n      }\n      if (!getBlockConfig(lastAttributeName).terminal) {\n        return\n      }\n      blockList = blockList.editObjectAtIndex(index, () => block.removeAttribute(lastAttributeName))\n    })\n    return new this.constructor(blockList)\n  }\n\n  removeBlockAttributesAtRange(range) {\n    let { blockList } = this\n    this.eachBlockAtRange(range, function(block, textRange, index) {\n      if (block.hasAttributes()) {\n        blockList = blockList.editObjectAtIndex(index, () => block.copyWithoutAttributes())\n      }\n    })\n    return new this.constructor(blockList)\n  }\n\n  expandRangeToLineBreaksAndSplitBlocks(range) {\n    let position\n    range = normalizeRange(range)\n    let [ startPosition, endPosition ] = range\n    const startLocation = this.locationFromPosition(startPosition)\n    const endLocation = this.locationFromPosition(endPosition)\n    let document = this\n\n    const startBlock = document.getBlockAtIndex(startLocation.index)\n    startLocation.offset = startBlock.findLineBreakInDirectionFromPosition(\"backward\", startLocation.offset)\n    if (startLocation.offset != null) {\n      position = document.positionFromLocation(startLocation)\n      document = document.insertBlockBreakAtRange([ position, position + 1 ])\n      endLocation.index += 1\n      endLocation.offset -= document.getBlockAtIndex(startLocation.index).getLength()\n      startLocation.index += 1\n    }\n    startLocation.offset = 0\n\n    if (endLocation.offset === 0 && endLocation.index > startLocation.index) {\n      endLocation.index -= 1\n      endLocation.offset = document.getBlockAtIndex(endLocation.index).getBlockBreakPosition()\n    } else {\n      const endBlock = document.getBlockAtIndex(endLocation.index)\n      if (endBlock.text.getStringAtRange([ endLocation.offset - 1, endLocation.offset ]) === \"\\n\") {\n        endLocation.offset -= 1\n      } else {\n        endLocation.offset = endBlock.findLineBreakInDirectionFromPosition(\"forward\", endLocation.offset)\n      }\n      if (endLocation.offset !== endBlock.getBlockBreakPosition()) {\n        position = document.positionFromLocation(endLocation)\n        document = document.insertBlockBreakAtRange([ position, position + 1 ])\n      }\n    }\n\n    startPosition = document.positionFromLocation(startLocation)\n    endPosition = document.positionFromLocation(endLocation)\n    range = normalizeRange([ startPosition, endPosition ])\n\n    return { document, range }\n  }\n\n  convertLineBreaksToBlockBreaksInRange(range) {\n    range = normalizeRange(range)\n    let [ position ] = range\n    const string = this.getStringAtRange(range).slice(0, -1)\n    let document = this\n\n    string.replace(/.*?\\n/g, function(match) {\n      position += match.length\n      document = document.insertBlockBreakAtRange([ position - 1, position ])\n    })\n\n    return { document, range }\n  }\n\n  consolidateBlocksAtRange(range) {\n    range = normalizeRange(range)\n    const [ startPosition, endPosition ] = range\n    const startIndex = this.locationFromPosition(startPosition).index\n    const endIndex = this.locationFromPosition(endPosition).index\n    return new this.constructor(this.blockList.consolidateFromIndexToIndex(startIndex, endIndex))\n  }\n\n  getDocumentAtRange(range) {\n    range = normalizeRange(range)\n    const blocks = this.blockList.getSplittableListInRange(range).toArray()\n    return new this.constructor(blocks)\n  }\n\n  getStringAtRange(range) {\n    let endIndex\n    const array = range = normalizeRange(range),\n      endPosition = array[array.length - 1]\n    if (endPosition !== this.getLength()) {\n      endIndex = -1\n    }\n    return this.getDocumentAtRange(range).toString().slice(0, endIndex)\n  }\n\n  getBlockAtIndex(index) {\n    return this.blockList.getObjectAtIndex(index)\n  }\n\n  getBlockAtPosition(position) {\n    const { index } = this.locationFromPosition(position)\n    return this.getBlockAtIndex(index)\n  }\n\n  getTextAtIndex(index) {\n    return this.getBlockAtIndex(index)?.text\n  }\n\n  getTextAtPosition(position) {\n    const { index } = this.locationFromPosition(position)\n    return this.getTextAtIndex(index)\n  }\n\n  getPieceAtPosition(position) {\n    const { index, offset } = this.locationFromPosition(position)\n    return this.getTextAtIndex(index).getPieceAtPosition(offset)\n  }\n\n  getCharacterAtPosition(position) {\n    const { index, offset } = this.locationFromPosition(position)\n    return this.getTextAtIndex(index).getStringAtRange([ offset, offset + 1 ])\n  }\n\n  getLength() {\n    return this.blockList.getEndPosition()\n  }\n\n  getBlocks() {\n    return this.blockList.toArray()\n  }\n\n  getBlockCount() {\n    return this.blockList.length\n  }\n\n  getEditCount() {\n    return this.editCount\n  }\n\n  eachBlock(callback) {\n    return this.blockList.eachObject(callback)\n  }\n\n  eachBlockAtRange(range, callback) {\n    let block, textRange\n    range = normalizeRange(range)\n    const [ startPosition, endPosition ] = range\n    const startLocation = this.locationFromPosition(startPosition)\n    const endLocation = this.locationFromPosition(endPosition)\n\n    if (startLocation.index === endLocation.index) {\n      block = this.getBlockAtIndex(startLocation.index)\n      textRange = [ startLocation.offset, endLocation.offset ]\n      return callback(block, textRange, startLocation.index)\n    } else {\n      for (let index = startLocation.index; index <= endLocation.index; index++) {\n        block = this.getBlockAtIndex(index)\n        if (block) {\n          switch (index) {\n            case startLocation.index:\n              textRange = [ startLocation.offset, block.text.getLength() ]\n              break\n            case endLocation.index:\n              textRange = [ 0, endLocation.offset ]\n              break\n            default:\n              textRange = [ 0, block.text.getLength() ]\n          }\n          callback(block, textRange, index)\n        }\n      }\n    }\n  }\n\n  getCommonAttributesAtRange(range) {\n    range = normalizeRange(range)\n    const [ startPosition ] = range\n    if (rangeIsCollapsed(range)) {\n      return this.getCommonAttributesAtPosition(startPosition)\n    } else {\n      const textAttributes = []\n      const blockAttributes = []\n\n      this.eachBlockAtRange(range, function(block, textRange) {\n        if (textRange[0] !== textRange[1]) {\n          textAttributes.push(block.text.getCommonAttributesAtRange(textRange))\n          return blockAttributes.push(attributesForBlock(block))\n        }\n      })\n\n      return Hash.fromCommonAttributesOfObjects(textAttributes)\n        .merge(Hash.fromCommonAttributesOfObjects(blockAttributes))\n        .toObject()\n    }\n  }\n\n  getCommonAttributesAtPosition(position) {\n    let key, value\n    const { index, offset } = this.locationFromPosition(position)\n    const block = this.getBlockAtIndex(index)\n    if (!block) {\n      return {}\n    }\n\n    const commonAttributes = attributesForBlock(block)\n    const attributes = block.text.getAttributesAtPosition(offset)\n    const attributesLeft = block.text.getAttributesAtPosition(offset - 1)\n    const inheritableAttributes = Object.keys(config.textAttributes).filter((key) => {\n      return config.textAttributes[key].inheritable\n    })\n\n    for (key in attributesLeft) {\n      value = attributesLeft[key]\n      if (value === attributes[key] || inheritableAttributes.includes(key)) {\n        commonAttributes[key] = value\n      }\n    }\n\n    return commonAttributes\n  }\n\n  getRangeOfCommonAttributeAtPosition(attributeName, position) {\n    const { index, offset } = this.locationFromPosition(position)\n    const text = this.getTextAtIndex(index)\n    const [ startOffset, endOffset ] = Array.from(text.getExpandedRangeForAttributeAtOffset(attributeName, offset))\n\n    const start = this.positionFromLocation({ index, offset: startOffset })\n    const end = this.positionFromLocation({ index, offset: endOffset })\n    return normalizeRange([ start, end ])\n  }\n\n  getBaseBlockAttributes() {\n    let baseBlockAttributes = this.getBlockAtIndex(0).getAttributes()\n\n    for (let blockIndex = 1; blockIndex < this.getBlockCount(); blockIndex++) {\n      const blockAttributes = this.getBlockAtIndex(blockIndex).getAttributes()\n      const lastAttributeIndex = Math.min(baseBlockAttributes.length, blockAttributes.length)\n\n      baseBlockAttributes = (() => {\n        const result = []\n        for (let index = 0; index < lastAttributeIndex; index++) {\n          if (blockAttributes[index] !== baseBlockAttributes[index]) {\n            break\n          }\n          result.push(blockAttributes[index])\n        }\n        return result\n      })()\n    }\n\n    return baseBlockAttributes\n  }\n\n  getAttachmentById(attachmentId) {\n    for (const attachment of this.getAttachments()) {\n      if (attachment.id === attachmentId) {\n        return attachment\n      }\n    }\n  }\n\n  getAttachmentPieces() {\n    let attachmentPieces = []\n    this.blockList.eachObject(({ text }) => attachmentPieces = attachmentPieces.concat(text.getAttachmentPieces()))\n    return attachmentPieces\n  }\n\n  getAttachments() {\n    return this.getAttachmentPieces().map((piece) => piece.attachment)\n  }\n\n  getRangeOfAttachment(attachment) {\n    let position = 0\n    const iterable = this.blockList.toArray()\n    for (let index = 0; index < iterable.length; index++) {\n      const { text } = iterable[index]\n      const textRange = text.getRangeOfAttachment(attachment)\n      if (textRange) {\n        return normalizeRange([ position + textRange[0], position + textRange[1] ])\n      }\n      position += text.getLength()\n    }\n  }\n\n  getLocationRangeOfAttachment(attachment) {\n    const range = this.getRangeOfAttachment(attachment)\n    return this.locationRangeFromRange(range)\n  }\n\n  getAttachmentPieceForAttachment(attachment) {\n    for (const piece of this.getAttachmentPieces()) {\n      if (piece.attachment === attachment) {\n        return piece\n      }\n    }\n  }\n\n  findRangesForBlockAttribute(attributeName) {\n    let position = 0\n    const ranges = []\n\n    this.getBlocks().forEach((block) => {\n      const length = block.getLength()\n      if (block.hasAttribute(attributeName)) {\n        ranges.push([ position, position + length ])\n      }\n      position += length\n    })\n\n    return ranges\n  }\n\n  findRangesForTextAttribute(attributeName, { withValue } = {}) {\n    let position = 0\n    let range = []\n    const ranges = []\n\n    const match = function(piece) {\n      if (withValue) {\n        return piece.getAttribute(attributeName) === withValue\n      } else {\n        return piece.hasAttribute(attributeName)\n      }\n    }\n\n    this.getPieces().forEach((piece) => {\n      const length = piece.getLength()\n      if (match(piece)) {\n        if (range[1] === position) {\n          range[1] = position + length\n        } else {\n          ranges.push(range = [ position, position + length ])\n        }\n      }\n      position += length\n    })\n\n    return ranges\n  }\n\n  locationFromPosition(position) {\n    const location = this.blockList.findIndexAndOffsetAtPosition(Math.max(0, position))\n    if (location.index != null) {\n      return location\n    } else {\n      const blocks = this.getBlocks()\n      return { index: blocks.length - 1, offset: blocks[blocks.length - 1].getLength() }\n    }\n  }\n\n  positionFromLocation(location) {\n    return this.blockList.findPositionAtIndexAndOffset(location.index, location.offset)\n  }\n\n  locationRangeFromPosition(position) {\n    return normalizeRange(this.locationFromPosition(position))\n  }\n\n  locationRangeFromRange(range) {\n    range = normalizeRange(range)\n    if (!range) return\n\n    const [ startPosition, endPosition ] = Array.from(range)\n    const startLocation = this.locationFromPosition(startPosition)\n    const endLocation = this.locationFromPosition(endPosition)\n    return normalizeRange([ startLocation, endLocation ])\n  }\n\n  rangeFromLocationRange(locationRange) {\n    let rightPosition\n    locationRange = normalizeRange(locationRange)\n    const leftPosition = this.positionFromLocation(locationRange[0])\n    if (!rangeIsCollapsed(locationRange)) {\n      rightPosition = this.positionFromLocation(locationRange[1])\n    }\n    return normalizeRange([ leftPosition, rightPosition ])\n  }\n\n  isEqualTo(document) {\n    return this.blockList.isEqualTo(document?.blockList)\n  }\n\n  getTexts() {\n    return this.getBlocks().map((block) => block.text)\n  }\n\n  getPieces() {\n    const pieces = []\n\n    Array.from(this.getTexts()).forEach((text) => {\n      pieces.push(...Array.from(text.getPieces() || []))\n    })\n\n    return pieces\n  }\n\n  getObjects() {\n    return this.getBlocks().concat(this.getTexts()).concat(this.getPieces())\n  }\n\n  toSerializableDocument() {\n    const blocks = []\n    this.blockList.eachObject((block) => blocks.push(block.copyWithText(block.text.toSerializableText())))\n    return new this.constructor(blocks)\n  }\n\n  toString() {\n    return this.blockList.toString()\n  }\n\n  toJSON() {\n    return this.blockList.toJSON()\n  }\n\n  toConsole() {\n    return JSON.stringify(this.blockList.toArray().map((block) => JSON.parse(block.text.toConsole())))\n  }\n}\n\nconst attributesForBlock = function(block) {\n  const attributes = {}\n  const attributeName = block.getLastAttribute()\n  if (attributeName) {\n    attributes[attributeName] = true\n  }\n  return attributes\n}\n"
  },
  {
    "path": "src/trix/models/editor.js",
    "content": "import Document from \"trix/models/document\"\nimport HTMLParser from \"trix/models/html_parser\"\n\nimport UndoManager from \"trix/models/undo_manager\"\nimport { attachmentGalleryFilter } from \"trix/filters/attachment_gallery_filter\"\nconst DEFAULT_FILTERS = [ attachmentGalleryFilter ]\n\nexport default class Editor {\n  constructor(composition, selectionManager, element) {\n    this.insertFiles = this.insertFiles.bind(this)\n    this.composition = composition\n    this.selectionManager = selectionManager\n    this.element = element\n    this.undoManager = new UndoManager(this.composition)\n    this.filters = DEFAULT_FILTERS.slice(0)\n  }\n\n  loadDocument(document) {\n    return this.loadSnapshot({ document, selectedRange: [ 0, 0 ] })\n  }\n\n  loadHTML(html = \"\") {\n    const document = HTMLParser.parse(html, { referenceElement: this.element }).getDocument()\n    return this.loadDocument(document)\n  }\n\n  loadJSON({ document, selectedRange }) {\n    document = Document.fromJSON(document)\n    return this.loadSnapshot({ document, selectedRange })\n  }\n\n  loadSnapshot(snapshot) {\n    this.undoManager = new UndoManager(this.composition)\n    return this.composition.loadSnapshot(snapshot)\n  }\n\n  getDocument() {\n    return this.composition.document\n  }\n\n  getSelectedDocument() {\n    return this.composition.getSelectedDocument()\n  }\n\n  getSnapshot() {\n    return this.composition.getSnapshot()\n  }\n\n  toJSON() {\n    return this.getSnapshot()\n  }\n\n  // Document manipulation\n\n  deleteInDirection(direction) {\n    return this.composition.deleteInDirection(direction)\n  }\n\n  insertAttachment(attachment) {\n    return this.composition.insertAttachment(attachment)\n  }\n\n  insertAttachments(attachments) {\n    return this.composition.insertAttachments(attachments)\n  }\n\n  insertDocument(document) {\n    return this.composition.insertDocument(document)\n  }\n\n  insertFile(file) {\n    return this.composition.insertFile(file)\n  }\n\n  insertFiles(files) {\n    return this.composition.insertFiles(files)\n  }\n\n  insertHTML(html) {\n    return this.composition.insertHTML(html)\n  }\n\n  insertString(string) {\n    return this.composition.insertString(string)\n  }\n\n  insertText(text) {\n    return this.composition.insertText(text)\n  }\n\n  insertLineBreak() {\n    return this.composition.insertLineBreak()\n  }\n\n  // Selection\n\n  getSelectedRange() {\n    return this.composition.getSelectedRange()\n  }\n\n  getPosition() {\n    return this.composition.getPosition()\n  }\n\n  getClientRectAtPosition(position) {\n    const locationRange = this.getDocument().locationRangeFromRange([ position, position + 1 ])\n    return this.selectionManager.getClientRectAtLocationRange(locationRange)\n  }\n\n  expandSelectionInDirection(direction) {\n    return this.composition.expandSelectionInDirection(direction)\n  }\n\n  moveCursorInDirection(direction) {\n    return this.composition.moveCursorInDirection(direction)\n  }\n\n  setSelectedRange(selectedRange) {\n    return this.composition.setSelectedRange(selectedRange)\n  }\n\n  // Attributes\n\n  activateAttribute(name, value = true) {\n    return this.composition.setCurrentAttribute(name, value)\n  }\n\n  attributeIsActive(name) {\n    return this.composition.hasCurrentAttribute(name)\n  }\n\n  canActivateAttribute(name) {\n    return this.composition.canSetCurrentAttribute(name)\n  }\n\n  deactivateAttribute(name) {\n    return this.composition.removeCurrentAttribute(name)\n  }\n\n  // HTML attributes\n  setHTMLAtributeAtPosition(position, name, value) {\n    this.composition.setHTMLAtributeAtPosition(position, name, value)\n  }\n\n  // Nesting level\n\n  canDecreaseNestingLevel() {\n    return this.composition.canDecreaseNestingLevel()\n  }\n\n  canIncreaseNestingLevel() {\n    return this.composition.canIncreaseNestingLevel()\n  }\n\n  decreaseNestingLevel() {\n    if (this.canDecreaseNestingLevel()) {\n      return this.composition.decreaseNestingLevel()\n    }\n  }\n\n  increaseNestingLevel() {\n    if (this.canIncreaseNestingLevel()) {\n      return this.composition.increaseNestingLevel()\n    }\n  }\n\n  // Undo/redo\n\n  canRedo() {\n    return this.undoManager.canRedo()\n  }\n\n  canUndo() {\n    return this.undoManager.canUndo()\n  }\n\n  recordUndoEntry(description, { context, consolidatable } = {}) {\n    return this.undoManager.recordUndoEntry(description, { context, consolidatable })\n  }\n\n  redo() {\n    if (this.canRedo()) {\n      return this.undoManager.redo()\n    }\n  }\n\n  undo() {\n    if (this.canUndo()) {\n      return this.undoManager.undo()\n    }\n  }\n}\n"
  },
  {
    "path": "src/trix/models/flaky_android_keyboard_detector.js",
    "content": "import * as config from \"trix/config\"\nimport { NON_BREAKING_SPACE, OBJECT_REPLACEMENT_CHARACTER, ZERO_WIDTH_SPACE } from \"trix/constants\"\n\n// Each software keyboard on Android emits its own set of events and some of them can be buggy.\n// This class detects when some buggy events are being emitted and lets know the input controller\n// that they should be ignored.\nexport default class FlakyAndroidKeyboardDetector {\n  constructor(element) {\n    this.element = element\n  }\n\n  shouldIgnore(event) {\n    if (!config.browser.samsungAndroid) return false\n\n    this.previousEvent = this.event\n    this.event = event\n\n    this.checkSamsungKeyboardBuggyModeStart()\n    this.checkSamsungKeyboardBuggyModeEnd()\n\n    return this.buggyMode\n  }\n\n  // private\n\n  // The Samsung keyboard on Android can enter a buggy state in which it emits a flurry of confused events that,\n  // if processed, corrupts the editor. The buggy mode always starts with an insertText event, right after a\n  // keydown event with for an \"Unidentified\" key, with the same text as the editor element, except for a few\n  // extra whitespace, or exotic utf8, characters.\n  checkSamsungKeyboardBuggyModeStart() {\n    if (this.insertingLongTextAfterUnidentifiedChar() && differsInWhitespace(this.element.innerText, this.event.data)) {\n      this.buggyMode = true\n      this.event.preventDefault()\n    }\n  }\n\n  // The flurry of buggy events are always insertText. If we see any other type, it means it's over.\n  checkSamsungKeyboardBuggyModeEnd() {\n    if (this.buggyMode && this.event.inputType !== \"insertText\") {\n      this.buggyMode = false\n    }\n  }\n\n  insertingLongTextAfterUnidentifiedChar() {\n    return this.isBeforeInputInsertText() && this.previousEventWasUnidentifiedKeydown() && this.event.data?.length > 50\n  }\n\n  isBeforeInputInsertText() {\n    return this.event.type === \"beforeinput\" && this.event.inputType === \"insertText\"\n  }\n\n  previousEventWasUnidentifiedKeydown() {\n    return this.previousEvent?.type === \"keydown\" && this.previousEvent?.key === \"Unidentified\"\n  }\n}\n\nconst differsInWhitespace = (text1, text2) => {\n  return normalize(text1) === normalize(text2)\n}\n\nconst whiteSpaceNormalizerRegexp = new RegExp(`(${OBJECT_REPLACEMENT_CHARACTER}|${ZERO_WIDTH_SPACE}|${NON_BREAKING_SPACE}|\\\\s)+`, \"g\")\nconst normalize = (text) => text.replace(whiteSpaceNormalizerRegexp, \" \").trim()\n"
  },
  {
    "path": "src/trix/models/html_parser.js",
    "content": "/* eslint-disable\n    no-case-declarations,\n    no-irregular-whitespace,\n*/\nimport * as config from \"trix/config\"\nimport BasicObject from \"trix/core/basic_object\"\nimport Document from \"trix/models/document\"\nimport HTMLSanitizer from \"trix/models/html_sanitizer\"\n\nimport {\n  arraysAreEqual,\n  breakableWhitespacePattern,\n  elementContainsNode,\n  findClosestElementFromNode,\n  getBlockTagNames,\n  makeElement,\n  nodeIsAttachmentElement,\n  normalizeSpaces,\n  removeNode,\n  squishBreakableWhitespace,\n  tagName,\n  walkTree,\n} from \"trix/core/helpers\"\n\nconst pieceForString = (string, attributes = {}) => {\n  const type = \"string\"\n  string = normalizeSpaces(string)\n  return { string, attributes, type }\n}\n\nconst pieceForAttachment = (attachment, attributes = {}) => {\n  const type = \"attachment\"\n  return { attachment, attributes, type }\n}\n\nconst blockForAttributes = (attributes = {}, htmlAttributes = {}) => {\n  const text = []\n  return { text, attributes, htmlAttributes }\n}\n\nconst parseTrixDataAttribute = (element, name) => {\n  try {\n    return JSON.parse(element.getAttribute(`data-trix-${name}`))\n  } catch (error) {\n    return {}\n  }\n}\n\nconst getImageDimensions = (element) => {\n  const width = element.getAttribute(\"width\")\n  const height = element.getAttribute(\"height\")\n  const dimensions = {}\n  if (width) {\n    dimensions.width = parseInt(width, 10)\n  }\n  if (height) {\n    dimensions.height = parseInt(height, 10)\n  }\n  return dimensions\n}\n\nexport default class HTMLParser extends BasicObject {\n  static parse(html, options) {\n    const parser = new this(html, options)\n    parser.parse()\n    return parser\n  }\n\n  constructor(html, { referenceElement, purifyOptions } = {}) {\n    super(...arguments)\n    this.html = html\n    this.referenceElement = referenceElement\n    this.purifyOptions = purifyOptions\n    this.blocks = []\n    this.blockElements = []\n    this.processedElements = []\n  }\n\n  getDocument() {\n    return Document.fromJSON(this.blocks)\n  }\n\n  // HTML parsing\n\n  parse() {\n    try {\n      this.createHiddenContainer()\n      HTMLSanitizer.setHTML(this.containerElement, this.html, { purifyOptions: this.purifyOptions })\n      const walker = walkTree(this.containerElement, { usingFilter: nodeFilter })\n      while (walker.nextNode()) {\n        this.processNode(walker.currentNode)\n      }\n      return this.translateBlockElementMarginsToNewlines()\n    } finally {\n      this.removeHiddenContainer()\n    }\n  }\n\n  createHiddenContainer() {\n    if (this.referenceElement) {\n      this.containerElement = this.referenceElement.cloneNode(false)\n      this.containerElement.removeAttribute(\"id\")\n      this.containerElement.setAttribute(\"data-trix-internal\", \"\")\n      this.containerElement.style.display = \"none\"\n      return this.referenceElement.parentNode.insertBefore(this.containerElement, this.referenceElement.nextSibling)\n    } else {\n      this.containerElement = makeElement({ tagName: \"div\", style: { display: \"none\" } })\n      return document.body.appendChild(this.containerElement)\n    }\n  }\n\n  removeHiddenContainer() {\n    return removeNode(this.containerElement)\n  }\n\n  processNode(node) {\n    switch (node.nodeType) {\n      case Node.TEXT_NODE:\n        if (!this.isInsignificantTextNode(node)) {\n          this.appendBlockForTextNode(node)\n          return this.processTextNode(node)\n        }\n        break\n      case Node.ELEMENT_NODE:\n        this.appendBlockForElement(node)\n        return this.processElement(node)\n    }\n  }\n\n  appendBlockForTextNode(node) {\n    const element = node.parentNode\n    if (element === this.currentBlockElement && this.isBlockElement(node.previousSibling)) {\n      return this.appendStringWithAttributes(\"\\n\")\n    } else if (element === this.containerElement || this.isBlockElement(element)) {\n      const attributes = this.getBlockAttributes(element)\n      const htmlAttributes = this.getBlockHTMLAttributes(element)\n      if (!arraysAreEqual(attributes, this.currentBlock?.attributes)) {\n        this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes)\n        this.currentBlockElement = element\n      }\n    }\n  }\n\n  appendBlockForElement(element) {\n    const elementIsBlockElement = this.isBlockElement(element)\n    const currentBlockContainsElement = elementContainsNode(this.currentBlockElement, element)\n\n    if (elementIsBlockElement && !this.isBlockElement(element.firstChild)) {\n      if (!this.isInsignificantTextNode(element.firstChild) || !this.isBlockElement(element.firstElementChild)) {\n        const attributes = this.getBlockAttributes(element)\n        const htmlAttributes = this.getBlockHTMLAttributes(element)\n        if (element.firstChild) {\n          if (!(currentBlockContainsElement && arraysAreEqual(attributes, this.currentBlock.attributes))) {\n            this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes)\n            this.currentBlockElement = element\n          } else {\n            return this.appendStringWithAttributes(\"\\n\")\n          }\n        }\n      }\n    } else if (this.currentBlockElement && !currentBlockContainsElement && !elementIsBlockElement) {\n      const parentBlockElement = this.findParentBlockElement(element)\n      if (parentBlockElement) {\n        return this.appendBlockForElement(parentBlockElement)\n      } else {\n        this.currentBlock = this.appendEmptyBlock()\n        this.currentBlockElement = null\n      }\n    }\n  }\n\n  findParentBlockElement(element) {\n    let { parentElement } = element\n    while (parentElement && parentElement !== this.containerElement) {\n      if (this.isBlockElement(parentElement) && this.blockElements.includes(parentElement)) {\n        return parentElement\n      } else {\n        parentElement = parentElement.parentElement\n      }\n    }\n    return null\n  }\n\n  processTextNode(node) {\n    let string = node.data\n    if (!elementCanDisplayPreformattedText(node.parentNode)) {\n      string = squishBreakableWhitespace(string)\n      if (stringEndsWithWhitespace(node.previousSibling?.textContent)) {\n        string = leftTrimBreakableWhitespace(string)\n      }\n    }\n    return this.appendStringWithAttributes(string, this.getTextAttributes(node.parentNode))\n  }\n\n  processElement(element) {\n    let attributes\n    if (nodeIsAttachmentElement(element)) {\n      attributes = parseTrixDataAttribute(element, \"attachment\")\n      if (Object.keys(attributes).length) {\n        const textAttributes = this.getTextAttributes(element)\n        this.appendAttachmentWithAttributes(attributes, textAttributes)\n        // We have everything we need so avoid processing inner nodes\n        element.innerHTML = \"\"\n      }\n      return this.processedElements.push(element)\n    } else {\n      switch (tagName(element)) {\n        case \"br\":\n          if (!this.isExtraBR(element) && !this.isBlockElement(element.nextSibling)) {\n            this.appendStringWithAttributes(\"\\n\", this.getTextAttributes(element))\n          }\n          return this.processedElements.push(element)\n        case \"img\":\n          attributes = { url: element.getAttribute(\"src\"), contentType: \"image\" }\n          const object = getImageDimensions(element)\n          for (const key in object) {\n            const value = object[key]\n            attributes[key] = value\n          }\n          this.appendAttachmentWithAttributes(attributes, this.getTextAttributes(element))\n          return this.processedElements.push(element)\n        case \"tr\":\n          if (this.needsTableSeparator(element)) {\n            return this.appendStringWithAttributes(config.parser.tableRowSeparator)\n          }\n          break\n        case \"td\":\n          if (this.needsTableSeparator(element)) {\n            return this.appendStringWithAttributes(config.parser.tableCellSeparator)\n          }\n          break\n      }\n    }\n  }\n\n  // Document construction\n\n  appendBlockForAttributesWithElement(attributes, element, htmlAttributes = {}) {\n    this.blockElements.push(element)\n    const block = blockForAttributes(attributes, htmlAttributes)\n    this.blocks.push(block)\n    return block\n  }\n\n  appendEmptyBlock() {\n    return this.appendBlockForAttributesWithElement([], null)\n  }\n\n  appendStringWithAttributes(string, attributes) {\n    return this.appendPiece(pieceForString(string, attributes))\n  }\n\n  appendAttachmentWithAttributes(attachment, attributes) {\n    return this.appendPiece(pieceForAttachment(attachment, attributes))\n  }\n\n  appendPiece(piece) {\n    if (this.blocks.length === 0) {\n      this.appendEmptyBlock()\n    }\n    return this.blocks[this.blocks.length - 1].text.push(piece)\n  }\n\n  appendStringToTextAtIndex(string, index) {\n    const { text } = this.blocks[index]\n    const piece = text[text.length - 1]\n\n    if (piece?.type === \"string\") {\n      piece.string += string\n    } else {\n      return text.push(pieceForString(string))\n    }\n  }\n\n  prependStringToTextAtIndex(string, index) {\n    const { text } = this.blocks[index]\n    const piece = text[0]\n\n    if (piece?.type === \"string\") {\n      piece.string = string + piece.string\n    } else {\n      return text.unshift(pieceForString(string))\n    }\n  }\n\n  // Attribute parsing\n\n  getTextAttributes(element) {\n    let value\n    const attributes = {}\n    for (const attribute in config.textAttributes) {\n      const configAttr = config.textAttributes[attribute]\n      if (\n        configAttr.tagName &&\n        findClosestElementFromNode(element, {\n          matchingSelector: configAttr.tagName,\n          untilNode: this.containerElement,\n        })\n      ) {\n        attributes[attribute] = true\n      } else if (configAttr.parser) {\n        value = configAttr.parser(element)\n        if (value) {\n          let attributeInheritedFromBlock = false\n          for (const blockElement of this.findBlockElementAncestors(element)) {\n            if (configAttr.parser(blockElement) === value) {\n              attributeInheritedFromBlock = true\n              break\n            }\n          }\n          if (!attributeInheritedFromBlock) {\n            attributes[attribute] = value\n          }\n        }\n      } else if (configAttr.styleProperty) {\n        value = element.style[configAttr.styleProperty]\n        if (value) {\n          attributes[attribute] = value\n        }\n      }\n    }\n\n    if (nodeIsAttachmentElement(element)) {\n      const object = parseTrixDataAttribute(element, \"attributes\")\n      for (const key in object) {\n        value = object[key]\n        attributes[key] = value\n      }\n    }\n\n    return attributes\n  }\n\n  getBlockAttributes(element) {\n    const attributes = []\n    while (element && element !== this.containerElement) {\n      for (const attribute in config.blockAttributes) {\n        const attrConfig = config.blockAttributes[attribute]\n        if (attrConfig.parse !== false) {\n          if (tagName(element) === attrConfig.tagName) {\n            if (attrConfig.test?.(element) || !attrConfig.test) {\n              attributes.push(attribute)\n              if (attrConfig.listAttribute) {\n                attributes.push(attrConfig.listAttribute)\n              }\n            }\n          }\n        }\n      }\n      element = element.parentNode\n    }\n    return attributes.reverse()\n  }\n\n  getBlockHTMLAttributes(element) {\n    const attributes = {}\n    const blockConfig = Object.values(config.blockAttributes).find(settings => settings.tagName === tagName(element))\n    const allowedAttributes = blockConfig?.htmlAttributes || []\n\n    allowedAttributes.forEach((attribute) => {\n      if (element.hasAttribute(attribute)) {\n        attributes[attribute] = element.getAttribute(attribute)\n      }\n    })\n\n    return attributes\n  }\n\n  findBlockElementAncestors(element) {\n    const ancestors = []\n    while (element && element !== this.containerElement) {\n      const tag = tagName(element)\n      if (getBlockTagNames().includes(tag)) {\n        ancestors.push(element)\n      }\n      element = element.parentNode\n    }\n    return ancestors\n  }\n\n  // Element inspection\n\n  isBlockElement(element) {\n    if (element?.nodeType !== Node.ELEMENT_NODE) return\n    if (nodeIsAttachmentElement(element)) return\n    if (findClosestElementFromNode(element, { matchingSelector: \"td\", untilNode: this.containerElement })) return\n\n    return getBlockTagNames().includes(tagName(element)) ||\n      window.getComputedStyle(element).display === \"block\"\n  }\n\n  isInsignificantTextNode(node) {\n    if (node?.nodeType !== Node.TEXT_NODE) return\n    if (!stringIsAllBreakableWhitespace(node.data)) return\n    const { parentNode, previousSibling, nextSibling } = node\n    if (nodeEndsWithNonWhitespace(parentNode.previousSibling) && !this.isBlockElement(parentNode.previousSibling)) return\n    if (elementCanDisplayPreformattedText(parentNode)) return\n    return !previousSibling || this.isBlockElement(previousSibling) || !nextSibling || this.isBlockElement(nextSibling)\n  }\n\n  isExtraBR(element) {\n    return tagName(element) === \"br\" && this.isBlockElement(element.parentNode) && element.parentNode.lastChild === element\n  }\n\n  needsTableSeparator(element) {\n    if (config.parser.removeBlankTableCells) {\n      const content = element.previousSibling?.textContent\n      return content && /\\S/.test(content)\n    } else {\n      return element.previousSibling\n    }\n  }\n\n  // Margin translation\n\n  translateBlockElementMarginsToNewlines() {\n    const defaultMargin = this.getMarginOfDefaultBlockElement()\n\n    for (let index = 0; index < this.blocks.length; index++) {\n      const margin = this.getMarginOfBlockElementAtIndex(index)\n      if (margin) {\n        if (margin.top > defaultMargin.top * 2) {\n          this.prependStringToTextAtIndex(\"\\n\", index)\n        }\n\n        if (margin.bottom > defaultMargin.bottom * 2) {\n          this.appendStringToTextAtIndex(\"\\n\", index)\n        }\n      }\n    }\n  }\n\n  getMarginOfBlockElementAtIndex(index) {\n    const element = this.blockElements[index]\n    if (element) {\n      if (element.textContent) {\n        if (!getBlockTagNames().includes(tagName(element)) && !this.processedElements.includes(element)) {\n          return getBlockElementMargin(element)\n        }\n      }\n    }\n  }\n\n  getMarginOfDefaultBlockElement() {\n    const element = makeElement(config.blockAttributes.default.tagName)\n    this.containerElement.appendChild(element)\n    return getBlockElementMargin(element)\n  }\n}\n\n// Helpers\n\nconst elementCanDisplayPreformattedText = function(element) {\n  const { whiteSpace } = window.getComputedStyle(element)\n  return [ \"pre\", \"pre-wrap\", \"pre-line\" ].includes(whiteSpace)\n}\n\nconst nodeEndsWithNonWhitespace = (node) => node && !stringEndsWithWhitespace(node.textContent)\n\nconst getBlockElementMargin = function(element) {\n  const style = window.getComputedStyle(element)\n  if (style.display === \"block\") {\n    return { top: parseInt(style.marginTop), bottom: parseInt(style.marginBottom) }\n  }\n}\n\nconst nodeFilter = function(node) {\n  if (tagName(node) === \"style\") {\n    return NodeFilter.FILTER_REJECT\n  } else {\n    return NodeFilter.FILTER_ACCEPT\n  }\n}\n\n// Whitespace\n\nconst leftTrimBreakableWhitespace = (string) => string.replace(new RegExp(`^${breakableWhitespacePattern.source}+`), \"\")\n\nconst stringIsAllBreakableWhitespace = (string) => new RegExp(`^${breakableWhitespacePattern.source}*$`).test(string)\n\nconst stringEndsWithWhitespace = (string) => /\\s$/.test(string)\n"
  },
  {
    "path": "src/trix/models/html_sanitizer.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nimport { nodeIsAttachmentElement, removeNode, tagName, walkTree } from \"trix/core/helpers\"\nimport DOMPurify from \"dompurify\"\nimport * as config from \"trix/config\"\n\nDOMPurify.addHook(\"uponSanitizeAttribute\", function (node, data) {\n  if (data.attrName === \"data-trix-serialized-attributes\") {\n    data.keepAttr = false\n    return\n  }\n\n  const allowedAttributePattern = /^data-trix-/\n  if (allowedAttributePattern.test(data.attrName)) {\n    data.forceKeepAttr = true\n  }\n})\n\nconst DEFAULT_ALLOWED_ATTRIBUTES = \"style href src width height language class\".split(\" \")\nconst DEFAULT_FORBIDDEN_PROTOCOLS = \"javascript:\".split(\" \")\nconst DEFAULT_FORBIDDEN_ELEMENTS = \"script iframe form noscript\".split(\" \")\n\nexport default class HTMLSanitizer extends BasicObject {\n  static setHTML(element, html, options) {\n    const sanitizedElement = new this(html, options).sanitize()\n    const sanitizedHtml = sanitizedElement.getHTML ? sanitizedElement.getHTML() : sanitizedElement.outerHTML\n    element.innerHTML = sanitizedHtml\n  }\n\n  static sanitize(html, options) {\n    const sanitizer = new this(html, options)\n    sanitizer.sanitize()\n    return sanitizer\n  }\n\n  constructor(html, { allowedAttributes, forbiddenProtocols, forbiddenElements, purifyOptions } = {}) {\n    super(...arguments)\n    this.allowedAttributes = allowedAttributes || DEFAULT_ALLOWED_ATTRIBUTES\n    this.forbiddenProtocols = forbiddenProtocols || DEFAULT_FORBIDDEN_PROTOCOLS\n    this.forbiddenElements = forbiddenElements || DEFAULT_FORBIDDEN_ELEMENTS\n    this.purifyOptions = purifyOptions || {}\n    this.body = createBodyElementForHTML(html)\n  }\n\n  sanitize() {\n    this.sanitizeElements()\n    this.normalizeListElementNesting()\n    const purifyConfig = Object.assign({}, config.dompurify, this.purifyOptions)\n    DOMPurify.setConfig(purifyConfig)\n    this.body = DOMPurify.sanitize(this.body)\n\n    return this.body\n  }\n\n  getHTML() {\n    return this.body.innerHTML\n  }\n\n  getBody() {\n    return this.body\n  }\n\n  // Private\n\n  sanitizeElements() {\n    const walker = walkTree(this.body)\n    const nodesToRemove = []\n\n    while (walker.nextNode()) {\n      const node = walker.currentNode\n      switch (node.nodeType) {\n        case Node.ELEMENT_NODE:\n          if (this.elementIsRemovable(node)) {\n            nodesToRemove.push(node)\n          } else {\n            this.sanitizeElement(node)\n          }\n          break\n        case Node.COMMENT_NODE:\n          nodesToRemove.push(node)\n          break\n      }\n    }\n\n    nodesToRemove.forEach((node) => removeNode(node))\n\n    return this.body\n  }\n\n  sanitizeElement(element) {\n    if (element.hasAttribute(\"href\")) {\n      if (this.forbiddenProtocols.includes(element.protocol)) {\n        element.removeAttribute(\"href\")\n      }\n    }\n\n    Array.from(element.attributes).forEach(({ name }) => {\n      if (!this.allowedAttributes.includes(name) && name.indexOf(\"data-trix\") !== 0) {\n        element.removeAttribute(name)\n      }\n    })\n\n    return element\n  }\n\n  normalizeListElementNesting() {\n    Array.from(this.body.querySelectorAll(\"ul,ol\")).forEach((listElement) => {\n      const previousElement = listElement.previousElementSibling\n      if (previousElement) {\n        if (tagName(previousElement) === \"li\") {\n          previousElement.appendChild(listElement)\n        }\n      }\n    })\n\n    return this.body\n  }\n\n  elementIsRemovable(element) {\n    if (element?.nodeType !== Node.ELEMENT_NODE) return\n    return this.elementIsForbidden(element) || this.elementIsntSerializable(element)\n  }\n\n  elementIsForbidden(element) {\n    return this.forbiddenElements.includes(tagName(element))\n  }\n\n  elementIsntSerializable(element) {\n    return element.getAttribute(\"data-trix-serialize\") === \"false\" && !nodeIsAttachmentElement(element)\n  }\n}\n\nconst createBodyElementForHTML = function(html = \"\") {\n  // Remove everything after </html>\n  html = html.replace(/<\\/html[^>]*>[^]*$/i, \"</html>\")\n  const doc = document.implementation.createHTMLDocument(\"\")\n  doc.documentElement.innerHTML = html\n\n  Array.from(doc.head.querySelectorAll(\"style\")).forEach((element) => {\n    doc.body.appendChild(element)\n  })\n\n  return doc.body\n}\n"
  },
  {
    "path": "src/trix/models/index.js",
    "content": "export { default as Attachment } from \"./attachment\"\nexport { default as AttachmentManager } from \"./attachment_manager\"\nexport { default as AttachmentPiece } from \"./attachment_piece\"\nexport { default as Block } from \"./block\"\nexport { default as Composition } from \"./composition\"\nexport { default as Document } from \"./document\"\nexport { default as Editor } from \"./editor\"\nexport { default as HTMLParser } from \"./html_parser\"\nexport { default as HTMLSanitizer } from \"./html_sanitizer\"\nexport { default as LineBreakInsertion } from \"./line_break_insertion\"\nexport { default as LocationMapper } from \"./location_mapper\"\nexport { default as ManagedAttachment } from \"./managed_attachment\"\nexport { default as Piece } from \"./piece\"\nexport { default as PointMapper } from \"./point_mapper\"\nexport { default as SelectionManager } from \"./selection_manager\"\nexport { default as SplittableList } from \"./splittable_list\"\nexport { default as StringPiece } from \"./string_piece\"\nexport { default as Text } from \"./text\"\nexport { default as UndoManager } from \"./undo_manager\"\n"
  },
  {
    "path": "src/trix/models/line_break_insertion.js",
    "content": "export default class LineBreakInsertion {\n  constructor(composition) {\n    this.composition = composition\n    this.document = this.composition.document\n    const selectedRange = this.composition.getSelectedRange()\n    this.startPosition = selectedRange[0]\n    this.endPosition = selectedRange[1]\n\n    this.startLocation = this.document.locationFromPosition(this.startPosition)\n    this.endLocation = this.document.locationFromPosition(this.endPosition)\n\n    this.block = this.document.getBlockAtIndex(this.endLocation.index)\n    this.breaksOnReturn = this.block.breaksOnReturn()\n    this.previousCharacter = this.block.text.getStringAtPosition(this.endLocation.offset - 1)\n    this.nextCharacter = this.block.text.getStringAtPosition(this.endLocation.offset)\n  }\n\n  shouldInsertBlockBreak() {\n    if (this.block.hasAttributes() && this.block.isListItem() && !this.block.isEmpty()) {\n      return this.startLocation.offset !== 0\n    } else {\n      return this.breaksOnReturn && this.nextCharacter !== \"\\n\"\n    }\n  }\n\n  shouldBreakFormattedBlock() {\n    return (\n      this.block.hasAttributes() &&\n      !this.block.isListItem() &&\n      (this.breaksOnReturn && this.nextCharacter === \"\\n\" || this.previousCharacter === \"\\n\")\n    )\n  }\n\n  shouldDecreaseListLevel() {\n    return this.block.hasAttributes() && this.block.isListItem() && this.block.isEmpty()\n  }\n\n  shouldPrependListItem() {\n    return this.block.isListItem() && this.startLocation.offset === 0 && !this.block.isEmpty()\n  }\n\n  shouldRemoveLastBlockAttribute() {\n    return this.block.hasAttributes() && !this.block.isListItem() && this.block.isEmpty()\n  }\n}\n"
  },
  {
    "path": "src/trix/models/location_mapper.js",
    "content": "/* eslint-disable\n    no-var,\n    prefer-const,\n*/\nimport {\n  elementContainsNode,\n  findChildIndexOfNode,\n  nodeIsAttachmentElement,\n  nodeIsBlockContainer,\n  nodeIsBlockStart,\n  nodeIsBlockStartComment,\n  nodeIsCursorTarget,\n  nodeIsEmptyTextNode,\n  nodeIsTextNode,\n  tagName,\n  walkTree,\n} from \"trix/core/helpers\"\n\nexport default class LocationMapper {\n  constructor(element) {\n    this.element = element\n  }\n\n  findLocationFromContainerAndOffset(container, offset, { strict } = { strict: true }) {\n    let childIndex = 0\n    let foundBlock = false\n    const location = { index: 0, offset: 0 }\n    const attachmentElement = this.findAttachmentElementParentForNode(container)\n\n    if (attachmentElement) {\n      container = attachmentElement.parentNode\n      offset = findChildIndexOfNode(attachmentElement)\n    }\n\n    const walker = walkTree(this.element, { usingFilter: rejectAttachmentContents })\n\n    while (walker.nextNode()) {\n      const node = walker.currentNode\n\n      if (node === container && nodeIsTextNode(container)) {\n        if (!nodeIsCursorTarget(node)) {\n          location.offset += offset\n        }\n        break\n      } else {\n        if (node.parentNode === container) {\n          if (childIndex++ === offset) {\n            if (!strict && nodeIsBlockStart(node, { strict })) {\n              if (foundBlock) {\n                location.index++\n              }\n              location.offset = 0\n              foundBlock = true\n            }\n            break\n          }\n        } else if (!elementContainsNode(container, node)) {\n          if (childIndex > 0) {\n            break\n          }\n        }\n\n        if (nodeIsBlockStart(node, { strict })) {\n          if (foundBlock) {\n            location.index++\n          }\n          location.offset = 0\n          foundBlock = true\n        } else {\n          location.offset += nodeLength(node)\n        }\n      }\n    }\n\n    return location\n  }\n\n  findContainerAndOffsetFromLocation(location) {\n    let container, offset\n    if (location.index === 0 && location.offset === 0) {\n      container = this.element\n      offset = 0\n\n      while (container.firstChild) {\n        container = container.firstChild\n        if (nodeIsBlockContainer(container)) {\n          offset = 1\n          break\n        }\n      }\n\n      return [ container, offset ]\n    }\n\n    let [ node, nodeOffset ] = this.findNodeAndOffsetFromLocation(location)\n    if (!node) return\n\n    if (nodeIsTextNode(node)) {\n      if (nodeLength(node) === 0) {\n        container = node.parentNode.parentNode\n        offset = findChildIndexOfNode(node.parentNode)\n        if (nodeIsCursorTarget(node, { name: \"right\" })) {\n          offset++\n        }\n      } else {\n        container = node\n        offset = location.offset - nodeOffset\n      }\n    } else {\n      container = node.parentNode\n\n      if (!nodeIsBlockStart(node.previousSibling)) {\n        if (!nodeIsBlockContainer(container)) {\n          while (node === container.lastChild) {\n            node = container\n            container = container.parentNode\n            if (nodeIsBlockContainer(container)) {\n              break\n            }\n          }\n        }\n      }\n\n      offset = findChildIndexOfNode(node)\n      if (location.offset !== 0) {\n        offset++\n      }\n    }\n\n    return [ container, offset ]\n  }\n\n  findNodeAndOffsetFromLocation(location) {\n    let node, nodeOffset\n    let offset = 0\n\n    for (const currentNode of this.getSignificantNodesForIndex(location.index)) {\n      const length = nodeLength(currentNode)\n\n      if (location.offset <= offset + length) {\n        if (nodeIsTextNode(currentNode)) {\n          node = currentNode\n          nodeOffset = offset\n          if (location.offset === nodeOffset && nodeIsCursorTarget(node)) {\n            break\n          }\n        } else if (!node) {\n          node = currentNode\n          nodeOffset = offset\n        }\n      }\n\n      offset += length\n      if (offset > location.offset) {\n        break\n      }\n    }\n\n    return [ node, nodeOffset ]\n  }\n\n  // Private\n\n  findAttachmentElementParentForNode(node) {\n    while (node && node !== this.element) {\n      if (nodeIsAttachmentElement(node)) {\n        return node\n      }\n      node = node.parentNode\n    }\n  }\n\n  getSignificantNodesForIndex(index) {\n    const nodes = []\n    const walker = walkTree(this.element, { usingFilter: acceptSignificantNodes })\n    let recordingNodes = false\n\n    while (walker.nextNode()) {\n      const node = walker.currentNode\n      if (nodeIsBlockStartComment(node)) {\n        var blockIndex\n        if (blockIndex != null) {\n          blockIndex++\n        } else {\n          blockIndex = 0\n        }\n\n        if (blockIndex === index) {\n          recordingNodes = true\n        } else if (recordingNodes) {\n          break\n        }\n      } else if (recordingNodes) {\n        nodes.push(node)\n      }\n    }\n\n    return nodes\n  }\n}\n\nconst nodeLength = function(node) {\n  if (node.nodeType === Node.TEXT_NODE) {\n    if (nodeIsCursorTarget(node)) {\n      return 0\n    } else {\n      const string = node.textContent\n      return string.length\n    }\n  } else if (tagName(node) === \"br\" || nodeIsAttachmentElement(node)) {\n    return 1\n  } else {\n    return 0\n  }\n}\n\nconst acceptSignificantNodes = function(node) {\n  if (rejectEmptyTextNodes(node) === NodeFilter.FILTER_ACCEPT) {\n    return rejectAttachmentContents(node)\n  } else {\n    return NodeFilter.FILTER_REJECT\n  }\n}\n\nconst rejectEmptyTextNodes = function(node) {\n  if (nodeIsEmptyTextNode(node)) {\n    return NodeFilter.FILTER_REJECT\n  } else {\n    return NodeFilter.FILTER_ACCEPT\n  }\n}\n\nconst rejectAttachmentContents = function(node) {\n  if (nodeIsAttachmentElement(node.parentNode)) {\n    return NodeFilter.FILTER_REJECT\n  } else {\n    return NodeFilter.FILTER_ACCEPT\n  }\n}\n"
  },
  {
    "path": "src/trix/models/managed_attachment.js",
    "content": "import \"trix/models/attachment\"\nimport BasicObject from \"trix/core/basic_object\"\n\nexport default class ManagedAttachment extends BasicObject {\n  constructor(attachmentManager, attachment) {\n    super(...arguments)\n    this.attachmentManager = attachmentManager\n    this.attachment = attachment\n    this.id = this.attachment.id\n    this.file = this.attachment.file\n  }\n\n  remove() {\n    return this.attachmentManager.requestRemovalOfAttachment(this.attachment)\n  }\n}\n\nManagedAttachment.proxyMethod(\"attachment.getAttribute\")\nManagedAttachment.proxyMethod(\"attachment.hasAttribute\")\nManagedAttachment.proxyMethod(\"attachment.setAttribute\")\nManagedAttachment.proxyMethod(\"attachment.getAttributes\")\nManagedAttachment.proxyMethod(\"attachment.setAttributes\")\nManagedAttachment.proxyMethod(\"attachment.isPending\")\nManagedAttachment.proxyMethod(\"attachment.isPreviewable\")\nManagedAttachment.proxyMethod(\"attachment.getURL\")\nManagedAttachment.proxyMethod(\"attachment.getPreviewURL\")\nManagedAttachment.proxyMethod(\"attachment.setPreviewURL\")\nManagedAttachment.proxyMethod(\"attachment.getHref\")\nManagedAttachment.proxyMethod(\"attachment.getFilename\")\nManagedAttachment.proxyMethod(\"attachment.getFilesize\")\nManagedAttachment.proxyMethod(\"attachment.getFormattedFilesize\")\nManagedAttachment.proxyMethod(\"attachment.getExtension\")\nManagedAttachment.proxyMethod(\"attachment.getContentType\")\nManagedAttachment.proxyMethod(\"attachment.getFile\")\nManagedAttachment.proxyMethod(\"attachment.setFile\")\nManagedAttachment.proxyMethod(\"attachment.releaseFile\")\nManagedAttachment.proxyMethod(\"attachment.getUploadProgress\")\nManagedAttachment.proxyMethod(\"attachment.setUploadProgress\")\n\n"
  },
  {
    "path": "src/trix/models/piece.js",
    "content": "import TrixObject from \"trix/core/object\" // Don't override window.Object\nimport Hash from \"trix/core/collections/hash\"\n\nexport default class Piece extends TrixObject {\n  static types = {}\n\n  static registerType(type, constructor) {\n    constructor.type = type\n    this.types[type] = constructor\n  }\n\n  static fromJSON(pieceJSON) {\n    const constructor = this.types[pieceJSON.type]\n    if (constructor) {\n      return constructor.fromJSON(pieceJSON)\n    }\n  }\n\n  constructor(value, attributes = {}) {\n    super(...arguments)\n    this.attributes = Hash.box(attributes)\n  }\n\n  copyWithAttributes(attributes) {\n    return new this.constructor(this.getValue(), attributes)\n  }\n\n  copyWithAdditionalAttributes(attributes) {\n    return this.copyWithAttributes(this.attributes.merge(attributes))\n  }\n\n  copyWithoutAttribute(attribute) {\n    return this.copyWithAttributes(this.attributes.remove(attribute))\n  }\n\n  copy() {\n    return this.copyWithAttributes(this.attributes)\n  }\n\n  getAttribute(attribute) {\n    return this.attributes.get(attribute)\n  }\n\n  getAttributesHash() {\n    return this.attributes\n  }\n\n  getAttributes() {\n    return this.attributes.toObject()\n  }\n\n  hasAttribute(attribute) {\n    return this.attributes.has(attribute)\n  }\n\n  hasSameStringValueAsPiece(piece) {\n    return piece && this.toString() === piece.toString()\n  }\n\n  hasSameAttributesAsPiece(piece) {\n    return piece && (this.attributes === piece.attributes || this.attributes.isEqualTo(piece.attributes))\n  }\n\n  isBlockBreak() {\n    return false\n  }\n\n  isEqualTo(piece) {\n    return (\n      super.isEqualTo(...arguments) ||\n      this.hasSameConstructorAs(piece) &&\n        this.hasSameStringValueAsPiece(piece) &&\n        this.hasSameAttributesAsPiece(piece)\n    )\n  }\n\n  isEmpty() {\n    return this.length === 0\n  }\n\n  isSerializable() {\n    return true\n  }\n\n  toJSON() {\n    return {\n      type: this.constructor.type,\n      attributes: this.getAttributes(),\n    }\n  }\n\n  contentsForInspection() {\n    return {\n      type: this.constructor.type,\n      attributes: this.attributes.inspect(),\n    }\n  }\n\n  // Grouping\n\n  canBeGrouped() {\n    return this.hasAttribute(\"href\")\n  }\n\n  canBeGroupedWith(piece) {\n    return this.getAttribute(\"href\") === piece.getAttribute(\"href\")\n  }\n\n  // Splittable\n\n  getLength() {\n    return this.length\n  }\n\n  canBeConsolidatedWith(piece) {\n    return false\n  }\n}\n"
  },
  {
    "path": "src/trix/models/point_mapper.js",
    "content": "/* eslint-disable\n    id-length,\n    no-empty,\n*/\nimport { getDOMRange, setDOMRange } from \"trix/core/helpers\"\n\nexport default class PointMapper {\n  createDOMRangeFromPoint({ x, y }) {\n    let domRange\n    if (document.caretPositionFromPoint) {\n      const { offsetNode, offset } = document.caretPositionFromPoint(x, y)\n      domRange = document.createRange()\n      domRange.setStart(offsetNode, offset)\n      return domRange\n    } else if (document.caretRangeFromPoint) {\n      return document.caretRangeFromPoint(x, y)\n    } else if (document.body.createTextRange) {\n      const originalDOMRange = getDOMRange()\n      try {\n        // IE 11 throws \"Unspecified error\" when using moveToPoint\n        // during a drag-and-drop operation.\n        const textRange = document.body.createTextRange()\n        textRange.moveToPoint(x, y)\n        textRange.select()\n      } catch (error) {}\n      domRange = getDOMRange()\n      setDOMRange(originalDOMRange)\n      return domRange\n    }\n  }\n\n  getClientRectsForDOMRange(domRange) {\n    const array = Array.from(domRange.getClientRects())\n    const start = array[0]\n    const end = array[array.length - 1]\n\n    return [ start, end ]\n  }\n}\n"
  },
  {
    "path": "src/trix/models/selection_manager.js",
    "content": "/* eslint-disable\n*/\nimport BasicObject from \"trix/core/basic_object\"\n\nimport LocationMapper from \"trix/models/location_mapper\"\nimport PointMapper from \"trix/models/point_mapper\"\n\nimport {\n  elementContainsNode,\n  getDOMRange,\n  getDOMSelection,\n  handleEvent,\n  innerElementIsActive,\n  nodeIsCursorTarget,\n  normalizeRange,\n  rangeIsCollapsed,\n  rangesAreEqual,\n  setDOMRange,\n} from \"trix/core/helpers\"\n\nexport default class SelectionManager extends BasicObject {\n  constructor(element) {\n    super(...arguments)\n    this.didMouseDown = this.didMouseDown.bind(this)\n    this.selectionDidChange = this.selectionDidChange.bind(this)\n    this.element = element\n    this.locationMapper = new LocationMapper(this.element)\n    this.pointMapper = new PointMapper()\n    this.lockCount = 0\n    handleEvent(\"mousedown\", { onElement: this.element, withCallback: this.didMouseDown })\n  }\n\n  getLocationRange(options = {}) {\n    if (options.strict === false) {\n      return this.createLocationRangeFromDOMRange(getDOMRange())\n    } else if (options.ignoreLock) {\n      return this.currentLocationRange\n    } else if (this.lockedLocationRange) {\n      return this.lockedLocationRange\n    } else {\n      return this.currentLocationRange\n    }\n  }\n\n  setLocationRange(locationRange) {\n    if (this.lockedLocationRange) return\n    locationRange = normalizeRange(locationRange)\n\n    const domRange = this.createDOMRangeFromLocationRange(locationRange)\n    if (domRange) {\n      setDOMRange(domRange)\n      this.updateCurrentLocationRange(locationRange)\n    }\n  }\n\n  setLocationRangeFromPointRange(pointRange) {\n    pointRange = normalizeRange(pointRange)\n    const startLocation = this.getLocationAtPoint(pointRange[0])\n    const endLocation = this.getLocationAtPoint(pointRange[1])\n    this.setLocationRange([ startLocation, endLocation ])\n  }\n\n  getClientRectAtLocationRange(locationRange) {\n    const domRange = this.createDOMRangeFromLocationRange(locationRange)\n    if (domRange) {\n      return this.getClientRectsForDOMRange(domRange)[1]\n    }\n  }\n\n  locationIsCursorTarget(location) {\n    const node = Array.from(this.findNodeAndOffsetFromLocation(location))[0]\n    return nodeIsCursorTarget(node)\n  }\n\n  lock() {\n    if (this.lockCount++ === 0) {\n      this.updateCurrentLocationRange()\n      this.lockedLocationRange = this.getLocationRange()\n    }\n  }\n\n  unlock() {\n    if (--this.lockCount === 0) {\n      const { lockedLocationRange } = this\n      this.lockedLocationRange = null\n      if (lockedLocationRange != null) {\n        return this.setLocationRange(lockedLocationRange)\n      }\n    }\n  }\n\n  clearSelection() {\n    return getDOMSelection()?.removeAllRanges()\n  }\n\n  selectionIsCollapsed() {\n    return getDOMRange()?.collapsed === true\n  }\n\n  selectionIsExpanded() {\n    return !this.selectionIsCollapsed()\n  }\n\n  createLocationRangeFromDOMRange(domRange, options) {\n    if (domRange == null || !this.domRangeWithinElement(domRange)) return\n\n    const start = this.findLocationFromContainerAndOffset(domRange.startContainer, domRange.startOffset, options)\n    if (!start) return\n\n    const end = domRange.collapsed\n      ? undefined\n      : this.findLocationFromContainerAndOffset(domRange.endContainer, domRange.endOffset, options)\n\n    return normalizeRange([ start, end ])\n  }\n\n  didMouseDown() {\n    return this.pauseTemporarily()\n  }\n\n  pauseTemporarily() {\n    let resumeHandlers\n    this.paused = true\n\n    const resume = () => {\n      this.paused = false\n      clearTimeout(resumeTimeout)\n\n      Array.from(resumeHandlers).forEach((handler) => {\n        handler.destroy()\n      })\n\n      if (elementContainsNode(document, this.element)) {\n        return this.selectionDidChange()\n      }\n    }\n\n    const resumeTimeout = setTimeout(resume, 200)\n\n    resumeHandlers = [ \"mousemove\", \"keydown\" ].map((eventName) =>\n      handleEvent(eventName, { onElement: document, withCallback: resume })\n    )\n  }\n\n  selectionDidChange() {\n    if (!this.paused && !innerElementIsActive(this.element)) {\n      return this.updateCurrentLocationRange()\n    }\n  }\n\n  updateCurrentLocationRange(locationRange) {\n    if (locationRange != null ? locationRange : locationRange = this.createLocationRangeFromDOMRange(getDOMRange())) {\n      if (!rangesAreEqual(locationRange, this.currentLocationRange)) {\n        this.currentLocationRange = locationRange\n        return this.delegate?.locationRangeDidChange?.(this.currentLocationRange.slice(0))\n      }\n    }\n  }\n\n  createDOMRangeFromLocationRange(locationRange) {\n    const rangeStart = this.findContainerAndOffsetFromLocation(locationRange[0])\n    const rangeEnd = rangeIsCollapsed(locationRange)\n      ? rangeStart\n      : this.findContainerAndOffsetFromLocation(locationRange[1]) || rangeStart\n\n    if (rangeStart != null && rangeEnd != null) {\n      const domRange = document.createRange()\n      domRange.setStart(...Array.from(rangeStart || []))\n      domRange.setEnd(...Array.from(rangeEnd || []))\n      return domRange\n    }\n  }\n\n  getLocationAtPoint(point) {\n    const domRange = this.createDOMRangeFromPoint(point)\n    if (domRange) {\n      return this.createLocationRangeFromDOMRange(domRange)?.[0]\n    }\n  }\n\n  domRangeWithinElement(domRange) {\n    if (domRange.collapsed) {\n      return elementContainsNode(this.element, domRange.startContainer)\n    } else {\n      return (\n        elementContainsNode(this.element, domRange.startContainer) &&\n        elementContainsNode(this.element, domRange.endContainer)\n      )\n    }\n  }\n}\n\nSelectionManager.proxyMethod(\"locationMapper.findLocationFromContainerAndOffset\")\nSelectionManager.proxyMethod(\"locationMapper.findContainerAndOffsetFromLocation\")\nSelectionManager.proxyMethod(\"locationMapper.findNodeAndOffsetFromLocation\")\nSelectionManager.proxyMethod(\"pointMapper.createDOMRangeFromPoint\")\nSelectionManager.proxyMethod(\"pointMapper.getClientRectsForDOMRange\")\n\n"
  },
  {
    "path": "src/trix/models/splittable_list.js",
    "content": "/* eslint-disable\n    prefer-const,\n*/\nimport TrixObject from \"trix/core/object\" // Don't override window.Object\n\nimport { spliceArray } from \"trix/core/helpers\"\n\nexport default class SplittableList extends TrixObject {\n  static box(objects) {\n    if (objects instanceof this) {\n      return objects\n    } else {\n      return new this(objects)\n    }\n  }\n\n  constructor(objects = []) {\n    super(...arguments)\n    this.objects = objects.slice(0)\n    this.length = this.objects.length\n  }\n\n  indexOf(object) {\n    return this.objects.indexOf(object)\n  }\n\n  splice(...args) {\n    return new this.constructor(spliceArray(this.objects, ...args))\n  }\n\n  eachObject(callback) {\n    return this.objects.map((object, index) => callback(object, index))\n  }\n\n  insertObjectAtIndex(object, index) {\n    return this.splice(index, 0, object)\n  }\n\n  insertSplittableListAtIndex(splittableList, index) {\n    return this.splice(index, 0, ...splittableList.objects)\n  }\n\n  insertSplittableListAtPosition(splittableList, position) {\n    const [ objects, index ] = this.splitObjectAtPosition(position)\n    return new this.constructor(objects).insertSplittableListAtIndex(splittableList, index)\n  }\n\n  editObjectAtIndex(index, callback) {\n    return this.replaceObjectAtIndex(callback(this.objects[index]), index)\n  }\n\n  replaceObjectAtIndex(object, index) {\n    return this.splice(index, 1, object)\n  }\n\n  removeObjectAtIndex(index) {\n    return this.splice(index, 1)\n  }\n\n  getObjectAtIndex(index) {\n    return this.objects[index]\n  }\n\n  getSplittableListInRange(range) {\n    const [ objects, leftIndex, rightIndex ] = this.splitObjectsAtRange(range)\n    return new this.constructor(objects.slice(leftIndex, rightIndex + 1))\n  }\n\n  selectSplittableList(test) {\n    const objects = this.objects.filter((object) => test(object))\n    return new this.constructor(objects)\n  }\n\n  removeObjectsInRange(range) {\n    const [ objects, leftIndex, rightIndex ] = this.splitObjectsAtRange(range)\n    return new this.constructor(objects).splice(leftIndex, rightIndex - leftIndex + 1)\n  }\n\n  transformObjectsInRange(range, transform) {\n    const [ objects, leftIndex, rightIndex ] = this.splitObjectsAtRange(range)\n    const transformedObjects = objects.map((object, index) =>\n      leftIndex <= index && index <= rightIndex ? transform(object) : object\n    )\n    return new this.constructor(transformedObjects)\n  }\n\n  splitObjectsAtRange(range) {\n    let rightOuterIndex\n    let [ objects, leftInnerIndex, offset ] = this.splitObjectAtPosition(startOfRange(range))\n    ;[ objects, rightOuterIndex ] = new this.constructor(objects).splitObjectAtPosition(endOfRange(range) + offset)\n\n    return [ objects, leftInnerIndex, rightOuterIndex - 1 ]\n  }\n\n  getObjectAtPosition(position) {\n    const { index } = this.findIndexAndOffsetAtPosition(position)\n    return this.objects[index]\n  }\n\n  splitObjectAtPosition(position) {\n    let splitIndex, splitOffset\n    const { index, offset } = this.findIndexAndOffsetAtPosition(position)\n    const objects = this.objects.slice(0)\n    if (index != null) {\n      if (offset === 0) {\n        splitIndex = index\n        splitOffset = 0\n      } else {\n        const object = this.getObjectAtIndex(index)\n        const [ leftObject, rightObject ] = object.splitAtOffset(offset)\n        objects.splice(index, 1, leftObject, rightObject)\n        splitIndex = index + 1\n        splitOffset = leftObject.getLength() - offset\n      }\n    } else {\n      splitIndex = objects.length\n      splitOffset = 0\n    }\n\n    return [ objects, splitIndex, splitOffset ]\n  }\n\n  consolidate() {\n    const objects = []\n    let pendingObject = this.objects[0]\n\n    this.objects.slice(1).forEach((object) => {\n      if (pendingObject.canBeConsolidatedWith?.(object)) {\n        pendingObject = pendingObject.consolidateWith(object)\n      } else {\n        objects.push(pendingObject)\n        pendingObject = object\n      }\n    })\n\n    if (pendingObject) {\n      objects.push(pendingObject)\n    }\n\n    return new this.constructor(objects)\n  }\n\n  consolidateFromIndexToIndex(startIndex, endIndex) {\n    const objects = this.objects.slice(0)\n    const objectsInRange = objects.slice(startIndex, endIndex + 1)\n    const consolidatedInRange = new this.constructor(objectsInRange).consolidate().toArray()\n    return this.splice(startIndex, objectsInRange.length, ...consolidatedInRange)\n  }\n\n  findIndexAndOffsetAtPosition(position) {\n    let index\n    let currentPosition = 0\n    for (index = 0; index < this.objects.length; index++) {\n      const object = this.objects[index]\n      const nextPosition = currentPosition + object.getLength()\n      if (currentPosition <= position && position < nextPosition) {\n        return { index, offset: position - currentPosition }\n      }\n      currentPosition = nextPosition\n    }\n    return { index: null, offset: null }\n  }\n\n  findPositionAtIndexAndOffset(index, offset) {\n    let position = 0\n    for (let currentIndex = 0; currentIndex < this.objects.length; currentIndex++) {\n      const object = this.objects[currentIndex]\n      if (currentIndex < index) {\n        position += object.getLength()\n      } else if (currentIndex === index) {\n        position += offset\n        break\n      }\n    }\n    return position\n  }\n\n  getEndPosition() {\n    if (this.endPosition == null) {\n      this.endPosition = 0\n      this.objects.forEach((object) => this.endPosition += object.getLength())\n    }\n\n    return this.endPosition\n  }\n\n  toString() {\n    return this.objects.join(\"\")\n  }\n\n  toArray() {\n    return this.objects.slice(0)\n  }\n\n  toJSON() {\n    return this.toArray()\n  }\n\n  isEqualTo(splittableList) {\n    return super.isEqualTo(...arguments) || objectArraysAreEqual(this.objects, splittableList?.objects)\n  }\n\n  contentsForInspection() {\n    return {\n      objects: `[${this.objects.map((object) => object.inspect()).join(\", \")}]`,\n    }\n  }\n}\n\nconst objectArraysAreEqual = function(left, right = []) {\n  if (left.length !== right.length) {\n    return false\n  }\n  let result = true\n  for (let index = 0; index < left.length; index++) {\n    const object = left[index]\n    if (result && !object.isEqualTo(right[index])) {\n      result = false\n    }\n  }\n  return result\n}\n\nconst startOfRange = (range) => range[0]\n\nconst endOfRange = (range) => range[1]\n"
  },
  {
    "path": "src/trix/models/string_piece.js",
    "content": "import Piece from \"trix/models/piece\"\n\nimport { normalizeNewlines } from \"trix/core/helpers\"\n\nexport default class StringPiece extends Piece {\n  static fromJSON(pieceJSON) {\n    return new this(pieceJSON.string, pieceJSON.attributes)\n  }\n\n  constructor(string) {\n    super(...arguments)\n    this.string = normalizeNewlines(string)\n    this.length = this.string.length\n  }\n\n  getValue() {\n    return this.string\n  }\n\n  toString() {\n    return this.string.toString()\n  }\n\n  isBlockBreak() {\n    return this.toString() === \"\\n\" && this.getAttribute(\"blockBreak\") === true\n  }\n\n  toJSON() {\n    const result = super.toJSON(...arguments)\n    result.string = this.string\n    return result\n  }\n\n  // Splittable\n\n  canBeConsolidatedWith(piece) {\n    return piece && this.hasSameConstructorAs(piece) && this.hasSameAttributesAsPiece(piece)\n  }\n\n  consolidateWith(piece) {\n    return new this.constructor(this.toString() + piece.toString(), this.attributes)\n  }\n\n  splitAtOffset(offset) {\n    let left, right\n    if (offset === 0) {\n      left = null\n      right = this\n    } else if (offset === this.length) {\n      left = this\n      right = null\n    } else {\n      left = new this.constructor(this.string.slice(0, offset), this.attributes)\n      right = new this.constructor(this.string.slice(offset), this.attributes)\n    }\n    return [ left, right ]\n  }\n\n  toConsole() {\n    let { string } = this\n    if (string.length > 15) {\n      string = string.slice(0, 14) + \"…\"\n    }\n    return JSON.stringify(string.toString())\n  }\n}\n\nPiece.registerType(\"string\", StringPiece)\n"
  },
  {
    "path": "src/trix/models/text.js",
    "content": "import TrixObject from \"trix/core/object\" // Don't override window.Object\n\nimport { getDirection } from \"trix/core/helpers\"\n\nimport Piece from \"trix/models/piece\"\nimport AttachmentPiece from \"trix/models/attachment_piece\"\nimport StringPiece from \"trix/models/string_piece\"\nimport SplittableList from \"trix/models/splittable_list\"\n\nimport Hash from \"trix/core/collections/hash\"\n\nexport default class Text extends TrixObject {\n  static textForAttachmentWithAttributes(attachment, attributes) {\n    const piece = new AttachmentPiece(attachment, attributes)\n    return new this([ piece ])\n  }\n\n  static textForStringWithAttributes(string, attributes) {\n    const piece = new StringPiece(string, attributes)\n    return new this([ piece ])\n  }\n\n  static fromJSON(textJSON) {\n    const pieces = Array.from(textJSON).map((pieceJSON) => Piece.fromJSON(pieceJSON))\n    return new this(pieces)\n  }\n\n  constructor(pieces = []) {\n    super(...arguments)\n    const notEmpty = pieces.filter((piece) => !piece.isEmpty())\n    this.pieceList = new SplittableList(notEmpty)\n  }\n\n  copy() {\n    return this.copyWithPieceList(this.pieceList)\n  }\n\n  copyWithPieceList(pieceList) {\n    return new this.constructor(pieceList.consolidate().toArray())\n  }\n\n  copyUsingObjectMap(objectMap) {\n    const pieces = this.getPieces().map((piece) => objectMap.find(piece) || piece)\n    return new this.constructor(pieces)\n  }\n\n  appendText(text) {\n    return this.insertTextAtPosition(text, this.getLength())\n  }\n\n  insertTextAtPosition(text, position) {\n    return this.copyWithPieceList(this.pieceList.insertSplittableListAtPosition(text.pieceList, position))\n  }\n\n  removeTextAtRange(range) {\n    return this.copyWithPieceList(this.pieceList.removeObjectsInRange(range))\n  }\n\n  replaceTextAtRange(text, range) {\n    return this.removeTextAtRange(range).insertTextAtPosition(text, range[0])\n  }\n\n  moveTextFromRangeToPosition(range, position) {\n    if (range[0] <= position && position <= range[1]) return\n    const text = this.getTextAtRange(range)\n    const length = text.getLength()\n    if (range[0] < position) {\n      position -= length\n    }\n    return this.removeTextAtRange(range).insertTextAtPosition(text, position)\n  }\n\n  addAttributeAtRange(attribute, value, range) {\n    const attributes = {}\n    attributes[attribute] = value\n    return this.addAttributesAtRange(attributes, range)\n  }\n\n  addAttributesAtRange(attributes, range) {\n    return this.copyWithPieceList(\n      this.pieceList.transformObjectsInRange(range, (piece) => piece.copyWithAdditionalAttributes(attributes))\n    )\n  }\n\n  removeAttributeAtRange(attribute, range) {\n    return this.copyWithPieceList(\n      this.pieceList.transformObjectsInRange(range, (piece) => piece.copyWithoutAttribute(attribute))\n    )\n  }\n\n  setAttributesAtRange(attributes, range) {\n    return this.copyWithPieceList(\n      this.pieceList.transformObjectsInRange(range, (piece) => piece.copyWithAttributes(attributes))\n    )\n  }\n\n  getAttributesAtPosition(position) {\n    return this.pieceList.getObjectAtPosition(position)?.getAttributes() || {}\n  }\n\n  getCommonAttributes() {\n    const objects = Array.from(this.pieceList.toArray()).map((piece) => piece.getAttributes())\n    return Hash.fromCommonAttributesOfObjects(objects).toObject()\n  }\n\n  getCommonAttributesAtRange(range) {\n    return this.getTextAtRange(range).getCommonAttributes() || {}\n  }\n\n  getExpandedRangeForAttributeAtOffset(attributeName, offset) {\n    let right\n    let left = right = offset\n    const length = this.getLength()\n\n    while (left > 0 && this.getCommonAttributesAtRange([ left - 1, right ])[attributeName]) {\n      left--\n    }\n    while (right < length && this.getCommonAttributesAtRange([ offset, right + 1 ])[attributeName]) {\n      right++\n    }\n\n    return [ left, right ]\n  }\n\n  getTextAtRange(range) {\n    return this.copyWithPieceList(this.pieceList.getSplittableListInRange(range))\n  }\n\n  getStringAtRange(range) {\n    return this.pieceList.getSplittableListInRange(range).toString()\n  }\n\n  getStringAtPosition(position) {\n    return this.getStringAtRange([ position, position + 1 ])\n  }\n\n  startsWithString(string) {\n    return this.getStringAtRange([ 0, string.length ]) === string\n  }\n\n  endsWithString(string) {\n    const length = this.getLength()\n    return this.getStringAtRange([ length - string.length, length ]) === string\n  }\n\n  getAttachmentPieces() {\n    return this.pieceList.toArray().filter((piece) => !!piece.attachment)\n  }\n\n  getAttachments() {\n    return this.getAttachmentPieces().map((piece) => piece.attachment)\n  }\n\n  getAttachmentAndPositionById(attachmentId) {\n    let position = 0\n    for (const piece of this.pieceList.toArray()) {\n      if (piece.attachment?.id === attachmentId) {\n        return { attachment: piece.attachment, position }\n      }\n      position += piece.length\n    }\n    return { attachment: null, position: null }\n  }\n\n  getAttachmentById(attachmentId) {\n    const { attachment } = this.getAttachmentAndPositionById(attachmentId)\n    return attachment\n  }\n\n  getRangeOfAttachment(attachment) {\n    const attachmentAndPosition = this.getAttachmentAndPositionById(attachment.id)\n    const position = attachmentAndPosition.position\n    attachment = attachmentAndPosition.attachment\n    if (attachment) {\n      return [ position, position + 1 ]\n    }\n  }\n\n  updateAttributesForAttachment(attributes, attachment) {\n    const range = this.getRangeOfAttachment(attachment)\n    if (range) {\n      return this.addAttributesAtRange(attributes, range)\n    } else {\n      return this\n    }\n  }\n\n  getLength() {\n    return this.pieceList.getEndPosition()\n  }\n\n  isEmpty() {\n    return this.getLength() === 0\n  }\n\n  isEqualTo(text) {\n    return super.isEqualTo(text) || text?.pieceList?.isEqualTo(this.pieceList)\n  }\n\n  isBlockBreak() {\n    return this.getLength() === 1 && this.pieceList.getObjectAtIndex(0).isBlockBreak()\n  }\n\n  eachPiece(callback) {\n    return this.pieceList.eachObject(callback)\n  }\n\n  getPieces() {\n    return this.pieceList.toArray()\n  }\n\n  getPieceAtPosition(position) {\n    return this.pieceList.getObjectAtPosition(position)\n  }\n\n  contentsForInspection() {\n    return { pieceList: this.pieceList.inspect() }\n  }\n\n  toSerializableText() {\n    const pieceList = this.pieceList.selectSplittableList((piece) => piece.isSerializable())\n    return this.copyWithPieceList(pieceList)\n  }\n\n  toString() {\n    return this.pieceList.toString()\n  }\n\n  toJSON() {\n    return this.pieceList.toJSON()\n  }\n\n  toConsole() {\n    return JSON.stringify(this.pieceList.toArray().map((piece) => JSON.parse(piece.toConsole())))\n  }\n\n  // BIDI\n\n  getDirection() {\n    return getDirection(this.toString())\n  }\n\n  isRTL() {\n    return this.getDirection() === \"rtl\"\n  }\n}\n"
  },
  {
    "path": "src/trix/models/undo_manager.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nexport default class UndoManager extends BasicObject {\n  constructor(composition) {\n    super(...arguments)\n    this.composition = composition\n    this.undoEntries = []\n    this.redoEntries = []\n  }\n\n  recordUndoEntry(description, { context, consolidatable } = {}) {\n    const previousEntry = this.undoEntries.slice(-1)[0]\n\n    if (!consolidatable || !entryHasDescriptionAndContext(previousEntry, description, context)) {\n      const undoEntry = this.createEntry({ description, context })\n      this.undoEntries.push(undoEntry)\n      this.redoEntries = []\n    }\n  }\n\n  undo() {\n    const undoEntry = this.undoEntries.pop()\n    if (undoEntry) {\n      const redoEntry = this.createEntry(undoEntry)\n      this.redoEntries.push(redoEntry)\n      return this.composition.loadSnapshot(undoEntry.snapshot)\n    }\n  }\n\n  redo() {\n    const redoEntry = this.redoEntries.pop()\n    if (redoEntry) {\n      const undoEntry = this.createEntry(redoEntry)\n      this.undoEntries.push(undoEntry)\n      return this.composition.loadSnapshot(redoEntry.snapshot)\n    }\n  }\n\n  canUndo() {\n    return this.undoEntries.length > 0\n  }\n\n  canRedo() {\n    return this.redoEntries.length > 0\n  }\n\n  // Private\n\n  createEntry({ description, context } = {}) {\n    return {\n      description: description?.toString(),\n      context: JSON.stringify(context),\n      snapshot: this.composition.getSnapshot(),\n    }\n  }\n}\n\nconst entryHasDescriptionAndContext = (entry, description, context) =>\n  entry?.description === description?.toString() && entry?.context === JSON.stringify(context)\n"
  },
  {
    "path": "src/trix/observers/index.js",
    "content": "export { default as MutationObserver } from \"./mutation_observer\"\nexport { default as SelectionChangeObserver } from \"./selection_change_observer\"\n"
  },
  {
    "path": "src/trix/observers/mutation_observer.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nimport {\n  findClosestElementFromNode,\n  nodeIsBlockStartComment,\n  nodeIsEmptyTextNode,\n  normalizeSpaces,\n  summarizeStringChange,\n  tagName,\n} from \"trix/core/helpers\"\n\nconst mutableAttributeName = \"data-trix-mutable\"\nconst mutableSelector = `[${mutableAttributeName}]`\n\nconst options = {\n  attributes: true,\n  childList: true,\n  characterData: true,\n  characterDataOldValue: true,\n  subtree: true,\n}\n\nexport default class MutationObserver extends BasicObject {\n  constructor(element) {\n    super(element)\n    this.didMutate = this.didMutate.bind(this)\n    this.element = element\n    this.observer = new window.MutationObserver(this.didMutate)\n    this.start()\n  }\n\n  start() {\n    this.reset()\n    return this.observer.observe(this.element, options)\n  }\n\n  stop() {\n    return this.observer.disconnect()\n  }\n\n  didMutate(mutations) {\n    this.mutations.push(...Array.from(this.findSignificantMutations(mutations) || []))\n\n    if (this.mutations.length) {\n      this.delegate?.elementDidMutate?.(this.getMutationSummary())\n      return this.reset()\n    }\n  }\n\n  // Private\n\n  reset() {\n    this.mutations = []\n  }\n\n  findSignificantMutations(mutations) {\n    return mutations.filter((mutation) => {\n      return this.mutationIsSignificant(mutation)\n    })\n  }\n\n  mutationIsSignificant(mutation) {\n    if (this.nodeIsMutable(mutation.target)) {\n      return false\n    }\n    for (const node of Array.from(this.nodesModifiedByMutation(mutation))) {\n      if (this.nodeIsSignificant(node)) return true\n    }\n    return false\n  }\n\n  nodeIsSignificant(node) {\n    return node !== this.element && !this.nodeIsMutable(node) && !nodeIsEmptyTextNode(node)\n  }\n\n  nodeIsMutable(node) {\n    return findClosestElementFromNode(node, { matchingSelector: mutableSelector })\n  }\n\n  nodesModifiedByMutation(mutation) {\n    const nodes = []\n    switch (mutation.type) {\n      case \"attributes\":\n        if (mutation.attributeName !== mutableAttributeName) {\n          nodes.push(mutation.target)\n        }\n        break\n      case \"characterData\":\n        // Changes to text nodes should consider the parent element\n        nodes.push(mutation.target.parentNode)\n        nodes.push(mutation.target)\n        break\n      case \"childList\":\n        // Consider each added or removed node\n        nodes.push(...Array.from(mutation.addedNodes || []))\n        nodes.push(...Array.from(mutation.removedNodes || []))\n        break\n    }\n    return nodes\n  }\n\n  getMutationSummary() {\n    return this.getTextMutationSummary()\n  }\n\n  getTextMutationSummary() {\n    const { additions, deletions } = this.getTextChangesFromCharacterData()\n    const textChanges = this.getTextChangesFromChildList()\n\n    Array.from(textChanges.additions).forEach((addition) => {\n      if (!Array.from(additions).includes(addition)) {\n        additions.push(addition)\n      }\n    })\n\n    deletions.push(...Array.from(textChanges.deletions || []))\n\n    const summary = {}\n\n    const added = additions.join(\"\")\n    if (added) {\n      summary.textAdded = added\n    }\n\n    const deleted = deletions.join(\"\")\n    if (deleted) {\n      summary.textDeleted = deleted\n    }\n\n    return summary\n  }\n\n  getMutationsByType(type) {\n    return Array.from(this.mutations).filter((mutation) => mutation.type === type)\n  }\n\n  getTextChangesFromChildList() {\n    let textAdded, textRemoved\n    const addedNodes = []\n    const removedNodes = []\n\n    Array.from(this.getMutationsByType(\"childList\")).forEach((mutation) => {\n      addedNodes.push(...Array.from(mutation.addedNodes || []))\n      removedNodes.push(...Array.from(mutation.removedNodes || []))\n    })\n\n    const singleBlockCommentRemoved =\n      addedNodes.length === 0 && removedNodes.length === 1 && nodeIsBlockStartComment(removedNodes[0])\n\n    if (singleBlockCommentRemoved) {\n      textAdded = []\n      textRemoved = [ \"\\n\" ]\n    } else {\n      textAdded = getTextForNodes(addedNodes)\n      textRemoved = getTextForNodes(removedNodes)\n    }\n\n    const additions = textAdded.filter((text, index) => text !== textRemoved[index]).map(normalizeSpaces)\n    const deletions = textRemoved.filter((text, index) => text !== textAdded[index]).map(normalizeSpaces)\n\n    return { additions, deletions }\n  }\n\n  getTextChangesFromCharacterData() {\n    let added, removed\n    const characterMutations = this.getMutationsByType(\"characterData\")\n\n    if (characterMutations.length) {\n      const startMutation = characterMutations[0],\n        endMutation = characterMutations[characterMutations.length - 1]\n\n      const oldString = normalizeSpaces(startMutation.oldValue)\n      const newString = normalizeSpaces(endMutation.target.data)\n      const summarized = summarizeStringChange(oldString, newString)\n      added = summarized.added\n      removed = summarized.removed\n    }\n\n    return {\n      additions: added ? [ added ] : [],\n      deletions: removed ? [ removed ] : [],\n    }\n  }\n}\n\nconst getTextForNodes = function(nodes = []) {\n  const text = []\n  for (const node of Array.from(nodes)) {\n    switch (node.nodeType) {\n      case Node.TEXT_NODE:\n        text.push(node.data)\n        break\n      case Node.ELEMENT_NODE:\n        if (tagName(node) === \"br\") {\n          text.push(\"\\n\")\n        } else {\n          text.push(...Array.from(getTextForNodes(node.childNodes) || []))\n        }\n        break\n    }\n  }\n  return text\n}\n"
  },
  {
    "path": "src/trix/observers/selection_change_observer.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\n\nexport default class SelectionChangeObserver extends BasicObject {\n  constructor() {\n    super(...arguments)\n    this.update = this.update.bind(this)\n    this.selectionManagers = []\n  }\n\n  start() {\n    if (!this.started) {\n      this.started = true\n      document.addEventListener(\"selectionchange\", this.update, true)\n    }\n  }\n\n  stop() {\n    if (this.started) {\n      this.started = false\n      return document.removeEventListener(\"selectionchange\", this.update, true)\n    }\n  }\n\n  registerSelectionManager(selectionManager) {\n    if (!this.selectionManagers.includes(selectionManager)) {\n      this.selectionManagers.push(selectionManager)\n      return this.start()\n    }\n  }\n\n  unregisterSelectionManager(selectionManager) {\n    this.selectionManagers = this.selectionManagers.filter((sm) => sm !== selectionManager)\n    if (this.selectionManagers.length === 0) {\n      return this.stop()\n    }\n  }\n\n  notifySelectionManagersOfSelectionChange() {\n    return this.selectionManagers.map((selectionManager) => selectionManager.selectionDidChange())\n  }\n\n  update() {\n    this.notifySelectionManagersOfSelectionChange()\n  }\n\n  reset() {\n    this.update()\n  }\n}\n\nexport const selectionChangeObserver = new SelectionChangeObserver()\n\nexport const getDOMSelection = function() {\n  const selection = window.getSelection()\n  if (selection.rangeCount > 0) {\n    return selection\n  }\n}\n\nexport const getDOMRange = function() {\n  const domRange = getDOMSelection()?.getRangeAt(0)\n  if (domRange) {\n    if (!domRangeIsPrivate(domRange)) {\n      return domRange\n    }\n  }\n}\n\nexport const setDOMRange = function(domRange) {\n  const selection = window.getSelection()\n  selection.removeAllRanges()\n  selection.addRange(domRange)\n  return selectionChangeObserver.update()\n}\n\n// In Firefox, clicking certain <input> elements changes the selection to a\n// private element used to draw its UI. Attempting to access properties of those\n// elements throws an error.\n// https://bugzilla.mozilla.org/show_bug.cgi?id=208427\nconst domRangeIsPrivate = (domRange) => nodeIsPrivate(domRange.startContainer) || nodeIsPrivate(domRange.endContainer)\n\nconst nodeIsPrivate = (node) => !Object.getPrototypeOf(node)\n"
  },
  {
    "path": "src/trix/operations/file_verification_operation.js",
    "content": "/* eslint-disable\n    no-empty,\n*/\nimport Operation from \"trix/core/utilities/operation\"\n\nexport default class FileVerificationOperation extends Operation {\n  constructor(file) {\n    super(...arguments)\n    this.file = file\n  }\n\n  perform(callback) {\n    const reader = new FileReader()\n\n    reader.onerror = () => callback(false)\n\n    reader.onload = () => {\n      reader.onerror = null\n      try {\n        reader.abort()\n      } catch (error) {}\n      return callback(true, this.file)\n    }\n\n    return reader.readAsArrayBuffer(this.file)\n  }\n}\n"
  },
  {
    "path": "src/trix/operations/image_preload_operation.js",
    "content": "import Operation from \"trix/core/utilities/operation\"\n\nexport default class ImagePreloadOperation extends Operation {\n  constructor(url) {\n    super(...arguments)\n    this.url = url\n  }\n\n  perform(callback) {\n    const image = new Image()\n\n    image.onload = () => {\n      image.width = this.width = image.naturalWidth\n      image.height = this.height = image.naturalHeight\n      return callback(true, image)\n    }\n\n    image.onerror = () => callback(false)\n\n    image.src = this.url\n  }\n}\n"
  },
  {
    "path": "src/trix/operations/index.js",
    "content": "export { default as FileVerificationOperation } from \"./file_verification_operation\"\nexport { default as ImagePreloadOperation } from \"./image_preload_operation\"\n"
  },
  {
    "path": "src/trix/trix.js",
    "content": "import { version } from \"../../package.json\"\n\nimport * as config from \"trix/config\"\nimport * as core from \"trix/core\"\nimport * as models from \"trix/models\"\nimport * as views from \"trix/views\"\nimport * as controllers from \"trix/controllers\"\nimport * as observers from \"trix/observers\"\nimport * as operations from \"trix/operations\"\nimport * as elements from \"trix/elements\"\nimport * as filters from \"trix/filters\"\n\nconst Trix = {\n  VERSION: version,\n  config,\n  core,\n  models,\n  views,\n  controllers,\n  observers,\n  operations,\n  elements,\n  filters\n}\n\n// Expose models under the Trix constant for compatibility with v1\nObject.assign(Trix, models)\n\nfunction start() {\n  if (!customElements.get(\"trix-toolbar\")) {\n    customElements.define(\"trix-toolbar\", elements.TrixToolbarElement)\n  }\n\n  if (!customElements.get(\"trix-editor\")) {\n    customElements.define(\"trix-editor\", elements.TrixEditorElement)\n  }\n}\n\nwindow.Trix = Trix\nsetTimeout(start, 0)\n\nexport default Trix\n"
  },
  {
    "path": "src/trix/views/attachment_view.js",
    "content": "import * as config from \"trix/config\"\nimport { ZERO_WIDTH_SPACE } from \"trix/constants\"\nimport { copyObject, makeElement } from \"trix/core/helpers\"\nimport ObjectView from \"trix/views/object_view\"\nimport HTMLSanitizer from \"trix/models/html_sanitizer\"\nimport DOMPurify from \"dompurify\"\n\nconst { css } = config\n\nexport default class AttachmentView extends ObjectView {\n  constructor() {\n    super(...arguments)\n    this.attachment = this.object\n    this.attachment.uploadProgressDelegate = this\n    this.attachmentPiece = this.options.piece\n  }\n\n  createContentNodes() {\n    return []\n  }\n\n  createNodes() {\n    let innerElement\n    const figure = innerElement = makeElement({\n      tagName: \"figure\",\n      className: this.getClassName(),\n      data: this.getData(),\n      editable: false,\n    })\n\n    const href = this.getHref()\n    if (href) {\n      innerElement = makeElement({ tagName: \"a\", editable: false, attributes: { href, tabindex: -1 } })\n      figure.appendChild(innerElement)\n    }\n\n    if (this.attachment.hasContent()) {\n      HTMLSanitizer.setHTML(innerElement, this.attachment.getContent())\n    } else {\n      this.createContentNodes().forEach((node) => {\n        innerElement.appendChild(node)\n      })\n    }\n\n    innerElement.appendChild(this.createCaptionElement())\n\n    if (this.attachment.isPending()) {\n      this.progressElement = makeElement({\n        tagName: \"progress\",\n        attributes: {\n          class: css.attachmentProgress,\n          value: this.attachment.getUploadProgress(),\n          max: 100,\n        },\n        data: {\n          trixMutable: true,\n          trixStoreKey: [ \"progressElement\", this.attachment.id ].join(\"/\"),\n        },\n      })\n\n      figure.appendChild(this.progressElement)\n    }\n\n    return [ createCursorTarget(\"left\"), figure, createCursorTarget(\"right\") ]\n  }\n\n  createCaptionElement() {\n    const figcaption = makeElement({ tagName: \"figcaption\", className: css.attachmentCaption })\n    const caption = this.attachmentPiece.getCaption()\n    if (caption) {\n      figcaption.classList.add(`${css.attachmentCaption}--edited`)\n      figcaption.textContent = caption\n    } else {\n      let name, size\n      const captionConfig = this.getCaptionConfig()\n      if (captionConfig.name) {\n        name = this.attachment.getFilename()\n      }\n      if (captionConfig.size) {\n        size = this.attachment.getFormattedFilesize()\n      }\n\n      if (name) {\n        const nameElement = makeElement({ tagName: \"span\", className: css.attachmentName, textContent: name })\n        figcaption.appendChild(nameElement)\n      }\n\n      if (size) {\n        if (name) {\n          figcaption.appendChild(document.createTextNode(\" \"))\n        }\n        const sizeElement = makeElement({ tagName: \"span\", className: css.attachmentSize, textContent: size })\n        figcaption.appendChild(sizeElement)\n      }\n    }\n\n    return figcaption\n  }\n\n  getClassName() {\n    const names = [ css.attachment, `${css.attachment}--${this.attachment.getType()}` ]\n    const extension = this.attachment.getExtension()\n    if (extension) {\n      names.push(`${css.attachment}--${extension}`)\n    }\n    return names.join(\" \")\n  }\n\n  getData() {\n    const data = {\n      trixAttachment: JSON.stringify(this.attachment),\n      trixContentType: this.attachment.getContentType(),\n      trixId: this.attachment.id,\n    }\n\n    const { attributes } = this.attachmentPiece\n    if (!attributes.isEmpty()) {\n      data.trixAttributes = JSON.stringify(attributes)\n    }\n\n    if (this.attachment.isPending()) {\n      data.trixSerialize = false\n    }\n\n    return data\n  }\n\n  getHref() {\n    if (!htmlContainsTagName(this.attachment.getContent(), \"a\")) {\n      const href = this.attachment.getHref()\n      if (href && DOMPurify.isValidAttribute(\"a\", \"href\", href)) {\n        return href\n      }\n    }\n  }\n\n  getCaptionConfig() {\n    const type = this.attachment.getType()\n    const captionConfig = copyObject(config.attachments[type]?.caption)\n    if (type === \"file\") {\n      captionConfig.name = true\n    }\n    return captionConfig\n  }\n\n  findProgressElement() {\n    return this.findElement()?.querySelector(\"progress\")\n  }\n\n  // Attachment delegate\n\n  attachmentDidChangeUploadProgress() {\n    const value = this.attachment.getUploadProgress()\n    const progressElement = this.findProgressElement()\n    if (progressElement) {\n      progressElement.value = value\n    }\n  }\n}\n\nconst createCursorTarget = (name) =>\n  makeElement({\n    tagName: \"span\",\n    textContent: ZERO_WIDTH_SPACE,\n    data: {\n      trixCursorTarget: name,\n      trixSerialize: false,\n    },\n  })\n\nconst htmlContainsTagName = function(html, tagName) {\n  const div = makeElement(\"div\")\n  HTMLSanitizer.setHTML(div, html || \"\")\n  return div.querySelector(tagName)\n}\n"
  },
  {
    "path": "src/trix/views/block_view.js",
    "content": "import * as config from \"trix/config\"\nimport ObjectView from \"trix/views/object_view\"\nimport TextView from \"trix/views/text_view\"\n\nimport { getBlockConfig, makeElement } from \"trix/core/helpers\"\nconst { css } = config\n\nexport default class BlockView extends ObjectView {\n  constructor() {\n    super(...arguments)\n    this.block = this.object\n    this.attributes = this.block.getAttributes()\n  }\n\n  createNodes() {\n    const comment = document.createComment(\"block\")\n    const nodes = [ comment ]\n    if (this.block.isEmpty()) {\n      nodes.push(makeElement(\"br\"))\n    } else {\n      const textConfig = getBlockConfig(this.block.getLastAttribute())?.text\n      const textView = this.findOrCreateCachedChildView(TextView, this.block.text, { textConfig })\n      nodes.push(...Array.from(textView.getNodes() || []))\n      if (this.shouldAddExtraNewlineElement()) {\n        nodes.push(makeElement(\"br\"))\n      }\n    }\n\n    if (this.attributes.length) {\n      return nodes\n    } else {\n      let attributes\n      const { tagName } = config.blockAttributes.default\n      if (this.block.isRTL()) {\n        attributes = { dir: \"rtl\" }\n      }\n\n      const element = makeElement({ tagName, attributes })\n      nodes.forEach((node) => element.appendChild(node))\n      return [ element ]\n    }\n  }\n\n  createContainerElement(depth) {\n    const attributes = {}\n    let className\n    const attributeName = this.attributes[depth]\n\n    const { tagName, htmlAttributes = [] } = getBlockConfig(attributeName)\n\n    if (depth === 0 && this.block.isRTL()) {\n      Object.assign(attributes, { dir: \"rtl\" })\n    }\n\n    if (attributeName === \"attachmentGallery\") {\n      const size = this.block.getBlockBreakPosition()\n      className = `${css.attachmentGallery} ${css.attachmentGallery}--${size}`\n    }\n\n    Object.entries(this.block.htmlAttributes).forEach(([ name, value ]) => {\n      if (htmlAttributes.includes(name)) {\n        attributes[name] = value\n      }\n    })\n\n    return makeElement({ tagName, className, attributes })\n  }\n\n  // A single <br> at the end of a block element has no visual representation\n  // so add an extra one.\n  shouldAddExtraNewlineElement() {\n    return /\\n\\n$/.test(this.block.toString())\n  }\n}\n"
  },
  {
    "path": "src/trix/views/document_view.js",
    "content": "import { createEvent, makeElement } from \"trix/core/helpers\"\n\nimport ElementStore from \"trix/core/collections/element_store\"\nimport ObjectGroup from \"trix/core/collections/object_group\"\nimport ObjectView from \"trix/views/object_view\"\nimport BlockView from \"trix/views/block_view\"\n\nimport { defer } from \"trix/core/helpers\"\n\nexport default class DocumentView extends ObjectView {\n  static render(document) {\n    const element = makeElement(\"div\")\n    const view = new this(document, { element })\n    view.render()\n    view.sync()\n    return element\n  }\n\n  constructor() {\n    super(...arguments)\n    this.element = this.options.element\n    this.elementStore = new ElementStore()\n    this.setDocument(this.object)\n  }\n\n  setDocument(document) {\n    if (!document.isEqualTo(this.document)) {\n      this.document = this.object = document\n    }\n  }\n\n  render() {\n    this.childViews = []\n\n    this.shadowElement = makeElement(\"div\")\n\n    if (!this.document.isEmpty()) {\n      const objects = ObjectGroup.groupObjects(this.document.getBlocks(), { asTree: true })\n\n      Array.from(objects).forEach((object) => {\n        const view = this.findOrCreateCachedChildView(BlockView, object)\n        Array.from(view.getNodes()).map((node) => this.shadowElement.appendChild(node))\n      })\n    }\n  }\n\n  isSynced() {\n    return elementsHaveEqualHTML(this.shadowElement, this.element)\n  }\n\n  sync() {\n    const render = (element, documentFragment) => {\n      while (element.lastChild) {\n        element.removeChild(element.lastChild)\n      }\n      element.appendChild(documentFragment)\n    }\n\n    const event = createEvent(\"trix-before-render\", { cancelable: false, attributes: { render } })\n    this.element.dispatchEvent(event)\n\n    const fragment = this.createDocumentFragmentForSync()\n    event.render(this.element, fragment)\n    return this.didSync()\n  }\n\n  // Private\n\n  didSync() {\n    this.elementStore.reset(findStoredElements(this.element))\n    return defer(() => this.garbageCollectCachedViews())\n  }\n\n  createDocumentFragmentForSync() {\n    const fragment = document.createDocumentFragment()\n\n    Array.from(this.shadowElement.childNodes).forEach((node) => {\n      fragment.appendChild(node.cloneNode(true))\n    })\n\n    Array.from(findStoredElements(fragment)).forEach((element) => {\n      const storedElement = this.elementStore.remove(element)\n      if (storedElement) {\n        element.parentNode.replaceChild(storedElement, element)\n      }\n    })\n\n    return fragment\n  }\n}\n\nconst findStoredElements = (element) => element.querySelectorAll(\"[data-trix-store-key]\")\n\nconst elementsHaveEqualHTML = (element, otherElement) =>\n  ignoreSpaces(element.innerHTML) === ignoreSpaces(otherElement.innerHTML)\n\nconst ignoreSpaces = (html) => html.replace(/&nbsp;/g, \" \")\n"
  },
  {
    "path": "src/trix/views/index.js",
    "content": "export { default as ObjectView } from \"./object_view\"\nexport { default as AttachmentView } from \"./attachment_view\"\nexport { default as BlockView } from \"./block_view\"\nexport { default as DocumentView } from \"./document_view\"\nexport { default as PieceView } from \"./piece_view\"\nexport { default as PreviewableAttachmentView } from \"./previewable_attachment_view\"\nexport { default as TextView } from \"./text_view\"\n"
  },
  {
    "path": "src/trix/views/object_view.js",
    "content": "import BasicObject from \"trix/core/basic_object\"\nimport ObjectGroup from \"trix/core/collections/object_group\"\n\nexport default class ObjectView extends BasicObject {\n  constructor(object, options = {}) {\n    super(...arguments)\n    this.object = object\n    this.options = options\n    this.childViews = []\n    this.rootView = this\n  }\n\n  getNodes() {\n    if (!this.nodes) { this.nodes = this.createNodes() }\n    return this.nodes.map((node) => node.cloneNode(true))\n  }\n\n  invalidate() {\n    this.nodes = null\n    this.childViews = []\n    return this.parentView?.invalidate()\n  }\n\n  invalidateViewForObject(object) {\n    return this.findViewForObject(object)?.invalidate()\n  }\n\n  findOrCreateCachedChildView(viewClass, object, options) {\n    let view = this.getCachedViewForObject(object)\n    if (view) {\n      this.recordChildView(view)\n    } else {\n      view = this.createChildView(...arguments)\n      this.cacheViewForObject(view, object)\n    }\n    return view\n  }\n\n  createChildView(viewClass, object, options = {}) {\n    if (object instanceof ObjectGroup) {\n      options.viewClass = viewClass\n      viewClass = ObjectGroupView\n    }\n\n    const view = new viewClass(object, options)\n    return this.recordChildView(view)\n  }\n\n  recordChildView(view) {\n    view.parentView = this\n    view.rootView = this.rootView\n    this.childViews.push(view)\n    return view\n  }\n\n  getAllChildViews() {\n    let views = []\n\n    this.childViews.forEach((childView) => {\n      views.push(childView)\n      views = views.concat(childView.getAllChildViews())\n    })\n\n    return views\n  }\n\n  findElement() {\n    return this.findElementForObject(this.object)\n  }\n\n  findElementForObject(object) {\n    const id = object?.id\n    if (id) {\n      return this.rootView.element.querySelector(`[data-trix-id='${id}']`)\n    }\n  }\n\n  findViewForObject(object) {\n    for (const view of this.getAllChildViews()) {\n      if (view.object === object) {\n        return view\n      }\n    }\n  }\n\n  getViewCache() {\n    if (this.rootView === this) {\n      if (this.isViewCachingEnabled()) {\n        if (!this.viewCache) { this.viewCache = {} }\n        return this.viewCache\n      }\n    } else {\n      return this.rootView.getViewCache()\n    }\n  }\n\n  isViewCachingEnabled() {\n    return this.shouldCacheViews !== false\n  }\n\n  enableViewCaching() {\n    this.shouldCacheViews = true\n  }\n\n  disableViewCaching() {\n    this.shouldCacheViews = false\n  }\n\n  getCachedViewForObject(object) {\n    return this.getViewCache()?.[object.getCacheKey()]\n  }\n\n  cacheViewForObject(view, object) {\n    const cache = this.getViewCache()\n    if (cache) {\n      cache[object.getCacheKey()] = view\n    }\n  }\n\n  garbageCollectCachedViews() {\n    const cache = this.getViewCache()\n    if (cache) {\n      const views = this.getAllChildViews().concat(this)\n      const objectKeys = views.map((view) => view.object.getCacheKey())\n      for (const key in cache) {\n        if (!objectKeys.includes(key)) {\n          delete cache[key]\n        }\n      }\n    }\n  }\n}\n\nexport class ObjectGroupView extends ObjectView {\n  constructor() {\n    super(...arguments)\n    this.objectGroup = this.object\n    this.viewClass = this.options.viewClass\n    delete this.options.viewClass\n  }\n\n  getChildViews() {\n    if (!this.childViews.length) {\n      Array.from(this.objectGroup.getObjects()).forEach((object) => {\n        this.findOrCreateCachedChildView(this.viewClass, object, this.options)\n      })\n    }\n    return this.childViews\n  }\n\n  createNodes() {\n    const element = this.createContainerElement()\n\n    this.getChildViews().forEach((view) => {\n      Array.from(view.getNodes()).forEach((node) => {\n        element.appendChild(node)\n      })\n    })\n\n    return [ element ]\n  }\n\n  createContainerElement(depth = this.objectGroup.getDepth()) {\n    return this.getChildViews()[0].createContainerElement(depth)\n  }\n}\n"
  },
  {
    "path": "src/trix/views/piece_view.js",
    "content": "/* eslint-disable\n    no-useless-escape,\n    no-var,\n*/\nimport { NON_BREAKING_SPACE } from \"trix/constants\"\n\nimport ObjectView from \"trix/views/object_view\"\nimport AttachmentView from \"trix/views/attachment_view\"\nimport PreviewableAttachmentView from \"trix/views/previewable_attachment_view\"\n\nimport { findInnerElement, getTextConfig, makeElement } from \"trix/core/helpers\"\n\nexport default class PieceView extends ObjectView {\n  constructor() {\n    super(...arguments)\n    this.piece = this.object\n    this.attributes = this.piece.getAttributes()\n    this.textConfig = this.options.textConfig\n    this.context = this.options.context\n\n    if (this.piece.attachment) {\n      this.attachment = this.piece.attachment\n    } else {\n      this.string = this.piece.toString()\n    }\n  }\n\n  createNodes() {\n    let nodes = this.attachment ? this.createAttachmentNodes() : this.createStringNodes()\n    const element = this.createElement()\n    if (element) {\n      const innerElement = findInnerElement(element)\n      Array.from(nodes).forEach((node) => {\n        innerElement.appendChild(node)\n      })\n      nodes = [ element ]\n    }\n    return nodes\n  }\n\n  createAttachmentNodes() {\n    const constructor = this.attachment.isPreviewable() ? PreviewableAttachmentView : AttachmentView\n\n    const view = this.createChildView(constructor, this.piece.attachment, { piece: this.piece })\n    return view.getNodes()\n  }\n\n  createStringNodes() {\n    if (this.textConfig?.plaintext) {\n      return [ document.createTextNode(this.string) ]\n    } else {\n      const nodes = []\n      const iterable = this.string.split(\"\\n\")\n      for (let index = 0; index < iterable.length; index++) {\n        const substring = iterable[index]\n        if (index > 0) {\n          const element = makeElement(\"br\")\n          nodes.push(element)\n        }\n\n        if (substring.length) {\n          const node = document.createTextNode(this.preserveSpaces(substring))\n          nodes.push(node)\n        }\n      }\n      return nodes\n    }\n  }\n\n  createElement() {\n    let element, key, value\n    const styles = {}\n\n    for (key in this.attributes) {\n      value = this.attributes[key]\n      const config = getTextConfig(key)\n      if (config) {\n        if (config.tagName) {\n          var innerElement\n          const pendingElement = makeElement(config.tagName)\n\n          if (innerElement) {\n            innerElement.appendChild(pendingElement)\n            innerElement = pendingElement\n          } else {\n            element = innerElement = pendingElement\n          }\n        }\n\n        if (config.styleProperty) {\n          styles[config.styleProperty] = value\n        }\n\n        if (config.style) {\n          for (key in config.style) {\n            value = config.style[key]\n            styles[key] = value\n          }\n        }\n      }\n    }\n\n    if (Object.keys(styles).length) {\n      if (!element) { element = makeElement(\"span\") }\n      for (key in styles) {\n        value = styles[key]\n        element.style[key] = value\n      }\n    }\n    return element\n  }\n\n  createContainerElement() {\n    for (const key in this.attributes) {\n      const value = this.attributes[key]\n      const config = getTextConfig(key)\n      if (config) {\n        if (config.groupTagName) {\n          const attributes = {}\n          attributes[key] = value\n          return makeElement(config.groupTagName, attributes)\n        }\n      }\n    }\n  }\n\n  preserveSpaces(string) {\n    if (this.context.isLast) {\n      string = string.replace(/\\ $/, NON_BREAKING_SPACE)\n    }\n\n    string = string\n      .replace(/(\\S)\\ {3}(\\S)/g, `$1 ${NON_BREAKING_SPACE} $2`)\n      .replace(/\\ {2}/g, `${NON_BREAKING_SPACE} `)\n      .replace(/\\ {2}/g, ` ${NON_BREAKING_SPACE}`)\n\n    if (this.context.isFirst || this.context.followsWhitespace) {\n      string = string.replace(/^\\ /, NON_BREAKING_SPACE)\n    }\n\n    return string\n  }\n}\n"
  },
  {
    "path": "src/trix/views/previewable_attachment_view.js",
    "content": "import * as config from \"trix/config\"\nimport { makeElement } from \"trix/core/helpers\"\n\nimport AttachmentView from \"trix/views/attachment_view\"\n\nexport default class PreviewableAttachmentView extends AttachmentView {\n  constructor() {\n    super(...arguments)\n    this.attachment.previewDelegate = this\n  }\n\n  createContentNodes() {\n    this.image = makeElement({\n      tagName: \"img\",\n      attributes: {\n        src: \"\",\n      },\n      data: {\n        trixMutable: true,\n      },\n    })\n\n    this.refresh(this.image)\n    return [ this.image ]\n  }\n\n  createCaptionElement() {\n    const figcaption = super.createCaptionElement(...arguments)\n    if (!figcaption.textContent) {\n      figcaption.setAttribute(\"data-trix-placeholder\", config.lang.captionPlaceholder)\n    }\n    return figcaption\n  }\n\n  refresh(image) {\n    if (!image) { image = this.findElement()?.querySelector(\"img\") }\n    if (image) {\n      return this.updateAttributesForImage(image)\n    }\n  }\n\n  updateAttributesForImage(image) {\n    const url = this.attachment.getURL()\n    const previewURL = this.attachment.getPreviewURL()\n    image.src = previewURL || url\n\n    if (previewURL === url) {\n      image.removeAttribute(\"data-trix-serialized-attributes\")\n    } else {\n      const serializedAttributes = JSON.stringify({ src: url })\n      image.setAttribute(\"data-trix-serialized-attributes\", serializedAttributes)\n    }\n\n    const width = this.attachment.getWidth()\n    const height = this.attachment.getHeight()\n    const alt = this.attachment.getAttribute(\"alt\")\n\n    if (width != null) {\n      image.width = width\n    }\n    if (height != null) {\n      image.height = height\n    }\n    if (alt != null) {\n      image.alt = alt\n    }\n\n    const storeKey = [ \"imageElement\", this.attachment.id, image.src, image.width, image.height ].join(\"/\")\n    image.dataset.trixStoreKey = storeKey\n  }\n\n  // Attachment delegate\n\n  attachmentDidChangeAttributes() {\n    this.refresh(this.image)\n    return this.refresh()\n  }\n}\n"
  },
  {
    "path": "src/trix/views/text_view.js",
    "content": "/* eslint-disable\n    no-var,\n*/\nimport ObjectView from \"trix/views/object_view\"\nimport ObjectGroup from \"trix/core/collections/object_group\"\nimport PieceView from \"trix/views/piece_view\"\n\nexport default class TextView extends ObjectView {\n  constructor() {\n    super(...arguments)\n    this.text = this.object\n    this.textConfig = this.options.textConfig\n  }\n\n  createNodes() {\n    const nodes = []\n    const pieces = ObjectGroup.groupObjects(this.getPieces())\n    const lastIndex = pieces.length - 1\n\n    for (let index = 0; index < pieces.length; index++) {\n      const piece = pieces[index]\n      const context = {}\n      if (index === 0) {\n        context.isFirst = true\n      }\n      if (index === lastIndex) {\n        context.isLast = true\n      }\n      if (endsWithWhitespace(previousPiece)) {\n        context.followsWhitespace = true\n      }\n\n      const view = this.findOrCreateCachedChildView(PieceView, piece, { textConfig: this.textConfig, context })\n      nodes.push(...Array.from(view.getNodes() || []))\n\n      var previousPiece = piece\n    }\n    return nodes\n  }\n\n  getPieces() {\n    return Array.from(this.text.getPieces()).filter((piece) => !piece.hasAttribute(\"blockBreak\"))\n  }\n}\n\nconst endsWithWhitespace = (piece) => /\\s$/.test(piece?.toString())\n"
  },
  {
    "path": "web-test-runner.config.mjs",
    "content": "import { playwrightLauncher } from '@web/test-runner-playwright';\nimport path from 'path';\nimport fs from 'fs';\nimport { SourceMapConsumer } from 'source-map';\n\n// Load source map for translating stack traces (local dev only)\nlet sourceMapConsumer = null;\nconst sourceMapPath = path.join(process.cwd(), 'dist/test.js.map');\nif (fs.existsSync(sourceMapPath)) {\n  const sourceMapData = JSON.parse(fs.readFileSync(sourceMapPath, 'utf8'));\n  sourceMapConsumer = await new SourceMapConsumer(sourceMapData);\n}\n\n// Translate a stack trace using source maps\nfunction translateStack(stack) {\n  if (!sourceMapConsumer || !stack) return stack;\n\n  return stack.split('\\n').map(line => {\n    // Match stack frame pattern: \"at ... (url:line:col)\" or \"at url:line:col\"\n    const match = line.match(/^(\\s*at\\s+.*?)(?:\\()?(?:https?:\\/\\/[^/]+)?\\/dist\\/test\\.js[^:]*:(\\d+):(\\d+)\\)?$/);\n    if (!match) return line;\n\n    const [, prefix, lineNum, colNum] = match;\n    const pos = sourceMapConsumer.originalPositionFor({\n      line: parseInt(lineNum, 10),\n      column: parseInt(colNum, 10)\n    });\n\n    if (pos.source) {\n      const source = pos.source.replace(/^\\.\\.\\//, '');\n      return `${prefix}(${source}:${pos.line}:${pos.column})`;\n    }\n    return line;\n  }).join('\\n');\n}\n\n// Map SAUCE_REGION env var to hostname\nconst sauceRegionHostnames = {\n  'us': 'ondemand.us-west-1.saucelabs.com',\n  'us-west-1': 'ondemand.us-west-1.saucelabs.com',\n  'us-east-4': 'ondemand.us-east-4.saucelabs.com',\n  'eu': 'ondemand.eu-central-1.saucelabs.com',\n  'eu-central-1': 'ondemand.eu-central-1.saucelabs.com',\n};\n\n// Build SauceLabs launchers if credentials are available\nconst sauceLabsConfig = process.env.SAUCE_ACCESS_KEY ? {\n  hostname: sauceRegionHostnames[process.env.SAUCE_REGION] || 'ondemand.us-west-1.saucelabs.com',\n  port: 443,\n  path: '/wd/hub',\n  protocol: 'https',\n  user: process.env.SAUCE_USERNAME,\n  key: process.env.SAUCE_ACCESS_KEY,\n} : null;\n\n// Dynamic import for webdriver launcher only when needed\nconst createSauceLabsLaunchers = async () => {\n  if (!sauceLabsConfig) return [];\n\n  const { webdriverLauncher } = await import('@web/test-runner-webdriver');\n\n  const { GITHUB_WORKFLOW, GITHUB_RUN_NUMBER, GITHUB_RUN_ID, SAUCE_TUNNEL_IDENTIFIER } = process.env;\n\n  const buildId = GITHUB_WORKFLOW && GITHUB_RUN_NUMBER && GITHUB_RUN_ID\n    ? `${GITHUB_WORKFLOW} #${GITHUB_RUN_NUMBER} (${GITHUB_RUN_ID})`\n    : 'local';\n\n  const sauceOptions = {\n    name: 'Trix',\n    build: buildId,\n    // Use tunnelName for SC5 (tunnelIdentifier is SC4 legacy)\n    ...(SAUCE_TUNNEL_IDENTIFIER && { tunnelName: SAUCE_TUNNEL_IDENTIFIER }),\n  };\n\n  return [\n    webdriverLauncher({\n      ...sauceLabsConfig,\n      capabilities: {\n        browserName: 'chrome',\n        browserVersion: 'latest',\n        platformName: 'Windows 10',\n        'sauce:options': sauceOptions,\n      },\n    }),\n    webdriverLauncher({\n      ...sauceLabsConfig,\n      capabilities: {\n        browserName: 'firefox',\n        browserVersion: 'latest',\n        platformName: 'Windows 10',\n        'moz:debuggerAddress': true,  // Required for SauceLabs Firefox\n        'sauce:options': sauceOptions,\n      },\n    }),\n    webdriverLauncher({\n      ...sauceLabsConfig,\n      capabilities: {\n        browserName: 'MicrosoftEdge',\n        browserVersion: 'latest',\n        platformName: 'Windows 10',\n        'sauce:options': sauceOptions,\n      },\n    }),\n    // Android emulator - Chrome browser\n    webdriverLauncher({\n      ...sauceLabsConfig,\n      capabilities: {\n        browserName: 'chrome',\n        platformName: 'Android',\n        'appium:deviceName': 'Android GoogleAPI Emulator',\n        'appium:platformVersion': '14.0',\n        'appium:automationName': 'UiAutomator2',\n        'sauce:options': sauceOptions,\n      },\n    }),\n  ];\n};\n\n// Default to Playwright Chromium for local development\nconst defaultBrowsers = [\n  playwrightLauncher({ product: 'chromium' }),\n];\n\n// Enable real-time progress reporting for local dev (single browser)\nconst localDev = !sauceLabsConfig && !process.env.CI;\n\nexport default {\n  // The test file(s)\n  files: ['dist/test.js'],\n\n  // Serve files from project root\n  rootDir: '.',\n\n  // Bind to all interfaces so Sauce Connect can reach the server\n  hostname: '0.0.0.0',\n\n  // Enable node module resolution for WTR core imports\n  nodeResolve: true,\n\n  // Browser configuration - SauceLabs if credentials available, otherwise Playwright\n  browsers: sauceLabsConfig ? await createSauceLabsLaunchers() : defaultBrowsers,\n\n  // Timeouts (generous for SauceLabs network latency and slow tests)\n  browserStartTimeout: 120000,\n  testsStartTimeout: 120000,\n  testsFinishTimeout: 600000,\n\n  // Parallel browser execution\n  concurrency: sauceLabsConfig ? 4 : 1,\n\n  // Use static logging for real-time progress (local dev only)\n  staticLogging: localDev,\n\n  // Custom reporter for local dev; undefined falls back to default for CI\n  reporters: localDev ? [\n    {\n      onTestRunFinished({ sessions }) {\n        let passed = 0, failed = 0, skipped = 0;\n        for (const session of sessions) {\n          const countTests = (suite) => {\n            for (const test of suite.tests || []) {\n              if (test.skipped) skipped++;\n              else if (test.passed) passed++;\n              else failed++;\n            }\n            for (const child of suite.suites || []) countTests(child);\n          };\n          if (session.testResults) countTests(session.testResults);\n        }\n        const total = passed + failed + skipped;\n        process.stdout.write(`\\n\\n${total} tests: ${passed} passed, ${failed} failed, ${skipped} skipped.\\n\\n`);\n      },\n    },\n  ] : undefined,\n\n  // Middleware to serve test fixtures and QUnit from local files\n  middleware: [\n    // Real-time test progress reporting\n    async function testProgressReporter(context, next) {\n      if (context.method === 'POST' && context.url === '/test-progress') {\n        const chunks = [];\n        for await (const chunk of context.req) {\n          chunks.push(chunk);\n        }\n        const body = Buffer.concat(chunks).toString();\n        const { status, name, error } = JSON.parse(body);\n        // Match the same logic used in the reporter: skipped, failed, or passed (everything else)\n        const progressIndicator = status === 'skipped' ? 'S' : status === 'failed' ? 'F' : '.';\n        process.stdout.write(progressIndicator);\n\n        // Print failure details immediately\n        if (status === 'failed') {\n          process.stdout.write(`\\n\\nFAIL: ${name}\\n`);\n          if (error) {\n            if (error.message) {\n              process.stdout.write(`  Message: ${error.message}\\n`);\n            }\n            if (error.expected !== undefined) {\n              process.stdout.write(`  Expected: ${error.expected}\\n`);\n            }\n            if (error.actual !== undefined) {\n              process.stdout.write(`  Actual: ${error.actual}\\n`);\n            }\n            if (error.stack) {\n              const translatedStack = translateStack(error.stack);\n              process.stdout.write(`  Stack:\\n    ${translatedStack.split('\\n').join('\\n    ')}\\n`);\n            }\n          }\n          process.stdout.write('\\n');\n        }\n\n        context.status = 200;\n        context.body = 'ok';\n        return;\n      }\n      return next();\n    },\n    function serveLocalFiles(context, next) {\n      // Serve test fixtures from src/test/test_helpers/fixtures/\n      if (context.url.startsWith('/test_helpers/fixtures/')) {\n        const filePath = path.join(process.cwd(), 'src/test', context.url);\n        if (fs.existsSync(filePath)) {\n          context.body = fs.createReadStream(filePath);\n          const ext = path.extname(filePath).toLowerCase();\n          const mimeTypes = { '.png': 'image/png', '.jpg': 'image/jpeg', '.gif': 'image/gif' };\n          context.type = mimeTypes[ext] || 'application/octet-stream';\n          return;\n        }\n      }\n      // Serve QUnit from node_modules (avoid CDN issues with SauceLabs)\n      if (context.url.startsWith('/qunit/')) {\n        const filePath = path.join(process.cwd(), 'node_modules', context.url);\n        if (fs.existsSync(filePath)) {\n          context.body = fs.createReadStream(filePath);\n          const ext = path.extname(filePath).toLowerCase();\n          const mimeTypes = { '.js': 'application/javascript', '.css': 'text/css' };\n          context.type = mimeTypes[ext] || 'application/octet-stream';\n          return;\n        }\n      }\n      return next();\n    },\n  ],\n\n  // Custom HTML that sets up QUnit and bridges to WTR\n  // Note: first param is testFrameworkImport (mocha path by default), which we ignore\n  // We use getConfig() to get the actual test file path\n  testRunnerHtml: () => `\n<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Trix Tests</title>\n  <link rel=\"stylesheet\" href=\"/qunit/qunit/qunit.css\">\n  <link rel=\"stylesheet\" href=\"/dist/trix.css\">\n  <style>\n    #trix-container { height: 150px; }\n    trix-toolbar { margin-bottom: 10px; }\n    trix-toolbar button { border: 1px solid #ccc; background: #fff; }\n    trix-toolbar button.active { background: #d3e6fd; }\n    trix-toolbar button:disabled { color: #ccc; }\n    #qunit { position: relative !important; }\n  </style>\n</head>\n<body>\n  <div id=\"qunit\"></div>\n  <div id=\"qunit-fixture\"></div>\n\n  <!-- 1. Load QUnit locally (classic script, runs first) -->\n  <script src=\"/qunit/qunit/qunit.js\"></script>\n\n  <!-- 2. Prevent QUnit autostart before we set up hooks -->\n  <script>QUnit.config.autostart = false;</script>\n\n  <!-- 3. WTR integration + load tests + start QUnit -->\n  <script type=\"module\">\n    import { getConfig, sessionStarted, sessionFinished, sessionFailed }\n      from '@web/test-runner-core/browser/session.js';\n\n    // Real-time progress reporting only for local dev (single browser)\n    const reportProgress = ${localDev};\n\n    try {\n      await sessionStarted();\n\n      // Get the actual test file path from WTR config\n      const { testFile } = await getConfig();\n\n      // Build test results structure for WTR\n      const testSuite = { name: testFile, tests: [], suites: [] };\n      const errors = [];\n\n      QUnit.on('error', (error) => {\n        errors.push({ message: error?.message, stack: error?.stack });\n      });\n\n      QUnit.on('testEnd', (result) => {\n        // POST progress to server for real-time output\n        if (reportProgress) {\n          const payload = { status: result.status };\n          if (result.status === 'failed') {\n            payload.name = result.fullName.join(' > ');\n            if (result.errors?.[0]) {\n              const err = result.errors[0];\n              payload.error = {\n                message: err.message || 'Assertion Error',\n                expected: JSON.stringify(err.expected, null, 2),\n                actual: JSON.stringify(err.actual, null, 2),\n                stack: err.stack\n              };\n            }\n          }\n          fetch('/test-progress', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(payload)\n          }).catch(() => {});\n        }\n\n        // Navigate to correct suite in hierarchy\n        const modules = result.fullName.slice(0, -1);\n        let currentSuite = testSuite;\n        for (const name of modules) {\n          let suite = currentSuite.suites.find(s => s.name === name);\n          if (!suite) {\n            suite = { name, suites: [], tests: [] };\n            currentSuite.suites.push(suite);\n          }\n          currentSuite = suite;\n        }\n\n        const testResult = {\n          name: result.name,\n          passed: result.status !== 'failed',\n          skipped: result.status === 'skipped',\n          duration: result.runtime\n        };\n\n        if (!testResult.passed && result.errors?.[0]) {\n          const err = result.errors[0];\n          testResult.error = {\n            message: err.message || 'Assertion Error',\n            expected: JSON.stringify(err.expected, null, 2),\n            actual: JSON.stringify(err.actual, null, 2),\n            stack: err.stack\n          };\n        }\n        currentSuite.tests.push(testResult);\n      });\n\n      QUnit.on('runEnd', (results) => {\n        sessionFinished({\n          passed: results.status === 'passed',\n          errors,\n          testResults: testSuite\n        }).catch(console.error);\n      });\n\n      // Import test bundle using path from getConfig()\n      await import(testFile);\n\n      // Start QUnit\n      QUnit.start();\n    } catch (error) {\n      console.error('Test setup failed:', error);\n      sessionFailed({ message: error.message, stack: error.stack });\n    }\n  </script>\n</body>\n</html>\n  `,\n};\n"
  }
]