From e034c73a921001cbdd814f2634350c41178507a1 Mon Sep 17 00:00:00 2001 From: sergibaron-at-wiris Date: Thu, 13 Nov 2025 14:43:04 +0100 Subject: [PATCH 01/29] test: migration to playwright (WIP) --- .../cypress-Run-tests-with-npm-packages.yml | 146 ------ cypress.json | 8 - cypress/README.md | 88 ---- cypress/YYY-ZZZ.category.js | 32 -- cypress/fixtures/formulas.json | 20 - cypress/mathtype-web-app.png | Bin 89806 -> 0 bytes cypress/modal.jpg | Bin 48423 -> 0 bytes cypress/plugins/index.js | 24 - cypress/support/commands.d.ts | 119 ----- cypress/support/commands.js | 161 ------- cypress/support/index.js | 22 - cypress/support/utils.js | 19 - cypress/support/validations.d.ts | 37 -- cypress/support/validations.js | 4 - cypress/tests/e2e/STD-018.insertion.js | 29 -- cypress/tests/e2e/STD-026.modal.js | 38 -- cypress/tests/e2e/STD-028.modal.js | 32 -- cypress/tests/sandbox/.gitkeep | 0 cypress/tests/smoke/.gitkeep | 0 cypress/tests/smoke/STD-001.insertion.js | 29 -- cypress/tests/smoke/STD-002.insertion.js | 32 -- cypress/tests/smoke/STD-003.insertion.js | 36 -- cypress/tests/smoke/STD-004.images.js | 28 -- cypress/tests/smoke/STD-005.images.js | 35 -- cypress/tests/smoke/STD-007.images.js | 26 - cypress/tests/smoke/STD-010.images.js | 30 -- cypress/tests/smoke/STD-011.modal.js | 20 - cypress/tests/smoke/STD-012.modal.js | 32 -- cypress/tests/smoke/STD-014.insertion.js | 23 - cypress/tests/smoke/STD-016.insertion.js | 46 -- cypress/tests/smoke/STD-017.insertion.js | 46 -- cypress/tests/smoke/STD-019.insertion.js | 33 -- cypress/tests/smoke/STD-020.modal.js | 23 - cypress/tests/smoke/STD-021.modal.js | 23 - cypress/tests/smoke/STD-022.modal.js | 40 -- cypress/tests/smoke/STD-023.modal.js | 40 -- cypress/tests/ui/.gitkeep | 0 cypress/tests/validation/.gitkeep | 0 package.json | 5 +- packages/ckeditor5/dist/browser/index.css.map | 2 +- packages/ckeditor5/dist/browser/index.js | 28 +- packages/ckeditor5/dist/browser/index.js.map | 2 +- packages/ckeditor5/dist/browser/index.umd.js | 28 +- .../ckeditor5/dist/browser/index.umd.js.map | 2 +- .../dist/browser/telemeter_wasm_bg.wasm | Bin 590432 -> 0 bytes packages/ckeditor5/dist/index.js | 6 +- packages/ckeditor5/dist/index.js.map | 2 +- tests/e2e/.env.example | 12 + tests/e2e/.gitignore | 10 + tests/e2e/README.md | 225 +++++++++ tests/e2e/enums/equation_entry_mode.ts | 6 + tests/e2e/enums/equations.ts | 37 ++ tests/e2e/enums/toolbar.ts | 6 + tests/e2e/enums/typing_mode.ts | 7 + tests/e2e/helpers/network.ts | 20 + tests/e2e/helpers/test-setup.ts | 27 ++ tests/e2e/interfaces/equation.ts | 5 + tests/e2e/page-objects/base_editor.ts | 453 ++++++++++++++++++ tests/e2e/page-objects/editor_manager.ts | 42 ++ tests/e2e/page-objects/equation_entry_form.ts | 46 ++ tests/e2e/page-objects/html/ckeditor4.ts | 16 + tests/e2e/page-objects/html/ckeditor5.ts | 25 + tests/e2e/page-objects/html/froala.ts | 23 + tests/e2e/page-objects/html/generic.ts | 15 + tests/e2e/page-objects/html/tinymce5.ts | 16 + tests/e2e/page-objects/html/tinymce6.ts | 16 + tests/e2e/page-objects/html/tinymce7.ts | 16 + tests/e2e/page-objects/html/tinymce8.ts | 16 + tests/e2e/page-objects/page.ts | 26 + tests/e2e/page-objects/wiris_editor.ts | 213 ++++++++ tests/e2e/playwright.config.ts | 46 ++ .../e2e/tests/edit/edit_corner_cases.spec.ts | 96 ++++ tests/e2e/tests/edit/edit_hand.spec.ts | 38 ++ .../tests/edit/edit_via_doble_click.spec.ts | 56 +++ .../e2e/tests/edit/edit_via_selection.spec.ts | 56 +++ tests/e2e/tests/editor/copy_cut_drop.spec.ts | 78 +++ tests/e2e/tests/editor/editor.spec.ts | 125 +++++ tests/e2e/tests/insert/insert.spec.ts | 68 +++ .../tests/insert/insert_corner_cases.spec.ts | 66 +++ tests/e2e/tests/insert/insert_hand.spec.ts | 37 ++ tests/e2e/tests/latex/latex.spec.ts | 87 ++++ .../tests/modal/confirmation_dialog.spec.ts | 108 +++++ tests/e2e/tests/modal/toolbar.spec.ts | 102 ++++ tests/e2e/tests/telemetry/telemetry.spec.ts | 45 ++ tests/e2e/tsconfig.json | 27 ++ 85 files changed, 2351 insertions(+), 1358 deletions(-) delete mode 100644 cypress.json delete mode 100644 cypress/README.md delete mode 100644 cypress/YYY-ZZZ.category.js delete mode 100644 cypress/fixtures/formulas.json delete mode 100644 cypress/mathtype-web-app.png delete mode 100644 cypress/modal.jpg delete mode 100644 cypress/plugins/index.js delete mode 100644 cypress/support/commands.d.ts delete mode 100644 cypress/support/commands.js delete mode 100644 cypress/support/index.js delete mode 100644 cypress/support/utils.js delete mode 100644 cypress/support/validations.d.ts delete mode 100644 cypress/support/validations.js delete mode 100644 cypress/tests/e2e/STD-018.insertion.js delete mode 100644 cypress/tests/e2e/STD-026.modal.js delete mode 100644 cypress/tests/e2e/STD-028.modal.js delete mode 100644 cypress/tests/sandbox/.gitkeep delete mode 100644 cypress/tests/smoke/.gitkeep delete mode 100644 cypress/tests/smoke/STD-001.insertion.js delete mode 100644 cypress/tests/smoke/STD-002.insertion.js delete mode 100644 cypress/tests/smoke/STD-003.insertion.js delete mode 100644 cypress/tests/smoke/STD-004.images.js delete mode 100644 cypress/tests/smoke/STD-005.images.js delete mode 100644 cypress/tests/smoke/STD-007.images.js delete mode 100644 cypress/tests/smoke/STD-010.images.js delete mode 100644 cypress/tests/smoke/STD-011.modal.js delete mode 100644 cypress/tests/smoke/STD-012.modal.js delete mode 100644 cypress/tests/smoke/STD-014.insertion.js delete mode 100644 cypress/tests/smoke/STD-016.insertion.js delete mode 100644 cypress/tests/smoke/STD-017.insertion.js delete mode 100644 cypress/tests/smoke/STD-019.insertion.js delete mode 100644 cypress/tests/smoke/STD-020.modal.js delete mode 100644 cypress/tests/smoke/STD-021.modal.js delete mode 100644 cypress/tests/smoke/STD-022.modal.js delete mode 100644 cypress/tests/smoke/STD-023.modal.js delete mode 100644 cypress/tests/ui/.gitkeep delete mode 100644 cypress/tests/validation/.gitkeep delete mode 100644 packages/ckeditor5/dist/browser/telemeter_wasm_bg.wasm create mode 100644 tests/e2e/.env.example create mode 100644 tests/e2e/.gitignore create mode 100644 tests/e2e/README.md create mode 100644 tests/e2e/enums/equation_entry_mode.ts create mode 100644 tests/e2e/enums/equations.ts create mode 100644 tests/e2e/enums/toolbar.ts create mode 100644 tests/e2e/enums/typing_mode.ts create mode 100644 tests/e2e/helpers/network.ts create mode 100644 tests/e2e/helpers/test-setup.ts create mode 100644 tests/e2e/interfaces/equation.ts create mode 100644 tests/e2e/page-objects/base_editor.ts create mode 100644 tests/e2e/page-objects/editor_manager.ts create mode 100644 tests/e2e/page-objects/equation_entry_form.ts create mode 100644 tests/e2e/page-objects/html/ckeditor4.ts create mode 100644 tests/e2e/page-objects/html/ckeditor5.ts create mode 100644 tests/e2e/page-objects/html/froala.ts create mode 100644 tests/e2e/page-objects/html/generic.ts create mode 100644 tests/e2e/page-objects/html/tinymce5.ts create mode 100644 tests/e2e/page-objects/html/tinymce6.ts create mode 100644 tests/e2e/page-objects/html/tinymce7.ts create mode 100644 tests/e2e/page-objects/html/tinymce8.ts create mode 100644 tests/e2e/page-objects/page.ts create mode 100644 tests/e2e/page-objects/wiris_editor.ts create mode 100644 tests/e2e/playwright.config.ts create mode 100644 tests/e2e/tests/edit/edit_corner_cases.spec.ts create mode 100644 tests/e2e/tests/edit/edit_hand.spec.ts create mode 100644 tests/e2e/tests/edit/edit_via_doble_click.spec.ts create mode 100644 tests/e2e/tests/edit/edit_via_selection.spec.ts create mode 100644 tests/e2e/tests/editor/copy_cut_drop.spec.ts create mode 100644 tests/e2e/tests/editor/editor.spec.ts create mode 100644 tests/e2e/tests/insert/insert.spec.ts create mode 100644 tests/e2e/tests/insert/insert_corner_cases.spec.ts create mode 100644 tests/e2e/tests/insert/insert_hand.spec.ts create mode 100644 tests/e2e/tests/latex/latex.spec.ts create mode 100644 tests/e2e/tests/modal/confirmation_dialog.spec.ts create mode 100644 tests/e2e/tests/modal/toolbar.spec.ts create mode 100644 tests/e2e/tests/telemetry/telemetry.spec.ts create mode 100644 tests/e2e/tsconfig.json diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index ae600ce55..e69de29bb 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -1,146 +0,0 @@ -# It runs all available Cypress tests and sends test data to Cypress Dashboard. -# It executes on every Monday at 01 a.m, and on demand by the user. -# The tests run with a fail-fast strategy, once one fails, the others will not run. -# If a test fails, it creates a set of screenshots showing the errors. -# -# Matrix: -# - Browser: chrome. -# - Test folder: All tests available on the `/tests` folder. -# -# Jobs: -# - Set up the Cypress Environment: `setup-cypress`. -# - Runs all tests on the matrix: `run-all-tests-matrix`. - -name: Run Cypress tests with npm packages - -on: - schedule: - # runs every Monday at 1 a.m - - cron: "0 1 * * 1" - filters: - branches: - only: - - stable - workflow_dispatch: - inputs: - # Sends data to Cy - cypress-record: - description: Send test data to Cypress Dashboard? Write 'Yes' to send data. - required: true - default: "No" - -jobs: - setup-cypress: - # Setup Cypress environment without cache. - name: Setup Cypress environment without cache - runs-on: ubuntu-latest - steps: - # 01. Checkout the repository. - - name: Checkout - uses: actions/checkout@v2 - - # 02. Install a specific version of Node using. - - name: Use Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - - # 03. Install dependencies and verify Cypress - - name: Install dependencies and verify Cypress - env: - # make sure every Cypress install prints minimal information - CI: 1 - # print Cypress and OS info - # This next command should use "npm ci" instead of "npm install" - run: | - npm ci - npx cypress verify - npx cypress info - npx cypress version - npx cypress version --component package - npx cypress version --component binary - npx cypress version --component electron - npx cypress version --component node - - run-all-tests-matrix: - # Runs all tests. - runs-on: ubuntu-latest - needs: setup-cypress - strategy: - fail-fast: true - matrix: - # Define values for browsers from - browser: ["chrome"] - # browser: ['chrome', 'edge', 'firefox', 'chromium'] - type: ["all"] - # type: ['smoke','e2e', 'ui', 'validation'] - # env: ['local', 'public'] - name: Run ${{ matrix.type }} tests on ${{ matrix.browser }} - steps: - # 01. Checkout the repository. - - name: Checkout - uses: actions/checkout@v2 - - # 02. Install a specific version of Node. - - name: Use Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - - # 03a. Decide whether to send data to Cypress Dashboard, or not, for workflow_dispatch and schedule. - - name: Decide if we send data to Cypress 2 - if: ${{ github.event.inputs.cypress-record == 'Yes' || github.event_name == 'schedule' }} - run: | - echo "CY_RECORD_KEY=${{ secrets.CYPRESS_RECORD_KEY }}" >> $GITHUB_ENV - echo "CY_RECORD_FLAG=-- --record --key " >> $GITHUB_ENV - - # 03a. Decide whether to send data to Cypress Dashboard, or not, for workflow_dispatch. - - name: Decide if we send data to Cypress 1 - # We don't want to send the tests to Dashboard through workflow dispatch - if: ${{ github.event.inputs.cypress-record != 'Yes' }} - # we set the environment variables dynamically to empty in order to avoid - # recording the test execution to Cypress Dashboard. - run: | - echo "CY_RECORD_KEY=" >> $GITHUB_ENV - echo "CY_RECORD_FLAG=" >> $GITHUB_ENV - - # 04. Run the tests following the initial matrix: by browser and test type - - name: Run tests by browser - uses: cypress-io/github-action@v2 - timeout-minutes: 10 - with: - # 'build' starts the default demo - build: npm run build - # 'test:ci' runs tests over Docker image for build context - # we also send the Cypress Dashboard record key dynamically - command: npm run test:ci ${{ env.CY_RECORD_FLAG }} ${{ env.CY_RECORD_KEY }} - record: true - parallel: true - group: "${{ matrix.type }} tests on ${{ matrix.browser }}" - browser: ${{ matrix.browser }} - config: "video: true" - spec: | - cypress/tests/**/*.js - env: - # https://github.com/wiris/html-integrations/settings/secrets/actions - CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} - GITHUB_TOKEN: ${{ secrets.GH_CICD_TOKEN }} - - # 05a. Save videos and screenshots as test artifacts - # https://github.com/actions/upload-artifact - - name: Upload screenshots - uses: actions/upload-artifact@master - # there might be no screenshots created when: - # - there are no test failures - # so only upload screenshots if previous step has failed - if: failure() - with: - name: screenshots-${{ matrix.type }}-${{ matrix.browser }} - path: cypress/screenshots - - # 05b. Upload videos, since they are always be generated. - - name: Upload videos for all tests - uses: actions/upload-artifact@master - with: - name: videos-${{ matrix.type }}-${{ matrix.browser }} - path: cypress/videos diff --git a/cypress.json b/cypress.json deleted file mode 100644 index ea606fa3b..000000000 --- a/cypress.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "integrationFolder": "cypress/tests", - "screenshotOnRunFailure": true, - "env": { - "FAIL_FAST_STRATEGY": "run", - "FAIL_FAST_ENABLED": true - } -} diff --git a/cypress/README.md b/cypress/README.md deleted file mode 100644 index 0291ea446..000000000 --- a/cypress/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# Cypress End-to-end tests for the MathType Web Integrations - -[Cypress.io](https://www.cypress.io) is an open source, MIT licensed end-to-end test runner. - -**Important**: More information about testing commands and how to use instructions on the [Testing section](/docs/development/testing/README.md) of this project documentation. - -## Folder structure - -These folders hold end-to-end tests and supporting files for the Cypress Test Runner. - -- [fixtures](fixtures) holds sample data for mocking our tests, [read more](https://on.cypress.io/fixture). -- [tests](tests) holds the actual integration test files, [read more](https://on.cypress.io/writing-and-organizing-tests) -- [plugins](plugins) allow you to customize how tests are loaded, [read more](https://on.cypress.io/plugins) -- [support](support) includes our custom commands, [read more](https://on.cypress.io/writing-and-organizing-tests#Support-file) - -## What do we want to test? - -A minimal MathType Integration for the web that includes mathematical formula editing & rendering features from Wiris. - -### Elements - -It would consist on these next HTML elements: - -1. an editable element, with a default value or not -2. a read-only element with its content synchronized to the previous element through an `onChange` event -3. the MathType and ChemType buttons, over the editable content - -Also, whenever the MathType or ChemType buttons are clicked, or a mathematical expression inside the textarea is clicked, - -4. a MathType Modal Window is shown to the user to edit the formula. - -### Source code - -A canonical representation of the HTML source code of this app would look like this: - -```html - - - - - -
...
- - -``` - -### UI Preview - -This next diagram represents a common E2E interaction with the MathType for the Web UI elements of the canonical MathType Integration sample app: adding a mathematical formula from scratch using the MathType editor. - -![Minimal MathType integration snapshot](mathtype-web-app.png) - -### MathType Modal Window - -The MathType Modal Window consists on the following elements: - -![Diagram of the MathType modal window](modal.jpg) - -> This diagram is based on a comment in the source code of [`modal.js`](/packages/mathtype-html-integration-devkit/src/modal.js) from the `mathtype-html-integration-devkit` package. - -## `cypress.json` file - -You can configure project options in the [../cypress.json](../cypress.json) file, see [Cypress configuration doc](https://on.cypress.io/configuration). - -The current values we've set by default for all environments are: - -```json - // By default, point to the demos/html/generic App. - "baseUrl": "http://localhost:8007", - // Override default 'integration' value to 'tests' - "integrationFolder": "cypress/tests", - "screenshotOnRunFailure": true, - // Optimize test execution by activating Fail_fast feature everywhere. - "env": - { - "FAIL_FAST_STRATEGY": "run", - "FAIL_FAST_ENABLED": true - } - -``` - -The main cypress.json files will hold the default settings for all tests in all environments: local, build, ... - -## More information - -- [https://github.com/cypress.io/cypress](https://github.com/cypress.io/cypress) -- [https://docs.cypress.io/](https://docs.cypress.io/) -- [Writing your first Cypress test](http://on.cypress.io/intro) diff --git a/cypress/YYY-ZZZ.category.js b/cypress/YYY-ZZZ.category.js deleted file mode 100644 index aca165ed6..000000000 --- a/cypress/YYY-ZZZ.category.js +++ /dev/null @@ -1,32 +0,0 @@ -/// -// *********************************************************** -// Test case: {Test.ID} Ex. INT-STD-014 -// Title: {Test.Title} Ex. User creates a new formula from scratch using MathType. -// Document: {Test.URL} Ex. https://docs.google.com/document/d/1fiGsUwqNIsjiaJI0aGfH_aNX5OJKEHkfWtfvlQkEEFI/edit -// Context: {Test.Type} - {Text.category} Ex. UI - Formula insertion/edition -// Issue: {Test.Issue} Ex. KB-99999 -// *********************************************************** - -describe("Formula insertion/edition", () => { - beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - // and visit page. - cy.visit("/"); - // Eventually, clear the editor content: by default the editor could include a mathematical expression. - cy.getTextEditor().clear(); - }); - - it("User creates a new formula from scratch using MathType", function () { - // Open a new MathType modal window clicking the button - cy.clickButtonToOpenModal(); - // then type a general formula inside the editor - cy.typeInModal(this.formulas["formula-general"]); - // and insert the formula at the beginning of the target element using the 'Insert' button. - cy.clickModalButton("insert"); - - // Check the recently inserted formula - // and validate is rendered succesfully using MathType services. - cy.getFormula(0).isRendered(); - }); -}); diff --git a/cypress/fixtures/formulas.json b/cypress/fixtures/formulas.json deleted file mode 100644 index 8623cf382..000000000 --- a/cypress/fixtures/formulas.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "formula-general": "x + y", - "formula-general-alt-en": "x space plus space y", - "formula-general-alt-es": "x espacio mรกs espacio y", - "formula-addition": " = z", - "formula-total-alt-es": "x espacio mรกs espacio y espacio igual espacio z", - "text-alignment": "2222", - "formula-alignment": "2+2", - "latex-general": "$$\\cos^2(x)+\\sin^2(x)$$", - "latex-general-alt-en": "cos squared left parenthesis x right parenthesis plus sin squared left parenthesis x right parenthesis", - "latex-addition": "=log(e)", - "quadratic": "x=-b±b2-4ac2a", - "quadratic-accessible-en": "x equals fraction numerator negative b plus-or-minus square root of b squared minus 4 a c end root over denominator 2 a end fraction", - "formula-drawn": [ - { "x": 0, "y": 0 }, - { "x": 0, "y": 1 }, - { "x": 1, "y": 0 }, - { "x": 1, "y": 1 } - ] -} diff --git a/cypress/mathtype-web-app.png b/cypress/mathtype-web-app.png deleted file mode 100644 index b2c4cb2d22c9eb9b29c0800b6459b6d977aa6c17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89806 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 @0+gv5^AhA6AS*!PWRQ9pRkE- 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

}-yD%&9_O~sz4*2gyAvJIgaim-g*$(f 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 -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - // eslint-disable-next-line global-require - require("cypress-fail-fast/plugin")(on, config); - return config; -}; diff --git a/cypress/support/commands.d.ts b/cypress/support/commands.d.ts deleted file mode 100644 index 7a95b4714..000000000 --- a/cypress/support/commands.d.ts +++ /dev/null @@ -1,119 +0,0 @@ -/// - -declare namespace Cypress { - interface Chainable { - /** - * Yield the text editor. - */ - getTextEditor(); - - /** - * Click the MathType or ChemType button in the text editor. - * @param chem {default = false} whether to click the MathType or ChemType button. - */ - clickButtonToOpenModal(chem?: boolean); - - /** - * Append the given string in the text editor at the given offset. - * @param text string to append in the editor - * @param offset position of the caret when typing. At the end if omitted. Not yet implemented - */ - typeInTextEditor(text: string, offset?: number); - - /** - * Append the given text in MathType. - * @param formula formula to append in the editor - * @param paste whether the text appended is typed or pasted. Not yet implemented - */ - typeInModal(formula: string, paste?: boolean); - - /** - * Click the specified button on the MathType modal dialog - * @param {string} button Button identifier. Values can be: - * - insert: inserts a formula - * - cancel: closes the modal. If there are changes, opens a confirmation dialog - * - confirmationClose: discards the changes when closing the modal dialog - * - confirmationCancel: cancels the confirmation dialog and returns to editing the formula - * - xClose: closes the modal through the top right x button - * - maximize: makes the modal full screen through the top right button - * - stack: changes the modal to not be full screen through the top right button - * - minimize: hides the modal, if visible, and shows it again, if not visible, through the top right button - * - hand: opens/closes Hand mode - */ - clickModalButton(button: string); - - /** - * Insert a formula from scratch - * @param formula formula to append in the editor - * @param chem {default = false} whether to click the MathType or ChemType button - * @param paste {default = false} whether the text appended is typed or pasted. Not yet implemented - */ - insertFormulaFromScratch(formula: string, chem?: boolean, paste?: boolean); - - /** - * Obtain a formula from a given identifier. - * @param formulaId identifier of the formula to obtain. The identifier is the 0-indexed position of the formula inside the text editor. - * @returns the formula - */ - getFormula(formulaId: number): Chainable; - - /** - * Select a LaTeX formula from a given identifier. - * @param formulaId id of the formula to obtain. The id is the 0-indexed position of the formula inside the text editor. - * @returns the formula - */ - selectLatexFormula(formulaId: number): Chainable; - - /** - * Edit an existing MathType formula by clicking the MathType or ChemType button. - * Must be applied to a father command unless latex is set to true. - * @param subject the formula to apply this command to. Not yet implemented - * @param options object with options: - * chem {default = false} whether to edit a chem or math formula - * latex {default = false} whether it is a LaTeX formula or not - * formulaId id of the formula to edit. Only used when latex is set to true - * formula string to be added when editing the formula - */ - editFormula( - subject: Element, - formula: string, - options?: { chem?: boolean; latex?: boolean; formulaId?: number }, - ): Chainable | null; - - /** - * Press the ESC keyboard button - */ - pressESCButton(); - - /** - * Not yet implemented. - * Drag and drop the MathType or ChemType modal - * @param coordinates place to drop the modal - */ - dragDropModal(coordinates: { x: number; y: number }); - - /** - * Not yet implemented. - * Drag and drop a Formula. - * Must be applied to a father command. - * @param subject the formula to apply this command to - * @param coordinates place to drop the formula - */ - dragDropFormula(subject: Element, coordinates: { x: number; y: number }): Chainable; - - /** - * Not yet implemented. - * Draw a formula with Hand mode. - * @param points list of ordered coordinates to draw - */ - drawFormula(points: { x: number; y: number }[]); - - /** - * Not yet implemented. - * Resize a given formula. - * Must be applied to a father command. - * @param subject the formula to apply this command to - */ - resizeFormula(subject: Element): Chainable; - } -} diff --git a/cypress/support/commands.js b/cypress/support/commands.js deleted file mode 100644 index 66cfd3a8b..000000000 --- a/cypress/support/commands.js +++ /dev/null @@ -1,161 +0,0 @@ -import { createSelection } from "./utils"; - -Cypress.Commands.add("getTextEditor", () => { - cy.get("div[contenteditable]"); -}); - -Cypress.Commands.add("clickButtonToOpenModal", (chem = false) => { - if (!chem) { - cy.get("#editorIcon").click(); - } else { - cy.get("#chemistryIcon").click(); - } - cy - // We wait for the toolbar to load before typing, as it is a good indicator of whether MathType has fully loaded. - // This depends on the implementation of the modal and is not too desirable, but works well. - .get(".wrs_toolbar") - .should("be.visible"); -}); - -Cypress.Commands.add("typeInTextEditor", (text) => { - cy.getTextEditor().type(text); -}); - -Cypress.Commands.add("typeInModal", (formula) => { - cy.get(".wrs_focusElementContainer > input").type(formula); -}); - -Cypress.Commands.add("clickModalButton", (button) => { - switch (button) { - case "insert": - cy.get(".wrs_modal_button_accept").click(); - break; - case "cancel": - cy.get(".wrs_modal_button_cancel").click(); - break; - case "confirmationClose": - cy.get("#wrs_popup_accept_button").click({ force: true }); - break; - case "confirmationCancel": - cy.get("#wrs_popup_cancel_button").click({ force: true }); - break; - case "xClose": - cy.get(".wrs_modal_close_button").click(); - break; - case "maximize": - cy.get(".wrs_modal_maximize_button").click(); - break; - case "stack": - cy.get(".wrs_modal_stack_button").click(); - break; - case "minimize": - cy.get(".wrs_modal_minimize_button").click(); - break; - case "hand": - cy.get(".wrs_handWrapper > input").click(); - break; - default: - throw new Error(`The button '${button}' does not exist. Check the clickModalButton documentation.`); - } -}); - -// eslint-disable-next-line no-unused-vars -Cypress.Commands.add("insertFormulaFromScratch", (formula, chem = false, paste = false) => { - // Open the mathtype modal - cy.clickButtonToOpenModal(chem); - - // Type the formula that matxes the previous inserted text on the mathtype modal - cy.typeInModal(formula); - - // Insert the formula - cy.clickModalButton("insert"); -}); - -Cypress.Commands.add("getFormula", (formulaId) => { - cy.get(".Wirisformula") - .should("have.length.at.least", formulaId + 1) - .eq(formulaId); -}); - -Cypress.Commands.add("selectLatexFormula", (formulaId) => { - let block; - let startOffset; - let endOffset; - - // Get the block, and offsets of the latex formula - let countFormula = 0; - const edit = Cypress.$("#editable"); - const kids = edit[0].children; - for (let j = 0; j < kids.length; ++j) { - const elem = kids[j]; - const html = elem.innerHTML; - let prevDolar = false; - let waitEndDolar = false; - for (let i = 0; i < html.length; i++) { - const caracter = html[i]; - if (caracter === "$" && prevDolar === false && waitEndDolar === false) { - prevDolar = true; - } else if (caracter === "$" && prevDolar === true && waitEndDolar === false) { - prevDolar = false; - waitEndDolar = true; - if (countFormula === formulaId) startOffset = i + 1; - } else if (caracter === "$" && prevDolar === false && waitEndDolar === true) { - prevDolar = true; - if (countFormula === formulaId) endOffset = i; - else countFormula += 1; - } else if (caracter === "$" && prevDolar === true && waitEndDolar === true) { - prevDolar = false; - waitEndDolar = false; - } - if (startOffset && endOffset) { - block = j; - break; - } - } - if (startOffset && endOffset) break; - } - - // Throw error if the latex formula identifier does not correspond to a latex formula on the test - if (!startOffset || !endOffset) { - throw new Error(`The latex formula number '${formulaId}' does not exist`); - } - - // Select the latex formula - cy.getTextEditor() - .children() - .eq(block) - .trigger("mousedown") - .then(($el) => { - createSelection($el, startOffset, endOffset); - }) - .trigger("mouseup"); - cy.document().trigger("selectionchange"); -}); - -Cypress.Commands.add( - "editFormula", - { prevSubject: "optional" }, - ( - subject, - formula, - options = { - chem: false, - latex: false, - formulaId: 0, - }, - ) => { - // Select the latex formula and edit it - if (options.latex) { - cy.selectLatexFormula(options.formulaId); - cy.clickButtonToOpenModal(options.chem); - cy.typeInModal(formula); - cy.clickModalButton("insert"); - } else { - throw new Error("Not implemented yet"); - } - }, -); - -Cypress.Commands.add("pressESCButton", () => { - cy.get("body").type("{esc}"); -}); diff --git a/cypress/support/index.js b/cypress/support/index.js deleted file mode 100644 index fececccb1..000000000 --- a/cypress/support/index.js +++ /dev/null @@ -1,22 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import "./commands"; -import "./validations"; -import "cypress-fail-fast"; - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/cypress/support/utils.js b/cypress/support/utils.js deleted file mode 100644 index a2d60c7c6..000000000 --- a/cypress/support/utils.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Create a selection in the document in the given field, at the given start and end positions. - * @param field DOM element to make the selection in - * @param start index of the character to start the selecion in - * @param end index of the character to end the selection in - */ -function createSelection(field, start, end) { - const el = field[0]; - const document = el.ownerDocument; - const range = document.createRange(); - range.selectNodeContents(el); - document.getSelection().removeAllRanges(range); - if (start) range.setStart(el.firstChild, start); - if (end) range.setEnd(el.firstChild, end); - document.getSelection().addRange(range); -} - -// eslint-disable-next-line import/prefer-default-export -export { createSelection }; diff --git a/cypress/support/validations.d.ts b/cypress/support/validations.d.ts deleted file mode 100644 index 83fc62762..000000000 --- a/cypress/support/validations.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -/// - -declare namespace Cypress { - interface Chainable { - /** - * Validate that the height of the given formula and the surrounding text is the same. - * Must be applied to a father command. - * @param subject the formula to apply this validation to - * @param preview {default = false} whether to check aligment on the preview or on the text editor - */ - isAligned(subject: Element, preview?: boolean): Chainable; - - /** - * Check that the given formula is rendered in the preview mode or the text editor area. - * Must be applied to a father command. - * @param subject the formula to apply this validation to - * @param preview {default = false} whether to check aligment on the preview or on the text editor - */ - isRendered(subject: Element, preview?: boolean): Chainable; - - /** - * Validates that Hand mode is activated. - */ - isHandModeOn(); - - /** - * Validates that the ChemType modal is open. - */ - isChemTypeOn(); - - /** - * Check that the text inside the modal matches the given text string. - * @param text the text to match the modal content against - */ - modalTextEquals(text: string); - } -} diff --git a/cypress/support/validations.js b/cypress/support/validations.js deleted file mode 100644 index dc5f2cebd..000000000 --- a/cypress/support/validations.js +++ /dev/null @@ -1,4 +0,0 @@ -// eslint-disable-next-line no-unused-vars -Cypress.Commands.add("isRendered", { prevSubject: "element" }, (subject, preview = false) => { - cy.wrap(subject).should("be.visible"); -}); diff --git a/cypress/tests/e2e/STD-018.insertion.js b/cypress/tests/e2e/STD-018.insertion.js deleted file mode 100644 index cebb8a13a..000000000 --- a/cypress/tests/e2e/STD-018.insertion.js +++ /dev/null @@ -1,29 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-018 -// Title: User writes a latex formula and visualizes it on preview. -// Document: https://docs.google.com/document/d/1fiGsUwqNIsjiaJI0aGfH_aNX5OJKEHkfWtfvlQkEEFI/edit -// Context: E2E / Insertion -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("an inserted latex formula should be rendered on preview", function () { - // Type the formula that matxes the previous inserted text on the mathtype modal - cy.typeInTextEditor(this.formulas["latex-general"]); - - // // Click the update button - // cy.get('#btn_update').click(); - - // // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222 - // cy.getFormula(0).isRendered().and('have.attr', 'alt', this.formulas['latex-general-alt-en']); -}); diff --git a/cypress/tests/e2e/STD-026.modal.js b/cypress/tests/e2e/STD-026.modal.js deleted file mode 100644 index 94a61f222..000000000 --- a/cypress/tests/e2e/STD-026.modal.js +++ /dev/null @@ -1,38 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-026 -// Title: Validate Hand formulas open Hand directly when edited -// Document: https://docs.google.com/document/d/10nBVV0y3O5Eo7hEHtok8-s8zsZqVId7s_jwT5vziy7g/edit -// Context: E2E / Modal -// Issue: - -// *********************************************************** -beforeEach(() => { - // Load fixtures - cy.fixture("formulas.json").as("formulas"); - - // Visit page - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("Validate Hand formulas open Hand directly when edited", function () { - // Click the MT button on the HTML editor toolbar - cy.clickButtonToOpenModal(); - - // Switch to Hand editing mode by clicking the Hand icon in the MT modal window - // Draw a formula - // Instead of drawing the formula by hand, we type them in and let Hand transform them - cy.typeInModal(this.formulas["formula-general"]); - cy.clickModalButton("hand"); - - // Click the OK button in the MT modal window - cy.clickModalButton("insert"); - - // Double-click the created Hand formula - cy.getFormula(0).dblclick(); - - // MT modal window opens and Hand editing mode is already displayed with the formula - cy.get("canvas").should("be.visible"); -}); diff --git a/cypress/tests/e2e/STD-028.modal.js b/cypress/tests/e2e/STD-028.modal.js deleted file mode 100644 index 7bf395bf2..000000000 --- a/cypress/tests/e2e/STD-028.modal.js +++ /dev/null @@ -1,32 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-028 -// Title: Validate switch between CT and MT, and viceversa -// Document: https://docs.google.com/document/d/1PlqZUsfta5GMXXRq89oy50NTW5zBQCeUi8YBdO83Yug/edit -// Context: E2E / Modal -// Issue: - -// *********************************************************** -beforeEach(() => { - // Load fixtures - cy.fixture("formulas.json").as("formulas"); - - // Visit page - cy.visit("/"); -}); - -it("Validate switch between CT and MT, and viceversa", () => { - // Click the MT icon in the HTML editor toolbar. - cy.clickButtonToOpenModal(); - - // Click the CT icon in the HTML editor toolbar - cy.clickButtonToOpenModal(true); - - // MT modal window changes to CT modal window. - cy.get(".wrs_modal_title").eq(0).should("have.text", "ChemType"); - - // Click the MT icon in the HTML editor toolbar. - cy.clickButtonToOpenModal(); - - // CT modal window changes to MT modal window. - cy.get(".wrs_modal_title").eq(0).should("have.text", "MathType"); -}); diff --git a/cypress/tests/sandbox/.gitkeep b/cypress/tests/sandbox/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cypress/tests/smoke/.gitkeep b/cypress/tests/smoke/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cypress/tests/smoke/STD-001.insertion.js b/cypress/tests/smoke/STD-001.insertion.js deleted file mode 100644 index f8ef0540b..000000000 --- a/cypress/tests/smoke/STD-001.insertion.js +++ /dev/null @@ -1,29 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-001 -// Title: Validate alignment of a formula after insertion. -// Document: https://docs.google.com/document/d/1RTZlelOssfwWAqx-ilTvRatrEQoaFIpk6ErWa7xMwIw/edit -// Context: UI / Insertion -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("an inserted formula that looks like plain text should be aligned with the same plain text", function () { - // Type the text plane on the text editor - cy.typeInTextEditor(this.formulas["text-alignment"]); - - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-alignment"]); - - // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222 - cy.getFormula(0).should("have.attr", "style").and("contain", "vertical-align: -4px"); -}); diff --git a/cypress/tests/smoke/STD-002.insertion.js b/cypress/tests/smoke/STD-002.insertion.js deleted file mode 100644 index 42b4f6590..000000000 --- a/cypress/tests/smoke/STD-002.insertion.js +++ /dev/null @@ -1,32 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-002 -// Title: Validate alignment of a formula on preview. -// Document: https://docs.google.com/document/d/1aAPzvAe8WEEXgZECLsmml07TG4l3Fdy3AlJiaFSp6Iw/edit -// Context: UI / Insertion -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("an inserted formula that looks like plain text should be aligned with the same plane text on preview", function () { - // Type the text plane on the text editor - cy.typeInTextEditor(this.formulas["text-alignment"]); - - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-alignment"]); - - // // Click the update button - // cy.get('#btn_update').click(); - - // // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222 - // cy.getFormula(1).should('have.attr', 'style').and('contain', 'vertical-align: -4px'); -}); diff --git a/cypress/tests/smoke/STD-003.insertion.js b/cypress/tests/smoke/STD-003.insertion.js deleted file mode 100644 index d6818bc2a..000000000 --- a/cypress/tests/smoke/STD-003.insertion.js +++ /dev/null @@ -1,36 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-003 -// Title: Validate caret is placed after the inserted formula -// Document: https://docs.google.com/document/d/1YjSGL5yfvdMQOrFrqL48tQ2vUgfxD6YNSUgqsH65xI8/edit -// Context: UI / Insertion -// Issue: - -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("Validate Hand formulas open Hand directly when edited", function () { - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-general"]); - - // User types the string โ€˜wirisโ€™ on the HTML editor - cy.typeInTextEditor("wiris"); - - // The string wiris is written right after the formula - cy.getTextEditor() - .children() - .first() // First paragraph - .then(($p) => { - // Get the second node inside the paragraph - cy.wrap($p[0].childNodes[1].textContent); - }) - .should("eq", "wiris"); -}); diff --git a/cypress/tests/smoke/STD-004.images.js b/cypress/tests/smoke/STD-004.images.js deleted file mode 100644 index 648bdb82e..000000000 --- a/cypress/tests/smoke/STD-004.images.js +++ /dev/null @@ -1,28 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-004 -// Title: Validate wiris formulas contain class with value Wirisformula. -// Document: https://docs.google.com/document/d/1LCM0z-kmZKdwpSMnrosMsyRVmZ5Zg5TNw_-VEkYyZng/edit -// Context: Integration / Image -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("formula should have wirisformula class", function () { - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-general"]); - - // Get the formula by it's alt text and assert it has the Wirisformula class - // We could find the formula by using getFormula, but internally, that looks for - // .Wirisformula, so it defeats the purpose. That's why we use the alt instead. - cy.get(`img[alt="${this.formulas["formula-general-alt-es"]}"]`).should("have.class", "Wirisformula"); -}); diff --git a/cypress/tests/smoke/STD-005.images.js b/cypress/tests/smoke/STD-005.images.js deleted file mode 100644 index d0673d1d4..000000000 --- a/cypress/tests/smoke/STD-005.images.js +++ /dev/null @@ -1,35 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-005 -// Title: Validate formula height and width is correct. -// Document: https://docs.google.com/document/d/167zTPA2JxtbPaxdCp8kBKIEHoRyjHZnIWYmOPLEEMY4/edit -// Context: UI / Image -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("The inserted formula should have the correct width and height", () => { - // Open the mathtype modal - cy.clickButtonToOpenModal(); - - // Write a mathtype formula: x/3 - cy.typeInModal("{ctrl}/").type("x").type("{downarrow}3"); - - // Insert the written formula by clicking the insert button on the modal - cy.clickModalButton("insert"); - - // Get the previous inserted formula - cy.getFormula(0).then(($formula) => { - const formula = $formula[0]; - - // Assert that the width and height are the ones writen in the test case for the inserted formula - expect(formula.width).to.equal(18); - expect(formula.height).to.equal(41); - }); -}); diff --git a/cypress/tests/smoke/STD-007.images.js b/cypress/tests/smoke/STD-007.images.js deleted file mode 100644 index d743c7340..000000000 --- a/cypress/tests/smoke/STD-007.images.js +++ /dev/null @@ -1,26 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-007 -// Title: Validate wiris formulas contain alt attribute. -// Document: https://docs.google.com/document/d/1Sa83zG7-sRpS1WIPQNTLtaeUFrwqZVSpdTbrtnkZHVI/edit -// Context: UI / Image -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("A wiris formula should have the alt attribute", function () { - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-general"]); - - // Get the previous inserted formula - cy.getFormula(0).should("have.attr", "alt"); -}); diff --git a/cypress/tests/smoke/STD-010.images.js b/cypress/tests/smoke/STD-010.images.js deleted file mode 100644 index 12ee07397..000000000 --- a/cypress/tests/smoke/STD-010.images.js +++ /dev/null @@ -1,30 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-010 -// Title: Validate the formula is the same on editing mode and on preview . -// Document: https://docs.google.com/document/d/1bRxBBG_OLS_1HTGdOBRbHHIRJPZHboVJ2Zy68z80fyY/edit -// Context: UI / Images -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("an inserted formula should be the same on preview when this is updated", function () { - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-general"], true); - - // // Click the update button - // cy.get('#btn_update').click(); - - // // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222 - // cy.getFormula(0).isRendered().and('have.attr', 'alt', this.formulas['formula-general-alt-es']); - // cy.getFormula(1).isRendered().and('have.attr', 'alt', this.formulas['formula-general-alt-en']); -}); diff --git a/cypress/tests/smoke/STD-011.modal.js b/cypress/tests/smoke/STD-011.modal.js deleted file mode 100644 index 0f11b4449..000000000 --- a/cypress/tests/smoke/STD-011.modal.js +++ /dev/null @@ -1,20 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-011 -// Title: Check Hand icon is visible. -// Document: https://docs.google.com/document/d/12cxOZRwLVhE_Aby2Ckjjee2WWJcTuceOcXmuBgZBAlE/edit -// Context: UI / Modal -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Visit the page. - cy.visit("/"); -}); - -it("Hand icon should be visible on the mathtype modal", () => { - // Open the mathtype modal - cy.clickButtonToOpenModal(); - - // Check that the hand button is visible on mathtype modal - cy.get(".wrs_handWrapper > input").should("be.visible"); -}); diff --git a/cypress/tests/smoke/STD-012.modal.js b/cypress/tests/smoke/STD-012.modal.js deleted file mode 100644 index e8c73abc2..000000000 --- a/cypress/tests/smoke/STD-012.modal.js +++ /dev/null @@ -1,32 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-011 -// Title: Check Minimize, Maximize, and Close icons are visible in the modal. -// Document: https://docs.google.com/document/d/1soW156YvORb3TIumKmlIjhpRccme5c_YyQhVLypIh0s/edit -// Context: UI / Modal -// Issue: KB-13069 -// *********************************************************** -describe("Resize modal icons are visible", () => { - beforeEach(() => { - // Visit the page. - cy.visit("/"); - - // Open the mathtype modal - cy.clickButtonToOpenModal(); - }); - - it("minimize icon should be visible on mathtype modal", () => { - // Check that minimize button is visible on mathtype modal - cy.get(".wrs_modal_minimize_button").should("be.visible"); - }); - - it("maximize icon should be visible on mathtype modal", () => { - // Check that minimize button is visible on mathtype modal - cy.get(".wrs_modal_maximize_button").should("be.visible"); - }); - - it("close icon should be visible on mathtype modal", () => { - // Check that minimize button is visible on mathtype modal - cy.get(".wrs_modal_close_button").should("be.visible"); - }); -}); diff --git a/cypress/tests/smoke/STD-014.insertion.js b/cypress/tests/smoke/STD-014.insertion.js deleted file mode 100644 index 752ff3d32..000000000 --- a/cypress/tests/smoke/STD-014.insertion.js +++ /dev/null @@ -1,23 +0,0 @@ -/// - -beforeEach(() => { - // Load fixtures - cy.fixture("formulas.json").as("formulas"); - - // Visit page - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("User creates a new formula from scratch using MT", function () { - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-general"]); - - // MT editor modal window is closed. - cy.get(".wrs_modal_dialogContainer").should("not.to.be.visible"); - - // The formula is inserted at the beginning of the HTML editor content and perfectly rendered - cy.getFormula(0).isRendered(); -}); diff --git a/cypress/tests/smoke/STD-016.insertion.js b/cypress/tests/smoke/STD-016.insertion.js deleted file mode 100644 index 807c9d38d..000000000 --- a/cypress/tests/smoke/STD-016.insertion.js +++ /dev/null @@ -1,46 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-016 -// Title: User edits a formula by Double-click and inserts it. -// Document: https://docs.google.com/document/d/1bIZOmDigkvhMCpAcTf81nz3Wp252aZpyPol9AxY0OXY/edit -// Context: E2E / Insertion -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("should be able to edit an existing formula", { retries: 3 }, function () { - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-general"]); - - // Double-click the previous inserted formula to start editing it - cy.getFormula(0).dblclick(); - - // Assert that the toolbar is visible so that we know the modal is fully loaded - cy.get(".wrs_toolbar").should("be.visible"); - - // Wait for the formula clocked to be loaded - cy.get(".wrs_container").invoke("text").should("contain", "y"); // .children().should('have.length.at.least', 9); - - // Modify the opened formula - cy.get(".wrs_focusElement").click().type(this.formulas["formula-addition"]); - // cy.typeInModal('{movetostart}{del}{del}{del}'); - - // Click the insert button on the mathtype modal to insert the previous edited formula - cy.clickModalButton("insert"); - - // Expect the formula to be edited propertly - cy.getFormula(0) - .should("have.attr", "alt") - .then((alt) => { - expect(alt).to.equal(this.formulas["formula-total-alt-es"]); - }); -}); diff --git a/cypress/tests/smoke/STD-017.insertion.js b/cypress/tests/smoke/STD-017.insertion.js deleted file mode 100644 index 769c3e2e3..000000000 --- a/cypress/tests/smoke/STD-017.insertion.js +++ /dev/null @@ -1,46 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-017 -// Title: User edits a formula and cancels the edition. -// Document: https://docs.google.com/document/d/1CqlEq9p0oVrRhpXjaguehtA4LBl9157qSZ_vg0pdswM/edit -// Context: E2E / Insertion -// Issue: KB-13069 -// *********************************************************** - -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("should be able to edit and existing formula and cancel the edition", function () { - // Insert a new MathType formula from scratch on the editor - cy.insertFormulaFromScratch(this.formulas["formula-general"]); - - // Double-click the previous inserted formula to start editing it - cy.getFormula(0).dblclick(); - - // Edit the opened formula by adding some other content (=y) - cy.typeInModal(this.formulas["formula-addition"]); - - // Click the cancel button after editing the formula on the mathtype modal - cy.clickModalButton("cancel"); - - // CLick the close button on the confirmation close mathtype modal the cancel all changes and close it - cy.clickModalButton("confirmationClose"); - - // Assert the formula has no changes - cy.getFormula(0) - .should("have.attr", "alt") - .then((alt) => { - expect(alt).to.equal(this.formulas["formula-general-alt-es"]); - }); - - // Verify the formula is propertly rendered - cy.getFormula(0).isRendered(); -}); diff --git a/cypress/tests/smoke/STD-019.insertion.js b/cypress/tests/smoke/STD-019.insertion.js deleted file mode 100644 index 9b43252c8..000000000 --- a/cypress/tests/smoke/STD-019.insertion.js +++ /dev/null @@ -1,33 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-019 -// Title: User edits a latex formula. -// Document: https://docs.google.com/document/d/1tkYS_g5ZZcjIiUT-nMPv4G2AGlcumw9siTY_B1bFtkE/edit -// Context: E2E / Insertion -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Load fixture data - cy.fixture("formulas.json").as("formulas"); - - // Visit the page. - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("should be able to edit an existing latex formula", function () { - // Write a latex formula on the text editor - cy.typeInTextEditor(this.formulas["latex-general"]); - - // Edit the first latex formula with mathtype - cy.editFormula(this.formulas["latex-addition"], { - chem: false, - latex: true, - formulaId: 0, - }); - - // Expect that the text editor contains the latex formula - cy.getTextEditor().invoke("text").should("contain", "$$\\cos^2(x)+\\sin^2(x)=\\log(e)$$"); -}); diff --git a/cypress/tests/smoke/STD-020.modal.js b/cypress/tests/smoke/STD-020.modal.js deleted file mode 100644 index 14b85859c..000000000 --- a/cypress/tests/smoke/STD-020.modal.js +++ /dev/null @@ -1,23 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-020 -// Title: User opens MT and closes it via ESC -// Document: https://docs.google.com/document/d/1v6NyWfvoFrgX7CWufN12_tCzyO9ITvX3HTAXTIYWCSQ/edit -// Context: E2E / Modal -// Issue: - -// *********************************************************** -beforeEach(() => { - // Visit page - cy.visit("/"); -}); - -it("User opens MT and closes it via ESC", () => { - // Click the MT button in the HTML editor toolbar - cy.clickButtonToOpenModal(); - - // Press the ESC key - cy.pressESCButton(); - - // MT editor modal window is closed - cy.get(".wrs_modal_dialogContainer").should("not.be.visible"); -}); diff --git a/cypress/tests/smoke/STD-021.modal.js b/cypress/tests/smoke/STD-021.modal.js deleted file mode 100644 index 02076ec3c..000000000 --- a/cypress/tests/smoke/STD-021.modal.js +++ /dev/null @@ -1,23 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-021 -// Title: Open and close the mathtype modal without adding any formula. -// Document: https://docs.google.com/document/d/1MloNEApADlavZHfODqScyGCNUrZyMgiGYrPx46c4waQ/edit -// Context: E2E / Modal -// Issue: KB-13069 -// *********************************************************** -beforeEach(() => { - // Visit the page. - cy.visit("/"); -}); - -it("should be able to edit and existing formula and cancel the edition", () => { - // Open the mathtype modal bu clicking the mathtype button - cy.clickButtonToOpenModal(); - - // Click the cancel button on the mathtype modal to close the modal - cy.clickModalButton("cancel"); - - // Verify the modal is closed - cy.get(".wrs_focusElement").should("not.be.visible"); -}); diff --git a/cypress/tests/smoke/STD-022.modal.js b/cypress/tests/smoke/STD-022.modal.js deleted file mode 100644 index 77a8e842e..000000000 --- a/cypress/tests/smoke/STD-022.modal.js +++ /dev/null @@ -1,40 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-022 -// Title: User opens MT, edits an equation and Cancels. Accepts the โ€˜are you sure you want to leave?โ€™ dialog -// Document: https://docs.google.com/document/d/11R4j3ZW0a50Lp02frqihtfZPeBFAYfN_xtqrs3AejdM/edit -// Context: E2E / Modal -// Issue: - -// *********************************************************** -beforeEach(() => { - // Load fixtures - cy.fixture("formulas.json").as("formulas"); - - // Visit page - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("User opens MT, edits an equation and Cancels. Accepts the โ€˜are you sure you want to leave?โ€™ dialog", function () { - // Click on the MT icon in the HTML editor toolbar - cy.clickButtonToOpenModal(); - - // Type the formula - cy.typeInModal(this.formulas["formula-general"]); - - // Click the Cancel button in the MT editor - cy.clickModalButton("cancel"); - - // Click the Close button from the โ€˜Are you sure you want to leave?โ€™ dialog - cy.clickModalButton("confirmationClose"); - - // The MT editor modal window is closed and... - cy.get(".wrs_modal_dialogContainer").should("not.to.be.visible"); - - // ... no formula is inserted to the HTML editor - // We check for the 2nd formula, as currently the demos come with one formula by default - // (So one .Wirisformula in the editor and one .Wirisformula in the preview) - cy.get(".Wirisformula").eq(1).should("not.exist"); -}); diff --git a/cypress/tests/smoke/STD-023.modal.js b/cypress/tests/smoke/STD-023.modal.js deleted file mode 100644 index 583add900..000000000 --- a/cypress/tests/smoke/STD-023.modal.js +++ /dev/null @@ -1,40 +0,0 @@ -/// -// *********************************************************** -// Test case: INT-STD-023 -// Title: User opens MT, edits an equation and closes the modal via X button. Denies the โ€˜are you sure you want to leave?โ€™ dialog and inserts the formula -// Document: https://docs.google.com/document/d/1EaC9zB9eIADTk06j3TyPouOwzIp3AHael5zV39kOMyM/edit -// Context: E2E / Modal -// Issue: - -// *********************************************************** -beforeEach(() => { - // Load fixtures - cy.fixture("formulas.json").as("formulas"); - - // Visit page - cy.visit("/"); - - // Clear the editor content in order to reduce noise - cy.getTextEditor().clear(); -}); - -it("User opens MT, edits an equation and closes the modal via X button. Denies the โ€˜are you sure you want to leave?โ€™ dialog and inserts the formula", function () { - // Click on the MT icon in the HTML editor toolbar - cy.clickButtonToOpenModal(); - - // Type the formula - cy.typeInModal(this.formulas["formula-general"]); - - // Click the โ€˜Xโ€™ button of the MT modal window in order to close it - cy.clickModalButton("xClose"); - - // Click the Cancel button from the โ€˜Are you sure you want to leave?โ€™ dialog - cy.clickModalButton("confirmationCancel"); - - // Click the Insert button in the MT modal window - cy.clickModalButton("insert"); - - // The formula is rendered in the HTML editor - // We check for the 3rd formula, as currently the demos come with one formula by default - // (So one .Wirisformula in the editor and one .Wirisformula in the preview) - cy.getFormula(0).should("be.visible"); -}); diff --git a/cypress/tests/ui/.gitkeep b/cypress/tests/ui/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cypress/tests/validation/.gitkeep b/cypress/tests/validation/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/package.json b/package.json index c2a3b8f2c..7ee69a982 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "postinstall": "rm -rf ~/.config/yarn/link/* && for d in packages/*/ ; do (cd $d && yarn link); done", "test-old": "node scripts/services/executeTests.js", "test": "nx run-many --target=test --all --parallel", - "test:ci": "docker run -v $PWD:/cypress --net=host -w /cypress -e CYPRESS_PROJECT_ID --entrypoint=cypress cypress/included:7.5.0 run --project .", + "test:e2e": "playwright test", "build": "cd demos/html/generic && npm install && npm start &", "lint": "prettier --write . --ignore-path .gitignore --ignore-path .prettierignore --ignore-path .eslintignore && nx run-many --target=lint --all --parallel --fix" }, @@ -18,14 +18,15 @@ "@babel/eslint-parser": "^7.24.1", "@nrwl/js": "18.2.2", "@nrwl/tao": "18.2.2", - "@nx/cypress": "18.2.2", "@nx/eslint-plugin": "18.2.2", "@nx/linter": "18.2.2", "@nx/web": "18.2.2", "@nx/webpack": "18.2.2", "@nx/workspace": "18.2.2", + "@playwright/test": "^1.40.0", "@types/node": "20.12.4", "clean-webpack-plugin": "^4.0.0", + "dotenv": "^16.3.1", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.29.0", diff --git a/packages/ckeditor5/dist/browser/index.css.map b/packages/ckeditor5/dist/browser/index.css.map index 0057d38dd..054e78e97 100644 --- a/packages/ckeditor5/dist/browser/index.css.map +++ b/packages/ckeditor5/dist/browser/index.css.map @@ -1 +1 @@ -{"version":3,"sources":["../../../devkit/styles/styles.css"],"names":[],"mappings":"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","file":"index.css","sourcesContent":[".wrs_modal_overlay {\n position: fixed;\n font-family: arial, sans-serif;\n top: 0;\n right: 0;\n left: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.8);\n z-index: 999998;\n opacity: 0.65;\n pointer-events: auto;\n}\n\n.wrs_modal_overlay.wrs_modal_ios {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_android {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_ios.moodle {\n position: fixed;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_maximized {\n background: rgba(0, 0, 0, 0.8);\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_minimized {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_closed {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_title {\n color: #fff;\n padding: 5px 0 5px 10px;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n text-align: left;\n}\n\n.wrs_modal_close_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n margin: 10px 7px 0 0;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_minimize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n top: inherit;\n margin: 10px 7px 0 0;\n}\n\n.wrs_modal_stack_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_stack_button.wrs_stack {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_stack_button.wrs_minimized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_maximize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_maximize_button.wrs_maximized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_title_bar {\n display: block;\n background-color: #778e9a;\n}\n\n.wrs_modal_dialogContainer {\n border: none;\n background: #fafafa;\n z-index: 999999;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop {\n font-size: 14px;\n display: flex;\n flex-flow: column;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_maximized {\n position: fixed;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized {\n position: fixed;\n top: inherit;\n margin: 0;\n margin-right: 10px;\n}\n\n.wrs_modal_dialogContainer.wrs_closed {\n visibility: hidden;\n display: none;\n opacity: 0;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized.wrs_drag {} */\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_stack {\n position: fixed;\n bottom: 0;\n right: 0;\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_ios,\n.wrs_modal_dialogContainer.wrs_modal_android {\n margin: 0px;\n position: fixed;\n height: auto;\n overflow: hidden;\n top: calc(env(safe-area-inset-top));\n right: calc(env(safe-area-inset-right));\n left: calc(env(safe-area-inset-left));\n bottom: 0px;\n transform: none;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_content_container.wrs_maximized {} */\n\n.wrs_content_container.wrs_minimized {\n display: none;\n}\n\n/* .wrs_editor {\n flex-grow: 1;\n} */\n\n.wrs_content_container.wrs_modal_desktop > div:first-child {\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_android,\n.wrs_modal_wrapper.wrs_modal_ios {\n margin: 0.5rem !important;\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_ios,\n.wrs_content_container.wrs_modal_android {\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_desktop {\n width: 100%;\n flex-grow: 1;\n display: flex;\n flex-direction: column;\n height: auto !important;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_modal_wrapper.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_noselect {\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_right_resizer {\n width: 10px;\n height: 10px;\n color: #778e9a;\n position: absolute;\n right: 4px;\n bottom: 8px;\n cursor: se-resize;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_left_resizer {\n width: 15px;\n height: 15px;\n color: #778e9a;\n position: absolute;\n left: 0;\n top: 0;\n cursor: se-resize;\n}\n\n.wrs_modal_controls {\n height: 42px;\n line-height: normal;\n}\n\n.wrs_modal_links {\n margin: 10px auto;\n margin-bottom: 0;\n font-family: arial, sans-serif;\n padding: 6px;\n display: inline;\n float: right;\n text-align: right;\n}\n\n.wrs_modal_links > a {\n text-decoration: none;\n color: #778e9a;\n font-size: 16px;\n}\n\n.wrs_modal_button_cancel,\n.wrs_modal_button_cancel:hover,\n.wrs_modal_button_cancel:visited,\n.wrs_modal_button_cancel:active,\n.wrs_modal_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n height: 32px;\n}\n\n.wrs_modal_button_accept,\n.wrs_modal_button_accept:hover,\n.wrs_modal_button_accept:visited,\n.wrs_modal_button_accept:active,\n.wrs_modal_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor_vertical_bar {\n height: 20px;\n float: right;\n background: none;\n width: 20px;\n cursor: pointer;\n}\n\n.wrs_modal_buttons_container {\n display: inline;\n float: left;\n}\n\n.wrs_modal_buttons_container.wrs_modalDesktop {\n padding-left: 0;\n}\n\n.wrs_modal_buttons_container > button {\n line-height: normal;\n background-image: none;\n}\n\n.wrs_modal_wrapper {\n margin: 6px;\n display: flex;\n flex-direction: column;\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_desktop.wrs_minimized {\n display: none;\n}\n\n.wrs_popupmessage_overlay_envolture {\n display: none;\n width: 100%;\n}\n\n.wrs_popupmessage_overlay {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 4;\n cursor: pointer;\n}\n\n.wrs_popupmessage_panel {\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n position: absolute;\n background: white;\n max-width: 500px;\n width: 75%;\n border-radius: 2px;\n padding: 20px;\n font-family: sans-serif;\n font-size: 15px;\n text-align: left;\n color: #2e2e2e;\n z-index: 5;\n max-height: 75%;\n overflow: auto;\n}\n\n.wrs_popupmessage_button_area {\n margin: 10px 0 0 0;\n}\n\n.wrs_panelContainer * {\n border: 0;\n}\n\n.wrs_button_cancel,\n.wrs_button_cancel:hover,\n.wrs_button_cancel:visited,\n.wrs_button_cancel:active,\n.wrs_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n background-image: none;\n height: 32px;\n}\n\n.wrs_button_accept,\n.wrs_button_accept:hover,\n.wrs_button_accept:visited,\n.wrs_button_accept:active,\n.wrs_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor button {\n box-shadow: none;\n}\n\n.wrs_editor .wrs_header button {\n border-bottom: none;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack.wrs_overlay_active {\n display: block;\n}\n\n/* Fix selection in drupal style */\n.wrs_toolbar tr:focus {\n background: none;\n}\n\n.wrs_toolbar tr:hover {\n background: none;\n}\n\n/* End of fix drupal */\n.wrs_modal_rtl .wrs_modal_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_modal_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n.wrs_modal_rtl .wrs_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n/* The Offline Modal (background) */\n.wrs_modal_offline {\n display: none;\n /* Hidden by default */\n position: fixed;\n /* Stay in place */\n z-index: 2;\n /* Sit on top */\n padding-top: 150px;\n /* Location of the box */\n left: 0;\n top: 0;\n width: 100%;\n /* Full width */\n height: 100%;\n /* Full height */\n overflow: auto;\n /* Enable scroll if needed */\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n margin: auto;\n}\n\n/* Modal Content */\n.wrs_modal_content_offline {\n margin: auto;\n padding: 16px;\n background: #fff7ed;\n border-radius: 6px;\n width: 517px;\n height: 100px;\n position: relative;\n}\n\n/* The Close Button */\n.wrs_modal_offline_close {\n color: #c2410c;\n font-size: 24px;\n font-weight: bold;\n left: 95.7%;\n right: 2.08%;\n top: 7.6%;\n bottom: 75%;\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n position: absolute;\n}\n\n/* The Warn Icon */\n.wrs_modal_offline_warn {\n position: absolute;\n left: 2.08%;\n right: 94%;\n top: 11.6%;\n bottom: 75%;\n font-size: 24px;\n color: #fb923c;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_offline_text_container {\n position: absolute;\n left: 6.8%;\n right: 6.08%;\n top: 10.4%;\n bottom: 2%;\n}\n\n.wrs_modal_offline_text {\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n color: #c2410c;\n}\n\n.wrs_modal_offline_text_warn {\n height: 25px;\n font-family: \"Inter\";\n font-style: normal;\n font-size: 14px;\n line-height: 20px;\n font-weight: bold;\n color: #c2410c;\n}\n\n.wrs_modal_offline_close:hover,\n.wrs_modal_offline_close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n"]} \ No newline at end of file +{"version":3,"sources":["../../../devkit/styles/styles.css"],"names":[],"mappings":"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","file":"index.css","sourcesContent":[".wrs_modal_overlay {\r\n position: fixed;\r\n font-family: arial, sans-serif;\r\n top: 0;\r\n right: 0;\r\n left: 0;\r\n bottom: 0;\r\n background: rgba(0, 0, 0, 0.8);\r\n z-index: 999998;\r\n opacity: 0.65;\r\n pointer-events: auto;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_ios {\r\n visibility: hidden;\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_android {\r\n visibility: hidden;\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_ios.moodle {\r\n position: fixed;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack {\r\n background: rgba(0, 0, 0, 0);\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_maximized {\r\n background: rgba(0, 0, 0, 0.8);\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_minimized {\r\n background: rgba(0, 0, 0, 0);\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_closed {\r\n background: rgba(0, 0, 0, 0);\r\n display: none;\r\n}\r\n\r\n.wrs_modal_title {\r\n color: #fff;\r\n padding: 5px 0 5px 10px;\r\n -moz-user-select: none;\r\n -webkit-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n text-align: left;\r\n}\r\n\r\n.wrs_modal_close_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n padding: 5px 10px 5px 0;\r\n margin: 10px 7px 0 0;\r\n background-repeat: no-repeat;\r\n}\r\n\r\n.wrs_modal_minimize_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n padding: 5px 10px 5px 0;\r\n top: inherit;\r\n margin: 10px 7px 0 0;\r\n}\r\n\r\n.wrs_modal_stack_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n margin: 10px 7px 0 0;\r\n padding: 5px 10px 5px 0;\r\n top: inherit;\r\n}\r\n\r\n.wrs_modal_stack_button.wrs_stack {\r\n visibility: hidden;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.wrs_modal_stack_button.wrs_minimized {\r\n visibility: hidden;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.wrs_modal_maximize_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n margin: 10px 7px 0 0;\r\n padding: 5px 10px 5px 0;\r\n top: inherit;\r\n}\r\n\r\n.wrs_modal_maximize_button.wrs_maximized {\r\n visibility: hidden;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.wrs_modal_title_bar {\r\n display: block;\r\n background-color: #778e9a;\r\n}\r\n\r\n.wrs_modal_dialogContainer {\r\n border: none;\r\n background: #fafafa;\r\n z-index: 999999;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop {\r\n font-size: 14px;\r\n display: flex;\r\n flex-flow: column;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_maximized {\r\n position: fixed;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized {\r\n position: fixed;\r\n top: inherit;\r\n margin: 0;\r\n margin-right: 10px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_closed {\r\n visibility: hidden;\r\n display: none;\r\n opacity: 0;\r\n}\r\n\r\n/* Class that exists but hasn't got css properties defined\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized.wrs_drag {} */\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_stack {\r\n position: fixed;\r\n bottom: 0;\r\n right: 0;\r\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_drag {\r\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_drag {\r\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_ios,\r\n.wrs_modal_dialogContainer.wrs_modal_android {\r\n margin: 0px;\r\n position: fixed;\r\n height: auto;\r\n overflow: hidden;\r\n top: calc(env(safe-area-inset-top));\r\n right: calc(env(safe-area-inset-right));\r\n left: calc(env(safe-area-inset-left));\r\n bottom: 0px;\r\n transform: none;\r\n box-sizing: border-box;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n/* Class that exists but hasn't got css properties defined\r\n.wrs_content_container.wrs_maximized {} */\r\n\r\n.wrs_content_container.wrs_minimized {\r\n display: none;\r\n}\r\n\r\n/* .wrs_editor {\r\n flex-grow: 1;\r\n} */\r\n\r\n.wrs_content_container.wrs_modal_desktop > div:first-child {\r\n flex-grow: 1;\r\n}\r\n\r\n.wrs_modal_wrapper.wrs_modal_android,\r\n.wrs_modal_wrapper.wrs_modal_ios {\r\n margin: 0.5rem !important;\r\n flex-grow: 0.98;\r\n display: flex;\r\n}\r\n\r\n.wrs_content_container.wrs_modal_ios,\r\n.wrs_content_container.wrs_modal_android {\r\n flex-grow: 0.98;\r\n display: flex;\r\n}\r\n\r\n.wrs_content_container.wrs_modal_desktop {\r\n width: 100%;\r\n flex-grow: 1;\r\n display: flex;\r\n flex-direction: column;\r\n height: auto !important;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_badStock {\r\n width: 100%;\r\n height: 280px;\r\n margin: 0 auto;\r\n border-width: 0;\r\n}\r\n\r\n.wrs_modal_wrapper.wrs_modal_badStock {\r\n width: 100%;\r\n height: 280px;\r\n margin: 0 auto;\r\n border-width: 0;\r\n}\r\n\r\n.wrs_noselect {\r\n -moz-user-select: none;\r\n -khtml-user-select: none;\r\n -webkit-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n.wrs_bottom_right_resizer {\r\n width: 10px;\r\n height: 10px;\r\n color: #778e9a;\r\n position: absolute;\r\n right: 4px;\r\n bottom: 8px;\r\n cursor: se-resize;\r\n -moz-user-select: none;\r\n -webkit-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n.wrs_bottom_left_resizer {\r\n width: 15px;\r\n height: 15px;\r\n color: #778e9a;\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n cursor: se-resize;\r\n}\r\n\r\n.wrs_modal_controls {\r\n height: 42px;\r\n line-height: normal;\r\n}\r\n\r\n.wrs_modal_links {\r\n margin: 10px auto;\r\n margin-bottom: 0;\r\n font-family: arial, sans-serif;\r\n padding: 6px;\r\n display: inline;\r\n float: right;\r\n text-align: right;\r\n}\r\n\r\n.wrs_modal_links > a {\r\n text-decoration: none;\r\n color: #778e9a;\r\n font-size: 16px;\r\n}\r\n\r\n.wrs_modal_button_cancel,\r\n.wrs_modal_button_cancel:hover,\r\n.wrs_modal_button_cancel:visited,\r\n.wrs_modal_button_cancel:active,\r\n.wrs_modal_button_cancel:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-left: 5px;\r\n margin-bottom: 0;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n background-color: #ddd;\r\n height: 32px;\r\n}\r\n\r\n.wrs_modal_button_accept,\r\n.wrs_modal_button_accept:hover,\r\n.wrs_modal_button_accept:visited,\r\n.wrs_modal_button_accept:active,\r\n.wrs_modal_button_accept:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-right: 5px;\r\n margin-bottom: 0;\r\n color: #fff;\r\n background: #778e9a;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n height: 32px;\r\n}\r\n\r\n.wrs_editor_vertical_bar {\r\n height: 20px;\r\n float: right;\r\n background: none;\r\n width: 20px;\r\n cursor: pointer;\r\n}\r\n\r\n.wrs_modal_buttons_container {\r\n display: inline;\r\n float: left;\r\n}\r\n\r\n.wrs_modal_buttons_container.wrs_modalDesktop {\r\n padding-left: 0;\r\n}\r\n\r\n.wrs_modal_buttons_container > button {\r\n line-height: normal;\r\n background-image: none;\r\n}\r\n\r\n.wrs_modal_wrapper {\r\n margin: 6px;\r\n display: flex;\r\n flex-direction: column;\r\n flex-grow: 1;\r\n}\r\n\r\n.wrs_modal_wrapper.wrs_modal_desktop.wrs_minimized {\r\n display: none;\r\n}\r\n\r\n.wrs_popupmessage_overlay_envolture {\r\n display: none;\r\n width: 100%;\r\n}\r\n\r\n.wrs_popupmessage_overlay {\r\n position: absolute;\r\n width: 100%;\r\n height: 100%;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n z-index: 4;\r\n cursor: pointer;\r\n}\r\n\r\n.wrs_popupmessage_panel {\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n position: absolute;\r\n background: white;\r\n max-width: 500px;\r\n width: 75%;\r\n border-radius: 2px;\r\n padding: 20px;\r\n font-family: sans-serif;\r\n font-size: 15px;\r\n text-align: left;\r\n color: #2e2e2e;\r\n z-index: 5;\r\n max-height: 75%;\r\n overflow: auto;\r\n}\r\n\r\n.wrs_popupmessage_button_area {\r\n margin: 10px 0 0 0;\r\n}\r\n\r\n.wrs_panelContainer * {\r\n border: 0;\r\n}\r\n\r\n.wrs_button_cancel,\r\n.wrs_button_cancel:hover,\r\n.wrs_button_cancel:visited,\r\n.wrs_button_cancel:active,\r\n.wrs_button_cancel:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-left: 5px;\r\n margin-bottom: 0;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n background-color: #ddd;\r\n background-image: none;\r\n height: 32px;\r\n}\r\n\r\n.wrs_button_accept,\r\n.wrs_button_accept:hover,\r\n.wrs_button_accept:visited,\r\n.wrs_button_accept:active,\r\n.wrs_button_accept:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-right: 5px;\r\n margin-bottom: 0;\r\n color: #fff;\r\n background: #778e9a;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n height: 32px;\r\n}\r\n\r\n.wrs_editor button {\r\n box-shadow: none;\r\n}\r\n\r\n.wrs_editor .wrs_header button {\r\n border-bottom: none;\r\n border-bottom-left-radius: 0;\r\n border-bottom-right-radius: 0;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack.wrs_overlay_active {\r\n display: block;\r\n}\r\n\r\n/* Fix selection in drupal style */\r\n.wrs_toolbar tr:focus {\r\n background: none;\r\n}\r\n\r\n.wrs_toolbar tr:hover {\r\n background: none;\r\n}\r\n\r\n/* End of fix drupal */\r\n.wrs_modal_rtl .wrs_modal_button_cancel {\r\n margin-right: 5px;\r\n margin-left: 0;\r\n}\r\n\r\n.wrs_modal_rtl .wrs_modal_button_accept {\r\n margin-right: 0;\r\n margin-left: 5px;\r\n}\r\n\r\n.wrs_modal_rtl .wrs_button_cancel {\r\n margin-right: 5px;\r\n margin-left: 0;\r\n}\r\n\r\n.wrs_modal_rtl .wrs_button_accept {\r\n margin-right: 0;\r\n margin-left: 5px;\r\n}\r\n\r\n/* The Offline Modal (background) */\r\n.wrs_modal_offline {\r\n display: none;\r\n /* Hidden by default */\r\n position: fixed;\r\n /* Stay in place */\r\n z-index: 2;\r\n /* Sit on top */\r\n padding-top: 150px;\r\n /* Location of the box */\r\n left: 0;\r\n top: 0;\r\n width: 100%;\r\n /* Full width */\r\n height: 100%;\r\n /* Full height */\r\n overflow: auto;\r\n /* Enable scroll if needed */\r\n background-color: rgb(0, 0, 0);\r\n /* Fallback color */\r\n background-color: rgba(0, 0, 0, 0.4);\r\n /* Black w/ opacity */\r\n margin: auto;\r\n}\r\n\r\n/* Modal Content */\r\n.wrs_modal_content_offline {\r\n margin: auto;\r\n padding: 16px;\r\n background: #fff7ed;\r\n border-radius: 6px;\r\n width: 517px;\r\n height: 100px;\r\n position: relative;\r\n}\r\n\r\n/* The Close Button */\r\n.wrs_modal_offline_close {\r\n color: #c2410c;\r\n font-size: 24px;\r\n font-weight: bold;\r\n left: 95.7%;\r\n right: 2.08%;\r\n top: 7.6%;\r\n bottom: 75%;\r\n font-family: \"Inter\";\r\n font-style: normal;\r\n font-weight: 400;\r\n position: absolute;\r\n}\r\n\r\n/* The Warn Icon */\r\n.wrs_modal_offline_warn {\r\n position: absolute;\r\n left: 2.08%;\r\n right: 94%;\r\n top: 11.6%;\r\n bottom: 75%;\r\n font-size: 24px;\r\n color: #fb923c;\r\n background-repeat: no-repeat;\r\n}\r\n\r\n.wrs_modal_offline_text_container {\r\n position: absolute;\r\n left: 6.8%;\r\n right: 6.08%;\r\n top: 10.4%;\r\n bottom: 2%;\r\n}\r\n\r\n.wrs_modal_offline_text {\r\n font-family: \"Inter\";\r\n font-style: normal;\r\n font-weight: 400;\r\n font-size: 14px;\r\n line-height: 20px;\r\n color: #c2410c;\r\n}\r\n\r\n.wrs_modal_offline_text_warn {\r\n height: 25px;\r\n font-family: \"Inter\";\r\n font-style: normal;\r\n font-size: 14px;\r\n line-height: 20px;\r\n font-weight: bold;\r\n color: #c2410c;\r\n}\r\n\r\n.wrs_modal_offline_close:hover,\r\n.wrs_modal_offline_close:focus {\r\n color: #000;\r\n text-decoration: none;\r\n cursor: pointer;\r\n}\r\n"]} \ No newline at end of file diff --git a/packages/ckeditor5/dist/browser/index.js b/packages/ckeditor5/dist/browser/index.js index 942be4b8e..a26662594 100644 --- a/packages/ckeditor5/dist/browser/index.js +++ b/packages/ckeditor5/dist/browser/index.js @@ -7170,25 +7170,25 @@ class Event { }; }; -var closeIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var closeIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var closeHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var closeHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var fullsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var fullsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var fullsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var fullsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var minIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var minIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var minHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var minHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var minsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var minsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var minsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var minsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var maxIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var maxIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; -var maxHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; +var maxHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; // eslint-disable-next-line max-classes-per-file const { unprotect, protect } = focusProtection(); @@ -9274,7 +9274,7 @@ if (!String.prototype.startsWith) { * @private */ Core._initialized = false; -var warnIcon = "\n\n\n"; +var warnIcon = "\r\n\r\n\r\n"; // eslint-disable-next-line no-unused-vars, import/named /** @@ -11465,11 +11465,11 @@ delete Array.prototype.__class__; // @codingStandardsIgnoreEnd } } -var mathIcon = "\n\n\n\n\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n"; +var mathIcon = "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n"; -var chemIcon = "\n\n\n\n\n\n"; +var chemIcon = "\r\n\r\n\r\n\r\n\r\n\r\n"; -var version = "8.13.4"; +var version = "8.14.0"; var packageInfo = { version: version}; diff --git a/packages/ckeditor5/dist/browser/index.js.map b/packages/ckeditor5/dist/browser/index.js.map index 36c36b0d2..01ac27819 100644 --- a/packages/ckeditor5/dist/browser/index.js.map +++ b/packages/ckeditor5/dist/browser/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../../../../node_modules/dompurify/dist/purify.es.mjs","../../../devkit/src/constants.js","../../../devkit/src/mathml.js","../../../devkit/src/configuration.js","../../../devkit/src/textcache.js","../../../devkit/src/listeners.js","../../../devkit/src/serviceprovider.js","../../../devkit/src/latex.js","../../../devkit/src/stringmanager.js","../../../devkit/src/util.js","../../../devkit/src/image.js","../../../devkit/src/accessibility.js","../../../devkit/src/parser.js","../../../devkit/src/editorlistener.js","../../../devkit/telemeter-wasm/telemeter_wasm.js","../../../devkit/src/telemeter.js","../../../devkit/src/contentmanager.js","../../../devkit/src/customeditors.js","../../../devkit/src/jsvariables.js","../../../devkit/src/event.js","../../../devkit/src/popupmessage.js","../../../devkit/src/focusprotection.js","../../../devkit/src/modal.js","../../../devkit/src/polyfills.js","../../../devkit/src/core.src.js","../../../devkit/src/integrationmodel.js","../../../devkit/src/md5.js","../../src/integration.js","../../src/commands.js","../../src/plugin.js"],"sourcesContent":["/*! @license DOMPurify 3.3.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.0/LICENSE */\n\nconst {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor\n} = Object;\nlet {\n freeze,\n seal,\n create\n} = Object; // eslint-disable-line import/no-mutable-exports\nlet {\n apply,\n construct\n} = typeof Reflect !== 'undefined' && Reflect;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!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}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\n * This class represents all the constants needed in a MathType integration among different classes.\n * If a constant should be used across different classes should be defined using attribute\n * accessors.\n */\nexport default class Constants {\n /**\n * Safe XML entities.\n * @type {Object}\n */\n static get safeXmlCharactersEntities() {\n return {\n tagOpener: \"«\",\n tagCloser: \"»\",\n doubleQuote: \"¨\",\n realDoubleQuote: \""\",\n };\n }\n\n /**\n * Blackboard invalid safe characters.\n * @type {Object}\n */\n static get safeBadBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยป<ยซ/moยป\",\n gtElement: \"ยซmoยป>ยซ/moยป\",\n ampElement: \"ยซmoยป&ยซ/moยป\",\n };\n }\n\n /**\n * Blackboard valid safe characters.\n * @type{Object}\n */\n static get safeGoodBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\n };\n }\n\n /**\n * Standard XML special characters.\n * @type {Object}\n */\n static get xmlCharacters() {\n return {\n id: \"xmlCharacters\",\n tagOpener: \"<\", // Hex: \\x3C.\n tagCloser: \">\", // Hex: \\x3E.\n doubleQuote: '\"', // Hex: \\x22.\n ampersand: \"&\", // Hex: \\x26.\n quote: \"'\", // Hex: \\x27.\n };\n }\n\n /**\n * Safe XML special characters. This characters are used instead the standard\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\n * special character have a UTF-8 representation.\n * @type {Object}\n */\n static get safeXmlCharacters() {\n return {\n id: \"safeXmlCharacters\",\n tagOpener: \"ยซ\", // Hex: \\xAB.\n tagCloser: \"ยป\", // Hex: \\xBB.\n doubleQuote: \"ยจ\", // Hex: \\xA8.\n ampersand: \"ยง\", // Hex: \\xA7.\n quote: \"`\", // Hex: \\x60.\n realDoubleQuote: \"ยจ\",\n };\n }\n}\n","import Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a class to manage MathML objects.\n */\nexport default class MathML {\n /**\n * Checks if the mathml at position i is inside an HTML attribute or not.\n * @param {string} content - a string containing MathML code.\n * @param {number} i - search index.\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\n */\n static isMathmlInAttribute(content, i) {\n // Regex =\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\n const expression = new RegExp(regex);\n\n const actualContent = content.substring(0, i);\n const reversed = actualContent.split(\"\").reverse().join(\"\");\n const exists = expression.test(reversed);\n\n return exists;\n }\n\n /**\n * Decodes an encoded MathML with standard XML tags.\n * We use these entities because IE doesn't support html entities\n * on its attributes sometimes. Yes, sometimes.\n * @param {string} input - string to be decoded.\n * @return {string} decoded string.\n */\n static safeXmlDecode(input) {\n let { tagOpener } = Constants.safeXmlCharactersEntities;\n let { tagCloser } = Constants.safeXmlCharactersEntities;\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\n // Decoding entities.\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n // Added to fix problem due to import from 1.9.x.\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\n\n // Blackboard.\n const { ltElement } = Constants.safeBadBlackboardCharacters;\n const { gtElement } = Constants.safeBadBlackboardCharacters;\n const { ampElement } = Constants.safeBadBlackboardCharacters;\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\n }\n\n ({ tagOpener } = Constants.safeXmlCharacters);\n ({ tagCloser } = Constants.safeXmlCharacters);\n ({ doubleQuote } = Constants.safeXmlCharacters);\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\n const { ampersand } = Constants.safeXmlCharacters;\n const { quote } = Constants.safeXmlCharacters;\n\n // Decoding characters.\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\n input = input.split(quote).join(Constants.xmlCharacters.quote);\n\n // We are replacing $ by & when its part of an entity for retro-compatibility.\n // Now, the standard is replace ยง by &.\n let returnValue = \"\";\n let currentEntity = null;\n\n for (let i = 0; i < input.length; i += 1) {\n const character = input.charAt(i);\n if (currentEntity == null) {\n if (character === \"$\") {\n currentEntity = \"\";\n } else {\n returnValue += character;\n }\n } else if (character === \";\") {\n returnValue += `&${currentEntity}`;\n currentEntity = null;\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\n // Character is part of an entity.\n currentEntity += character;\n } else {\n returnValue += `$${currentEntity}`; // Is not an entity.\n currentEntity = null;\n i -= 1; // Parse again the current character.\n }\n }\n\n return returnValue;\n }\n\n /**\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\n * @param {string} input - input string to be encoded\n * @returns {string} encoded string.\n */\n static safeXmlEncode(input) {\n const { tagOpener } = Constants.xmlCharacters;\n const { tagCloser } = Constants.xmlCharacters;\n const { doubleQuote } = Constants.xmlCharacters;\n const { ampersand } = Constants.xmlCharacters;\n const { quote } = Constants.xmlCharacters;\n\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\n\n return input;\n }\n\n /**\n * Converts special symbols (> 128) to entities and replaces all textual\n * entities by its number entities.\n * @param {string} mathml - MathML string containing - or not - special symbols\n * @returns {string} MathML with all textual entities replaced.\n */\n static mathMLEntities(mathml) {\n let toReturn = \"\";\n\n for (let i = 0; i < mathml.length; i += 1) {\n const character = mathml.charAt(i);\n\n // Parsing > 128 characters.\n if (mathml.codePointAt(i) > 128) {\n toReturn += `&#${mathml.codePointAt(i)};`;\n // For UTF-32 characters we need to move the index one position.\n if (mathml.codePointAt(i) > 0xffff) {\n i += 1;\n }\n } else if (character === \"&\") {\n const end = mathml.indexOf(\";\", i + 1);\n if (end >= 0) {\n const container = document.createElement(\"span\");\n container.innerHTML = mathml.substring(i, end + 1);\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\n i = end;\n } else {\n toReturn += character;\n }\n } else {\n toReturn += character;\n }\n }\n\n return toReturn;\n }\n\n /**\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\n * @param {string} customEditor - custom editor name.\n * @returns {string} MathML string with his class containing the editor toolbar string.\n */\n static addCustomEditorClassAttribute(mathml, customEditor) {\n let toReturn = \"\";\n\n const start = mathml.indexOf(\"\");\n if (mathml.indexOf(\"class\") === -1) {\n // Adding custom editor type.\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\n toReturn += mathml.substr(end + 1, mathml.length);\n return toReturn;\n }\n }\n return mathml;\n }\n\n /**\n * Remove a custom editor name from the MathML class attribute.\n * @param {string} mathml - a MathML string.\n * @param {string} customEditor - custom editor name.\n * @returns {string} The input MathML without customEditor name in his class.\n */\n static removeCustomEditorClassAttribute(mathml, customEditor) {\n // Discard MathML without the specified class.\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\n return mathml;\n }\n\n // Trivial case: class attribute value equal to editor name. Then\n // class attribute is removed.\n // First try to remove it with a space before if there is one\n // Otherwise without the space\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\n }\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\n }\n\n // Non Trivial case: class attribute contains editor name.\n return mathml.replace(`wrs_${customEditor}`, \"\");\n }\n\n /**\n * Adds annotation tag in MathML element.\n * @param {String} mathml - valid MathML.\n * @param {String} content - value to put inside annotation tag.\n * @param {String} annotationEncoding - annotation encoding.\n * @returns {String} - 'mathml' with an annotation that contains\n * 'content' and encoding 'encoding'.\n */\n static addAnnotation(mathml, content, annotationEncoding) {\n // If contains annotation, also contains semantics tag.\n const containsAnnotation = mathml.indexOf(\"\");\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\n } else if (MathML.isEmpty(mathml)) {\n const endIndexInline = mathml.indexOf(\"/>\");\n const endIndexNonInline = mathml.indexOf(\">\");\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\n } else {\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\n const endMathmlContent = mathml.lastIndexOf(\"\");\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\n }\n\n return mathmlWithAnnotation;\n }\n\n /**\n * Removes specific annotation tag in MathML element.\n * In case of remove the unique annotation, also is removed semantics tag.\n * @param {String} mathml - valid MathML.\n * @param {String} annotationEncoding - annotation encoding to remove.\n * @returns {String} - 'mathml' without the annotation encoding specified.\n */\n static removeAnnotation(mathml, annotationEncoding) {\n let mathmlWithoutAnnotation = mathml;\n const openAnnotationTag = ``;\n const closeAnnotationTag = \"\";\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\n if (startAnnotationIndex !== -1) {\n let differentAnnotationFound = false;\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\n\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\n }\n\n /**\n * Removes semantics tag to element that contains mathml.\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\n * @param {string} element - Inner HTML text string.\n * @returns {string} - 'mathml' without semantics tag.\n */\n static removeSafeXMLSemantics(element) {\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\n\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\n }\n\n /**\n * Transforms all xml mathml occurrences that contain semantics to the same\n * xml mathml occurrences without semantics.\n * @param {string} text - string that can contain xml mathml occurrences.\n * @param {Constants} [characters] - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * xmlCharacters by default.\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\n */\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\n const mathTagStart = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const mathTagEndline = `/${characters.tagCloser}`;\n const { tagCloser } = characters;\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\n\n let output = \"\";\n let start = text.indexOf(mathTagStart);\n let end = 0;\n while (start !== -1) {\n output += text.substring(end, start);\n\n // MathML can be written as '' or ''.\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\n const firstTagCloser = text.indexOf(tagCloser, start);\n if (mathTagEndIndex !== -1) {\n end = mathTagEndIndex;\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\n end = mathTagEndlineIndex;\n }\n\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\n if (semanticsIndex !== -1) {\n const mmlTagStart = text.substring(start, semanticsIndex);\n const annotationIndex = text.indexOf(annotationTagStart, start);\n if (annotationIndex !== -1) {\n const startIndex = semanticsIndex + semanticsTagStart.length;\n const mmlContent = text.substring(startIndex, annotationIndex);\n output += mmlTagStart + mmlContent + mathTagEnd;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n end += mathTagEnd.length;\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n }\n\n output += text.substring(end, text.length);\n return output;\n }\n\n /**\n * Returns true if a MathML contains a certain class.\n * @param {string} mathML - input MathML.\n * @param {string} className - className.\n * @returns {boolean} true if the input MathML contains the input class.\n * false otherwise.\n * @static\n */\n static containClass(mathML, className) {\n const classIndex = mathML.indexOf(\"class\");\n if (classIndex === -1) {\n return false;\n }\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\n const classTag = mathML.substring(classIndex, classTagEndIndex);\n if (classTag.indexOf(className) !== -1) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns true if mathml is empty. Otherwise, false.\n * @param {string} mathml - valid MathML with standard XML tags.\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\n */\n static isEmpty(mathml) {\n // MathML can have the shape or ''.\n const closeTag = \">\";\n const closeTagInline = \"/>\";\n const firstCloseTagIndex = mathml.indexOf(closeTag);\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\n let empty = false;\n // MathML is always empty in the second shape.\n if (firstCloseTagInlineIndex !== -1) {\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\n empty = true;\n }\n }\n\n // MathML is always empty in the first shape when there aren't elements\n // between math tags.\n if (!empty) {\n const mathTagEndRegex = new RegExp(\"\");\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\n if (mathTagEndArray) {\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\n }\n }\n\n return empty;\n }\n\n /**\n * Encodes html entities inside properties.\n * @param {String} mathml - valid MathML with standard XML tags.\n * @returns {String} - 'mathml' with property entities encoded.\n */\n static encodeProperties(mathml) {\n // Search all the properties.\n const regex = /\\w+=\".*?\"/g;\n // Encode html entities.\n const replacer = (match) => {\n // It has the shape:\n // .\n const quoteIndex = match.indexOf('\"');\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\n return matchEncoded;\n };\n\n const mathmlEncoded = mathml.replace(regex, replacer);\n return mathmlEncoded;\n }\n}\n","/**\n * This class represents the configuration class.\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\n */\nexport default class Configuration {\n /**\n * Adds a properties object to {@link Configuration.properties}.\n * @param {Object} properties - properties to append to current properties.\n */\n static addConfiguration(properties) {\n Object.assign(Configuration.properties, properties);\n }\n\n /**\n * Static property.\n * The configuration properties object.\n * @private\n * @type {Object}\n */\n static get properties() {\n return Configuration._properties;\n }\n\n /**\n * Static property setter.\n * Set configuration properties.\n * @param {Object} value - The property value.\n * @ignore\n */\n static set properties(value) {\n Configuration._properties = value;\n }\n\n /**\n * Returns the value of a property key.\n * @param {String} key - Property key\n * @returns {String} Property value\n */\n static get(key) {\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\n // Backwards compatibility.\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\n return Configuration.properties[`_wrs_conf_${key}`];\n }\n return false;\n }\n return Configuration.properties[key];\n }\n\n /**\n * Adds a new property to Configuration class.\n * @param {String} key - Property key.\n * @param {Object} value - Property value.\n */\n static set(key, value) {\n Configuration.properties[key] = value;\n }\n\n /**\n * Updates a property object value with new values.\n * @param {String} key - The property key to be updated.\n * @param {Object} propertyValue - Object containing the new values.\n */\n static update(key, propertyValue) {\n if (!Configuration.get(key)) {\n Configuration.set(key, propertyValue);\n } else {\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\n Configuration.set(key, updateProperty);\n }\n }\n}\n\n/**\n * Static properties object. Stores all configuration properties.\n * Needed to attribute accessors.\n * @private\n * @type {Object}\n */\nConfiguration._properties = {};\n","export default class TextCache {\n /**\n * @classdesc\n * This class represent a client-side text cache class. Contains pairs of\n * strings (key/value) which can be retrieved in any moment. Usually used\n * to store AJAX responses for text services like mathml2latex\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\n * @constructs\n */\n constructor() {\n /**\n * Cache array property storing the cache entries.\n * @type {Array.}\n */\n this.cache = [];\n }\n\n /**\n * This method populates a key/value pair into the {@link this.cache} property.\n * @param {String} key - Cache key, usually the service string parameter.\n * @param {String} value - Cache value, usually the service response.\n */\n populate(key, value) {\n this.cache[key] = value;\n }\n\n /**\n * Returns the cache value associated to certain cache key.\n * @param {String} key - Cache key, usually the service string parameter.\n * @return {String} value - Cache value, if exists. False otherwise.\n */\n get(key) {\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\n return this.cache[key];\n }\n return false;\n }\n}\n","/**\n * This object represents a custom listener.\n * @typedef {Object} Listener\n * @property {String} Listener.eventName - The listener name.\n * @property {Function} Listener.callback - The listener callback function.\n */\n\nexport default class Listeners {\n /**\n * @classdesc\n * This class represents a custom listeners manager.\n * @constructs\n */\n constructor() {\n /**\n * Array containing all custom listeners.\n * @type {Object[]}\n */\n this.listeners = [];\n }\n\n /**\n * Add a listener to Listener class.\n * @param {Object} listener - A listener object.\n */\n add(listener) {\n this.listeners.push(listener);\n }\n\n /**\n * Fires MathType event listeners\n * @param {String} eventName - event name\n * @param {Event} event - event object.\n * @return {boolean} false if event has been prevented. true otherwise.\n */\n fire(eventName, event) {\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\n if (this.listeners[i].eventName === eventName) {\n // Calling listener.\n this.listeners[i].callback(event);\n }\n }\n return event.defaultPrevented;\n }\n\n /**\n * Creates a new listener object.\n * @param {string} eventName - Event name.\n * @param {Object} callback - Callback function.\n * @returns {object} the listener object.\n */\n static newListener(eventName, callback) {\n const listener = {};\n listener.eventName = eventName;\n listener.callback = callback;\n return listener;\n }\n}\n","import Util from \"./util\";\nimport Listeners from \"./listeners\";\nimport Configuration from \"./configuration\";\n\n/**\n * @typedef {Object} ServiceProviderProperties\n * @property {String} URI - Service URI.\n * @property {String} server - Service server language.\n */\n\n/**\n * @classdesc\n * Class representing a serviceProvider. A serviceProvider is a class containing\n * an arbitrary number of services with the correspondent path.\n */\nexport default class ServiceProvider {\n /**\n * Returns Service Provider listeners.\n * @type {Listeners}\n */\n static get listeners() {\n return ServiceProvider._listeners;\n }\n\n /**\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\n * @param {Listener} listener - Instance of {@link Listener}.\n */\n static addListener(listener) {\n ServiceProvider.listeners.add(listener);\n }\n\n /**\n * Fires events in Service Provider.\n * @param {String} eventName - Event name.\n * @param {Event} event - Event object.\n */\n static fireEvent(eventName, event) {\n ServiceProvider.listeners.fire(eventName, event);\n }\n\n /**\n * Service parameters.\n * @type {ServiceProviderProperties}\n *\n */\n static get parameters() {\n return ServiceProvider._parameters;\n }\n\n /**\n * Service parameters.\n * @private\n * @type {ServiceProviderProperties}\n */\n static set parameters(parameters) {\n ServiceProvider._parameters = parameters;\n }\n\n /**\n * Static property.\n * Return service provider paths.\n * @private\n * @type {String}\n */\n static get servicePaths() {\n return ServiceProvider._servicePaths;\n }\n\n /**\n * Static property setter.\n * Set service paths.\n * @param {String} value - The property value.\n * @ignore\n */\n static set servicePaths(value) {\n ServiceProvider._servicePaths = value;\n }\n\n /**\n * Adds a new service to the ServiceProvider.\n * @param {String} service - Service name.\n * @param {String} path - Service path.\n * @static\n */\n static setServicePath(service, path) {\n ServiceProvider.servicePaths[service] = path;\n }\n\n /**\n * Returns the service path for a certain service.\n * @param {String} serviceName - Service name.\n * @returns {String} The service path.\n * @static\n */\n static getServicePath(serviceName) {\n return ServiceProvider.servicePaths[serviceName];\n }\n\n /**\n * Static property.\n * Service provider integration path.\n * @type {String}\n */\n static get integrationPath() {\n return ServiceProvider._integrationPath;\n }\n\n /**\n * Static property setter.\n * Set service provider integration path.\n * @param {String} value - The property value.\n * @ignore\n */\n static set integrationPath(value) {\n ServiceProvider._integrationPath = value;\n }\n\n /**\n * Returns the server URL in the form protocol://serverName:serverPort.\n * @return {String} The client side server path.\n */\n static getServerURL() {\n const url = window.location.href;\n const arr = url.split(\"/\");\n const result = `${arr[0]}//${arr[2]}`;\n return result;\n }\n\n /**\n * Inits {@link this} class. Uses {@link this.integrationPath} as\n * base path to generate all backend services paths.\n * @param {Object} parameters - Function parameters.\n * @param {String} parameters.integrationPath - Service path.\n */\n static init(parameters) {\n ServiceProvider.parameters = parameters;\n // Services path (tech dependant).\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\n\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\n // for example: /app/service. For them we calculate the absolute URL path, i.e\n // protocol://domain:port/app/service\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\n const serverPath = ServiceProvider.getServerURL();\n configurationURI = serverPath + configurationURI;\n showImageURI = serverPath + showImageURI;\n createImageURI = serverPath + createImageURI;\n getMathMLURI = serverPath + getMathMLURI;\n serviceURI = serverPath + serviceURI;\n }\n\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\n ServiceProvider.setServicePath(\"service\", serviceURI);\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n\n ServiceProvider.listeners.fire(\"onInit\", {});\n }\n\n /**\n * Gets the content from an URL.\n * @param {String} url - Target URL.\n * @param {Object} [postVariables] - Object containing post variables.\n * null if a GET query should be done.\n * @returns {String} Content of the target URL.\n * @private\n * @static\n */\n static getUrl(url, postVariables) {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n const httpRequest = Util.createHttpRequest();\n\n if (httpRequest) {\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\n httpRequest.open(\"GET\", url, false);\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\n httpRequest.open(\"POST\", url, false);\n } else {\n httpRequest.open(\"POST\", currentPath + url, false);\n }\n\n let header = Configuration.get(\"customHeaders\");\n if (header) {\n if (typeof header === \"string\") {\n header = Util.convertStringToObject(header);\n }\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\n }\n\n if (typeof postVariables !== \"undefined\" && postVariables) {\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n httpRequest.send(Util.httpBuildQuery(postVariables));\n } else {\n httpRequest.send(null);\n }\n\n return httpRequest.responseText;\n }\n return \"\";\n }\n\n /**\n * Returns the response text of a certain service.\n * @param {String} service - Service name.\n * @param {String} postVariables - Post variables.\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\n * @returns {String} Service response text.\n */\n static getService(service, postVariables, get) {\n let response;\n if (get === true) {\n const getVariables = postVariables ? `?${postVariables}` : \"\";\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\n response = ServiceProvider.getUrl(serviceUrl);\n } else {\n const serviceUrl = ServiceProvider.getServicePath(service);\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\n }\n return response;\n }\n\n /**\n * Returns the server language of a certain service. The possible values\n * are: php, aspx, java and ruby.\n * This method has backward compatibility purposes.\n * @param {String} service - The configuration service.\n * @returns {String} - The server technology associated with the configuration service.\n */\n static getServerLanguageFromService(service) {\n if (service.indexOf(\".php\") !== -1) {\n return \"php\";\n }\n if (service.indexOf(\".aspx\") !== -1) {\n return \"aspx\";\n }\n if (service.indexOf(\"wirispluginengine\") !== -1) {\n return \"ruby\";\n }\n return \"java\";\n }\n\n /**\n * Returns the URI associated with a certain service.\n * @param {String} service - The service name.\n * @return {String} The service path.\n */\n static createServiceURI(service) {\n const extension = ServiceProvider.serverExtension();\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\n }\n\n static serverExtension() {\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\n return \".php\";\n }\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\n return \".aspx\";\n }\n return \"\";\n }\n}\n\n/**\n * @property {String} service - The service name.\n * @property {String} path - The service path.\n * @static\n */\nServiceProvider._servicePaths = {};\n\n/**\n * The integration path. Contains the path of the configuration service.\n * Used to define the path for all services.\n * @type {String}\n * @private\n */\nServiceProvider._integrationPath = \"\";\n\n/**\n * ServiceProvider static listeners.\n * @type {Listeners}\n * @private\n */\nServiceProvider._listeners = new Listeners();\n\n/**\n * Service provider parameters.\n * @type {ServiceProviderParameters}\n */\nServiceProvider._parameters = {};\n","import TextCache from \"./textcache\";\nimport MathML from \"./mathml\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a LaTeX parser. Manages the services which allows to convert\n * LaTeX into MathML and MathML into LaTeX.\n */\nexport default class Latex {\n /**\n * Static property.\n * Return latex cache.\n * @private\n * @type {Cache}\n */\n static get cache() {\n return Latex._cache;\n }\n\n /**\n * Static property setter.\n * Set latex cache.\n * @param {Cache} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Latex._cache = value;\n }\n\n /**\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\n * we call a text service with the param mathml2latex.\n * @param {String} mathml - MathML String.\n * @return {String} LaTeX string generated by the MathML argument.\n */\n static getLatexFromMathML(mathml) {\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\n /**\n * @type {TextCache}\n */\n const { cache } = Latex;\n\n const data = {\n service: \"mathml2latex\",\n mml: mathmlWithoutSemantics,\n };\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n // TODO: Error handling.\n let latex = \"\";\n\n if (jsonResponse.status === \"ok\") {\n latex = jsonResponse.result.text;\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\n // Inserting LaTeX semantics.\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\n cache.populate(latex, mathmlWithSemantics);\n }\n\n return latex;\n }\n\n /**\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\n * we call a text service with the param latex2mathml.\n * @param {String} latex - String containing a LaTeX formula.\n * @param {Boolean} includeLatexOnSemantics\n * - If true LaTeX would me included into MathML semantics.\n * @return {String} MathML string generated by the LaTeX argument.\n */\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\n /**\n * @type {TextCache}\n */\n const latexCache = Latex.cache;\n\n if (Latex.cache.get(latex)) {\n return Latex.cache.get(latex);\n }\n const data = {\n service: \"latex2mathml\",\n latex,\n };\n\n if (includeLatexOnSemantics) {\n data.saveLatex = \"\";\n }\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n let output;\n if (jsonResponse.status === \"ok\") {\n let mathml = jsonResponse.result.text;\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\n\n // Populate LatexCache.\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\n const content = Util.htmlEntities(latex);\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\n output = mathml;\n } else {\n output = mathml;\n }\n if (!latexCache.get(latex)) {\n latexCache.populate(latex, mathml);\n }\n } else {\n output = `$$${latex}$$`;\n }\n return output;\n }\n\n /**\n * Converts all occurrences of MathML code to LaTeX.\n * The MathML code should containing to be converted.\n * @param {String} content - A string containing MathML valid code.\n * @param {Object} characters - An object containing special characters.\n * @return {String} A string containing all MathML annotated occurrences\n * replaced by the corresponding LaTeX code.\n */\n static parseMathmlToLatex(content, characters) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n let mathml;\n let startAnnotation;\n let closeAnnotation;\n\n while (start !== -1) {\n output += content.substring(end, start);\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else {\n end += mathTagEnd.length;\n }\n\n mathml = content.substring(start, end);\n\n startAnnotation = mathml.indexOf(openTarget);\n if (startAnnotation !== -1) {\n startAnnotation += openTarget.length;\n closeAnnotation = mathml.indexOf(closeTarget);\n let latex = mathml.substring(startAnnotation, closeAnnotation);\n if (characters === Constants.safeXmlCharacters) {\n latex = MathML.safeXmlDecode(latex);\n }\n output += `$$${latex}$$`;\n // Populate latex into cache.\n\n Latex.cache.populate(latex, mathml);\n } else {\n output += mathml;\n }\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n\n /**\n * Extracts the latex of a determined position in a text.\n * @param {Node} textNode - textNode to extract the LaTeX\n * @param {Number} caretPosition - Starting position to find LaTeX.\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\n * \"$$\" by default.\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\n * @static\n */\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\n // Tags used for LaTeX formulas.\n const defaultLatexTags = {\n open: \"$$\",\n close: \"$$\",\n };\n // latexTags is an optional parameter. If is not set, use default latexTags.\n if (typeof latexTags === \"undefined\" || latexTags == null) {\n latexTags = defaultLatexTags;\n }\n // Looking for the first textNode.\n let startNode = textNode;\n\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\n // TEXT_NODE.\n startNode = startNode.previousSibling;\n }\n\n /**\n * Returns the next latex position and node from a specific node and position.\n * @param {Node} currentNode - Node where searching latex.\n * @param {Number} currentPosition - Current position inside the currentNode.\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\n * \"$$\" by default.\n * @param {Boolean} tag - Tag containing the current search.\n * @returns {Object} Object containing the current node and the position.\n */\n function getNextLatexPosition(currentNode, currentPosition, tag) {\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\n\n while (position === -1) {\n currentNode = currentNode.nextSibling;\n\n if (!currentNode) {\n // TEXT_NODE.\n return null; // Not found.\n }\n\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\n }\n\n return {\n node: currentNode,\n position,\n };\n }\n\n /**\n * Determines if a node is previous, or not, to a second one.\n * @param {Node} node - Start node.\n * @param {Number} position - Start node position.\n * @param {Node} endNode - End node.\n * @param {Number} endPosition - End node position.\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\n */\n function isPrevious(node, position, endNode, endPosition) {\n if (node === endNode) {\n return position <= endPosition;\n }\n while (node && node !== endNode) {\n node = node.nextSibling;\n }\n\n return node === endNode;\n }\n\n let start;\n let end = {\n node: startNode,\n position: 0,\n };\n // Is supposed that open and close tags has the same length.\n const tagLength = latexTags.open.length;\n do {\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\n\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\n return null;\n }\n\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\n\n if (end == null) {\n return null;\n }\n\n end.position += tagLength;\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\n\n // Isolating latex.\n let latex;\n\n if (start.node === end.node) {\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\n } else {\n const index = start.position + tagLength;\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\n let currentNode = start.node;\n\n do {\n currentNode = currentNode.nextSibling;\n if (currentNode === end.node) {\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\n } else {\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\n }\n } while (currentNode !== end.node);\n }\n\n return {\n latex,\n startNode: start.node,\n startPosition: start.position,\n endNode: end.node,\n endPosition: end.position,\n };\n }\n}\n\n/**\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\n * @type {Cache}\n * @static\n */\nLatex._cache = new TextCache();\n","import translations from \"../lang/strings.json\";\n/**\n * This class represents a string manager. It's used to load localized strings.\n */\nexport default class StringManager {\n constructor() {\n throw new Error(\"Static class StringManager can not be instantiated.\");\n }\n\n /**\n * Returns the associated value of certain string key. If the associated value\n * doesn't exits returns the original key.\n * @param {string} key - string key\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\n * @returns {string} correspondent value. If doesn't exists original key.\n */\n static get(key, lang) {\n // Default language definition\n let { language } = this;\n\n // If parameter language, use it\n if (lang) {\n language = lang;\n }\n\n // Cut down on strings. e.g. en_US -> en\n if (language && language.length > 2) {\n language = language.slice(0, 2);\n }\n\n // Check if we support the language\n if (!this.strings.hasOwnProperty(language)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown language ${language} set in StringManager.`);\n language = \"en\";\n }\n\n // Check if the key is supported in the given language\n if (!this.strings[language].hasOwnProperty(key)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\n return key;\n }\n\n return this.strings[language][key];\n }\n}\n\n/**\n * Dictionary of dictionaries:\n * Key: language code\n * Value: Key: id of the string\n * Value: translation of the string\n */\nStringManager.strings = translations;\n\n/**\n * Language of the translations; English by default\n */\nStringManager.language = \"en\";\n","/* eslint-disable no-bitwise */\nimport DOMPurify from \"dompurify\";\nimport MathML from \"./mathml\";\nimport Configuration from \"./configuration\";\nimport Latex from \"./latex\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * This class represents an utility class.\n */\nexport default class Util {\n /**\n * Fires an event in a target.\n * @param {EventTarget} eventTarget - target where event should be fired.\n * @param {string} eventName event to fire.\n * @static\n */\n static fireEvent(eventTarget, eventName) {\n if (document.createEvent) {\n const eventObject = document.createEvent(\"HTMLEvents\");\n eventObject.initEvent(eventName, true, true);\n return !eventTarget.dispatchEvent(eventObject);\n }\n\n const eventObject = document.createEventObject();\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\n }\n\n /**\n * Cross-browser addEventListener/attachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - callback function.\n * @static\n */\n static addEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.addEventListener) {\n eventTarget.addEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.attachEvent) {\n // Backwards compatibility.\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * Cross-browser removeEventListener/detachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - function to remove from the event target.\n * @static\n */\n static removeEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.removeEventListener) {\n eventTarget.removeEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.detachEvent) {\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * A map from event target to event handlers so we can remove the event\n * listeners in removeElementEvents\n *\n * @type {Map}\n * @static\n */\n static elementEventsMap = new Map();\n\n /**\n * Adds the a callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\n * @param {Function} mousedownHandler - function to run when on mousedown event.\n * @param {Function} mouseupHandler - function to run when on mouseup event.\n * @static\n */\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\n // Make sure not to leak event listeners if we've already added events to\n // this element\n Util.removeElementEvents(eventTarget);\n\n let entry = {};\n Util.elementEventsMap.set(eventTarget, entry);\n\n if (doubleClickHandler) {\n entry.callbackDblclick = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n doubleClickHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n }\n\n if (mousedownHandler) {\n entry.callbackMousedown = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mousedownHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n }\n\n if (mouseupHandler) {\n entry.callbackMouseup = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mouseupHandler(element, realEvent);\n };\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\n // while the mouse is outside the editor text field.\n // This is a workaround: we trigger the event independently of where the mouse\n // is when we release its button.\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n }\n\n /**\n * Remove all callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @static\n */\n static removeElementEvents(eventTarget) {\n let entry = Util.elementEventsMap.get(eventTarget);\n if (!entry) {\n return;\n }\n\n Util.elementEventsMap.delete(eventTarget);\n\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n\n /**\n * Adds a class name to a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static addClass(element, className) {\n if (!Util.containsClass(element, className)) {\n element.className += ` ${className}`;\n }\n }\n\n /**\n * Checks if a HTMLElement contains a certain class.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the className.\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\n * @static\n */\n static containsClass(element, className) {\n if (element == null || !(\"className\" in element)) {\n return false;\n }\n\n const currentClasses = element.className.split(\" \");\n\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\n if (currentClasses[i] === className) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Remove a certain class for a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static removeClass(element, className) {\n let newClassName = \"\";\n const classes = element.className.split(\" \");\n\n for (let i = 0; i < classes.length; i += 1) {\n if (classes[i] !== className) {\n newClassName += `${classes[i]} `;\n }\n }\n element.className = newClassName.trim();\n }\n\n /**\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\n * used to parse old applets.\n * @param {string} text - string containing safeXml characters\n * @returns {string} a string with safeXml characters parsed.\n * @static\n */\n static convertOldXmlinitialtextAttribute(text) {\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\n // This could be removed in future.\n const val = \"value=\";\n\n const xitpos = text.indexOf(\"xmlinitialtext\");\n const valpos = text.indexOf(val, xitpos);\n const quote = text.charAt(valpos + val.length);\n const startquote = valpos + val.length + 1;\n const endquote = text.indexOf(quote, startquote);\n\n const value = text.substring(startquote, endquote);\n\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\n newvalue = newvalue.split(\"&\").join(\"ยง\");\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\n\n text = text.split(value).join(newvalue);\n return text;\n }\n\n /**\n * Convert a string representation of key-value pairs to an object.\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\n * @returns {Object} - Object containing the key-value pairs\n */\n static convertStringToObject(keyValueString) {\n if (!keyValueString || typeof keyValueString !== \"string\") {\n return {};\n }\n\n return keyValueString\n .split(\",\")\n .map((pair) => pair.trim().split(\"=\"))\n .reduce((resultObject, [key, value]) => {\n if (key && value) {\n resultObject[key] = value;\n }\n return resultObject;\n }, {});\n }\n\n /**\n * Cross-browser solution for creating new elements.\n * @param {string} tagName - tag name of the wished element.\n * @param {Object} attributes - an object where each key is a wished\n * attribute name and each value is its value.\n * @param {Object} [creator] - if supplied, this function will use\n * the \"createElement\" method from this param. Otherwise\n * document will be used as creator.\n * @returns {Element} The DOM element with the specified attributes assigned.\n * @static\n */\n static createElement(tagName, attributes, creator) {\n if (attributes === undefined) {\n attributes = {};\n }\n\n if (creator === undefined) {\n creator = document;\n }\n\n let element;\n\n /*\n * Internet Explorer fix:\n * If you create a new object dynamically, you can't set a non-standard attribute.\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\n * Other browsers will throw an exception and will run the standard code.\n */\n try {\n let html = `<${tagName}`;\n\n Object.keys(attributes).forEach((attributeName) => {\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\n });\n html += \">\";\n element = creator.createElement(html);\n } catch (e) {\n element = creator.createElement(tagName);\n Object.keys(attributes).forEach((attributeName) => {\n element.setAttribute(attributeName, attributes[attributeName]);\n });\n }\n return element;\n }\n\n /**\n * Creates new HTML from it's HTML code as string.\n * @param {string} objectCode - html code\n * @returns {Element} the HTML element.\n * @static\n */\n static createObject(objectCode, creator) {\n if (creator === undefined) {\n creator = document;\n }\n\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\n objectCode = objectCode\n .split(\"\").join(\"\").split(\"\").join(\"\");\n\n objectCode = objectCode\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\n\n const container = Util.createElement(\"div\", {}, creator);\n container.innerHTML = objectCode;\n\n function recursiveParamsFix(object) {\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const param = Util.createElement(\"param\", attributesParsed, creator);\n\n // IE fix.\n if (param.NAME) {\n param.name = param.NAME;\n param.value = param.VALUE;\n }\n\n param.removeAttribute(\"wirisObject\");\n object.parentNode.replaceChild(param, object);\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\n applet.removeAttribute(\"wirisObject\");\n\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\n applet.appendChild(object.childNodes[i]);\n i -= 1; // When we insert the object child into the applet, object loses one child.\n }\n }\n\n object.parentNode.replaceChild(applet, object);\n } else {\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n }\n }\n }\n\n recursiveParamsFix(container);\n return container.firstChild;\n }\n\n /**\n * Converts an Element to its HTML code.\n * @param {Element} element - entry element.\n * @return {string} the HTML code of the input element.\n * @static\n */\n static createObjectCode(element) {\n // In case that the image was not created, the object can be null or undefined.\n if (typeof element === \"undefined\" || element === null) {\n return null;\n }\n\n if (element.nodeType === 1) {\n // ELEMENT_NODE.\n let output = `<${element.tagName}`;\n\n for (let i = 0; i < element.attributes.length; i += 1) {\n if (element.attributes[i].specified) {\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\n }\n }\n\n if (element.childNodes.length > 0) {\n output += \">\";\n\n for (let i = 0; i < element.childNodes.length; i += 1) {\n output += Util.createObject(element.childNodes[i]);\n }\n\n output += ``;\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\n output += `>`;\n } else {\n output += \"/>\";\n }\n\n return output;\n }\n\n if (element.nodeType === 3) {\n // TEXT_NODE.\n return Util.htmlEntities(element.nodeValue);\n }\n\n return \"\";\n }\n\n /**\n * Concatenates two URL paths.\n * @param {string} path1 - first URL path\n * @param {string} path2 - second URL path\n * @returns {string} new URL.\n */\n static concatenateUrl(path1, path2) {\n let separator = \"\";\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\n separator = \"/\";\n }\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\n }\n\n /**\n * Parses a text and replaces all HTML special characters by their correspondent entities.\n * @param {string} input - text to be parsed.\n * @returns {string} the input text with all their special characters replaced by their entities.\n * @static\n */\n static htmlEntities(input) {\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\n }\n\n /**\n * Sanitize HTML to prevent XSS injections.\n * @param {string} html - html to be sanitize.\n * @returns {string} html sanitized.\n * @static\n */\n static htmlSanitize(html) {\n const annotationRegex = /\\/;\n // Get all the annotation content including the tags.\n const annotation = html.match(annotationRegex);\n // Sanitize html code without removing our supported MathML tags and attributes.\n html = DOMPurify.sanitize(html, {\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\n });\n // Readd old annotation content.\n return html.replace(annotationRegex, annotation);\n }\n\n /**\n * Parses a text and replaces all the HTML entities by their characters.\n * @param {string} input - text to be parsed\n * @returns {string} the input text with all their entities replaced by characters.\n * @static\n */\n static htmlEntitiesDecode(input) {\n // Textarea element decodes when inner html is set.\n const textarea = document.createElement(\"textarea\");\n textarea.innerHTML = input;\n return textarea.value;\n }\n\n /**\n * Returns a cross-browser http request.\n * @return {object} httpRequest request object.\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\n */\n static createHttpRequest() {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n if (currentPath.substr(0, 7) === \"file://\") {\n throw StringManager.get(\"exception_cross_site\");\n }\n\n if (typeof XMLHttpRequest !== \"undefined\") {\n return new XMLHttpRequest();\n }\n\n try {\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\n } catch (e) {\n try {\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\n } catch (oc) {\n return null;\n }\n }\n }\n\n /**\n * Converts a hash to a HTTP query.\n * @param {Object[]} properties - a key/value object.\n * @returns {string} a HTTP query containing all the key value pairs with\n * all the special characters replaced by their entities.\n * @static\n */\n static httpBuildQuery(properties) {\n let result = \"\";\n\n Object.keys(properties).forEach((i) => {\n if (properties[i] != null) {\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\n }\n });\n\n // Deleting last '&' empty character.\n if (result.substring(result.length - 1) === \"&\") {\n result = result.substring(0, result.length - 1);\n }\n\n return result;\n }\n\n /**\n * Convert a hash to string sorting keys to get a deterministic output\n * @param {Object[]} hash - a key/value object.\n * @returns {string} a string with the form key1=value1...keyn=valuen\n * @static\n */\n static propertiesToString(hash) {\n // 1. Sort keys. We sort the keys because we want a deterministic output.\n const keys = [];\n Object.keys(hash).forEach((key) => {\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\n keys.push(key);\n }\n });\n\n const n = keys.length;\n for (let i = 0; i < n; i += 1) {\n for (let j = i + 1; j < n; j += 1) {\n const s1 = keys[i];\n const s2 = keys[j];\n if (Util.compareStrings(s1, s2) > 0) {\n // Swap.\n keys[i] = s2;\n keys[j] = s1;\n }\n }\n }\n\n // 2. Generate output.\n let output = \"\";\n for (let i = 0; i < n; i += 1) {\n const key = keys[i];\n output += key;\n output += \"=\";\n let value = hash[key];\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\n value = value.replace(\"\\n\", \"\\\\n\");\n value = value.replace(\"\\r\", \"\\\\r\");\n value = value.replace(\"\\t\", \"\\\\t\");\n\n output += value;\n output += \"\\n\";\n }\n return output;\n }\n\n /**\n * Compare two strings using charCodeAt method\n * @param {string} a - first string to compare.\n * @param {string} b - second string to compare.\n * @returns {number} the difference between a and b\n * @static\n */\n static compareStrings(a, b) {\n let i;\n const an = a.length;\n const bn = b.length;\n const n = an > bn ? bn : an;\n for (i = 0; i < n; i += 1) {\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\n if (c !== 0) {\n return c;\n }\n }\n return a.length - b.length;\n }\n\n /**\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\n * @param {string} string - input string\n * @param {number} idx - an integer greater than or equal to 0\n * and less than the length of the string\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\n * @static\n */\n static fixedCharCodeAt(string, idx) {\n idx = idx || 0;\n const code = string.charCodeAt(idx);\n let hi;\n let low;\n\n /* High surrogate (could change last hex to 0xDB7F to treat high\n private surrogates as single characters) */\n\n if (code >= 0xd800 && code <= 0xdbff) {\n hi = code;\n low = string.charCodeAt(idx + 1);\n if (Number.isNaN(low)) {\n throw StringManager.get(\"exception_high_surrogate\");\n }\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\n }\n\n if (code >= 0xdc00 && code <= 0xdfff) {\n // Low surrogate.\n /* We return false to allow loops to skip this iteration since should have\n already handled high surrogate above in the previous iteration. */\n return false;\n }\n return code;\n }\n\n /**\n * Returns an URL with it's query params converted into array.\n * @param {string} url - input URL.\n * @returns {Object[]} an array containing all URL query params.\n * @static\n */\n static urlToAssArray(url) {\n let i;\n i = url.indexOf(\"?\");\n if (i > 0) {\n const query = url.substring(i + 1);\n const ss = query.split(\"&\");\n const h = {};\n for (i = 0; i < ss.length; i += 1) {\n const s = ss[i];\n const kv = s.split(\"=\");\n if (kv.length > 1) {\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\n }\n }\n return h;\n }\n return {};\n }\n\n /**\n * Returns an encoded URL by replacing each instance of certain characters by\n * one, two, three or four escape sequences using encodeURIComponent method.\n * !'()* . will not be encoded.\n *\n * @param {string} clearString - URL string to be encoded\n * @returns {string} URL with it's special characters replaced.\n * @static\n */\n static urlEncode(clearString) {\n let output = \"\";\n // Method encodeURIComponent doesn't encode !'()*~ .\n output = encodeURIComponent(clearString);\n return output;\n }\n\n // TODO: To parser?\n /**\n * Converts the HTML of a image into the output code that WIRIS must return.\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\n * or the Wiriscas attribute of a WIRIS applet.\n * @param {string} imgCode - the html code from a formula or a CAS image.\n * @param {boolean} convertToXml - true if the image should be converted to XML.\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\n * @returns {string} the XML or safeXML of a WIRIS image.\n * @static\n */\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\n const imgObject = Util.createObject(imgCode);\n if (imgObject) {\n if (\n imgObject.className === Configuration.get(\"imageClassName\") ||\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\n ) {\n if (!convertToXml) {\n return imgCode;\n }\n\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n // To handle annotations, first we need the MathML in XML.\n let mathML = MathML.safeXmlDecode(dataMathML);\n\n if (!Configuration.get(\"saveHandTraces\")) {\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\n }\n\n if (mathML == null) {\n mathML = imgObject.getAttribute(\"alt\");\n }\n\n if (convertToSafeXml) {\n const safeMathML = MathML.safeXmlEncode(mathML);\n return safeMathML;\n }\n\n return mathML;\n }\n }\n return imgCode;\n }\n\n /**\n * Gets the node length in characters.\n * @param {Node} node - HTML node.\n * @returns {number} node length.\n * @static\n */\n static getNodeLength(node) {\n const staticNodeLengths = {\n IMG: 1,\n BR: 1,\n };\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return node.nodeValue.length;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\n\n if (length === undefined) {\n length = 0;\n }\n\n for (let i = 0; i < node.childNodes.length; i += 1) {\n length += Util.getNodeLength(node.childNodes[i]);\n }\n return length;\n }\n return 0;\n }\n\n /**\n * Gets a selected node or text from an editable HTMLElement.\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\n * @param {HTMLElement} target - the editable HTMLElement.\n * @param {boolean} isIframe - specifies if the target is an iframe or not\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\n * the current selection and uses window.getSelection()\n * @returns {object} an object with the 'node' key set if the item is an\n * element or the keys 'node' and 'caretPosition' if the element is text.\n * @static\n */\n static getSelectedItem(target, isIframe, forceGetSelection) {\n let windowTarget;\n\n if (isIframe) {\n windowTarget = target.contentWindow;\n windowTarget.focus();\n } else {\n windowTarget = window;\n target.focus();\n }\n\n if (document.selection && !forceGetSelection) {\n const range = windowTarget.document.selection.createRange();\n\n if (range.parentElement) {\n if (range.htmlText.length > 0) {\n if (range.text.length === 0) {\n return Util.getSelectedItem(target, isIframe, true);\n }\n\n return null;\n }\n\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\n let temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\n // IE9 fix: parentElement() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML('');\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\n }\n\n let node;\n let caretPosition;\n\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\n // TEXT_NODE.\n node = temporalObject.nextSibling;\n caretPosition = 0;\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\n node = temporalObject.previousSibling;\n caretPosition = node.nodeValue.length;\n } else {\n node = windowTarget.document.createTextNode(\"\");\n temporalObject.parentNode.insertBefore(node, temporalObject);\n caretPosition = 0;\n }\n\n temporalObject.parentNode.removeChild(temporalObject);\n\n return {\n node,\n caretPosition,\n };\n }\n\n if (range.length > 1) {\n return null;\n }\n\n return {\n node: range.item(0),\n };\n }\n\n if (windowTarget.getSelection) {\n let range;\n const selection = windowTarget.getSelection();\n\n try {\n range = selection.getRangeAt(0);\n } catch (e) {\n range = windowTarget.document.createRange();\n }\n\n const node = range.startContainer;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return {\n node,\n caretPosition: range.startOffset,\n };\n }\n\n if (node !== range.endContainer) {\n return null;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n const position = range.startOffset;\n\n if (node.childNodes[position]) {\n // In case that a formula is detected but not selected,\n // we create an empty span where we could insert the new formula.\n if (range.startOffset === range.endOffset) {\n if (\n position !== 0 &&\n node.childNodes[position - 1].localName === \"span\" &&\n node.childNodes[position].classList?.contains(\"Wirisformula\")\n ) {\n node.childNodes[position - 1].remove();\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\n }\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\n if (\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\n position === 0\n ) {\n const emptySpan = document.createElement(\"span\");\n node.insertBefore(emptySpan, node.childNodes[position]);\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n\n return null;\n }\n\n /**\n * Returns null if there isn't any item or if it is malformed.\n * Otherwise returns an object containing the node with the MathML image\n * and the cursor position inside the textarea.\n * @param {HTMLTextAreaElement} textarea - textarea element.\n * @returns {Object} An object containing the node, the index of the\n * beginning of the selected text, caret position and the start and end position of the\n * text node.\n * @static\n */\n static getSelectedItemOnTextarea(textarea) {\n const textNode = document.createTextNode(textarea.value);\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\n if (textNodeValues === null) {\n return null;\n }\n\n return {\n node: textNode,\n caretPosition: textarea.selectionStart,\n startPosition: textNodeValues.startPosition,\n endPosition: textNodeValues.endPosition,\n };\n }\n\n /**\n * Looks for elements that match the given name in a HTML code string.\n * Important: this function is very concrete for WIRIS code.\n * It takes as preconditions lots of behaviors that are not the general case.\n * @param {string} code - HTML code.\n * @param {string} name - element name.\n * @param {boolean} autoClosed - true if the elements are autoClosed.\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\n * @static\n */\n static getElementsByNameFromString(code, name, autoClosed) {\n const elements = [];\n code = code.toLowerCase();\n name = name.toLowerCase();\n let start = code.indexOf(`<${name} `);\n\n while (start !== -1) {\n // Look for nodes.\n let endString;\n\n if (autoClosed) {\n endString = \">\";\n } else {\n endString = ``;\n }\n\n let end = code.indexOf(endString, start);\n\n if (end !== -1) {\n end += endString.length;\n elements.push({\n start,\n end,\n });\n } else {\n end = start + 1;\n }\n\n start = code.indexOf(`<${name} `, end);\n }\n\n return elements;\n }\n\n /**\n * Returns the numeric value of a base64 character.\n * @param {string} character - base64 character.\n * @returns {number} base64 character numeric value.\n * @static\n */\n static decode64(character) {\n const PLUS = \"+\".charCodeAt(0);\n const SLASH = \"/\".charCodeAt(0);\n const NUMBER = \"0\".charCodeAt(0);\n const LOWER = \"a\".charCodeAt(0);\n const UPPER = \"A\".charCodeAt(0);\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\n const code = character.charCodeAt(0);\n\n if (code === PLUS || code === PLUS_URL_SAFE) {\n return 62; // Char '+'.\n }\n if (code === SLASH || code === SLASH_URL_SAFE) {\n return 63; // Char '/'.\n }\n if (code < NUMBER) {\n return -1; // No match.\n }\n if (code < NUMBER + 10) {\n return code - NUMBER + 26 + 26;\n }\n if (code < UPPER + 26) {\n return code - UPPER;\n }\n if (code < LOWER + 26) {\n return code - LOWER + 26;\n }\n\n return null;\n }\n\n /**\n * Converts a base64 string to a array of bytes.\n * @param {string} b64String - base64 string.\n * @param {number} length - dimension of byte array (by default whole string).\n * @return {Object[]} the resultant byte array.\n * @static\n */\n static b64ToByteArray(b64String, length) {\n let tmp;\n\n if (b64String.length % 4 > 0) {\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\n }\n\n const arr = [];\n\n let l;\n let placeHolders;\n if (!length) {\n // All b64String string.\n if (b64String.charAt(b64String.length - 2) === \"=\") {\n placeHolders = 2;\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\n placeHolders = 1;\n } else {\n placeHolders = 0;\n }\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\n } else {\n l = length;\n }\n\n let i;\n for (i = 0; i < l; i += 4) {\n // Ignoring code checker standards (bitewise operators).\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 18) |\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\n Util.decode64(b64String.charAt(i + 3));\n\n arr.push((tmp >> 16) & 0xff);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n\n if (placeHolders) {\n if (placeHolders === 2) {\n // Ignoring code checker standards (bitewise operators).\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\n arr.push(tmp & 0xff);\n } else if (placeHolders === 1) {\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 10) |\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n }\n return arr;\n }\n\n /**\n * Returns the first 32-bit signed integer from a byte array.\n * @param {Object[]} bytes - array of bytes.\n * @returns {number} the 32-bit signed integer.\n * @static\n */\n static readInt32(bytes) {\n if (bytes.length < 4) {\n return false;\n }\n const int32 = bytes.splice(0, 4);\n // @codingStandardsIgnoreStartยก\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read the first byte from a byte array.\n * @param {Object} bytes - input byte array.\n * @returns {number} first byte of the byte array.\n * @static\n */\n static readByte(bytes) {\n // @codingStandardsIgnoreStart\n return bytes.shift() << 0;\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\n * @param {Object[]} bytes - byte array.\n * @param {number} pos - start position.\n * @param {number} len - number of bytes to read.\n * @returns {Object[]} the byte array.\n * @static\n */\n static readBytes(bytes, pos, len) {\n return bytes.splice(pos, len);\n }\n\n /**\n * Inserts or modifies formulas or CAS on a textarea.\n * @param {HTMLTextAreaElement} textarea - textarea target.\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\n * call this function as (textarea, Parser.createImageSrc(mathml));\n * @static\n */\n static updateTextArea(textarea, text) {\n if (textarea && text) {\n textarea.focus();\n\n if (textarea.selectionStart != null) {\n const { selectionEnd } = textarea;\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\n textarea.value = selectionStart + text + selectionEndSub;\n textarea.selectionEnd = selectionEnd + text.length;\n } else {\n const selection = document.selection.createRange();\n selection.text = text;\n }\n }\n }\n\n /**\n * Modifies existing formula on a textarea.\n * @param {HTMLTextAreaElement} textarea - text area target.\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\n * to the image,you can call this function as\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\n * @param {number} end - ending index from textarea where it needs to be replaced by text\n * @static\n */\n static updateExistingTextOnTextarea(textarea, text, start, end) {\n textarea.focus();\n const textareaStart = textarea.value.substring(0, start);\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\n textarea.selectionEnd = start + text.length;\n }\n\n /**\n * Add a parameter with it's correspondent value to an URL (GET).\n * @param {string} path - URL path\n * @param {string} parameter - parameter\n * @param {string} value - value\n * @static\n */\n static addArgument(path, parameter, value) {\n let sep;\n if (path.indexOf(\"?\") > 0) {\n sep = \"&\";\n } else {\n sep = \"?\";\n }\n return `${path + sep + parameter}=${value}`;\n }\n}\n","import Configuration from \"./configuration\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents MathType Image class. Contains all the logic related\n * to MathType images manipulation.\n * All MathType images are generated using the appropriate MathType\n * integration service: showimage or createimage.\n *\n * There are two available image formats:\n * - svg (default)\n * - png\n *\n * There are two formats for the image src attribute:\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\n * - A link to the showimage service.\n */\nexport default class Image {\n /**\n * Removes data attributes from an image.\n * @param {HTMLImageElement} img - Image where remove data attributes.\n */\n static removeImgDataAttributes(img) {\n const attributesToRemove = [];\n const { attributes } = img;\n\n Object.keys(attributes).forEach((key) => {\n const attribute = attributes[key];\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\n // Is preferred keep an array and remove after the search\n // because when attribute is removed the array of attributes\n // is modified.\n attributesToRemove.push(attribute.name);\n }\n });\n\n attributesToRemove.forEach((attribute) => {\n img.removeAttribute(attribute);\n });\n }\n\n /**\n * @static\n * Clones all MathType image attributes from a HTMLImageElement to another.\n * @param {HTMLImageElement} originImg - The original image.\n * @param {HTMLImageElement} destImg - The destination image.\n */\n static clone(originImg, destImg) {\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (!originImg.hasAttribute(customEditorAttributeName)) {\n destImg.removeAttribute(customEditorAttributeName);\n }\n\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\n const imgAttributes = [\n mathmlAttributeName,\n customEditorAttributeName,\n \"alt\",\n \"height\",\n \"width\",\n \"style\",\n \"src\",\n \"role\",\n ];\n\n imgAttributes.forEach((iterator) => {\n const originAttribute = originImg.getAttribute(iterator);\n if (originAttribute) {\n destImg.setAttribute(iterator, originAttribute);\n }\n });\n }\n\n /**\n * Determines whether an img src contains an SVG.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src contains an SVG, false otherwise\n */\n static isSvg(img) {\n return img.src.startsWith(\"data:image/svg+xml;\");\n }\n\n /**\n * Determines whether an img src is encoded in base64 or not.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src is encoded in base64, false otherwise\n */\n static isBase64(img) {\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\n }\n\n /**\n * Calculates the metrics of a MathType image given the the service response and the image format.\n * @param {HTMLImageElement} img - The HTMLImageElement.\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\n * @param {Boolean} jsonResponse - True the response of the image service is a\n * JSON object. False otherwise.\n */\n static setImgSize(img, uri, jsonResponse) {\n let ar;\n let base64String;\n let bytes;\n let svgString;\n if (jsonResponse) {\n // Cleaning data:image/png;base64.\n if (Image.isSvg(img)) {\n // SVG format.\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\n if (!Image.isBase64(img)) {\n ar = Image.getMetricsFromSvgString(uri);\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n svgString = \"\";\n bytes = Util.b64ToByteArray(base64String, base64String.length);\n for (let i = 0; i < bytes.length; i += 1) {\n svgString += String.fromCharCode(bytes[i]);\n }\n ar = Image.getMetricsFromSvgString(svgString);\n }\n // PNG format: we store all metrics information in the first 88 bytes.\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n bytes = Util.b64ToByteArray(base64String, 88);\n ar = Image.getMetricsFromBytes(bytes);\n }\n // Backwards compatibility: we store the metrics into createimage response.\n } else {\n ar = Util.urlToAssArray(uri);\n }\n let width = ar.cw;\n if (!width) {\n return;\n }\n let height = ar.ch;\n let baseline = ar.cb;\n const { dpi } = ar;\n if (dpi) {\n width = (width * 96) / dpi;\n height = (height * 96) / dpi;\n baseline = (baseline * 96) / dpi;\n }\n img.width = width;\n img.height = height;\n img.style.verticalAlign = `-${height - baseline}px`;\n }\n\n /**\n * Calculates the metrics of an image which has been resized. Is used to restore the original\n * metrics of a resized image.\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\n */\n static fixAfterResize(img) {\n img.removeAttribute(\"style\");\n img.removeAttribute(\"width\");\n img.removeAttribute(\"height\");\n // In order to avoid resize with max-width css property.\n img.style.maxWidth = \"none\";\n\n const processImg = (img) => {\n if (img.src.indexOf(\"data:image\") !== -1) {\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\n // (which would actually make more sense for readibility and efficiency).\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\n // 'data:image/svg+xml;base64,'.length === 26\n const base64String = img.getAttribute(\"src\").substring(26);\n const svgString = window.atob(base64String);\n const encodedSvgString = encodeURIComponent(svgString);\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n // Return src to base64!\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\n } else {\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n }\n } else {\n // 'data:image/png;base64,' === 22.\n const base64 = img.src.substring(22, img.src.length);\n Image.setImgSize(img, base64, true);\n }\n } else {\n Image.setImgSize(img, img.src);\n }\n };\n\n // If the image doesn't contain a blob, just process it normally\n if (img.src.indexOf(\"blob:\") === -1) {\n processImg(img);\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\n } else {\n const reader = new FileReader();\n reader.onload = function () {\n img.setAttribute(\"src\", reader.result);\n processImg(img);\n };\n fetch(img.src)\n .then((r) => r.blob())\n .then((blob) => {\n reader.readAsDataURL(blob);\n });\n }\n }\n\n /**\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\n * by the MathType image service. This image contains as an extra custom attribute:\n * the baseline (wrs:baseline).\n * @param {String} svgString - The SVG image.\n * @return {Array} - The image metrics.\n */\n static getMetricsFromSvgString(svgString) {\n let first = svgString.indexOf('height=\"');\n let last = svgString.indexOf('\"', first + 8, svgString.length);\n const height = svgString.substring(first + 8, last);\n\n first = svgString.indexOf('width=\"');\n last = svgString.indexOf('\"', first + 7, svgString.length);\n const width = svgString.substring(first + 7, last);\n\n first = svgString.indexOf('wrs:baseline=\"');\n last = svgString.indexOf('\"', first + 14, svgString.length);\n const baseline = svgString.substring(first + 14, last);\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n if (typeof baseline !== \"undefined\") {\n arr.cb = baseline;\n }\n return arr;\n }\n return [];\n }\n\n /**\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\n * @param {Array.} bytes - png byte array.\n * @return {Array} The png metrics.\n */\n static getMetricsFromBytes(bytes) {\n Util.readBytes(bytes, 0, 8);\n let width;\n let height;\n let typ;\n let baseline;\n let dpi;\n while (bytes.length >= 4) {\n typ = Util.readInt32(bytes);\n if (typ === 0x49484452) {\n width = Util.readInt32(bytes);\n height = Util.readInt32(bytes);\n // Read 5 bytes.\n Util.readInt32(bytes);\n Util.readByte(bytes);\n } else if (typ === 0x62615345) {\n // Baseline: 'baSE'.\n baseline = Util.readInt32(bytes);\n } else if (typ === 0x70485973) {\n // Dpis: 'pHYs'.\n dpi = Util.readInt32(bytes);\n dpi = Math.round(dpi / 39.37);\n Util.readInt32(bytes);\n Util.readByte(bytes);\n }\n Util.readInt32(bytes);\n }\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n arr.dpi = dpi;\n if (baseline) {\n arr.cb = baseline;\n }\n\n return arr;\n }\n return [];\n }\n}\n","import TextCache from \"./textcache\";\nimport ServiceProvider from \"./serviceprovider\";\nimport MathML from \"./mathml\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * @classdesc\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\n * the associated client-side cache.\n */\nexport default class Accessibility {\n /**\n * Static property.\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\n * @type {TextCache}\n */\n static get cache() {\n return Accessibility._cache;\n }\n\n /**\n * Static property setter.\n * Set accessibility cache.\n * @param {TextCahe} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Accessibility._cache = value;\n }\n\n /**\n * Converts MathML strings to its accessible text representation.\n * @param {String} mathML - MathML to be converted to accessible text.\n * @param {String} [language] - Language of the accessible text. 'en' by default.\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\n * @return {String} Accessibility text.\n */\n static mathMLToAccessible(mathML, language, data) {\n if (typeof language === \"undefined\") {\n language = \"en\";\n }\n // Check MathML class. If the class is chemistry,\n // we add chemistry to data to force accessibility service\n // to load chemistry grammar.\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\n data.mode = \"chemistry\";\n }\n // Ignore accesibility styles\n data.ignoreStyles = true;\n let accessibleText = \"\";\n\n if (Accessibility.cache.get(mathML)) {\n accessibleText = Accessibility.cache.get(mathML);\n } else {\n data.service = \"mathml2accessible\";\n data.lang = language;\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n if (accessibleJsonResponse.status !== \"error\") {\n accessibleText = accessibleJsonResponse.result.text;\n Accessibility.cache.populate(mathML, accessibleText);\n } else {\n accessibleText = StringManager.get(\"error_convert_accessibility\");\n }\n }\n\n return accessibleText;\n }\n}\n\n/**\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\n * @private\n * @type {TextCache}\n */\nAccessibility._cache = new TextCache();\n","import Util from \"./util\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport Image from \"./image\";\nimport Accessibility from \"./accessibility\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Configuration from \"./configuration\";\nimport Constants from \"./constants\";\n// eslint-disable-next-line no-unused-vars\nimport md5 from \"./md5\";\n\n/**\n * @classdesc\n * This class represent a MahML parser. Converts MathML into formulas depending on the\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\n * in the backend.\n */\nexport default class Parser {\n /**\n * Converts a MathML string to an img element.\n * @param {Document} creator - Document object to call createElement method.\n * @param {string} mathml - MathML code\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\n * @param {language} language - custom language for accessibility.\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\n * @static\n */\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\n const imgObject = creator.createElement(\"img\");\n imgObject.align = \"middle\";\n imgObject.style.maxWidth = \"none\";\n let data = wirisProperties || {};\n\n // Take into account the backend config\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\n data = { ...wirisEditorProperties, ...data };\n\n data.mml = mathml;\n data.lang = language;\n // Request metrics of the generated image.\n data.metrics = \"true\";\n data.centerbaseline = \"false\";\n\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n // Render js params: _wrs_int_wirisProperties contains some js render params.\n // Since MathML can support render params, js params should be send only to editor.\n\n imgObject.className = Configuration.get(\"imageClassName\");\n\n if (mathml.indexOf('class=\"') !== -1) {\n // We check here if the MathML has been created from a customEditor (such chemistry)\n // to add custom editor name attribute to img object (if necessary).\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\n }\n\n // Performance enabled.\n if (\n Configuration.get(\"wirisPluginPerformance\") &&\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\n ) {\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\n if (result.status === \"warning\") {\n // POST call.\n // if the mathml is malformed, this function will throw an exception.\n try {\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\n } catch (e) {\n return null;\n }\n }\n ({ result } = result);\n if (result.format === \"png\") {\n imgObject.src = `data:image/png;base64,${result.content}`;\n } else {\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\n }\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n Image.setImgSize(imgObject, result.content, true);\n\n if (Configuration.get(\"enableAccessibility\")) {\n if (typeof result.alt === \"undefined\") {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n } else {\n imgObject.alt = result.alt;\n }\n }\n } else {\n const result = Parser.createImageSrc(mathml, data);\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n imgObject.src = result;\n Image.setImgSize(\n imgObject,\n result,\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\n );\n if (Configuration.get(\"enableAccessibility\")) {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n }\n }\n\n if (typeof Parser.observer !== \"undefined\") {\n Parser.observer.observe(imgObject);\n }\n\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\n imgObject.setAttribute(\"role\", \"math\");\n return imgObject;\n }\n\n /**\n * Returns the source to showimage service by calling createimage service. The\n * output of the createimage service is a URL path pointing to showimage service.\n * This method is called when performance is disabled.\n * @param {string} mathml - MathML code.\n * @param {Object[]} data - data object containing service parameters.\n * @returns {string} the showimage path.\n */\n static createImageSrc(mathml, data) {\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n let result = ServiceProvider.getService(\"createimage\", data);\n\n if (result.indexOf(\"@BASE@\") !== -1) {\n // Replacing '@BASE@' with the base URL of createimage.\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\n baseParts.pop();\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\n }\n\n return result;\n }\n\n /**\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\n * this data would be converted as following:\n *

\n   * MathML code: Image containing the corresponding MathML formulas.\n   * MathML code with LaTeX annotation : LaTeX string.\n   * 
\n * @param {string} code - HTML code containing MathML data.\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\n */\n static initParse(code, language) {\n /* Note: The code inside this function has been inverted.\n If you invert again the code then you cannot use correctly LaTeX\n in Moodle.\n */\n code = Parser.initParseSaveMode(code, language);\n return Parser.initParseEditMode(code);\n }\n\n /**\n * Parses initial HTML code depending on the save mode. Transforms all MathML\n * occurrences for it's correspondent image or LaTeX.\n * @param {string} code - HTML code to be parsed\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code parsed.\n */\n static initParseSaveMode(code, language) {\n if (Configuration.get(\"saveMode\")) {\n // Converting XML to tags.\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\n code = Parser.codeImgTransform(code, \"base642showimage\");\n }\n }\n return code;\n }\n\n /**\n * Parses initial HTML code depending on the edit mode.\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\n * be converted into a LaTeX string instead of an image.\n * @param {string} code - HTML code containing MathML.\n * @returns {string} parsed HTML code.\n */\n static initParseEditMode(code) {\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\n const token = 'encoding=\"LaTeX\">';\n // While replacing images with latex, the indexes of the found images changes\n // respecting the original code, so this carry is needed.\n let carry = 0;\n\n for (let i = 0; i < imgList.length; i += 1) {\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\n\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\n\n if (mathmlStart === -1) {\n mathmlStartToken = ' alt=\"';\n mathmlStart = imgCode.indexOf(mathmlStartToken);\n }\n\n if (mathmlStart !== -1) {\n mathmlStart += mathmlStartToken.length;\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\n let latexStartPosition = mathml.indexOf(token);\n\n if (latexStartPosition !== -1) {\n latexStartPosition += token.length;\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\n\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\n const start = code.substring(0, imgList[i].start + carry);\n const end = code.substring(imgList[i].end + carry);\n code = start + replaceText + end;\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\n }\n }\n }\n }\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code. The end HTML code is HTML code with embedded images\n * or LaTeX formulas created with MathType.
\n * By default this method converts the formula images and LaTeX strings in MathML.
\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\n * @param {string} code - HTML to be parsed\n * @returns {string} the HTML code parsed.\n */\n static endParse(code) {\n // Transform LaTeX ocurrences to MathML elements.\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\n // Transform img elements to MathML elements.\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\n return codeEndParseSaveMode;\n }\n\n /**\n * Parses end HTML code depending on the edit mode.\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\n * @param {string} code - HTML code to be parsed.\n * @returns {string} HTML code parsed.\n */\n static endParseEditMode(code) {\n // Converting LaTeX to images.\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n let output = \"\";\n let endPosition = 0;\n let startPosition = code.indexOf(\"$$\");\n while (startPosition !== -1) {\n output += code.substring(endPosition, startPosition);\n endPosition = code.indexOf(\"$$\", startPosition + 2);\n\n if (endPosition !== -1) {\n // Before, it was a condition here to execute the next codelines\n // 'latex.indexOf('<') == -1'.\n // We don't know why it was used, but seems to have a conflict with\n // latex formulas that contains '<'.\n const latex = code.substring(startPosition + 2, endPosition);\n const decodedLatex = Util.htmlEntitiesDecode(latex);\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\n if (!Configuration.get(\"saveHandTraces\")) {\n // Remove hand traces.\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\n }\n output += mathml;\n endPosition += 2;\n } else {\n output += \"$$\";\n endPosition = startPosition + 2;\n }\n\n startPosition = code.indexOf(\"$$\", endPosition);\n }\n\n output += code.substring(endPosition, code.length);\n code = output;\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code depending on the save mode. Converts all\n * images into the element determined by the save mode:\n * - xml: Parses images formulas into MathML.\n * - safeXml: Parses images formulas into safeMAthML\n * - base64: Parses images into base64 images.\n * - image: Parse images into images (no parsing)\n * @param {string} code - HTML code to be parsed\n * @returns {string} HTML code parsed.\n */\n static endParseSaveMode(code) {\n const savemode = Configuration.get(\"saveMode\");\n const base64savemode = Configuration.get(\"base64savemode\");\n\n if (savemode) {\n if (savemode === \"safeXml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"xml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\n code = Parser.codeImgTransform(code, \"img264\");\n }\n }\n\n return code;\n }\n\n /**\n * Auxiliar function that builds the data object to send to the showimage endpoint\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object with the data to send to showimage.\n */\n static createShowImageSrcData(data, language) {\n const dataMd5 = {};\n const renderParams = [\n \"mml\",\n \"color\",\n \"centerbaseline\",\n \"zoom\",\n \"dpi\",\n \"fontSize\",\n \"fontFamily\",\n \"defaultStretchy\",\n \"backgroundColor\",\n \"format\",\n ];\n renderParams.forEach((param) => {\n if (typeof data[param] !== \"undefined\") {\n dataMd5[param] = data[param];\n }\n });\n // Data variables to get.\n const dataObject = {};\n Object.keys(data).forEach((key) => {\n // We don't need mathml in this request we try to get cached.\n // Only need the formula md5 calculated before.\n if (key !== \"mml\") {\n dataObject[key] = data[key];\n }\n });\n\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\n dataObject.version = Configuration.get(\"version\");\n\n return dataObject;\n }\n\n /**\n * Returns the result to call showimage service with the formula md5 as parameter.\n * The result could be:\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object containing showimage response.\n */\n static createShowImageSrc(data, language) {\n const dataObject = this.createShowImageSrcData(data, language);\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\n return result;\n }\n\n /**\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\n * or showimage img tags (i.e with showimage.php on src)\n * @param {string} code - HTML code\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\n * @returns {string} html - code transformed.\n */\n static codeImgTransform(code, mode) {\n let output = \"\";\n let endPosition = 0;\n const pattern = /\") {\n endPosition = i + 1;\n }\n\n i += 1;\n }\n\n if (endPosition < startPosition) {\n // The img tag is stripped.\n output += code.substring(startPosition, code.length);\n return output;\n }\n let imgCode = code.substring(startPosition, endPosition);\n const imgObject = Util.createObject(imgCode);\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n let convertToXml;\n let convertToSafeXml;\n\n if (mode === \"base642showimage\") {\n if (xmlCode == null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\n output += Util.createObjectCode(imgCode);\n } else if (mode === \"img2mathml\") {\n if (Configuration.get(\"saveMode\")) {\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\n convertToXml = true;\n convertToSafeXml = true;\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\n convertToXml = true;\n convertToSafeXml = false;\n }\n }\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\n } else if (mode === \"img264\") {\n if (xmlCode === null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n\n const properties = {};\n properties.base64 = \"true\";\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\n // Metrics.\n Image.setImgSize(imgCode, imgCode.src, true);\n output += Util.createObjectCode(imgCode);\n }\n }\n output += code.substring(endPosition, code.length);\n return output;\n }\n\n /**\n * Converts all occurrences of MathML to the corresponding image.\n * @param {string} content - string with valid MathML code.\n * The MathML code doesn't contain semantics.\n * @param {Constants} characters - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * @param {string} language - a valid language code\n * in order to generate formula accessibility.\n * @returns {string} The input string with all the MathML\n * occurrences replaced by the corresponding image.\n */\n static parseMathmlToImg(content, characters, language) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n\n while (start !== -1) {\n output += content.substring(end, start);\n // Avoid WIRIS images to be parsed.\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else if (imageMathmlAtrribute !== -1) {\n // First close tag of img attribute\n // If a mathmlAttribute exists should be inside a img tag.\n end += content.indexOf(\"/>\", start);\n } else {\n end += mathTagEnd.length;\n }\n\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\n let mathml = content.substring(start, end);\n mathml =\n characters.id === Constants.safeXmlCharacters.id\n ? MathML.safeXmlDecode(mathml)\n : MathML.mathMLEntities(mathml);\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\n } else {\n output += content.substring(start, end);\n }\n\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n}\n\n// Mutation observers to avoid wiris image formulas class be removed.\nif (typeof MutationObserver !== \"undefined\") {\n const mutationObserver = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\n mutation.attributeName === \"class\" &&\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\n ) {\n mutation.target.className = Configuration.get(\"imageClassName\");\n }\n });\n });\n\n Parser.observer = Object.create(mutationObserver);\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\n // We use own default config.\n Parser.observer.observe = function (target) {\n Object.getPrototypeOf(this).observe(target, this.Config);\n };\n}\n","/* eslint-disable class-methods-use-this */\n/* eslint-disable no-unused-vars */\n/* eslint-disable no-extra-semi */\n\n// The rules above are disabled because we are implementing\n// an external interface.\n\nexport default class EditorListener {\n /**\n * @classdesc\n * Determines if the content of the\n * MathType Editor has changes.\n * @implements {EditorListeners}\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the content of the editor has changed.\n * @type {Boolean}\n */\n this.isContentChanged = false;\n\n /**\n * Indicates if the listener should be waiting for changes in the editor.\n * @type {Boolean}\n */\n this.waitingForChanges = false;\n }\n\n /**\n * Sets {@link EditorListener.isContentChanged} property.\n * @param {Boolean} value - The new vlue.\n */\n setIsContentChanged(value) {\n this.isContentChanged = value;\n }\n\n /**\n * Returns true if the content of the editor has been changed, false otherwise.\n * @return {Boolean}\n */\n getIsContentChanged() {\n return this.isContentChanged;\n }\n\n /**\n * Determines if the EditorListener should wait for any changes.\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\n */\n setWaitingForChanges(value) {\n this.waitingForChanges = value;\n }\n\n /**\n * EditorListener method to overwrite.\n * @type {JsEditor}\n * @ignore\n */\n caretPositionChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @type {JsEditor}\n * @ignore\n */\n clipboardChanged(_editor) {}\n\n /**\n * Determines if the content of an editor has been changed.\n * @param {JsEditor} editor - editor object.\n */\n contentChanged(_editor) {\n if (this.waitingForChanges === true && this.isContentChanged === false) {\n this.isContentChanged = true;\n }\n }\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} editor - The editor instance.\n */\n styleChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} - The editor instance.\n */\n transformationReceived(_editor) {}\n}\n","let wasm;\n\nconst cachedTextDecoder =\n typeof TextDecoder !== \"undefined\"\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\n : {\n decode: () => {\n throw Error(\"TextDecoder not available\");\n },\n };\n\nif (typeof TextDecoder !== \"undefined\") {\n cachedTextDecoder.decode();\n}\n\nlet cachedUint8Memory0 = null;\n\nfunction getUint8Memory0() {\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachedUint8Memory0;\n}\n\nfunction getStringFromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\n}\n\nconst heap = new Array(128).fill(undefined);\n\nheap.push(undefined, null, true, false);\n\nlet heap_next = heap.length;\n\nfunction addHeapObject(obj) {\n if (heap_next === heap.length) heap.push(heap.length + 1);\n const idx = heap_next;\n heap_next = heap[idx];\n\n heap[idx] = obj;\n return idx;\n}\n\nfunction getObject(idx) {\n return heap[idx];\n}\n\nfunction dropObject(idx) {\n if (idx < 132) return;\n heap[idx] = heap_next;\n heap_next = idx;\n}\n\nfunction takeObject(idx) {\n const ret = getObject(idx);\n dropObject(idx);\n return ret;\n}\n\nlet WASM_VECTOR_LEN = 0;\n\nconst cachedTextEncoder =\n typeof TextEncoder !== \"undefined\"\n ? new TextEncoder(\"utf-8\")\n : {\n encode: () => {\n throw Error(\"TextEncoder not available\");\n },\n };\n\nconst encodeString =\n typeof cachedTextEncoder.encodeInto === \"function\"\n ? function (arg, view) {\n return cachedTextEncoder.encodeInto(arg, view);\n }\n : function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length,\n };\n };\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length, 1) >>> 0;\n getUint8Memory0()\n .subarray(ptr, ptr + buf.length)\n .set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len, 1) >>> 0;\n\n const mem = getUint8Memory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7f) break;\n mem[ptr + offset] = code;\n }\n\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\n const ret = encodeString(arg, view);\n\n offset += ret.written;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction isLikeNone(x) {\n return x === undefined || x === null;\n}\n\nlet cachedInt32Memory0 = null;\n\nfunction getInt32Memory0() {\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\n }\n return cachedInt32Memory0;\n}\n\nlet cachedFloat64Memory0 = null;\n\nfunction getFloat64Memory0() {\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\n }\n return cachedFloat64Memory0;\n}\n\nlet cachedBigInt64Memory0 = null;\n\nfunction getBigInt64Memory0() {\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\n }\n return cachedBigInt64Memory0;\n}\n\nfunction debugString(val) {\n // primitive types\n const type = typeof val;\n if (type == \"number\" || type == \"boolean\" || val == null) {\n return `${val}`;\n }\n if (type == \"string\") {\n return `\"${val}\"`;\n }\n if (type == \"symbol\") {\n const description = val.description;\n if (description == null) {\n return \"Symbol\";\n } else {\n return `Symbol(${description})`;\n }\n }\n if (type == \"function\") {\n const name = val.name;\n if (typeof name == \"string\" && name.length > 0) {\n return `Function(${name})`;\n } else {\n return \"Function\";\n }\n }\n // objects\n if (Array.isArray(val)) {\n const length = val.length;\n let debug = \"[\";\n if (length > 0) {\n debug += debugString(val[0]);\n }\n for (let i = 1; i < length; i++) {\n debug += \", \" + debugString(val[i]);\n }\n debug += \"]\";\n return debug;\n }\n // Test for built-in\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\n let className;\n if (builtInMatches.length > 1) {\n className = builtInMatches[1];\n } else {\n // Failed to match the standard '[object ClassName]'\n return toString.call(val);\n }\n if (className == \"Object\") {\n // we're a user defined class or Object\n // JSON.stringify avoids problems with cycles, and is generally much\n // easier than looping through ownProperties of `val`.\n try {\n return \"Object(\" + JSON.stringify(val) + \")\";\n } catch (_) {\n return \"Object\";\n }\n }\n // errors\n if (val instanceof Error) {\n return `${val.name}: ${val.message}\\n${val.stack}`;\n }\n // TODO we could test for more things here, like `Set`s and `Map`s.\n return className;\n}\n\nfunction makeClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n try {\n return f(state.a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\n state.a = 0;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction makeMutClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n const a = state.a;\n state.a = 0;\n try {\n return f(a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\n } else {\n state.a = a;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_49(arg0, arg1) {\n wasm.__wbindgen_export_4(arg0, arg1);\n}\n\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction handleError(f, args) {\n try {\n return f.apply(this, args);\n } catch (e) {\n wasm.__wbindgen_export_6(addHeapObject(e));\n }\n}\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\n}\n\n/**\n */\nexport function main_js() {\n wasm.main_js();\n}\n\nfunction getArrayU8FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\n}\n/**\n */\nexport const Level = Object.freeze({\n Err: 0,\n 0: \"Err\",\n Warn: 1,\n 1: \"Warn\",\n Info: 2,\n 2: \"Info\",\n Debug: 3,\n 3: \"Debug\",\n});\n/**\n */\nexport class Telemeter {\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n\n return ptr;\n }\n\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_telemeter_free(ptr);\n }\n /**\n * @param {any} solution\n * @param {any} hosts\n * @param {any} config\n */\n constructor(solution, hosts, config) {\n try {\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\n var r0 = getInt32Memory0()[retptr / 4 + 0];\n var r1 = getInt32Memory0()[retptr / 4 + 1];\n var r2 = getInt32Memory0()[retptr / 4 + 2];\n if (r2) {\n throw takeObject(r1);\n }\n this.__wbg_ptr = r0 >>> 0;\n return this;\n } finally {\n wasm.__wbindgen_add_to_stack_pointer(16);\n }\n }\n /**\n * @param {string} sender_id\n * @returns {Promise}\n */\n identify(sender_id) {\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\n return takeObject(ret);\n }\n /**\n * @param {string} event_type\n * @param {any} event_payload\n * @returns {Promise}\n */\n track(event_type, event_payload) {\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\n return takeObject(ret);\n }\n /**\n * @param {any} level\n * @param {string} message\n * @param {any} payload\n * @returns {Promise}\n */\n log(level, message, payload) {\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\n return takeObject(ret);\n }\n /**\n * @returns {Promise}\n */\n finish() {\n const ptr = this.__destroy_into_raw();\n const ret = wasm.telemeter_finish(ptr);\n return takeObject(ret);\n }\n /**\n * @param {boolean | undefined} [new_debug_status]\n */\n debug(new_debug_status) {\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\n }\n}\n\nasync function __wbg_load(module, imports) {\n if (typeof Response === \"function\" && module instanceof Response) {\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\n try {\n return await WebAssembly.instantiateStreaming(module, imports);\n } catch (e) {\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\n console.warn(\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\n e,\n );\n } else {\n throw e;\n }\n }\n }\n\n const bytes = await module.arrayBuffer();\n return await WebAssembly.instantiate(bytes, imports);\n } else {\n const instance = await WebAssembly.instantiate(module, imports);\n\n if (instance instanceof WebAssembly.Instance) {\n return { instance, module };\n } else {\n return instance;\n }\n }\n}\n\nfunction __wbg_get_imports() {\n const imports = {};\n imports.wbg = {};\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\n const ret = getStringFromWasm0(arg0, arg1);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\n const ret = new Object();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\n const ret = getObject(arg0).status;\n return ret;\n };\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\n const ret = getObject(arg0).headers;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\n const ret = new Date();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\n const ret = getObject(arg0).getTime();\n return ret;\n };\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\n takeObject(arg0);\n };\n imports.wbg.__wbindgen_is_object = function (arg0) {\n const val = getObject(arg0);\n const ret = typeof val === \"object\" && val !== null;\n return ret;\n };\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\n const ret = getObject(arg0).crypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\n const ret = getObject(arg0).process;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\n const ret = getObject(arg0).versions;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\n const ret = getObject(arg0).node;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_string = function (arg0) {\n const ret = typeof getObject(arg0) === \"string\";\n return ret;\n };\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\n const ret = getObject(arg0).msCrypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\n return handleError(function () {\n const ret = module.require;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\n const ret = new Uint8Array(arg0 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\n const ret = getObject(arg0)[arg1 >>> 0];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).next();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\n const ret = getObject(arg0).done;\n return ret;\n };\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\n const ret = getObject(arg0).value;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\n const ret = Symbol.iterator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\n const ret = getObject(arg0).next;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_function = function (arg0) {\n const ret = typeof getObject(arg0) === \"function\";\n return ret;\n };\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\n return handleError(function (arg0, arg1) {\n const ret = getObject(arg0).call(getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\n const ret = getObject(arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\n return handleError(function () {\n const ret = self.self;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\n return handleError(function () {\n const ret = window.window;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\n return handleError(function () {\n const ret = globalThis.globalThis;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\n return handleError(function () {\n const ret = global.global;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\n const ret = getObject(arg0) === undefined;\n return ret;\n };\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\n const ret = new Function(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\n const ret = Array.isArray(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\n const ret = Number.isSafeInteger(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\n try {\n var state0 = { a: arg0, b: arg1 };\n var cb0 = (arg0, arg1) => {\n const a = state0.a;\n state0.a = 0;\n try {\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\n } finally {\n state0.a = a;\n }\n };\n const ret = new Promise(cb0);\n return addHeapObject(ret);\n } finally {\n state0.a = state0.b = 0;\n }\n };\n imports.wbg.__wbindgen_memory = function () {\n const ret = wasm.memory;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\n const ret = getObject(arg0).buffer;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\n const ret = new Uint8Array(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\n };\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"string\" ? obj : undefined;\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\n return handleError(function (arg0) {\n const ret = JSON.stringify(getObject(arg0));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\n const ret = getObject(arg0).fetch(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\n const ret = fetch(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\n return handleError(function () {\n const ret = new AbortController();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\n const ret = getObject(arg0).signal;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\n getObject(arg0).abort();\n };\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\n const ret = new Error(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\n const ret = getObject(arg0) == getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\n const v = getObject(arg0);\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\n return ret;\n };\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"number\" ? obj : undefined;\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Uint8Array;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof ArrayBuffer;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\n const ret = Object.entries(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\n const ret = String(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\n console.warn(getObject(arg0), getObject(arg1));\n };\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\n const ret = getObject(arg0).readyState;\n return ret;\n };\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\n console.warn(getObject(arg0));\n };\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\n return handleError(function (arg0) {\n getObject(arg0).close();\n }, arguments);\n };\n imports.wbg.__wbg_new_b9b318679315404f = function () {\n return handleError(function (arg0, arg1) {\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\n getObject(arg0).binaryType = takeObject(arg1);\n };\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\n console.log(getObject(arg0));\n };\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\n console.error(getObject(arg0));\n };\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\n console.info(getObject(arg0));\n };\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\n const ret = getObject(arg0).document;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n };\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\n const ret = getObject(arg0).visibilityState;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\n const ret = getObject(arg0)[getObject(arg1)];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\n const ret = typeof getObject(arg0) === \"bigint\";\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\n const ret = arg0;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\n const ret = getObject(arg0) in getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\n const ret = BigInt.asUintN(64, arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\n const ret = getObject(arg0) === getObject(arg1);\n return ret;\n };\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).localStorage;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n }, arguments);\n };\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\n const ret = getObject(arg0).navigator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).mediaDevices;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).enumerateDevices();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\n const obj = takeObject(arg0).original;\n if (obj.cnt-- == 1) {\n obj.a = 0;\n return true;\n }\n const ret = false;\n return ret;\n };\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\n const ret = getObject(arg1).deviceId;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Response;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).randomFillSync(takeObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).getRandomValues(getObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\n const v = getObject(arg1);\n const ret = typeof v === \"bigint\" ? v : undefined;\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\n const ret = debugString(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\n throw new Error(getStringFromWasm0(arg0, arg1));\n };\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\n const ret = getObject(arg0).then(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\n queueMicrotask(getObject(arg0));\n };\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\n const ret = getObject(arg0).queueMicrotask;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\n const ret = Promise.resolve(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\n const ret = getObject(arg1).url;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_send_2860805104507701 = function () {\n return handleError(function (arg0, arg1, arg2) {\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\n }, arguments);\n };\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Window;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\n return handleError(function () {\n const ret = new Headers();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\n return addHeapObject(ret);\n };\n\n return imports;\n}\n\nfunction __wbg_init_memory(imports, maybe_memory) {}\n\nfunction __wbg_finalize_init(instance, module) {\n wasm = instance.exports;\n __wbg_init.__wbindgen_wasm_module = module;\n cachedBigInt64Memory0 = null;\n cachedFloat64Memory0 = null;\n cachedInt32Memory0 = null;\n cachedUint8Memory0 = null;\n\n wasm.__wbindgen_start();\n return wasm;\n}\n\nfunction initSync(module) {\n if (wasm !== undefined) return wasm;\n\n const imports = __wbg_get_imports();\n\n __wbg_init_memory(imports);\n\n if (!(module instanceof WebAssembly.Module)) {\n module = new WebAssembly.Module(module);\n }\n\n const instance = new WebAssembly.Instance(module, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nasync function __wbg_init(input) {\n if (wasm !== undefined) return wasm;\n\n if (typeof input === \"undefined\") {\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\n }\n const imports = __wbg_get_imports();\n\n if (\n typeof input === \"string\" ||\n (typeof Request === \"function\" && input instanceof Request) ||\n (typeof URL === \"function\" && input instanceof URL)\n ) {\n input = fetch(input);\n }\n\n __wbg_init_memory(imports);\n\n const { instance, module } = await __wbg_load(await input, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nexport { initSync };\nexport default __wbg_init;\n","/* eslint-disable-next-line */\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\n\n/**\n * @classdesc\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\n * to Telemetry in a more comfortable and homogeneous way.\n */\nexport default class Telemeter {\n /**\n * Inits Telemeter class.\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\n * @param {Object} telemeterAttributes.config - Configuration parameters.\n */\n static init(telemeterAttributes) {\n if (!this.telemeter && !this.waitingForInit) {\n this.waitingForInit = true;\n init(telemeterAttributes.url)\n .then(() => {\n this.telemeter = new TelemeterWASM(\n telemeterAttributes.solution,\n telemeterAttributes.hosts,\n telemeterAttributes.config,\n );\n })\n .catch((error) => {\n console.log(error);\n })\n .finally(() => (this.waitingForInit = false));\n }\n }\n\n /**\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\n */\n static async finish() {\n if (!this.telemeter) return;\n\n try {\n const local_telemeter = this.telemeter;\n this.telemeter = undefined;\n await local_telemeter.finish();\n } catch (e) {\n console.error(e);\n }\n }\n}\n","import Configuration from \"./configuration\";\nimport Core from \"./core.src\";\nimport EditorListener from \"./editorlistener\";\nimport Listeners from \"./listeners\";\nimport MathML from \"./mathml\";\nimport Util from \"./util\";\nimport Telemeter from \"./telemeter\";\n\nexport default class ContentManager {\n /**\n * @classdesc\n * This class represents a modal dialog, managing the following:\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\n * - The actions to be done once the modal object has been submitted\n * (submitAction} method).\n * - The update of the content when the {@link ModalDialog} class is also updated,\n * for example when ModalDialog is re-opened.\n * - The communication between the {@link ModalDialog} class and itself, if the content\n * has been changed (hasChanges} method).\n * @constructs\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\n * create a new instance.\n */\n constructor(contentManagerAttributes) {\n /**\n * An object containing MathType editor parameters. See\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\n * @type {Object}\n */\n this.editorAttributes = {};\n if (\"editorAttributes\" in contentManagerAttributes) {\n this.editorAttributes = contentManagerAttributes.editorAttributes;\n } else {\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\n }\n\n /**\n * CustomEditors instance. Contains the custom editors.\n * @type {CustomEditors}\n */\n this.customEditors = null;\n if (\"customEditors\" in contentManagerAttributes) {\n this.customEditors = contentManagerAttributes.customEditors;\n }\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @type {Object}\n * @property {String} editor - Editor name. Usually the HTML editor.\n * @property {String} mode - Save mode. Xml by default.\n * @property {String} version - Plugin version.\n */\n this.environment = {};\n if (\"environment\" in contentManagerAttributes) {\n this.environment = contentManagerAttributes.environment;\n } else {\n throw new Error(\"ContentManager constructor error: environment property missed\");\n }\n\n /**\n * ContentManager language.\n * @type {String}\n */\n this.language = \"\";\n if (\"language\" in contentManagerAttributes) {\n this.language = contentManagerAttributes.language;\n } else {\n throw new Error(\"ContentManager constructor error: language property missed\");\n }\n\n /**\n * {@link EditorListener} instance. Manages the changes inside the editor.\n * @type {EditorListener}\n */\n this.editorListener = new EditorListener();\n\n /**\n * MathType editor instance.\n * @type {JsEditor}\n */\n this.editor = null;\n\n /**\n * Navigator user agent.\n * @type {String}\n */\n this.ua = navigator.userAgent.toLowerCase();\n\n /**\n * Mobile device properties object\n * @type {DeviceProperties}\n */\n this.deviceProperties = {};\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\n this.deviceProperties.isIOS = ContentManager.isIOS();\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.toolbar = null;\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.dbclick = null;\n\n /**\n * Instance of the {@link ModalDialog} class associated with the current\n * {@link ContentManager} instance.\n * @type {ModalDialog}\n */\n this.modalDialogInstance = null;\n\n /**\n * ContentManager listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n /**\n * MathML associated to the ContentManager instance.\n * @type {String}\n */\n this.mathML = null;\n\n /**\n * Indicates if the edited element is a new one or not.\n * @type {Boolean}\n */\n this.isNewElement = true;\n\n /**\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n }\n\n /**\n * Adds a new listener to the current {@link ContentManager} instance.\n * @param {Object} listener - The listener to be added.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\n * instance.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\n */\n setModalDialogInstance(modalDialogInstance) {\n this.modalDialogInstance = modalDialogInstance;\n }\n\n /**\n * Inserts the content into the current {@link ModalDialog} instance updating\n * the title and inserting the JavaScript editor.\n */\n insert() {\n // Before insert the editor we update the modal object title to avoid weird render display.\n this.updateTitle(this.modalDialogInstance);\n this.insertEditor(this.modalDialogInstance);\n }\n\n /**\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\n * editor's JavaScript is loaded.\n */\n insertEditor() {\n if (ContentManager.isEditorLoaded()) {\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\n this.editor.focus();\n\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\n this.editor.action(\"rtl\");\n }\n // Setting div in rtl in case of it's activated.\n if (this.editor.getEditorModel().isRTL()) {\n this.editor.element.style.direction = \"rtl\";\n }\n\n // Editor listener: this object manages the changes logic of editor.\n this.editor.getEditorModel().addEditorListener(this.editorListener);\n\n // iOS events.\n if (this.modalDialogInstance.deviceProperties.isIOS) {\n setTimeout(function () {\n // Make sure the modalDialogInstance is available when the timeout is over\n // to avoid throw errors and stop execution.\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\n }, 400);\n\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\n }\n // Fire onLoad event. Necessary to set the MathML into the editor\n // after is loaded.\n this.listeners.fire(\"onLoad\", {});\n } else {\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\n }\n }\n\n /**\n * Initializes the current class by loading MathType script.\n */\n init() {\n if (!ContentManager.isEditorLoaded()) {\n this.addEditorAsExternalDependency();\n }\n }\n\n /**\n * Adds script element to the DOM to include editor externally.\n */\n addEditorAsExternalDependency() {\n const script = document.createElement(\"script\");\n script.type = \"text/javascript\";\n let editorUrl = Configuration.get(\"editorUrl\");\n\n // We create an object url for parse url string and work more efficiently.\n const anchorElement = document.createElement(\"a\");\n\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\n ContentManager.setProtocolToAnchorElement(anchorElement);\n\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\n\n // Load editor URL. We add stats as GET params.\n const stats = this.getEditorStats();\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\n\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n }\n\n /**\n * Sets the specified url to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\n * @param {String} url - URL to set.\n */\n static setHrefToAnchorElement(anchorElement, url) {\n anchorElement.href = url;\n }\n\n /**\n * Sets the current protocol to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\n */\n static setProtocolToAnchorElement(anchorElement) {\n // Change to https if necessary.\n if (window.location.href.indexOf(\"https://\") === 0) {\n // It check if browser is https and configuration is http.\n // If this is so, we will replace protocol.\n if (anchorElement.protocol === \"http:\") {\n anchorElement.protocol = \"https:\";\n }\n }\n }\n\n /**\n * Returns the url of the anchor element adding the current port\n * if it is needed.\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\n * @returns {String}\n */\n static getURLFromAnchorElement(anchorElement) {\n // Check protocol and remove port if it's standard.\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\n }\n\n /**\n * Returns object with editor stats.\n *\n * @typedef {Object} EditorStatsObject\n * @property {string} editor - Editor name.\n * @property {string} mode - Current configuration for formula save mode.\n * @property {string} version - Current plugins version.\n * @returns {EditorStatsObject}\n */\n getEditorStats() {\n // Editor stats. Use environment property to set it.\n const stats = {};\n if (\"editor\" in this.environment) {\n stats.editor = this.environment.editor;\n } else {\n stats.editor = \"unknown\";\n }\n\n if (\"mode\" in this.environment) {\n stats.mode = this.environment.mode;\n } else {\n stats.mode = Configuration.get(\"saveMode\");\n }\n\n if (\"version\" in this.environment) {\n stats.version = this.environment.version;\n } else {\n stats.version = Configuration.get(\"version\");\n }\n\n return stats;\n }\n\n /**\n * Returns true if device is iOS. Otherwise, false.\n * @returns {Boolean}\n */\n static isIOS() {\n return (\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\n // iPad on iOS 13 detection\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\n );\n }\n\n /**\n * Returns true if device is Mobile. Otherwise, false.\n * @returns {Boolean}\n */\n static isMobile() {\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n }\n\n /**\n * Returns true if editor is loaded. Otherwise, false.\n * @returns {Boolean}\n */\n static isEditorLoaded() {\n // To know if editor JavaScript is loaded we need to wait until\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\n return (\n window.com &&\n window.com.wiris &&\n window.com.wiris.jsEditor &&\n window.com.wiris.jsEditor.JsEditor &&\n window.com.wiris.jsEditor.JsEditor.newInstance\n );\n }\n\n /**\n * Sets the {@link ContentManager.editor} initial content.\n */\n setInitialContent() {\n if (!this.isNewElement) {\n this.setMathML(this.mathML);\n }\n }\n\n /**\n * Sets a MathML into {@link ContentManager.editor} instance.\n * @param {String} mathml - MathML string.\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\n * False by default.\n */\n setMathML(mathml, focusDisabled) {\n // By default focus is enabled.\n if (typeof focusDisabled === \"undefined\") {\n focusDisabled = false;\n }\n // Using setMathML method is not a change produced by the user but for the API\n // so we set to false the contentChange property of editorListener.\n this.editor.setMathMLWithCallback(mathml, () => {\n this.editorListener.setWaitingForChanges(true);\n });\n\n // We need to wait a little until the callback finish.\n setTimeout(() => {\n this.editorListener.setIsContentChanged(false);\n }, 500);\n\n // In some scenarios - like closing modal object - editor mustn't be focused.\n if (!focusDisabled) {\n this.onFocus();\n }\n }\n\n /**\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\n * {@link ModalDialog.focus}.\n */\n onFocus() {\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\n this.editor.focus();\n\n // On WordPress integration, the focus gets lost right after setting it.\n // To fix this, we enforce another focus some milliseconds after this behaviour.\n setTimeout(() => {\n this.editor.focus();\n }, 100);\n }\n }\n\n /**\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\n * Triggered by {@link ModalDialog.submitAction}.\n */\n submitAction() {\n if (!this.editor.isFormulaEmpty()) {\n let mathML = this.editor.getMathMLWithSemantics();\n // Add class for custom editors.\n if (this.customEditors.getActiveEditor() !== null) {\n const { toolbar } = this.customEditors.getActiveEditor();\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\n } else {\n // We need - if exists - the editor name from MathML\n // class attribute.\n Object.keys(this.customEditors.editors).forEach((key) => {\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\n });\n }\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\n } else {\n this.integrationModel.updateFormula(null);\n }\n\n this.customEditors.disable();\n this.integrationModel.notifyWindowClosed();\n\n // Set disabled focus to prevent lost focus.\n this.setEmptyMathML();\n this.customEditors.disable();\n }\n\n /**\n * Sets an empty MathML as {@link ContentManager.editor} content.\n * This will open the MT/CT editor with the hand mode.\n * It adds dir rtl in case of it's activated.\n */\n setEmptyMathML() {\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\n const isRTL = this.editor.getEditorModel().isRTL();\n\n if (isMobile || this.integrationModel.forcedHandMode) {\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\n const mathML = `[]`;\n this.setMathML(mathML, true);\n } else {\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\n const mathML = ``;\n this.setMathML(mathML, true);\n }\n }\n\n /**\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\n * - Updates the {@link ContentManager.editor} content\n * (with an empty MathML or an existing formula),\n * - Updates the {@link ContentManager.editor} toolbar.\n * - Recovers the the focus.\n */\n onOpen() {\n if (this.isNewElement) {\n this.setEmptyMathML();\n } else {\n this.setMathML(this.mathML);\n }\n const toolbar = this.updateToolbar();\n this.onFocus();\n\n if (this.deviceProperties.isIOS) {\n const zoom = document.documentElement.clientWidth / window.innerWidth;\n\n if (zoom !== 1) {\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\n this.setKeyboardMode();\n }\n }\n\n const trigger = this.dbclick ? \"formula\" : \"button\";\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\n toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\n }\n\n Core.globalListeners.fire(\"onModalOpen\", {});\n\n if (this.integrationModel.forcedHandMode) {\n this.hideHandModeButton();\n\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\n }\n }\n }\n\n /**\n * Change Editor in keyboard mode when is loaded\n */\n setKeyboardMode() {\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\n if (wrsEditor) {\n wrsEditor.classList.remove(\"wrs_handOpen\");\n wrsEditor.classList.remove(\"wrs_disablePalette\");\n } else {\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\n }\n }\n\n /**\n * Hides the hand <-> keyboard mode switch.\n *\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\n * any change on those classes will make this code stop working properly.\n *\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\n */\n hideHandModeButton(forced = true) {\n if (this.handSwitchHidden) {\n return; // hand <-> keyboard button already hidden.\n }\n\n // \"Open hand mode\" button takes a little bit to be available.\n // This selector gets the hand <-> keyboard mode switch\n const handModeButtonSelector =\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\n\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\n // We use an observer to ensure that the button it hidden as soon as it appears.\n if (forced) {\n const mutationInstance = new MutationObserver((mutations) => {\n const handModeButton = document.querySelector(handModeButtonSelector);\n if (handModeButton) {\n handModeButton.hidden = true;\n this.handSwitchHidden = true;\n mutationInstance.disconnect();\n }\n });\n mutationInstance.observe(document.body, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n }\n\n /**\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\n *\n * @param {String} mathml The original KeyBoard MathML\n * @param {Object} editor The editor object.\n */\n async openHandOnKeyboardMathML(mathml, editor) {\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\n await new Promise((resolve) => {\n editor.setMathMLWithCallback(mathml, resolve);\n });\n\n // We wait until the hand editor object exists.\n await this.waitForHand(editor);\n\n // Logic to get the hand traces and open the formula in hand mode.\n // This logic comes from the editor.\n const handEditor = editor.hand;\n editor.handTemporalMathML = editor.getMathML();\n const handCoordinates = editor.editorModel.getHandStrokes();\n handEditor.setStrokes(handCoordinates);\n handEditor.fitStrokes(true);\n editor.openHand();\n }\n\n /**\n * Waits until the hand editor object exists.\n * @param {Obect} editor The editor object.\n */\n async waitForHand(editor) {\n while (!editor.hand) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n\n /**\n * Sets the correct toolbar depending if exist other custom toolbars\n * at the same time (e.g: Chemistry).\n */\n updateToolbar() {\n this.updateTitle(this.modalDialogInstance);\n const customEditor = this.customEditors.getActiveEditor();\n\n let toolbar;\n if (customEditor) {\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\n\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n }\n } else {\n toolbar = this.getToolbar();\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n this.customEditors.disable();\n }\n }\n\n return toolbar;\n }\n\n /**\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\n * sets the custom editor title. Otherwise sets the default title.\n */\n updateTitle() {\n const customEditor = this.customEditors.getActiveEditor();\n if (customEditor) {\n this.modalDialogInstance.setTitle(customEditor.title);\n } else {\n this.modalDialogInstance.setTitle(\"MathType\");\n }\n }\n\n /**\n * Returns the editor toolbar, depending on the configuration local or server side.\n * @returns {String} - Toolbar identifier.\n */\n getToolbar() {\n let toolbar = \"general\";\n if (\"toolbar\" in this.editorAttributes) {\n ({ toolbar } = this.editorAttributes);\n }\n // TODO: Change global integration variable for integration custom toolbar.\n if (toolbar === \"general\") {\n // eslint-disable-next-line camelcase\n toolbar =\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\n ? \"general\"\n : _wrs_int_wirisProperties.toolbar;\n }\n\n return toolbar;\n }\n\n /**\n * Sets the current {@link ContentManager.editor} instance toolbar.\n * @param {String} toolbar - The toolbar name.\n */\n setToolbar(toolbar) {\n this.toolbar = toolbar;\n this.editor.setParams({ toolbar: this.toolbar });\n }\n\n /**\n * Sets the custom headers added on editor requests.\n * @returns {Object} headers - key value headers.\n */\n setCustomHeaders(headers) {\n let headersObj = {};\n\n // We control that we only get String or Object as the input.\n if (typeof headers === \"object\") {\n headersObj = headers;\n } else if (typeof headers === \"string\") {\n headersObj = Util.convertStringToObject(headers);\n }\n\n this.editor.setParams({ customHeaders: headersObj });\n return headersObj;\n }\n\n /**\n * Returns true if the content of the editor has been changed. The logic of the changes\n * is delegated to {@link EditorListener} class.\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\n */\n hasChanges() {\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n // Code to detect Esc event.\n // There should be only one element with class name 'wrs_pressed' at the same time.\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\n if (list.length === 0) {\n this.modalDialogInstance.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.modalDialogInstance.submitButton) {\n // Focus is on OK button.\n this.editor.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\n // Focus is on minimize button (_).\n this.modalDialogInstance.closeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\n // Focus on cancel button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n this.modalDialogInstance.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\n // Focus is on X button.\n this.modalDialogInstance.minimizeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\n // Focus on help button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n const element = document.querySelector('[title=\"Manual\"]');\n element.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n } else {\n // There should be only one element with class name 'wrs_formulaDisplay'.\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\n // Focus is on formuladisplay.\n this.modalDialogInstance.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n }\n }\n}\n","/**\n * A custom editor is MathType editor with a different\n * @typedef {Object} CustomEditor\n * @property {String} CustomEditor.name - Custom editor name.\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\n * @property {String} CustomEditor.icon - Custom editor icon.\n * @property {String} CustomEditor.confVariable - Configuration property to manage\n * the availability of the custom editor.\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\n */\n\nexport default class CustomEditors {\n /**\n * @classdesc\n * This class represents the MathType custom editors manager.\n * A custom editor is MathType editor with a custom toolbar.\n * This class associates a {@link CustomEditor} to:\n * - It's own formulas\n * - A custom toolbar\n * - An icon to open it from a HTML editor.\n * - A tooltip for the icon.\n * - A global variable to enable or disable it globally.\n * @constructs\n */\n constructor() {\n /**\n * The custom editors.\n * @type {Array.}\n */\n\n this.editors = [];\n /**\n * The active editor name.\n * @type {String}\n */\n this.activeEditor = \"default\";\n }\n\n /**\n * Adds a {@link CustomEditor} to editors array.\n * @param {String} editorName - The editor name.\n * @param {CustomEditor} editorParams - The custom editor parameters.\n */\n addEditor(editorName, editorParams) {\n const customEditor = {};\n customEditor.name = editorParams.name;\n customEditor.toolbar = editorParams.toolbar;\n customEditor.icon = editorParams.icon;\n customEditor.confVariable = editorParams.confVariable;\n customEditor.title = editorParams.title;\n customEditor.tooltip = editorParams.tooltip;\n this.editors[editorName] = customEditor;\n }\n\n /**\n * Enables a {@link CustomEditor}.\n * @param {String} customEditorName - The custom editor name.\n */\n enable(customEditorName) {\n this.activeEditor = customEditorName;\n }\n\n /**\n * Disables a {@link CustomEditor}.\n */\n disable() {\n this.activeEditor = \"default\";\n }\n\n /**\n * Returns the active editor.\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\n */\n getActiveEditor() {\n if (this.activeEditor !== \"default\") {\n return this.editors[this.activeEditor];\n }\n return null;\n }\n}\n","/**\n * Represents the configuration properties generated from the frontend (JavaScript variables).\n * @type {Object}\n * @property {string} imageClassName - Default MathType formula image class.\n * @property {string} imageClassName - Default MathType CAS image class.\n * @ignore\n */\nconst jsProperties = {\n imageCustomEditorName: \"data-custom-editor\",\n imageClassName: \"Wirisformula\",\n CASClassName: \"Wiriscas\",\n};\nexport default jsProperties;\n","export default class Event {\n /**\n * @classdesc\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\n *\n * ```js\n * let customEvent = new Event();\n * customEvent.properties = {};\n *\n * let listeners = new Listeners();\n * listeners.newListener(eventName, callback);\n *\n * listeners.fire(eventName, customEvent) *\n * ```\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the event should be cancelled.\n * @type {Boolean}\n */\n\n this.cancelled = false;\n /**\n * Indicates if the event should be prevented.\n * @type {Boolean}\n */\n this.defaultPrevented = false;\n }\n\n /**\n * Cancels the event.\n */\n cancel() {\n this.cancelled = true;\n }\n\n /**\n * Prevents the default action.\n */\n preventDefault() {\n this.defaultPrevented = true;\n }\n}\n","import IntegrationModel from \"./integrationmodel\";\n\n/**\n\n */\nexport default class PopUpMessage {\n /**\n * @classdesc\n * This class represents a dialog message overlaying a DOM element in order to\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\n * o canceled. In this last case a callback function should be called.\n * @constructs\n * @param {Object} popupMessageAttributes - Object containing popup properties.\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\n * methods for close and cancel actions.\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\n */\n constructor(popupMessageAttributes) {\n /**\n * Element to be overlaid when the popup appears.\n */\n this.overlayElement = popupMessageAttributes.overlayElement;\n\n this.callbacks = popupMessageAttributes.callbacks;\n\n /**\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\n */\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\n\n /**\n * HTMLElement to display the popup message, close button and cancel button.\n */\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n this.message.id = \"wrs_popupmessage\";\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\n this.message.setAttribute(\"role\", \"dialog\");\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\n const paragraph = document.createElement(\"p\");\n const text = document.createTextNode(popupMessageAttributes.strings.message);\n paragraph.appendChild(text);\n paragraph.id = \"description_txt\";\n this.message.appendChild(paragraph);\n\n /**\n * HTML element overlaying the overlayElement.\n */\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\n // We create a overlay that close popup message on click in there\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n /**\n * HTML element containing cancel and close buttons.\n */\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\n this.buttonArea.id = \"wrs_popup_button_area\";\n\n // Close button arguments.\n const buttonSubmitArguments = {\n class: \"wrs_button_accept\",\n innerHTML: popupMessageAttributes.strings.submitString,\n id: \"wrs_popup_accept_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-close-button\",\n };\n\n /**\n * Close button arguments.\n */\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\n this.buttonArea.appendChild(this.closeButton);\n\n // Cancel button arguments.\n const buttonCancelArguments = {\n class: \"wrs_button_cancel\",\n innerHTML: popupMessageAttributes.strings.cancelString,\n id: \"wrs_popup_cancel_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\n };\n\n /**\n * Cancel button.\n */\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\n this.buttonArea.appendChild(this.cancelButton);\n }\n\n /**\n * This method create a button with arguments and return button dom object\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\n * @param {String} parameters.id - Button id.\n * @param {String} parameters.class - Button class name.\n * @param {String} parameters.innerHTML - Button innerHTML text.\n * @param {Object} callback- Callback method to call on click event.\n * @returns {HTMLElement} HTML button.\n */\n // eslint-disable-next-line class-methods-use-this\n createButton(parameters, callback) {\n let element = {};\n element = document.createElement(\"button\");\n element.setAttribute(\"id\", parameters.id);\n element.setAttribute(\"class\", parameters.class);\n element.innerHTML = parameters.innerHTML;\n element.addEventListener(\"click\", callback);\n if (parameters[\"data-testid\"]) {\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\n }\n\n return element;\n }\n\n /**\n * Shows the popupmessage containing a message, and two buttons\n * to cancel the action or close the modal dialog.\n */\n show() {\n if (this.overlayWrapper.style.display !== \"block\") {\n // Clear focus with blur for prevent press any key.\n document.activeElement.blur();\n this.overlayWrapper.style.display = \"block\";\n this.closeButton.focus();\n } else {\n this.overlayWrapper.style.display = \"none\";\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\n }\n }\n\n /**\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\n * A callback method is called (if defined). For example a method to focus the overlaid element.\n */\n cancelAction() {\n this.overlayWrapper.style.display = \"none\";\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\n this.callbacks.cancelCallback();\n // Set temporal image to null to prevent loading\n // an existent formula when starting one from scratch. Make focus come back too.\n // IntegrationModel.setActionsOnCancelButtons();\n }\n }\n\n /**\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\n * For example to close the overlaid element.\n */\n closeAction() {\n this.cancelAction();\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\n this.callbacks.closeCallback();\n }\n IntegrationModel.setActionsOnCancelButtons();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Code to detect Esc event.\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n this.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.closeButton) {\n this.cancelButton.focus();\n } else {\n this.closeButton.focus();\n }\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n}\n","/**\n * This module provides protection against external focus management scripts\n * that might interfere with the MathType editor modal.\n */\n\n/**\n * focusProtection function creates and returns methods to prevent external scripts from\n * interfering with the focus of the MathType modal dialog.\n *\n * @returns {Object} An object with protect and unprotect methods.\n */\nconst focusProtection = () => {\n /**\n * Initialize focus protection on the given modal element.\n *\n * @param {HTMLElement} modalElement - The modal element to protect\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\n * @param {HTMLElement} editorElement - The editor element inside the modal\n */\n const protect = (modalElement, overlayElement, editorElement) => {\n if (!modalElement || !overlayElement || !editorElement) {\n console.warn(\"FocusProtection: Missing required elements\");\n return;\n }\n\n // Apply the focus protection\n overrideFocusBehavior(modalElement, editorElement);\n };\n\n /**\n * Apply focus protection by overriding focus-related methods\n *\n * @param {HTMLElement} modalElement - The modal element\n * @param {HTMLElement} editorElement - The editor element to keep focused\n * @private\n */\n const overrideFocusBehavior = (modalElement, editorElement) => {\n // Store original focus methods to be able to restore them\n const originalElementFocus = HTMLElement.prototype.focus;\n const originalElementBlur = HTMLElement.prototype.blur;\n\n // Override the focus method for all elements\n HTMLElement.prototype.focus = function (...args) {\n // If the modal is open and this is not part of the modal, prevent focus\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\n // If some external script is trying to focus another element, prevent it\n // and restore focus to the editor\n if (editorElement) {\n // Use the original focus method to avoid infinite recursion\n originalElementFocus.apply(editorElement, args);\n }\n return;\n }\n\n // Otherwise, allow the focus to happen\n originalElementFocus.apply(this, args);\n };\n\n // Store the methods to remove them when the modal is closed\n modalElement.originalElementFocus = originalElementFocus;\n modalElement.originalElementBlur = originalElementBlur;\n };\n\n /**\n * Remove focus protection from the modal\n *\n * @param {HTMLElement} modalElement - The modal element to unprotect\n */\n const unprotect = (modalElement) => {\n if (!modalElement) {\n return;\n }\n\n // Restore original focus methods\n if (modalElement.originalElementFocus) {\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\n delete modalElement.originalElementFocus;\n }\n\n if (modalElement.originalElementBlur) {\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\n delete modalElement.originalElementBlur;\n }\n };\n\n return {\n protect,\n unprotect,\n };\n};\n\nexport default focusProtection;\n","// eslint-disable-next-line max-classes-per-file\nimport PopUpMessage from \"./popupmessage\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport Listeners from \"./listeners\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Telemeter from \"./telemeter\";\nimport IntegrationModel from \"./integrationmodel\";\nimport Core from \"./core.src\";\nimport focusProtection from \"./focusprotection\";\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\nconst { unprotect, protect } = focusProtection();\n\n/**\n * @typedef {Object} DeviceProperties\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\n * False otherwise.\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\n * False otherwise.\n */\n\nexport default class ModalDialog {\n /**\n * @classdesc\n * This class represents a modal dialog. The modal dialog admits\n * a {@link ContentManager} instance to manage the content of the dialog.\n * @constructs\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\n */\n constructor(modalDialogAttributes) {\n this.attributes = modalDialogAttributes;\n\n // Metrics.\n const ua = navigator.userAgent.toLowerCase();\n const isAndroid = ua.indexOf(\"android\") > -1;\n const isIOS = ContentManager.isIOS();\n this.iosSoftkeyboardOpened = false;\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\n this.iosDivHeight = `auto`;\n\n const deviceWidth = window.outerWidth;\n const deviceHeight = window.outerHeight;\n\n const landscape = deviceWidth > deviceHeight;\n const portrait = deviceWidth < deviceHeight;\n\n // TODO: Detect isMobile without using editor metrics.\n const isLandscape = landscape && this.attributes.height > deviceHeight;\n const isPortrait = portrait && this.attributes.width > deviceWidth;\n const isMobile = ContentManager.isMobile();\n\n // Obtain number of current instance.\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\n\n // Device object properties.\n\n /**\n * @type {DeviceProperties}\n */\n this.deviceProperties = {\n orientation: landscape ? \"landscape\" : \"portrait\",\n isAndroid,\n isIOS,\n isMobile,\n isDesktop: !isMobile && !isIOS && !isAndroid,\n };\n\n this.properties = {\n created: false,\n state: \"\",\n previousState: \"\",\n position: { bottom: 0, right: 10 },\n size: { height: 338, width: 580 },\n };\n\n /**\n * Object to keep website's style before change it on lock scroll for mobile devices.\n * @type {Object}\n * @property {String} bodyStylePosition - Previous body style position.\n * @property {String} bodyStyleOverflow - Previous body style overflow.\n * @property {String} htmlStyleOverflow - Previous body style overflow.\n * @property {String} windowScrollX - Previous window's scroll Y.\n * @property {String} windowScrollY - Previous window's scroll X.\n */\n this.websiteBeforeLockParameters = null;\n\n let attributes = {};\n attributes.class = \"wrs_modal_overlay\";\n attributes.id = this.getElementId(attributes.class);\n this.overlay = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title_bar\";\n attributes.id = this.getElementId(attributes.class);\n this.titleBar = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title\";\n attributes.id = this.getElementId(attributes.class);\n this.title = Util.createElement(\"div\", attributes);\n this.title.innerHTML = \"offline\";\n\n attributes = {};\n attributes.class = \"wrs_modal_close_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"close\");\n attributes.style = {};\n this.closeDiv = Util.createElement(\"a\", attributes);\n this.closeDiv.setAttribute(\"role\", \"button\");\n this.closeDiv.setAttribute(\"tabindex\", 3);\n // Apply styles and events after the creation as createElement doesn't process them correctly\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\n // To identifiy the element in automated testing\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_stack_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"exit_fullscreen\");\n this.stackDiv = Util.createElement(\"a\", attributes);\n this.stackDiv.setAttribute(\"role\", \"button\");\n this.stackDiv.setAttribute(\"tabindex\", 2);\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\n // To identifiy the element in automated testing\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_maximize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"fullscreen\");\n this.maximizeDiv = Util.createElement(\"a\", attributes);\n this.maximizeDiv.setAttribute(\"role\", \"button\");\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\n // To identifiy the element in automated testing\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_minimize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"minimize\");\n this.minimizeDiv = Util.createElement(\"a\", attributes);\n this.minimizeDiv.setAttribute(\"role\", \"button\");\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\n // To identify the element in automated testing\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_dialogContainer\";\n attributes.id = this.getElementId(attributes.class);\n attributes.role = \"dialog\";\n this.container = Util.createElement(\"div\", attributes);\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\n\n attributes = {};\n attributes.class = \"wrs_modal_wrapper\";\n attributes.id = this.getElementId(attributes.class);\n this.wrapper = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_content_container\";\n attributes.id = this.getElementId(attributes.class);\n this.contentContainer = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_controls\";\n attributes.id = this.getElementId(attributes.class);\n this.controls = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_buttons_container\";\n attributes.id = this.getElementId(attributes.class);\n this.buttonContainer = Util.createElement(\"div\", attributes);\n\n // Buttons: all button must be created using createSubmitButton method.\n this.submitButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_accept\"),\n class: \"wrs_modal_button_accept\",\n innerHTML: StringManager.get(\"accept\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-insert-button\",\n },\n this.submitAction.bind(this),\n );\n\n this.cancelButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_cancel\"),\n class: \"wrs_modal_button_cancel\",\n innerHTML: StringManager.get(\"cancel\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cancel-button\",\n },\n this.cancelAction.bind(this),\n );\n\n this.contentManager = null;\n\n // Overlay popup.\n const popupStrings = {\n cancelString: StringManager.get(\"cancel\"),\n submitString: StringManager.get(\"close\"),\n message: StringManager.get(\"close_modal_warning\"),\n };\n\n const callbacks = {\n closeCallback: () => {\n this.close(\"mtc_close\");\n },\n cancelCallback: () => {\n this.focus();\n },\n };\n\n const popupupProperties = {\n overlayElement: this.container,\n callbacks,\n strings: popupStrings,\n };\n\n this.popup = new PopUpMessage(popupupProperties);\n\n /**\n * Indicates if directionality of the modal dialog is RTL. false by default.\n * @type {Boolean}\n */\n this.rtl = false;\n if (\"rtl\" in this.attributes) {\n this.rtl = this.attributes.rtl;\n }\n\n // Event handlers need modal instance context.\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\n }\n\n /**\n * This method sets an ContentManager instance to ModalDialog. ContentManager\n * manages the logic of ModalDialog content: submit, update, close and changes.\n * @param {ContentManager} contentManager - ContentManager instance.\n */\n setContentManager(contentManager) {\n this.contentManager = contentManager;\n }\n\n /**\n * Returns the modal contentElement object.\n * @returns {ContentManager} the instance of the ContentManager class.\n */\n getContentManager() {\n return this.contentManager;\n }\n\n /**\n * This method is called when the modal object has been submitted. Calls\n * contentElement submitAction method - if exists - and closes the modal\n * object. No logic about the content should be placed here,\n * contentElement.submitAction is the responsible of the content logic.\n */\n async submitAction() {\n if (typeof this.contentManager.submitAction !== \"undefined\") {\n this.contentManager.submitAction();\n }\n\n await this.close(\"mtc_insert\");\n }\n\n /**\n * Performs the cancel action.\n * If there are no changes in the content, it closes the modal.\n * Otherwise, it shows a pop-up message to confirm the cancel action.\n * @returns {Promise} - A promise that resolves when the modal is closed.\n */\n async cancelAction() {\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\n IntegrationModel.setActionsOnCancelButtons();\n await this.close(\"mtc_close\");\n } else {\n this.showPopUpMessage();\n }\n }\n\n /**\n * Returns a button element.\n * @param {Object} properties - Input button properties.\n * @param {String} properties.class - Input button class.\n * @param {String} properties.innerHTML - Input button innerHTML.\n * @param {Object} callback - Callback function associated to click event.\n * @returns {HTMLButtonElement} The button element.\n *\n */\n // eslint-disable-next-line class-methods-use-this\n createSubmitButton(properties, callback) {\n class SubmitButton {\n constructor() {\n this.element = document.createElement(\"button\");\n this.element.id = properties.id;\n this.element.className = properties.class;\n this.element.innerHTML = properties.innerHTML;\n this.element.dataset.testid = properties[\"data-testid\"];\n Util.addEvent(this.element, \"click\", callback);\n }\n\n getElement() {\n return this.element;\n }\n }\n return new SubmitButton(properties, callback).getElement();\n }\n\n /**\n * Creates the modal window object inserting a contentElement object.\n */\n create() {\n /* Modal Window Structure\n _____________________________________________________________________________________\n |wrs_modal_dialog_Container |\n | _________________________________________________________________________________ |\n | |title_bar minimize_button stack_button close_button | |\n | |_______________________________________________________________________________| |\n | |wrapper | |\n | | _____________________________________________________________________________ | |\n | | |content | | |\n | | | | | |\n | | | | | |\n | | |___________________________________________________________________________| | |\n | | _____________________________________________________________________________ | |\n | | |controls | | |\n | | | ___________________________________ | | |\n | | | |buttonContainer | | | |\n | | | | _______________________________ | | | |\n | | | | |button_accept | button_cancel| | | | |\n | | | |_|_____________ |______________|_| | | |\n | | |___________________________________________________________________________| | |\n | |_______________________________________________________________________________| |\n |___________________________________________________________________________________| */\n\n this.titleBar.appendChild(this.closeDiv);\n this.titleBar.appendChild(this.stackDiv);\n this.titleBar.appendChild(this.maximizeDiv);\n this.titleBar.appendChild(this.minimizeDiv);\n this.titleBar.appendChild(this.title);\n\n if (this.deviceProperties.isDesktop) {\n this.container.appendChild(this.titleBar);\n }\n\n this.wrapper.appendChild(this.contentContainer);\n this.wrapper.appendChild(this.controls);\n\n this.controls.appendChild(this.buttonContainer);\n\n this.buttonContainer.appendChild(this.submitButton);\n this.buttonContainer.appendChild(this.cancelButton);\n\n this.container.appendChild(this.wrapper);\n\n // Check if browser has scrollBar before modal has modified.\n this.recalculateScrollBar();\n\n document.body.appendChild(this.container);\n document.body.appendChild(this.overlay);\n\n if (this.deviceProperties.isDesktop) {\n // Desktop.\n this.createModalWindowDesktop();\n this.createResizeButtons();\n\n this.addListeners();\n // Maximize window only when the configuration is set and the device is not iOS or Android.\n if (Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n } else if (this.deviceProperties.isAndroid) {\n this.createModalWindowAndroid();\n } else if (this.deviceProperties.isIOS) {\n this.createModalWindowIos();\n }\n\n if (this.contentManager != null) {\n this.contentManager.insert(this);\n }\n\n this.properties.open = true;\n this.properties.created = true;\n\n // Checks language directionality.\n if (this.isRTL()) {\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\n this.container.className += \" wrs_modal_rtl\";\n }\n }\n\n /**\n * Creates a button in the modal object to resize it.\n */\n createResizeButtons() {\n // This is a definition of Resize Button Bottom-Right.\n this.resizerBR = document.createElement(\"div\");\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\n this.resizerBR.innerHTML = \"โ—ข\";\n // To identifiy the element in automated testing\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\n // This is a definition of Resize Button Top-Left.\n this.resizerTL = document.createElement(\"div\");\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\n // To identifiy the element in automated testing\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\n // Append resize buttons to modal.\n this.container.appendChild(this.resizerBR);\n this.titleBar.appendChild(this.resizerTL);\n // Add events to resize on click and drag.\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\n }\n\n /**\n * Initialize variables for Bottom-Right resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateBR(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, false);\n }\n\n /**\n * Initialize variables for Top-Left resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateTL(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, true);\n }\n\n /**\n * Common method to initialize variables at resize.\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n initializeResizeProperties(mouseEvent, leftOption) {\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n this.resizeDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Save Initial state of modal to compare on drag and obtain the difference.\n this.initialWidth = parseInt(this.container.style.width, 10);\n this.initialHeight = parseInt(this.container.style.height, 10);\n if (!leftOption) {\n this.initialRight = parseInt(this.container.style.right, 10);\n this.initialBottom = parseInt(this.container.style.bottom, 10);\n } else {\n this.leftScale = true;\n }\n if (!this.initialRight) {\n this.initialRight = 0;\n }\n if (!this.initialBottom) {\n this.initialBottom = 0;\n }\n // Disable mouse events on editor when we start to drag modal.\n document.body.style[\"user-select\"] = \"none\";\n }\n\n /**\n * This method opens the modal window, restoring the previous state, position and metrics,\n * if exists. By default the modal object opens in stack mode.\n */\n open() {\n // Removing close class.\n this.removeClass(\"wrs_closed\");\n // Hiding keyboard for mobile devices.\n const { isIOS } = this.deviceProperties;\n const { isAndroid } = this.deviceProperties;\n const { isMobile } = this.deviceProperties;\n if (isIOS || isAndroid || isMobile) {\n // Restore scale to 1.\n this.restoreWebsiteScale();\n this.lockWebsiteScroll();\n // Due to editor wait we need to wait until editor focus.\n setTimeout(() => {\n this.hideKeyboard();\n }, 400);\n }\n\n // New modal window. He need to create the whole object.\n if (!this.properties.created) {\n this.create();\n } else {\n // Previous state closed. Open method can be called even the previous state is open,\n // for example updating the content of the modal object.\n if (!this.properties.open) {\n this.properties.open = true;\n\n // Restoring the previous open state: if the modal object has been closed\n // re-open it should preserve the state and the metrics.\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\n this.restoreState();\n }\n }\n\n // Maximize window only when the configuration is set and the device is not iOs or Android.\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n\n // In iOS we need to recalculate the size of the modal object because\n // iOS keyboard is a float div which can overlay the modal object.\n if (this.deviceProperties.isIOS) {\n this.iosSoftkeyboardOpened = false;\n }\n }\n\n if (!ContentManager.isEditorLoaded()) {\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.displayEditor();\n });\n this.contentManager.addListener(listener);\n } else {\n this.displayEditor();\n }\n }\n\n /**\n * Prepares and displays the editor in the modal.\n *\n * This method is responsible for displaying the MathType editor inside the modal container.\n *\n * For Moodle environments, it applies focus protection to prevent external scripts\n * from hijacking focus away from the editor while it's open. This is particularly\n * important in Moodle which may have its own focus management scripts.\n * @returns {void}\n */\n displayEditor() {\n if (this.contentManager.integrationModel.isMoodle) {\n protect(this.container, this.overlay, this.contentContainer);\n }\n\n // Initialize and open the editor using the contentManager.\n this.contentManager.onOpen(this);\n }\n\n /**\n * Closes the modal.\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\n * @returns {Promise} A promise that resolves when the modal is closed.\n */\n async close(trigger) {\n // Remove focus protection before closing\n unprotect(this.container);\n\n this.removeClass(\"wrs_maximized\");\n this.removeClass(\"wrs_minimized\");\n this.removeClass(\"wrs_stack\");\n this.addClass(\"wrs_closed\");\n this.saveModalProperties();\n this.unlockWebsiteScroll();\n this.properties.open = false;\n\n if (trigger) {\n try {\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\n toolbar: this.contentManager.toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\n }\n }\n\n Core.globalListeners.fire(\"onModalClose\", {});\n }\n\n /**\n * Closes modal window and destroys the object.\n */\n destroy() {\n // Remove focus protection before destroying\n unprotect(this.container);\n\n // Close modal window.\n this.close();\n // Remove listeners and destroy the object.\n this.removeListeners();\n this.overlay.remove();\n this.container.remove();\n // Reset properties to allow open again.\n this.properties.created = false;\n }\n\n /**\n * Sets the website scale to one.\n */\n // eslint-disable-next-line class-methods-use-this\n restoreWebsiteScale() {\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\n // Let the equal symbols in order to search and make meta's final content.\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\n const contentAttr = viewportelement.getAttribute(\"content\");\n // If it exists, we need to maintain old values and put our values.\n if (contentAttr) {\n const attrArray = contentAttr.split(\",\");\n let finalContentMeta = \"\";\n const oldAttrs = [];\n for (let i = 0; i < attrArray.length; i += 1) {\n let isAttrToUpdate = false;\n let j = 0;\n while (!isAttrToUpdate && j < contentAttrs.length) {\n if (attrArray[i].indexOf(contentAttrs[j])) {\n isAttrToUpdate = true;\n }\n j += 1;\n }\n\n if (!isAttrToUpdate) {\n oldAttrs.push(attrArray[i]);\n }\n }\n\n for (let i = 0; i < contentAttrs.length; i += 1) {\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\n finalContentMeta += i === 0 ? attr : `,${attr}`;\n }\n\n for (let i = 0; i < oldAttrs.length; i += 1) {\n finalContentMeta += `,${oldAttrs[i]}`;\n }\n viewportelement.setAttribute(\"content\", finalContentMeta);\n // It needs to set to empty because setAttribute refresh only when attribute is different.\n viewportelement.setAttribute(\"content\", \"\");\n viewportelement.setAttribute(\"content\", contentAttr);\n } else {\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\n viewportelement.removeAttribute(\"content\");\n }\n };\n\n if (!viewportmeta) {\n viewportmeta = document.createElement(\"meta\");\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n viewportmeta.remove();\n } else {\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n }\n }\n\n /**\n * Locks website scroll for mobile devices.\n */\n lockWebsiteScroll() {\n this.websiteBeforeLockParameters = {\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\n windowScrollX: window.scrollX,\n windowScrollY: window.scrollY,\n };\n }\n\n /**\n * Unlocks website scroll for mobile devices.\n */\n unlockWebsiteScroll() {\n if (this.websiteBeforeLockParameters) {\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\n const { windowScrollX } = this.websiteBeforeLockParameters;\n const { windowScrollY } = this.websiteBeforeLockParameters;\n window.scrollTo(windowScrollX, windowScrollY);\n this.websiteBeforeLockParameters = null;\n }\n }\n\n /**\n * Util function to known if browser is IE11.\n * @returns {Boolean} true if the browser is IE11. false otherwise.\n */\n // eslint-disable-next-line class-methods-use-this\n isIE11() {\n if (\n navigator.userAgent.search(\"Msie/\") >= 0 ||\n navigator.userAgent.search(\"Trident/\") >= 0 ||\n navigator.userAgent.search(\"Edge/\") >= 0\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns if the current language type is RTL.\n * @return {Boolean} true if current language is RTL. false otherwise.\n */\n isRTL() {\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\n return true;\n }\n return this.rtl;\n }\n\n /**\n * Adds a class to all modal ModalDialog DOM elements.\n * @param {String} className - Class name.\n */\n addClass(className) {\n Util.addClass(this.overlay, className);\n Util.addClass(this.titleBar, className);\n Util.addClass(this.overlay, className);\n Util.addClass(this.container, className);\n Util.addClass(this.contentContainer, className);\n Util.addClass(this.stackDiv, className);\n Util.addClass(this.minimizeDiv, className);\n Util.addClass(this.maximizeDiv, className);\n Util.addClass(this.wrapper, className);\n }\n\n /**\n * Remove a class from all modal DOM elements.\n * @param {String} className - Class name.\n */\n removeClass(className) {\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.titleBar, className);\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.container, className);\n Util.removeClass(this.contentContainer, className);\n Util.removeClass(this.stackDiv, className);\n Util.removeClass(this.minimizeDiv, className);\n Util.removeClass(this.maximizeDiv, className);\n Util.removeClass(this.wrapper, className);\n }\n\n /**\n * Create modal dialog for desktop.\n */\n createModalWindowDesktop() {\n this.addClass(\"wrs_modal_desktop\");\n this.stack();\n }\n\n /**\n * Create modal dialog for non android devices.\n */\n createModalWindowAndroid() {\n this.addClass(\"wrs_modal_android\");\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\n }\n\n /**\n * Create modal dialog for iOS devices.\n */\n createModalWindowIos() {\n this.addClass(\"wrs_modal_ios\");\n // Refresh the size when the orientation is changed.\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\n }\n\n /**\n * Restore previous state, position and size of previous stacked modal dialog.\n */\n restoreState() {\n if (this.properties.state === \"maximized\") {\n // Reset states for prevent return to stack state.\n this.maximize();\n } else if (this.properties.state === \"minimized\") {\n // Reset states for prevent return to stack state.\n this.properties.state = this.properties.previousState;\n this.properties.previousState = \"\";\n this.minimize();\n } else {\n this.stack();\n }\n }\n\n /**\n * Stacks the modal object.\n */\n stack() {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"stack\";\n this.removeClass(\"wrs_maximized\");\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n this.addClass(\"wrs_stack\");\n\n // Change maximize/minimize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n this.restoreModalProperties();\n\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\n this.setResizeButtonsVisibility();\n }\n\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n this.focus();\n }\n\n /**\n * Minimizes the modal object.\n */\n minimize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n this.title.style.cursor = \"pointer\";\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\n this.stack();\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n // Setting css to prevent important tag into css style.\n this.container.style.height = \"30px\";\n this.container.style.width = \"250px\";\n this.container.style.bottom = \"0px\";\n this.container.style.right = \"10px\";\n\n this.removeListeners();\n this.properties.previousState = this.properties.state;\n this.properties.state = \"minimized\";\n this.setResizeButtonsVisibility();\n this.minimizeDiv.title = StringManager.get(\"maximize\");\n\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.removeClass(\"wrs_stack\");\n } else {\n this.removeClass(\"wrs_maximized\");\n }\n this.addClass(\"wrs_minimized\");\n\n // Change minimize icon to maximize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n }\n }\n\n /**\n * Maximizes the modal object.\n */\n maximize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n if (this.properties.state !== \"maximized\") {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"maximized\";\n }\n // Don't permit resize on maximize mode.\n this.setResizeButtonsVisibility();\n\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.container.style.left = null;\n this.container.style.top = null;\n this.removeClass(\"wrs_stack\");\n }\n\n this.addClass(\"wrs_maximized\");\n\n // Change maximize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n // Set size to 80% screen with a max size.\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\n if (this.container.clientHeight > 700) {\n this.container.style.height = \"700px\";\n }\n if (this.container.clientWidth > 1200) {\n this.container.style.width = \"1200px\";\n }\n\n // Setting modal position in center on screen.\n const { innerHeight } = window;\n const { innerWidth } = window;\n const { offsetHeight } = this.container;\n const { offsetWidth } = this.container;\n const bottom = innerHeight / 2 - offsetHeight / 2;\n const right = innerWidth / 2 - offsetWidth / 2;\n\n this.setPosition(bottom, right);\n this.recalculateScale();\n this.recalculatePosition();\n this.recalculateSize();\n this.focus();\n }\n\n /**\n * Expand again the modal object from a minimized state.\n */\n reExpand() {\n if (this.properties.state === \"minimized\") {\n if (this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n this.stack();\n }\n this.title.style.cursor = \"\";\n }\n }\n\n /**\n * Sets modal size.\n * @param {Number} height - Height of the ModalDialog\n * @param {Number} width - Width of the ModalDialog.\n */\n setSize(height, width) {\n this.container.style.height = `${height}px`;\n this.container.style.width = `${width}px`;\n this.recalculateSize();\n }\n\n /**\n * Sets modal position using bottom and right style attributes.\n * @param {number} bottom - bottom attribute.\n * @param {number} right - right attribute.\n */\n setPosition(bottom, right) {\n this.container.style.bottom = `${bottom}px`;\n this.container.style.right = `${right}px`;\n }\n\n /**\n * Saves position and size parameters of and open ModalDialog. This attributes\n * are needed to restore it on re-open.\n */\n saveModalProperties() {\n // Saving values of modal only when modal is in stack state.\n if (this.properties.state === \"stack\") {\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\n this.properties.position.right = parseInt(this.container.style.right, 10);\n this.properties.size.width = parseInt(this.container.style.width, 10);\n this.properties.size.height = parseInt(this.container.style.height, 10);\n }\n }\n\n /**\n * Restore ModalDialog position and size parameters.\n */\n restoreModalProperties() {\n if (this.properties.state === \"stack\") {\n // Restoring Bottom and Right values from last modal.\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\n // Restoring Height and Left values from last modal.\n this.setSize(this.properties.size.height, this.properties.size.width);\n }\n }\n\n /**\n * Sets the modal dialog initial size.\n */\n recalculateSize() {\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\n }\n\n /**\n * Enable or disable visibility of resize buttons in modal window depend on state.\n */\n setResizeButtonsVisibility() {\n if (this.properties.state === \"stack\") {\n this.resizerTL.style.visibility = \"visible\";\n this.resizerBR.style.visibility = \"visible\";\n } else {\n this.resizerTL.style.visibility = \"hidden\";\n this.resizerBR.style.visibility = \"hidden\";\n }\n }\n\n /**\n * Makes an object draggable adding mouse and touch events.\n */\n addListeners() {\n // Button events (maximize, minimize, stack and close).\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\n this.maximizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n }\n },\n true,\n );\n this.stackDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.minimizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.closeDiv.addEventListener(\"keypress\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n });\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\n\n // Overlay events (close).\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n // Mouse events.\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\n // Key events.\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\n }\n\n /**\n * Removes draggable events from an object.\n */\n removeListeners() {\n // Mouse events.\n Util.removeEvent(window, \"mousedown\", this.startDrag);\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\n Util.removeEvent(window, \"mousemove\", this.drag);\n Util.removeEvent(window, \"resize\", this.onWindowResize);\n // Key events.\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\n }\n\n /**\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\n * @param {MouseEvent} mouseEvent - Mouse event.\n * @return {Object} With the X and Y coordinates.\n */\n // eslint-disable-next-line class-methods-use-this\n eventClient(mouseEvent) {\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\n const client = {\n X: mouseEvent.changedTouches[0].clientX,\n Y: mouseEvent.changedTouches[0].clientY,\n };\n return client;\n }\n const client = {\n X: mouseEvent.clientX,\n Y: mouseEvent.clientY,\n };\n return client;\n }\n\n /**\n * Start drag function: set the object dragDataObject with the draggable\n * object offsets coordinates.\n * when drag starts (on touchstart or mousedown events).\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\n */\n startDrag(mouseEvent) {\n if (this.properties.state === \"minimized\") {\n return;\n }\n if (mouseEvent.target === this.title) {\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\n // Save first click mouse point on screen.\n this.dragDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Reset last drag position when start drag.\n this.lastDrag = {\n x: \"0px\",\n y: \"0px\",\n };\n // Init right and bottom values for window modal if it isn't exist.\n if (this.container.style.right === \"\") {\n this.container.style.right = \"0px\";\n }\n if (this.container.style.bottom === \"\") {\n this.container.style.bottom = \"0px\";\n }\n\n // Needed for IE11 for apply disabled mouse events on editor because\n // internet explorer needs a dynamic object to apply this property.\n if (this.isIE11()) {\n // this.iframe.style['position'] = 'relative';\n }\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n // Obtain screen limits for prevent overflow.\n this.limitWindow = this.getLimitWindow();\n }\n }\n }\n\n /**\n * Updates dragDataObject with the draggable object coordinates when\n * the draggable object is being moved.\n * @param {MouseEvent} mouseEvent - The mouse event.\n */\n drag(mouseEvent) {\n if (this.dragDataObject) {\n mouseEvent.preventDefault();\n // Calculate max and min between actual mouse position and limit of screeen.\n // It restric the movement of modal into window.\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\n // Subtract limit with first position to obtain relative pixels increment\n // to the anchor point.\n const dragX = `${limitX - this.dragDataObject.x}px`;\n const dragY = `${limitY - this.dragDataObject.y}px`;\n // Save last valid position of modal before window overflow.\n this.lastDrag = {\n x: dragX,\n y: dragY,\n };\n // This move modal with hardware acceleration.\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\n }\n if (this.resizeDataObject) {\n const { innerWidth } = window;\n const { innerHeight } = window;\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\n if (limitX < 0) {\n limitX = 0;\n }\n\n if (limitY < 0) {\n limitY = 0;\n }\n\n let scaleMultiplier;\n if (this.leftScale) {\n scaleMultiplier = -1;\n } else {\n scaleMultiplier = 1;\n }\n\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\n if (!this.leftScale) {\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\n } else {\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\n this.container.style.width = \"580px\";\n }\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\n } else {\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\n this.container.style.height = \"338px\";\n }\n }\n this.recalculateScale();\n this.recalculatePosition();\n }\n }\n\n /**\n * Returns the boundaries of actual window to limit modal movement.\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\n */\n getLimitWindow() {\n // Obtain dimensions of window page.\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight;\n\n // Calculate relative position of mouse point into window.\n const { offsetHeight } = this.container;\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\n const contStyleRight = parseInt(this.container.style.right, 10);\n\n const { pageXOffset } = window;\n const dragY = this.dragDataObject.y;\n const dragX = this.dragDataObject.x;\n\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\n\n // Calculate limits with sizes of window, modal and mouse position.\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\n const minPointer = { x: minPointerX, y: minPointerY };\n const maxPointer = { x: maxPointerX, y: maxPointerY };\n return { minPointer, maxPointer };\n }\n\n /**\n * Returns the scrollbar width size of browser\n * @returns {Number} The scrollbar width.\n */\n // eslint-disable-next-line class-methods-use-this\n getScrollBarWidth() {\n // Create a paragraph with full width of page.\n const inner = document.createElement(\"p\");\n inner.style.width = \"100%\";\n inner.style.height = \"200px\";\n\n // Create a hidden div to compare sizes.\n const outer = document.createElement(\"div\");\n outer.style.position = \"absolute\";\n outer.style.top = \"0px\";\n outer.style.left = \"0px\";\n outer.style.visibility = \"hidden\";\n outer.style.width = \"200px\";\n outer.style.height = \"150px\";\n outer.style.overflow = \"hidden\";\n outer.appendChild(inner);\n\n document.body.appendChild(outer);\n const widthOuter = inner.offsetWidth;\n\n // Change type overflow of paragraph for measure scrollbar.\n outer.style.overflow = \"scroll\";\n let widthInner = inner.offsetWidth;\n\n // If measure is the same, we compare with internal div.\n if (widthOuter === widthInner) {\n widthInner = outer.clientWidth;\n }\n document.body.removeChild(outer);\n\n return widthOuter - widthInner;\n }\n\n /**\n * Set the dragDataObject to null.\n */\n stopDrag() {\n // Due to we have multiple events that call this function, we need only to execute\n // the next modifiers one time,\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\n if (this.dragDataObject || this.resizeDataObject) {\n // If modal doesn't change, it's not necessary to set position with interpolation.\n this.container.style.transform = \"\";\n if (this.dragDataObject) {\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\n }\n // We make focus on editor after drag modal windows to prevent lose focus.\n this.focus();\n // Restore mouse events on iframe.\n // this.iframe.style['pointer-events'] = 'auto';\n document.body.style[\"user-select\"] = \"\";\n // Restore static state of iframe if we use Internet Explorer.\n if (this.isIE11()) {\n // this.iframe.style['position'] = null;\n }\n // Active text select event.\n Util.removeClass(document.body, \"wrs_noselect\");\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\n }\n this.dragDataObject = null;\n this.resizeDataObject = null;\n this.initialWidth = null;\n this.leftScale = null;\n }\n\n /**\n * Recalculates scale for modal when resize browser window.\n */\n onWindowResize() {\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n }\n\n /**\n * Triggers keyboard events:\n * - Tab key tab to go to submit button.\n * - Esc key to close the modal dialog.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Popupmessage is not oppened.\n if (this.popup.overlayWrapper.style.display !== \"block\") {\n // Code to detect Esc event\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n if (this.properties.open) {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.cancelButton) {\n this.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.submitButton) {\n this.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n }\n } else {\n // Popupmessage oppened.\n this.popup.onKeyDown(keyboardEvent);\n }\n }\n }\n\n /**\n * Recalculating position for modal dialog when the browser is resized.\n */\n recalculatePosition() {\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\n if (parseInt(this.container.style.right, 10) < 0) {\n this.container.style.right = \"0px\";\n }\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\n if (parseInt(this.container.style.bottom, 10) < 0) {\n this.container.style.bottom = \"0px\";\n }\n }\n\n /**\n * Recalculating scale for modal when the browser is resized.\n */\n recalculateScale() {\n let sizeModified = false;\n\n if (parseInt(this.container.style.width, 10) > 580) {\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\n sizeModified = true;\n } else {\n this.container.style.width = \"580px\";\n sizeModified = true;\n }\n\n if (parseInt(this.container.style.height, 10) > 338) {\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\n sizeModified = true;\n } else {\n this.container.style.height = \"338px\";\n sizeModified = true;\n }\n\n if (sizeModified) {\n this.recalculateSize();\n }\n }\n\n /**\n * Recalculating width of browser scroll bar.\n */\n recalculateScrollBar() {\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\n if (this.hasScrollBar) {\n this.scrollbarWidth = this.getScrollBarWidth();\n } else {\n this.scrollbarWidth = 0;\n }\n }\n\n /**\n * Hide soft keyboards on iOS devices.\n */\n // eslint-disable-next-line class-methods-use-this\n hideKeyboard() {\n // iOS keyboard can't be detected or hide directly from JavaScript.\n // So, this method simulates that user focus a text input and blur\n // the selection.\n const inputField = document.createElement(\"input\");\n this.container.appendChild(inputField);\n inputField.focus();\n inputField.blur();\n // Is removed to not see it.\n inputField.remove();\n }\n\n /**\n * Focus to contentManager object.\n */\n focus() {\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\n this.contentManager.onFocus();\n }\n }\n\n /**\n * Returns true when the device is on portrait mode.\n */\n // eslint-disable-next-line class-methods-use-this\n portraitMode() {\n return window.innerHeight > window.innerWidth;\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is opened.\n */\n handleOpenedIosSoftkeyboard() {\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\n if (this.portraitMode()) {\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\n }\n }\n this.iosSoftkeyboardOpened = true;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is closed.\n */\n handleClosedIosSoftkeyboard() {\n this.iosSoftkeyboardOpened = false;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Change container sizes when orientation is changed on iOS.\n */\n orientationChangeIosSoftkeyboard() {\n if (this.iosSoftkeyboardOpened) {\n if (this.portraitMode()) {\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\n }\n } else {\n this.wrapper.style.flexGrow = \"1\";\n }\n }\n\n /**\n * Change container sizes when orientation is changed on Android.\n */\n orientationChangeAndroidSoftkeyboard() {\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Set iframe container height.\n * @param {Number} height - New height.\n */\n setContainerHeight(height) {\n this.iosDivHeight = height;\n this.wrapper.style.height = height;\n }\n\n /**\n * Check content of editor before close action.\n */\n showPopUpMessage() {\n if (this.properties.state === \"minimized\") {\n this.stack();\n }\n this.popup.show();\n }\n\n /**\n * Sets the title of the modal dialog.\n * @param {String} title - Modal dialog title.\n */\n setTitle(title) {\n this.title.innerHTML = title;\n }\n\n /**\n * Returns the id of an element, adding the instance number to\n * the element class name:\n * className --> className[idNumber]\n * @param {String} className - The element class name.\n * @returns {String} A string appending the instance id to the className.\n */\n getElementId(className) {\n return `${className}[${this.instanceId}]`;\n }\n}\n","/* eslint-disable */\nvar polyfills;\nexport default polyfills;\n\n// Polyfills.\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\nif (!String.prototype.codePointAt) {\n (function () {\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\n var codePointAt = function (position) {\n if (this == null) {\n throw TypeError();\n }\n var string = String(this);\n var size = string.length;\n // `ToInteger`\n var index = position ? Number(position) : 0;\n if (index != index) {\n // better `isNaN`\n index = 0;\n }\n // Account for out-of-bounds indices:\n if (index < 0 || index >= size) {\n return undefined;\n }\n // Get the first code unit\n var first = string.charCodeAt(index);\n var second;\n if (\n // check if itโ€™s the start of a surrogate pair\n first >= 0xd800 &&\n first <= 0xdbff && // high surrogate\n size > index + 1 // there is a next code unit\n ) {\n second = string.charCodeAt(index + 1);\n if (second >= 0xdc00 && second <= 0xdfff) {\n // low surrogate\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\n }\n }\n return first;\n };\n if (Object.defineProperty) {\n Object.defineProperty(String.prototype, \"codePointAt\", {\n value: codePointAt,\n configurable: true,\n writable: true,\n });\n } else {\n String.prototype.codePointAt = codePointAt;\n }\n })();\n}\n\n// Object.assign polyfill.\nif (typeof Object.assign != \"function\") {\n // Must be writable: true, enumerable: false, configurable: true\n Object.defineProperty(Object, \"assign\", {\n value: function assign(target, varArgs) {\n // .length of function is 2\n \"use strict\";\n if (target == null) {\n // TypeError if undefined or null\n throw new TypeError(\"Cannot convert undefined or null to object\");\n }\n\n var to = Object(target);\n\n for (var index = 1; index < arguments.length; index++) {\n var nextSource = arguments[index];\n\n if (nextSource != null) {\n // Skip over if undefined or null\n for (var nextKey in nextSource) {\n // Avoid bugs when hasOwnProperty is shadowed\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\n to[nextKey] = nextSource[nextKey];\n }\n }\n }\n }\n return to;\n },\n writable: true,\n configurable: true,\n });\n}\n\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\nif (!Array.prototype.includes) {\n Object.defineProperty(Array.prototype, \"includes\", {\n value: function (searchElement, fromIndex) {\n if (this == null) {\n throw new TypeError('\"this\" s null or is not defined');\n }\n\n // 1. Let O be ? ToObject(this value).\n var o = Object(this);\n\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\n var len = o.length >>> 0;\n\n // 3. if len is 0, return false.\n if (len === 0) {\n return false;\n }\n\n // 4. Let n be ? ToInteger(fromIndex).\n // (if fromIndex is undefinedo, this step generates the value 0.)\n var n = fromIndex | 0;\n\n // 5. if n โ‰ฅ 0, then\n // a. Let k be n.\n // 6. Else n < 0,\n // a. Let k be len + n.\n // b. if k < 0, let k be 0.\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\n\n function sameValueZero(x, y) {\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\n }\n\n // 7. Repeat while k < len\n while (k < len) {\n // a. let element k be the result of ? Get(O, ! ToString(k)).\n // b. if SameValueZero(searchElement, elementK) is true, return true.\n if (sameValueZero(o[k], searchElement)) {\n return true;\n }\n // c. Increase k by 1.\n k++;\n }\n\n // 8. Return false\n return false;\n },\n });\n}\n\nif (!String.prototype.includes) {\n String.prototype.includes = function (search, start) {\n \"use strict\";\n\n if (search instanceof RegExp) {\n throw TypeError(\"first argument must not be a RegExp\");\n }\n if (start === undefined) {\n start = 0;\n }\n return this.indexOf(search, start) !== -1;\n };\n}\n\nif (!String.prototype.startsWith) {\n Object.defineProperty(String.prototype, \"startsWith\", {\n value: function (search, rawPos) {\n var pos = rawPos > 0 ? rawPos | 0 : 0;\n return this.substring(pos, pos + search.length) === search;\n },\n });\n}\n","import Parser from \"./parser\";\nimport Util from \"./util\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport CustomEditors from \"./customeditors\";\nimport Configuration from \"./configuration\";\nimport jsProperties from \"./jsvariables\";\nimport Event from \"./event\";\nimport Listeners from \"./listeners\";\nimport Image from \"./image\";\nimport ServiceProvider from \"./serviceprovider\";\nimport ModalDialog from \"./modal\";\nimport Telemeter from \"./telemeter\";\nimport \"./polyfills\";\nimport \"../styles/styles.css\";\n\n/**\n * @typedef {Object} CoreProperties\n * @property {ServiceProviderProperties} serviceProviderProperties\n * - The ServiceProvider class properties. *\n */\nexport default class Core {\n /**\n * @classdesc\n * This class represents MathType integration Core, managing the following:\n * - Integration initialization.\n * - Event managing.\n * - Insertion of formulas into the edit area.\n * ```js\n * let core = new Core();\n * core.addListener(listener);\n * core.language = 'en';\n *\n * // Initializing Core class.\n * core.init(configurationService);\n * ```\n * @constructs\n * Core constructor.\n * @param {CoreProperties}\n */\n constructor(coreProperties) {\n /**\n * Language. Needed for accessibility and locales. 'en' by default.\n * @type {String}\n */\n this.language = \"en\";\n\n /**\n * Edit mode, 'images' by default. Admits the following values:\n * - images\n * - latex\n * @type {String}\n */\n this.editMode = \"images\";\n\n /**\n * Modal dialog instance.\n * @type {ModalDialog}\n */\n this.modalDialog = null;\n\n /**\n * The instance of {@link CustomEditors}. By default\n * the only custom editor is the Chemistry editor.\n * @type {CustomEditors}\n */\n this.customEditors = new CustomEditors();\n\n /**\n * Chemistry editor.\n * @type {CustomEditor}\n */\n const chemEditorParams = {\n name: \"Chemistry\",\n toolbar: \"chemistry\",\n icon: \"chem.png\",\n confVariable: \"chemEnabled\",\n title: \"ChemType\",\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\n };\n\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @typedef IntegrationEnvironment\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\n * @property {String} IntegrationEnvironment.version - Integration version.\n *\n */\n\n /**\n * The environment properties object.\n * @type {IntegrationEnvironment}\n */\n this.environment = {};\n\n /**\n * @typedef EditionProperties\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\n * False otherwise.\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\n * Null if the formula is new.\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\n * @property {Range} editionProperties.range - The range that contains the image element.\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\n */\n\n /**\n * The properties of the current edition process.\n * @type {EditionProperties}\n */\n this.editionProperties = {};\n\n this.editionProperties.isNewElement = true;\n this.editionProperties.temporalImage = null;\n this.editionProperties.latexRange = null;\n this.editionProperties.range = null;\n this.editionProperties.editionStartTime = null;\n\n /**\n * The {@link IntegrationModel} instance.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n\n /**\n * The {@link ContentManager} instance.\n * @type {ContentManager}\n */\n this.contentManager = null;\n\n /**\n * The current browser.\n * @type {String}\n */\n this.browser = (() => {\n const ua = navigator.userAgent;\n let browser = \"none\";\n if (ua.search(\"Edge/\") >= 0) {\n browser = \"EDGE\";\n } else if (ua.search(\"Chrome/\") >= 0) {\n browser = \"CHROME\";\n } else if (ua.search(\"Trident/\") >= 0) {\n browser = \"IE\";\n } else if (ua.search(\"Firefox/\") >= 0) {\n browser = \"FIREFOX\";\n } else if (ua.search(\"Safari/\") >= 0) {\n browser = \"SAFARI\";\n }\n return browser;\n })();\n\n /**\n * Plugin listeners.\n * @type {Array.}\n */\n this.listeners = new Listeners();\n\n /**\n * Service provider properties.\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = {};\n if (\"serviceProviderProperties\" in coreProperties) {\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\n } else {\n throw new Error(\"serviceProviderProperties property missing.\");\n }\n }\n\n /**\n * Static property.\n * Core listeners.\n * @private\n * @type {Listeners}\n */\n static get globalListeners() {\n return Core._globalListeners;\n }\n\n /**\n * Static property setter.\n * Set core listeners.\n * @param {Listeners} value - The property value.\n * @ignore\n */\n static set globalListeners(value) {\n Core._globalListeners = value;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * True when Core.init was called. Otherwise, false.\n * @private\n * @type {Boolean}\n */\n static get initialized() {\n return Core._initialized;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\n * @ignore\n */\n static set initialized(value) {\n Core._initialized = value;\n }\n\n /**\n * Sets the {@link Core.integrationModel} property.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link Core.environment} property.\n * @param {IntegrationEnvironment} integrationEnvironment -\n * The {@link IntegrationEnvironment} object.\n */\n setEnvironment(integrationEnvironment) {\n if (\"editor\" in integrationEnvironment) {\n this.environment.editor = integrationEnvironment.editor;\n }\n if (\"mode\" in integrationEnvironment) {\n this.environment.mode = integrationEnvironment.mode;\n }\n if (\"version\" in integrationEnvironment) {\n this.environment.version = integrationEnvironment.version;\n }\n }\n\n /**\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\n * @returns {Object} headers - key value headers.\n */\n setHeaders(headers) {\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\n Configuration.set(\"customHeaders\", headerObject);\n }\n\n /**\n * Returns the current {@link ModalDialog} instance.\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\n */\n getModalDialog() {\n return this.modalDialog;\n }\n\n /**\n * Inits the {@link Core} class, doing the following:\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\n * - Updates {@link Configuration} class with the previous configuration properties.\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\n * - Loads language strings.\n * - Fires onLoad event.\n * @param {Object} serviceParameters - Service parameters.\n */\n init() {\n if (!Core.initialized) {\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\n const jsonConfiguration = JSON.parse(jsConfiguration);\n Configuration.addConfiguration(jsonConfiguration);\n // Adding JavaScript (not backend) configuration variables.\n Configuration.addConfiguration(jsProperties);\n // Fire 'onLoad' event:\n // All integration must listen this event in order to know if the plugin\n // has been properly loaded.\n StringManager.language = this.language;\n this.listeners.fire(\"onLoad\", {});\n });\n\n ServiceProvider.addListener(serviceProviderListener);\n ServiceProvider.init(this.serviceProviderProperties);\n\n Core.initialized = true;\n } else {\n // Case when there are more than two editor instances.\n // After the first editor all the other editors don't need to load any file or service.\n this.listeners.fire(\"onLoad\", {});\n }\n }\n\n /**\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\n * @param {Listener} listener - The listener object.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Adds the global {@link Listener} instance to {@link Core} class.\n * @param {Listener} listener - The event listener to be added.\n * @static\n */\n static addGlobalListener(listener) {\n Core.globalListeners.add(listener);\n }\n\n beforeUpdateFormula(mathml, wirisProperties) {\n /**\n * This event is fired before updating the formula.\n * @type {Object}\n * @property {String} mathml - MathML to be transformed.\n * @property {String} editMode - Edit mode.\n * @property {Object} wirisProperties - Extra attributes for the formula.\n * @property {String} language - Formula language.\n */\n const beforeUpdateEvent = new Event();\n\n beforeUpdateEvent.mathml = mathml;\n\n // Cloning wirisProperties object\n // We don't want wirisProperties object modified.\n beforeUpdateEvent.wirisProperties = {};\n\n if (wirisProperties != null) {\n Object.keys(wirisProperties).forEach((attr) => {\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\n });\n }\n\n // Read only.\n beforeUpdateEvent.language = this.language;\n beforeUpdateEvent.editMode = this.editMode;\n\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n return {\n mathml: beforeUpdateEvent.mathml,\n wirisProperties: beforeUpdateEvent.wirisProperties,\n };\n }\n\n /**\n * Converts a MathML into it's correspondent image and inserts the image is\n * inserted in a HTMLElement target by creating\n * a new image or updating an existing one.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\n * @param {Window} windowTarget - The window element where the editable content is.\n * @param {String} mathml - The MathML.\n * @param {Array.} wirisProperties - The extra attributes for the formula.\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n /**\n * It is the object with the information of the node or latex to insert.\n * @typedef ReturnObject\n * @property {Node} [node] - The DOM node to insert.\n * @property {String} [latex] - The latex to insert.\n */\n const returnObject = {};\n\n if (!mathml) {\n this.insertElementOnSelection(null, focusElement, windowTarget);\n } else if (this.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n // this.integrationModel.getNonLatexNode is an integration wrapper\n // to have special behaviours for nonLatex.\n // Not all the integrations have special behaviours for nonLatex.\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.latex = returnObject.latex;\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\n } else {\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n }\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n } else {\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\n\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n }\n\n return returnObject;\n }\n\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\n /**\n * This event is fired after update the formula.\n * @type {Event}\n * @param {String} editMode - edit mode.\n * @param {Object} windowTarget - target window.\n * @param {Object} focusElement - target element to be focused after update.\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\n */\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.node = node;\n afterUpdateEvent.latex = latex;\n\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n return {};\n }\n\n /**\n * Sets the caret after a given Node and set the focus to the owner document.\n * @param {Node} node - The Node element.\n */\n placeCaretAfterNode(node) {\n if (node === null) return;\n\n this.integrationModel.getSelection();\n const nodeDocument = node.ownerDocument;\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\n const range = nodeDocument.createRange();\n range.setStartAfter(node);\n range.collapse(true);\n const selection = nodeDocument.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n nodeDocument.body.focus();\n }\n }\n\n /**\n * Replaces a Selection object with an HTMLElement.\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\n * @param {Window} windowTarget - The window target.\n */\n insertElementOnSelection(element, focusElement, windowTarget) {\n let mathmlOrigin = null;\n if (this.editionProperties.isNewElement) {\n if (element) {\n if (focusElement.type === \"textarea\") {\n Util.updateTextArea(focusElement, element.textContent);\n } else if (document.selection && document.getSelection === 0) {\n let range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n\n if (!(\"parentElement\" in range)) {\n windowTarget.document.execCommand(\"delete\", false);\n range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n }\n\n if (\"parentElement\" in range) {\n const temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\n temporalObject.parentNode.replaceChild(element, temporalObject);\n } else {\n // IE9 fix: parentNode() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML(Util.createObjectCode(element));\n }\n }\n } else {\n let range = null;\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n if (this.editionProperties.range) {\n ({ range } = this.editionProperties);\n this.editionProperties.range = null;\n } else {\n const editorSelection = this.integrationModel.getSelection();\n range = editorSelection.getRangeAt(0);\n }\n\n // Delete if something was surrounded.\n range.deleteContents();\n\n let node = range.startContainer;\n const position = range.startOffset;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n node = node.splitText(position);\n node.parentNode.insertBefore(element, node);\n } else if (node.nodeType === 1) {\n // ELEMENT_NODE.\n node.insertBefore(element, node.childNodes[position]);\n }\n\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n focusElement.focus();\n } else {\n const editorSelection = this.integrationModel.getSelection();\n editorSelection.removeAllRanges();\n\n if (this.editionProperties.range) {\n const { range } = this.editionProperties;\n this.editionProperties.range = null;\n editorSelection.addRange(range);\n }\n }\n } else if (this.editionProperties.latexRange) {\n if (document.selection && document.getSelection === 0) {\n this.editionProperties.isNewElement = true;\n this.editionProperties.latexRange.select();\n this.insertElementOnSelection(element, focusElement, windowTarget);\n } else {\n this.editionProperties.latexRange.deleteContents();\n this.editionProperties.latexRange.insertNode(element);\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n let item;\n // Wrapper for some integrations that can have special behaviours to show latex.\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n item = this.integrationModel.getSelectedItem(focusElement, false);\n } else {\n item = Util.getSelectedItemOnTextarea(focusElement);\n }\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\n } else {\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\n if (element && element.nodeName.toLowerCase() === \"img\") {\n // Editor empty, formula has been erased on edit.\n // There are editors (e.g: CKEditor) that put attributes in images.\n // We don't allow that behaviour in our images.\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\n // Clone is needed to maintain event references to temporalImage.\n Image.clone(element, this.editionProperties.temporalImage);\n } else {\n this.editionProperties.temporalImage.remove();\n }\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const mathml = element?.dataset?.mathml;\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove the desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n }\n\n /**\n * Opens a modal dialog containing MathType editor..\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\n */\n openModalDialog(target, isIframe) {\n // Count the time since the editor is open\n this.editionProperties.editionStartTime = Date.now();\n\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\n this.editMode = \"images\";\n\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n try {\n if (isIframe) {\n // Is needed focus the target first.\n target.contentWindow.focus();\n const selection = target.contentWindow.getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n } else {\n // Is needed focus the target first.\n target.focus();\n const selection = getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n }\n } catch (e) {\n this.editionProperties.range = null;\n }\n\n if (isIframe === undefined) {\n isIframe = true;\n }\n\n this.editionProperties.latexRange = null;\n\n if (target) {\n let selectedItem;\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\n } else {\n selectedItem = Util.getSelectedItem(target, isIframe);\n }\n\n // Check LaTeX if and only if the node is a text node (nodeType==3).\n if (selectedItem) {\n // Case when image was selected and button pressed.\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\n this.editionProperties.temporalImage = selectedItem.node;\n this.editionProperties.isNewElement = false;\n } else if (selectedItem.node.nodeType === 3) {\n // If it's a text node means that editor is working with LaTeX.\n if (this.integrationModel.getMathmlFromTextNode) {\n // If integration has this function it isn't set range due to we don't\n // know if it will be put into a textarea as a text or image.\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (mathml) {\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n }\n } else {\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (latexResult) {\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n const windowTarget = isIframe ? target.contentWindow : window;\n\n if (target.tagName.toLowerCase() !== \"textarea\") {\n if (document.selection) {\n let leftOffset = 0;\n let previousNode = latexResult.startNode.previousSibling;\n\n while (previousNode) {\n leftOffset += Util.getNodeLength(previousNode);\n previousNode = previousNode.previousSibling;\n }\n\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\n } else {\n this.editionProperties.latexRange = windowTarget.document.createRange();\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\n }\n }\n }\n }\n }\n } else if (target.tagName.toLowerCase() === \"textarea\") {\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\n this.editMode = \"latex\";\n }\n }\n\n // Setting an object with the editor parameters.\n // Editor parameters can be customized in several ways:\n // 1 - editorAttributes: Contains the default editor attributes,\n // usually the metrics in a comma separated string. Always exists.\n // 2 - editorParameters: Object containing custom editor parameters.\n // These parameters are defined in the backend. So they affects all integration instances.\n\n // The backend send the default editor attributes in a coma separated\n // with the following structure: key1=value1,key2=value2...\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\n const defaultEditorAttributes = {};\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\n const key = tempAttribute[0];\n const value = tempAttribute[1];\n defaultEditorAttributes[key] = value;\n }\n // Custom editor parameters.\n const editorAttributes = {\n language: this.language, // Default language value\n };\n // Editor parameters in backend, usually configuration.ini.\n const serverEditorParameters = Configuration.get(\"editorParameters\");\n // Editor parameters through JavaScript configuration.\n const clientEditorParameters = this.integrationModel.editorParameters;\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\n\n // Now, update backwards: if user has set a custom language, pass that back to core properties\n this.language = editorAttributes.language;\n StringManager.language = this.language;\n\n editorAttributes.rtl = this.integrationModel.rtl;\n\n const customHeaders = Configuration.get(\"customHeaders\");\n // This is not being used in the code, we are keeping it just in case it's needed.\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\n editorAttributes.customHeaders =\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\n\n const contentManagerAttributes = {};\n contentManagerAttributes.editorAttributes = editorAttributes;\n contentManagerAttributes.language = this.language;\n contentManagerAttributes.customEditors = this.customEditors;\n contentManagerAttributes.environment = this.environment;\n\n if (this.modalDialog == null) {\n this.modalDialog = new ModalDialog(editorAttributes);\n this.contentManager = new ContentManager(contentManagerAttributes);\n // When an instance of ContentManager is created we need to wait until\n // the ContentManager is ready by listening 'onLoad' event.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n });\n this.contentManager.addListener(listener);\n this.contentManager.init();\n this.modalDialog.setContentManager(this.contentManager);\n this.contentManager.setModalDialogInstance(this.modalDialog);\n } else {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n }\n this.contentManager.setIntegrationModel(this.integrationModel);\n this.modalDialog.open();\n }\n\n /**\n * Returns the {@link CustomEditors} instance.\n * @return {CustomEditors} The current {@link CustomEditors} instance.\n */\n getCustomEditors() {\n return this.customEditors;\n }\n}\n\n/**\n * Core static listeners.\n * @type {Listeners}\n * @private\n */\nCore._globalListeners = new Listeners();\n\n/**\n * Resources state. Says if they were loaded or not.\n * @type {Boolean}\n * @private\n */\nCore._initialized = false;\n","// eslint-disable-next-line no-unused-vars, import/named\nimport Core from \"./core.src\";\nimport Image from \"./image\";\nimport Listeners from \"./listeners\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Telemeter from \"./telemeter\";\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\n\n/**\n * @typedef {Object} IntegrationModelProperties\n * @property {string} configurationService - Configuration service path.\n * This parameter is needed to determine all services paths.\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\n * @property {string} integrationModelProperties.scriptName - Integration script name.\n * Usually the name of the integration script.\n * @property {Object} integrationModelProperties.environment - integration environment properties.\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\n * callback method arguments.\n * @property {string} [integrationModelProperties.version] - integration version number.\n * @property {Object} [integrationModelProperties.editorObject] - object containing\n * the integration editor instance.\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\n * false otherwise.\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\n * - The service parameters.\n * @property {Object} [integrationModelProperties.integrationParameters]\n * - Overwritten integration parameters.\n */\n\nexport default class IntegrationModel {\n /**\n * @classdesc\n * This class represents an integration model, allowing the integration script to\n * communicate with Core class. Each integration must extend this class.\n * @constructs\n * @param {IntegrationModelProperties} integrationModelProperties\n */\n constructor(integrationModelProperties) {\n /**\n * Language. Needed for accessibility and locales. English by default.\n */\n this.language = \"en\";\n\n /**\n * Service parameters\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\n\n /**\n * Configuration service path. The integration service is needed by Core class to\n * load all the backend configuration into the frontend and also to create the paths\n * of all services (all services lives in the same route). Mandatory property.\n */\n this.configurationService = \"\";\n if (\"configurationService\" in integrationModelProperties) {\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\n integrationModelProperties.configurationService,\n ]);\n }\n\n /**\n * Plugin version. Needed to stats and caching.\n * @type {string}\n */\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\n\n /**\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\n * Mandatory property.\n */\n this.target = null;\n if (\"target\" in integrationModelProperties) {\n this.target = integrationModelProperties.target;\n } else {\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\n }\n\n /**\n * Integration script name. Needed to know the plugin path.\n */\n if (\"scriptName\" in integrationModelProperties) {\n this.scriptName = integrationModelProperties.scriptName;\n }\n\n /**\n * Object containing the arguments needed by the callback function.\n */\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\n\n /**\n * Contains information about the integration environment:\n * like the name of the editor, the version, etc.\n */\n this.environment = integrationModelProperties.environment ?? {};\n\n /**\n * Indicates if the DOM target is - or not - and iframe.\n */\n this.isIframe = false;\n if (this.target != null) {\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Instance of the integration editor object. Usually the entry point to access the API\n * of a HTML editor.\n */\n this.editorObject = integrationModelProperties.editorObject ?? null;\n\n /**\n * Specifies if the direction of the text is RTL. false by default.\n */\n this.rtl = integrationModelProperties.rtl ?? false;\n\n /**\n * Specifies if the integration model exposes the locale strings. false by default.\n */\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\n\n /**\n * Specify if editor will open in hand mode only\n */\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\n\n /**\n * Indicates if an image is selected. Needed to resize the image to the original size in case\n * the image is resized.\n * @type {boolean}\n */\n this.temporalImageResizing = false;\n\n /**\n * The Core class instance associated to the integration model.\n * @type {Core}\n */\n this.core = null;\n\n /**\n * Integration model listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n // Parameters overwrite.\n if (\"integrationParameters\" in integrationModelProperties) {\n IntegrationModel.integrationParameters.forEach((parameter) => {\n if (parameter in integrationModelProperties.integrationParameters) {\n // Don't add empty parameters.\n const value = integrationModelProperties.integrationParameters[parameter];\n if (Object.keys(value).length !== 0) {\n this[parameter] = value;\n }\n }\n });\n }\n }\n\n /**\n * Init function. Usually called from the integration side once the core.js file is loaded.\n */\n init() {\n // Check if language is an object and select the property necessary\n this.language = this.getLanguage();\n\n // We need to wait until Core class is loaded ('onLoad' event) before\n // call the callback method.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.callbackFunction(this.callbackMethodArguments);\n });\n\n // Backwards compatibility.\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\n const uri = this.serviceProviderProperties.URI;\n const server = ServiceProvider.getServerLanguageFromService(uri);\n this.serviceProviderProperties.server = server;\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\n this.serviceProviderProperties.URI = subsTring;\n }\n\n let serviceParametersURI = this.serviceProviderProperties.URI;\n serviceParametersURI =\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\n ? serviceParametersURI\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\n\n this.serviceProviderProperties.URI = serviceParametersURI;\n\n const coreProperties = {};\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\n\n this.setCore(new Core(coreProperties));\n this.core.addListener(listener);\n this.core.language = this.language;\n\n // Initializing Core class.\n this.core.init();\n // TODO: Move to Core constructor.\n this.core.setEnvironment(this.environment);\n\n // No internet connection modal.\n let attributes = {};\n attributes.class = attributes.id = \"wrs_modal_offline\";\n this.offlineModal = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_content_offline\";\n this.offlineModalContent = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_close\";\n this.offlineModalClose = Util.createElement(\"span\", attributes);\n this.offlineModalClose.innerHTML = \"×\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_warn\";\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_container\";\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_warn\";\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text\";\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\n\n // Append offline modal elements\n this.offlineModalContent.appendChild(this.offlineModalClose);\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\n this.offlineModalContent.appendChild(this.offlineModalMessage);\n this.offlineModalContent.appendChild(this.offlineModalWarn);\n this.offlineModal.appendChild(this.offlineModalContent);\n document.body.appendChild(this.offlineModal);\n\n const modal = document.getElementById(\"wrs_modal_offline\");\n this.offlineModalClose.addEventListener(\"click\", () => {\n modal.style.display = \"none\";\n });\n\n // Store editor name for telemetry purposes.\n let editorName = this.environment.editor;\n editorName = editorName.slice(0, -1); // Remove version number from editors\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\n let solutionTelemeter = editorName;\n\n // If moodle, add information to hosts and solution.\n const isMoodle = !!(typeof M === \"object\" && M !== null);\n let lms;\n\n if (isMoodle) {\n solutionTelemeter = \"Moodle\";\n lms = {\n nam: \"moodle\",\n fam: \"lms\",\n ver: this.environment.moodleVersion,\n category: this.environment.moodleCourseCategory,\n course: this.environment.moodleCourseName,\n };\n if (!editorName.includes(\"TinyMCE\")) {\n editorName = \"Atto\";\n }\n }\n\n // Get the OS and its version.\n const OSData = this.getOS();\n\n // Get the broswer and its version.\n const broswerData = this.getBrowser();\n\n // Create list of hosts to send to telemetry.\n let hosts = [\n {\n nam: broswerData.detectedBrowser,\n fam: \"browser\",\n ver: broswerData.versionBrowser,\n },\n {\n nam: editorName.toLowerCase(),\n fam: \"html-editor\",\n ver: this.environment.editorVersion,\n },\n {\n nam: OSData.detectedOS,\n fam: \"os\",\n ver: OSData.versionOS,\n },\n {\n nam: window.location.hostname,\n fam: \"domain\",\n },\n lms,\n ];\n\n // Filter hosts to remove empty objects and empty keys.\n hosts = hosts.filter((element) => {\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\n return element !== undefined;\n });\n\n // Initialize telemeter\n Telemeter.init({\n solution: {\n name: `MathType for ${solutionTelemeter}`,\n version: this.version,\n },\n hosts,\n config: {\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\n },\n url: undefined,\n });\n }\n\n /**\n * Returns the Browser name and its version.\n * @return {array} - detectedBrowser = Operating System name.\n * versionBrowser = Operating System version.\n */\n getBrowser() {\n // default value for OS just in case nothing is detected\n let detectedBrowser = \"unknown\";\n let versionBrowser = \"unknown\";\n\n const userAgent = window.navigator.userAgent;\n\n if (/Brave/.test(userAgent)) {\n detectedBrowser = \"brave\";\n if (userAgent.indexOf(\"Brave/\")) {\n const start = userAgent.indexOf(\"Brave\") + 6;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\n detectedBrowser = \"edge_chromium\";\n const start = userAgent.indexOf(\"Edg/\") + 4;\n versionBrowser = userAgent\n .substring(start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Edg/.test(userAgent)) {\n detectedBrowser = \"edge\";\n let start = userAgent.indexOf(\"Edg\") + 3;\n start += userAgent.substring(start).indexOf(\"/\");\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\n detectedBrowser = \"firefox\";\n let start = userAgent.indexOf(\"Firefox\");\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/OPR/.test(userAgent)) {\n detectedBrowser = \"opera\";\n const start = userAgent.indexOf(\"OPR/\") + 4;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\n detectedBrowser = \"chrome\";\n let start = userAgent.indexOf(\"Chrome\");\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/Safari/.test(userAgent)) {\n detectedBrowser = \"safari\";\n let start = userAgent.indexOf(\"Version/\");\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n }\n\n return { detectedBrowser, versionBrowser };\n }\n\n /**\n * Returns the operating system and its version.\n * @return {array} - detectedOS = Operating System name.\n * versionOS = Operating System version.\n */\n getOS() {\n // default value for OS just in case nothing is detected\n let detectedOS = \"unknown\";\n let versionOS = \"unknown\";\n\n // Retrieve properties to easily detect the OS\n const userAgent = window.navigator.userAgent;\n const platform = window.navigator.platform;\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\n\n // Find OS and their respective versions\n if (macosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"macos\";\n if (userAgent.indexOf(\"OS X\") !== -1) {\n const start = userAgent.indexOf(\"OS X\") + 5;\n const end = userAgent.substring(start).indexOf(\" \");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (iosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"ios\";\n if (userAgent.indexOf(\"OS \") !== -1) {\n const start = userAgent.indexOf(\"OS \") + 3;\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"windows\";\n const start = userAgent.indexOf(\"Windows\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Android/.test(userAgent)) {\n detectedOS = \"android\";\n const start = userAgent.indexOf(\"Android\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/CrOS/.test(userAgent)) {\n detectedOS = \"chromeos\";\n let start = userAgent.indexOf(\"CrOS \") + 5;\n start += userAgent.substring(start).indexOf(\" \");\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\n detectedOS = \"linux\";\n }\n\n return { detectedOS, versionOS };\n }\n\n /**\n * Returns the absolute path of the integration script.\n * @return {string} - Absolute path for the integration script.\n */\n getPath() {\n if (typeof this.scriptName === \"undefined\") {\n throw new Error(\"scriptName property needed for getPath.\");\n }\n const col = document.getElementsByTagName(\"script\");\n let path = \"\";\n for (let i = 0; i < col.length; i += 1) {\n const j = col[i].src.lastIndexOf(this.scriptName);\n if (j >= 0) {\n path = col[i].src.substr(0, j - 1);\n }\n }\n return path;\n }\n\n /**\n * Returns integration model plugin version\n * @param {string} - Plugin version\n */\n getVersion() {\n return this.version;\n }\n\n /**\n * Sets the language property.\n * @param {string} language - language code.\n */\n setLanguage(language) {\n this.language = language;\n }\n\n /**\n * Sets a Core instance.\n * @param {Core} core - instance of Core class.\n */\n setCore(core) {\n this.core = core;\n core.setIntegrationModel(this);\n }\n\n /**\n * Returns the Core instance.\n * @returns {Core} instance of Core class.\n */\n getCore() {\n return this.core;\n }\n\n /**\n * Sets the object target and updates the iframe property.\n * @param {HTMLElement} target - target object.\n */\n setTarget(target) {\n this.target = target;\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Sets the editor object.\n * @param {Object} editorObject - The editor object.\n */\n setEditorObject(editorObject) {\n this.editorObject = editorObject;\n }\n\n /**\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openNewFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.dbclick = false;\n this.core.editionProperties.isNewElement = true;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openExistingFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.isNewElement = false;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Wrapper to Core.updateFormula method.\n * Transform a MathML into a image formula.\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\n * or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n */\n updateFormula(mathml) {\n if (this.editorParameters) {\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\n mathml,\n \"application/vnd.wiris.mtweb-params+json\",\n JSON.stringify(this.editorParameters),\n );\n }\n let focusElement;\n let windowTarget;\n const wirisProperties = null;\n\n if (this.isIframe) {\n focusElement = this.target.contentWindow;\n windowTarget = this.target.contentWindow;\n } else {\n focusElement = this.target;\n windowTarget = window;\n }\n\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\n }\n\n /**\n * Wrapper to Core.insertFormula method.\n * Inserts the formula in the specified target, creating\n * a new image (new formula) or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\n\n // Delete temporal image when inserted\n this.core.editionProperties.temporalImage = null;\n\n return obj;\n }\n\n /**\n * Returns the target selection.\n * @returns {Selection} target selection.\n */\n getSelection() {\n if (this.isIframe) {\n this.target.contentWindow.focus();\n return this.target.contentWindow.getSelection();\n }\n this.target.focus();\n return window.getSelection();\n }\n\n /**\n * Add events to formulas in the DOM target. The events added are the following:\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\n * to edit them.\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\n * in case the the formula is resized.\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\n * in case the formula is resized.\n */\n addEvents() {\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\n Util.addElementEvents(\n eventTarget,\n (element, event) => {\n this.doubleClickHandler(element, event);\n // Avoid creating the double click listener more than once for each element.\n // This also allows CKEditor4 to add their own double click listener.\n event.preventDefault();\n },\n (element, event) => {\n this.mousedownHandler(element, event);\n },\n (element, event) => {\n this.mouseupHandler(element, event);\n },\n );\n }\n\n /**\n * Remove events to formulas in the DOM target.\n */\n removeEvents() {\n const eventTarget =\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\n\n if (!eventTarget) {\n return;\n }\n\n Util.removeElementEvents(eventTarget);\n }\n\n /**\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\n */\n destroy() {\n this.removeEvents();\n // Destroy modal dialog if exists.\n if (this.core.modalDialog) {\n this.core.modalDialog.destroy();\n }\n\n // Remove offline modal dialog if exists.\n if (this.offlineModal) {\n this.offlineModal.remove();\n }\n\n this.editorObject = null;\n }\n\n /**\n * Handles a Double-click on the target element. Opens an editor\n * to re-edit the double-clicked formula.\n * @param {HTMLElement} element - DOM object target.\n */\n doubleClickHandler(element) {\n this.core.editionProperties.dbclick = true;\n if (element.nodeName.toLowerCase() === \"img\") {\n this.core.getCustomEditors().disable();\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (element.hasAttribute(customEditorAttributeName)) {\n const customEditor = element.getAttribute(customEditorAttributeName);\n this.core.getCustomEditors().enable(customEditor);\n }\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.core.editionProperties.temporalImage = element;\n this.core.editionProperties.isNewElement = true;\n this.openExistingFormulaEditor();\n }\n }\n }\n\n /**\n * Handles a mouse up event on the target element. Restores the image size to avoid\n * resizing formulas.\n */\n mouseupHandler() {\n if (this.temporalImageResizing) {\n setTimeout(() => {\n Image.fixAfterResize(this.temporalImageResizing);\n }, 10);\n }\n }\n\n /**\n * Handles a mouse down event on the target element. Saves the formula size to avoid\n * resizing formulas.\n * @param {HTMLElement} element - target element.\n */\n mousedownHandler(element) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.temporalImageResizing = element;\n }\n }\n }\n\n /**\n * Returns the integration language. By default the browser agent. This method\n * should be overwritten to obtain the integration language, for example using the\n * plugin API of an HTML editor.\n * @returns {string} integration language.\n */\n getLanguage() {\n return this.getBrowserLanguage();\n }\n\n /**\n * Returns the browser language.\n * @returns {string} the browser language.\n */\n // eslint-disable-next-line class-methods-use-this\n getBrowserLanguage() {\n let language = \"en\";\n if (navigator.userLanguage) {\n language = navigator.userLanguage.substring(0, 2);\n } else if (navigator.language) {\n language = navigator.language.substring(0, 2);\n } else {\n language = \"en\";\n }\n return language;\n }\n\n /**\n * This function is called once the {@link Core} is loaded. IntegrationModel class\n * will fire this method when {@link Core} 'onLoad' event is fired.\n * This method should content all the logic to init\n * the integration.\n */\n callbackFunction() {\n // It's needed to wait until the integration target is ready. The event is fired\n // from the integration side.\n const listener = Listeners.newListener(\"onTargetReady\", () => {\n this.addEvents(this.target);\n });\n this.listeners.add(listener);\n }\n\n /**\n * Function called when the content submits an action.\n */\n // eslint-disable-next-line class-methods-use-this\n notifyWindowClosed() {\n // Nothing.\n }\n\n /**\n * Wrapper.\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\n * @param {string} textNode - text node to extract the MathML.\n * @param {int} caretPosition - caret position inside the text node.\n * @returns {string} MathML inside the text node.\n */\n\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getMathmlFromTextNode(textNode, caretPosition) {}\n\n /**\n * Wrapper\n * It fills wrs event object of nonLatex with the desired data.\n * @param {Object} event - event object.\n * @param {Object} window dom window object.\n * @param {string} mathml valid mathml.\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n fillNonLatexNode(event, window, mathml) {}\n\n /**\n Wrapper.\n * Returns selected item from the target.\n * @param {HTMLElement} target - target element\n * @param {boolean} iframe\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getSelectedItem(target, isIframe) {}\n\n // Set temporal image to null and make focus come back.\n static setActionsOnCancelButtons() {\n // Make focus come back on the previous place it was when click cancel button\n const currentInstance = WirisPlugin.currentInstance;\n const editorSelection = currentInstance.getSelection();\n editorSelection.removeAllRanges();\n\n if (currentInstance.core.editionProperties.range) {\n const { range } = currentInstance.core.editionProperties;\n currentInstance.core.editionProperties.range = null;\n editorSelection.addRange(range);\n if (range.startOffset !== range.endOffset) {\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\n }\n }\n\n // eslint-disable-next-line no-undef\n if (WirisPlugin.currentInstance) {\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\n }\n }\n}\n\n// To know if the integration that extends this class implements\n// wrapper methods, they are set as undefined.\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\nIntegrationModel.prototype.fillNonLatexNode = undefined;\nIntegrationModel.prototype.getSelectedItem = undefined;\n\n/**\n * An object containing a list with the overwritable class constructor properties.\n * @type {Object}\n */\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\n","/* eslint-disable */\nvar md5;\nexport default md5;\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n delete Array.prototype.__class__;\n})();\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n})();\ndelete Array.prototype.__class__;\n// @codingStandardsIgnoreEnd\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\n\n/**\n * This class represents the MathType integration for CKEditor5.\n * @extends {IntegrationModel}\n */\nexport default class CKEditor5Integration extends IntegrationModel {\n constructor(ckeditorIntegrationModelProperties) {\n const editor = ckeditorIntegrationModelProperties.editorObject;\n\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\n }\n /**\n * CKEditor5 Integration.\n *\n * @param {integrationModelProperties} integrationModelAttributes\n */\n super(ckeditorIntegrationModelProperties);\n\n /**\n * Folder name used for the integration inside CKEditor plugins folder.\n */\n this.integrationFolderName = \"ckeditor_wiris\";\n }\n\n /**\n * @inheritdoc\n * @returns {string} - The CKEditor instance language.\n * @override\n */\n getLanguage() {\n // Returns the CKEDitor instance language taking into account that the language can be an object.\n // Try to get editorParameters.language, fail silently otherwise\n try {\n return this.editorParameters.language;\n } catch (e) {}\n const languageObject = this.editorObject.config.get(\"language\");\n if (languageObject != null) {\n if (typeof languageObject === \"object\") {\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\n return languageObject.ui;\n }\n return this.editorObject.locale.uiLanguage;\n }\n return languageObject;\n }\n return super.getLanguage();\n }\n\n /**\n * Adds callbacks to the following CKEditor listeners:\n * - 'focus' - updates the current instance.\n * - 'contentDom' - adds 'doubleclick' callback.\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\n * - 'setData' - parses the data converting MathML into images.\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\n * - 'mode' - recalculates the active element.\n */\n addEditorListeners() {\n const editor = this.editorObject;\n\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\n this.checkElement();\n }\n }\n\n /**\n * Checks the current container and assign events in case that it doesn't have them.\n * CKEditor replaces several times the element element during its execution,\n * so we must assign the events again to editor element.\n */\n checkElement() {\n const editor = this.editorObject;\n const newElement = editor.sourceElement;\n\n // If the element wasn't treated, add the events.\n if (!newElement.wirisActive) {\n this.setTarget(newElement);\n this.addEvents();\n // Set the element as treated\n newElement.wirisActive = true;\n }\n }\n\n /**\n * @inheritdoc\n * @param {HTMLElement} element - HTMLElement target.\n * @param {MouseEvent} event - event which trigger the handler.\n */\n doubleClickHandler(element, event) {\n this.core.editionProperties.dbclick = true;\n if (this.editorObject.isReadOnly === false) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\n // doubleclick event ends here.\n if (typeof event.stopPropagation !== \"undefined\") {\n // old I.E compatibility.\n event.stopPropagation();\n } else {\n event.returnValue = false;\n }\n this.core.getCustomEditors().disable();\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\n if (customEditorAttr) {\n this.core.getCustomEditors().enable(customEditorAttr);\n }\n this.core.editionProperties.temporalImage = element;\n this.openExistingFormulaEditor();\n }\n }\n }\n }\n\n /** @inheritdoc */\n static getCorePath() {\n return null; // TODO\n }\n\n /** @inheritdoc */\n callbackFunction() {\n super.callbackFunction();\n this.addEditorListeners();\n }\n\n openNewFormulaEditor() {\n // Store the editor selection as it will be lost upon opening the modal\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\n\n // Focus on the selected editor when multiple editor instances are present\n WirisPlugin.currentInstance = this;\n\n return super.openNewFormulaEditor();\n }\n\n /**\n * Replaces old formula with new MathML or inserts it in caret position if new\n * @param {String} mathml MathML to update old one or insert\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\n */\n insertMathml(mathml) {\n // This returns the value returned by the callback function (writer => {...})\n return this.editorObject.model.change((writer) => {\n const core = this.getCore();\n const selection = this.editorObject.model.document.selection;\n\n const modelElementNew = writer.createElement(\"mathml\", {\n formula: mathml,\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\n });\n\n // Obtain the DOM object corresponding to the formula\n if (core.editionProperties.isNewElement) {\n // Don't bother inserting anything at all if the MathML is empty.\n if (!mathml) return;\n\n const viewSelection =\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\n\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\n\n // Remove selection\n if (!viewSelection.isCollapsed) {\n for (const range of viewSelection.getRanges()) {\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\n }\n }\n\n // Set carret after the formula\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\n writer.setSelection(position);\n } else {\n const img = core.editionProperties.temporalImage;\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\n\n // Insert the new and remove the old one\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\n\n // If the given MathML is empty, don't insert a new formula.\n if (mathml) {\n this.editorObject.model.insertObject(modelElementNew, position);\n }\n writer.remove(modelElementOld);\n }\n\n // eslint-disable-next-line consistent-return\n return modelElementNew;\n });\n }\n\n /**\n * Finds the text node corresponding to given DOM text element.\n * @param {element} viewElement Element to find corresponding text node of.\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\n */\n findText(viewElement) {\n // eslint-disable-line consistent-return\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\n let pivot = viewElement;\n let element;\n while (!element) {\n element = this.editorObject.editing.mapper.toModelElement(\n this.editorObject.editing.view.domConverter.domToView(pivot),\n );\n pivot = pivot.parentElement;\n }\n\n // Navigate through all the subtree under `pivot` in order to find the correct text node\n const range = this.editorObject.model.createRangeIn(element);\n const descendants = Array.from(range.getItems());\n for (const node of descendants) {\n let viewElementData = viewElement.data;\n if (viewElement.nodeType === 3) {\n // Remove invisible white spaces\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\n }\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\n return node.textNode;\n }\n }\n }\n\n /** @inheritdoc */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n // eslint-disable-line no-unused-vars\n const returnObject = {};\n\n let mathmlOrigin;\n if (!mathml) {\n this.insertMathml(\"\");\n } else if (this.core.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n\n this.editorObject.model.change((writer) => {\n const { latexRange } = this.core.editionProperties;\n\n const startNode = this.findText(latexRange.startContainer);\n const endNode = this.findText(latexRange.endContainer);\n\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\n\n let range = writer.createRange(startPosition, endPosition);\n\n // When Latex is next to image/formula.\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\n // Get the position of the latex to be replaced.\n const latexEdited = `$$${Latex.getLatexFromMathML(\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\n )}$$`;\n let data = latexRange.startContainer.data;\n\n // Remove invisible characters.\n data = data.replaceAll(String.fromCharCode(8288), \"\");\n\n // Get to the start of the latex we are editing.\n const offset = data.indexOf(latexEdited);\n const dataOffset = data.substring(offset);\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\n const substring = dataOffset.substr(0, second$);\n data = data.replace(substring, \"\");\n\n if (!data) {\n startPosition = writer.createPositionBefore(startNode);\n range = startNode;\n } else {\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\n range = writer.createRange(startPosition, endPosition);\n }\n }\n\n writer.remove(range);\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\n });\n } else {\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\n try {\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\n windowTarget.document,\n );\n } catch (e) {\n const x = e.toString();\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\n this.core.modalDialog.cancelAction();\n }\n }\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.core.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\n We probably should add it here as well, but we should look further into how */\n // this.editorObject.fire('change');\n\n // Remove temporal image of inserted formula\n this.core.editionProperties.temporalImage = null;\n\n return returnObject;\n }\n\n /**\n * Function called when the content submits an action.\n */\n notifyWindowClosed() {\n this.editorObject.editing.view.focus();\n }\n}\n","/* eslint-disable max-classes-per-file */\nimport { Command } from \"ckeditor5/src/core.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\n/**\n * Command for opening the MathType editor\n */\nexport class MathTypeCommand extends Command {\n execute(options = {}) {\n // Check we get a valid integration\n // eslint-disable-next-line no-prototype-builtins\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\n }\n\n // Save the integration instance as a property of the command.\n this.integration = options.integration;\n\n // Set custom editor or disable it\n this.setEditor();\n\n // Open the editor\n this.openEditor();\n }\n\n /**\n * Sets the appropriate custom editor, if any, or disables them.\n */\n setEditor() {\n // It's possible that a custom editor was last used.\n // We need to disable it to avoid wrong behaviors.\n this.integration.core.getCustomEditors().disable();\n }\n\n /**\n * Checks whether we are editing an existing formula or a new one and opens the editor.\n */\n openEditor() {\n this.integration.core.editionProperties.dbclick = false;\n const image = this._getSelectedImage();\n if (\n typeof image !== \"undefined\" &&\n image !== null &&\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\n ) {\n this.integration.core.editionProperties.temporalImage = image;\n this.integration.openExistingFormulaEditor();\n } else {\n this.integration.openNewFormulaEditor();\n }\n }\n\n /**\n * Gets the currently selected formula image\n * @returns {Element} selected image, if any, undefined otherwise\n */\n _getSelectedImage() {\n const { selection } = this.editor.editing.view.document;\n\n // If we can not extract the formula, fall back to default behavior.\n if (selection.isCollapsed || selection.rangeCount !== 1) {\n return;\n }\n\n // Look for the wrapping the formula and then for the inside\n\n const range = selection.getFirstRange();\n\n let image;\n\n for (const span of range) {\n if (span.item.name !== \"span\") {\n return;\n }\n image = span.item.getChild(0);\n break;\n }\n\n if (!image) {\n return;\n }\n\n // eslint-disable-next-line consistent-return\n return this.editor.editing.view.domConverter.mapViewToDom(image);\n }\n}\n\n/**\n * Command for opening the ChemType editor\n */\nexport class ChemTypeCommand extends MathTypeCommand {\n setEditor() {\n this.integration.core.getCustomEditors().enable(\"chemistry\");\n }\n}\n","// CKEditor imports\nimport { Plugin } from \"ckeditor5/src/core.js\";\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\n\n// MathType API imports\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\n\n// Local imports\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\n\nimport packageInfo from \"../package.json\";\n\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\n\nexport default class MathType extends Plugin {\n static get requires() {\n return [Widget];\n }\n\n static get pluginName() {\n return \"MathType\";\n }\n\n init() {\n // Create the MathType API Integration object\n const integration = this._addIntegration();\n currentInstance = integration;\n\n // Add the MathType and ChemType commands to the editor\n this._addCommands();\n\n // Add the buttons for MathType and ChemType\n this._addViews(integration);\n\n // Registers the element in the schema\n this._addSchema();\n\n // Add the downcast and upcast converters\n this._addConverters(integration);\n\n // Expose the WirisPlugin variable to the window\n this._exposeWiris();\n }\n\n /**\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\n */\n destroy() {\n // eslint-disable-line class-methods-use-this\n currentInstance?.destroy();\n }\n\n /**\n * Create the MathType API Integration object\n * @returns {CKEditor5Integration} the integration object\n */\n _addIntegration() {\n const { editor } = this;\n\n /**\n * Integration model constructor attributes.\n * @type {integrationModelProperties}\n */\n const integrationProperties = {};\n integrationProperties.environment = {};\n integrationProperties.environment.editor = \"CKEditor5\";\n integrationProperties.environment.editorVersion = \"5.x\";\n integrationProperties.version = packageInfo.version;\n integrationProperties.editorObject = editor;\n integrationProperties.serviceProviderProperties = {};\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\n integrationProperties.serviceProviderProperties.server = \"java\";\n integrationProperties.target = editor.sourceElement;\n integrationProperties.scriptName = \"bundle.js\";\n integrationProperties.managesLanguage = true;\n // etc\n\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\n let integration;\n if (integrationProperties.target) {\n // Instance of the integration associated to this editor instance\n integration = new CKEditor5Integration(integrationProperties);\n integration.init();\n integration.listeners.fire(\"onTargetReady\", {});\n\n integration.checkElement();\n\n this.listenTo(\n editor.editing.view.document,\n \"click\",\n (evt, data) => {\n // Is Double-click\n if (data.domEvent.detail === 2) {\n integration.doubleClickHandler(data.domTarget, data.domEvent);\n evt.stop();\n }\n },\n { priority: \"highest\" },\n );\n }\n\n return integration;\n }\n\n /**\n * Add the MathType and ChemType commands to the editor\n */\n _addCommands() {\n const { editor } = this;\n\n // Add command to open the formula editor\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\n\n // Add command to open the chemistry formula editor\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\n }\n\n /**\n * Add the buttons for MathType and ChemType\n * @param {CKEditor5Integration} integration the integration object\n */\n _addViews(integration) {\n const { editor } = this;\n\n // Check if MathType editor is enabled\n if (Configuration.get(\"editorEnabled\")) {\n // Add button for the formula editor\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\n view.set({\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\n icon: mathIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"MathType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Check if ChemType editor is enabled\n if (Configuration.get(\"chemEnabled\")) {\n // Add button for the chemistry formula editor\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\n\n view.set({\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\n icon: chemIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"ChemType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Observer for the Double-click event\n editor.editing.view.addObserver(ClickObserver);\n }\n\n /**\n * Registers the element in the schema\n */\n _addSchema() {\n const { schema } = this.editor.model;\n\n schema.register(\"mathml\", {\n inheritAllFrom: \"$inlineObject\",\n allowAttributes: [\"formula\", \"htmlContent\"],\n });\n }\n\n /**\n * Add the downcast and upcast converters\n */\n _addConverters(integration) {\n const { editor } = this;\n\n // Editing view -> Model\n editor.conversion.for(\"upcast\").elementToElement({\n view: {\n name: \"span\",\n classes: \"ck-math-widget\",\n },\n model: (viewElement, { writer: modelWriter }) => {\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\n return modelWriter.createElement(\"mathml\", {\n formula,\n });\n },\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // When element was already consumed then skip it.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // If we encounter any with a LaTeX annotation inside,\n // convert it into a \"$$...$$\" string.\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\n\n // Get the formula of the (which is all its children).\n const processor = new XmlDataProcessor(editor.editing.view.document);\n\n // Only god knows why the following line makes viewItem lose all of its children,\n // so we obtain isLatex before doing this because we need viewItem's children for that.\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\n\n // and obtain the attributes of too!\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n\n // We process the document fragment\n let formula = processor.toData(viewDocumentFragment) || \"\";\n\n // And obtain the complete formula\n formula = Util.htmlSanitize(`${formula}`);\n\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\n\n /* Model node that contains what's going to actually be inserted. This can be either:\n - A element with a formula attribute set to the given formula, or\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\n const modelNode = isLatex\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\n : writer.createElement(\"mathml\", { formula });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\n \"element:img\",\n (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // Only upcast when is wiris formula\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\n return;\n }\n\n // The following code ensures that the element's name, attributes, and classes are consumed.\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\n\n // Check if we can consume the element name.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // Consume the name, attributes, and classes so nothing else processes it.\n consumable.consume(viewItem, { name: true });\n for (const attrName of viewItem.getAttributes()) {\n consumable.consume(viewItem, { attributes: [attrName] });\n }\n\n for (const className of viewItem.getClassNames()) {\n consumable.consume(viewItem, { classes: [className] });\n }\n\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n const htmlContent = Util.htmlSanitize(``);\n\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n },\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\n { priority: \"high\" },\n );\n\n /**\n * Whether the given view element has a LaTeX annotation element.\n * @param {*} math\n * @returns {bool}\n */\n function mathIsLatex(math) {\n const semantics = math.getChild(0);\n if (!semantics || semantics.name !== \"semantics\") return false;\n for (const child of semantics.getChildren()) {\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\n return true;\n }\n }\n return false;\n }\n\n function createViewWidget(modelItem, { writer: viewWriter }) {\n const widgetElement = viewWriter.createContainerElement(\"span\", {\n class: \"ck-math-widget\",\n });\n\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\n\n if (mathUIElement) {\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\n }\n\n return toWidget(widgetElement, viewWriter);\n }\n\n function createViewImage(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n const formula = modelItem.getAttribute(\"formula\");\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\n\n if (!formula && !htmlContent) {\n return null;\n }\n\n let imgElement = null;\n\n if (htmlContent) {\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\n } else if (formula) {\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\n\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\n\n // Add HTML element () to model\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\n }\n\n /* Although we use the HtmlDataProcessor to obtain the attributes,\n * we must create a new EmptyElement which is independent of the\n * DataProcessor being used by this editor instance\n */\n if (imgElement) {\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\n renderUnsafeAttributes: [\"src\"],\n });\n }\n\n return null;\n }\n\n // Model -> Editing view\n editor.conversion.for(\"editingDowncast\").elementToElement({\n model: \"mathml\",\n view: createViewWidget,\n });\n\n // Model -> Data view\n editor.conversion.for(\"dataDowncast\").elementToElement({\n model: \"mathml\",\n view: createDataString, // eslint-disable-line no-use-before-define\n });\n\n /**\n * Makes a copy of the given view node.\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\n * @returns {module:engine/view/node~Node} Copy of the node.\n */\n function clone(viewWriter, sourceNode) {\n if (sourceNode.is(\"text\")) {\n return viewWriter.createText(sourceNode.data);\n }\n if (sourceNode.is(\"element\")) {\n if (sourceNode.is(\"emptyElement\")) {\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\n }\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\n for (const child of sourceNode.getChildren()) {\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\n }\n return element;\n }\n\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\n }\n\n function createDataString(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n // Load img element\n const mathString =\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\n\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\n\n return clone(viewWriter, sourceMathElement);\n }\n\n // This stops the view selection getting into the s and messing up caret movement\n editor.editing.mapper.on(\n \"viewToModelPosition\",\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\n );\n\n // Keep a reference to the original get and set function.\n const { get, set } = editor.data;\n\n /**\n * Hack to transform $$latex$$ into in editor.getData()'s output.\n */\n editor.data.on(\n \"get\",\n (e) => {\n const output = e.return;\n const parsedResult = Parser.endParse(output);\n\n // Cleans all the semantics tag for safexml\n // including the handwritten data points\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\n },\n { priority: \"low\" },\n );\n\n /**\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\n */\n editor.data.on(\n \"set\",\n (e, args) => {\n // Retrieve the data to be set on the CKEditor.\n let modifiedData = args[0];\n // Regex to find all mathml formulas.\n const regexp = /(]*>)|()/gm;\n const formulas = [];\n let formula;\n\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\n while ((formula = regexp.exec(modifiedData)) !== null) {\n formulas.push(formula[0]);\n }\n\n // Loop to find LaTeX and formula images and replace the MathML for the both.\n formulas.forEach((formula) => {\n if (formula.includes('encoding=\"LaTeX\"')) {\n // LaTeX found.\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\n modifiedData = modifiedData.replace(formula, latex);\n } else if (formula.includes(\" 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return func.apply(thisArg, args);\n };\n}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\r\n * This class represents all the constants needed in a MathType integration among different classes.\r\n * If a constant should be used across different classes should be defined using attribute\r\n * accessors.\r\n */\r\nexport default class Constants {\r\n /**\r\n * Safe XML entities.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharactersEntities() {\r\n return {\r\n tagOpener: \"«\",\r\n tagCloser: \"»\",\r\n doubleQuote: \"¨\",\r\n realDoubleQuote: \""\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard invalid safe characters.\r\n * @type {Object}\r\n */\r\n static get safeBadBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยป<ยซ/moยป\",\r\n gtElement: \"ยซmoยป>ยซ/moยป\",\r\n ampElement: \"ยซmoยป&ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard valid safe characters.\r\n * @type{Object}\r\n */\r\n static get safeGoodBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\r\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\r\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Standard XML special characters.\r\n * @type {Object}\r\n */\r\n static get xmlCharacters() {\r\n return {\r\n id: \"xmlCharacters\",\r\n tagOpener: \"<\", // Hex: \\x3C.\r\n tagCloser: \">\", // Hex: \\x3E.\r\n doubleQuote: '\"', // Hex: \\x22.\r\n ampersand: \"&\", // Hex: \\x26.\r\n quote: \"'\", // Hex: \\x27.\r\n };\r\n }\r\n\r\n /**\r\n * Safe XML special characters. This characters are used instead the standard\r\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\r\n * special character have a UTF-8 representation.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharacters() {\r\n return {\r\n id: \"safeXmlCharacters\",\r\n tagOpener: \"ยซ\", // Hex: \\xAB.\r\n tagCloser: \"ยป\", // Hex: \\xBB.\r\n doubleQuote: \"ยจ\", // Hex: \\xA8.\r\n ampersand: \"ยง\", // Hex: \\xA7.\r\n quote: \"`\", // Hex: \\x60.\r\n realDoubleQuote: \"ยจ\",\r\n };\r\n }\r\n}\r\n","import Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a class to manage MathML objects.\r\n */\r\nexport default class MathML {\r\n /**\r\n * Checks if the mathml at position i is inside an HTML attribute or not.\r\n * @param {string} content - a string containing MathML code.\r\n * @param {number} i - search index.\r\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\r\n */\r\n static isMathmlInAttribute(content, i) {\r\n // Regex =\r\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\r\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\r\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\r\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\r\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\r\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\r\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\r\n const expression = new RegExp(regex);\r\n\r\n const actualContent = content.substring(0, i);\r\n const reversed = actualContent.split(\"\").reverse().join(\"\");\r\n const exists = expression.test(reversed);\r\n\r\n return exists;\r\n }\r\n\r\n /**\r\n * Decodes an encoded MathML with standard XML tags.\r\n * We use these entities because IE doesn't support html entities\r\n * on its attributes sometimes. Yes, sometimes.\r\n * @param {string} input - string to be decoded.\r\n * @return {string} decoded string.\r\n */\r\n static safeXmlDecode(input) {\r\n let { tagOpener } = Constants.safeXmlCharactersEntities;\r\n let { tagCloser } = Constants.safeXmlCharactersEntities;\r\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\r\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\r\n // Decoding entities.\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n // Added to fix problem due to import from 1.9.x.\r\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\r\n\r\n // Blackboard.\r\n const { ltElement } = Constants.safeBadBlackboardCharacters;\r\n const { gtElement } = Constants.safeBadBlackboardCharacters;\r\n const { ampElement } = Constants.safeBadBlackboardCharacters;\r\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\r\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\r\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\r\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\r\n }\r\n\r\n ({ tagOpener } = Constants.safeXmlCharacters);\r\n ({ tagCloser } = Constants.safeXmlCharacters);\r\n ({ doubleQuote } = Constants.safeXmlCharacters);\r\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\r\n const { ampersand } = Constants.safeXmlCharacters;\r\n const { quote } = Constants.safeXmlCharacters;\r\n\r\n // Decoding characters.\r\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.xmlCharacters.quote);\r\n\r\n // We are replacing $ by & when its part of an entity for retro-compatibility.\r\n // Now, the standard is replace ยง by &.\r\n let returnValue = \"\";\r\n let currentEntity = null;\r\n\r\n for (let i = 0; i < input.length; i += 1) {\r\n const character = input.charAt(i);\r\n if (currentEntity == null) {\r\n if (character === \"$\") {\r\n currentEntity = \"\";\r\n } else {\r\n returnValue += character;\r\n }\r\n } else if (character === \";\") {\r\n returnValue += `&${currentEntity}`;\r\n currentEntity = null;\r\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\r\n // Character is part of an entity.\r\n currentEntity += character;\r\n } else {\r\n returnValue += `$${currentEntity}`; // Is not an entity.\r\n currentEntity = null;\r\n i -= 1; // Parse again the current character.\r\n }\r\n }\r\n\r\n return returnValue;\r\n }\r\n\r\n /**\r\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\r\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\r\n * @param {string} input - input string to be encoded\r\n * @returns {string} encoded string.\r\n */\r\n static safeXmlEncode(input) {\r\n const { tagOpener } = Constants.xmlCharacters;\r\n const { tagCloser } = Constants.xmlCharacters;\r\n const { doubleQuote } = Constants.xmlCharacters;\r\n const { ampersand } = Constants.xmlCharacters;\r\n const { quote } = Constants.xmlCharacters;\r\n\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\r\n\r\n return input;\r\n }\r\n\r\n /**\r\n * Converts special symbols (> 128) to entities and replaces all textual\r\n * entities by its number entities.\r\n * @param {string} mathml - MathML string containing - or not - special symbols\r\n * @returns {string} MathML with all textual entities replaced.\r\n */\r\n static mathMLEntities(mathml) {\r\n let toReturn = \"\";\r\n\r\n for (let i = 0; i < mathml.length; i += 1) {\r\n const character = mathml.charAt(i);\r\n\r\n // Parsing > 128 characters.\r\n if (mathml.codePointAt(i) > 128) {\r\n toReturn += `&#${mathml.codePointAt(i)};`;\r\n // For UTF-32 characters we need to move the index one position.\r\n if (mathml.codePointAt(i) > 0xffff) {\r\n i += 1;\r\n }\r\n } else if (character === \"&\") {\r\n const end = mathml.indexOf(\";\", i + 1);\r\n if (end >= 0) {\r\n const container = document.createElement(\"span\");\r\n container.innerHTML = mathml.substring(i, end + 1);\r\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\r\n i = end;\r\n } else {\r\n toReturn += character;\r\n }\r\n } else {\r\n toReturn += character;\r\n }\r\n }\r\n\r\n return toReturn;\r\n }\r\n\r\n /**\r\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\r\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} MathML string with his class containing the editor toolbar string.\r\n */\r\n static addCustomEditorClassAttribute(mathml, customEditor) {\r\n let toReturn = \"\";\r\n\r\n const start = mathml.indexOf(\"\");\r\n if (mathml.indexOf(\"class\") === -1) {\r\n // Adding custom editor type.\r\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\r\n toReturn += mathml.substr(end + 1, mathml.length);\r\n return toReturn;\r\n }\r\n }\r\n return mathml;\r\n }\r\n\r\n /**\r\n * Remove a custom editor name from the MathML class attribute.\r\n * @param {string} mathml - a MathML string.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} The input MathML without customEditor name in his class.\r\n */\r\n static removeCustomEditorClassAttribute(mathml, customEditor) {\r\n // Discard MathML without the specified class.\r\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\r\n return mathml;\r\n }\r\n\r\n // Trivial case: class attribute value equal to editor name. Then\r\n // class attribute is removed.\r\n // First try to remove it with a space before if there is one\r\n // Otherwise without the space\r\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n\r\n // Non Trivial case: class attribute contains editor name.\r\n return mathml.replace(`wrs_${customEditor}`, \"\");\r\n }\r\n\r\n /**\r\n * Adds annotation tag in MathML element.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} content - value to put inside annotation tag.\r\n * @param {String} annotationEncoding - annotation encoding.\r\n * @returns {String} - 'mathml' with an annotation that contains\r\n * 'content' and encoding 'encoding'.\r\n */\r\n static addAnnotation(mathml, content, annotationEncoding) {\r\n // If contains annotation, also contains semantics tag.\r\n const containsAnnotation = mathml.indexOf(\"\");\r\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\r\n } else if (MathML.isEmpty(mathml)) {\r\n const endIndexInline = mathml.indexOf(\"/>\");\r\n const endIndexNonInline = mathml.indexOf(\">\");\r\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\r\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\r\n } else {\r\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\r\n const endMathmlContent = mathml.lastIndexOf(\"\");\r\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\r\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\r\n }\r\n\r\n return mathmlWithAnnotation;\r\n }\r\n\r\n /**\r\n * Removes specific annotation tag in MathML element.\r\n * In case of remove the unique annotation, also is removed semantics tag.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} annotationEncoding - annotation encoding to remove.\r\n * @returns {String} - 'mathml' without the annotation encoding specified.\r\n */\r\n static removeAnnotation(mathml, annotationEncoding) {\r\n let mathmlWithoutAnnotation = mathml;\r\n const openAnnotationTag = ``;\r\n const closeAnnotationTag = \"\";\r\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\r\n if (startAnnotationIndex !== -1) {\r\n let differentAnnotationFound = false;\r\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\r\n\r\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Removes semantics tag to element that contains mathml.\r\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\r\n * @param {string} element - Inner HTML text string.\r\n * @returns {string} - 'mathml' without semantics tag.\r\n */\r\n static removeSafeXMLSemantics(element) {\r\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\r\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\r\n\r\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Transforms all xml mathml occurrences that contain semantics to the same\r\n * xml mathml occurrences without semantics.\r\n * @param {string} text - string that can contain xml mathml occurrences.\r\n * @param {Constants} [characters] - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * xmlCharacters by default.\r\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\r\n */\r\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\r\n const mathTagStart = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const mathTagEndline = `/${characters.tagCloser}`;\r\n const { tagCloser } = characters;\r\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\r\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\r\n\r\n let output = \"\";\r\n let start = text.indexOf(mathTagStart);\r\n let end = 0;\r\n while (start !== -1) {\r\n output += text.substring(end, start);\r\n\r\n // MathML can be written as '' or ''.\r\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\r\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\r\n const firstTagCloser = text.indexOf(tagCloser, start);\r\n if (mathTagEndIndex !== -1) {\r\n end = mathTagEndIndex;\r\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\r\n end = mathTagEndlineIndex;\r\n }\r\n\r\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\r\n if (semanticsIndex !== -1) {\r\n const mmlTagStart = text.substring(start, semanticsIndex);\r\n const annotationIndex = text.indexOf(annotationTagStart, start);\r\n if (annotationIndex !== -1) {\r\n const startIndex = semanticsIndex + semanticsTagStart.length;\r\n const mmlContent = text.substring(startIndex, annotationIndex);\r\n output += mmlTagStart + mmlContent + mathTagEnd;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n end += mathTagEnd.length;\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n }\r\n\r\n output += text.substring(end, text.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Returns true if a MathML contains a certain class.\r\n * @param {string} mathML - input MathML.\r\n * @param {string} className - className.\r\n * @returns {boolean} true if the input MathML contains the input class.\r\n * false otherwise.\r\n * @static\r\n */\r\n static containClass(mathML, className) {\r\n const classIndex = mathML.indexOf(\"class\");\r\n if (classIndex === -1) {\r\n return false;\r\n }\r\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\r\n const classTag = mathML.substring(classIndex, classTagEndIndex);\r\n if (classTag.indexOf(className) !== -1) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns true if mathml is empty. Otherwise, false.\r\n * @param {string} mathml - valid MathML with standard XML tags.\r\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\r\n */\r\n static isEmpty(mathml) {\r\n // MathML can have the shape or ''.\r\n const closeTag = \">\";\r\n const closeTagInline = \"/>\";\r\n const firstCloseTagIndex = mathml.indexOf(closeTag);\r\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\r\n let empty = false;\r\n // MathML is always empty in the second shape.\r\n if (firstCloseTagInlineIndex !== -1) {\r\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\r\n empty = true;\r\n }\r\n }\r\n\r\n // MathML is always empty in the first shape when there aren't elements\r\n // between math tags.\r\n if (!empty) {\r\n const mathTagEndRegex = new RegExp(\"\");\r\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\r\n if (mathTagEndArray) {\r\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\r\n }\r\n }\r\n\r\n return empty;\r\n }\r\n\r\n /**\r\n * Encodes html entities inside properties.\r\n * @param {String} mathml - valid MathML with standard XML tags.\r\n * @returns {String} - 'mathml' with property entities encoded.\r\n */\r\n static encodeProperties(mathml) {\r\n // Search all the properties.\r\n const regex = /\\w+=\".*?\"/g;\r\n // Encode html entities.\r\n const replacer = (match) => {\r\n // It has the shape:\r\n // .\r\n const quoteIndex = match.indexOf('\"');\r\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\r\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\r\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\r\n return matchEncoded;\r\n };\r\n\r\n const mathmlEncoded = mathml.replace(regex, replacer);\r\n return mathmlEncoded;\r\n }\r\n}\r\n","/**\r\n * This class represents the configuration class.\r\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\r\n */\r\nexport default class Configuration {\r\n /**\r\n * Adds a properties object to {@link Configuration.properties}.\r\n * @param {Object} properties - properties to append to current properties.\r\n */\r\n static addConfiguration(properties) {\r\n Object.assign(Configuration.properties, properties);\r\n }\r\n\r\n /**\r\n * Static property.\r\n * The configuration properties object.\r\n * @private\r\n * @type {Object}\r\n */\r\n static get properties() {\r\n return Configuration._properties;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set configuration properties.\r\n * @param {Object} value - The property value.\r\n * @ignore\r\n */\r\n static set properties(value) {\r\n Configuration._properties = value;\r\n }\r\n\r\n /**\r\n * Returns the value of a property key.\r\n * @param {String} key - Property key\r\n * @returns {String} Property value\r\n */\r\n static get(key) {\r\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\r\n // Backwards compatibility.\r\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\r\n return Configuration.properties[`_wrs_conf_${key}`];\r\n }\r\n return false;\r\n }\r\n return Configuration.properties[key];\r\n }\r\n\r\n /**\r\n * Adds a new property to Configuration class.\r\n * @param {String} key - Property key.\r\n * @param {Object} value - Property value.\r\n */\r\n static set(key, value) {\r\n Configuration.properties[key] = value;\r\n }\r\n\r\n /**\r\n * Updates a property object value with new values.\r\n * @param {String} key - The property key to be updated.\r\n * @param {Object} propertyValue - Object containing the new values.\r\n */\r\n static update(key, propertyValue) {\r\n if (!Configuration.get(key)) {\r\n Configuration.set(key, propertyValue);\r\n } else {\r\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\r\n Configuration.set(key, updateProperty);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Static properties object. Stores all configuration properties.\r\n * Needed to attribute accessors.\r\n * @private\r\n * @type {Object}\r\n */\r\nConfiguration._properties = {};\r\n","export default class TextCache {\r\n /**\r\n * @classdesc\r\n * This class represent a client-side text cache class. Contains pairs of\r\n * strings (key/value) which can be retrieved in any moment. Usually used\r\n * to store AJAX responses for text services like mathml2latex\r\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Cache array property storing the cache entries.\r\n * @type {Array.}\r\n */\r\n this.cache = [];\r\n }\r\n\r\n /**\r\n * This method populates a key/value pair into the {@link this.cache} property.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @param {String} value - Cache value, usually the service response.\r\n */\r\n populate(key, value) {\r\n this.cache[key] = value;\r\n }\r\n\r\n /**\r\n * Returns the cache value associated to certain cache key.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @return {String} value - Cache value, if exists. False otherwise.\r\n */\r\n get(key) {\r\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\r\n return this.cache[key];\r\n }\r\n return false;\r\n }\r\n}\r\n","/**\r\n * This object represents a custom listener.\r\n * @typedef {Object} Listener\r\n * @property {String} Listener.eventName - The listener name.\r\n * @property {Function} Listener.callback - The listener callback function.\r\n */\r\n\r\nexport default class Listeners {\r\n /**\r\n * @classdesc\r\n * This class represents a custom listeners manager.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Array containing all custom listeners.\r\n * @type {Object[]}\r\n */\r\n this.listeners = [];\r\n }\r\n\r\n /**\r\n * Add a listener to Listener class.\r\n * @param {Object} listener - A listener object.\r\n */\r\n add(listener) {\r\n this.listeners.push(listener);\r\n }\r\n\r\n /**\r\n * Fires MathType event listeners\r\n * @param {String} eventName - event name\r\n * @param {Event} event - event object.\r\n * @return {boolean} false if event has been prevented. true otherwise.\r\n */\r\n fire(eventName, event) {\r\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\r\n if (this.listeners[i].eventName === eventName) {\r\n // Calling listener.\r\n this.listeners[i].callback(event);\r\n }\r\n }\r\n return event.defaultPrevented;\r\n }\r\n\r\n /**\r\n * Creates a new listener object.\r\n * @param {string} eventName - Event name.\r\n * @param {Object} callback - Callback function.\r\n * @returns {object} the listener object.\r\n */\r\n static newListener(eventName, callback) {\r\n const listener = {};\r\n listener.eventName = eventName;\r\n listener.callback = callback;\r\n return listener;\r\n }\r\n}\r\n","import Util from \"./util\";\r\nimport Listeners from \"./listeners\";\r\nimport Configuration from \"./configuration\";\r\n\r\n/**\r\n * @typedef {Object} ServiceProviderProperties\r\n * @property {String} URI - Service URI.\r\n * @property {String} server - Service server language.\r\n */\r\n\r\n/**\r\n * @classdesc\r\n * Class representing a serviceProvider. A serviceProvider is a class containing\r\n * an arbitrary number of services with the correspondent path.\r\n */\r\nexport default class ServiceProvider {\r\n /**\r\n * Returns Service Provider listeners.\r\n * @type {Listeners}\r\n */\r\n static get listeners() {\r\n return ServiceProvider._listeners;\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\r\n * @param {Listener} listener - Instance of {@link Listener}.\r\n */\r\n static addListener(listener) {\r\n ServiceProvider.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Fires events in Service Provider.\r\n * @param {String} eventName - Event name.\r\n * @param {Event} event - Event object.\r\n */\r\n static fireEvent(eventName, event) {\r\n ServiceProvider.listeners.fire(eventName, event);\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @type {ServiceProviderProperties}\r\n *\r\n */\r\n static get parameters() {\r\n return ServiceProvider._parameters;\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @private\r\n * @type {ServiceProviderProperties}\r\n */\r\n static set parameters(parameters) {\r\n ServiceProvider._parameters = parameters;\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Return service provider paths.\r\n * @private\r\n * @type {String}\r\n */\r\n static get servicePaths() {\r\n return ServiceProvider._servicePaths;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service paths.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set servicePaths(value) {\r\n ServiceProvider._servicePaths = value;\r\n }\r\n\r\n /**\r\n * Adds a new service to the ServiceProvider.\r\n * @param {String} service - Service name.\r\n * @param {String} path - Service path.\r\n * @static\r\n */\r\n static setServicePath(service, path) {\r\n ServiceProvider.servicePaths[service] = path;\r\n }\r\n\r\n /**\r\n * Returns the service path for a certain service.\r\n * @param {String} serviceName - Service name.\r\n * @returns {String} The service path.\r\n * @static\r\n */\r\n static getServicePath(serviceName) {\r\n return ServiceProvider.servicePaths[serviceName];\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Service provider integration path.\r\n * @type {String}\r\n */\r\n static get integrationPath() {\r\n return ServiceProvider._integrationPath;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service provider integration path.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set integrationPath(value) {\r\n ServiceProvider._integrationPath = value;\r\n }\r\n\r\n /**\r\n * Returns the server URL in the form protocol://serverName:serverPort.\r\n * @return {String} The client side server path.\r\n */\r\n static getServerURL() {\r\n const url = window.location.href;\r\n const arr = url.split(\"/\");\r\n const result = `${arr[0]}//${arr[2]}`;\r\n return result;\r\n }\r\n\r\n /**\r\n * Inits {@link this} class. Uses {@link this.integrationPath} as\r\n * base path to generate all backend services paths.\r\n * @param {Object} parameters - Function parameters.\r\n * @param {String} parameters.integrationPath - Service path.\r\n */\r\n static init(parameters) {\r\n ServiceProvider.parameters = parameters;\r\n // Services path (tech dependant).\r\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\r\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\r\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\r\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\r\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\r\n\r\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\r\n // for example: /app/service. For them we calculate the absolute URL path, i.e\r\n // protocol://domain:port/app/service\r\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\r\n const serverPath = ServiceProvider.getServerURL();\r\n configurationURI = serverPath + configurationURI;\r\n showImageURI = serverPath + showImageURI;\r\n createImageURI = serverPath + createImageURI;\r\n getMathMLURI = serverPath + getMathMLURI;\r\n serviceURI = serverPath + serviceURI;\r\n }\r\n\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\r\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\r\n ServiceProvider.setServicePath(\"service\", serviceURI);\r\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n\r\n ServiceProvider.listeners.fire(\"onInit\", {});\r\n }\r\n\r\n /**\r\n * Gets the content from an URL.\r\n * @param {String} url - Target URL.\r\n * @param {Object} [postVariables] - Object containing post variables.\r\n * null if a GET query should be done.\r\n * @returns {String} Content of the target URL.\r\n * @private\r\n * @static\r\n */\r\n static getUrl(url, postVariables) {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n const httpRequest = Util.createHttpRequest();\r\n\r\n if (httpRequest) {\r\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\r\n httpRequest.open(\"GET\", url, false);\r\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\r\n httpRequest.open(\"POST\", url, false);\r\n } else {\r\n httpRequest.open(\"POST\", currentPath + url, false);\r\n }\r\n\r\n let header = Configuration.get(\"customHeaders\");\r\n if (header) {\r\n if (typeof header === \"string\") {\r\n header = Util.convertStringToObject(header);\r\n }\r\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\r\n }\r\n\r\n if (typeof postVariables !== \"undefined\" && postVariables) {\r\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\r\n httpRequest.send(Util.httpBuildQuery(postVariables));\r\n } else {\r\n httpRequest.send(null);\r\n }\r\n\r\n return httpRequest.responseText;\r\n }\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Returns the response text of a certain service.\r\n * @param {String} service - Service name.\r\n * @param {String} postVariables - Post variables.\r\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\r\n * @returns {String} Service response text.\r\n */\r\n static getService(service, postVariables, get) {\r\n let response;\r\n if (get === true) {\r\n const getVariables = postVariables ? `?${postVariables}` : \"\";\r\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\r\n response = ServiceProvider.getUrl(serviceUrl);\r\n } else {\r\n const serviceUrl = ServiceProvider.getServicePath(service);\r\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\r\n }\r\n return response;\r\n }\r\n\r\n /**\r\n * Returns the server language of a certain service. The possible values\r\n * are: php, aspx, java and ruby.\r\n * This method has backward compatibility purposes.\r\n * @param {String} service - The configuration service.\r\n * @returns {String} - The server technology associated with the configuration service.\r\n */\r\n static getServerLanguageFromService(service) {\r\n if (service.indexOf(\".php\") !== -1) {\r\n return \"php\";\r\n }\r\n if (service.indexOf(\".aspx\") !== -1) {\r\n return \"aspx\";\r\n }\r\n if (service.indexOf(\"wirispluginengine\") !== -1) {\r\n return \"ruby\";\r\n }\r\n return \"java\";\r\n }\r\n\r\n /**\r\n * Returns the URI associated with a certain service.\r\n * @param {String} service - The service name.\r\n * @return {String} The service path.\r\n */\r\n static createServiceURI(service) {\r\n const extension = ServiceProvider.serverExtension();\r\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\r\n }\r\n\r\n static serverExtension() {\r\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\r\n return \".php\";\r\n }\r\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\r\n return \".aspx\";\r\n }\r\n return \"\";\r\n }\r\n}\r\n\r\n/**\r\n * @property {String} service - The service name.\r\n * @property {String} path - The service path.\r\n * @static\r\n */\r\nServiceProvider._servicePaths = {};\r\n\r\n/**\r\n * The integration path. Contains the path of the configuration service.\r\n * Used to define the path for all services.\r\n * @type {String}\r\n * @private\r\n */\r\nServiceProvider._integrationPath = \"\";\r\n\r\n/**\r\n * ServiceProvider static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nServiceProvider._listeners = new Listeners();\r\n\r\n/**\r\n * Service provider parameters.\r\n * @type {ServiceProviderParameters}\r\n */\r\nServiceProvider._parameters = {};\r\n","import TextCache from \"./textcache\";\r\nimport MathML from \"./mathml\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a LaTeX parser. Manages the services which allows to convert\r\n * LaTeX into MathML and MathML into LaTeX.\r\n */\r\nexport default class Latex {\r\n /**\r\n * Static property.\r\n * Return latex cache.\r\n * @private\r\n * @type {Cache}\r\n */\r\n static get cache() {\r\n return Latex._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set latex cache.\r\n * @param {Cache} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Latex._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\r\n * we call a text service with the param mathml2latex.\r\n * @param {String} mathml - MathML String.\r\n * @return {String} LaTeX string generated by the MathML argument.\r\n */\r\n static getLatexFromMathML(mathml) {\r\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\r\n /**\r\n * @type {TextCache}\r\n */\r\n const { cache } = Latex;\r\n\r\n const data = {\r\n service: \"mathml2latex\",\r\n mml: mathmlWithoutSemantics,\r\n };\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n // TODO: Error handling.\r\n let latex = \"\";\r\n\r\n if (jsonResponse.status === \"ok\") {\r\n latex = jsonResponse.result.text;\r\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\r\n // Inserting LaTeX semantics.\r\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\r\n cache.populate(latex, mathmlWithSemantics);\r\n }\r\n\r\n return latex;\r\n }\r\n\r\n /**\r\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\r\n * we call a text service with the param latex2mathml.\r\n * @param {String} latex - String containing a LaTeX formula.\r\n * @param {Boolean} includeLatexOnSemantics\r\n * - If true LaTeX would me included into MathML semantics.\r\n * @return {String} MathML string generated by the LaTeX argument.\r\n */\r\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\r\n /**\r\n * @type {TextCache}\r\n */\r\n const latexCache = Latex.cache;\r\n\r\n if (Latex.cache.get(latex)) {\r\n return Latex.cache.get(latex);\r\n }\r\n const data = {\r\n service: \"latex2mathml\",\r\n latex,\r\n };\r\n\r\n if (includeLatexOnSemantics) {\r\n data.saveLatex = \"\";\r\n }\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n let output;\r\n if (jsonResponse.status === \"ok\") {\r\n let mathml = jsonResponse.result.text;\r\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\r\n\r\n // Populate LatexCache.\r\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\r\n const content = Util.htmlEntities(latex);\r\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\r\n output = mathml;\r\n } else {\r\n output = mathml;\r\n }\r\n if (!latexCache.get(latex)) {\r\n latexCache.populate(latex, mathml);\r\n }\r\n } else {\r\n output = `$$${latex}$$`;\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML code to LaTeX.\r\n * The MathML code should containing to be converted.\r\n * @param {String} content - A string containing MathML valid code.\r\n * @param {Object} characters - An object containing special characters.\r\n * @return {String} A string containing all MathML annotated occurrences\r\n * replaced by the corresponding LaTeX code.\r\n */\r\n static parseMathmlToLatex(content, characters) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\r\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n let mathml;\r\n let startAnnotation;\r\n let closeAnnotation;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n mathml = content.substring(start, end);\r\n\r\n startAnnotation = mathml.indexOf(openTarget);\r\n if (startAnnotation !== -1) {\r\n startAnnotation += openTarget.length;\r\n closeAnnotation = mathml.indexOf(closeTarget);\r\n let latex = mathml.substring(startAnnotation, closeAnnotation);\r\n if (characters === Constants.safeXmlCharacters) {\r\n latex = MathML.safeXmlDecode(latex);\r\n }\r\n output += `$$${latex}$$`;\r\n // Populate latex into cache.\r\n\r\n Latex.cache.populate(latex, mathml);\r\n } else {\r\n output += mathml;\r\n }\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Extracts the latex of a determined position in a text.\r\n * @param {Node} textNode - textNode to extract the LaTeX\r\n * @param {Number} caretPosition - Starting position to find LaTeX.\r\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\r\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\r\n * \"$$\" by default.\r\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\r\n * @static\r\n */\r\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\r\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\r\n // Tags used for LaTeX formulas.\r\n const defaultLatexTags = {\r\n open: \"$$\",\r\n close: \"$$\",\r\n };\r\n // latexTags is an optional parameter. If is not set, use default latexTags.\r\n if (typeof latexTags === \"undefined\" || latexTags == null) {\r\n latexTags = defaultLatexTags;\r\n }\r\n // Looking for the first textNode.\r\n let startNode = textNode;\r\n\r\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n startNode = startNode.previousSibling;\r\n }\r\n\r\n /**\r\n * Returns the next latex position and node from a specific node and position.\r\n * @param {Node} currentNode - Node where searching latex.\r\n * @param {Number} currentPosition - Current position inside the currentNode.\r\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\r\n * \"$$\" by default.\r\n * @param {Boolean} tag - Tag containing the current search.\r\n * @returns {Object} Object containing the current node and the position.\r\n */\r\n function getNextLatexPosition(currentNode, currentPosition, tag) {\r\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\r\n\r\n while (position === -1) {\r\n currentNode = currentNode.nextSibling;\r\n\r\n if (!currentNode) {\r\n // TEXT_NODE.\r\n return null; // Not found.\r\n }\r\n\r\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\r\n }\r\n\r\n return {\r\n node: currentNode,\r\n position,\r\n };\r\n }\r\n\r\n /**\r\n * Determines if a node is previous, or not, to a second one.\r\n * @param {Node} node - Start node.\r\n * @param {Number} position - Start node position.\r\n * @param {Node} endNode - End node.\r\n * @param {Number} endPosition - End node position.\r\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\r\n */\r\n function isPrevious(node, position, endNode, endPosition) {\r\n if (node === endNode) {\r\n return position <= endPosition;\r\n }\r\n while (node && node !== endNode) {\r\n node = node.nextSibling;\r\n }\r\n\r\n return node === endNode;\r\n }\r\n\r\n let start;\r\n let end = {\r\n node: startNode,\r\n position: 0,\r\n };\r\n // Is supposed that open and close tags has the same length.\r\n const tagLength = latexTags.open.length;\r\n do {\r\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\r\n\r\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\r\n return null;\r\n }\r\n\r\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\r\n\r\n if (end == null) {\r\n return null;\r\n }\r\n\r\n end.position += tagLength;\r\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\r\n\r\n // Isolating latex.\r\n let latex;\r\n\r\n if (start.node === end.node) {\r\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\r\n } else {\r\n const index = start.position + tagLength;\r\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\r\n let currentNode = start.node;\r\n\r\n do {\r\n currentNode = currentNode.nextSibling;\r\n if (currentNode === end.node) {\r\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\r\n } else {\r\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\r\n }\r\n } while (currentNode !== end.node);\r\n }\r\n\r\n return {\r\n latex,\r\n startNode: start.node,\r\n startPosition: start.position,\r\n endNode: end.node,\r\n endPosition: end.position,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\r\n * @type {Cache}\r\n * @static\r\n */\r\nLatex._cache = new TextCache();\r\n","import translations from \"../lang/strings.json\";\r\n/**\r\n * This class represents a string manager. It's used to load localized strings.\r\n */\r\nexport default class StringManager {\r\n constructor() {\r\n throw new Error(\"Static class StringManager can not be instantiated.\");\r\n }\r\n\r\n /**\r\n * Returns the associated value of certain string key. If the associated value\r\n * doesn't exits returns the original key.\r\n * @param {string} key - string key\r\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\r\n * @returns {string} correspondent value. If doesn't exists original key.\r\n */\r\n static get(key, lang) {\r\n // Default language definition\r\n let { language } = this;\r\n\r\n // If parameter language, use it\r\n if (lang) {\r\n language = lang;\r\n }\r\n\r\n // Cut down on strings. e.g. en_US -> en\r\n if (language && language.length > 2) {\r\n language = language.slice(0, 2);\r\n }\r\n\r\n // Check if we support the language\r\n if (!this.strings.hasOwnProperty(language)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown language ${language} set in StringManager.`);\r\n language = \"en\";\r\n }\r\n\r\n // Check if the key is supported in the given language\r\n if (!this.strings[language].hasOwnProperty(key)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\r\n return key;\r\n }\r\n\r\n return this.strings[language][key];\r\n }\r\n}\r\n\r\n/**\r\n * Dictionary of dictionaries:\r\n * Key: language code\r\n * Value: Key: id of the string\r\n * Value: translation of the string\r\n */\r\nStringManager.strings = translations;\r\n\r\n/**\r\n * Language of the translations; English by default\r\n */\r\nStringManager.language = \"en\";\r\n","/* eslint-disable no-bitwise */\r\nimport DOMPurify from \"dompurify\";\r\nimport MathML from \"./mathml\";\r\nimport Configuration from \"./configuration\";\r\nimport Latex from \"./latex\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * This class represents an utility class.\r\n */\r\nexport default class Util {\r\n /**\r\n * Fires an event in a target.\r\n * @param {EventTarget} eventTarget - target where event should be fired.\r\n * @param {string} eventName event to fire.\r\n * @static\r\n */\r\n static fireEvent(eventTarget, eventName) {\r\n if (document.createEvent) {\r\n const eventObject = document.createEvent(\"HTMLEvents\");\r\n eventObject.initEvent(eventName, true, true);\r\n return !eventTarget.dispatchEvent(eventObject);\r\n }\r\n\r\n const eventObject = document.createEventObject();\r\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\r\n }\r\n\r\n /**\r\n * Cross-browser addEventListener/attachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - callback function.\r\n * @static\r\n */\r\n static addEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.addEventListener) {\r\n eventTarget.addEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.attachEvent) {\r\n // Backwards compatibility.\r\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * Cross-browser removeEventListener/detachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - function to remove from the event target.\r\n * @static\r\n */\r\n static removeEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.removeEventListener) {\r\n eventTarget.removeEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.detachEvent) {\r\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * A map from event target to event handlers so we can remove the event\r\n * listeners in removeElementEvents\r\n *\r\n * @type {Map}\r\n * @static\r\n */\r\n static elementEventsMap = new Map();\r\n\r\n /**\r\n * Adds the a callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\r\n * @param {Function} mousedownHandler - function to run when on mousedown event.\r\n * @param {Function} mouseupHandler - function to run when on mouseup event.\r\n * @static\r\n */\r\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\r\n // Make sure not to leak event listeners if we've already added events to\r\n // this element\r\n Util.removeElementEvents(eventTarget);\r\n\r\n let entry = {};\r\n Util.elementEventsMap.set(eventTarget, entry);\r\n\r\n if (doubleClickHandler) {\r\n entry.callbackDblclick = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n doubleClickHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n }\r\n\r\n if (mousedownHandler) {\r\n entry.callbackMousedown = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mousedownHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n }\r\n\r\n if (mouseupHandler) {\r\n entry.callbackMouseup = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mouseupHandler(element, realEvent);\r\n };\r\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\r\n // while the mouse is outside the editor text field.\r\n // This is a workaround: we trigger the event independently of where the mouse\r\n // is when we release its button.\r\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n }\r\n\r\n /**\r\n * Remove all callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @static\r\n */\r\n static removeElementEvents(eventTarget) {\r\n let entry = Util.elementEventsMap.get(eventTarget);\r\n if (!entry) {\r\n return;\r\n }\r\n\r\n Util.elementEventsMap.delete(eventTarget);\r\n\r\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n\r\n /**\r\n * Adds a class name to a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static addClass(element, className) {\r\n if (!Util.containsClass(element, className)) {\r\n element.className += ` ${className}`;\r\n }\r\n }\r\n\r\n /**\r\n * Checks if a HTMLElement contains a certain class.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the className.\r\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\r\n * @static\r\n */\r\n static containsClass(element, className) {\r\n if (element == null || !(\"className\" in element)) {\r\n return false;\r\n }\r\n\r\n const currentClasses = element.className.split(\" \");\r\n\r\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\r\n if (currentClasses[i] === className) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Remove a certain class for a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static removeClass(element, className) {\r\n let newClassName = \"\";\r\n const classes = element.className.split(\" \");\r\n\r\n for (let i = 0; i < classes.length; i += 1) {\r\n if (classes[i] !== className) {\r\n newClassName += `${classes[i]} `;\r\n }\r\n }\r\n element.className = newClassName.trim();\r\n }\r\n\r\n /**\r\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\r\n * used to parse old applets.\r\n * @param {string} text - string containing safeXml characters\r\n * @returns {string} a string with safeXml characters parsed.\r\n * @static\r\n */\r\n static convertOldXmlinitialtextAttribute(text) {\r\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\r\n // This could be removed in future.\r\n const val = \"value=\";\r\n\r\n const xitpos = text.indexOf(\"xmlinitialtext\");\r\n const valpos = text.indexOf(val, xitpos);\r\n const quote = text.charAt(valpos + val.length);\r\n const startquote = valpos + val.length + 1;\r\n const endquote = text.indexOf(quote, startquote);\r\n\r\n const value = text.substring(startquote, endquote);\r\n\r\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\r\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\r\n newvalue = newvalue.split(\"&\").join(\"ยง\");\r\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\r\n\r\n text = text.split(value).join(newvalue);\r\n return text;\r\n }\r\n\r\n /**\r\n * Convert a string representation of key-value pairs to an object.\r\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\r\n * @returns {Object} - Object containing the key-value pairs\r\n */\r\n static convertStringToObject(keyValueString) {\r\n if (!keyValueString || typeof keyValueString !== \"string\") {\r\n return {};\r\n }\r\n\r\n return keyValueString\r\n .split(\",\")\r\n .map((pair) => pair.trim().split(\"=\"))\r\n .reduce((resultObject, [key, value]) => {\r\n if (key && value) {\r\n resultObject[key] = value;\r\n }\r\n return resultObject;\r\n }, {});\r\n }\r\n\r\n /**\r\n * Cross-browser solution for creating new elements.\r\n * @param {string} tagName - tag name of the wished element.\r\n * @param {Object} attributes - an object where each key is a wished\r\n * attribute name and each value is its value.\r\n * @param {Object} [creator] - if supplied, this function will use\r\n * the \"createElement\" method from this param. Otherwise\r\n * document will be used as creator.\r\n * @returns {Element} The DOM element with the specified attributes assigned.\r\n * @static\r\n */\r\n static createElement(tagName, attributes, creator) {\r\n if (attributes === undefined) {\r\n attributes = {};\r\n }\r\n\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n let element;\r\n\r\n /*\r\n * Internet Explorer fix:\r\n * If you create a new object dynamically, you can't set a non-standard attribute.\r\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\r\n * Other browsers will throw an exception and will run the standard code.\r\n */\r\n try {\r\n let html = `<${tagName}`;\r\n\r\n Object.keys(attributes).forEach((attributeName) => {\r\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\r\n });\r\n html += \">\";\r\n element = creator.createElement(html);\r\n } catch (e) {\r\n element = creator.createElement(tagName);\r\n Object.keys(attributes).forEach((attributeName) => {\r\n element.setAttribute(attributeName, attributes[attributeName]);\r\n });\r\n }\r\n return element;\r\n }\r\n\r\n /**\r\n * Creates new HTML from it's HTML code as string.\r\n * @param {string} objectCode - html code\r\n * @returns {Element} the HTML element.\r\n * @static\r\n */\r\n static createObject(objectCode, creator) {\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\r\n objectCode = objectCode\r\n .split(\"\").join(\"\").split(\"\").join(\"\");\r\n\r\n objectCode = objectCode\r\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\r\n\r\n const container = Util.createElement(\"div\", {}, creator);\r\n container.innerHTML = objectCode;\r\n\r\n function recursiveParamsFix(object) {\r\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const param = Util.createElement(\"param\", attributesParsed, creator);\r\n\r\n // IE fix.\r\n if (param.NAME) {\r\n param.name = param.NAME;\r\n param.value = param.VALUE;\r\n }\r\n\r\n param.removeAttribute(\"wirisObject\");\r\n object.parentNode.replaceChild(param, object);\r\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\r\n applet.removeAttribute(\"wirisObject\");\r\n\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n\r\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\r\n applet.appendChild(object.childNodes[i]);\r\n i -= 1; // When we insert the object child into the applet, object loses one child.\r\n }\r\n }\r\n\r\n object.parentNode.replaceChild(applet, object);\r\n } else {\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n }\r\n }\r\n }\r\n\r\n recursiveParamsFix(container);\r\n return container.firstChild;\r\n }\r\n\r\n /**\r\n * Converts an Element to its HTML code.\r\n * @param {Element} element - entry element.\r\n * @return {string} the HTML code of the input element.\r\n * @static\r\n */\r\n static createObjectCode(element) {\r\n // In case that the image was not created, the object can be null or undefined.\r\n if (typeof element === \"undefined\" || element === null) {\r\n return null;\r\n }\r\n\r\n if (element.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let output = `<${element.tagName}`;\r\n\r\n for (let i = 0; i < element.attributes.length; i += 1) {\r\n if (element.attributes[i].specified) {\r\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\r\n }\r\n }\r\n\r\n if (element.childNodes.length > 0) {\r\n output += \">\";\r\n\r\n for (let i = 0; i < element.childNodes.length; i += 1) {\r\n output += Util.createObject(element.childNodes[i]);\r\n }\r\n\r\n output += ``;\r\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\r\n output += `>`;\r\n } else {\r\n output += \"/>\";\r\n }\r\n\r\n return output;\r\n }\r\n\r\n if (element.nodeType === 3) {\r\n // TEXT_NODE.\r\n return Util.htmlEntities(element.nodeValue);\r\n }\r\n\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Concatenates two URL paths.\r\n * @param {string} path1 - first URL path\r\n * @param {string} path2 - second URL path\r\n * @returns {string} new URL.\r\n */\r\n static concatenateUrl(path1, path2) {\r\n let separator = \"\";\r\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\r\n separator = \"/\";\r\n }\r\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all HTML special characters by their correspondent entities.\r\n * @param {string} input - text to be parsed.\r\n * @returns {string} the input text with all their special characters replaced by their entities.\r\n * @static\r\n */\r\n static htmlEntities(input) {\r\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\r\n }\r\n\r\n /**\r\n * Sanitize HTML to prevent XSS injections.\r\n * @param {string} html - html to be sanitize.\r\n * @returns {string} html sanitized.\r\n * @static\r\n */\r\n static htmlSanitize(html) {\r\n const annotationRegex = /\\/;\r\n // Get all the annotation content including the tags.\r\n const annotation = html.match(annotationRegex);\r\n // Sanitize html code without removing our supported MathML tags and attributes.\r\n html = DOMPurify.sanitize(html, {\r\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\r\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\r\n });\r\n // Readd old annotation content.\r\n return html.replace(annotationRegex, annotation);\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all the HTML entities by their characters.\r\n * @param {string} input - text to be parsed\r\n * @returns {string} the input text with all their entities replaced by characters.\r\n * @static\r\n */\r\n static htmlEntitiesDecode(input) {\r\n // Textarea element decodes when inner html is set.\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.innerHTML = input;\r\n return textarea.value;\r\n }\r\n\r\n /**\r\n * Returns a cross-browser http request.\r\n * @return {object} httpRequest request object.\r\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\r\n */\r\n static createHttpRequest() {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n if (currentPath.substr(0, 7) === \"file://\") {\r\n throw StringManager.get(\"exception_cross_site\");\r\n }\r\n\r\n if (typeof XMLHttpRequest !== \"undefined\") {\r\n return new XMLHttpRequest();\r\n }\r\n\r\n try {\r\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\r\n } catch (e) {\r\n try {\r\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\r\n } catch (oc) {\r\n return null;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Converts a hash to a HTTP query.\r\n * @param {Object[]} properties - a key/value object.\r\n * @returns {string} a HTTP query containing all the key value pairs with\r\n * all the special characters replaced by their entities.\r\n * @static\r\n */\r\n static httpBuildQuery(properties) {\r\n let result = \"\";\r\n\r\n Object.keys(properties).forEach((i) => {\r\n if (properties[i] != null) {\r\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\r\n }\r\n });\r\n\r\n // Deleting last '&' empty character.\r\n if (result.substring(result.length - 1) === \"&\") {\r\n result = result.substring(0, result.length - 1);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Convert a hash to string sorting keys to get a deterministic output\r\n * @param {Object[]} hash - a key/value object.\r\n * @returns {string} a string with the form key1=value1...keyn=valuen\r\n * @static\r\n */\r\n static propertiesToString(hash) {\r\n // 1. Sort keys. We sort the keys because we want a deterministic output.\r\n const keys = [];\r\n Object.keys(hash).forEach((key) => {\r\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\r\n keys.push(key);\r\n }\r\n });\r\n\r\n const n = keys.length;\r\n for (let i = 0; i < n; i += 1) {\r\n for (let j = i + 1; j < n; j += 1) {\r\n const s1 = keys[i];\r\n const s2 = keys[j];\r\n if (Util.compareStrings(s1, s2) > 0) {\r\n // Swap.\r\n keys[i] = s2;\r\n keys[j] = s1;\r\n }\r\n }\r\n }\r\n\r\n // 2. Generate output.\r\n let output = \"\";\r\n for (let i = 0; i < n; i += 1) {\r\n const key = keys[i];\r\n output += key;\r\n output += \"=\";\r\n let value = hash[key];\r\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\r\n value = value.replace(\"\\n\", \"\\\\n\");\r\n value = value.replace(\"\\r\", \"\\\\r\");\r\n value = value.replace(\"\\t\", \"\\\\t\");\r\n\r\n output += value;\r\n output += \"\\n\";\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Compare two strings using charCodeAt method\r\n * @param {string} a - first string to compare.\r\n * @param {string} b - second string to compare.\r\n * @returns {number} the difference between a and b\r\n * @static\r\n */\r\n static compareStrings(a, b) {\r\n let i;\r\n const an = a.length;\r\n const bn = b.length;\r\n const n = an > bn ? bn : an;\r\n for (i = 0; i < n; i += 1) {\r\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\r\n if (c !== 0) {\r\n return c;\r\n }\r\n }\r\n return a.length - b.length;\r\n }\r\n\r\n /**\r\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\r\n * @param {string} string - input string\r\n * @param {number} idx - an integer greater than or equal to 0\r\n * and less than the length of the string\r\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\r\n * @static\r\n */\r\n static fixedCharCodeAt(string, idx) {\r\n idx = idx || 0;\r\n const code = string.charCodeAt(idx);\r\n let hi;\r\n let low;\r\n\r\n /* High surrogate (could change last hex to 0xDB7F to treat high\r\n private surrogates as single characters) */\r\n\r\n if (code >= 0xd800 && code <= 0xdbff) {\r\n hi = code;\r\n low = string.charCodeAt(idx + 1);\r\n if (Number.isNaN(low)) {\r\n throw StringManager.get(\"exception_high_surrogate\");\r\n }\r\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\r\n }\r\n\r\n if (code >= 0xdc00 && code <= 0xdfff) {\r\n // Low surrogate.\r\n /* We return false to allow loops to skip this iteration since should have\r\n already handled high surrogate above in the previous iteration. */\r\n return false;\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Returns an URL with it's query params converted into array.\r\n * @param {string} url - input URL.\r\n * @returns {Object[]} an array containing all URL query params.\r\n * @static\r\n */\r\n static urlToAssArray(url) {\r\n let i;\r\n i = url.indexOf(\"?\");\r\n if (i > 0) {\r\n const query = url.substring(i + 1);\r\n const ss = query.split(\"&\");\r\n const h = {};\r\n for (i = 0; i < ss.length; i += 1) {\r\n const s = ss[i];\r\n const kv = s.split(\"=\");\r\n if (kv.length > 1) {\r\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\r\n }\r\n }\r\n return h;\r\n }\r\n return {};\r\n }\r\n\r\n /**\r\n * Returns an encoded URL by replacing each instance of certain characters by\r\n * one, two, three or four escape sequences using encodeURIComponent method.\r\n * !'()* . will not be encoded.\r\n *\r\n * @param {string} clearString - URL string to be encoded\r\n * @returns {string} URL with it's special characters replaced.\r\n * @static\r\n */\r\n static urlEncode(clearString) {\r\n let output = \"\";\r\n // Method encodeURIComponent doesn't encode !'()*~ .\r\n output = encodeURIComponent(clearString);\r\n return output;\r\n }\r\n\r\n // TODO: To parser?\r\n /**\r\n * Converts the HTML of a image into the output code that WIRIS must return.\r\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\r\n * or the Wiriscas attribute of a WIRIS applet.\r\n * @param {string} imgCode - the html code from a formula or a CAS image.\r\n * @param {boolean} convertToXml - true if the image should be converted to XML.\r\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\r\n * @returns {string} the XML or safeXML of a WIRIS image.\r\n * @static\r\n */\r\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\r\n const imgObject = Util.createObject(imgCode);\r\n if (imgObject) {\r\n if (\r\n imgObject.className === Configuration.get(\"imageClassName\") ||\r\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\r\n ) {\r\n if (!convertToXml) {\r\n return imgCode;\r\n }\r\n\r\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n // To handle annotations, first we need the MathML in XML.\r\n let mathML = MathML.safeXmlDecode(dataMathML);\r\n\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\r\n }\r\n\r\n if (mathML == null) {\r\n mathML = imgObject.getAttribute(\"alt\");\r\n }\r\n\r\n if (convertToSafeXml) {\r\n const safeMathML = MathML.safeXmlEncode(mathML);\r\n return safeMathML;\r\n }\r\n\r\n return mathML;\r\n }\r\n }\r\n return imgCode;\r\n }\r\n\r\n /**\r\n * Gets the node length in characters.\r\n * @param {Node} node - HTML node.\r\n * @returns {number} node length.\r\n * @static\r\n */\r\n static getNodeLength(node) {\r\n const staticNodeLengths = {\r\n IMG: 1,\r\n BR: 1,\r\n };\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return node.nodeValue.length;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\r\n\r\n if (length === undefined) {\r\n length = 0;\r\n }\r\n\r\n for (let i = 0; i < node.childNodes.length; i += 1) {\r\n length += Util.getNodeLength(node.childNodes[i]);\r\n }\r\n return length;\r\n }\r\n return 0;\r\n }\r\n\r\n /**\r\n * Gets a selected node or text from an editable HTMLElement.\r\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\r\n * @param {HTMLElement} target - the editable HTMLElement.\r\n * @param {boolean} isIframe - specifies if the target is an iframe or not\r\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\r\n * the current selection and uses window.getSelection()\r\n * @returns {object} an object with the 'node' key set if the item is an\r\n * element or the keys 'node' and 'caretPosition' if the element is text.\r\n * @static\r\n */\r\n static getSelectedItem(target, isIframe, forceGetSelection) {\r\n let windowTarget;\r\n\r\n if (isIframe) {\r\n windowTarget = target.contentWindow;\r\n windowTarget.focus();\r\n } else {\r\n windowTarget = window;\r\n target.focus();\r\n }\r\n\r\n if (document.selection && !forceGetSelection) {\r\n const range = windowTarget.document.selection.createRange();\r\n\r\n if (range.parentElement) {\r\n if (range.htmlText.length > 0) {\r\n if (range.text.length === 0) {\r\n return Util.getSelectedItem(target, isIframe, true);\r\n }\r\n\r\n return null;\r\n }\r\n\r\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\r\n let temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\r\n // IE9 fix: parentElement() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML('');\r\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\r\n }\r\n\r\n let node;\r\n let caretPosition;\r\n\r\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = temporalObject.nextSibling;\r\n caretPosition = 0;\r\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\r\n node = temporalObject.previousSibling;\r\n caretPosition = node.nodeValue.length;\r\n } else {\r\n node = windowTarget.document.createTextNode(\"\");\r\n temporalObject.parentNode.insertBefore(node, temporalObject);\r\n caretPosition = 0;\r\n }\r\n\r\n temporalObject.parentNode.removeChild(temporalObject);\r\n\r\n return {\r\n node,\r\n caretPosition,\r\n };\r\n }\r\n\r\n if (range.length > 1) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: range.item(0),\r\n };\r\n }\r\n\r\n if (windowTarget.getSelection) {\r\n let range;\r\n const selection = windowTarget.getSelection();\r\n\r\n try {\r\n range = selection.getRangeAt(0);\r\n } catch (e) {\r\n range = windowTarget.document.createRange();\r\n }\r\n\r\n const node = range.startContainer;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return {\r\n node,\r\n caretPosition: range.startOffset,\r\n };\r\n }\r\n\r\n if (node !== range.endContainer) {\r\n return null;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n const position = range.startOffset;\r\n\r\n if (node.childNodes[position]) {\r\n // In case that a formula is detected but not selected,\r\n // we create an empty span where we could insert the new formula.\r\n if (range.startOffset === range.endOffset) {\r\n if (\r\n position !== 0 &&\r\n node.childNodes[position - 1].localName === \"span\" &&\r\n node.childNodes[position].classList?.contains(\"Wirisformula\")\r\n ) {\r\n node.childNodes[position - 1].remove();\r\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\r\n }\r\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\r\n if (\r\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\r\n position === 0\r\n ) {\r\n const emptySpan = document.createElement(\"span\");\r\n node.insertBefore(emptySpan, node.childNodes[position]);\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Returns null if there isn't any item or if it is malformed.\r\n * Otherwise returns an object containing the node with the MathML image\r\n * and the cursor position inside the textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea element.\r\n * @returns {Object} An object containing the node, the index of the\r\n * beginning of the selected text, caret position and the start and end position of the\r\n * text node.\r\n * @static\r\n */\r\n static getSelectedItemOnTextarea(textarea) {\r\n const textNode = document.createTextNode(textarea.value);\r\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\r\n if (textNodeValues === null) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: textNode,\r\n caretPosition: textarea.selectionStart,\r\n startPosition: textNodeValues.startPosition,\r\n endPosition: textNodeValues.endPosition,\r\n };\r\n }\r\n\r\n /**\r\n * Looks for elements that match the given name in a HTML code string.\r\n * Important: this function is very concrete for WIRIS code.\r\n * It takes as preconditions lots of behaviors that are not the general case.\r\n * @param {string} code - HTML code.\r\n * @param {string} name - element name.\r\n * @param {boolean} autoClosed - true if the elements are autoClosed.\r\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\r\n * @static\r\n */\r\n static getElementsByNameFromString(code, name, autoClosed) {\r\n const elements = [];\r\n code = code.toLowerCase();\r\n name = name.toLowerCase();\r\n let start = code.indexOf(`<${name} `);\r\n\r\n while (start !== -1) {\r\n // Look for nodes.\r\n let endString;\r\n\r\n if (autoClosed) {\r\n endString = \">\";\r\n } else {\r\n endString = ``;\r\n }\r\n\r\n let end = code.indexOf(endString, start);\r\n\r\n if (end !== -1) {\r\n end += endString.length;\r\n elements.push({\r\n start,\r\n end,\r\n });\r\n } else {\r\n end = start + 1;\r\n }\r\n\r\n start = code.indexOf(`<${name} `, end);\r\n }\r\n\r\n return elements;\r\n }\r\n\r\n /**\r\n * Returns the numeric value of a base64 character.\r\n * @param {string} character - base64 character.\r\n * @returns {number} base64 character numeric value.\r\n * @static\r\n */\r\n static decode64(character) {\r\n const PLUS = \"+\".charCodeAt(0);\r\n const SLASH = \"/\".charCodeAt(0);\r\n const NUMBER = \"0\".charCodeAt(0);\r\n const LOWER = \"a\".charCodeAt(0);\r\n const UPPER = \"A\".charCodeAt(0);\r\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\r\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\r\n const code = character.charCodeAt(0);\r\n\r\n if (code === PLUS || code === PLUS_URL_SAFE) {\r\n return 62; // Char '+'.\r\n }\r\n if (code === SLASH || code === SLASH_URL_SAFE) {\r\n return 63; // Char '/'.\r\n }\r\n if (code < NUMBER) {\r\n return -1; // No match.\r\n }\r\n if (code < NUMBER + 10) {\r\n return code - NUMBER + 26 + 26;\r\n }\r\n if (code < UPPER + 26) {\r\n return code - UPPER;\r\n }\r\n if (code < LOWER + 26) {\r\n return code - LOWER + 26;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Converts a base64 string to a array of bytes.\r\n * @param {string} b64String - base64 string.\r\n * @param {number} length - dimension of byte array (by default whole string).\r\n * @return {Object[]} the resultant byte array.\r\n * @static\r\n */\r\n static b64ToByteArray(b64String, length) {\r\n let tmp;\r\n\r\n if (b64String.length % 4 > 0) {\r\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\r\n }\r\n\r\n const arr = [];\r\n\r\n let l;\r\n let placeHolders;\r\n if (!length) {\r\n // All b64String string.\r\n if (b64String.charAt(b64String.length - 2) === \"=\") {\r\n placeHolders = 2;\r\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\r\n placeHolders = 1;\r\n } else {\r\n placeHolders = 0;\r\n }\r\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\r\n } else {\r\n l = length;\r\n }\r\n\r\n let i;\r\n for (i = 0; i < l; i += 4) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 18) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\r\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\r\n Util.decode64(b64String.charAt(i + 3));\r\n\r\n arr.push((tmp >> 16) & 0xff);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n if (placeHolders) {\r\n if (placeHolders === 2) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\r\n arr.push(tmp & 0xff);\r\n } else if (placeHolders === 1) {\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 10) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\r\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n }\r\n return arr;\r\n }\r\n\r\n /**\r\n * Returns the first 32-bit signed integer from a byte array.\r\n * @param {Object[]} bytes - array of bytes.\r\n * @returns {number} the 32-bit signed integer.\r\n * @static\r\n */\r\n static readInt32(bytes) {\r\n if (bytes.length < 4) {\r\n return false;\r\n }\r\n const int32 = bytes.splice(0, 4);\r\n // @codingStandardsIgnoreStartยก\r\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read the first byte from a byte array.\r\n * @param {Object} bytes - input byte array.\r\n * @returns {number} first byte of the byte array.\r\n * @static\r\n */\r\n static readByte(bytes) {\r\n // @codingStandardsIgnoreStart\r\n return bytes.shift() << 0;\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\r\n * @param {Object[]} bytes - byte array.\r\n * @param {number} pos - start position.\r\n * @param {number} len - number of bytes to read.\r\n * @returns {Object[]} the byte array.\r\n * @static\r\n */\r\n static readBytes(bytes, pos, len) {\r\n return bytes.splice(pos, len);\r\n }\r\n\r\n /**\r\n * Inserts or modifies formulas or CAS on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea target.\r\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\r\n * call this function as (textarea, Parser.createImageSrc(mathml));\r\n * @static\r\n */\r\n static updateTextArea(textarea, text) {\r\n if (textarea && text) {\r\n textarea.focus();\r\n\r\n if (textarea.selectionStart != null) {\r\n const { selectionEnd } = textarea;\r\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\r\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\r\n textarea.value = selectionStart + text + selectionEndSub;\r\n textarea.selectionEnd = selectionEnd + text.length;\r\n } else {\r\n const selection = document.selection.createRange();\r\n selection.text = text;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Modifies existing formula on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - text area target.\r\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\r\n * to the image,you can call this function as\r\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\r\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\r\n * @param {number} end - ending index from textarea where it needs to be replaced by text\r\n * @static\r\n */\r\n static updateExistingTextOnTextarea(textarea, text, start, end) {\r\n textarea.focus();\r\n const textareaStart = textarea.value.substring(0, start);\r\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\r\n textarea.selectionEnd = start + text.length;\r\n }\r\n\r\n /**\r\n * Add a parameter with it's correspondent value to an URL (GET).\r\n * @param {string} path - URL path\r\n * @param {string} parameter - parameter\r\n * @param {string} value - value\r\n * @static\r\n */\r\n static addArgument(path, parameter, value) {\r\n let sep;\r\n if (path.indexOf(\"?\") > 0) {\r\n sep = \"&\";\r\n } else {\r\n sep = \"?\";\r\n }\r\n return `${path + sep + parameter}=${value}`;\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType Image class. Contains all the logic related\r\n * to MathType images manipulation.\r\n * All MathType images are generated using the appropriate MathType\r\n * integration service: showimage or createimage.\r\n *\r\n * There are two available image formats:\r\n * - svg (default)\r\n * - png\r\n *\r\n * There are two formats for the image src attribute:\r\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\r\n * - A link to the showimage service.\r\n */\r\nexport default class Image {\r\n /**\r\n * Removes data attributes from an image.\r\n * @param {HTMLImageElement} img - Image where remove data attributes.\r\n */\r\n static removeImgDataAttributes(img) {\r\n const attributesToRemove = [];\r\n const { attributes } = img;\r\n\r\n Object.keys(attributes).forEach((key) => {\r\n const attribute = attributes[key];\r\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\r\n // Is preferred keep an array and remove after the search\r\n // because when attribute is removed the array of attributes\r\n // is modified.\r\n attributesToRemove.push(attribute.name);\r\n }\r\n });\r\n\r\n attributesToRemove.forEach((attribute) => {\r\n img.removeAttribute(attribute);\r\n });\r\n }\r\n\r\n /**\r\n * @static\r\n * Clones all MathType image attributes from a HTMLImageElement to another.\r\n * @param {HTMLImageElement} originImg - The original image.\r\n * @param {HTMLImageElement} destImg - The destination image.\r\n */\r\n static clone(originImg, destImg) {\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (!originImg.hasAttribute(customEditorAttributeName)) {\r\n destImg.removeAttribute(customEditorAttributeName);\r\n }\r\n\r\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\r\n const imgAttributes = [\r\n mathmlAttributeName,\r\n customEditorAttributeName,\r\n \"alt\",\r\n \"height\",\r\n \"width\",\r\n \"style\",\r\n \"src\",\r\n \"role\",\r\n ];\r\n\r\n imgAttributes.forEach((iterator) => {\r\n const originAttribute = originImg.getAttribute(iterator);\r\n if (originAttribute) {\r\n destImg.setAttribute(iterator, originAttribute);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Determines whether an img src contains an SVG.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src contains an SVG, false otherwise\r\n */\r\n static isSvg(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;\");\r\n }\r\n\r\n /**\r\n * Determines whether an img src is encoded in base64 or not.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src is encoded in base64, false otherwise\r\n */\r\n static isBase64(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\r\n }\r\n\r\n /**\r\n * Calculates the metrics of a MathType image given the the service response and the image format.\r\n * @param {HTMLImageElement} img - The HTMLImageElement.\r\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\r\n * @param {Boolean} jsonResponse - True the response of the image service is a\r\n * JSON object. False otherwise.\r\n */\r\n static setImgSize(img, uri, jsonResponse) {\r\n let ar;\r\n let base64String;\r\n let bytes;\r\n let svgString;\r\n if (jsonResponse) {\r\n // Cleaning data:image/png;base64.\r\n if (Image.isSvg(img)) {\r\n // SVG format.\r\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\r\n if (!Image.isBase64(img)) {\r\n ar = Image.getMetricsFromSvgString(uri);\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n svgString = \"\";\r\n bytes = Util.b64ToByteArray(base64String, base64String.length);\r\n for (let i = 0; i < bytes.length; i += 1) {\r\n svgString += String.fromCharCode(bytes[i]);\r\n }\r\n ar = Image.getMetricsFromSvgString(svgString);\r\n }\r\n // PNG format: we store all metrics information in the first 88 bytes.\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n bytes = Util.b64ToByteArray(base64String, 88);\r\n ar = Image.getMetricsFromBytes(bytes);\r\n }\r\n // Backwards compatibility: we store the metrics into createimage response.\r\n } else {\r\n ar = Util.urlToAssArray(uri);\r\n }\r\n let width = ar.cw;\r\n if (!width) {\r\n return;\r\n }\r\n let height = ar.ch;\r\n let baseline = ar.cb;\r\n const { dpi } = ar;\r\n if (dpi) {\r\n width = (width * 96) / dpi;\r\n height = (height * 96) / dpi;\r\n baseline = (baseline * 96) / dpi;\r\n }\r\n img.width = width;\r\n img.height = height;\r\n img.style.verticalAlign = `-${height - baseline}px`;\r\n }\r\n\r\n /**\r\n * Calculates the metrics of an image which has been resized. Is used to restore the original\r\n * metrics of a resized image.\r\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\r\n */\r\n static fixAfterResize(img) {\r\n img.removeAttribute(\"style\");\r\n img.removeAttribute(\"width\");\r\n img.removeAttribute(\"height\");\r\n // In order to avoid resize with max-width css property.\r\n img.style.maxWidth = \"none\";\r\n\r\n const processImg = (img) => {\r\n if (img.src.indexOf(\"data:image\") !== -1) {\r\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\r\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\r\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\r\n // (which would actually make more sense for readibility and efficiency).\r\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\r\n // 'data:image/svg+xml;base64,'.length === 26\r\n const base64String = img.getAttribute(\"src\").substring(26);\r\n const svgString = window.atob(base64String);\r\n const encodedSvgString = encodeURIComponent(svgString);\r\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n // Return src to base64!\r\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\r\n } else {\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n }\r\n } else {\r\n // 'data:image/png;base64,' === 22.\r\n const base64 = img.src.substring(22, img.src.length);\r\n Image.setImgSize(img, base64, true);\r\n }\r\n } else {\r\n Image.setImgSize(img, img.src);\r\n }\r\n };\r\n\r\n // If the image doesn't contain a blob, just process it normally\r\n if (img.src.indexOf(\"blob:\") === -1) {\r\n processImg(img);\r\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = function () {\r\n img.setAttribute(\"src\", reader.result);\r\n processImg(img);\r\n };\r\n fetch(img.src)\r\n .then((r) => r.blob())\r\n .then((blob) => {\r\n reader.readAsDataURL(blob);\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\r\n * by the MathType image service. This image contains as an extra custom attribute:\r\n * the baseline (wrs:baseline).\r\n * @param {String} svgString - The SVG image.\r\n * @return {Array} - The image metrics.\r\n */\r\n static getMetricsFromSvgString(svgString) {\r\n let first = svgString.indexOf('height=\"');\r\n let last = svgString.indexOf('\"', first + 8, svgString.length);\r\n const height = svgString.substring(first + 8, last);\r\n\r\n first = svgString.indexOf('width=\"');\r\n last = svgString.indexOf('\"', first + 7, svgString.length);\r\n const width = svgString.substring(first + 7, last);\r\n\r\n first = svgString.indexOf('wrs:baseline=\"');\r\n last = svgString.indexOf('\"', first + 14, svgString.length);\r\n const baseline = svgString.substring(first + 14, last);\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n if (typeof baseline !== \"undefined\") {\r\n arr.cb = baseline;\r\n }\r\n return arr;\r\n }\r\n return [];\r\n }\r\n\r\n /**\r\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\r\n * @param {Array.} bytes - png byte array.\r\n * @return {Array} The png metrics.\r\n */\r\n static getMetricsFromBytes(bytes) {\r\n Util.readBytes(bytes, 0, 8);\r\n let width;\r\n let height;\r\n let typ;\r\n let baseline;\r\n let dpi;\r\n while (bytes.length >= 4) {\r\n typ = Util.readInt32(bytes);\r\n if (typ === 0x49484452) {\r\n width = Util.readInt32(bytes);\r\n height = Util.readInt32(bytes);\r\n // Read 5 bytes.\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n } else if (typ === 0x62615345) {\r\n // Baseline: 'baSE'.\r\n baseline = Util.readInt32(bytes);\r\n } else if (typ === 0x70485973) {\r\n // Dpis: 'pHYs'.\r\n dpi = Util.readInt32(bytes);\r\n dpi = Math.round(dpi / 39.37);\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n }\r\n Util.readInt32(bytes);\r\n }\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n arr.dpi = dpi;\r\n if (baseline) {\r\n arr.cb = baseline;\r\n }\r\n\r\n return arr;\r\n }\r\n return [];\r\n }\r\n}\r\n","import TextCache from \"./textcache\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport MathML from \"./mathml\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\r\n * the associated client-side cache.\r\n */\r\nexport default class Accessibility {\r\n /**\r\n * Static property.\r\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\r\n * @type {TextCache}\r\n */\r\n static get cache() {\r\n return Accessibility._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set accessibility cache.\r\n * @param {TextCahe} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Accessibility._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML strings to its accessible text representation.\r\n * @param {String} mathML - MathML to be converted to accessible text.\r\n * @param {String} [language] - Language of the accessible text. 'en' by default.\r\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\r\n * @return {String} Accessibility text.\r\n */\r\n static mathMLToAccessible(mathML, language, data) {\r\n if (typeof language === \"undefined\") {\r\n language = \"en\";\r\n }\r\n // Check MathML class. If the class is chemistry,\r\n // we add chemistry to data to force accessibility service\r\n // to load chemistry grammar.\r\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\r\n data.mode = \"chemistry\";\r\n }\r\n // Ignore accesibility styles\r\n data.ignoreStyles = true;\r\n let accessibleText = \"\";\r\n\r\n if (Accessibility.cache.get(mathML)) {\r\n accessibleText = Accessibility.cache.get(mathML);\r\n } else {\r\n data.service = \"mathml2accessible\";\r\n data.lang = language;\r\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n if (accessibleJsonResponse.status !== \"error\") {\r\n accessibleText = accessibleJsonResponse.result.text;\r\n Accessibility.cache.populate(mathML, accessibleText);\r\n } else {\r\n accessibleText = StringManager.get(\"error_convert_accessibility\");\r\n }\r\n }\r\n\r\n return accessibleText;\r\n }\r\n}\r\n\r\n/**\r\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\r\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\r\n * @private\r\n * @type {TextCache}\r\n */\r\nAccessibility._cache = new TextCache();\r\n","import Util from \"./util\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport Image from \"./image\";\r\nimport Accessibility from \"./accessibility\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Configuration from \"./configuration\";\r\nimport Constants from \"./constants\";\r\n// eslint-disable-next-line no-unused-vars\r\nimport md5 from \"./md5\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represent a MahML parser. Converts MathML into formulas depending on the\r\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\r\n * in the backend.\r\n */\r\nexport default class Parser {\r\n /**\r\n * Converts a MathML string to an img element.\r\n * @param {Document} creator - Document object to call createElement method.\r\n * @param {string} mathml - MathML code\r\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\r\n * @param {language} language - custom language for accessibility.\r\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\r\n * @static\r\n */\r\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\r\n const imgObject = creator.createElement(\"img\");\r\n imgObject.align = \"middle\";\r\n imgObject.style.maxWidth = \"none\";\r\n let data = wirisProperties || {};\r\n\r\n // Take into account the backend config\r\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\r\n data = { ...wirisEditorProperties, ...data };\r\n\r\n data.mml = mathml;\r\n data.lang = language;\r\n // Request metrics of the generated image.\r\n data.metrics = \"true\";\r\n data.centerbaseline = \"false\";\r\n\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n // Render js params: _wrs_int_wirisProperties contains some js render params.\r\n // Since MathML can support render params, js params should be send only to editor.\r\n\r\n imgObject.className = Configuration.get(\"imageClassName\");\r\n\r\n if (mathml.indexOf('class=\"') !== -1) {\r\n // We check here if the MathML has been created from a customEditor (such chemistry)\r\n // to add custom editor name attribute to img object (if necessary).\r\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\r\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\r\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\r\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\r\n }\r\n\r\n // Performance enabled.\r\n if (\r\n Configuration.get(\"wirisPluginPerformance\") &&\r\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\r\n ) {\r\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\r\n if (result.status === \"warning\") {\r\n // POST call.\r\n // if the mathml is malformed, this function will throw an exception.\r\n try {\r\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n ({ result } = result);\r\n if (result.format === \"png\") {\r\n imgObject.src = `data:image/png;base64,${result.content}`;\r\n } else {\r\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\r\n }\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n Image.setImgSize(imgObject, result.content, true);\r\n\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n if (typeof result.alt === \"undefined\") {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n } else {\r\n imgObject.alt = result.alt;\r\n }\r\n }\r\n } else {\r\n const result = Parser.createImageSrc(mathml, data);\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n imgObject.src = result;\r\n Image.setImgSize(\r\n imgObject,\r\n result,\r\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\r\n );\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n }\r\n }\r\n\r\n if (typeof Parser.observer !== \"undefined\") {\r\n Parser.observer.observe(imgObject);\r\n }\r\n\r\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\r\n imgObject.setAttribute(\"role\", \"math\");\r\n return imgObject;\r\n }\r\n\r\n /**\r\n * Returns the source to showimage service by calling createimage service. The\r\n * output of the createimage service is a URL path pointing to showimage service.\r\n * This method is called when performance is disabled.\r\n * @param {string} mathml - MathML code.\r\n * @param {Object[]} data - data object containing service parameters.\r\n * @returns {string} the showimage path.\r\n */\r\n static createImageSrc(mathml, data) {\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n let result = ServiceProvider.getService(\"createimage\", data);\r\n\r\n if (result.indexOf(\"@BASE@\") !== -1) {\r\n // Replacing '@BASE@' with the base URL of createimage.\r\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\r\n baseParts.pop();\r\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\r\n * this data would be converted as following:\r\n *
\r\n   * MathML code: Image containing the corresponding MathML formulas.\r\n   * MathML code with LaTeX annotation : LaTeX string.\r\n   * 
\r\n * @param {string} code - HTML code containing MathML data.\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\r\n */\r\n static initParse(code, language) {\r\n /* Note: The code inside this function has been inverted.\r\n If you invert again the code then you cannot use correctly LaTeX\r\n in Moodle.\r\n */\r\n code = Parser.initParseSaveMode(code, language);\r\n return Parser.initParseEditMode(code);\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the save mode. Transforms all MathML\r\n * occurrences for it's correspondent image or LaTeX.\r\n * @param {string} code - HTML code to be parsed\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static initParseSaveMode(code, language) {\r\n if (Configuration.get(\"saveMode\")) {\r\n // Converting XML to tags.\r\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\r\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\r\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\r\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\r\n code = Parser.codeImgTransform(code, \"base642showimage\");\r\n }\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the edit mode.\r\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\r\n * be converted into a LaTeX string instead of an image.\r\n * @param {string} code - HTML code containing MathML.\r\n * @returns {string} parsed HTML code.\r\n */\r\n static initParseEditMode(code) {\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\r\n const token = 'encoding=\"LaTeX\">';\r\n // While replacing images with latex, the indexes of the found images changes\r\n // respecting the original code, so this carry is needed.\r\n let carry = 0;\r\n\r\n for (let i = 0; i < imgList.length; i += 1) {\r\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\r\n\r\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\r\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\r\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n\r\n if (mathmlStart === -1) {\r\n mathmlStartToken = ' alt=\"';\r\n mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n }\r\n\r\n if (mathmlStart !== -1) {\r\n mathmlStart += mathmlStartToken.length;\r\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\r\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\r\n let latexStartPosition = mathml.indexOf(token);\r\n\r\n if (latexStartPosition !== -1) {\r\n latexStartPosition += token.length;\r\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\r\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\r\n\r\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\r\n const start = code.substring(0, imgList[i].start + carry);\r\n const end = code.substring(imgList[i].end + carry);\r\n code = start + replaceText + end;\r\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code. The end HTML code is HTML code with embedded images\r\n * or LaTeX formulas created with MathType.
\r\n * By default this method converts the formula images and LaTeX strings in MathML.
\r\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\r\n * @param {string} code - HTML to be parsed\r\n * @returns {string} the HTML code parsed.\r\n */\r\n static endParse(code) {\r\n // Transform LaTeX ocurrences to MathML elements.\r\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\r\n // Transform img elements to MathML elements.\r\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\r\n return codeEndParseSaveMode;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the edit mode.\r\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\r\n * @param {string} code - HTML code to be parsed.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseEditMode(code) {\r\n // Converting LaTeX to images.\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n let startPosition = code.indexOf(\"$$\");\r\n while (startPosition !== -1) {\r\n output += code.substring(endPosition, startPosition);\r\n endPosition = code.indexOf(\"$$\", startPosition + 2);\r\n\r\n if (endPosition !== -1) {\r\n // Before, it was a condition here to execute the next codelines\r\n // 'latex.indexOf('<') == -1'.\r\n // We don't know why it was used, but seems to have a conflict with\r\n // latex formulas that contains '<'.\r\n const latex = code.substring(startPosition + 2, endPosition);\r\n const decodedLatex = Util.htmlEntitiesDecode(latex);\r\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n // Remove hand traces.\r\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\r\n }\r\n output += mathml;\r\n endPosition += 2;\r\n } else {\r\n output += \"$$\";\r\n endPosition = startPosition + 2;\r\n }\r\n\r\n startPosition = code.indexOf(\"$$\", endPosition);\r\n }\r\n\r\n output += code.substring(endPosition, code.length);\r\n code = output;\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the save mode. Converts all\r\n * images into the element determined by the save mode:\r\n * - xml: Parses images formulas into MathML.\r\n * - safeXml: Parses images formulas into safeMAthML\r\n * - base64: Parses images into base64 images.\r\n * - image: Parse images into images (no parsing)\r\n * @param {string} code - HTML code to be parsed\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseSaveMode(code) {\r\n const savemode = Configuration.get(\"saveMode\");\r\n const base64savemode = Configuration.get(\"base64savemode\");\r\n\r\n if (savemode) {\r\n if (savemode === \"safeXml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"xml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\r\n code = Parser.codeImgTransform(code, \"img264\");\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Auxiliar function that builds the data object to send to the showimage endpoint\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object with the data to send to showimage.\r\n */\r\n static createShowImageSrcData(data, language) {\r\n const dataMd5 = {};\r\n const renderParams = [\r\n \"mml\",\r\n \"color\",\r\n \"centerbaseline\",\r\n \"zoom\",\r\n \"dpi\",\r\n \"fontSize\",\r\n \"fontFamily\",\r\n \"defaultStretchy\",\r\n \"backgroundColor\",\r\n \"format\",\r\n ];\r\n renderParams.forEach((param) => {\r\n if (typeof data[param] !== \"undefined\") {\r\n dataMd5[param] = data[param];\r\n }\r\n });\r\n // Data variables to get.\r\n const dataObject = {};\r\n Object.keys(data).forEach((key) => {\r\n // We don't need mathml in this request we try to get cached.\r\n // Only need the formula md5 calculated before.\r\n if (key !== \"mml\") {\r\n dataObject[key] = data[key];\r\n }\r\n });\r\n\r\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\r\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\r\n dataObject.version = Configuration.get(\"version\");\r\n\r\n return dataObject;\r\n }\r\n\r\n /**\r\n * Returns the result to call showimage service with the formula md5 as parameter.\r\n * The result could be:\r\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\r\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object containing showimage response.\r\n */\r\n static createShowImageSrc(data, language) {\r\n const dataObject = this.createShowImageSrcData(data, language);\r\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\r\n return result;\r\n }\r\n\r\n /**\r\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\r\n * or showimage img tags (i.e with showimage.php on src)\r\n * @param {string} code - HTML code\r\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\r\n * @returns {string} html - code transformed.\r\n */\r\n static codeImgTransform(code, mode) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n const pattern = /\") {\r\n endPosition = i + 1;\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n if (endPosition < startPosition) {\r\n // The img tag is stripped.\r\n output += code.substring(startPosition, code.length);\r\n return output;\r\n }\r\n let imgCode = code.substring(startPosition, endPosition);\r\n const imgObject = Util.createObject(imgCode);\r\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n let convertToXml;\r\n let convertToSafeXml;\r\n\r\n if (mode === \"base642showimage\") {\r\n if (xmlCode == null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\r\n output += Util.createObjectCode(imgCode);\r\n } else if (mode === \"img2mathml\") {\r\n if (Configuration.get(\"saveMode\")) {\r\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\r\n convertToXml = true;\r\n convertToSafeXml = true;\r\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\r\n convertToXml = true;\r\n convertToSafeXml = false;\r\n }\r\n }\r\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\r\n } else if (mode === \"img264\") {\r\n if (xmlCode === null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n\r\n const properties = {};\r\n properties.base64 = \"true\";\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\r\n // Metrics.\r\n Image.setImgSize(imgCode, imgCode.src, true);\r\n output += Util.createObjectCode(imgCode);\r\n }\r\n }\r\n output += code.substring(endPosition, code.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML to the corresponding image.\r\n * @param {string} content - string with valid MathML code.\r\n * The MathML code doesn't contain semantics.\r\n * @param {Constants} characters - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * @param {string} language - a valid language code\r\n * in order to generate formula accessibility.\r\n * @returns {string} The input string with all the MathML\r\n * occurrences replaced by the corresponding image.\r\n */\r\n static parseMathmlToImg(content, characters, language) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n // Avoid WIRIS images to be parsed.\r\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else if (imageMathmlAtrribute !== -1) {\r\n // First close tag of img attribute\r\n // If a mathmlAttribute exists should be inside a img tag.\r\n end += content.indexOf(\"/>\", start);\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\r\n let mathml = content.substring(start, end);\r\n mathml =\r\n characters.id === Constants.safeXmlCharacters.id\r\n ? MathML.safeXmlDecode(mathml)\r\n : MathML.mathMLEntities(mathml);\r\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\r\n } else {\r\n output += content.substring(start, end);\r\n }\r\n\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n}\r\n\r\n// Mutation observers to avoid wiris image formulas class be removed.\r\nif (typeof MutationObserver !== \"undefined\") {\r\n const mutationObserver = new MutationObserver((mutations) => {\r\n mutations.forEach((mutation) => {\r\n if (\r\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\r\n mutation.attributeName === \"class\" &&\r\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\r\n ) {\r\n mutation.target.className = Configuration.get(\"imageClassName\");\r\n }\r\n });\r\n });\r\n\r\n Parser.observer = Object.create(mutationObserver);\r\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\r\n // We use own default config.\r\n Parser.observer.observe = function (target) {\r\n Object.getPrototypeOf(this).observe(target, this.Config);\r\n };\r\n}\r\n","/* eslint-disable class-methods-use-this */\r\n/* eslint-disable no-unused-vars */\r\n/* eslint-disable no-extra-semi */\r\n\r\n// The rules above are disabled because we are implementing\r\n// an external interface.\r\n\r\nexport default class EditorListener {\r\n /**\r\n * @classdesc\r\n * Determines if the content of the\r\n * MathType Editor has changes.\r\n * @implements {EditorListeners}\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the content of the editor has changed.\r\n * @type {Boolean}\r\n */\r\n this.isContentChanged = false;\r\n\r\n /**\r\n * Indicates if the listener should be waiting for changes in the editor.\r\n * @type {Boolean}\r\n */\r\n this.waitingForChanges = false;\r\n }\r\n\r\n /**\r\n * Sets {@link EditorListener.isContentChanged} property.\r\n * @param {Boolean} value - The new vlue.\r\n */\r\n setIsContentChanged(value) {\r\n this.isContentChanged = value;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed, false otherwise.\r\n * @return {Boolean}\r\n */\r\n getIsContentChanged() {\r\n return this.isContentChanged;\r\n }\r\n\r\n /**\r\n * Determines if the EditorListener should wait for any changes.\r\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\r\n */\r\n setWaitingForChanges(value) {\r\n this.waitingForChanges = value;\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite.\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n caretPositionChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n clipboardChanged(_editor) {}\r\n\r\n /**\r\n * Determines if the content of an editor has been changed.\r\n * @param {JsEditor} editor - editor object.\r\n */\r\n contentChanged(_editor) {\r\n if (this.waitingForChanges === true && this.isContentChanged === false) {\r\n this.isContentChanged = true;\r\n }\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} editor - The editor instance.\r\n */\r\n styleChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} - The editor instance.\r\n */\r\n transformationReceived(_editor) {}\r\n}\r\n","let wasm;\r\n\r\nconst cachedTextDecoder =\r\n typeof TextDecoder !== \"undefined\"\r\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\r\n : {\r\n decode: () => {\r\n throw Error(\"TextDecoder not available\");\r\n },\r\n };\r\n\r\nif (typeof TextDecoder !== \"undefined\") {\r\n cachedTextDecoder.decode();\r\n}\r\n\r\nlet cachedUint8Memory0 = null;\r\n\r\nfunction getUint8Memory0() {\r\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\r\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\r\n }\r\n return cachedUint8Memory0;\r\n}\r\n\r\nfunction getStringFromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\r\n}\r\n\r\nconst heap = new Array(128).fill(undefined);\r\n\r\nheap.push(undefined, null, true, false);\r\n\r\nlet heap_next = heap.length;\r\n\r\nfunction addHeapObject(obj) {\r\n if (heap_next === heap.length) heap.push(heap.length + 1);\r\n const idx = heap_next;\r\n heap_next = heap[idx];\r\n\r\n heap[idx] = obj;\r\n return idx;\r\n}\r\n\r\nfunction getObject(idx) {\r\n return heap[idx];\r\n}\r\n\r\nfunction dropObject(idx) {\r\n if (idx < 132) return;\r\n heap[idx] = heap_next;\r\n heap_next = idx;\r\n}\r\n\r\nfunction takeObject(idx) {\r\n const ret = getObject(idx);\r\n dropObject(idx);\r\n return ret;\r\n}\r\n\r\nlet WASM_VECTOR_LEN = 0;\r\n\r\nconst cachedTextEncoder =\r\n typeof TextEncoder !== \"undefined\"\r\n ? new TextEncoder(\"utf-8\")\r\n : {\r\n encode: () => {\r\n throw Error(\"TextEncoder not available\");\r\n },\r\n };\r\n\r\nconst encodeString =\r\n typeof cachedTextEncoder.encodeInto === \"function\"\r\n ? function (arg, view) {\r\n return cachedTextEncoder.encodeInto(arg, view);\r\n }\r\n : function (arg, view) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n view.set(buf);\r\n return {\r\n read: arg.length,\r\n written: buf.length,\r\n };\r\n };\r\n\r\nfunction passStringToWasm0(arg, malloc, realloc) {\r\n if (realloc === undefined) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n const ptr = malloc(buf.length, 1) >>> 0;\r\n getUint8Memory0()\r\n .subarray(ptr, ptr + buf.length)\r\n .set(buf);\r\n WASM_VECTOR_LEN = buf.length;\r\n return ptr;\r\n }\r\n\r\n let len = arg.length;\r\n let ptr = malloc(len, 1) >>> 0;\r\n\r\n const mem = getUint8Memory0();\r\n\r\n let offset = 0;\r\n\r\n for (; offset < len; offset++) {\r\n const code = arg.charCodeAt(offset);\r\n if (code > 0x7f) break;\r\n mem[ptr + offset] = code;\r\n }\r\n\r\n if (offset !== len) {\r\n if (offset !== 0) {\r\n arg = arg.slice(offset);\r\n }\r\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\r\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\r\n const ret = encodeString(arg, view);\r\n\r\n offset += ret.written;\r\n }\r\n\r\n WASM_VECTOR_LEN = offset;\r\n return ptr;\r\n}\r\n\r\nfunction isLikeNone(x) {\r\n return x === undefined || x === null;\r\n}\r\n\r\nlet cachedInt32Memory0 = null;\r\n\r\nfunction getInt32Memory0() {\r\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\r\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\r\n }\r\n return cachedInt32Memory0;\r\n}\r\n\r\nlet cachedFloat64Memory0 = null;\r\n\r\nfunction getFloat64Memory0() {\r\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\r\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\r\n }\r\n return cachedFloat64Memory0;\r\n}\r\n\r\nlet cachedBigInt64Memory0 = null;\r\n\r\nfunction getBigInt64Memory0() {\r\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\r\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\r\n }\r\n return cachedBigInt64Memory0;\r\n}\r\n\r\nfunction debugString(val) {\r\n // primitive types\r\n const type = typeof val;\r\n if (type == \"number\" || type == \"boolean\" || val == null) {\r\n return `${val}`;\r\n }\r\n if (type == \"string\") {\r\n return `\"${val}\"`;\r\n }\r\n if (type == \"symbol\") {\r\n const description = val.description;\r\n if (description == null) {\r\n return \"Symbol\";\r\n } else {\r\n return `Symbol(${description})`;\r\n }\r\n }\r\n if (type == \"function\") {\r\n const name = val.name;\r\n if (typeof name == \"string\" && name.length > 0) {\r\n return `Function(${name})`;\r\n } else {\r\n return \"Function\";\r\n }\r\n }\r\n // objects\r\n if (Array.isArray(val)) {\r\n const length = val.length;\r\n let debug = \"[\";\r\n if (length > 0) {\r\n debug += debugString(val[0]);\r\n }\r\n for (let i = 1; i < length; i++) {\r\n debug += \", \" + debugString(val[i]);\r\n }\r\n debug += \"]\";\r\n return debug;\r\n }\r\n // Test for built-in\r\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\r\n let className;\r\n if (builtInMatches.length > 1) {\r\n className = builtInMatches[1];\r\n } else {\r\n // Failed to match the standard '[object ClassName]'\r\n return toString.call(val);\r\n }\r\n if (className == \"Object\") {\r\n // we're a user defined class or Object\r\n // JSON.stringify avoids problems with cycles, and is generally much\r\n // easier than looping through ownProperties of `val`.\r\n try {\r\n return \"Object(\" + JSON.stringify(val) + \")\";\r\n } catch (_) {\r\n return \"Object\";\r\n }\r\n }\r\n // errors\r\n if (val instanceof Error) {\r\n return `${val.name}: ${val.message}\\n${val.stack}`;\r\n }\r\n // TODO we could test for more things here, like `Set`s and `Map`s.\r\n return className;\r\n}\r\n\r\nfunction makeClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n try {\r\n return f(state.a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\r\n state.a = 0;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction makeMutClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n const a = state.a;\r\n state.a = 0;\r\n try {\r\n return f(a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\r\n } else {\r\n state.a = a;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_49(arg0, arg1) {\r\n wasm.__wbindgen_export_4(arg0, arg1);\r\n}\r\n\r\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction handleError(f, args) {\r\n try {\r\n return f.apply(this, args);\r\n } catch (e) {\r\n wasm.__wbindgen_export_6(addHeapObject(e));\r\n }\r\n}\r\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\r\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\r\n}\r\n\r\n/**\r\n */\r\nexport function main_js() {\r\n wasm.main_js();\r\n}\r\n\r\nfunction getArrayU8FromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\r\n}\r\n/**\r\n */\r\nexport const Level = Object.freeze({\r\n Err: 0,\r\n 0: \"Err\",\r\n Warn: 1,\r\n 1: \"Warn\",\r\n Info: 2,\r\n 2: \"Info\",\r\n Debug: 3,\r\n 3: \"Debug\",\r\n});\r\n/**\r\n */\r\nexport class Telemeter {\r\n __destroy_into_raw() {\r\n const ptr = this.__wbg_ptr;\r\n this.__wbg_ptr = 0;\r\n\r\n return ptr;\r\n }\r\n\r\n free() {\r\n const ptr = this.__destroy_into_raw();\r\n wasm.__wbg_telemeter_free(ptr);\r\n }\r\n /**\r\n * @param {any} solution\r\n * @param {any} hosts\r\n * @param {any} config\r\n */\r\n constructor(solution, hosts, config) {\r\n try {\r\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\r\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\r\n var r0 = getInt32Memory0()[retptr / 4 + 0];\r\n var r1 = getInt32Memory0()[retptr / 4 + 1];\r\n var r2 = getInt32Memory0()[retptr / 4 + 2];\r\n if (r2) {\r\n throw takeObject(r1);\r\n }\r\n this.__wbg_ptr = r0 >>> 0;\r\n return this;\r\n } finally {\r\n wasm.__wbindgen_add_to_stack_pointer(16);\r\n }\r\n }\r\n /**\r\n * @param {string} sender_id\r\n * @returns {Promise}\r\n */\r\n identify(sender_id) {\r\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {string} event_type\r\n * @param {any} event_payload\r\n * @returns {Promise}\r\n */\r\n track(event_type, event_payload) {\r\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {any} level\r\n * @param {string} message\r\n * @param {any} payload\r\n * @returns {Promise}\r\n */\r\n log(level, message, payload) {\r\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @returns {Promise}\r\n */\r\n finish() {\r\n const ptr = this.__destroy_into_raw();\r\n const ret = wasm.telemeter_finish(ptr);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {boolean | undefined} [new_debug_status]\r\n */\r\n debug(new_debug_status) {\r\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\r\n }\r\n}\r\n\r\nasync function __wbg_load(module, imports) {\r\n if (typeof Response === \"function\" && module instanceof Response) {\r\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\r\n try {\r\n return await WebAssembly.instantiateStreaming(module, imports);\r\n } catch (e) {\r\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\r\n console.warn(\r\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\r\n e,\r\n );\r\n } else {\r\n throw e;\r\n }\r\n }\r\n }\r\n\r\n const bytes = await module.arrayBuffer();\r\n return await WebAssembly.instantiate(bytes, imports);\r\n } else {\r\n const instance = await WebAssembly.instantiate(module, imports);\r\n\r\n if (instance instanceof WebAssembly.Instance) {\r\n return { instance, module };\r\n } else {\r\n return instance;\r\n }\r\n }\r\n}\r\n\r\nfunction __wbg_get_imports() {\r\n const imports = {};\r\n imports.wbg = {};\r\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\r\n const ret = getStringFromWasm0(arg0, arg1);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\r\n const ret = new Object();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\r\n const ret = getObject(arg0).status;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\r\n const ret = getObject(arg0).headers;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\r\n const ret = new Date();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\r\n const ret = getObject(arg0).getTime();\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\r\n takeObject(arg0);\r\n };\r\n imports.wbg.__wbindgen_is_object = function (arg0) {\r\n const val = getObject(arg0);\r\n const ret = typeof val === \"object\" && val !== null;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\r\n const ret = getObject(arg0).crypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\r\n const ret = getObject(arg0).process;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\r\n const ret = getObject(arg0).versions;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\r\n const ret = getObject(arg0).node;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_string = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"string\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\r\n const ret = getObject(arg0).msCrypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\r\n return handleError(function () {\r\n const ret = module.require;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\r\n const ret = new Uint8Array(arg0 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[arg1 >>> 0];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).next();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\r\n const ret = getObject(arg0).done;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\r\n const ret = getObject(arg0).value;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\r\n const ret = Symbol.iterator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\r\n const ret = getObject(arg0).next;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_function = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"function\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = getObject(arg0).call(getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\r\n const ret = getObject(arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\r\n return handleError(function () {\r\n const ret = self.self;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\r\n return handleError(function () {\r\n const ret = window.window;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\r\n return handleError(function () {\r\n const ret = globalThis.globalThis;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\r\n return handleError(function () {\r\n const ret = global.global;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\r\n const ret = getObject(arg0) === undefined;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\r\n const ret = new Function(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\r\n const ret = Array.isArray(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\r\n const ret = Number.isSafeInteger(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\r\n try {\r\n var state0 = { a: arg0, b: arg1 };\r\n var cb0 = (arg0, arg1) => {\r\n const a = state0.a;\r\n state0.a = 0;\r\n try {\r\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\r\n } finally {\r\n state0.a = a;\r\n }\r\n };\r\n const ret = new Promise(cb0);\r\n return addHeapObject(ret);\r\n } finally {\r\n state0.a = state0.b = 0;\r\n }\r\n };\r\n imports.wbg.__wbindgen_memory = function () {\r\n const ret = wasm.memory;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\r\n const ret = getObject(arg0).buffer;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\r\n const ret = new Uint8Array(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\r\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\r\n };\r\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"string\" ? obj : undefined;\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\r\n return handleError(function (arg0) {\r\n const ret = JSON.stringify(getObject(arg0));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\r\n const ret = getObject(arg0).fetch(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\r\n const ret = fetch(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\r\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\r\n return handleError(function () {\r\n const ret = new AbortController();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\r\n const ret = getObject(arg0).signal;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\r\n getObject(arg0).abort();\r\n };\r\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\r\n const ret = new Error(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) == getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\r\n const v = getObject(arg0);\r\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"number\" ? obj : undefined;\r\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Uint8Array;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof ArrayBuffer;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\r\n const ret = Object.entries(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\r\n const ret = String(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\r\n console.warn(getObject(arg0), getObject(arg1));\r\n };\r\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\r\n const ret = getObject(arg0).readyState;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\r\n console.warn(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\r\n return handleError(function (arg0) {\r\n getObject(arg0).close();\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_new_b9b318679315404f = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\r\n getObject(arg0).binaryType = takeObject(arg1);\r\n };\r\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\r\n console.log(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\r\n console.error(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\r\n console.info(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\r\n const ret = getObject(arg0).document;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\r\n const ret = getObject(arg0).visibilityState;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[getObject(arg1)];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"bigint\";\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\r\n const ret = arg0;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\r\n const ret = getObject(arg0) in getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\r\n const ret = BigInt.asUintN(64, arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) === getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).localStorage;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\r\n const ret = getObject(arg0).navigator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).mediaDevices;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).enumerateDevices();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\r\n const obj = takeObject(arg0).original;\r\n if (obj.cnt-- == 1) {\r\n obj.a = 0;\r\n return true;\r\n }\r\n const ret = false;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\r\n const ret = getObject(arg1).deviceId;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Response;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).randomFillSync(takeObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).getRandomValues(getObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\r\n const v = getObject(arg1);\r\n const ret = typeof v === \"bigint\" ? v : undefined;\r\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\r\n const ret = debugString(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\r\n throw new Error(getStringFromWasm0(arg0, arg1));\r\n };\r\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\r\n const ret = getObject(arg0).then(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\r\n queueMicrotask(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\r\n const ret = getObject(arg0).queueMicrotask;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\r\n const ret = Promise.resolve(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\r\n const ret = getObject(arg1).url;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_send_2860805104507701 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Window;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\r\n return handleError(function () {\r\n const ret = new Headers();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\r\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\r\n return addHeapObject(ret);\r\n };\r\n\r\n return imports;\r\n}\r\n\r\nfunction __wbg_init_memory(imports, maybe_memory) {}\r\n\r\nfunction __wbg_finalize_init(instance, module) {\r\n wasm = instance.exports;\r\n __wbg_init.__wbindgen_wasm_module = module;\r\n cachedBigInt64Memory0 = null;\r\n cachedFloat64Memory0 = null;\r\n cachedInt32Memory0 = null;\r\n cachedUint8Memory0 = null;\r\n\r\n wasm.__wbindgen_start();\r\n return wasm;\r\n}\r\n\r\nfunction initSync(module) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n const imports = __wbg_get_imports();\r\n\r\n __wbg_init_memory(imports);\r\n\r\n if (!(module instanceof WebAssembly.Module)) {\r\n module = new WebAssembly.Module(module);\r\n }\r\n\r\n const instance = new WebAssembly.Instance(module, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nasync function __wbg_init(input) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n if (typeof input === \"undefined\") {\r\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\r\n }\r\n const imports = __wbg_get_imports();\r\n\r\n if (\r\n typeof input === \"string\" ||\r\n (typeof Request === \"function\" && input instanceof Request) ||\r\n (typeof URL === \"function\" && input instanceof URL)\r\n ) {\r\n input = fetch(input);\r\n }\r\n\r\n __wbg_init_memory(imports);\r\n\r\n const { instance, module } = await __wbg_load(await input, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nexport { initSync };\r\nexport default __wbg_init;\r\n","/* eslint-disable-next-line */\r\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\r\n\r\n/**\r\n * @classdesc\r\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\r\n * to Telemetry in a more comfortable and homogeneous way.\r\n */\r\nexport default class Telemeter {\r\n /**\r\n * Inits Telemeter class.\r\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\r\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\r\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\r\n * @param {Object} telemeterAttributes.config - Configuration parameters.\r\n */\r\n static init(telemeterAttributes) {\r\n if (!this.telemeter && !this.waitingForInit) {\r\n this.waitingForInit = true;\r\n init(telemeterAttributes.url)\r\n .then(() => {\r\n this.telemeter = new TelemeterWASM(\r\n telemeterAttributes.solution,\r\n telemeterAttributes.hosts,\r\n telemeterAttributes.config,\r\n );\r\n })\r\n .catch((error) => {\r\n console.log(error);\r\n })\r\n .finally(() => (this.waitingForInit = false));\r\n }\r\n }\r\n\r\n /**\r\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\r\n */\r\n static async finish() {\r\n if (!this.telemeter) return;\r\n\r\n try {\r\n const local_telemeter = this.telemeter;\r\n this.telemeter = undefined;\r\n await local_telemeter.finish();\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Core from \"./core.src\";\r\nimport EditorListener from \"./editorlistener\";\r\nimport Listeners from \"./listeners\";\r\nimport MathML from \"./mathml\";\r\nimport Util from \"./util\";\r\nimport Telemeter from \"./telemeter\";\r\n\r\nexport default class ContentManager {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog, managing the following:\r\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\r\n * - The actions to be done once the modal object has been submitted\r\n * (submitAction} method).\r\n * - The update of the content when the {@link ModalDialog} class is also updated,\r\n * for example when ModalDialog is re-opened.\r\n * - The communication between the {@link ModalDialog} class and itself, if the content\r\n * has been changed (hasChanges} method).\r\n * @constructs\r\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\r\n * create a new instance.\r\n */\r\n constructor(contentManagerAttributes) {\r\n /**\r\n * An object containing MathType editor parameters. See\r\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\r\n * @type {Object}\r\n */\r\n this.editorAttributes = {};\r\n if (\"editorAttributes\" in contentManagerAttributes) {\r\n this.editorAttributes = contentManagerAttributes.editorAttributes;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\r\n }\r\n\r\n /**\r\n * CustomEditors instance. Contains the custom editors.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = null;\r\n if (\"customEditors\" in contentManagerAttributes) {\r\n this.customEditors = contentManagerAttributes.customEditors;\r\n }\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @type {Object}\r\n * @property {String} editor - Editor name. Usually the HTML editor.\r\n * @property {String} mode - Save mode. Xml by default.\r\n * @property {String} version - Plugin version.\r\n */\r\n this.environment = {};\r\n if (\"environment\" in contentManagerAttributes) {\r\n this.environment = contentManagerAttributes.environment;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: environment property missed\");\r\n }\r\n\r\n /**\r\n * ContentManager language.\r\n * @type {String}\r\n */\r\n this.language = \"\";\r\n if (\"language\" in contentManagerAttributes) {\r\n this.language = contentManagerAttributes.language;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: language property missed\");\r\n }\r\n\r\n /**\r\n * {@link EditorListener} instance. Manages the changes inside the editor.\r\n * @type {EditorListener}\r\n */\r\n this.editorListener = new EditorListener();\r\n\r\n /**\r\n * MathType editor instance.\r\n * @type {JsEditor}\r\n */\r\n this.editor = null;\r\n\r\n /**\r\n * Navigator user agent.\r\n * @type {String}\r\n */\r\n this.ua = navigator.userAgent.toLowerCase();\r\n\r\n /**\r\n * Mobile device properties object\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {};\r\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\r\n this.deviceProperties.isIOS = ContentManager.isIOS();\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.toolbar = null;\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.dbclick = null;\r\n\r\n /**\r\n * Instance of the {@link ModalDialog} class associated with the current\r\n * {@link ContentManager} instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialogInstance = null;\r\n\r\n /**\r\n * ContentManager listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * MathML associated to the ContentManager instance.\r\n * @type {String}\r\n */\r\n this.mathML = null;\r\n\r\n /**\r\n * Indicates if the edited element is a new one or not.\r\n * @type {Boolean}\r\n */\r\n this.isNewElement = true;\r\n\r\n /**\r\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n }\r\n\r\n /**\r\n * Adds a new listener to the current {@link ContentManager} instance.\r\n * @param {Object} listener - The listener to be added.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\r\n * instance.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\r\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\r\n */\r\n setModalDialogInstance(modalDialogInstance) {\r\n this.modalDialogInstance = modalDialogInstance;\r\n }\r\n\r\n /**\r\n * Inserts the content into the current {@link ModalDialog} instance updating\r\n * the title and inserting the JavaScript editor.\r\n */\r\n insert() {\r\n // Before insert the editor we update the modal object title to avoid weird render display.\r\n this.updateTitle(this.modalDialogInstance);\r\n this.insertEditor(this.modalDialogInstance);\r\n }\r\n\r\n /**\r\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\r\n * editor's JavaScript is loaded.\r\n */\r\n insertEditor() {\r\n if (ContentManager.isEditorLoaded()) {\r\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\r\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\r\n this.editor.focus();\r\n\r\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\r\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\r\n this.editor.action(\"rtl\");\r\n }\r\n // Setting div in rtl in case of it's activated.\r\n if (this.editor.getEditorModel().isRTL()) {\r\n this.editor.element.style.direction = \"rtl\";\r\n }\r\n\r\n // Editor listener: this object manages the changes logic of editor.\r\n this.editor.getEditorModel().addEditorListener(this.editorListener);\r\n\r\n // iOS events.\r\n if (this.modalDialogInstance.deviceProperties.isIOS) {\r\n setTimeout(function () {\r\n // Make sure the modalDialogInstance is available when the timeout is over\r\n // to avoid throw errors and stop execution.\r\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\r\n }, 400);\r\n\r\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\r\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\r\n }\r\n // Fire onLoad event. Necessary to set the MathML into the editor\r\n // after is loaded.\r\n this.listeners.fire(\"onLoad\", {});\r\n } else {\r\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Initializes the current class by loading MathType script.\r\n */\r\n init() {\r\n if (!ContentManager.isEditorLoaded()) {\r\n this.addEditorAsExternalDependency();\r\n }\r\n }\r\n\r\n /**\r\n * Adds script element to the DOM to include editor externally.\r\n */\r\n addEditorAsExternalDependency() {\r\n const script = document.createElement(\"script\");\r\n script.type = \"text/javascript\";\r\n let editorUrl = Configuration.get(\"editorUrl\");\r\n\r\n // We create an object url for parse url string and work more efficiently.\r\n const anchorElement = document.createElement(\"a\");\r\n\r\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\r\n ContentManager.setProtocolToAnchorElement(anchorElement);\r\n\r\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\r\n\r\n // Load editor URL. We add stats as GET params.\r\n const stats = this.getEditorStats();\r\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\r\n\r\n document.getElementsByTagName(\"head\")[0].appendChild(script);\r\n }\r\n\r\n /**\r\n * Sets the specified url to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\r\n * @param {String} url - URL to set.\r\n */\r\n static setHrefToAnchorElement(anchorElement, url) {\r\n anchorElement.href = url;\r\n }\r\n\r\n /**\r\n * Sets the current protocol to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\r\n */\r\n static setProtocolToAnchorElement(anchorElement) {\r\n // Change to https if necessary.\r\n if (window.location.href.indexOf(\"https://\") === 0) {\r\n // It check if browser is https and configuration is http.\r\n // If this is so, we will replace protocol.\r\n if (anchorElement.protocol === \"http:\") {\r\n anchorElement.protocol = \"https:\";\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the url of the anchor element adding the current port\r\n * if it is needed.\r\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\r\n * @returns {String}\r\n */\r\n static getURLFromAnchorElement(anchorElement) {\r\n // Check protocol and remove port if it's standard.\r\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\r\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\r\n }\r\n\r\n /**\r\n * Returns object with editor stats.\r\n *\r\n * @typedef {Object} EditorStatsObject\r\n * @property {string} editor - Editor name.\r\n * @property {string} mode - Current configuration for formula save mode.\r\n * @property {string} version - Current plugins version.\r\n * @returns {EditorStatsObject}\r\n */\r\n getEditorStats() {\r\n // Editor stats. Use environment property to set it.\r\n const stats = {};\r\n if (\"editor\" in this.environment) {\r\n stats.editor = this.environment.editor;\r\n } else {\r\n stats.editor = \"unknown\";\r\n }\r\n\r\n if (\"mode\" in this.environment) {\r\n stats.mode = this.environment.mode;\r\n } else {\r\n stats.mode = Configuration.get(\"saveMode\");\r\n }\r\n\r\n if (\"version\" in this.environment) {\r\n stats.version = this.environment.version;\r\n } else {\r\n stats.version = Configuration.get(\"version\");\r\n }\r\n\r\n return stats;\r\n }\r\n\r\n /**\r\n * Returns true if device is iOS. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isIOS() {\r\n return (\r\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\r\n // iPad on iOS 13 detection\r\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\r\n );\r\n }\r\n\r\n /**\r\n * Returns true if device is Mobile. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isMobile() {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\r\n }\r\n\r\n /**\r\n * Returns true if editor is loaded. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isEditorLoaded() {\r\n // To know if editor JavaScript is loaded we need to wait until\r\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\r\n return (\r\n window.com &&\r\n window.com.wiris &&\r\n window.com.wiris.jsEditor &&\r\n window.com.wiris.jsEditor.JsEditor &&\r\n window.com.wiris.jsEditor.JsEditor.newInstance\r\n );\r\n }\r\n\r\n /**\r\n * Sets the {@link ContentManager.editor} initial content.\r\n */\r\n setInitialContent() {\r\n if (!this.isNewElement) {\r\n this.setMathML(this.mathML);\r\n }\r\n }\r\n\r\n /**\r\n * Sets a MathML into {@link ContentManager.editor} instance.\r\n * @param {String} mathml - MathML string.\r\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\r\n * False by default.\r\n */\r\n setMathML(mathml, focusDisabled) {\r\n // By default focus is enabled.\r\n if (typeof focusDisabled === \"undefined\") {\r\n focusDisabled = false;\r\n }\r\n // Using setMathML method is not a change produced by the user but for the API\r\n // so we set to false the contentChange property of editorListener.\r\n this.editor.setMathMLWithCallback(mathml, () => {\r\n this.editorListener.setWaitingForChanges(true);\r\n });\r\n\r\n // We need to wait a little until the callback finish.\r\n setTimeout(() => {\r\n this.editorListener.setIsContentChanged(false);\r\n }, 500);\r\n\r\n // In some scenarios - like closing modal object - editor mustn't be focused.\r\n if (!focusDisabled) {\r\n this.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\r\n * {@link ModalDialog.focus}.\r\n */\r\n onFocus() {\r\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\r\n this.editor.focus();\r\n\r\n // On WordPress integration, the focus gets lost right after setting it.\r\n // To fix this, we enforce another focus some milliseconds after this behaviour.\r\n setTimeout(() => {\r\n this.editor.focus();\r\n }, 100);\r\n }\r\n }\r\n\r\n /**\r\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\r\n * Triggered by {@link ModalDialog.submitAction}.\r\n */\r\n submitAction() {\r\n if (!this.editor.isFormulaEmpty()) {\r\n let mathML = this.editor.getMathMLWithSemantics();\r\n // Add class for custom editors.\r\n if (this.customEditors.getActiveEditor() !== null) {\r\n const { toolbar } = this.customEditors.getActiveEditor();\r\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\r\n } else {\r\n // We need - if exists - the editor name from MathML\r\n // class attribute.\r\n Object.keys(this.customEditors.editors).forEach((key) => {\r\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\r\n });\r\n }\r\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\r\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\r\n } else {\r\n this.integrationModel.updateFormula(null);\r\n }\r\n\r\n this.customEditors.disable();\r\n this.integrationModel.notifyWindowClosed();\r\n\r\n // Set disabled focus to prevent lost focus.\r\n this.setEmptyMathML();\r\n this.customEditors.disable();\r\n }\r\n\r\n /**\r\n * Sets an empty MathML as {@link ContentManager.editor} content.\r\n * This will open the MT/CT editor with the hand mode.\r\n * It adds dir rtl in case of it's activated.\r\n */\r\n setEmptyMathML() {\r\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\r\n const isRTL = this.editor.getEditorModel().isRTL();\r\n\r\n if (isMobile || this.integrationModel.forcedHandMode) {\r\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\r\n const mathML = `[]`;\r\n this.setMathML(mathML, true);\r\n } else {\r\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\r\n const mathML = ``;\r\n this.setMathML(mathML, true);\r\n }\r\n }\r\n\r\n /**\r\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\r\n * - Updates the {@link ContentManager.editor} content\r\n * (with an empty MathML or an existing formula),\r\n * - Updates the {@link ContentManager.editor} toolbar.\r\n * - Recovers the the focus.\r\n */\r\n onOpen() {\r\n if (this.isNewElement) {\r\n this.setEmptyMathML();\r\n } else {\r\n this.setMathML(this.mathML);\r\n }\r\n const toolbar = this.updateToolbar();\r\n this.onFocus();\r\n\r\n if (this.deviceProperties.isIOS) {\r\n const zoom = document.documentElement.clientWidth / window.innerWidth;\r\n\r\n if (zoom !== 1) {\r\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\r\n this.setKeyboardMode();\r\n }\r\n }\r\n\r\n const trigger = this.dbclick ? \"formula\" : \"button\";\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\r\n toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalOpen\", {});\r\n\r\n if (this.integrationModel.forcedHandMode) {\r\n this.hideHandModeButton();\r\n\r\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\r\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\r\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Change Editor in keyboard mode when is loaded\r\n */\r\n setKeyboardMode() {\r\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\r\n if (wrsEditor) {\r\n wrsEditor.classList.remove(\"wrs_handOpen\");\r\n wrsEditor.classList.remove(\"wrs_disablePalette\");\r\n } else {\r\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Hides the hand <-> keyboard mode switch.\r\n *\r\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\r\n * any change on those classes will make this code stop working properly.\r\n *\r\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\r\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\r\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\r\n */\r\n hideHandModeButton(forced = true) {\r\n if (this.handSwitchHidden) {\r\n return; // hand <-> keyboard button already hidden.\r\n }\r\n\r\n // \"Open hand mode\" button takes a little bit to be available.\r\n // This selector gets the hand <-> keyboard mode switch\r\n const handModeButtonSelector =\r\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\r\n\r\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\r\n // We use an observer to ensure that the button it hidden as soon as it appears.\r\n if (forced) {\r\n const mutationInstance = new MutationObserver((mutations) => {\r\n const handModeButton = document.querySelector(handModeButtonSelector);\r\n if (handModeButton) {\r\n handModeButton.hidden = true;\r\n this.handSwitchHidden = true;\r\n mutationInstance.disconnect();\r\n }\r\n });\r\n mutationInstance.observe(document.body, {\r\n attributes: true,\r\n childList: true,\r\n characterData: true,\r\n subtree: true,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\r\n *\r\n * @param {String} mathml The original KeyBoard MathML\r\n * @param {Object} editor The editor object.\r\n */\r\n async openHandOnKeyboardMathML(mathml, editor) {\r\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\r\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\r\n await new Promise((resolve) => {\r\n editor.setMathMLWithCallback(mathml, resolve);\r\n });\r\n\r\n // We wait until the hand editor object exists.\r\n await this.waitForHand(editor);\r\n\r\n // Logic to get the hand traces and open the formula in hand mode.\r\n // This logic comes from the editor.\r\n const handEditor = editor.hand;\r\n editor.handTemporalMathML = editor.getMathML();\r\n const handCoordinates = editor.editorModel.getHandStrokes();\r\n handEditor.setStrokes(handCoordinates);\r\n handEditor.fitStrokes(true);\r\n editor.openHand();\r\n }\r\n\r\n /**\r\n * Waits until the hand editor object exists.\r\n * @param {Obect} editor The editor object.\r\n */\r\n async waitForHand(editor) {\r\n while (!editor.hand) {\r\n await new Promise((resolve) => setTimeout(resolve, 100));\r\n }\r\n }\r\n\r\n /**\r\n * Sets the correct toolbar depending if exist other custom toolbars\r\n * at the same time (e.g: Chemistry).\r\n */\r\n updateToolbar() {\r\n this.updateTitle(this.modalDialogInstance);\r\n const customEditor = this.customEditors.getActiveEditor();\r\n\r\n let toolbar;\r\n if (customEditor) {\r\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\r\n\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n }\r\n } else {\r\n toolbar = this.getToolbar();\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n this.customEditors.disable();\r\n }\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\r\n * sets the custom editor title. Otherwise sets the default title.\r\n */\r\n updateTitle() {\r\n const customEditor = this.customEditors.getActiveEditor();\r\n if (customEditor) {\r\n this.modalDialogInstance.setTitle(customEditor.title);\r\n } else {\r\n this.modalDialogInstance.setTitle(\"MathType\");\r\n }\r\n }\r\n\r\n /**\r\n * Returns the editor toolbar, depending on the configuration local or server side.\r\n * @returns {String} - Toolbar identifier.\r\n */\r\n getToolbar() {\r\n let toolbar = \"general\";\r\n if (\"toolbar\" in this.editorAttributes) {\r\n ({ toolbar } = this.editorAttributes);\r\n }\r\n // TODO: Change global integration variable for integration custom toolbar.\r\n if (toolbar === \"general\") {\r\n // eslint-disable-next-line camelcase\r\n toolbar =\r\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\r\n ? \"general\"\r\n : _wrs_int_wirisProperties.toolbar;\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Sets the current {@link ContentManager.editor} instance toolbar.\r\n * @param {String} toolbar - The toolbar name.\r\n */\r\n setToolbar(toolbar) {\r\n this.toolbar = toolbar;\r\n this.editor.setParams({ toolbar: this.toolbar });\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setCustomHeaders(headers) {\r\n let headersObj = {};\r\n\r\n // We control that we only get String or Object as the input.\r\n if (typeof headers === \"object\") {\r\n headersObj = headers;\r\n } else if (typeof headers === \"string\") {\r\n headersObj = Util.convertStringToObject(headers);\r\n }\r\n\r\n this.editor.setParams({ customHeaders: headersObj });\r\n return headersObj;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed. The logic of the changes\r\n * is delegated to {@link EditorListener} class.\r\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\r\n */\r\n hasChanges() {\r\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n // Code to detect Esc event.\r\n // There should be only one element with class name 'wrs_pressed' at the same time.\r\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\r\n if (list.length === 0) {\r\n this.modalDialogInstance.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.modalDialogInstance.submitButton) {\r\n // Focus is on OK button.\r\n this.editor.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\r\n // Focus is on minimize button (_).\r\n this.modalDialogInstance.closeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\r\n // Focus on cancel button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n this.modalDialogInstance.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\r\n // Focus is on X button.\r\n this.modalDialogInstance.minimizeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\r\n // Focus on help button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n const element = document.querySelector('[title=\"Manual\"]');\r\n element.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n } else {\r\n // There should be only one element with class name 'wrs_formulaDisplay'.\r\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\r\n // Focus is on formuladisplay.\r\n this.modalDialogInstance.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * A custom editor is MathType editor with a different\r\n * @typedef {Object} CustomEditor\r\n * @property {String} CustomEditor.name - Custom editor name.\r\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\r\n * @property {String} CustomEditor.icon - Custom editor icon.\r\n * @property {String} CustomEditor.confVariable - Configuration property to manage\r\n * the availability of the custom editor.\r\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\r\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\r\n */\r\n\r\nexport default class CustomEditors {\r\n /**\r\n * @classdesc\r\n * This class represents the MathType custom editors manager.\r\n * A custom editor is MathType editor with a custom toolbar.\r\n * This class associates a {@link CustomEditor} to:\r\n * - It's own formulas\r\n * - A custom toolbar\r\n * - An icon to open it from a HTML editor.\r\n * - A tooltip for the icon.\r\n * - A global variable to enable or disable it globally.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * The custom editors.\r\n * @type {Array.}\r\n */\r\n\r\n this.editors = [];\r\n /**\r\n * The active editor name.\r\n * @type {String}\r\n */\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Adds a {@link CustomEditor} to editors array.\r\n * @param {String} editorName - The editor name.\r\n * @param {CustomEditor} editorParams - The custom editor parameters.\r\n */\r\n addEditor(editorName, editorParams) {\r\n const customEditor = {};\r\n customEditor.name = editorParams.name;\r\n customEditor.toolbar = editorParams.toolbar;\r\n customEditor.icon = editorParams.icon;\r\n customEditor.confVariable = editorParams.confVariable;\r\n customEditor.title = editorParams.title;\r\n customEditor.tooltip = editorParams.tooltip;\r\n this.editors[editorName] = customEditor;\r\n }\r\n\r\n /**\r\n * Enables a {@link CustomEditor}.\r\n * @param {String} customEditorName - The custom editor name.\r\n */\r\n enable(customEditorName) {\r\n this.activeEditor = customEditorName;\r\n }\r\n\r\n /**\r\n * Disables a {@link CustomEditor}.\r\n */\r\n disable() {\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Returns the active editor.\r\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\r\n */\r\n getActiveEditor() {\r\n if (this.activeEditor !== \"default\") {\r\n return this.editors[this.activeEditor];\r\n }\r\n return null;\r\n }\r\n}\r\n","/**\r\n * Represents the configuration properties generated from the frontend (JavaScript variables).\r\n * @type {Object}\r\n * @property {string} imageClassName - Default MathType formula image class.\r\n * @property {string} imageClassName - Default MathType CAS image class.\r\n * @ignore\r\n */\r\nconst jsProperties = {\r\n imageCustomEditorName: \"data-custom-editor\",\r\n imageClassName: \"Wirisformula\",\r\n CASClassName: \"Wiriscas\",\r\n};\r\nexport default jsProperties;\r\n","export default class Event {\r\n /**\r\n * @classdesc\r\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\r\n *\r\n * ```js\r\n * let customEvent = new Event();\r\n * customEvent.properties = {};\r\n *\r\n * let listeners = new Listeners();\r\n * listeners.newListener(eventName, callback);\r\n *\r\n * listeners.fire(eventName, customEvent) *\r\n * ```\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the event should be cancelled.\r\n * @type {Boolean}\r\n */\r\n\r\n this.cancelled = false;\r\n /**\r\n * Indicates if the event should be prevented.\r\n * @type {Boolean}\r\n */\r\n this.defaultPrevented = false;\r\n }\r\n\r\n /**\r\n * Cancels the event.\r\n */\r\n cancel() {\r\n this.cancelled = true;\r\n }\r\n\r\n /**\r\n * Prevents the default action.\r\n */\r\n preventDefault() {\r\n this.defaultPrevented = true;\r\n }\r\n}\r\n","import IntegrationModel from \"./integrationmodel\";\r\n\r\n/**\r\n\r\n */\r\nexport default class PopUpMessage {\r\n /**\r\n * @classdesc\r\n * This class represents a dialog message overlaying a DOM element in order to\r\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\r\n * o canceled. In this last case a callback function should be called.\r\n * @constructs\r\n * @param {Object} popupMessageAttributes - Object containing popup properties.\r\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\r\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\r\n * methods for close and cancel actions.\r\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\r\n */\r\n constructor(popupMessageAttributes) {\r\n /**\r\n * Element to be overlaid when the popup appears.\r\n */\r\n this.overlayElement = popupMessageAttributes.overlayElement;\r\n\r\n this.callbacks = popupMessageAttributes.callbacks;\r\n\r\n /**\r\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\r\n */\r\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\r\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\r\n\r\n /**\r\n * HTMLElement to display the popup message, close button and cancel button.\r\n */\r\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n this.message.id = \"wrs_popupmessage\";\r\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\r\n this.message.setAttribute(\"role\", \"dialog\");\r\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\r\n const paragraph = document.createElement(\"p\");\r\n const text = document.createTextNode(popupMessageAttributes.strings.message);\r\n paragraph.appendChild(text);\r\n paragraph.id = \"description_txt\";\r\n this.message.appendChild(paragraph);\r\n\r\n /**\r\n * HTML element overlaying the overlayElement.\r\n */\r\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\r\n // We create a overlay that close popup message on click in there\r\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n /**\r\n * HTML element containing cancel and close buttons.\r\n */\r\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\r\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\r\n this.buttonArea.id = \"wrs_popup_button_area\";\r\n\r\n // Close button arguments.\r\n const buttonSubmitArguments = {\r\n class: \"wrs_button_accept\",\r\n innerHTML: popupMessageAttributes.strings.submitString,\r\n id: \"wrs_popup_accept_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-close-button\",\r\n };\r\n\r\n /**\r\n * Close button arguments.\r\n */\r\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\r\n this.buttonArea.appendChild(this.closeButton);\r\n\r\n // Cancel button arguments.\r\n const buttonCancelArguments = {\r\n class: \"wrs_button_cancel\",\r\n innerHTML: popupMessageAttributes.strings.cancelString,\r\n id: \"wrs_popup_cancel_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\r\n };\r\n\r\n /**\r\n * Cancel button.\r\n */\r\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\r\n this.buttonArea.appendChild(this.cancelButton);\r\n }\r\n\r\n /**\r\n * This method create a button with arguments and return button dom object\r\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\r\n * @param {String} parameters.id - Button id.\r\n * @param {String} parameters.class - Button class name.\r\n * @param {String} parameters.innerHTML - Button innerHTML text.\r\n * @param {Object} callback- Callback method to call on click event.\r\n * @returns {HTMLElement} HTML button.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createButton(parameters, callback) {\r\n let element = {};\r\n element = document.createElement(\"button\");\r\n element.setAttribute(\"id\", parameters.id);\r\n element.setAttribute(\"class\", parameters.class);\r\n element.innerHTML = parameters.innerHTML;\r\n element.addEventListener(\"click\", callback);\r\n if (parameters[\"data-testid\"]) {\r\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\r\n }\r\n\r\n return element;\r\n }\r\n\r\n /**\r\n * Shows the popupmessage containing a message, and two buttons\r\n * to cancel the action or close the modal dialog.\r\n */\r\n show() {\r\n if (this.overlayWrapper.style.display !== \"block\") {\r\n // Clear focus with blur for prevent press any key.\r\n document.activeElement.blur();\r\n this.overlayWrapper.style.display = \"block\";\r\n this.closeButton.focus();\r\n } else {\r\n this.overlayWrapper.style.display = \"none\";\r\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\r\n }\r\n }\r\n\r\n /**\r\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\r\n * A callback method is called (if defined). For example a method to focus the overlaid element.\r\n */\r\n cancelAction() {\r\n this.overlayWrapper.style.display = \"none\";\r\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\r\n this.callbacks.cancelCallback();\r\n // Set temporal image to null to prevent loading\r\n // an existent formula when starting one from scratch. Make focus come back too.\r\n // IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n }\r\n\r\n /**\r\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\r\n * For example to close the overlaid element.\r\n */\r\n closeAction() {\r\n this.cancelAction();\r\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\r\n this.callbacks.closeCallback();\r\n }\r\n IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Code to detect Esc event.\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n this.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.closeButton) {\r\n this.cancelButton.focus();\r\n } else {\r\n this.closeButton.focus();\r\n }\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * This module provides protection against external focus management scripts\r\n * that might interfere with the MathType editor modal.\r\n */\r\n\r\n/**\r\n * focusProtection function creates and returns methods to prevent external scripts from\r\n * interfering with the focus of the MathType modal dialog.\r\n *\r\n * @returns {Object} An object with protect and unprotect methods.\r\n */\r\nconst focusProtection = () => {\r\n /**\r\n * Initialize focus protection on the given modal element.\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to protect\r\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\r\n * @param {HTMLElement} editorElement - The editor element inside the modal\r\n */\r\n const protect = (modalElement, overlayElement, editorElement) => {\r\n if (!modalElement || !overlayElement || !editorElement) {\r\n console.warn(\"FocusProtection: Missing required elements\");\r\n return;\r\n }\r\n\r\n // Apply the focus protection\r\n overrideFocusBehavior(modalElement, editorElement);\r\n };\r\n\r\n /**\r\n * Apply focus protection by overriding focus-related methods\r\n *\r\n * @param {HTMLElement} modalElement - The modal element\r\n * @param {HTMLElement} editorElement - The editor element to keep focused\r\n * @private\r\n */\r\n const overrideFocusBehavior = (modalElement, editorElement) => {\r\n // Store original focus methods to be able to restore them\r\n const originalElementFocus = HTMLElement.prototype.focus;\r\n const originalElementBlur = HTMLElement.prototype.blur;\r\n\r\n // Override the focus method for all elements\r\n HTMLElement.prototype.focus = function (...args) {\r\n // If the modal is open and this is not part of the modal, prevent focus\r\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\r\n // If some external script is trying to focus another element, prevent it\r\n // and restore focus to the editor\r\n if (editorElement) {\r\n // Use the original focus method to avoid infinite recursion\r\n originalElementFocus.apply(editorElement, args);\r\n }\r\n return;\r\n }\r\n\r\n // Otherwise, allow the focus to happen\r\n originalElementFocus.apply(this, args);\r\n };\r\n\r\n // Store the methods to remove them when the modal is closed\r\n modalElement.originalElementFocus = originalElementFocus;\r\n modalElement.originalElementBlur = originalElementBlur;\r\n };\r\n\r\n /**\r\n * Remove focus protection from the modal\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to unprotect\r\n */\r\n const unprotect = (modalElement) => {\r\n if (!modalElement) {\r\n return;\r\n }\r\n\r\n // Restore original focus methods\r\n if (modalElement.originalElementFocus) {\r\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\r\n delete modalElement.originalElementFocus;\r\n }\r\n\r\n if (modalElement.originalElementBlur) {\r\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\r\n delete modalElement.originalElementBlur;\r\n }\r\n };\r\n\r\n return {\r\n protect,\r\n unprotect,\r\n };\r\n};\r\n\r\nexport default focusProtection;\r\n","// eslint-disable-next-line max-classes-per-file\r\nimport PopUpMessage from \"./popupmessage\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport Listeners from \"./listeners\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Telemeter from \"./telemeter\";\r\nimport IntegrationModel from \"./integrationmodel\";\r\nimport Core from \"./core.src\";\r\nimport focusProtection from \"./focusprotection\";\r\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\r\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\r\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\r\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\r\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\r\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\r\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\r\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\r\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\r\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\r\nconst { unprotect, protect } = focusProtection();\r\n\r\n/**\r\n * @typedef {Object} DeviceProperties\r\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\r\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\r\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\r\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\r\n * False otherwise.\r\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\r\n * False otherwise.\r\n */\r\n\r\nexport default class ModalDialog {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog. The modal dialog admits\r\n * a {@link ContentManager} instance to manage the content of the dialog.\r\n * @constructs\r\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\r\n */\r\n constructor(modalDialogAttributes) {\r\n this.attributes = modalDialogAttributes;\r\n\r\n // Metrics.\r\n const ua = navigator.userAgent.toLowerCase();\r\n const isAndroid = ua.indexOf(\"android\") > -1;\r\n const isIOS = ContentManager.isIOS();\r\n this.iosSoftkeyboardOpened = false;\r\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\r\n this.iosDivHeight = `auto`;\r\n\r\n const deviceWidth = window.outerWidth;\r\n const deviceHeight = window.outerHeight;\r\n\r\n const landscape = deviceWidth > deviceHeight;\r\n const portrait = deviceWidth < deviceHeight;\r\n\r\n // TODO: Detect isMobile without using editor metrics.\r\n const isLandscape = landscape && this.attributes.height > deviceHeight;\r\n const isPortrait = portrait && this.attributes.width > deviceWidth;\r\n const isMobile = ContentManager.isMobile();\r\n\r\n // Obtain number of current instance.\r\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\r\n\r\n // Device object properties.\r\n\r\n /**\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {\r\n orientation: landscape ? \"landscape\" : \"portrait\",\r\n isAndroid,\r\n isIOS,\r\n isMobile,\r\n isDesktop: !isMobile && !isIOS && !isAndroid,\r\n };\r\n\r\n this.properties = {\r\n created: false,\r\n state: \"\",\r\n previousState: \"\",\r\n position: { bottom: 0, right: 10 },\r\n size: { height: 338, width: 580 },\r\n };\r\n\r\n /**\r\n * Object to keep website's style before change it on lock scroll for mobile devices.\r\n * @type {Object}\r\n * @property {String} bodyStylePosition - Previous body style position.\r\n * @property {String} bodyStyleOverflow - Previous body style overflow.\r\n * @property {String} htmlStyleOverflow - Previous body style overflow.\r\n * @property {String} windowScrollX - Previous window's scroll Y.\r\n * @property {String} windowScrollY - Previous window's scroll X.\r\n */\r\n this.websiteBeforeLockParameters = null;\r\n\r\n let attributes = {};\r\n attributes.class = \"wrs_modal_overlay\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.overlay = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title_bar\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.titleBar = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.title = Util.createElement(\"div\", attributes);\r\n this.title.innerHTML = \"offline\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_close_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"close\");\r\n attributes.style = {};\r\n this.closeDiv = Util.createElement(\"a\", attributes);\r\n this.closeDiv.setAttribute(\"role\", \"button\");\r\n this.closeDiv.setAttribute(\"tabindex\", 3);\r\n // Apply styles and events after the creation as createElement doesn't process them correctly\r\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\r\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\r\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\r\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\r\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\r\n // To identifiy the element in automated testing\r\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_stack_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"exit_fullscreen\");\r\n this.stackDiv = Util.createElement(\"a\", attributes);\r\n this.stackDiv.setAttribute(\"role\", \"button\");\r\n this.stackDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\r\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\r\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\r\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\r\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\r\n // To identifiy the element in automated testing\r\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_maximize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"fullscreen\");\r\n this.maximizeDiv = Util.createElement(\"a\", attributes);\r\n this.maximizeDiv.setAttribute(\"role\", \"button\");\r\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\r\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\r\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\r\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\r\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\r\n // To identifiy the element in automated testing\r\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_minimize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"minimize\");\r\n this.minimizeDiv = Util.createElement(\"a\", attributes);\r\n this.minimizeDiv.setAttribute(\"role\", \"button\");\r\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\r\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\r\n // To identify the element in automated testing\r\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_dialogContainer\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.role = \"dialog\";\r\n this.container = Util.createElement(\"div\", attributes);\r\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_wrapper\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.wrapper = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_content_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.contentContainer = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_controls\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.controls = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_buttons_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.buttonContainer = Util.createElement(\"div\", attributes);\r\n\r\n // Buttons: all button must be created using createSubmitButton method.\r\n this.submitButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_accept\"),\r\n class: \"wrs_modal_button_accept\",\r\n innerHTML: StringManager.get(\"accept\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-insert-button\",\r\n },\r\n this.submitAction.bind(this),\r\n );\r\n\r\n this.cancelButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_cancel\"),\r\n class: \"wrs_modal_button_cancel\",\r\n innerHTML: StringManager.get(\"cancel\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cancel-button\",\r\n },\r\n this.cancelAction.bind(this),\r\n );\r\n\r\n this.contentManager = null;\r\n\r\n // Overlay popup.\r\n const popupStrings = {\r\n cancelString: StringManager.get(\"cancel\"),\r\n submitString: StringManager.get(\"close\"),\r\n message: StringManager.get(\"close_modal_warning\"),\r\n };\r\n\r\n const callbacks = {\r\n closeCallback: () => {\r\n this.close(\"mtc_close\");\r\n },\r\n cancelCallback: () => {\r\n this.focus();\r\n },\r\n };\r\n\r\n const popupupProperties = {\r\n overlayElement: this.container,\r\n callbacks,\r\n strings: popupStrings,\r\n };\r\n\r\n this.popup = new PopUpMessage(popupupProperties);\r\n\r\n /**\r\n * Indicates if directionality of the modal dialog is RTL. false by default.\r\n * @type {Boolean}\r\n */\r\n this.rtl = false;\r\n if (\"rtl\" in this.attributes) {\r\n this.rtl = this.attributes.rtl;\r\n }\r\n\r\n // Event handlers need modal instance context.\r\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\r\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\r\n }\r\n\r\n /**\r\n * This method sets an ContentManager instance to ModalDialog. ContentManager\r\n * manages the logic of ModalDialog content: submit, update, close and changes.\r\n * @param {ContentManager} contentManager - ContentManager instance.\r\n */\r\n setContentManager(contentManager) {\r\n this.contentManager = contentManager;\r\n }\r\n\r\n /**\r\n * Returns the modal contentElement object.\r\n * @returns {ContentManager} the instance of the ContentManager class.\r\n */\r\n getContentManager() {\r\n return this.contentManager;\r\n }\r\n\r\n /**\r\n * This method is called when the modal object has been submitted. Calls\r\n * contentElement submitAction method - if exists - and closes the modal\r\n * object. No logic about the content should be placed here,\r\n * contentElement.submitAction is the responsible of the content logic.\r\n */\r\n async submitAction() {\r\n if (typeof this.contentManager.submitAction !== \"undefined\") {\r\n this.contentManager.submitAction();\r\n }\r\n\r\n await this.close(\"mtc_insert\");\r\n }\r\n\r\n /**\r\n * Performs the cancel action.\r\n * If there are no changes in the content, it closes the modal.\r\n * Otherwise, it shows a pop-up message to confirm the cancel action.\r\n * @returns {Promise} - A promise that resolves when the modal is closed.\r\n */\r\n async cancelAction() {\r\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\r\n IntegrationModel.setActionsOnCancelButtons();\r\n await this.close(\"mtc_close\");\r\n } else {\r\n this.showPopUpMessage();\r\n }\r\n }\r\n\r\n /**\r\n * Returns a button element.\r\n * @param {Object} properties - Input button properties.\r\n * @param {String} properties.class - Input button class.\r\n * @param {String} properties.innerHTML - Input button innerHTML.\r\n * @param {Object} callback - Callback function associated to click event.\r\n * @returns {HTMLButtonElement} The button element.\r\n *\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createSubmitButton(properties, callback) {\r\n class SubmitButton {\r\n constructor() {\r\n this.element = document.createElement(\"button\");\r\n this.element.id = properties.id;\r\n this.element.className = properties.class;\r\n this.element.innerHTML = properties.innerHTML;\r\n this.element.dataset.testid = properties[\"data-testid\"];\r\n Util.addEvent(this.element, \"click\", callback);\r\n }\r\n\r\n getElement() {\r\n return this.element;\r\n }\r\n }\r\n return new SubmitButton(properties, callback).getElement();\r\n }\r\n\r\n /**\r\n * Creates the modal window object inserting a contentElement object.\r\n */\r\n create() {\r\n /* Modal Window Structure\r\n _____________________________________________________________________________________\r\n |wrs_modal_dialog_Container |\r\n | _________________________________________________________________________________ |\r\n | |title_bar minimize_button stack_button close_button | |\r\n | |_______________________________________________________________________________| |\r\n | |wrapper | |\r\n | | _____________________________________________________________________________ | |\r\n | | |content | | |\r\n | | | | | |\r\n | | | | | |\r\n | | |___________________________________________________________________________| | |\r\n | | _____________________________________________________________________________ | |\r\n | | |controls | | |\r\n | | | ___________________________________ | | |\r\n | | | |buttonContainer | | | |\r\n | | | | _______________________________ | | | |\r\n | | | | |button_accept | button_cancel| | | | |\r\n | | | |_|_____________ |______________|_| | | |\r\n | | |___________________________________________________________________________| | |\r\n | |_______________________________________________________________________________| |\r\n |___________________________________________________________________________________| */\r\n\r\n this.titleBar.appendChild(this.closeDiv);\r\n this.titleBar.appendChild(this.stackDiv);\r\n this.titleBar.appendChild(this.maximizeDiv);\r\n this.titleBar.appendChild(this.minimizeDiv);\r\n this.titleBar.appendChild(this.title);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n this.container.appendChild(this.titleBar);\r\n }\r\n\r\n this.wrapper.appendChild(this.contentContainer);\r\n this.wrapper.appendChild(this.controls);\r\n\r\n this.controls.appendChild(this.buttonContainer);\r\n\r\n this.buttonContainer.appendChild(this.submitButton);\r\n this.buttonContainer.appendChild(this.cancelButton);\r\n\r\n this.container.appendChild(this.wrapper);\r\n\r\n // Check if browser has scrollBar before modal has modified.\r\n this.recalculateScrollBar();\r\n\r\n document.body.appendChild(this.container);\r\n document.body.appendChild(this.overlay);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n // Desktop.\r\n this.createModalWindowDesktop();\r\n this.createResizeButtons();\r\n\r\n this.addListeners();\r\n // Maximize window only when the configuration is set and the device is not iOS or Android.\r\n if (Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n } else if (this.deviceProperties.isAndroid) {\r\n this.createModalWindowAndroid();\r\n } else if (this.deviceProperties.isIOS) {\r\n this.createModalWindowIos();\r\n }\r\n\r\n if (this.contentManager != null) {\r\n this.contentManager.insert(this);\r\n }\r\n\r\n this.properties.open = true;\r\n this.properties.created = true;\r\n\r\n // Checks language directionality.\r\n if (this.isRTL()) {\r\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\r\n this.container.className += \" wrs_modal_rtl\";\r\n }\r\n }\r\n\r\n /**\r\n * Creates a button in the modal object to resize it.\r\n */\r\n createResizeButtons() {\r\n // This is a definition of Resize Button Bottom-Right.\r\n this.resizerBR = document.createElement(\"div\");\r\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\r\n this.resizerBR.innerHTML = \"โ—ข\";\r\n // To identifiy the element in automated testing\r\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\r\n // This is a definition of Resize Button Top-Left.\r\n this.resizerTL = document.createElement(\"div\");\r\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\r\n // To identifiy the element in automated testing\r\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\r\n // Append resize buttons to modal.\r\n this.container.appendChild(this.resizerBR);\r\n this.titleBar.appendChild(this.resizerTL);\r\n // Add events to resize on click and drag.\r\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\r\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\r\n }\r\n\r\n /**\r\n * Initialize variables for Bottom-Right resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateBR(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, false);\r\n }\r\n\r\n /**\r\n * Initialize variables for Top-Left resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateTL(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, true);\r\n }\r\n\r\n /**\r\n * Common method to initialize variables at resize.\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n initializeResizeProperties(mouseEvent, leftOption) {\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n this.resizeDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Save Initial state of modal to compare on drag and obtain the difference.\r\n this.initialWidth = parseInt(this.container.style.width, 10);\r\n this.initialHeight = parseInt(this.container.style.height, 10);\r\n if (!leftOption) {\r\n this.initialRight = parseInt(this.container.style.right, 10);\r\n this.initialBottom = parseInt(this.container.style.bottom, 10);\r\n } else {\r\n this.leftScale = true;\r\n }\r\n if (!this.initialRight) {\r\n this.initialRight = 0;\r\n }\r\n if (!this.initialBottom) {\r\n this.initialBottom = 0;\r\n }\r\n // Disable mouse events on editor when we start to drag modal.\r\n document.body.style[\"user-select\"] = \"none\";\r\n }\r\n\r\n /**\r\n * This method opens the modal window, restoring the previous state, position and metrics,\r\n * if exists. By default the modal object opens in stack mode.\r\n */\r\n open() {\r\n // Removing close class.\r\n this.removeClass(\"wrs_closed\");\r\n // Hiding keyboard for mobile devices.\r\n const { isIOS } = this.deviceProperties;\r\n const { isAndroid } = this.deviceProperties;\r\n const { isMobile } = this.deviceProperties;\r\n if (isIOS || isAndroid || isMobile) {\r\n // Restore scale to 1.\r\n this.restoreWebsiteScale();\r\n this.lockWebsiteScroll();\r\n // Due to editor wait we need to wait until editor focus.\r\n setTimeout(() => {\r\n this.hideKeyboard();\r\n }, 400);\r\n }\r\n\r\n // New modal window. He need to create the whole object.\r\n if (!this.properties.created) {\r\n this.create();\r\n } else {\r\n // Previous state closed. Open method can be called even the previous state is open,\r\n // for example updating the content of the modal object.\r\n if (!this.properties.open) {\r\n this.properties.open = true;\r\n\r\n // Restoring the previous open state: if the modal object has been closed\r\n // re-open it should preserve the state and the metrics.\r\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\r\n this.restoreState();\r\n }\r\n }\r\n\r\n // Maximize window only when the configuration is set and the device is not iOs or Android.\r\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n\r\n // In iOS we need to recalculate the size of the modal object because\r\n // iOS keyboard is a float div which can overlay the modal object.\r\n if (this.deviceProperties.isIOS) {\r\n this.iosSoftkeyboardOpened = false;\r\n }\r\n }\r\n\r\n if (!ContentManager.isEditorLoaded()) {\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.displayEditor();\r\n });\r\n this.contentManager.addListener(listener);\r\n } else {\r\n this.displayEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Prepares and displays the editor in the modal.\r\n *\r\n * This method is responsible for displaying the MathType editor inside the modal container.\r\n *\r\n * For Moodle environments, it applies focus protection to prevent external scripts\r\n * from hijacking focus away from the editor while it's open. This is particularly\r\n * important in Moodle which may have its own focus management scripts.\r\n * @returns {void}\r\n */\r\n displayEditor() {\r\n if (this.contentManager.integrationModel.isMoodle) {\r\n protect(this.container, this.overlay, this.contentContainer);\r\n }\r\n\r\n // Initialize and open the editor using the contentManager.\r\n this.contentManager.onOpen(this);\r\n }\r\n\r\n /**\r\n * Closes the modal.\r\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\r\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\r\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\r\n * @returns {Promise} A promise that resolves when the modal is closed.\r\n */\r\n async close(trigger) {\r\n // Remove focus protection before closing\r\n unprotect(this.container);\r\n\r\n this.removeClass(\"wrs_maximized\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.removeClass(\"wrs_stack\");\r\n this.addClass(\"wrs_closed\");\r\n this.saveModalProperties();\r\n this.unlockWebsiteScroll();\r\n this.properties.open = false;\r\n\r\n if (trigger) {\r\n try {\r\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\r\n toolbar: this.contentManager.toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\r\n }\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalClose\", {});\r\n }\r\n\r\n /**\r\n * Closes modal window and destroys the object.\r\n */\r\n destroy() {\r\n // Remove focus protection before destroying\r\n unprotect(this.container);\r\n\r\n // Close modal window.\r\n this.close();\r\n // Remove listeners and destroy the object.\r\n this.removeListeners();\r\n this.overlay.remove();\r\n this.container.remove();\r\n // Reset properties to allow open again.\r\n this.properties.created = false;\r\n }\r\n\r\n /**\r\n * Sets the website scale to one.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n restoreWebsiteScale() {\r\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\r\n // Let the equal symbols in order to search and make meta's final content.\r\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\r\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\r\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\r\n const contentAttr = viewportelement.getAttribute(\"content\");\r\n // If it exists, we need to maintain old values and put our values.\r\n if (contentAttr) {\r\n const attrArray = contentAttr.split(\",\");\r\n let finalContentMeta = \"\";\r\n const oldAttrs = [];\r\n for (let i = 0; i < attrArray.length; i += 1) {\r\n let isAttrToUpdate = false;\r\n let j = 0;\r\n while (!isAttrToUpdate && j < contentAttrs.length) {\r\n if (attrArray[i].indexOf(contentAttrs[j])) {\r\n isAttrToUpdate = true;\r\n }\r\n j += 1;\r\n }\r\n\r\n if (!isAttrToUpdate) {\r\n oldAttrs.push(attrArray[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < contentAttrs.length; i += 1) {\r\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\r\n finalContentMeta += i === 0 ? attr : `,${attr}`;\r\n }\r\n\r\n for (let i = 0; i < oldAttrs.length; i += 1) {\r\n finalContentMeta += `,${oldAttrs[i]}`;\r\n }\r\n viewportelement.setAttribute(\"content\", finalContentMeta);\r\n // It needs to set to empty because setAttribute refresh only when attribute is different.\r\n viewportelement.setAttribute(\"content\", \"\");\r\n viewportelement.setAttribute(\"content\", contentAttr);\r\n } else {\r\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\r\n viewportelement.removeAttribute(\"content\");\r\n }\r\n };\r\n\r\n if (!viewportmeta) {\r\n viewportmeta = document.createElement(\"meta\");\r\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n viewportmeta.remove();\r\n } else {\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n }\r\n }\r\n\r\n /**\r\n * Locks website scroll for mobile devices.\r\n */\r\n lockWebsiteScroll() {\r\n this.websiteBeforeLockParameters = {\r\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\r\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\r\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\r\n windowScrollX: window.scrollX,\r\n windowScrollY: window.scrollY,\r\n };\r\n }\r\n\r\n /**\r\n * Unlocks website scroll for mobile devices.\r\n */\r\n unlockWebsiteScroll() {\r\n if (this.websiteBeforeLockParameters) {\r\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\r\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\r\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\r\n const { windowScrollX } = this.websiteBeforeLockParameters;\r\n const { windowScrollY } = this.websiteBeforeLockParameters;\r\n window.scrollTo(windowScrollX, windowScrollY);\r\n this.websiteBeforeLockParameters = null;\r\n }\r\n }\r\n\r\n /**\r\n * Util function to known if browser is IE11.\r\n * @returns {Boolean} true if the browser is IE11. false otherwise.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n isIE11() {\r\n if (\r\n navigator.userAgent.search(\"Msie/\") >= 0 ||\r\n navigator.userAgent.search(\"Trident/\") >= 0 ||\r\n navigator.userAgent.search(\"Edge/\") >= 0\r\n ) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns if the current language type is RTL.\r\n * @return {Boolean} true if current language is RTL. false otherwise.\r\n */\r\n isRTL() {\r\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\r\n return true;\r\n }\r\n return this.rtl;\r\n }\r\n\r\n /**\r\n * Adds a class to all modal ModalDialog DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n addClass(className) {\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.titleBar, className);\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.container, className);\r\n Util.addClass(this.contentContainer, className);\r\n Util.addClass(this.stackDiv, className);\r\n Util.addClass(this.minimizeDiv, className);\r\n Util.addClass(this.maximizeDiv, className);\r\n Util.addClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Remove a class from all modal DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n removeClass(className) {\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.titleBar, className);\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.container, className);\r\n Util.removeClass(this.contentContainer, className);\r\n Util.removeClass(this.stackDiv, className);\r\n Util.removeClass(this.minimizeDiv, className);\r\n Util.removeClass(this.maximizeDiv, className);\r\n Util.removeClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Create modal dialog for desktop.\r\n */\r\n createModalWindowDesktop() {\r\n this.addClass(\"wrs_modal_desktop\");\r\n this.stack();\r\n }\r\n\r\n /**\r\n * Create modal dialog for non android devices.\r\n */\r\n createModalWindowAndroid() {\r\n this.addClass(\"wrs_modal_android\");\r\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Create modal dialog for iOS devices.\r\n */\r\n createModalWindowIos() {\r\n this.addClass(\"wrs_modal_ios\");\r\n // Refresh the size when the orientation is changed.\r\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Restore previous state, position and size of previous stacked modal dialog.\r\n */\r\n restoreState() {\r\n if (this.properties.state === \"maximized\") {\r\n // Reset states for prevent return to stack state.\r\n this.maximize();\r\n } else if (this.properties.state === \"minimized\") {\r\n // Reset states for prevent return to stack state.\r\n this.properties.state = this.properties.previousState;\r\n this.properties.previousState = \"\";\r\n this.minimize();\r\n } else {\r\n this.stack();\r\n }\r\n }\r\n\r\n /**\r\n * Stacks the modal object.\r\n */\r\n stack() {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"stack\";\r\n this.removeClass(\"wrs_maximized\");\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.addClass(\"wrs_stack\");\r\n\r\n // Change maximize/minimize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n this.restoreModalProperties();\r\n\r\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\r\n this.setResizeButtonsVisibility();\r\n }\r\n\r\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Minimizes the modal object.\r\n */\r\n minimize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n this.title.style.cursor = \"pointer\";\r\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\r\n this.stack();\r\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n // Setting css to prevent important tag into css style.\r\n this.container.style.height = \"30px\";\r\n this.container.style.width = \"250px\";\r\n this.container.style.bottom = \"0px\";\r\n this.container.style.right = \"10px\";\r\n\r\n this.removeListeners();\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"minimized\";\r\n this.setResizeButtonsVisibility();\r\n this.minimizeDiv.title = StringManager.get(\"maximize\");\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.removeClass(\"wrs_stack\");\r\n } else {\r\n this.removeClass(\"wrs_maximized\");\r\n }\r\n this.addClass(\"wrs_minimized\");\r\n\r\n // Change minimize icon to maximize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n }\r\n }\r\n\r\n /**\r\n * Maximizes the modal object.\r\n */\r\n maximize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n if (this.properties.state !== \"maximized\") {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"maximized\";\r\n }\r\n // Don't permit resize on maximize mode.\r\n this.setResizeButtonsVisibility();\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.container.style.left = null;\r\n this.container.style.top = null;\r\n this.removeClass(\"wrs_stack\");\r\n }\r\n\r\n this.addClass(\"wrs_maximized\");\r\n\r\n // Change maximize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n // Set size to 80% screen with a max size.\r\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\r\n if (this.container.clientHeight > 700) {\r\n this.container.style.height = \"700px\";\r\n }\r\n if (this.container.clientWidth > 1200) {\r\n this.container.style.width = \"1200px\";\r\n }\r\n\r\n // Setting modal position in center on screen.\r\n const { innerHeight } = window;\r\n const { innerWidth } = window;\r\n const { offsetHeight } = this.container;\r\n const { offsetWidth } = this.container;\r\n const bottom = innerHeight / 2 - offsetHeight / 2;\r\n const right = innerWidth / 2 - offsetWidth / 2;\r\n\r\n this.setPosition(bottom, right);\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n this.recalculateSize();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Expand again the modal object from a minimized state.\r\n */\r\n reExpand() {\r\n if (this.properties.state === \"minimized\") {\r\n if (this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n this.stack();\r\n }\r\n this.title.style.cursor = \"\";\r\n }\r\n }\r\n\r\n /**\r\n * Sets modal size.\r\n * @param {Number} height - Height of the ModalDialog\r\n * @param {Number} width - Width of the ModalDialog.\r\n */\r\n setSize(height, width) {\r\n this.container.style.height = `${height}px`;\r\n this.container.style.width = `${width}px`;\r\n this.recalculateSize();\r\n }\r\n\r\n /**\r\n * Sets modal position using bottom and right style attributes.\r\n * @param {number} bottom - bottom attribute.\r\n * @param {number} right - right attribute.\r\n */\r\n setPosition(bottom, right) {\r\n this.container.style.bottom = `${bottom}px`;\r\n this.container.style.right = `${right}px`;\r\n }\r\n\r\n /**\r\n * Saves position and size parameters of and open ModalDialog. This attributes\r\n * are needed to restore it on re-open.\r\n */\r\n saveModalProperties() {\r\n // Saving values of modal only when modal is in stack state.\r\n if (this.properties.state === \"stack\") {\r\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\r\n this.properties.position.right = parseInt(this.container.style.right, 10);\r\n this.properties.size.width = parseInt(this.container.style.width, 10);\r\n this.properties.size.height = parseInt(this.container.style.height, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Restore ModalDialog position and size parameters.\r\n */\r\n restoreModalProperties() {\r\n if (this.properties.state === \"stack\") {\r\n // Restoring Bottom and Right values from last modal.\r\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\r\n // Restoring Height and Left values from last modal.\r\n this.setSize(this.properties.size.height, this.properties.size.width);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the modal dialog initial size.\r\n */\r\n recalculateSize() {\r\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\r\n }\r\n\r\n /**\r\n * Enable or disable visibility of resize buttons in modal window depend on state.\r\n */\r\n setResizeButtonsVisibility() {\r\n if (this.properties.state === \"stack\") {\r\n this.resizerTL.style.visibility = \"visible\";\r\n this.resizerBR.style.visibility = \"visible\";\r\n } else {\r\n this.resizerTL.style.visibility = \"hidden\";\r\n this.resizerBR.style.visibility = \"hidden\";\r\n }\r\n }\r\n\r\n /**\r\n * Makes an object draggable adding mouse and touch events.\r\n */\r\n addListeners() {\r\n // Button events (maximize, minimize, stack and close).\r\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\r\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\r\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\r\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\r\n this.maximizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n }\r\n },\r\n true,\r\n );\r\n this.stackDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.minimizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.closeDiv.addEventListener(\"keypress\", (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n });\r\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\r\n\r\n // Overlay events (close).\r\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n // Mouse events.\r\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\r\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\r\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\r\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\r\n // Key events.\r\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\r\n }\r\n\r\n /**\r\n * Removes draggable events from an object.\r\n */\r\n removeListeners() {\r\n // Mouse events.\r\n Util.removeEvent(window, \"mousedown\", this.startDrag);\r\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\r\n Util.removeEvent(window, \"mousemove\", this.drag);\r\n Util.removeEvent(window, \"resize\", this.onWindowResize);\r\n // Key events.\r\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\r\n }\r\n\r\n /**\r\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n * @return {Object} With the X and Y coordinates.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n eventClient(mouseEvent) {\r\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\r\n const client = {\r\n X: mouseEvent.changedTouches[0].clientX,\r\n Y: mouseEvent.changedTouches[0].clientY,\r\n };\r\n return client;\r\n }\r\n const client = {\r\n X: mouseEvent.clientX,\r\n Y: mouseEvent.clientY,\r\n };\r\n return client;\r\n }\r\n\r\n /**\r\n * Start drag function: set the object dragDataObject with the draggable\r\n * object offsets coordinates.\r\n * when drag starts (on touchstart or mousedown events).\r\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\r\n */\r\n startDrag(mouseEvent) {\r\n if (this.properties.state === \"minimized\") {\r\n return;\r\n }\r\n if (mouseEvent.target === this.title) {\r\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\r\n // Save first click mouse point on screen.\r\n this.dragDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Reset last drag position when start drag.\r\n this.lastDrag = {\r\n x: \"0px\",\r\n y: \"0px\",\r\n };\r\n // Init right and bottom values for window modal if it isn't exist.\r\n if (this.container.style.right === \"\") {\r\n this.container.style.right = \"0px\";\r\n }\r\n if (this.container.style.bottom === \"\") {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n\r\n // Needed for IE11 for apply disabled mouse events on editor because\r\n // internet explorer needs a dynamic object to apply this property.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = 'relative';\r\n }\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n // Obtain screen limits for prevent overflow.\r\n this.limitWindow = this.getLimitWindow();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Updates dragDataObject with the draggable object coordinates when\r\n * the draggable object is being moved.\r\n * @param {MouseEvent} mouseEvent - The mouse event.\r\n */\r\n drag(mouseEvent) {\r\n if (this.dragDataObject) {\r\n mouseEvent.preventDefault();\r\n // Calculate max and min between actual mouse position and limit of screeen.\r\n // It restric the movement of modal into window.\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\r\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\r\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\r\n // Subtract limit with first position to obtain relative pixels increment\r\n // to the anchor point.\r\n const dragX = `${limitX - this.dragDataObject.x}px`;\r\n const dragY = `${limitY - this.dragDataObject.y}px`;\r\n // Save last valid position of modal before window overflow.\r\n this.lastDrag = {\r\n x: dragX,\r\n y: dragY,\r\n };\r\n // This move modal with hardware acceleration.\r\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\r\n }\r\n if (this.resizeDataObject) {\r\n const { innerWidth } = window;\r\n const { innerHeight } = window;\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\r\n if (limitX < 0) {\r\n limitX = 0;\r\n }\r\n\r\n if (limitY < 0) {\r\n limitY = 0;\r\n }\r\n\r\n let scaleMultiplier;\r\n if (this.leftScale) {\r\n scaleMultiplier = -1;\r\n } else {\r\n scaleMultiplier = 1;\r\n }\r\n\r\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\r\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\r\n if (!this.leftScale) {\r\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\r\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\r\n } else {\r\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\r\n this.container.style.width = \"580px\";\r\n }\r\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\r\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\r\n } else {\r\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\r\n this.container.style.height = \"338px\";\r\n }\r\n }\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boundaries of actual window to limit modal movement.\r\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\r\n */\r\n getLimitWindow() {\r\n // Obtain dimensions of window page.\r\n const maxWidth = window.innerWidth;\r\n const maxHeight = window.innerHeight;\r\n\r\n // Calculate relative position of mouse point into window.\r\n const { offsetHeight } = this.container;\r\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\r\n const contStyleRight = parseInt(this.container.style.right, 10);\r\n\r\n const { pageXOffset } = window;\r\n const dragY = this.dragDataObject.y;\r\n const dragX = this.dragDataObject.x;\r\n\r\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\r\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\r\n\r\n // Calculate limits with sizes of window, modal and mouse position.\r\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\r\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\r\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\r\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\r\n const minPointer = { x: minPointerX, y: minPointerY };\r\n const maxPointer = { x: maxPointerX, y: maxPointerY };\r\n return { minPointer, maxPointer };\r\n }\r\n\r\n /**\r\n * Returns the scrollbar width size of browser\r\n * @returns {Number} The scrollbar width.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getScrollBarWidth() {\r\n // Create a paragraph with full width of page.\r\n const inner = document.createElement(\"p\");\r\n inner.style.width = \"100%\";\r\n inner.style.height = \"200px\";\r\n\r\n // Create a hidden div to compare sizes.\r\n const outer = document.createElement(\"div\");\r\n outer.style.position = \"absolute\";\r\n outer.style.top = \"0px\";\r\n outer.style.left = \"0px\";\r\n outer.style.visibility = \"hidden\";\r\n outer.style.width = \"200px\";\r\n outer.style.height = \"150px\";\r\n outer.style.overflow = \"hidden\";\r\n outer.appendChild(inner);\r\n\r\n document.body.appendChild(outer);\r\n const widthOuter = inner.offsetWidth;\r\n\r\n // Change type overflow of paragraph for measure scrollbar.\r\n outer.style.overflow = \"scroll\";\r\n let widthInner = inner.offsetWidth;\r\n\r\n // If measure is the same, we compare with internal div.\r\n if (widthOuter === widthInner) {\r\n widthInner = outer.clientWidth;\r\n }\r\n document.body.removeChild(outer);\r\n\r\n return widthOuter - widthInner;\r\n }\r\n\r\n /**\r\n * Set the dragDataObject to null.\r\n */\r\n stopDrag() {\r\n // Due to we have multiple events that call this function, we need only to execute\r\n // the next modifiers one time,\r\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\r\n if (this.dragDataObject || this.resizeDataObject) {\r\n // If modal doesn't change, it's not necessary to set position with interpolation.\r\n this.container.style.transform = \"\";\r\n if (this.dragDataObject) {\r\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\r\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\r\n }\r\n // We make focus on editor after drag modal windows to prevent lose focus.\r\n this.focus();\r\n // Restore mouse events on iframe.\r\n // this.iframe.style['pointer-events'] = 'auto';\r\n document.body.style[\"user-select\"] = \"\";\r\n // Restore static state of iframe if we use Internet Explorer.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = null;\r\n }\r\n // Active text select event.\r\n Util.removeClass(document.body, \"wrs_noselect\");\r\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\r\n }\r\n this.dragDataObject = null;\r\n this.resizeDataObject = null;\r\n this.initialWidth = null;\r\n this.leftScale = null;\r\n }\r\n\r\n /**\r\n * Recalculates scale for modal when resize browser window.\r\n */\r\n onWindowResize() {\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n }\r\n\r\n /**\r\n * Triggers keyboard events:\r\n * - Tab key tab to go to submit button.\r\n * - Esc key to close the modal dialog.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Popupmessage is not oppened.\r\n if (this.popup.overlayWrapper.style.display !== \"block\") {\r\n // Code to detect Esc event\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n if (this.properties.open) {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.cancelButton) {\r\n this.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.submitButton) {\r\n this.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n } else {\r\n // Popupmessage oppened.\r\n this.popup.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating position for modal dialog when the browser is resized.\r\n */\r\n recalculatePosition() {\r\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\r\n if (parseInt(this.container.style.right, 10) < 0) {\r\n this.container.style.right = \"0px\";\r\n }\r\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\r\n if (parseInt(this.container.style.bottom, 10) < 0) {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating scale for modal when the browser is resized.\r\n */\r\n recalculateScale() {\r\n let sizeModified = false;\r\n\r\n if (parseInt(this.container.style.width, 10) > 580) {\r\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.width = \"580px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (parseInt(this.container.style.height, 10) > 338) {\r\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.height = \"338px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (sizeModified) {\r\n this.recalculateSize();\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating width of browser scroll bar.\r\n */\r\n recalculateScrollBar() {\r\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\r\n if (this.hasScrollBar) {\r\n this.scrollbarWidth = this.getScrollBarWidth();\r\n } else {\r\n this.scrollbarWidth = 0;\r\n }\r\n }\r\n\r\n /**\r\n * Hide soft keyboards on iOS devices.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n hideKeyboard() {\r\n // iOS keyboard can't be detected or hide directly from JavaScript.\r\n // So, this method simulates that user focus a text input and blur\r\n // the selection.\r\n const inputField = document.createElement(\"input\");\r\n this.container.appendChild(inputField);\r\n inputField.focus();\r\n inputField.blur();\r\n // Is removed to not see it.\r\n inputField.remove();\r\n }\r\n\r\n /**\r\n * Focus to contentManager object.\r\n */\r\n focus() {\r\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\r\n this.contentManager.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Returns true when the device is on portrait mode.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n portraitMode() {\r\n return window.innerHeight > window.innerWidth;\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is opened.\r\n */\r\n handleOpenedIosSoftkeyboard() {\r\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\r\n }\r\n }\r\n this.iosSoftkeyboardOpened = true;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is closed.\r\n */\r\n handleClosedIosSoftkeyboard() {\r\n this.iosSoftkeyboardOpened = false;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on iOS.\r\n */\r\n orientationChangeIosSoftkeyboard() {\r\n if (this.iosSoftkeyboardOpened) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\r\n }\r\n } else {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on Android.\r\n */\r\n orientationChangeAndroidSoftkeyboard() {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Set iframe container height.\r\n * @param {Number} height - New height.\r\n */\r\n setContainerHeight(height) {\r\n this.iosDivHeight = height;\r\n this.wrapper.style.height = height;\r\n }\r\n\r\n /**\r\n * Check content of editor before close action.\r\n */\r\n showPopUpMessage() {\r\n if (this.properties.state === \"minimized\") {\r\n this.stack();\r\n }\r\n this.popup.show();\r\n }\r\n\r\n /**\r\n * Sets the title of the modal dialog.\r\n * @param {String} title - Modal dialog title.\r\n */\r\n setTitle(title) {\r\n this.title.innerHTML = title;\r\n }\r\n\r\n /**\r\n * Returns the id of an element, adding the instance number to\r\n * the element class name:\r\n * className --> className[idNumber]\r\n * @param {String} className - The element class name.\r\n * @returns {String} A string appending the instance id to the className.\r\n */\r\n getElementId(className) {\r\n return `${className}[${this.instanceId}]`;\r\n }\r\n}\r\n","/* eslint-disable */\r\nvar polyfills;\r\nexport default polyfills;\r\n\r\n// Polyfills.\r\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\r\nif (!String.prototype.codePointAt) {\r\n (function () {\r\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\r\n var codePointAt = function (position) {\r\n if (this == null) {\r\n throw TypeError();\r\n }\r\n var string = String(this);\r\n var size = string.length;\r\n // `ToInteger`\r\n var index = position ? Number(position) : 0;\r\n if (index != index) {\r\n // better `isNaN`\r\n index = 0;\r\n }\r\n // Account for out-of-bounds indices:\r\n if (index < 0 || index >= size) {\r\n return undefined;\r\n }\r\n // Get the first code unit\r\n var first = string.charCodeAt(index);\r\n var second;\r\n if (\r\n // check if itโ€™s the start of a surrogate pair\r\n first >= 0xd800 &&\r\n first <= 0xdbff && // high surrogate\r\n size > index + 1 // there is a next code unit\r\n ) {\r\n second = string.charCodeAt(index + 1);\r\n if (second >= 0xdc00 && second <= 0xdfff) {\r\n // low surrogate\r\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\r\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\r\n }\r\n }\r\n return first;\r\n };\r\n if (Object.defineProperty) {\r\n Object.defineProperty(String.prototype, \"codePointAt\", {\r\n value: codePointAt,\r\n configurable: true,\r\n writable: true,\r\n });\r\n } else {\r\n String.prototype.codePointAt = codePointAt;\r\n }\r\n })();\r\n}\r\n\r\n// Object.assign polyfill.\r\nif (typeof Object.assign != \"function\") {\r\n // Must be writable: true, enumerable: false, configurable: true\r\n Object.defineProperty(Object, \"assign\", {\r\n value: function assign(target, varArgs) {\r\n // .length of function is 2\r\n \"use strict\";\r\n if (target == null) {\r\n // TypeError if undefined or null\r\n throw new TypeError(\"Cannot convert undefined or null to object\");\r\n }\r\n\r\n var to = Object(target);\r\n\r\n for (var index = 1; index < arguments.length; index++) {\r\n var nextSource = arguments[index];\r\n\r\n if (nextSource != null) {\r\n // Skip over if undefined or null\r\n for (var nextKey in nextSource) {\r\n // Avoid bugs when hasOwnProperty is shadowed\r\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\r\n to[nextKey] = nextSource[nextKey];\r\n }\r\n }\r\n }\r\n }\r\n return to;\r\n },\r\n writable: true,\r\n configurable: true,\r\n });\r\n}\r\n\r\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\r\nif (!Array.prototype.includes) {\r\n Object.defineProperty(Array.prototype, \"includes\", {\r\n value: function (searchElement, fromIndex) {\r\n if (this == null) {\r\n throw new TypeError('\"this\" s null or is not defined');\r\n }\r\n\r\n // 1. Let O be ? ToObject(this value).\r\n var o = Object(this);\r\n\r\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\r\n var len = o.length >>> 0;\r\n\r\n // 3. if len is 0, return false.\r\n if (len === 0) {\r\n return false;\r\n }\r\n\r\n // 4. Let n be ? ToInteger(fromIndex).\r\n // (if fromIndex is undefinedo, this step generates the value 0.)\r\n var n = fromIndex | 0;\r\n\r\n // 5. if n โ‰ฅ 0, then\r\n // a. Let k be n.\r\n // 6. Else n < 0,\r\n // a. Let k be len + n.\r\n // b. if k < 0, let k be 0.\r\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\r\n\r\n function sameValueZero(x, y) {\r\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\r\n }\r\n\r\n // 7. Repeat while k < len\r\n while (k < len) {\r\n // a. let element k be the result of ? Get(O, ! ToString(k)).\r\n // b. if SameValueZero(searchElement, elementK) is true, return true.\r\n if (sameValueZero(o[k], searchElement)) {\r\n return true;\r\n }\r\n // c. Increase k by 1.\r\n k++;\r\n }\r\n\r\n // 8. Return false\r\n return false;\r\n },\r\n });\r\n}\r\n\r\nif (!String.prototype.includes) {\r\n String.prototype.includes = function (search, start) {\r\n \"use strict\";\r\n\r\n if (search instanceof RegExp) {\r\n throw TypeError(\"first argument must not be a RegExp\");\r\n }\r\n if (start === undefined) {\r\n start = 0;\r\n }\r\n return this.indexOf(search, start) !== -1;\r\n };\r\n}\r\n\r\nif (!String.prototype.startsWith) {\r\n Object.defineProperty(String.prototype, \"startsWith\", {\r\n value: function (search, rawPos) {\r\n var pos = rawPos > 0 ? rawPos | 0 : 0;\r\n return this.substring(pos, pos + search.length) === search;\r\n },\r\n });\r\n}\r\n","import Parser from \"./parser\";\r\nimport Util from \"./util\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport CustomEditors from \"./customeditors\";\r\nimport Configuration from \"./configuration\";\r\nimport jsProperties from \"./jsvariables\";\r\nimport Event from \"./event\";\r\nimport Listeners from \"./listeners\";\r\nimport Image from \"./image\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport ModalDialog from \"./modal\";\r\nimport Telemeter from \"./telemeter\";\r\nimport \"./polyfills\";\r\nimport \"../styles/styles.css\";\r\n\r\n/**\r\n * @typedef {Object} CoreProperties\r\n * @property {ServiceProviderProperties} serviceProviderProperties\r\n * - The ServiceProvider class properties. *\r\n */\r\nexport default class Core {\r\n /**\r\n * @classdesc\r\n * This class represents MathType integration Core, managing the following:\r\n * - Integration initialization.\r\n * - Event managing.\r\n * - Insertion of formulas into the edit area.\r\n * ```js\r\n * let core = new Core();\r\n * core.addListener(listener);\r\n * core.language = 'en';\r\n *\r\n * // Initializing Core class.\r\n * core.init(configurationService);\r\n * ```\r\n * @constructs\r\n * Core constructor.\r\n * @param {CoreProperties}\r\n */\r\n constructor(coreProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. 'en' by default.\r\n * @type {String}\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Edit mode, 'images' by default. Admits the following values:\r\n * - images\r\n * - latex\r\n * @type {String}\r\n */\r\n this.editMode = \"images\";\r\n\r\n /**\r\n * Modal dialog instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialog = null;\r\n\r\n /**\r\n * The instance of {@link CustomEditors}. By default\r\n * the only custom editor is the Chemistry editor.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = new CustomEditors();\r\n\r\n /**\r\n * Chemistry editor.\r\n * @type {CustomEditor}\r\n */\r\n const chemEditorParams = {\r\n name: \"Chemistry\",\r\n toolbar: \"chemistry\",\r\n icon: \"chem.png\",\r\n confVariable: \"chemEnabled\",\r\n title: \"ChemType\",\r\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\r\n };\r\n\r\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @typedef IntegrationEnvironment\r\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\r\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\r\n * @property {String} IntegrationEnvironment.version - Integration version.\r\n *\r\n */\r\n\r\n /**\r\n * The environment properties object.\r\n * @type {IntegrationEnvironment}\r\n */\r\n this.environment = {};\r\n\r\n /**\r\n * @typedef EditionProperties\r\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\r\n * False otherwise.\r\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\r\n * Null if the formula is new.\r\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\r\n * @property {Range} editionProperties.range - The range that contains the image element.\r\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\r\n */\r\n\r\n /**\r\n * The properties of the current edition process.\r\n * @type {EditionProperties}\r\n */\r\n this.editionProperties = {};\r\n\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.temporalImage = null;\r\n this.editionProperties.latexRange = null;\r\n this.editionProperties.range = null;\r\n this.editionProperties.editionStartTime = null;\r\n\r\n /**\r\n * The {@link IntegrationModel} instance.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n\r\n /**\r\n * The {@link ContentManager} instance.\r\n * @type {ContentManager}\r\n */\r\n this.contentManager = null;\r\n\r\n /**\r\n * The current browser.\r\n * @type {String}\r\n */\r\n this.browser = (() => {\r\n const ua = navigator.userAgent;\r\n let browser = \"none\";\r\n if (ua.search(\"Edge/\") >= 0) {\r\n browser = \"EDGE\";\r\n } else if (ua.search(\"Chrome/\") >= 0) {\r\n browser = \"CHROME\";\r\n } else if (ua.search(\"Trident/\") >= 0) {\r\n browser = \"IE\";\r\n } else if (ua.search(\"Firefox/\") >= 0) {\r\n browser = \"FIREFOX\";\r\n } else if (ua.search(\"Safari/\") >= 0) {\r\n browser = \"SAFARI\";\r\n }\r\n return browser;\r\n })();\r\n\r\n /**\r\n * Plugin listeners.\r\n * @type {Array.}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * Service provider properties.\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = {};\r\n if (\"serviceProviderProperties\" in coreProperties) {\r\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\r\n } else {\r\n throw new Error(\"serviceProviderProperties property missing.\");\r\n }\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Core listeners.\r\n * @private\r\n * @type {Listeners}\r\n */\r\n static get globalListeners() {\r\n return Core._globalListeners;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set core listeners.\r\n * @param {Listeners} value - The property value.\r\n * @ignore\r\n */\r\n static set globalListeners(value) {\r\n Core._globalListeners = value;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * True when Core.init was called. Otherwise, false.\r\n * @private\r\n * @type {Boolean}\r\n */\r\n static get initialized() {\r\n return Core._initialized;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\r\n * @ignore\r\n */\r\n static set initialized(value) {\r\n Core._initialized = value;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.integrationModel} property.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.environment} property.\r\n * @param {IntegrationEnvironment} integrationEnvironment -\r\n * The {@link IntegrationEnvironment} object.\r\n */\r\n setEnvironment(integrationEnvironment) {\r\n if (\"editor\" in integrationEnvironment) {\r\n this.environment.editor = integrationEnvironment.editor;\r\n }\r\n if (\"mode\" in integrationEnvironment) {\r\n this.environment.mode = integrationEnvironment.mode;\r\n }\r\n if (\"version\" in integrationEnvironment) {\r\n this.environment.version = integrationEnvironment.version;\r\n }\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setHeaders(headers) {\r\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\r\n Configuration.set(\"customHeaders\", headerObject);\r\n }\r\n\r\n /**\r\n * Returns the current {@link ModalDialog} instance.\r\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\r\n */\r\n getModalDialog() {\r\n return this.modalDialog;\r\n }\r\n\r\n /**\r\n * Inits the {@link Core} class, doing the following:\r\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\r\n * - Updates {@link Configuration} class with the previous configuration properties.\r\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\r\n * - Loads language strings.\r\n * - Fires onLoad event.\r\n * @param {Object} serviceParameters - Service parameters.\r\n */\r\n init() {\r\n if (!Core.initialized) {\r\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\r\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\r\n const jsonConfiguration = JSON.parse(jsConfiguration);\r\n Configuration.addConfiguration(jsonConfiguration);\r\n // Adding JavaScript (not backend) configuration variables.\r\n Configuration.addConfiguration(jsProperties);\r\n // Fire 'onLoad' event:\r\n // All integration must listen this event in order to know if the plugin\r\n // has been properly loaded.\r\n StringManager.language = this.language;\r\n this.listeners.fire(\"onLoad\", {});\r\n });\r\n\r\n ServiceProvider.addListener(serviceProviderListener);\r\n ServiceProvider.init(this.serviceProviderProperties);\r\n\r\n Core.initialized = true;\r\n } else {\r\n // Case when there are more than two editor instances.\r\n // After the first editor all the other editors don't need to load any file or service.\r\n this.listeners.fire(\"onLoad\", {});\r\n }\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\r\n * @param {Listener} listener - The listener object.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Adds the global {@link Listener} instance to {@link Core} class.\r\n * @param {Listener} listener - The event listener to be added.\r\n * @static\r\n */\r\n static addGlobalListener(listener) {\r\n Core.globalListeners.add(listener);\r\n }\r\n\r\n beforeUpdateFormula(mathml, wirisProperties) {\r\n /**\r\n * This event is fired before updating the formula.\r\n * @type {Object}\r\n * @property {String} mathml - MathML to be transformed.\r\n * @property {String} editMode - Edit mode.\r\n * @property {Object} wirisProperties - Extra attributes for the formula.\r\n * @property {String} language - Formula language.\r\n */\r\n const beforeUpdateEvent = new Event();\r\n\r\n beforeUpdateEvent.mathml = mathml;\r\n\r\n // Cloning wirisProperties object\r\n // We don't want wirisProperties object modified.\r\n beforeUpdateEvent.wirisProperties = {};\r\n\r\n if (wirisProperties != null) {\r\n Object.keys(wirisProperties).forEach((attr) => {\r\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\r\n });\r\n }\r\n\r\n // Read only.\r\n beforeUpdateEvent.language = this.language;\r\n beforeUpdateEvent.editMode = this.editMode;\r\n\r\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {\r\n mathml: beforeUpdateEvent.mathml,\r\n wirisProperties: beforeUpdateEvent.wirisProperties,\r\n };\r\n }\r\n\r\n /**\r\n * Converts a MathML into it's correspondent image and inserts the image is\r\n * inserted in a HTMLElement target by creating\r\n * a new image or updating an existing one.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\r\n * @param {Window} windowTarget - The window element where the editable content is.\r\n * @param {String} mathml - The MathML.\r\n * @param {Array.} wirisProperties - The extra attributes for the formula.\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n /**\r\n * It is the object with the information of the node or latex to insert.\r\n * @typedef ReturnObject\r\n * @property {Node} [node] - The DOM node to insert.\r\n * @property {String} [latex] - The latex to insert.\r\n */\r\n const returnObject = {};\r\n\r\n if (!mathml) {\r\n this.insertElementOnSelection(null, focusElement, windowTarget);\r\n } else if (this.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n // this.integrationModel.getNonLatexNode is an integration wrapper\r\n // to have special behaviours for nonLatex.\r\n // Not all the integrations have special behaviours for nonLatex.\r\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.latex = returnObject.latex;\r\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\r\n } else {\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n }\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n } else {\r\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\r\n\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n }\r\n\r\n return returnObject;\r\n }\r\n\r\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\r\n /**\r\n * This event is fired after update the formula.\r\n * @type {Event}\r\n * @param {String} editMode - edit mode.\r\n * @param {Object} windowTarget - target window.\r\n * @param {Object} focusElement - target element to be focused after update.\r\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\r\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\r\n */\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.node = node;\r\n afterUpdateEvent.latex = latex;\r\n\r\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {};\r\n }\r\n\r\n /**\r\n * Sets the caret after a given Node and set the focus to the owner document.\r\n * @param {Node} node - The Node element.\r\n */\r\n placeCaretAfterNode(node) {\r\n if (node === null) return;\r\n\r\n this.integrationModel.getSelection();\r\n const nodeDocument = node.ownerDocument;\r\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\r\n const range = nodeDocument.createRange();\r\n range.setStartAfter(node);\r\n range.collapse(true);\r\n const selection = nodeDocument.getSelection();\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n nodeDocument.body.focus();\r\n }\r\n }\r\n\r\n /**\r\n * Replaces a Selection object with an HTMLElement.\r\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\r\n * @param {Window} windowTarget - The window target.\r\n */\r\n insertElementOnSelection(element, focusElement, windowTarget) {\r\n let mathmlOrigin = null;\r\n if (this.editionProperties.isNewElement) {\r\n if (element) {\r\n if (focusElement.type === \"textarea\") {\r\n Util.updateTextArea(focusElement, element.textContent);\r\n } else if (document.selection && document.getSelection === 0) {\r\n let range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n\r\n if (!(\"parentElement\" in range)) {\r\n windowTarget.document.execCommand(\"delete\", false);\r\n range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n }\r\n\r\n if (\"parentElement\" in range) {\r\n const temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\r\n temporalObject.parentNode.replaceChild(element, temporalObject);\r\n } else {\r\n // IE9 fix: parentNode() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML(Util.createObjectCode(element));\r\n }\r\n }\r\n } else {\r\n let range = null;\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n if (this.editionProperties.range) {\r\n ({ range } = this.editionProperties);\r\n this.editionProperties.range = null;\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n range = editorSelection.getRangeAt(0);\r\n }\r\n\r\n // Delete if something was surrounded.\r\n range.deleteContents();\r\n\r\n let node = range.startContainer;\r\n const position = range.startOffset;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = node.splitText(position);\r\n node.parentNode.insertBefore(element, node);\r\n } else if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n node.insertBefore(element, node.childNodes[position]);\r\n }\r\n\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n focusElement.focus();\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (this.editionProperties.range) {\r\n const { range } = this.editionProperties;\r\n this.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n }\r\n }\r\n } else if (this.editionProperties.latexRange) {\r\n if (document.selection && document.getSelection === 0) {\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.latexRange.select();\r\n this.insertElementOnSelection(element, focusElement, windowTarget);\r\n } else {\r\n this.editionProperties.latexRange.deleteContents();\r\n this.editionProperties.latexRange.insertNode(element);\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n let item;\r\n // Wrapper for some integrations that can have special behaviours to show latex.\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n item = this.integrationModel.getSelectedItem(focusElement, false);\r\n } else {\r\n item = Util.getSelectedItemOnTextarea(focusElement);\r\n }\r\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\r\n } else {\r\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\r\n if (element && element.nodeName.toLowerCase() === \"img\") {\r\n // Editor empty, formula has been erased on edit.\r\n // There are editors (e.g: CKEditor) that put attributes in images.\r\n // We don't allow that behaviour in our images.\r\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\r\n // Clone is needed to maintain event references to temporalImage.\r\n Image.clone(element, this.editionProperties.temporalImage);\r\n } else {\r\n this.editionProperties.temporalImage.remove();\r\n }\r\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const mathml = element?.dataset?.mathml;\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove the desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n }\r\n\r\n /**\r\n * Opens a modal dialog containing MathType editor..\r\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\r\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\r\n */\r\n openModalDialog(target, isIframe) {\r\n // Count the time since the editor is open\r\n this.editionProperties.editionStartTime = Date.now();\r\n\r\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\r\n this.editMode = \"images\";\r\n\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n try {\r\n if (isIframe) {\r\n // Is needed focus the target first.\r\n target.contentWindow.focus();\r\n const selection = target.contentWindow.getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n } else {\r\n // Is needed focus the target first.\r\n target.focus();\r\n const selection = getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n }\r\n } catch (e) {\r\n this.editionProperties.range = null;\r\n }\r\n\r\n if (isIframe === undefined) {\r\n isIframe = true;\r\n }\r\n\r\n this.editionProperties.latexRange = null;\r\n\r\n if (target) {\r\n let selectedItem;\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\r\n } else {\r\n selectedItem = Util.getSelectedItem(target, isIframe);\r\n }\r\n\r\n // Check LaTeX if and only if the node is a text node (nodeType==3).\r\n if (selectedItem) {\r\n // Case when image was selected and button pressed.\r\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\r\n this.editionProperties.temporalImage = selectedItem.node;\r\n this.editionProperties.isNewElement = false;\r\n } else if (selectedItem.node.nodeType === 3) {\r\n // If it's a text node means that editor is working with LaTeX.\r\n if (this.integrationModel.getMathmlFromTextNode) {\r\n // If integration has this function it isn't set range due to we don't\r\n // know if it will be put into a textarea as a text or image.\r\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (mathml) {\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n }\r\n } else {\r\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (latexResult) {\r\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n const windowTarget = isIframe ? target.contentWindow : window;\r\n\r\n if (target.tagName.toLowerCase() !== \"textarea\") {\r\n if (document.selection) {\r\n let leftOffset = 0;\r\n let previousNode = latexResult.startNode.previousSibling;\r\n\r\n while (previousNode) {\r\n leftOffset += Util.getNodeLength(previousNode);\r\n previousNode = previousNode.previousSibling;\r\n }\r\n\r\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\r\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\r\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\r\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\r\n } else {\r\n this.editionProperties.latexRange = windowTarget.document.createRange();\r\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\r\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n } else if (target.tagName.toLowerCase() === \"textarea\") {\r\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\r\n this.editMode = \"latex\";\r\n }\r\n }\r\n\r\n // Setting an object with the editor parameters.\r\n // Editor parameters can be customized in several ways:\r\n // 1 - editorAttributes: Contains the default editor attributes,\r\n // usually the metrics in a comma separated string. Always exists.\r\n // 2 - editorParameters: Object containing custom editor parameters.\r\n // These parameters are defined in the backend. So they affects all integration instances.\r\n\r\n // The backend send the default editor attributes in a coma separated\r\n // with the following structure: key1=value1,key2=value2...\r\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\r\n const defaultEditorAttributes = {};\r\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\r\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\r\n const key = tempAttribute[0];\r\n const value = tempAttribute[1];\r\n defaultEditorAttributes[key] = value;\r\n }\r\n // Custom editor parameters.\r\n const editorAttributes = {\r\n language: this.language, // Default language value\r\n };\r\n // Editor parameters in backend, usually configuration.ini.\r\n const serverEditorParameters = Configuration.get(\"editorParameters\");\r\n // Editor parameters through JavaScript configuration.\r\n const clientEditorParameters = this.integrationModel.editorParameters;\r\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\r\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\r\n\r\n // Now, update backwards: if user has set a custom language, pass that back to core properties\r\n this.language = editorAttributes.language;\r\n StringManager.language = this.language;\r\n\r\n editorAttributes.rtl = this.integrationModel.rtl;\r\n\r\n const customHeaders = Configuration.get(\"customHeaders\");\r\n // This is not being used in the code, we are keeping it just in case it's needed.\r\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\r\n editorAttributes.customHeaders =\r\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\r\n\r\n const contentManagerAttributes = {};\r\n contentManagerAttributes.editorAttributes = editorAttributes;\r\n contentManagerAttributes.language = this.language;\r\n contentManagerAttributes.customEditors = this.customEditors;\r\n contentManagerAttributes.environment = this.environment;\r\n\r\n if (this.modalDialog == null) {\r\n this.modalDialog = new ModalDialog(editorAttributes);\r\n this.contentManager = new ContentManager(contentManagerAttributes);\r\n // When an instance of ContentManager is created we need to wait until\r\n // the ContentManager is ready by listening 'onLoad' event.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n });\r\n this.contentManager.addListener(listener);\r\n this.contentManager.init();\r\n this.modalDialog.setContentManager(this.contentManager);\r\n this.contentManager.setModalDialogInstance(this.modalDialog);\r\n } else {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n }\r\n this.contentManager.setIntegrationModel(this.integrationModel);\r\n this.modalDialog.open();\r\n }\r\n\r\n /**\r\n * Returns the {@link CustomEditors} instance.\r\n * @return {CustomEditors} The current {@link CustomEditors} instance.\r\n */\r\n getCustomEditors() {\r\n return this.customEditors;\r\n }\r\n}\r\n\r\n/**\r\n * Core static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nCore._globalListeners = new Listeners();\r\n\r\n/**\r\n * Resources state. Says if they were loaded or not.\r\n * @type {Boolean}\r\n * @private\r\n */\r\nCore._initialized = false;\r\n","// eslint-disable-next-line no-unused-vars, import/named\r\nimport Core from \"./core.src\";\r\nimport Image from \"./image\";\r\nimport Listeners from \"./listeners\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Telemeter from \"./telemeter\";\r\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\r\n\r\n/**\r\n * @typedef {Object} IntegrationModelProperties\r\n * @property {string} configurationService - Configuration service path.\r\n * This parameter is needed to determine all services paths.\r\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\r\n * @property {string} integrationModelProperties.scriptName - Integration script name.\r\n * Usually the name of the integration script.\r\n * @property {Object} integrationModelProperties.environment - integration environment properties.\r\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\r\n * callback method arguments.\r\n * @property {string} [integrationModelProperties.version] - integration version number.\r\n * @property {Object} [integrationModelProperties.editorObject] - object containing\r\n * the integration editor instance.\r\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\r\n * false otherwise.\r\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\r\n * - The service parameters.\r\n * @property {Object} [integrationModelProperties.integrationParameters]\r\n * - Overwritten integration parameters.\r\n */\r\n\r\nexport default class IntegrationModel {\r\n /**\r\n * @classdesc\r\n * This class represents an integration model, allowing the integration script to\r\n * communicate with Core class. Each integration must extend this class.\r\n * @constructs\r\n * @param {IntegrationModelProperties} integrationModelProperties\r\n */\r\n constructor(integrationModelProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. English by default.\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Service parameters\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\r\n\r\n /**\r\n * Configuration service path. The integration service is needed by Core class to\r\n * load all the backend configuration into the frontend and also to create the paths\r\n * of all services (all services lives in the same route). Mandatory property.\r\n */\r\n this.configurationService = \"\";\r\n if (\"configurationService\" in integrationModelProperties) {\r\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\r\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\r\n integrationModelProperties.configurationService,\r\n ]);\r\n }\r\n\r\n /**\r\n * Plugin version. Needed to stats and caching.\r\n * @type {string}\r\n */\r\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\r\n\r\n /**\r\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\r\n * Mandatory property.\r\n */\r\n this.target = null;\r\n if (\"target\" in integrationModelProperties) {\r\n this.target = integrationModelProperties.target;\r\n } else {\r\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\r\n }\r\n\r\n /**\r\n * Integration script name. Needed to know the plugin path.\r\n */\r\n if (\"scriptName\" in integrationModelProperties) {\r\n this.scriptName = integrationModelProperties.scriptName;\r\n }\r\n\r\n /**\r\n * Object containing the arguments needed by the callback function.\r\n */\r\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\r\n\r\n /**\r\n * Contains information about the integration environment:\r\n * like the name of the editor, the version, etc.\r\n */\r\n this.environment = integrationModelProperties.environment ?? {};\r\n\r\n /**\r\n * Indicates if the DOM target is - or not - and iframe.\r\n */\r\n this.isIframe = false;\r\n if (this.target != null) {\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Instance of the integration editor object. Usually the entry point to access the API\r\n * of a HTML editor.\r\n */\r\n this.editorObject = integrationModelProperties.editorObject ?? null;\r\n\r\n /**\r\n * Specifies if the direction of the text is RTL. false by default.\r\n */\r\n this.rtl = integrationModelProperties.rtl ?? false;\r\n\r\n /**\r\n * Specifies if the integration model exposes the locale strings. false by default.\r\n */\r\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\r\n\r\n /**\r\n * Specify if editor will open in hand mode only\r\n */\r\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\r\n\r\n /**\r\n * Indicates if an image is selected. Needed to resize the image to the original size in case\r\n * the image is resized.\r\n * @type {boolean}\r\n */\r\n this.temporalImageResizing = false;\r\n\r\n /**\r\n * The Core class instance associated to the integration model.\r\n * @type {Core}\r\n */\r\n this.core = null;\r\n\r\n /**\r\n * Integration model listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n // Parameters overwrite.\r\n if (\"integrationParameters\" in integrationModelProperties) {\r\n IntegrationModel.integrationParameters.forEach((parameter) => {\r\n if (parameter in integrationModelProperties.integrationParameters) {\r\n // Don't add empty parameters.\r\n const value = integrationModelProperties.integrationParameters[parameter];\r\n if (Object.keys(value).length !== 0) {\r\n this[parameter] = value;\r\n }\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Init function. Usually called from the integration side once the core.js file is loaded.\r\n */\r\n init() {\r\n // Check if language is an object and select the property necessary\r\n this.language = this.getLanguage();\r\n\r\n // We need to wait until Core class is loaded ('onLoad' event) before\r\n // call the callback method.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.callbackFunction(this.callbackMethodArguments);\r\n });\r\n\r\n // Backwards compatibility.\r\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\r\n const uri = this.serviceProviderProperties.URI;\r\n const server = ServiceProvider.getServerLanguageFromService(uri);\r\n this.serviceProviderProperties.server = server;\r\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\r\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\r\n this.serviceProviderProperties.URI = subsTring;\r\n }\r\n\r\n let serviceParametersURI = this.serviceProviderProperties.URI;\r\n serviceParametersURI =\r\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\r\n ? serviceParametersURI\r\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\r\n\r\n this.serviceProviderProperties.URI = serviceParametersURI;\r\n\r\n const coreProperties = {};\r\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\r\n\r\n this.setCore(new Core(coreProperties));\r\n this.core.addListener(listener);\r\n this.core.language = this.language;\r\n\r\n // Initializing Core class.\r\n this.core.init();\r\n // TODO: Move to Core constructor.\r\n this.core.setEnvironment(this.environment);\r\n\r\n // No internet connection modal.\r\n let attributes = {};\r\n attributes.class = attributes.id = \"wrs_modal_offline\";\r\n this.offlineModal = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_content_offline\";\r\n this.offlineModalContent = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_close\";\r\n this.offlineModalClose = Util.createElement(\"span\", attributes);\r\n this.offlineModalClose.innerHTML = \"×\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_warn\";\r\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\r\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\r\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_container\";\r\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_warn\";\r\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text\";\r\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\r\n\r\n // Append offline modal elements\r\n this.offlineModalContent.appendChild(this.offlineModalClose);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\r\n this.offlineModalContent.appendChild(this.offlineModalMessage);\r\n this.offlineModalContent.appendChild(this.offlineModalWarn);\r\n this.offlineModal.appendChild(this.offlineModalContent);\r\n document.body.appendChild(this.offlineModal);\r\n\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n this.offlineModalClose.addEventListener(\"click\", () => {\r\n modal.style.display = \"none\";\r\n });\r\n\r\n // Store editor name for telemetry purposes.\r\n let editorName = this.environment.editor;\r\n editorName = editorName.slice(0, -1); // Remove version number from editors\r\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\r\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\r\n let solutionTelemeter = editorName;\r\n\r\n // If moodle, add information to hosts and solution.\r\n const isMoodle = !!(typeof M === \"object\" && M !== null);\r\n let lms;\r\n\r\n if (isMoodle) {\r\n solutionTelemeter = \"Moodle\";\r\n lms = {\r\n nam: \"moodle\",\r\n fam: \"lms\",\r\n ver: this.environment.moodleVersion,\r\n category: this.environment.moodleCourseCategory,\r\n course: this.environment.moodleCourseName,\r\n };\r\n if (!editorName.includes(\"TinyMCE\")) {\r\n editorName = \"Atto\";\r\n }\r\n }\r\n\r\n // Get the OS and its version.\r\n const OSData = this.getOS();\r\n\r\n // Get the broswer and its version.\r\n const broswerData = this.getBrowser();\r\n\r\n // Create list of hosts to send to telemetry.\r\n let hosts = [\r\n {\r\n nam: broswerData.detectedBrowser,\r\n fam: \"browser\",\r\n ver: broswerData.versionBrowser,\r\n },\r\n {\r\n nam: editorName.toLowerCase(),\r\n fam: \"html-editor\",\r\n ver: this.environment.editorVersion,\r\n },\r\n {\r\n nam: OSData.detectedOS,\r\n fam: \"os\",\r\n ver: OSData.versionOS,\r\n },\r\n {\r\n nam: window.location.hostname,\r\n fam: \"domain\",\r\n },\r\n lms,\r\n ];\r\n\r\n // Filter hosts to remove empty objects and empty keys.\r\n hosts = hosts.filter((element) => {\r\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\r\n return element !== undefined;\r\n });\r\n\r\n // Initialize telemeter\r\n Telemeter.init({\r\n solution: {\r\n name: `MathType for ${solutionTelemeter}`,\r\n version: this.version,\r\n },\r\n hosts,\r\n config: {\r\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\r\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\r\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\r\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\r\n },\r\n url: undefined,\r\n });\r\n }\r\n\r\n /**\r\n * Returns the Browser name and its version.\r\n * @return {array} - detectedBrowser = Operating System name.\r\n * versionBrowser = Operating System version.\r\n */\r\n getBrowser() {\r\n // default value for OS just in case nothing is detected\r\n let detectedBrowser = \"unknown\";\r\n let versionBrowser = \"unknown\";\r\n\r\n const userAgent = window.navigator.userAgent;\r\n\r\n if (/Brave/.test(userAgent)) {\r\n detectedBrowser = \"brave\";\r\n if (userAgent.indexOf(\"Brave/\")) {\r\n const start = userAgent.indexOf(\"Brave\") + 6;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\r\n detectedBrowser = \"edge_chromium\";\r\n const start = userAgent.indexOf(\"Edg/\") + 4;\r\n versionBrowser = userAgent\r\n .substring(start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Edg/.test(userAgent)) {\r\n detectedBrowser = \"edge\";\r\n let start = userAgent.indexOf(\"Edg\") + 3;\r\n start += userAgent.substring(start).indexOf(\"/\");\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\r\n detectedBrowser = \"firefox\";\r\n let start = userAgent.indexOf(\"Firefox\");\r\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/OPR/.test(userAgent)) {\r\n detectedBrowser = \"opera\";\r\n const start = userAgent.indexOf(\"OPR/\") + 4;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\r\n detectedBrowser = \"chrome\";\r\n let start = userAgent.indexOf(\"Chrome\");\r\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/Safari/.test(userAgent)) {\r\n detectedBrowser = \"safari\";\r\n let start = userAgent.indexOf(\"Version/\");\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n }\r\n\r\n return { detectedBrowser, versionBrowser };\r\n }\r\n\r\n /**\r\n * Returns the operating system and its version.\r\n * @return {array} - detectedOS = Operating System name.\r\n * versionOS = Operating System version.\r\n */\r\n getOS() {\r\n // default value for OS just in case nothing is detected\r\n let detectedOS = \"unknown\";\r\n let versionOS = \"unknown\";\r\n\r\n // Retrieve properties to easily detect the OS\r\n const userAgent = window.navigator.userAgent;\r\n const platform = window.navigator.platform;\r\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\r\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\r\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\r\n\r\n // Find OS and their respective versions\r\n if (macosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"macos\";\r\n if (userAgent.indexOf(\"OS X\") !== -1) {\r\n const start = userAgent.indexOf(\"OS X\") + 5;\r\n const end = userAgent.substring(start).indexOf(\" \");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (iosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"ios\";\r\n if (userAgent.indexOf(\"OS \") !== -1) {\r\n const start = userAgent.indexOf(\"OS \") + 3;\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"windows\";\r\n const start = userAgent.indexOf(\"Windows\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Android/.test(userAgent)) {\r\n detectedOS = \"android\";\r\n const start = userAgent.indexOf(\"Android\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/CrOS/.test(userAgent)) {\r\n detectedOS = \"chromeos\";\r\n let start = userAgent.indexOf(\"CrOS \") + 5;\r\n start += userAgent.substring(start).indexOf(\" \");\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\r\n detectedOS = \"linux\";\r\n }\r\n\r\n return { detectedOS, versionOS };\r\n }\r\n\r\n /**\r\n * Returns the absolute path of the integration script.\r\n * @return {string} - Absolute path for the integration script.\r\n */\r\n getPath() {\r\n if (typeof this.scriptName === \"undefined\") {\r\n throw new Error(\"scriptName property needed for getPath.\");\r\n }\r\n const col = document.getElementsByTagName(\"script\");\r\n let path = \"\";\r\n for (let i = 0; i < col.length; i += 1) {\r\n const j = col[i].src.lastIndexOf(this.scriptName);\r\n if (j >= 0) {\r\n path = col[i].src.substr(0, j - 1);\r\n }\r\n }\r\n return path;\r\n }\r\n\r\n /**\r\n * Returns integration model plugin version\r\n * @param {string} - Plugin version\r\n */\r\n getVersion() {\r\n return this.version;\r\n }\r\n\r\n /**\r\n * Sets the language property.\r\n * @param {string} language - language code.\r\n */\r\n setLanguage(language) {\r\n this.language = language;\r\n }\r\n\r\n /**\r\n * Sets a Core instance.\r\n * @param {Core} core - instance of Core class.\r\n */\r\n setCore(core) {\r\n this.core = core;\r\n core.setIntegrationModel(this);\r\n }\r\n\r\n /**\r\n * Returns the Core instance.\r\n * @returns {Core} instance of Core class.\r\n */\r\n getCore() {\r\n return this.core;\r\n }\r\n\r\n /**\r\n * Sets the object target and updates the iframe property.\r\n * @param {HTMLElement} target - target object.\r\n */\r\n setTarget(target) {\r\n this.target = target;\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Sets the editor object.\r\n * @param {Object} editorObject - The editor object.\r\n */\r\n setEditorObject(editorObject) {\r\n this.editorObject = editorObject;\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openNewFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.dbclick = false;\r\n this.core.editionProperties.isNewElement = true;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openExistingFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.isNewElement = false;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Wrapper to Core.updateFormula method.\r\n * Transform a MathML into a image formula.\r\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\r\n * or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n */\r\n updateFormula(mathml) {\r\n if (this.editorParameters) {\r\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\r\n mathml,\r\n \"application/vnd.wiris.mtweb-params+json\",\r\n JSON.stringify(this.editorParameters),\r\n );\r\n }\r\n let focusElement;\r\n let windowTarget;\r\n const wirisProperties = null;\r\n\r\n if (this.isIframe) {\r\n focusElement = this.target.contentWindow;\r\n windowTarget = this.target.contentWindow;\r\n } else {\r\n focusElement = this.target;\r\n windowTarget = window;\r\n }\r\n\r\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\r\n }\r\n\r\n /**\r\n * Wrapper to Core.insertFormula method.\r\n * Inserts the formula in the specified target, creating\r\n * a new image (new formula) or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\r\n\r\n // Delete temporal image when inserted\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return obj;\r\n }\r\n\r\n /**\r\n * Returns the target selection.\r\n * @returns {Selection} target selection.\r\n */\r\n getSelection() {\r\n if (this.isIframe) {\r\n this.target.contentWindow.focus();\r\n return this.target.contentWindow.getSelection();\r\n }\r\n this.target.focus();\r\n return window.getSelection();\r\n }\r\n\r\n /**\r\n * Add events to formulas in the DOM target. The events added are the following:\r\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\r\n * to edit them.\r\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\r\n * in case the the formula is resized.\r\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\r\n * in case the formula is resized.\r\n */\r\n addEvents() {\r\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\r\n Util.addElementEvents(\r\n eventTarget,\r\n (element, event) => {\r\n this.doubleClickHandler(element, event);\r\n // Avoid creating the double click listener more than once for each element.\r\n // This also allows CKEditor4 to add their own double click listener.\r\n event.preventDefault();\r\n },\r\n (element, event) => {\r\n this.mousedownHandler(element, event);\r\n },\r\n (element, event) => {\r\n this.mouseupHandler(element, event);\r\n },\r\n );\r\n }\r\n\r\n /**\r\n * Remove events to formulas in the DOM target.\r\n */\r\n removeEvents() {\r\n const eventTarget =\r\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\r\n\r\n if (!eventTarget) {\r\n return;\r\n }\r\n\r\n Util.removeElementEvents(eventTarget);\r\n }\r\n\r\n /**\r\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\r\n */\r\n destroy() {\r\n this.removeEvents();\r\n // Destroy modal dialog if exists.\r\n if (this.core.modalDialog) {\r\n this.core.modalDialog.destroy();\r\n }\r\n\r\n // Remove offline modal dialog if exists.\r\n if (this.offlineModal) {\r\n this.offlineModal.remove();\r\n }\r\n\r\n this.editorObject = null;\r\n }\r\n\r\n /**\r\n * Handles a Double-click on the target element. Opens an editor\r\n * to re-edit the double-clicked formula.\r\n * @param {HTMLElement} element - DOM object target.\r\n */\r\n doubleClickHandler(element) {\r\n this.core.editionProperties.dbclick = true;\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (element.hasAttribute(customEditorAttributeName)) {\r\n const customEditor = element.getAttribute(customEditorAttributeName);\r\n this.core.getCustomEditors().enable(customEditor);\r\n }\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.core.editionProperties.temporalImage = element;\r\n this.core.editionProperties.isNewElement = true;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse up event on the target element. Restores the image size to avoid\r\n * resizing formulas.\r\n */\r\n mouseupHandler() {\r\n if (this.temporalImageResizing) {\r\n setTimeout(() => {\r\n Image.fixAfterResize(this.temporalImageResizing);\r\n }, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse down event on the target element. Saves the formula size to avoid\r\n * resizing formulas.\r\n * @param {HTMLElement} element - target element.\r\n */\r\n mousedownHandler(element) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.temporalImageResizing = element;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the integration language. By default the browser agent. This method\r\n * should be overwritten to obtain the integration language, for example using the\r\n * plugin API of an HTML editor.\r\n * @returns {string} integration language.\r\n */\r\n getLanguage() {\r\n return this.getBrowserLanguage();\r\n }\r\n\r\n /**\r\n * Returns the browser language.\r\n * @returns {string} the browser language.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getBrowserLanguage() {\r\n let language = \"en\";\r\n if (navigator.userLanguage) {\r\n language = navigator.userLanguage.substring(0, 2);\r\n } else if (navigator.language) {\r\n language = navigator.language.substring(0, 2);\r\n } else {\r\n language = \"en\";\r\n }\r\n return language;\r\n }\r\n\r\n /**\r\n * This function is called once the {@link Core} is loaded. IntegrationModel class\r\n * will fire this method when {@link Core} 'onLoad' event is fired.\r\n * This method should content all the logic to init\r\n * the integration.\r\n */\r\n callbackFunction() {\r\n // It's needed to wait until the integration target is ready. The event is fired\r\n // from the integration side.\r\n const listener = Listeners.newListener(\"onTargetReady\", () => {\r\n this.addEvents(this.target);\r\n });\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n notifyWindowClosed() {\r\n // Nothing.\r\n }\r\n\r\n /**\r\n * Wrapper.\r\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\r\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\r\n * @param {string} textNode - text node to extract the MathML.\r\n * @param {int} caretPosition - caret position inside the text node.\r\n * @returns {string} MathML inside the text node.\r\n */\r\n\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getMathmlFromTextNode(textNode, caretPosition) {}\r\n\r\n /**\r\n * Wrapper\r\n * It fills wrs event object of nonLatex with the desired data.\r\n * @param {Object} event - event object.\r\n * @param {Object} window dom window object.\r\n * @param {string} mathml valid mathml.\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n fillNonLatexNode(event, window, mathml) {}\r\n\r\n /**\r\n Wrapper.\r\n * Returns selected item from the target.\r\n * @param {HTMLElement} target - target element\r\n * @param {boolean} iframe\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getSelectedItem(target, isIframe) {}\r\n\r\n // Set temporal image to null and make focus come back.\r\n static setActionsOnCancelButtons() {\r\n // Make focus come back on the previous place it was when click cancel button\r\n const currentInstance = WirisPlugin.currentInstance;\r\n const editorSelection = currentInstance.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (currentInstance.core.editionProperties.range) {\r\n const { range } = currentInstance.core.editionProperties;\r\n currentInstance.core.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n if (range.startOffset !== range.endOffset) {\r\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\r\n }\r\n }\r\n\r\n // eslint-disable-next-line no-undef\r\n if (WirisPlugin.currentInstance) {\r\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\r\n }\r\n }\r\n}\r\n\r\n// To know if the integration that extends this class implements\r\n// wrapper methods, they are set as undefined.\r\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\r\nIntegrationModel.prototype.fillNonLatexNode = undefined;\r\nIntegrationModel.prototype.getSelectedItem = undefined;\r\n\r\n/**\r\n * An object containing a list with the overwritable class constructor properties.\r\n * @type {Object}\r\n */\r\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\r\n","/* eslint-disable */\r\nvar md5;\r\nexport default md5;\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n delete Array.prototype.__class__;\r\n})();\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n})();\r\ndelete Array.prototype.__class__;\r\n// @codingStandardsIgnoreEnd\r\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\r\n\r\n/**\r\n * This class represents the MathType integration for CKEditor5.\r\n * @extends {IntegrationModel}\r\n */\r\nexport default class CKEditor5Integration extends IntegrationModel {\r\n constructor(ckeditorIntegrationModelProperties) {\r\n const editor = ckeditorIntegrationModelProperties.editorObject;\r\n\r\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\r\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\r\n }\r\n /**\r\n * CKEditor5 Integration.\r\n *\r\n * @param {integrationModelProperties} integrationModelAttributes\r\n */\r\n super(ckeditorIntegrationModelProperties);\r\n\r\n /**\r\n * Folder name used for the integration inside CKEditor plugins folder.\r\n */\r\n this.integrationFolderName = \"ckeditor_wiris\";\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @returns {string} - The CKEditor instance language.\r\n * @override\r\n */\r\n getLanguage() {\r\n // Returns the CKEDitor instance language taking into account that the language can be an object.\r\n // Try to get editorParameters.language, fail silently otherwise\r\n try {\r\n return this.editorParameters.language;\r\n } catch (e) {}\r\n const languageObject = this.editorObject.config.get(\"language\");\r\n if (languageObject != null) {\r\n if (typeof languageObject === \"object\") {\r\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\r\n return languageObject.ui;\r\n }\r\n return this.editorObject.locale.uiLanguage;\r\n }\r\n return languageObject;\r\n }\r\n return super.getLanguage();\r\n }\r\n\r\n /**\r\n * Adds callbacks to the following CKEditor listeners:\r\n * - 'focus' - updates the current instance.\r\n * - 'contentDom' - adds 'doubleclick' callback.\r\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\r\n * - 'setData' - parses the data converting MathML into images.\r\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\r\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\r\n * - 'mode' - recalculates the active element.\r\n */\r\n addEditorListeners() {\r\n const editor = this.editorObject;\r\n\r\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\r\n this.checkElement();\r\n }\r\n }\r\n\r\n /**\r\n * Checks the current container and assign events in case that it doesn't have them.\r\n * CKEditor replaces several times the element element during its execution,\r\n * so we must assign the events again to editor element.\r\n */\r\n checkElement() {\r\n const editor = this.editorObject;\r\n const newElement = editor.sourceElement;\r\n\r\n // If the element wasn't treated, add the events.\r\n if (!newElement.wirisActive) {\r\n this.setTarget(newElement);\r\n this.addEvents();\r\n // Set the element as treated\r\n newElement.wirisActive = true;\r\n }\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @param {HTMLElement} element - HTMLElement target.\r\n * @param {MouseEvent} event - event which trigger the handler.\r\n */\r\n doubleClickHandler(element, event) {\r\n this.core.editionProperties.dbclick = true;\r\n if (this.editorObject.isReadOnly === false) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\r\n // doubleclick event ends here.\r\n if (typeof event.stopPropagation !== \"undefined\") {\r\n // old I.E compatibility.\r\n event.stopPropagation();\r\n } else {\r\n event.returnValue = false;\r\n }\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\r\n if (customEditorAttr) {\r\n this.core.getCustomEditors().enable(customEditorAttr);\r\n }\r\n this.core.editionProperties.temporalImage = element;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n static getCorePath() {\r\n return null; // TODO\r\n }\r\n\r\n /** @inheritdoc */\r\n callbackFunction() {\r\n super.callbackFunction();\r\n this.addEditorListeners();\r\n }\r\n\r\n openNewFormulaEditor() {\r\n // Store the editor selection as it will be lost upon opening the modal\r\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\r\n\r\n // Focus on the selected editor when multiple editor instances are present\r\n WirisPlugin.currentInstance = this;\r\n\r\n return super.openNewFormulaEditor();\r\n }\r\n\r\n /**\r\n * Replaces old formula with new MathML or inserts it in caret position if new\r\n * @param {String} mathml MathML to update old one or insert\r\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\r\n */\r\n insertMathml(mathml) {\r\n // This returns the value returned by the callback function (writer => {...})\r\n return this.editorObject.model.change((writer) => {\r\n const core = this.getCore();\r\n const selection = this.editorObject.model.document.selection;\r\n\r\n const modelElementNew = writer.createElement(\"mathml\", {\r\n formula: mathml,\r\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\r\n });\r\n\r\n // Obtain the DOM object corresponding to the formula\r\n if (core.editionProperties.isNewElement) {\r\n // Don't bother inserting anything at all if the MathML is empty.\r\n if (!mathml) return;\r\n\r\n const viewSelection =\r\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\r\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\r\n\r\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\r\n\r\n // Remove selection\r\n if (!viewSelection.isCollapsed) {\r\n for (const range of viewSelection.getRanges()) {\r\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\r\n }\r\n }\r\n\r\n // Set carret after the formula\r\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\r\n writer.setSelection(position);\r\n } else {\r\n const img = core.editionProperties.temporalImage;\r\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\r\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\r\n\r\n // Insert the new and remove the old one\r\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\r\n\r\n // If the given MathML is empty, don't insert a new formula.\r\n if (mathml) {\r\n this.editorObject.model.insertObject(modelElementNew, position);\r\n }\r\n writer.remove(modelElementOld);\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return modelElementNew;\r\n });\r\n }\r\n\r\n /**\r\n * Finds the text node corresponding to given DOM text element.\r\n * @param {element} viewElement Element to find corresponding text node of.\r\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\r\n */\r\n findText(viewElement) {\r\n // eslint-disable-line consistent-return\r\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\r\n let pivot = viewElement;\r\n let element;\r\n while (!element) {\r\n element = this.editorObject.editing.mapper.toModelElement(\r\n this.editorObject.editing.view.domConverter.domToView(pivot),\r\n );\r\n pivot = pivot.parentElement;\r\n }\r\n\r\n // Navigate through all the subtree under `pivot` in order to find the correct text node\r\n const range = this.editorObject.model.createRangeIn(element);\r\n const descendants = Array.from(range.getItems());\r\n for (const node of descendants) {\r\n let viewElementData = viewElement.data;\r\n if (viewElement.nodeType === 3) {\r\n // Remove invisible white spaces\r\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\r\n }\r\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\r\n return node.textNode;\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n // eslint-disable-line no-unused-vars\r\n const returnObject = {};\r\n\r\n let mathmlOrigin;\r\n if (!mathml) {\r\n this.insertMathml(\"\");\r\n } else if (this.core.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n\r\n this.editorObject.model.change((writer) => {\r\n const { latexRange } = this.core.editionProperties;\r\n\r\n const startNode = this.findText(latexRange.startContainer);\r\n const endNode = this.findText(latexRange.endContainer);\r\n\r\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\r\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\r\n\r\n let range = writer.createRange(startPosition, endPosition);\r\n\r\n // When Latex is next to image/formula.\r\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\r\n // Get the position of the latex to be replaced.\r\n const latexEdited = `$$${Latex.getLatexFromMathML(\r\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\r\n )}$$`;\r\n let data = latexRange.startContainer.data;\r\n\r\n // Remove invisible characters.\r\n data = data.replaceAll(String.fromCharCode(8288), \"\");\r\n\r\n // Get to the start of the latex we are editing.\r\n const offset = data.indexOf(latexEdited);\r\n const dataOffset = data.substring(offset);\r\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\r\n const substring = dataOffset.substr(0, second$);\r\n data = data.replace(substring, \"\");\r\n\r\n if (!data) {\r\n startPosition = writer.createPositionBefore(startNode);\r\n range = startNode;\r\n } else {\r\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\r\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\r\n range = writer.createRange(startPosition, endPosition);\r\n }\r\n }\r\n\r\n writer.remove(range);\r\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\r\n });\r\n } else {\r\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\r\n try {\r\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\r\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\r\n windowTarget.document,\r\n );\r\n } catch (e) {\r\n const x = e.toString();\r\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\r\n this.core.modalDialog.cancelAction();\r\n }\r\n }\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.core.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n\r\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\r\n We probably should add it here as well, but we should look further into how */\r\n // this.editorObject.fire('change');\r\n\r\n // Remove temporal image of inserted formula\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return returnObject;\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n notifyWindowClosed() {\r\n this.editorObject.editing.view.focus();\r\n }\r\n}\r\n","/* eslint-disable max-classes-per-file */\r\nimport { Command } from \"ckeditor5/src/core.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\n/**\r\n * Command for opening the MathType editor\r\n */\r\nexport class MathTypeCommand extends Command {\r\n execute(options = {}) {\r\n // Check we get a valid integration\r\n // eslint-disable-next-line no-prototype-builtins\r\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\r\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\r\n }\r\n\r\n // Save the integration instance as a property of the command.\r\n this.integration = options.integration;\r\n\r\n // Set custom editor or disable it\r\n this.setEditor();\r\n\r\n // Open the editor\r\n this.openEditor();\r\n }\r\n\r\n /**\r\n * Sets the appropriate custom editor, if any, or disables them.\r\n */\r\n setEditor() {\r\n // It's possible that a custom editor was last used.\r\n // We need to disable it to avoid wrong behaviors.\r\n this.integration.core.getCustomEditors().disable();\r\n }\r\n\r\n /**\r\n * Checks whether we are editing an existing formula or a new one and opens the editor.\r\n */\r\n openEditor() {\r\n this.integration.core.editionProperties.dbclick = false;\r\n const image = this._getSelectedImage();\r\n if (\r\n typeof image !== \"undefined\" &&\r\n image !== null &&\r\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\r\n ) {\r\n this.integration.core.editionProperties.temporalImage = image;\r\n this.integration.openExistingFormulaEditor();\r\n } else {\r\n this.integration.openNewFormulaEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Gets the currently selected formula image\r\n * @returns {Element} selected image, if any, undefined otherwise\r\n */\r\n _getSelectedImage() {\r\n const { selection } = this.editor.editing.view.document;\r\n\r\n // If we can not extract the formula, fall back to default behavior.\r\n if (selection.isCollapsed || selection.rangeCount !== 1) {\r\n return;\r\n }\r\n\r\n // Look for the wrapping the formula and then for the inside\r\n\r\n const range = selection.getFirstRange();\r\n\r\n let image;\r\n\r\n for (const span of range) {\r\n if (span.item.name !== \"span\") {\r\n return;\r\n }\r\n image = span.item.getChild(0);\r\n break;\r\n }\r\n\r\n if (!image) {\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return this.editor.editing.view.domConverter.mapViewToDom(image);\r\n }\r\n}\r\n\r\n/**\r\n * Command for opening the ChemType editor\r\n */\r\nexport class ChemTypeCommand extends MathTypeCommand {\r\n setEditor() {\r\n this.integration.core.getCustomEditors().enable(\"chemistry\");\r\n }\r\n}\r\n","// CKEditor imports\r\nimport { Plugin } from \"ckeditor5/src/core.js\";\r\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\r\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\r\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\r\n\r\n// MathType API imports\r\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\r\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\r\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\r\n\r\n// Local imports\r\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\r\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\r\n\r\nimport packageInfo from \"../package.json\";\r\n\r\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\r\n\r\nexport default class MathType extends Plugin {\r\n static get requires() {\r\n return [Widget];\r\n }\r\n\r\n static get pluginName() {\r\n return \"MathType\";\r\n }\r\n\r\n init() {\r\n // Create the MathType API Integration object\r\n const integration = this._addIntegration();\r\n currentInstance = integration;\r\n\r\n // Add the MathType and ChemType commands to the editor\r\n this._addCommands();\r\n\r\n // Add the buttons for MathType and ChemType\r\n this._addViews(integration);\r\n\r\n // Registers the element in the schema\r\n this._addSchema();\r\n\r\n // Add the downcast and upcast converters\r\n this._addConverters(integration);\r\n\r\n // Expose the WirisPlugin variable to the window\r\n this._exposeWiris();\r\n }\r\n\r\n /**\r\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\r\n */\r\n destroy() {\r\n // eslint-disable-line class-methods-use-this\r\n currentInstance?.destroy();\r\n }\r\n\r\n /**\r\n * Create the MathType API Integration object\r\n * @returns {CKEditor5Integration} the integration object\r\n */\r\n _addIntegration() {\r\n const { editor } = this;\r\n\r\n /**\r\n * Integration model constructor attributes.\r\n * @type {integrationModelProperties}\r\n */\r\n const integrationProperties = {};\r\n integrationProperties.environment = {};\r\n integrationProperties.environment.editor = \"CKEditor5\";\r\n integrationProperties.environment.editorVersion = \"5.x\";\r\n integrationProperties.version = packageInfo.version;\r\n integrationProperties.editorObject = editor;\r\n integrationProperties.serviceProviderProperties = {};\r\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\r\n integrationProperties.serviceProviderProperties.server = \"java\";\r\n integrationProperties.target = editor.sourceElement;\r\n integrationProperties.scriptName = \"bundle.js\";\r\n integrationProperties.managesLanguage = true;\r\n // etc\r\n\r\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\r\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\r\n let integration;\r\n if (integrationProperties.target) {\r\n // Instance of the integration associated to this editor instance\r\n integration = new CKEditor5Integration(integrationProperties);\r\n integration.init();\r\n integration.listeners.fire(\"onTargetReady\", {});\r\n\r\n integration.checkElement();\r\n\r\n this.listenTo(\r\n editor.editing.view.document,\r\n \"click\",\r\n (evt, data) => {\r\n // Is Double-click\r\n if (data.domEvent.detail === 2) {\r\n integration.doubleClickHandler(data.domTarget, data.domEvent);\r\n evt.stop();\r\n }\r\n },\r\n { priority: \"highest\" },\r\n );\r\n }\r\n\r\n return integration;\r\n }\r\n\r\n /**\r\n * Add the MathType and ChemType commands to the editor\r\n */\r\n _addCommands() {\r\n const { editor } = this;\r\n\r\n // Add command to open the formula editor\r\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\r\n\r\n // Add command to open the chemistry formula editor\r\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\r\n }\r\n\r\n /**\r\n * Add the buttons for MathType and ChemType\r\n * @param {CKEditor5Integration} integration the integration object\r\n */\r\n _addViews(integration) {\r\n const { editor } = this;\r\n\r\n // Check if MathType editor is enabled\r\n if (Configuration.get(\"editorEnabled\")) {\r\n // Add button for the formula editor\r\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\r\n view.set({\r\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\r\n icon: mathIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"MathType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Check if ChemType editor is enabled\r\n if (Configuration.get(\"chemEnabled\")) {\r\n // Add button for the chemistry formula editor\r\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\r\n\r\n view.set({\r\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\r\n icon: chemIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"ChemType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Observer for the Double-click event\r\n editor.editing.view.addObserver(ClickObserver);\r\n }\r\n\r\n /**\r\n * Registers the element in the schema\r\n */\r\n _addSchema() {\r\n const { schema } = this.editor.model;\r\n\r\n schema.register(\"mathml\", {\r\n inheritAllFrom: \"$inlineObject\",\r\n allowAttributes: [\"formula\", \"htmlContent\"],\r\n });\r\n }\r\n\r\n /**\r\n * Add the downcast and upcast converters\r\n */\r\n _addConverters(integration) {\r\n const { editor } = this;\r\n\r\n // Editing view -> Model\r\n editor.conversion.for(\"upcast\").elementToElement({\r\n view: {\r\n name: \"span\",\r\n classes: \"ck-math-widget\",\r\n },\r\n model: (viewElement, { writer: modelWriter }) => {\r\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\r\n return modelWriter.createElement(\"mathml\", {\r\n formula,\r\n });\r\n },\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // When element was already consumed then skip it.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // If we encounter any with a LaTeX annotation inside,\r\n // convert it into a \"$$...$$\" string.\r\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\r\n\r\n // Get the formula of the (which is all its children).\r\n const processor = new XmlDataProcessor(editor.editing.view.document);\r\n\r\n // Only god knows why the following line makes viewItem lose all of its children,\r\n // so we obtain isLatex before doing this because we need viewItem's children for that.\r\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\r\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\r\n\r\n // and obtain the attributes of too!\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n\r\n // We process the document fragment\r\n let formula = processor.toData(viewDocumentFragment) || \"\";\r\n\r\n // And obtain the complete formula\r\n formula = Util.htmlSanitize(`${formula}`);\r\n\r\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\r\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\r\n\r\n /* Model node that contains what's going to actually be inserted. This can be either:\r\n - A element with a formula attribute set to the given formula, or\r\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\r\n const modelNode = isLatex\r\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\r\n : writer.createElement(\"mathml\", { formula });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\r\n \"element:img\",\r\n (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // Only upcast when is wiris formula\r\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\r\n return;\r\n }\r\n\r\n // The following code ensures that the element's name, attributes, and classes are consumed.\r\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\r\n\r\n // Check if we can consume the element name.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // Consume the name, attributes, and classes so nothing else processes it.\r\n consumable.consume(viewItem, { name: true });\r\n for (const attrName of viewItem.getAttributes()) {\r\n consumable.consume(viewItem, { attributes: [attrName] });\r\n }\r\n\r\n for (const className of viewItem.getClassNames()) {\r\n consumable.consume(viewItem, { classes: [className] });\r\n }\r\n\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n const htmlContent = Util.htmlSanitize(``);\r\n\r\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n },\r\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\r\n { priority: \"high\" },\r\n );\r\n\r\n /**\r\n * Whether the given view element has a LaTeX annotation element.\r\n * @param {*} math\r\n * @returns {bool}\r\n */\r\n function mathIsLatex(math) {\r\n const semantics = math.getChild(0);\r\n if (!semantics || semantics.name !== \"semantics\") return false;\r\n for (const child of semantics.getChildren()) {\r\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n function createViewWidget(modelItem, { writer: viewWriter }) {\r\n const widgetElement = viewWriter.createContainerElement(\"span\", {\r\n class: \"ck-math-widget\",\r\n });\r\n\r\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\r\n\r\n if (mathUIElement) {\r\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\r\n }\r\n\r\n return toWidget(widgetElement, viewWriter);\r\n }\r\n\r\n function createViewImage(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n const formula = modelItem.getAttribute(\"formula\");\r\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\r\n\r\n if (!formula && !htmlContent) {\r\n return null;\r\n }\r\n\r\n let imgElement = null;\r\n\r\n if (htmlContent) {\r\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\r\n } else if (formula) {\r\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\r\n\r\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\r\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\r\n\r\n // Add HTML element () to model\r\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\r\n }\r\n\r\n /* Although we use the HtmlDataProcessor to obtain the attributes,\r\n * we must create a new EmptyElement which is independent of the\r\n * DataProcessor being used by this editor instance\r\n */\r\n if (imgElement) {\r\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\r\n renderUnsafeAttributes: [\"src\"],\r\n });\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // Model -> Editing view\r\n editor.conversion.for(\"editingDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createViewWidget,\r\n });\r\n\r\n // Model -> Data view\r\n editor.conversion.for(\"dataDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createDataString, // eslint-disable-line no-use-before-define\r\n });\r\n\r\n /**\r\n * Makes a copy of the given view node.\r\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\r\n * @returns {module:engine/view/node~Node} Copy of the node.\r\n */\r\n function clone(viewWriter, sourceNode) {\r\n if (sourceNode.is(\"text\")) {\r\n return viewWriter.createText(sourceNode.data);\r\n }\r\n if (sourceNode.is(\"element\")) {\r\n if (sourceNode.is(\"emptyElement\")) {\r\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\r\n }\r\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\r\n for (const child of sourceNode.getChildren()) {\r\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\r\n }\r\n return element;\r\n }\r\n\r\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\r\n }\r\n\r\n function createDataString(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n // Load img element\r\n const mathString =\r\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\r\n\r\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\r\n\r\n return clone(viewWriter, sourceMathElement);\r\n }\r\n\r\n // This stops the view selection getting into the s and messing up caret movement\r\n editor.editing.mapper.on(\r\n \"viewToModelPosition\",\r\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\r\n );\r\n\r\n // Keep a reference to the original get and set function.\r\n const { get, set } = editor.data;\r\n\r\n /**\r\n * Hack to transform $$latex$$ into in editor.getData()'s output.\r\n */\r\n editor.data.on(\r\n \"get\",\r\n (e) => {\r\n const output = e.return;\r\n const parsedResult = Parser.endParse(output);\r\n\r\n // Cleans all the semantics tag for safexml\r\n // including the handwritten data points\r\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\r\n },\r\n { priority: \"low\" },\r\n );\r\n\r\n /**\r\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\r\n */\r\n editor.data.on(\r\n \"set\",\r\n (e, args) => {\r\n // Retrieve the data to be set on the CKEditor.\r\n let modifiedData = args[0];\r\n // Regex to find all mathml formulas.\r\n const regexp = /(]*>)|()/gm;\r\n const formulas = [];\r\n let formula;\r\n\r\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\r\n while ((formula = regexp.exec(modifiedData)) !== null) {\r\n formulas.push(formula[0]);\r\n }\r\n\r\n // Loop to find LaTeX and formula images and replace the MathML for the both.\r\n formulas.forEach((formula) => {\r\n if (formula.includes('encoding=\"LaTeX\"')) {\r\n // LaTeX found.\r\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\r\n modifiedData = modifiedData.replace(formula, latex);\r\n } else if (formula.includes(\"\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var closeIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var closeHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var closeHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var fullsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var fullsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var fullsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var fullsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var minIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var minIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var minHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var minHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var minsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var minsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var minsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var minsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var maxIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var maxIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; - var maxHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; + var maxHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; // eslint-disable-next-line max-classes-per-file const { unprotect, protect } = focusProtection(); @@ -9276,7 +9276,7 @@ * @private */ Core._initialized = false; - var warnIcon = "\n\n\n"; + var warnIcon = "\r\n\r\n\r\n"; // eslint-disable-next-line no-unused-vars, import/named /** @@ -11467,11 +11467,11 @@ } } - var mathIcon = "\n\n\n\n\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n"; + var mathIcon = "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n"; - var chemIcon = "\n\n\n\n\n\n"; + var chemIcon = "\r\n\r\n\r\n\r\n\r\n\r\n"; - var version = "8.13.4"; + var version = "8.14.0"; var packageInfo = { version: version}; diff --git a/packages/ckeditor5/dist/browser/index.umd.js.map b/packages/ckeditor5/dist/browser/index.umd.js.map index 39d6a768b..db6210c14 100644 --- a/packages/ckeditor5/dist/browser/index.umd.js.map +++ b/packages/ckeditor5/dist/browser/index.umd.js.map @@ -1 +1 @@ -{"version":3,"file":"index.umd.js","sources":["../../../../node_modules/dompurify/dist/purify.es.mjs","../../../devkit/src/constants.js","../../../devkit/src/mathml.js","../../../devkit/src/configuration.js","../../../devkit/src/textcache.js","../../../devkit/src/listeners.js","../../../devkit/src/serviceprovider.js","../../../devkit/src/latex.js","../../../devkit/src/stringmanager.js","../../../devkit/src/util.js","../../../devkit/src/image.js","../../../devkit/src/accessibility.js","../../../devkit/src/parser.js","../../../devkit/src/editorlistener.js","../../../devkit/telemeter-wasm/telemeter_wasm.js","../../../devkit/src/telemeter.js","../../../devkit/src/contentmanager.js","../../../devkit/src/customeditors.js","../../../devkit/src/jsvariables.js","../../../devkit/src/event.js","../../../devkit/src/popupmessage.js","../../../devkit/src/focusprotection.js","../../../devkit/src/modal.js","../../../devkit/src/polyfills.js","../../../devkit/src/core.src.js","../../../devkit/src/integrationmodel.js","../../../devkit/src/md5.js","../../src/integration.js","../../src/commands.js","../../src/plugin.js"],"sourcesContent":["/*! @license DOMPurify 3.3.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.0/LICENSE */\n\nconst {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor\n} = Object;\nlet {\n freeze,\n seal,\n create\n} = Object; // eslint-disable-line import/no-mutable-exports\nlet {\n apply,\n construct\n} = typeof Reflect !== 'undefined' && Reflect;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!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}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\n * This class represents all the constants needed in a MathType integration among different classes.\n * If a constant should be used across different classes should be defined using attribute\n * accessors.\n */\nexport default class Constants {\n /**\n * Safe XML entities.\n * @type {Object}\n */\n static get safeXmlCharactersEntities() {\n return {\n tagOpener: \"«\",\n tagCloser: \"»\",\n doubleQuote: \"¨\",\n realDoubleQuote: \""\",\n };\n }\n\n /**\n * Blackboard invalid safe characters.\n * @type {Object}\n */\n static get safeBadBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยป<ยซ/moยป\",\n gtElement: \"ยซmoยป>ยซ/moยป\",\n ampElement: \"ยซmoยป&ยซ/moยป\",\n };\n }\n\n /**\n * Blackboard valid safe characters.\n * @type{Object}\n */\n static get safeGoodBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\n };\n }\n\n /**\n * Standard XML special characters.\n * @type {Object}\n */\n static get xmlCharacters() {\n return {\n id: \"xmlCharacters\",\n tagOpener: \"<\", // Hex: \\x3C.\n tagCloser: \">\", // Hex: \\x3E.\n doubleQuote: '\"', // Hex: \\x22.\n ampersand: \"&\", // Hex: \\x26.\n quote: \"'\", // Hex: \\x27.\n };\n }\n\n /**\n * Safe XML special characters. This characters are used instead the standard\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\n * special character have a UTF-8 representation.\n * @type {Object}\n */\n static get safeXmlCharacters() {\n return {\n id: \"safeXmlCharacters\",\n tagOpener: \"ยซ\", // Hex: \\xAB.\n tagCloser: \"ยป\", // Hex: \\xBB.\n doubleQuote: \"ยจ\", // Hex: \\xA8.\n ampersand: \"ยง\", // Hex: \\xA7.\n quote: \"`\", // Hex: \\x60.\n realDoubleQuote: \"ยจ\",\n };\n }\n}\n","import Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a class to manage MathML objects.\n */\nexport default class MathML {\n /**\n * Checks if the mathml at position i is inside an HTML attribute or not.\n * @param {string} content - a string containing MathML code.\n * @param {number} i - search index.\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\n */\n static isMathmlInAttribute(content, i) {\n // Regex =\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\n const expression = new RegExp(regex);\n\n const actualContent = content.substring(0, i);\n const reversed = actualContent.split(\"\").reverse().join(\"\");\n const exists = expression.test(reversed);\n\n return exists;\n }\n\n /**\n * Decodes an encoded MathML with standard XML tags.\n * We use these entities because IE doesn't support html entities\n * on its attributes sometimes. Yes, sometimes.\n * @param {string} input - string to be decoded.\n * @return {string} decoded string.\n */\n static safeXmlDecode(input) {\n let { tagOpener } = Constants.safeXmlCharactersEntities;\n let { tagCloser } = Constants.safeXmlCharactersEntities;\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\n // Decoding entities.\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n // Added to fix problem due to import from 1.9.x.\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\n\n // Blackboard.\n const { ltElement } = Constants.safeBadBlackboardCharacters;\n const { gtElement } = Constants.safeBadBlackboardCharacters;\n const { ampElement } = Constants.safeBadBlackboardCharacters;\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\n }\n\n ({ tagOpener } = Constants.safeXmlCharacters);\n ({ tagCloser } = Constants.safeXmlCharacters);\n ({ doubleQuote } = Constants.safeXmlCharacters);\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\n const { ampersand } = Constants.safeXmlCharacters;\n const { quote } = Constants.safeXmlCharacters;\n\n // Decoding characters.\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\n input = input.split(quote).join(Constants.xmlCharacters.quote);\n\n // We are replacing $ by & when its part of an entity for retro-compatibility.\n // Now, the standard is replace ยง by &.\n let returnValue = \"\";\n let currentEntity = null;\n\n for (let i = 0; i < input.length; i += 1) {\n const character = input.charAt(i);\n if (currentEntity == null) {\n if (character === \"$\") {\n currentEntity = \"\";\n } else {\n returnValue += character;\n }\n } else if (character === \";\") {\n returnValue += `&${currentEntity}`;\n currentEntity = null;\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\n // Character is part of an entity.\n currentEntity += character;\n } else {\n returnValue += `$${currentEntity}`; // Is not an entity.\n currentEntity = null;\n i -= 1; // Parse again the current character.\n }\n }\n\n return returnValue;\n }\n\n /**\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\n * @param {string} input - input string to be encoded\n * @returns {string} encoded string.\n */\n static safeXmlEncode(input) {\n const { tagOpener } = Constants.xmlCharacters;\n const { tagCloser } = Constants.xmlCharacters;\n const { doubleQuote } = Constants.xmlCharacters;\n const { ampersand } = Constants.xmlCharacters;\n const { quote } = Constants.xmlCharacters;\n\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\n\n return input;\n }\n\n /**\n * Converts special symbols (> 128) to entities and replaces all textual\n * entities by its number entities.\n * @param {string} mathml - MathML string containing - or not - special symbols\n * @returns {string} MathML with all textual entities replaced.\n */\n static mathMLEntities(mathml) {\n let toReturn = \"\";\n\n for (let i = 0; i < mathml.length; i += 1) {\n const character = mathml.charAt(i);\n\n // Parsing > 128 characters.\n if (mathml.codePointAt(i) > 128) {\n toReturn += `&#${mathml.codePointAt(i)};`;\n // For UTF-32 characters we need to move the index one position.\n if (mathml.codePointAt(i) > 0xffff) {\n i += 1;\n }\n } else if (character === \"&\") {\n const end = mathml.indexOf(\";\", i + 1);\n if (end >= 0) {\n const container = document.createElement(\"span\");\n container.innerHTML = mathml.substring(i, end + 1);\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\n i = end;\n } else {\n toReturn += character;\n }\n } else {\n toReturn += character;\n }\n }\n\n return toReturn;\n }\n\n /**\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\n * @param {string} customEditor - custom editor name.\n * @returns {string} MathML string with his class containing the editor toolbar string.\n */\n static addCustomEditorClassAttribute(mathml, customEditor) {\n let toReturn = \"\";\n\n const start = mathml.indexOf(\"\");\n if (mathml.indexOf(\"class\") === -1) {\n // Adding custom editor type.\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\n toReturn += mathml.substr(end + 1, mathml.length);\n return toReturn;\n }\n }\n return mathml;\n }\n\n /**\n * Remove a custom editor name from the MathML class attribute.\n * @param {string} mathml - a MathML string.\n * @param {string} customEditor - custom editor name.\n * @returns {string} The input MathML without customEditor name in his class.\n */\n static removeCustomEditorClassAttribute(mathml, customEditor) {\n // Discard MathML without the specified class.\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\n return mathml;\n }\n\n // Trivial case: class attribute value equal to editor name. Then\n // class attribute is removed.\n // First try to remove it with a space before if there is one\n // Otherwise without the space\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\n }\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\n }\n\n // Non Trivial case: class attribute contains editor name.\n return mathml.replace(`wrs_${customEditor}`, \"\");\n }\n\n /**\n * Adds annotation tag in MathML element.\n * @param {String} mathml - valid MathML.\n * @param {String} content - value to put inside annotation tag.\n * @param {String} annotationEncoding - annotation encoding.\n * @returns {String} - 'mathml' with an annotation that contains\n * 'content' and encoding 'encoding'.\n */\n static addAnnotation(mathml, content, annotationEncoding) {\n // If contains annotation, also contains semantics tag.\n const containsAnnotation = mathml.indexOf(\"\");\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\n } else if (MathML.isEmpty(mathml)) {\n const endIndexInline = mathml.indexOf(\"/>\");\n const endIndexNonInline = mathml.indexOf(\">\");\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\n } else {\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\n const endMathmlContent = mathml.lastIndexOf(\"\");\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\n }\n\n return mathmlWithAnnotation;\n }\n\n /**\n * Removes specific annotation tag in MathML element.\n * In case of remove the unique annotation, also is removed semantics tag.\n * @param {String} mathml - valid MathML.\n * @param {String} annotationEncoding - annotation encoding to remove.\n * @returns {String} - 'mathml' without the annotation encoding specified.\n */\n static removeAnnotation(mathml, annotationEncoding) {\n let mathmlWithoutAnnotation = mathml;\n const openAnnotationTag = ``;\n const closeAnnotationTag = \"\";\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\n if (startAnnotationIndex !== -1) {\n let differentAnnotationFound = false;\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\n\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\n }\n\n /**\n * Removes semantics tag to element that contains mathml.\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\n * @param {string} element - Inner HTML text string.\n * @returns {string} - 'mathml' without semantics tag.\n */\n static removeSafeXMLSemantics(element) {\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\n\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\n }\n\n /**\n * Transforms all xml mathml occurrences that contain semantics to the same\n * xml mathml occurrences without semantics.\n * @param {string} text - string that can contain xml mathml occurrences.\n * @param {Constants} [characters] - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * xmlCharacters by default.\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\n */\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\n const mathTagStart = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const mathTagEndline = `/${characters.tagCloser}`;\n const { tagCloser } = characters;\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\n\n let output = \"\";\n let start = text.indexOf(mathTagStart);\n let end = 0;\n while (start !== -1) {\n output += text.substring(end, start);\n\n // MathML can be written as '' or ''.\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\n const firstTagCloser = text.indexOf(tagCloser, start);\n if (mathTagEndIndex !== -1) {\n end = mathTagEndIndex;\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\n end = mathTagEndlineIndex;\n }\n\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\n if (semanticsIndex !== -1) {\n const mmlTagStart = text.substring(start, semanticsIndex);\n const annotationIndex = text.indexOf(annotationTagStart, start);\n if (annotationIndex !== -1) {\n const startIndex = semanticsIndex + semanticsTagStart.length;\n const mmlContent = text.substring(startIndex, annotationIndex);\n output += mmlTagStart + mmlContent + mathTagEnd;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n end += mathTagEnd.length;\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n }\n\n output += text.substring(end, text.length);\n return output;\n }\n\n /**\n * Returns true if a MathML contains a certain class.\n * @param {string} mathML - input MathML.\n * @param {string} className - className.\n * @returns {boolean} true if the input MathML contains the input class.\n * false otherwise.\n * @static\n */\n static containClass(mathML, className) {\n const classIndex = mathML.indexOf(\"class\");\n if (classIndex === -1) {\n return false;\n }\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\n const classTag = mathML.substring(classIndex, classTagEndIndex);\n if (classTag.indexOf(className) !== -1) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns true if mathml is empty. Otherwise, false.\n * @param {string} mathml - valid MathML with standard XML tags.\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\n */\n static isEmpty(mathml) {\n // MathML can have the shape or ''.\n const closeTag = \">\";\n const closeTagInline = \"/>\";\n const firstCloseTagIndex = mathml.indexOf(closeTag);\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\n let empty = false;\n // MathML is always empty in the second shape.\n if (firstCloseTagInlineIndex !== -1) {\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\n empty = true;\n }\n }\n\n // MathML is always empty in the first shape when there aren't elements\n // between math tags.\n if (!empty) {\n const mathTagEndRegex = new RegExp(\"\");\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\n if (mathTagEndArray) {\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\n }\n }\n\n return empty;\n }\n\n /**\n * Encodes html entities inside properties.\n * @param {String} mathml - valid MathML with standard XML tags.\n * @returns {String} - 'mathml' with property entities encoded.\n */\n static encodeProperties(mathml) {\n // Search all the properties.\n const regex = /\\w+=\".*?\"/g;\n // Encode html entities.\n const replacer = (match) => {\n // It has the shape:\n // .\n const quoteIndex = match.indexOf('\"');\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\n return matchEncoded;\n };\n\n const mathmlEncoded = mathml.replace(regex, replacer);\n return mathmlEncoded;\n }\n}\n","/**\n * This class represents the configuration class.\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\n */\nexport default class Configuration {\n /**\n * Adds a properties object to {@link Configuration.properties}.\n * @param {Object} properties - properties to append to current properties.\n */\n static addConfiguration(properties) {\n Object.assign(Configuration.properties, properties);\n }\n\n /**\n * Static property.\n * The configuration properties object.\n * @private\n * @type {Object}\n */\n static get properties() {\n return Configuration._properties;\n }\n\n /**\n * Static property setter.\n * Set configuration properties.\n * @param {Object} value - The property value.\n * @ignore\n */\n static set properties(value) {\n Configuration._properties = value;\n }\n\n /**\n * Returns the value of a property key.\n * @param {String} key - Property key\n * @returns {String} Property value\n */\n static get(key) {\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\n // Backwards compatibility.\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\n return Configuration.properties[`_wrs_conf_${key}`];\n }\n return false;\n }\n return Configuration.properties[key];\n }\n\n /**\n * Adds a new property to Configuration class.\n * @param {String} key - Property key.\n * @param {Object} value - Property value.\n */\n static set(key, value) {\n Configuration.properties[key] = value;\n }\n\n /**\n * Updates a property object value with new values.\n * @param {String} key - The property key to be updated.\n * @param {Object} propertyValue - Object containing the new values.\n */\n static update(key, propertyValue) {\n if (!Configuration.get(key)) {\n Configuration.set(key, propertyValue);\n } else {\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\n Configuration.set(key, updateProperty);\n }\n }\n}\n\n/**\n * Static properties object. Stores all configuration properties.\n * Needed to attribute accessors.\n * @private\n * @type {Object}\n */\nConfiguration._properties = {};\n","export default class TextCache {\n /**\n * @classdesc\n * This class represent a client-side text cache class. Contains pairs of\n * strings (key/value) which can be retrieved in any moment. Usually used\n * to store AJAX responses for text services like mathml2latex\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\n * @constructs\n */\n constructor() {\n /**\n * Cache array property storing the cache entries.\n * @type {Array.}\n */\n this.cache = [];\n }\n\n /**\n * This method populates a key/value pair into the {@link this.cache} property.\n * @param {String} key - Cache key, usually the service string parameter.\n * @param {String} value - Cache value, usually the service response.\n */\n populate(key, value) {\n this.cache[key] = value;\n }\n\n /**\n * Returns the cache value associated to certain cache key.\n * @param {String} key - Cache key, usually the service string parameter.\n * @return {String} value - Cache value, if exists. False otherwise.\n */\n get(key) {\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\n return this.cache[key];\n }\n return false;\n }\n}\n","/**\n * This object represents a custom listener.\n * @typedef {Object} Listener\n * @property {String} Listener.eventName - The listener name.\n * @property {Function} Listener.callback - The listener callback function.\n */\n\nexport default class Listeners {\n /**\n * @classdesc\n * This class represents a custom listeners manager.\n * @constructs\n */\n constructor() {\n /**\n * Array containing all custom listeners.\n * @type {Object[]}\n */\n this.listeners = [];\n }\n\n /**\n * Add a listener to Listener class.\n * @param {Object} listener - A listener object.\n */\n add(listener) {\n this.listeners.push(listener);\n }\n\n /**\n * Fires MathType event listeners\n * @param {String} eventName - event name\n * @param {Event} event - event object.\n * @return {boolean} false if event has been prevented. true otherwise.\n */\n fire(eventName, event) {\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\n if (this.listeners[i].eventName === eventName) {\n // Calling listener.\n this.listeners[i].callback(event);\n }\n }\n return event.defaultPrevented;\n }\n\n /**\n * Creates a new listener object.\n * @param {string} eventName - Event name.\n * @param {Object} callback - Callback function.\n * @returns {object} the listener object.\n */\n static newListener(eventName, callback) {\n const listener = {};\n listener.eventName = eventName;\n listener.callback = callback;\n return listener;\n }\n}\n","import Util from \"./util\";\nimport Listeners from \"./listeners\";\nimport Configuration from \"./configuration\";\n\n/**\n * @typedef {Object} ServiceProviderProperties\n * @property {String} URI - Service URI.\n * @property {String} server - Service server language.\n */\n\n/**\n * @classdesc\n * Class representing a serviceProvider. A serviceProvider is a class containing\n * an arbitrary number of services with the correspondent path.\n */\nexport default class ServiceProvider {\n /**\n * Returns Service Provider listeners.\n * @type {Listeners}\n */\n static get listeners() {\n return ServiceProvider._listeners;\n }\n\n /**\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\n * @param {Listener} listener - Instance of {@link Listener}.\n */\n static addListener(listener) {\n ServiceProvider.listeners.add(listener);\n }\n\n /**\n * Fires events in Service Provider.\n * @param {String} eventName - Event name.\n * @param {Event} event - Event object.\n */\n static fireEvent(eventName, event) {\n ServiceProvider.listeners.fire(eventName, event);\n }\n\n /**\n * Service parameters.\n * @type {ServiceProviderProperties}\n *\n */\n static get parameters() {\n return ServiceProvider._parameters;\n }\n\n /**\n * Service parameters.\n * @private\n * @type {ServiceProviderProperties}\n */\n static set parameters(parameters) {\n ServiceProvider._parameters = parameters;\n }\n\n /**\n * Static property.\n * Return service provider paths.\n * @private\n * @type {String}\n */\n static get servicePaths() {\n return ServiceProvider._servicePaths;\n }\n\n /**\n * Static property setter.\n * Set service paths.\n * @param {String} value - The property value.\n * @ignore\n */\n static set servicePaths(value) {\n ServiceProvider._servicePaths = value;\n }\n\n /**\n * Adds a new service to the ServiceProvider.\n * @param {String} service - Service name.\n * @param {String} path - Service path.\n * @static\n */\n static setServicePath(service, path) {\n ServiceProvider.servicePaths[service] = path;\n }\n\n /**\n * Returns the service path for a certain service.\n * @param {String} serviceName - Service name.\n * @returns {String} The service path.\n * @static\n */\n static getServicePath(serviceName) {\n return ServiceProvider.servicePaths[serviceName];\n }\n\n /**\n * Static property.\n * Service provider integration path.\n * @type {String}\n */\n static get integrationPath() {\n return ServiceProvider._integrationPath;\n }\n\n /**\n * Static property setter.\n * Set service provider integration path.\n * @param {String} value - The property value.\n * @ignore\n */\n static set integrationPath(value) {\n ServiceProvider._integrationPath = value;\n }\n\n /**\n * Returns the server URL in the form protocol://serverName:serverPort.\n * @return {String} The client side server path.\n */\n static getServerURL() {\n const url = window.location.href;\n const arr = url.split(\"/\");\n const result = `${arr[0]}//${arr[2]}`;\n return result;\n }\n\n /**\n * Inits {@link this} class. Uses {@link this.integrationPath} as\n * base path to generate all backend services paths.\n * @param {Object} parameters - Function parameters.\n * @param {String} parameters.integrationPath - Service path.\n */\n static init(parameters) {\n ServiceProvider.parameters = parameters;\n // Services path (tech dependant).\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\n\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\n // for example: /app/service. For them we calculate the absolute URL path, i.e\n // protocol://domain:port/app/service\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\n const serverPath = ServiceProvider.getServerURL();\n configurationURI = serverPath + configurationURI;\n showImageURI = serverPath + showImageURI;\n createImageURI = serverPath + createImageURI;\n getMathMLURI = serverPath + getMathMLURI;\n serviceURI = serverPath + serviceURI;\n }\n\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\n ServiceProvider.setServicePath(\"service\", serviceURI);\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n\n ServiceProvider.listeners.fire(\"onInit\", {});\n }\n\n /**\n * Gets the content from an URL.\n * @param {String} url - Target URL.\n * @param {Object} [postVariables] - Object containing post variables.\n * null if a GET query should be done.\n * @returns {String} Content of the target URL.\n * @private\n * @static\n */\n static getUrl(url, postVariables) {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n const httpRequest = Util.createHttpRequest();\n\n if (httpRequest) {\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\n httpRequest.open(\"GET\", url, false);\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\n httpRequest.open(\"POST\", url, false);\n } else {\n httpRequest.open(\"POST\", currentPath + url, false);\n }\n\n let header = Configuration.get(\"customHeaders\");\n if (header) {\n if (typeof header === \"string\") {\n header = Util.convertStringToObject(header);\n }\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\n }\n\n if (typeof postVariables !== \"undefined\" && postVariables) {\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n httpRequest.send(Util.httpBuildQuery(postVariables));\n } else {\n httpRequest.send(null);\n }\n\n return httpRequest.responseText;\n }\n return \"\";\n }\n\n /**\n * Returns the response text of a certain service.\n * @param {String} service - Service name.\n * @param {String} postVariables - Post variables.\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\n * @returns {String} Service response text.\n */\n static getService(service, postVariables, get) {\n let response;\n if (get === true) {\n const getVariables = postVariables ? `?${postVariables}` : \"\";\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\n response = ServiceProvider.getUrl(serviceUrl);\n } else {\n const serviceUrl = ServiceProvider.getServicePath(service);\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\n }\n return response;\n }\n\n /**\n * Returns the server language of a certain service. The possible values\n * are: php, aspx, java and ruby.\n * This method has backward compatibility purposes.\n * @param {String} service - The configuration service.\n * @returns {String} - The server technology associated with the configuration service.\n */\n static getServerLanguageFromService(service) {\n if (service.indexOf(\".php\") !== -1) {\n return \"php\";\n }\n if (service.indexOf(\".aspx\") !== -1) {\n return \"aspx\";\n }\n if (service.indexOf(\"wirispluginengine\") !== -1) {\n return \"ruby\";\n }\n return \"java\";\n }\n\n /**\n * Returns the URI associated with a certain service.\n * @param {String} service - The service name.\n * @return {String} The service path.\n */\n static createServiceURI(service) {\n const extension = ServiceProvider.serverExtension();\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\n }\n\n static serverExtension() {\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\n return \".php\";\n }\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\n return \".aspx\";\n }\n return \"\";\n }\n}\n\n/**\n * @property {String} service - The service name.\n * @property {String} path - The service path.\n * @static\n */\nServiceProvider._servicePaths = {};\n\n/**\n * The integration path. Contains the path of the configuration service.\n * Used to define the path for all services.\n * @type {String}\n * @private\n */\nServiceProvider._integrationPath = \"\";\n\n/**\n * ServiceProvider static listeners.\n * @type {Listeners}\n * @private\n */\nServiceProvider._listeners = new Listeners();\n\n/**\n * Service provider parameters.\n * @type {ServiceProviderParameters}\n */\nServiceProvider._parameters = {};\n","import TextCache from \"./textcache\";\nimport MathML from \"./mathml\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a LaTeX parser. Manages the services which allows to convert\n * LaTeX into MathML and MathML into LaTeX.\n */\nexport default class Latex {\n /**\n * Static property.\n * Return latex cache.\n * @private\n * @type {Cache}\n */\n static get cache() {\n return Latex._cache;\n }\n\n /**\n * Static property setter.\n * Set latex cache.\n * @param {Cache} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Latex._cache = value;\n }\n\n /**\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\n * we call a text service with the param mathml2latex.\n * @param {String} mathml - MathML String.\n * @return {String} LaTeX string generated by the MathML argument.\n */\n static getLatexFromMathML(mathml) {\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\n /**\n * @type {TextCache}\n */\n const { cache } = Latex;\n\n const data = {\n service: \"mathml2latex\",\n mml: mathmlWithoutSemantics,\n };\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n // TODO: Error handling.\n let latex = \"\";\n\n if (jsonResponse.status === \"ok\") {\n latex = jsonResponse.result.text;\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\n // Inserting LaTeX semantics.\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\n cache.populate(latex, mathmlWithSemantics);\n }\n\n return latex;\n }\n\n /**\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\n * we call a text service with the param latex2mathml.\n * @param {String} latex - String containing a LaTeX formula.\n * @param {Boolean} includeLatexOnSemantics\n * - If true LaTeX would me included into MathML semantics.\n * @return {String} MathML string generated by the LaTeX argument.\n */\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\n /**\n * @type {TextCache}\n */\n const latexCache = Latex.cache;\n\n if (Latex.cache.get(latex)) {\n return Latex.cache.get(latex);\n }\n const data = {\n service: \"latex2mathml\",\n latex,\n };\n\n if (includeLatexOnSemantics) {\n data.saveLatex = \"\";\n }\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n let output;\n if (jsonResponse.status === \"ok\") {\n let mathml = jsonResponse.result.text;\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\n\n // Populate LatexCache.\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\n const content = Util.htmlEntities(latex);\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\n output = mathml;\n } else {\n output = mathml;\n }\n if (!latexCache.get(latex)) {\n latexCache.populate(latex, mathml);\n }\n } else {\n output = `$$${latex}$$`;\n }\n return output;\n }\n\n /**\n * Converts all occurrences of MathML code to LaTeX.\n * The MathML code should containing to be converted.\n * @param {String} content - A string containing MathML valid code.\n * @param {Object} characters - An object containing special characters.\n * @return {String} A string containing all MathML annotated occurrences\n * replaced by the corresponding LaTeX code.\n */\n static parseMathmlToLatex(content, characters) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n let mathml;\n let startAnnotation;\n let closeAnnotation;\n\n while (start !== -1) {\n output += content.substring(end, start);\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else {\n end += mathTagEnd.length;\n }\n\n mathml = content.substring(start, end);\n\n startAnnotation = mathml.indexOf(openTarget);\n if (startAnnotation !== -1) {\n startAnnotation += openTarget.length;\n closeAnnotation = mathml.indexOf(closeTarget);\n let latex = mathml.substring(startAnnotation, closeAnnotation);\n if (characters === Constants.safeXmlCharacters) {\n latex = MathML.safeXmlDecode(latex);\n }\n output += `$$${latex}$$`;\n // Populate latex into cache.\n\n Latex.cache.populate(latex, mathml);\n } else {\n output += mathml;\n }\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n\n /**\n * Extracts the latex of a determined position in a text.\n * @param {Node} textNode - textNode to extract the LaTeX\n * @param {Number} caretPosition - Starting position to find LaTeX.\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\n * \"$$\" by default.\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\n * @static\n */\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\n // Tags used for LaTeX formulas.\n const defaultLatexTags = {\n open: \"$$\",\n close: \"$$\",\n };\n // latexTags is an optional parameter. If is not set, use default latexTags.\n if (typeof latexTags === \"undefined\" || latexTags == null) {\n latexTags = defaultLatexTags;\n }\n // Looking for the first textNode.\n let startNode = textNode;\n\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\n // TEXT_NODE.\n startNode = startNode.previousSibling;\n }\n\n /**\n * Returns the next latex position and node from a specific node and position.\n * @param {Node} currentNode - Node where searching latex.\n * @param {Number} currentPosition - Current position inside the currentNode.\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\n * \"$$\" by default.\n * @param {Boolean} tag - Tag containing the current search.\n * @returns {Object} Object containing the current node and the position.\n */\n function getNextLatexPosition(currentNode, currentPosition, tag) {\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\n\n while (position === -1) {\n currentNode = currentNode.nextSibling;\n\n if (!currentNode) {\n // TEXT_NODE.\n return null; // Not found.\n }\n\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\n }\n\n return {\n node: currentNode,\n position,\n };\n }\n\n /**\n * Determines if a node is previous, or not, to a second one.\n * @param {Node} node - Start node.\n * @param {Number} position - Start node position.\n * @param {Node} endNode - End node.\n * @param {Number} endPosition - End node position.\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\n */\n function isPrevious(node, position, endNode, endPosition) {\n if (node === endNode) {\n return position <= endPosition;\n }\n while (node && node !== endNode) {\n node = node.nextSibling;\n }\n\n return node === endNode;\n }\n\n let start;\n let end = {\n node: startNode,\n position: 0,\n };\n // Is supposed that open and close tags has the same length.\n const tagLength = latexTags.open.length;\n do {\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\n\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\n return null;\n }\n\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\n\n if (end == null) {\n return null;\n }\n\n end.position += tagLength;\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\n\n // Isolating latex.\n let latex;\n\n if (start.node === end.node) {\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\n } else {\n const index = start.position + tagLength;\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\n let currentNode = start.node;\n\n do {\n currentNode = currentNode.nextSibling;\n if (currentNode === end.node) {\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\n } else {\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\n }\n } while (currentNode !== end.node);\n }\n\n return {\n latex,\n startNode: start.node,\n startPosition: start.position,\n endNode: end.node,\n endPosition: end.position,\n };\n }\n}\n\n/**\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\n * @type {Cache}\n * @static\n */\nLatex._cache = new TextCache();\n","import translations from \"../lang/strings.json\";\n/**\n * This class represents a string manager. It's used to load localized strings.\n */\nexport default class StringManager {\n constructor() {\n throw new Error(\"Static class StringManager can not be instantiated.\");\n }\n\n /**\n * Returns the associated value of certain string key. If the associated value\n * doesn't exits returns the original key.\n * @param {string} key - string key\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\n * @returns {string} correspondent value. If doesn't exists original key.\n */\n static get(key, lang) {\n // Default language definition\n let { language } = this;\n\n // If parameter language, use it\n if (lang) {\n language = lang;\n }\n\n // Cut down on strings. e.g. en_US -> en\n if (language && language.length > 2) {\n language = language.slice(0, 2);\n }\n\n // Check if we support the language\n if (!this.strings.hasOwnProperty(language)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown language ${language} set in StringManager.`);\n language = \"en\";\n }\n\n // Check if the key is supported in the given language\n if (!this.strings[language].hasOwnProperty(key)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\n return key;\n }\n\n return this.strings[language][key];\n }\n}\n\n/**\n * Dictionary of dictionaries:\n * Key: language code\n * Value: Key: id of the string\n * Value: translation of the string\n */\nStringManager.strings = translations;\n\n/**\n * Language of the translations; English by default\n */\nStringManager.language = \"en\";\n","/* eslint-disable no-bitwise */\nimport DOMPurify from \"dompurify\";\nimport MathML from \"./mathml\";\nimport Configuration from \"./configuration\";\nimport Latex from \"./latex\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * This class represents an utility class.\n */\nexport default class Util {\n /**\n * Fires an event in a target.\n * @param {EventTarget} eventTarget - target where event should be fired.\n * @param {string} eventName event to fire.\n * @static\n */\n static fireEvent(eventTarget, eventName) {\n if (document.createEvent) {\n const eventObject = document.createEvent(\"HTMLEvents\");\n eventObject.initEvent(eventName, true, true);\n return !eventTarget.dispatchEvent(eventObject);\n }\n\n const eventObject = document.createEventObject();\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\n }\n\n /**\n * Cross-browser addEventListener/attachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - callback function.\n * @static\n */\n static addEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.addEventListener) {\n eventTarget.addEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.attachEvent) {\n // Backwards compatibility.\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * Cross-browser removeEventListener/detachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - function to remove from the event target.\n * @static\n */\n static removeEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.removeEventListener) {\n eventTarget.removeEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.detachEvent) {\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * A map from event target to event handlers so we can remove the event\n * listeners in removeElementEvents\n *\n * @type {Map}\n * @static\n */\n static elementEventsMap = new Map();\n\n /**\n * Adds the a callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\n * @param {Function} mousedownHandler - function to run when on mousedown event.\n * @param {Function} mouseupHandler - function to run when on mouseup event.\n * @static\n */\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\n // Make sure not to leak event listeners if we've already added events to\n // this element\n Util.removeElementEvents(eventTarget);\n\n let entry = {};\n Util.elementEventsMap.set(eventTarget, entry);\n\n if (doubleClickHandler) {\n entry.callbackDblclick = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n doubleClickHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n }\n\n if (mousedownHandler) {\n entry.callbackMousedown = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mousedownHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n }\n\n if (mouseupHandler) {\n entry.callbackMouseup = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mouseupHandler(element, realEvent);\n };\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\n // while the mouse is outside the editor text field.\n // This is a workaround: we trigger the event independently of where the mouse\n // is when we release its button.\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n }\n\n /**\n * Remove all callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @static\n */\n static removeElementEvents(eventTarget) {\n let entry = Util.elementEventsMap.get(eventTarget);\n if (!entry) {\n return;\n }\n\n Util.elementEventsMap.delete(eventTarget);\n\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n\n /**\n * Adds a class name to a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static addClass(element, className) {\n if (!Util.containsClass(element, className)) {\n element.className += ` ${className}`;\n }\n }\n\n /**\n * Checks if a HTMLElement contains a certain class.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the className.\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\n * @static\n */\n static containsClass(element, className) {\n if (element == null || !(\"className\" in element)) {\n return false;\n }\n\n const currentClasses = element.className.split(\" \");\n\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\n if (currentClasses[i] === className) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Remove a certain class for a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static removeClass(element, className) {\n let newClassName = \"\";\n const classes = element.className.split(\" \");\n\n for (let i = 0; i < classes.length; i += 1) {\n if (classes[i] !== className) {\n newClassName += `${classes[i]} `;\n }\n }\n element.className = newClassName.trim();\n }\n\n /**\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\n * used to parse old applets.\n * @param {string} text - string containing safeXml characters\n * @returns {string} a string with safeXml characters parsed.\n * @static\n */\n static convertOldXmlinitialtextAttribute(text) {\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\n // This could be removed in future.\n const val = \"value=\";\n\n const xitpos = text.indexOf(\"xmlinitialtext\");\n const valpos = text.indexOf(val, xitpos);\n const quote = text.charAt(valpos + val.length);\n const startquote = valpos + val.length + 1;\n const endquote = text.indexOf(quote, startquote);\n\n const value = text.substring(startquote, endquote);\n\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\n newvalue = newvalue.split(\"&\").join(\"ยง\");\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\n\n text = text.split(value).join(newvalue);\n return text;\n }\n\n /**\n * Convert a string representation of key-value pairs to an object.\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\n * @returns {Object} - Object containing the key-value pairs\n */\n static convertStringToObject(keyValueString) {\n if (!keyValueString || typeof keyValueString !== \"string\") {\n return {};\n }\n\n return keyValueString\n .split(\",\")\n .map((pair) => pair.trim().split(\"=\"))\n .reduce((resultObject, [key, value]) => {\n if (key && value) {\n resultObject[key] = value;\n }\n return resultObject;\n }, {});\n }\n\n /**\n * Cross-browser solution for creating new elements.\n * @param {string} tagName - tag name of the wished element.\n * @param {Object} attributes - an object where each key is a wished\n * attribute name and each value is its value.\n * @param {Object} [creator] - if supplied, this function will use\n * the \"createElement\" method from this param. Otherwise\n * document will be used as creator.\n * @returns {Element} The DOM element with the specified attributes assigned.\n * @static\n */\n static createElement(tagName, attributes, creator) {\n if (attributes === undefined) {\n attributes = {};\n }\n\n if (creator === undefined) {\n creator = document;\n }\n\n let element;\n\n /*\n * Internet Explorer fix:\n * If you create a new object dynamically, you can't set a non-standard attribute.\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\n * Other browsers will throw an exception and will run the standard code.\n */\n try {\n let html = `<${tagName}`;\n\n Object.keys(attributes).forEach((attributeName) => {\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\n });\n html += \">\";\n element = creator.createElement(html);\n } catch (e) {\n element = creator.createElement(tagName);\n Object.keys(attributes).forEach((attributeName) => {\n element.setAttribute(attributeName, attributes[attributeName]);\n });\n }\n return element;\n }\n\n /**\n * Creates new HTML from it's HTML code as string.\n * @param {string} objectCode - html code\n * @returns {Element} the HTML element.\n * @static\n */\n static createObject(objectCode, creator) {\n if (creator === undefined) {\n creator = document;\n }\n\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\n objectCode = objectCode\n .split(\"\").join(\"\").split(\"\").join(\"\");\n\n objectCode = objectCode\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\n\n const container = Util.createElement(\"div\", {}, creator);\n container.innerHTML = objectCode;\n\n function recursiveParamsFix(object) {\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const param = Util.createElement(\"param\", attributesParsed, creator);\n\n // IE fix.\n if (param.NAME) {\n param.name = param.NAME;\n param.value = param.VALUE;\n }\n\n param.removeAttribute(\"wirisObject\");\n object.parentNode.replaceChild(param, object);\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\n applet.removeAttribute(\"wirisObject\");\n\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\n applet.appendChild(object.childNodes[i]);\n i -= 1; // When we insert the object child into the applet, object loses one child.\n }\n }\n\n object.parentNode.replaceChild(applet, object);\n } else {\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n }\n }\n }\n\n recursiveParamsFix(container);\n return container.firstChild;\n }\n\n /**\n * Converts an Element to its HTML code.\n * @param {Element} element - entry element.\n * @return {string} the HTML code of the input element.\n * @static\n */\n static createObjectCode(element) {\n // In case that the image was not created, the object can be null or undefined.\n if (typeof element === \"undefined\" || element === null) {\n return null;\n }\n\n if (element.nodeType === 1) {\n // ELEMENT_NODE.\n let output = `<${element.tagName}`;\n\n for (let i = 0; i < element.attributes.length; i += 1) {\n if (element.attributes[i].specified) {\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\n }\n }\n\n if (element.childNodes.length > 0) {\n output += \">\";\n\n for (let i = 0; i < element.childNodes.length; i += 1) {\n output += Util.createObject(element.childNodes[i]);\n }\n\n output += ``;\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\n output += `>`;\n } else {\n output += \"/>\";\n }\n\n return output;\n }\n\n if (element.nodeType === 3) {\n // TEXT_NODE.\n return Util.htmlEntities(element.nodeValue);\n }\n\n return \"\";\n }\n\n /**\n * Concatenates two URL paths.\n * @param {string} path1 - first URL path\n * @param {string} path2 - second URL path\n * @returns {string} new URL.\n */\n static concatenateUrl(path1, path2) {\n let separator = \"\";\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\n separator = \"/\";\n }\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\n }\n\n /**\n * Parses a text and replaces all HTML special characters by their correspondent entities.\n * @param {string} input - text to be parsed.\n * @returns {string} the input text with all their special characters replaced by their entities.\n * @static\n */\n static htmlEntities(input) {\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\n }\n\n /**\n * Sanitize HTML to prevent XSS injections.\n * @param {string} html - html to be sanitize.\n * @returns {string} html sanitized.\n * @static\n */\n static htmlSanitize(html) {\n const annotationRegex = /\\/;\n // Get all the annotation content including the tags.\n const annotation = html.match(annotationRegex);\n // Sanitize html code without removing our supported MathML tags and attributes.\n html = DOMPurify.sanitize(html, {\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\n });\n // Readd old annotation content.\n return html.replace(annotationRegex, annotation);\n }\n\n /**\n * Parses a text and replaces all the HTML entities by their characters.\n * @param {string} input - text to be parsed\n * @returns {string} the input text with all their entities replaced by characters.\n * @static\n */\n static htmlEntitiesDecode(input) {\n // Textarea element decodes when inner html is set.\n const textarea = document.createElement(\"textarea\");\n textarea.innerHTML = input;\n return textarea.value;\n }\n\n /**\n * Returns a cross-browser http request.\n * @return {object} httpRequest request object.\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\n */\n static createHttpRequest() {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n if (currentPath.substr(0, 7) === \"file://\") {\n throw StringManager.get(\"exception_cross_site\");\n }\n\n if (typeof XMLHttpRequest !== \"undefined\") {\n return new XMLHttpRequest();\n }\n\n try {\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\n } catch (e) {\n try {\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\n } catch (oc) {\n return null;\n }\n }\n }\n\n /**\n * Converts a hash to a HTTP query.\n * @param {Object[]} properties - a key/value object.\n * @returns {string} a HTTP query containing all the key value pairs with\n * all the special characters replaced by their entities.\n * @static\n */\n static httpBuildQuery(properties) {\n let result = \"\";\n\n Object.keys(properties).forEach((i) => {\n if (properties[i] != null) {\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\n }\n });\n\n // Deleting last '&' empty character.\n if (result.substring(result.length - 1) === \"&\") {\n result = result.substring(0, result.length - 1);\n }\n\n return result;\n }\n\n /**\n * Convert a hash to string sorting keys to get a deterministic output\n * @param {Object[]} hash - a key/value object.\n * @returns {string} a string with the form key1=value1...keyn=valuen\n * @static\n */\n static propertiesToString(hash) {\n // 1. Sort keys. We sort the keys because we want a deterministic output.\n const keys = [];\n Object.keys(hash).forEach((key) => {\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\n keys.push(key);\n }\n });\n\n const n = keys.length;\n for (let i = 0; i < n; i += 1) {\n for (let j = i + 1; j < n; j += 1) {\n const s1 = keys[i];\n const s2 = keys[j];\n if (Util.compareStrings(s1, s2) > 0) {\n // Swap.\n keys[i] = s2;\n keys[j] = s1;\n }\n }\n }\n\n // 2. Generate output.\n let output = \"\";\n for (let i = 0; i < n; i += 1) {\n const key = keys[i];\n output += key;\n output += \"=\";\n let value = hash[key];\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\n value = value.replace(\"\\n\", \"\\\\n\");\n value = value.replace(\"\\r\", \"\\\\r\");\n value = value.replace(\"\\t\", \"\\\\t\");\n\n output += value;\n output += \"\\n\";\n }\n return output;\n }\n\n /**\n * Compare two strings using charCodeAt method\n * @param {string} a - first string to compare.\n * @param {string} b - second string to compare.\n * @returns {number} the difference between a and b\n * @static\n */\n static compareStrings(a, b) {\n let i;\n const an = a.length;\n const bn = b.length;\n const n = an > bn ? bn : an;\n for (i = 0; i < n; i += 1) {\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\n if (c !== 0) {\n return c;\n }\n }\n return a.length - b.length;\n }\n\n /**\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\n * @param {string} string - input string\n * @param {number} idx - an integer greater than or equal to 0\n * and less than the length of the string\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\n * @static\n */\n static fixedCharCodeAt(string, idx) {\n idx = idx || 0;\n const code = string.charCodeAt(idx);\n let hi;\n let low;\n\n /* High surrogate (could change last hex to 0xDB7F to treat high\n private surrogates as single characters) */\n\n if (code >= 0xd800 && code <= 0xdbff) {\n hi = code;\n low = string.charCodeAt(idx + 1);\n if (Number.isNaN(low)) {\n throw StringManager.get(\"exception_high_surrogate\");\n }\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\n }\n\n if (code >= 0xdc00 && code <= 0xdfff) {\n // Low surrogate.\n /* We return false to allow loops to skip this iteration since should have\n already handled high surrogate above in the previous iteration. */\n return false;\n }\n return code;\n }\n\n /**\n * Returns an URL with it's query params converted into array.\n * @param {string} url - input URL.\n * @returns {Object[]} an array containing all URL query params.\n * @static\n */\n static urlToAssArray(url) {\n let i;\n i = url.indexOf(\"?\");\n if (i > 0) {\n const query = url.substring(i + 1);\n const ss = query.split(\"&\");\n const h = {};\n for (i = 0; i < ss.length; i += 1) {\n const s = ss[i];\n const kv = s.split(\"=\");\n if (kv.length > 1) {\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\n }\n }\n return h;\n }\n return {};\n }\n\n /**\n * Returns an encoded URL by replacing each instance of certain characters by\n * one, two, three or four escape sequences using encodeURIComponent method.\n * !'()* . will not be encoded.\n *\n * @param {string} clearString - URL string to be encoded\n * @returns {string} URL with it's special characters replaced.\n * @static\n */\n static urlEncode(clearString) {\n let output = \"\";\n // Method encodeURIComponent doesn't encode !'()*~ .\n output = encodeURIComponent(clearString);\n return output;\n }\n\n // TODO: To parser?\n /**\n * Converts the HTML of a image into the output code that WIRIS must return.\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\n * or the Wiriscas attribute of a WIRIS applet.\n * @param {string} imgCode - the html code from a formula or a CAS image.\n * @param {boolean} convertToXml - true if the image should be converted to XML.\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\n * @returns {string} the XML or safeXML of a WIRIS image.\n * @static\n */\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\n const imgObject = Util.createObject(imgCode);\n if (imgObject) {\n if (\n imgObject.className === Configuration.get(\"imageClassName\") ||\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\n ) {\n if (!convertToXml) {\n return imgCode;\n }\n\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n // To handle annotations, first we need the MathML in XML.\n let mathML = MathML.safeXmlDecode(dataMathML);\n\n if (!Configuration.get(\"saveHandTraces\")) {\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\n }\n\n if (mathML == null) {\n mathML = imgObject.getAttribute(\"alt\");\n }\n\n if (convertToSafeXml) {\n const safeMathML = MathML.safeXmlEncode(mathML);\n return safeMathML;\n }\n\n return mathML;\n }\n }\n return imgCode;\n }\n\n /**\n * Gets the node length in characters.\n * @param {Node} node - HTML node.\n * @returns {number} node length.\n * @static\n */\n static getNodeLength(node) {\n const staticNodeLengths = {\n IMG: 1,\n BR: 1,\n };\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return node.nodeValue.length;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\n\n if (length === undefined) {\n length = 0;\n }\n\n for (let i = 0; i < node.childNodes.length; i += 1) {\n length += Util.getNodeLength(node.childNodes[i]);\n }\n return length;\n }\n return 0;\n }\n\n /**\n * Gets a selected node or text from an editable HTMLElement.\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\n * @param {HTMLElement} target - the editable HTMLElement.\n * @param {boolean} isIframe - specifies if the target is an iframe or not\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\n * the current selection and uses window.getSelection()\n * @returns {object} an object with the 'node' key set if the item is an\n * element or the keys 'node' and 'caretPosition' if the element is text.\n * @static\n */\n static getSelectedItem(target, isIframe, forceGetSelection) {\n let windowTarget;\n\n if (isIframe) {\n windowTarget = target.contentWindow;\n windowTarget.focus();\n } else {\n windowTarget = window;\n target.focus();\n }\n\n if (document.selection && !forceGetSelection) {\n const range = windowTarget.document.selection.createRange();\n\n if (range.parentElement) {\n if (range.htmlText.length > 0) {\n if (range.text.length === 0) {\n return Util.getSelectedItem(target, isIframe, true);\n }\n\n return null;\n }\n\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\n let temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\n // IE9 fix: parentElement() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML('');\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\n }\n\n let node;\n let caretPosition;\n\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\n // TEXT_NODE.\n node = temporalObject.nextSibling;\n caretPosition = 0;\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\n node = temporalObject.previousSibling;\n caretPosition = node.nodeValue.length;\n } else {\n node = windowTarget.document.createTextNode(\"\");\n temporalObject.parentNode.insertBefore(node, temporalObject);\n caretPosition = 0;\n }\n\n temporalObject.parentNode.removeChild(temporalObject);\n\n return {\n node,\n caretPosition,\n };\n }\n\n if (range.length > 1) {\n return null;\n }\n\n return {\n node: range.item(0),\n };\n }\n\n if (windowTarget.getSelection) {\n let range;\n const selection = windowTarget.getSelection();\n\n try {\n range = selection.getRangeAt(0);\n } catch (e) {\n range = windowTarget.document.createRange();\n }\n\n const node = range.startContainer;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return {\n node,\n caretPosition: range.startOffset,\n };\n }\n\n if (node !== range.endContainer) {\n return null;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n const position = range.startOffset;\n\n if (node.childNodes[position]) {\n // In case that a formula is detected but not selected,\n // we create an empty span where we could insert the new formula.\n if (range.startOffset === range.endOffset) {\n if (\n position !== 0 &&\n node.childNodes[position - 1].localName === \"span\" &&\n node.childNodes[position].classList?.contains(\"Wirisformula\")\n ) {\n node.childNodes[position - 1].remove();\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\n }\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\n if (\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\n position === 0\n ) {\n const emptySpan = document.createElement(\"span\");\n node.insertBefore(emptySpan, node.childNodes[position]);\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n\n return null;\n }\n\n /**\n * Returns null if there isn't any item or if it is malformed.\n * Otherwise returns an object containing the node with the MathML image\n * and the cursor position inside the textarea.\n * @param {HTMLTextAreaElement} textarea - textarea element.\n * @returns {Object} An object containing the node, the index of the\n * beginning of the selected text, caret position and the start and end position of the\n * text node.\n * @static\n */\n static getSelectedItemOnTextarea(textarea) {\n const textNode = document.createTextNode(textarea.value);\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\n if (textNodeValues === null) {\n return null;\n }\n\n return {\n node: textNode,\n caretPosition: textarea.selectionStart,\n startPosition: textNodeValues.startPosition,\n endPosition: textNodeValues.endPosition,\n };\n }\n\n /**\n * Looks for elements that match the given name in a HTML code string.\n * Important: this function is very concrete for WIRIS code.\n * It takes as preconditions lots of behaviors that are not the general case.\n * @param {string} code - HTML code.\n * @param {string} name - element name.\n * @param {boolean} autoClosed - true if the elements are autoClosed.\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\n * @static\n */\n static getElementsByNameFromString(code, name, autoClosed) {\n const elements = [];\n code = code.toLowerCase();\n name = name.toLowerCase();\n let start = code.indexOf(`<${name} `);\n\n while (start !== -1) {\n // Look for nodes.\n let endString;\n\n if (autoClosed) {\n endString = \">\";\n } else {\n endString = ``;\n }\n\n let end = code.indexOf(endString, start);\n\n if (end !== -1) {\n end += endString.length;\n elements.push({\n start,\n end,\n });\n } else {\n end = start + 1;\n }\n\n start = code.indexOf(`<${name} `, end);\n }\n\n return elements;\n }\n\n /**\n * Returns the numeric value of a base64 character.\n * @param {string} character - base64 character.\n * @returns {number} base64 character numeric value.\n * @static\n */\n static decode64(character) {\n const PLUS = \"+\".charCodeAt(0);\n const SLASH = \"/\".charCodeAt(0);\n const NUMBER = \"0\".charCodeAt(0);\n const LOWER = \"a\".charCodeAt(0);\n const UPPER = \"A\".charCodeAt(0);\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\n const code = character.charCodeAt(0);\n\n if (code === PLUS || code === PLUS_URL_SAFE) {\n return 62; // Char '+'.\n }\n if (code === SLASH || code === SLASH_URL_SAFE) {\n return 63; // Char '/'.\n }\n if (code < NUMBER) {\n return -1; // No match.\n }\n if (code < NUMBER + 10) {\n return code - NUMBER + 26 + 26;\n }\n if (code < UPPER + 26) {\n return code - UPPER;\n }\n if (code < LOWER + 26) {\n return code - LOWER + 26;\n }\n\n return null;\n }\n\n /**\n * Converts a base64 string to a array of bytes.\n * @param {string} b64String - base64 string.\n * @param {number} length - dimension of byte array (by default whole string).\n * @return {Object[]} the resultant byte array.\n * @static\n */\n static b64ToByteArray(b64String, length) {\n let tmp;\n\n if (b64String.length % 4 > 0) {\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\n }\n\n const arr = [];\n\n let l;\n let placeHolders;\n if (!length) {\n // All b64String string.\n if (b64String.charAt(b64String.length - 2) === \"=\") {\n placeHolders = 2;\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\n placeHolders = 1;\n } else {\n placeHolders = 0;\n }\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\n } else {\n l = length;\n }\n\n let i;\n for (i = 0; i < l; i += 4) {\n // Ignoring code checker standards (bitewise operators).\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 18) |\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\n Util.decode64(b64String.charAt(i + 3));\n\n arr.push((tmp >> 16) & 0xff);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n\n if (placeHolders) {\n if (placeHolders === 2) {\n // Ignoring code checker standards (bitewise operators).\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\n arr.push(tmp & 0xff);\n } else if (placeHolders === 1) {\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 10) |\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n }\n return arr;\n }\n\n /**\n * Returns the first 32-bit signed integer from a byte array.\n * @param {Object[]} bytes - array of bytes.\n * @returns {number} the 32-bit signed integer.\n * @static\n */\n static readInt32(bytes) {\n if (bytes.length < 4) {\n return false;\n }\n const int32 = bytes.splice(0, 4);\n // @codingStandardsIgnoreStartยก\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read the first byte from a byte array.\n * @param {Object} bytes - input byte array.\n * @returns {number} first byte of the byte array.\n * @static\n */\n static readByte(bytes) {\n // @codingStandardsIgnoreStart\n return bytes.shift() << 0;\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\n * @param {Object[]} bytes - byte array.\n * @param {number} pos - start position.\n * @param {number} len - number of bytes to read.\n * @returns {Object[]} the byte array.\n * @static\n */\n static readBytes(bytes, pos, len) {\n return bytes.splice(pos, len);\n }\n\n /**\n * Inserts or modifies formulas or CAS on a textarea.\n * @param {HTMLTextAreaElement} textarea - textarea target.\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\n * call this function as (textarea, Parser.createImageSrc(mathml));\n * @static\n */\n static updateTextArea(textarea, text) {\n if (textarea && text) {\n textarea.focus();\n\n if (textarea.selectionStart != null) {\n const { selectionEnd } = textarea;\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\n textarea.value = selectionStart + text + selectionEndSub;\n textarea.selectionEnd = selectionEnd + text.length;\n } else {\n const selection = document.selection.createRange();\n selection.text = text;\n }\n }\n }\n\n /**\n * Modifies existing formula on a textarea.\n * @param {HTMLTextAreaElement} textarea - text area target.\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\n * to the image,you can call this function as\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\n * @param {number} end - ending index from textarea where it needs to be replaced by text\n * @static\n */\n static updateExistingTextOnTextarea(textarea, text, start, end) {\n textarea.focus();\n const textareaStart = textarea.value.substring(0, start);\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\n textarea.selectionEnd = start + text.length;\n }\n\n /**\n * Add a parameter with it's correspondent value to an URL (GET).\n * @param {string} path - URL path\n * @param {string} parameter - parameter\n * @param {string} value - value\n * @static\n */\n static addArgument(path, parameter, value) {\n let sep;\n if (path.indexOf(\"?\") > 0) {\n sep = \"&\";\n } else {\n sep = \"?\";\n }\n return `${path + sep + parameter}=${value}`;\n }\n}\n","import Configuration from \"./configuration\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents MathType Image class. Contains all the logic related\n * to MathType images manipulation.\n * All MathType images are generated using the appropriate MathType\n * integration service: showimage or createimage.\n *\n * There are two available image formats:\n * - svg (default)\n * - png\n *\n * There are two formats for the image src attribute:\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\n * - A link to the showimage service.\n */\nexport default class Image {\n /**\n * Removes data attributes from an image.\n * @param {HTMLImageElement} img - Image where remove data attributes.\n */\n static removeImgDataAttributes(img) {\n const attributesToRemove = [];\n const { attributes } = img;\n\n Object.keys(attributes).forEach((key) => {\n const attribute = attributes[key];\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\n // Is preferred keep an array and remove after the search\n // because when attribute is removed the array of attributes\n // is modified.\n attributesToRemove.push(attribute.name);\n }\n });\n\n attributesToRemove.forEach((attribute) => {\n img.removeAttribute(attribute);\n });\n }\n\n /**\n * @static\n * Clones all MathType image attributes from a HTMLImageElement to another.\n * @param {HTMLImageElement} originImg - The original image.\n * @param {HTMLImageElement} destImg - The destination image.\n */\n static clone(originImg, destImg) {\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (!originImg.hasAttribute(customEditorAttributeName)) {\n destImg.removeAttribute(customEditorAttributeName);\n }\n\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\n const imgAttributes = [\n mathmlAttributeName,\n customEditorAttributeName,\n \"alt\",\n \"height\",\n \"width\",\n \"style\",\n \"src\",\n \"role\",\n ];\n\n imgAttributes.forEach((iterator) => {\n const originAttribute = originImg.getAttribute(iterator);\n if (originAttribute) {\n destImg.setAttribute(iterator, originAttribute);\n }\n });\n }\n\n /**\n * Determines whether an img src contains an SVG.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src contains an SVG, false otherwise\n */\n static isSvg(img) {\n return img.src.startsWith(\"data:image/svg+xml;\");\n }\n\n /**\n * Determines whether an img src is encoded in base64 or not.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src is encoded in base64, false otherwise\n */\n static isBase64(img) {\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\n }\n\n /**\n * Calculates the metrics of a MathType image given the the service response and the image format.\n * @param {HTMLImageElement} img - The HTMLImageElement.\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\n * @param {Boolean} jsonResponse - True the response of the image service is a\n * JSON object. False otherwise.\n */\n static setImgSize(img, uri, jsonResponse) {\n let ar;\n let base64String;\n let bytes;\n let svgString;\n if (jsonResponse) {\n // Cleaning data:image/png;base64.\n if (Image.isSvg(img)) {\n // SVG format.\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\n if (!Image.isBase64(img)) {\n ar = Image.getMetricsFromSvgString(uri);\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n svgString = \"\";\n bytes = Util.b64ToByteArray(base64String, base64String.length);\n for (let i = 0; i < bytes.length; i += 1) {\n svgString += String.fromCharCode(bytes[i]);\n }\n ar = Image.getMetricsFromSvgString(svgString);\n }\n // PNG format: we store all metrics information in the first 88 bytes.\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n bytes = Util.b64ToByteArray(base64String, 88);\n ar = Image.getMetricsFromBytes(bytes);\n }\n // Backwards compatibility: we store the metrics into createimage response.\n } else {\n ar = Util.urlToAssArray(uri);\n }\n let width = ar.cw;\n if (!width) {\n return;\n }\n let height = ar.ch;\n let baseline = ar.cb;\n const { dpi } = ar;\n if (dpi) {\n width = (width * 96) / dpi;\n height = (height * 96) / dpi;\n baseline = (baseline * 96) / dpi;\n }\n img.width = width;\n img.height = height;\n img.style.verticalAlign = `-${height - baseline}px`;\n }\n\n /**\n * Calculates the metrics of an image which has been resized. Is used to restore the original\n * metrics of a resized image.\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\n */\n static fixAfterResize(img) {\n img.removeAttribute(\"style\");\n img.removeAttribute(\"width\");\n img.removeAttribute(\"height\");\n // In order to avoid resize with max-width css property.\n img.style.maxWidth = \"none\";\n\n const processImg = (img) => {\n if (img.src.indexOf(\"data:image\") !== -1) {\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\n // (which would actually make more sense for readibility and efficiency).\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\n // 'data:image/svg+xml;base64,'.length === 26\n const base64String = img.getAttribute(\"src\").substring(26);\n const svgString = window.atob(base64String);\n const encodedSvgString = encodeURIComponent(svgString);\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n // Return src to base64!\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\n } else {\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n }\n } else {\n // 'data:image/png;base64,' === 22.\n const base64 = img.src.substring(22, img.src.length);\n Image.setImgSize(img, base64, true);\n }\n } else {\n Image.setImgSize(img, img.src);\n }\n };\n\n // If the image doesn't contain a blob, just process it normally\n if (img.src.indexOf(\"blob:\") === -1) {\n processImg(img);\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\n } else {\n const reader = new FileReader();\n reader.onload = function () {\n img.setAttribute(\"src\", reader.result);\n processImg(img);\n };\n fetch(img.src)\n .then((r) => r.blob())\n .then((blob) => {\n reader.readAsDataURL(blob);\n });\n }\n }\n\n /**\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\n * by the MathType image service. This image contains as an extra custom attribute:\n * the baseline (wrs:baseline).\n * @param {String} svgString - The SVG image.\n * @return {Array} - The image metrics.\n */\n static getMetricsFromSvgString(svgString) {\n let first = svgString.indexOf('height=\"');\n let last = svgString.indexOf('\"', first + 8, svgString.length);\n const height = svgString.substring(first + 8, last);\n\n first = svgString.indexOf('width=\"');\n last = svgString.indexOf('\"', first + 7, svgString.length);\n const width = svgString.substring(first + 7, last);\n\n first = svgString.indexOf('wrs:baseline=\"');\n last = svgString.indexOf('\"', first + 14, svgString.length);\n const baseline = svgString.substring(first + 14, last);\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n if (typeof baseline !== \"undefined\") {\n arr.cb = baseline;\n }\n return arr;\n }\n return [];\n }\n\n /**\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\n * @param {Array.} bytes - png byte array.\n * @return {Array} The png metrics.\n */\n static getMetricsFromBytes(bytes) {\n Util.readBytes(bytes, 0, 8);\n let width;\n let height;\n let typ;\n let baseline;\n let dpi;\n while (bytes.length >= 4) {\n typ = Util.readInt32(bytes);\n if (typ === 0x49484452) {\n width = Util.readInt32(bytes);\n height = Util.readInt32(bytes);\n // Read 5 bytes.\n Util.readInt32(bytes);\n Util.readByte(bytes);\n } else if (typ === 0x62615345) {\n // Baseline: 'baSE'.\n baseline = Util.readInt32(bytes);\n } else if (typ === 0x70485973) {\n // Dpis: 'pHYs'.\n dpi = Util.readInt32(bytes);\n dpi = Math.round(dpi / 39.37);\n Util.readInt32(bytes);\n Util.readByte(bytes);\n }\n Util.readInt32(bytes);\n }\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n arr.dpi = dpi;\n if (baseline) {\n arr.cb = baseline;\n }\n\n return arr;\n }\n return [];\n }\n}\n","import TextCache from \"./textcache\";\nimport ServiceProvider from \"./serviceprovider\";\nimport MathML from \"./mathml\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * @classdesc\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\n * the associated client-side cache.\n */\nexport default class Accessibility {\n /**\n * Static property.\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\n * @type {TextCache}\n */\n static get cache() {\n return Accessibility._cache;\n }\n\n /**\n * Static property setter.\n * Set accessibility cache.\n * @param {TextCahe} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Accessibility._cache = value;\n }\n\n /**\n * Converts MathML strings to its accessible text representation.\n * @param {String} mathML - MathML to be converted to accessible text.\n * @param {String} [language] - Language of the accessible text. 'en' by default.\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\n * @return {String} Accessibility text.\n */\n static mathMLToAccessible(mathML, language, data) {\n if (typeof language === \"undefined\") {\n language = \"en\";\n }\n // Check MathML class. If the class is chemistry,\n // we add chemistry to data to force accessibility service\n // to load chemistry grammar.\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\n data.mode = \"chemistry\";\n }\n // Ignore accesibility styles\n data.ignoreStyles = true;\n let accessibleText = \"\";\n\n if (Accessibility.cache.get(mathML)) {\n accessibleText = Accessibility.cache.get(mathML);\n } else {\n data.service = \"mathml2accessible\";\n data.lang = language;\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n if (accessibleJsonResponse.status !== \"error\") {\n accessibleText = accessibleJsonResponse.result.text;\n Accessibility.cache.populate(mathML, accessibleText);\n } else {\n accessibleText = StringManager.get(\"error_convert_accessibility\");\n }\n }\n\n return accessibleText;\n }\n}\n\n/**\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\n * @private\n * @type {TextCache}\n */\nAccessibility._cache = new TextCache();\n","import Util from \"./util\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport Image from \"./image\";\nimport Accessibility from \"./accessibility\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Configuration from \"./configuration\";\nimport Constants from \"./constants\";\n// eslint-disable-next-line no-unused-vars\nimport md5 from \"./md5\";\n\n/**\n * @classdesc\n * This class represent a MahML parser. Converts MathML into formulas depending on the\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\n * in the backend.\n */\nexport default class Parser {\n /**\n * Converts a MathML string to an img element.\n * @param {Document} creator - Document object to call createElement method.\n * @param {string} mathml - MathML code\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\n * @param {language} language - custom language for accessibility.\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\n * @static\n */\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\n const imgObject = creator.createElement(\"img\");\n imgObject.align = \"middle\";\n imgObject.style.maxWidth = \"none\";\n let data = wirisProperties || {};\n\n // Take into account the backend config\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\n data = { ...wirisEditorProperties, ...data };\n\n data.mml = mathml;\n data.lang = language;\n // Request metrics of the generated image.\n data.metrics = \"true\";\n data.centerbaseline = \"false\";\n\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n // Render js params: _wrs_int_wirisProperties contains some js render params.\n // Since MathML can support render params, js params should be send only to editor.\n\n imgObject.className = Configuration.get(\"imageClassName\");\n\n if (mathml.indexOf('class=\"') !== -1) {\n // We check here if the MathML has been created from a customEditor (such chemistry)\n // to add custom editor name attribute to img object (if necessary).\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\n }\n\n // Performance enabled.\n if (\n Configuration.get(\"wirisPluginPerformance\") &&\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\n ) {\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\n if (result.status === \"warning\") {\n // POST call.\n // if the mathml is malformed, this function will throw an exception.\n try {\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\n } catch (e) {\n return null;\n }\n }\n ({ result } = result);\n if (result.format === \"png\") {\n imgObject.src = `data:image/png;base64,${result.content}`;\n } else {\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\n }\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n Image.setImgSize(imgObject, result.content, true);\n\n if (Configuration.get(\"enableAccessibility\")) {\n if (typeof result.alt === \"undefined\") {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n } else {\n imgObject.alt = result.alt;\n }\n }\n } else {\n const result = Parser.createImageSrc(mathml, data);\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n imgObject.src = result;\n Image.setImgSize(\n imgObject,\n result,\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\n );\n if (Configuration.get(\"enableAccessibility\")) {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n }\n }\n\n if (typeof Parser.observer !== \"undefined\") {\n Parser.observer.observe(imgObject);\n }\n\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\n imgObject.setAttribute(\"role\", \"math\");\n return imgObject;\n }\n\n /**\n * Returns the source to showimage service by calling createimage service. The\n * output of the createimage service is a URL path pointing to showimage service.\n * This method is called when performance is disabled.\n * @param {string} mathml - MathML code.\n * @param {Object[]} data - data object containing service parameters.\n * @returns {string} the showimage path.\n */\n static createImageSrc(mathml, data) {\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n let result = ServiceProvider.getService(\"createimage\", data);\n\n if (result.indexOf(\"@BASE@\") !== -1) {\n // Replacing '@BASE@' with the base URL of createimage.\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\n baseParts.pop();\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\n }\n\n return result;\n }\n\n /**\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\n * this data would be converted as following:\n *
\n   * MathML code: Image containing the corresponding MathML formulas.\n   * MathML code with LaTeX annotation : LaTeX string.\n   * 
\n * @param {string} code - HTML code containing MathML data.\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\n */\n static initParse(code, language) {\n /* Note: The code inside this function has been inverted.\n If you invert again the code then you cannot use correctly LaTeX\n in Moodle.\n */\n code = Parser.initParseSaveMode(code, language);\n return Parser.initParseEditMode(code);\n }\n\n /**\n * Parses initial HTML code depending on the save mode. Transforms all MathML\n * occurrences for it's correspondent image or LaTeX.\n * @param {string} code - HTML code to be parsed\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code parsed.\n */\n static initParseSaveMode(code, language) {\n if (Configuration.get(\"saveMode\")) {\n // Converting XML to tags.\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\n code = Parser.codeImgTransform(code, \"base642showimage\");\n }\n }\n return code;\n }\n\n /**\n * Parses initial HTML code depending on the edit mode.\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\n * be converted into a LaTeX string instead of an image.\n * @param {string} code - HTML code containing MathML.\n * @returns {string} parsed HTML code.\n */\n static initParseEditMode(code) {\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\n const token = 'encoding=\"LaTeX\">';\n // While replacing images with latex, the indexes of the found images changes\n // respecting the original code, so this carry is needed.\n let carry = 0;\n\n for (let i = 0; i < imgList.length; i += 1) {\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\n\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\n\n if (mathmlStart === -1) {\n mathmlStartToken = ' alt=\"';\n mathmlStart = imgCode.indexOf(mathmlStartToken);\n }\n\n if (mathmlStart !== -1) {\n mathmlStart += mathmlStartToken.length;\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\n let latexStartPosition = mathml.indexOf(token);\n\n if (latexStartPosition !== -1) {\n latexStartPosition += token.length;\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\n\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\n const start = code.substring(0, imgList[i].start + carry);\n const end = code.substring(imgList[i].end + carry);\n code = start + replaceText + end;\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\n }\n }\n }\n }\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code. The end HTML code is HTML code with embedded images\n * or LaTeX formulas created with MathType.
\n * By default this method converts the formula images and LaTeX strings in MathML.
\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\n * @param {string} code - HTML to be parsed\n * @returns {string} the HTML code parsed.\n */\n static endParse(code) {\n // Transform LaTeX ocurrences to MathML elements.\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\n // Transform img elements to MathML elements.\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\n return codeEndParseSaveMode;\n }\n\n /**\n * Parses end HTML code depending on the edit mode.\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\n * @param {string} code - HTML code to be parsed.\n * @returns {string} HTML code parsed.\n */\n static endParseEditMode(code) {\n // Converting LaTeX to images.\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n let output = \"\";\n let endPosition = 0;\n let startPosition = code.indexOf(\"$$\");\n while (startPosition !== -1) {\n output += code.substring(endPosition, startPosition);\n endPosition = code.indexOf(\"$$\", startPosition + 2);\n\n if (endPosition !== -1) {\n // Before, it was a condition here to execute the next codelines\n // 'latex.indexOf('<') == -1'.\n // We don't know why it was used, but seems to have a conflict with\n // latex formulas that contains '<'.\n const latex = code.substring(startPosition + 2, endPosition);\n const decodedLatex = Util.htmlEntitiesDecode(latex);\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\n if (!Configuration.get(\"saveHandTraces\")) {\n // Remove hand traces.\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\n }\n output += mathml;\n endPosition += 2;\n } else {\n output += \"$$\";\n endPosition = startPosition + 2;\n }\n\n startPosition = code.indexOf(\"$$\", endPosition);\n }\n\n output += code.substring(endPosition, code.length);\n code = output;\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code depending on the save mode. Converts all\n * images into the element determined by the save mode:\n * - xml: Parses images formulas into MathML.\n * - safeXml: Parses images formulas into safeMAthML\n * - base64: Parses images into base64 images.\n * - image: Parse images into images (no parsing)\n * @param {string} code - HTML code to be parsed\n * @returns {string} HTML code parsed.\n */\n static endParseSaveMode(code) {\n const savemode = Configuration.get(\"saveMode\");\n const base64savemode = Configuration.get(\"base64savemode\");\n\n if (savemode) {\n if (savemode === \"safeXml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"xml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\n code = Parser.codeImgTransform(code, \"img264\");\n }\n }\n\n return code;\n }\n\n /**\n * Auxiliar function that builds the data object to send to the showimage endpoint\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object with the data to send to showimage.\n */\n static createShowImageSrcData(data, language) {\n const dataMd5 = {};\n const renderParams = [\n \"mml\",\n \"color\",\n \"centerbaseline\",\n \"zoom\",\n \"dpi\",\n \"fontSize\",\n \"fontFamily\",\n \"defaultStretchy\",\n \"backgroundColor\",\n \"format\",\n ];\n renderParams.forEach((param) => {\n if (typeof data[param] !== \"undefined\") {\n dataMd5[param] = data[param];\n }\n });\n // Data variables to get.\n const dataObject = {};\n Object.keys(data).forEach((key) => {\n // We don't need mathml in this request we try to get cached.\n // Only need the formula md5 calculated before.\n if (key !== \"mml\") {\n dataObject[key] = data[key];\n }\n });\n\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\n dataObject.version = Configuration.get(\"version\");\n\n return dataObject;\n }\n\n /**\n * Returns the result to call showimage service with the formula md5 as parameter.\n * The result could be:\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object containing showimage response.\n */\n static createShowImageSrc(data, language) {\n const dataObject = this.createShowImageSrcData(data, language);\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\n return result;\n }\n\n /**\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\n * or showimage img tags (i.e with showimage.php on src)\n * @param {string} code - HTML code\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\n * @returns {string} html - code transformed.\n */\n static codeImgTransform(code, mode) {\n let output = \"\";\n let endPosition = 0;\n const pattern = /\") {\n endPosition = i + 1;\n }\n\n i += 1;\n }\n\n if (endPosition < startPosition) {\n // The img tag is stripped.\n output += code.substring(startPosition, code.length);\n return output;\n }\n let imgCode = code.substring(startPosition, endPosition);\n const imgObject = Util.createObject(imgCode);\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n let convertToXml;\n let convertToSafeXml;\n\n if (mode === \"base642showimage\") {\n if (xmlCode == null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\n output += Util.createObjectCode(imgCode);\n } else if (mode === \"img2mathml\") {\n if (Configuration.get(\"saveMode\")) {\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\n convertToXml = true;\n convertToSafeXml = true;\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\n convertToXml = true;\n convertToSafeXml = false;\n }\n }\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\n } else if (mode === \"img264\") {\n if (xmlCode === null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n\n const properties = {};\n properties.base64 = \"true\";\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\n // Metrics.\n Image.setImgSize(imgCode, imgCode.src, true);\n output += Util.createObjectCode(imgCode);\n }\n }\n output += code.substring(endPosition, code.length);\n return output;\n }\n\n /**\n * Converts all occurrences of MathML to the corresponding image.\n * @param {string} content - string with valid MathML code.\n * The MathML code doesn't contain semantics.\n * @param {Constants} characters - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * @param {string} language - a valid language code\n * in order to generate formula accessibility.\n * @returns {string} The input string with all the MathML\n * occurrences replaced by the corresponding image.\n */\n static parseMathmlToImg(content, characters, language) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n\n while (start !== -1) {\n output += content.substring(end, start);\n // Avoid WIRIS images to be parsed.\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else if (imageMathmlAtrribute !== -1) {\n // First close tag of img attribute\n // If a mathmlAttribute exists should be inside a img tag.\n end += content.indexOf(\"/>\", start);\n } else {\n end += mathTagEnd.length;\n }\n\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\n let mathml = content.substring(start, end);\n mathml =\n characters.id === Constants.safeXmlCharacters.id\n ? MathML.safeXmlDecode(mathml)\n : MathML.mathMLEntities(mathml);\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\n } else {\n output += content.substring(start, end);\n }\n\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n}\n\n// Mutation observers to avoid wiris image formulas class be removed.\nif (typeof MutationObserver !== \"undefined\") {\n const mutationObserver = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\n mutation.attributeName === \"class\" &&\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\n ) {\n mutation.target.className = Configuration.get(\"imageClassName\");\n }\n });\n });\n\n Parser.observer = Object.create(mutationObserver);\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\n // We use own default config.\n Parser.observer.observe = function (target) {\n Object.getPrototypeOf(this).observe(target, this.Config);\n };\n}\n","/* eslint-disable class-methods-use-this */\n/* eslint-disable no-unused-vars */\n/* eslint-disable no-extra-semi */\n\n// The rules above are disabled because we are implementing\n// an external interface.\n\nexport default class EditorListener {\n /**\n * @classdesc\n * Determines if the content of the\n * MathType Editor has changes.\n * @implements {EditorListeners}\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the content of the editor has changed.\n * @type {Boolean}\n */\n this.isContentChanged = false;\n\n /**\n * Indicates if the listener should be waiting for changes in the editor.\n * @type {Boolean}\n */\n this.waitingForChanges = false;\n }\n\n /**\n * Sets {@link EditorListener.isContentChanged} property.\n * @param {Boolean} value - The new vlue.\n */\n setIsContentChanged(value) {\n this.isContentChanged = value;\n }\n\n /**\n * Returns true if the content of the editor has been changed, false otherwise.\n * @return {Boolean}\n */\n getIsContentChanged() {\n return this.isContentChanged;\n }\n\n /**\n * Determines if the EditorListener should wait for any changes.\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\n */\n setWaitingForChanges(value) {\n this.waitingForChanges = value;\n }\n\n /**\n * EditorListener method to overwrite.\n * @type {JsEditor}\n * @ignore\n */\n caretPositionChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @type {JsEditor}\n * @ignore\n */\n clipboardChanged(_editor) {}\n\n /**\n * Determines if the content of an editor has been changed.\n * @param {JsEditor} editor - editor object.\n */\n contentChanged(_editor) {\n if (this.waitingForChanges === true && this.isContentChanged === false) {\n this.isContentChanged = true;\n }\n }\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} editor - The editor instance.\n */\n styleChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} - The editor instance.\n */\n transformationReceived(_editor) {}\n}\n","let wasm;\n\nconst cachedTextDecoder =\n typeof TextDecoder !== \"undefined\"\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\n : {\n decode: () => {\n throw Error(\"TextDecoder not available\");\n },\n };\n\nif (typeof TextDecoder !== \"undefined\") {\n cachedTextDecoder.decode();\n}\n\nlet cachedUint8Memory0 = null;\n\nfunction getUint8Memory0() {\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachedUint8Memory0;\n}\n\nfunction getStringFromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\n}\n\nconst heap = new Array(128).fill(undefined);\n\nheap.push(undefined, null, true, false);\n\nlet heap_next = heap.length;\n\nfunction addHeapObject(obj) {\n if (heap_next === heap.length) heap.push(heap.length + 1);\n const idx = heap_next;\n heap_next = heap[idx];\n\n heap[idx] = obj;\n return idx;\n}\n\nfunction getObject(idx) {\n return heap[idx];\n}\n\nfunction dropObject(idx) {\n if (idx < 132) return;\n heap[idx] = heap_next;\n heap_next = idx;\n}\n\nfunction takeObject(idx) {\n const ret = getObject(idx);\n dropObject(idx);\n return ret;\n}\n\nlet WASM_VECTOR_LEN = 0;\n\nconst cachedTextEncoder =\n typeof TextEncoder !== \"undefined\"\n ? new TextEncoder(\"utf-8\")\n : {\n encode: () => {\n throw Error(\"TextEncoder not available\");\n },\n };\n\nconst encodeString =\n typeof cachedTextEncoder.encodeInto === \"function\"\n ? function (arg, view) {\n return cachedTextEncoder.encodeInto(arg, view);\n }\n : function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length,\n };\n };\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length, 1) >>> 0;\n getUint8Memory0()\n .subarray(ptr, ptr + buf.length)\n .set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len, 1) >>> 0;\n\n const mem = getUint8Memory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7f) break;\n mem[ptr + offset] = code;\n }\n\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\n const ret = encodeString(arg, view);\n\n offset += ret.written;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction isLikeNone(x) {\n return x === undefined || x === null;\n}\n\nlet cachedInt32Memory0 = null;\n\nfunction getInt32Memory0() {\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\n }\n return cachedInt32Memory0;\n}\n\nlet cachedFloat64Memory0 = null;\n\nfunction getFloat64Memory0() {\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\n }\n return cachedFloat64Memory0;\n}\n\nlet cachedBigInt64Memory0 = null;\n\nfunction getBigInt64Memory0() {\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\n }\n return cachedBigInt64Memory0;\n}\n\nfunction debugString(val) {\n // primitive types\n const type = typeof val;\n if (type == \"number\" || type == \"boolean\" || val == null) {\n return `${val}`;\n }\n if (type == \"string\") {\n return `\"${val}\"`;\n }\n if (type == \"symbol\") {\n const description = val.description;\n if (description == null) {\n return \"Symbol\";\n } else {\n return `Symbol(${description})`;\n }\n }\n if (type == \"function\") {\n const name = val.name;\n if (typeof name == \"string\" && name.length > 0) {\n return `Function(${name})`;\n } else {\n return \"Function\";\n }\n }\n // objects\n if (Array.isArray(val)) {\n const length = val.length;\n let debug = \"[\";\n if (length > 0) {\n debug += debugString(val[0]);\n }\n for (let i = 1; i < length; i++) {\n debug += \", \" + debugString(val[i]);\n }\n debug += \"]\";\n return debug;\n }\n // Test for built-in\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\n let className;\n if (builtInMatches.length > 1) {\n className = builtInMatches[1];\n } else {\n // Failed to match the standard '[object ClassName]'\n return toString.call(val);\n }\n if (className == \"Object\") {\n // we're a user defined class or Object\n // JSON.stringify avoids problems with cycles, and is generally much\n // easier than looping through ownProperties of `val`.\n try {\n return \"Object(\" + JSON.stringify(val) + \")\";\n } catch (_) {\n return \"Object\";\n }\n }\n // errors\n if (val instanceof Error) {\n return `${val.name}: ${val.message}\\n${val.stack}`;\n }\n // TODO we could test for more things here, like `Set`s and `Map`s.\n return className;\n}\n\nfunction makeClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n try {\n return f(state.a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\n state.a = 0;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction makeMutClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n const a = state.a;\n state.a = 0;\n try {\n return f(a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\n } else {\n state.a = a;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_49(arg0, arg1) {\n wasm.__wbindgen_export_4(arg0, arg1);\n}\n\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction handleError(f, args) {\n try {\n return f.apply(this, args);\n } catch (e) {\n wasm.__wbindgen_export_6(addHeapObject(e));\n }\n}\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\n}\n\n/**\n */\nexport function main_js() {\n wasm.main_js();\n}\n\nfunction getArrayU8FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\n}\n/**\n */\nexport const Level = Object.freeze({\n Err: 0,\n 0: \"Err\",\n Warn: 1,\n 1: \"Warn\",\n Info: 2,\n 2: \"Info\",\n Debug: 3,\n 3: \"Debug\",\n});\n/**\n */\nexport class Telemeter {\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n\n return ptr;\n }\n\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_telemeter_free(ptr);\n }\n /**\n * @param {any} solution\n * @param {any} hosts\n * @param {any} config\n */\n constructor(solution, hosts, config) {\n try {\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\n var r0 = getInt32Memory0()[retptr / 4 + 0];\n var r1 = getInt32Memory0()[retptr / 4 + 1];\n var r2 = getInt32Memory0()[retptr / 4 + 2];\n if (r2) {\n throw takeObject(r1);\n }\n this.__wbg_ptr = r0 >>> 0;\n return this;\n } finally {\n wasm.__wbindgen_add_to_stack_pointer(16);\n }\n }\n /**\n * @param {string} sender_id\n * @returns {Promise}\n */\n identify(sender_id) {\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\n return takeObject(ret);\n }\n /**\n * @param {string} event_type\n * @param {any} event_payload\n * @returns {Promise}\n */\n track(event_type, event_payload) {\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\n return takeObject(ret);\n }\n /**\n * @param {any} level\n * @param {string} message\n * @param {any} payload\n * @returns {Promise}\n */\n log(level, message, payload) {\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\n return takeObject(ret);\n }\n /**\n * @returns {Promise}\n */\n finish() {\n const ptr = this.__destroy_into_raw();\n const ret = wasm.telemeter_finish(ptr);\n return takeObject(ret);\n }\n /**\n * @param {boolean | undefined} [new_debug_status]\n */\n debug(new_debug_status) {\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\n }\n}\n\nasync function __wbg_load(module, imports) {\n if (typeof Response === \"function\" && module instanceof Response) {\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\n try {\n return await WebAssembly.instantiateStreaming(module, imports);\n } catch (e) {\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\n console.warn(\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\n e,\n );\n } else {\n throw e;\n }\n }\n }\n\n const bytes = await module.arrayBuffer();\n return await WebAssembly.instantiate(bytes, imports);\n } else {\n const instance = await WebAssembly.instantiate(module, imports);\n\n if (instance instanceof WebAssembly.Instance) {\n return { instance, module };\n } else {\n return instance;\n }\n }\n}\n\nfunction __wbg_get_imports() {\n const imports = {};\n imports.wbg = {};\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\n const ret = getStringFromWasm0(arg0, arg1);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\n const ret = new Object();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\n const ret = getObject(arg0).status;\n return ret;\n };\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\n const ret = getObject(arg0).headers;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\n const ret = new Date();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\n const ret = getObject(arg0).getTime();\n return ret;\n };\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\n takeObject(arg0);\n };\n imports.wbg.__wbindgen_is_object = function (arg0) {\n const val = getObject(arg0);\n const ret = typeof val === \"object\" && val !== null;\n return ret;\n };\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\n const ret = getObject(arg0).crypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\n const ret = getObject(arg0).process;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\n const ret = getObject(arg0).versions;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\n const ret = getObject(arg0).node;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_string = function (arg0) {\n const ret = typeof getObject(arg0) === \"string\";\n return ret;\n };\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\n const ret = getObject(arg0).msCrypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\n return handleError(function () {\n const ret = module.require;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\n const ret = new Uint8Array(arg0 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\n const ret = getObject(arg0)[arg1 >>> 0];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).next();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\n const ret = getObject(arg0).done;\n return ret;\n };\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\n const ret = getObject(arg0).value;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\n const ret = Symbol.iterator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\n const ret = getObject(arg0).next;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_function = function (arg0) {\n const ret = typeof getObject(arg0) === \"function\";\n return ret;\n };\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\n return handleError(function (arg0, arg1) {\n const ret = getObject(arg0).call(getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\n const ret = getObject(arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\n return handleError(function () {\n const ret = self.self;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\n return handleError(function () {\n const ret = window.window;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\n return handleError(function () {\n const ret = globalThis.globalThis;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\n return handleError(function () {\n const ret = global.global;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\n const ret = getObject(arg0) === undefined;\n return ret;\n };\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\n const ret = new Function(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\n const ret = Array.isArray(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\n const ret = Number.isSafeInteger(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\n try {\n var state0 = { a: arg0, b: arg1 };\n var cb0 = (arg0, arg1) => {\n const a = state0.a;\n state0.a = 0;\n try {\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\n } finally {\n state0.a = a;\n }\n };\n const ret = new Promise(cb0);\n return addHeapObject(ret);\n } finally {\n state0.a = state0.b = 0;\n }\n };\n imports.wbg.__wbindgen_memory = function () {\n const ret = wasm.memory;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\n const ret = getObject(arg0).buffer;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\n const ret = new Uint8Array(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\n };\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"string\" ? obj : undefined;\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\n return handleError(function (arg0) {\n const ret = JSON.stringify(getObject(arg0));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\n const ret = getObject(arg0).fetch(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\n const ret = fetch(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\n return handleError(function () {\n const ret = new AbortController();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\n const ret = getObject(arg0).signal;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\n getObject(arg0).abort();\n };\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\n const ret = new Error(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\n const ret = getObject(arg0) == getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\n const v = getObject(arg0);\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\n return ret;\n };\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"number\" ? obj : undefined;\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Uint8Array;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof ArrayBuffer;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\n const ret = Object.entries(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\n const ret = String(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\n console.warn(getObject(arg0), getObject(arg1));\n };\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\n const ret = getObject(arg0).readyState;\n return ret;\n };\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\n console.warn(getObject(arg0));\n };\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\n return handleError(function (arg0) {\n getObject(arg0).close();\n }, arguments);\n };\n imports.wbg.__wbg_new_b9b318679315404f = function () {\n return handleError(function (arg0, arg1) {\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\n getObject(arg0).binaryType = takeObject(arg1);\n };\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\n console.log(getObject(arg0));\n };\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\n console.error(getObject(arg0));\n };\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\n console.info(getObject(arg0));\n };\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\n const ret = getObject(arg0).document;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n };\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\n const ret = getObject(arg0).visibilityState;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\n const ret = getObject(arg0)[getObject(arg1)];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\n const ret = typeof getObject(arg0) === \"bigint\";\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\n const ret = arg0;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\n const ret = getObject(arg0) in getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\n const ret = BigInt.asUintN(64, arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\n const ret = getObject(arg0) === getObject(arg1);\n return ret;\n };\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).localStorage;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n }, arguments);\n };\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\n const ret = getObject(arg0).navigator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).mediaDevices;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).enumerateDevices();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\n const obj = takeObject(arg0).original;\n if (obj.cnt-- == 1) {\n obj.a = 0;\n return true;\n }\n const ret = false;\n return ret;\n };\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\n const ret = getObject(arg1).deviceId;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Response;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).randomFillSync(takeObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).getRandomValues(getObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\n const v = getObject(arg1);\n const ret = typeof v === \"bigint\" ? v : undefined;\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\n const ret = debugString(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\n throw new Error(getStringFromWasm0(arg0, arg1));\n };\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\n const ret = getObject(arg0).then(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\n queueMicrotask(getObject(arg0));\n };\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\n const ret = getObject(arg0).queueMicrotask;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\n const ret = Promise.resolve(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\n const ret = getObject(arg1).url;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_send_2860805104507701 = function () {\n return handleError(function (arg0, arg1, arg2) {\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\n }, arguments);\n };\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Window;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\n return handleError(function () {\n const ret = new Headers();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\n return addHeapObject(ret);\n };\n\n return imports;\n}\n\nfunction __wbg_init_memory(imports, maybe_memory) {}\n\nfunction __wbg_finalize_init(instance, module) {\n wasm = instance.exports;\n __wbg_init.__wbindgen_wasm_module = module;\n cachedBigInt64Memory0 = null;\n cachedFloat64Memory0 = null;\n cachedInt32Memory0 = null;\n cachedUint8Memory0 = null;\n\n wasm.__wbindgen_start();\n return wasm;\n}\n\nfunction initSync(module) {\n if (wasm !== undefined) return wasm;\n\n const imports = __wbg_get_imports();\n\n __wbg_init_memory(imports);\n\n if (!(module instanceof WebAssembly.Module)) {\n module = new WebAssembly.Module(module);\n }\n\n const instance = new WebAssembly.Instance(module, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nasync function __wbg_init(input) {\n if (wasm !== undefined) return wasm;\n\n if (typeof input === \"undefined\") {\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\n }\n const imports = __wbg_get_imports();\n\n if (\n typeof input === \"string\" ||\n (typeof Request === \"function\" && input instanceof Request) ||\n (typeof URL === \"function\" && input instanceof URL)\n ) {\n input = fetch(input);\n }\n\n __wbg_init_memory(imports);\n\n const { instance, module } = await __wbg_load(await input, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nexport { initSync };\nexport default __wbg_init;\n","/* eslint-disable-next-line */\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\n\n/**\n * @classdesc\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\n * to Telemetry in a more comfortable and homogeneous way.\n */\nexport default class Telemeter {\n /**\n * Inits Telemeter class.\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\n * @param {Object} telemeterAttributes.config - Configuration parameters.\n */\n static init(telemeterAttributes) {\n if (!this.telemeter && !this.waitingForInit) {\n this.waitingForInit = true;\n init(telemeterAttributes.url)\n .then(() => {\n this.telemeter = new TelemeterWASM(\n telemeterAttributes.solution,\n telemeterAttributes.hosts,\n telemeterAttributes.config,\n );\n })\n .catch((error) => {\n console.log(error);\n })\n .finally(() => (this.waitingForInit = false));\n }\n }\n\n /**\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\n */\n static async finish() {\n if (!this.telemeter) return;\n\n try {\n const local_telemeter = this.telemeter;\n this.telemeter = undefined;\n await local_telemeter.finish();\n } catch (e) {\n console.error(e);\n }\n }\n}\n","import Configuration from \"./configuration\";\nimport Core from \"./core.src\";\nimport EditorListener from \"./editorlistener\";\nimport Listeners from \"./listeners\";\nimport MathML from \"./mathml\";\nimport Util from \"./util\";\nimport Telemeter from \"./telemeter\";\n\nexport default class ContentManager {\n /**\n * @classdesc\n * This class represents a modal dialog, managing the following:\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\n * - The actions to be done once the modal object has been submitted\n * (submitAction} method).\n * - The update of the content when the {@link ModalDialog} class is also updated,\n * for example when ModalDialog is re-opened.\n * - The communication between the {@link ModalDialog} class and itself, if the content\n * has been changed (hasChanges} method).\n * @constructs\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\n * create a new instance.\n */\n constructor(contentManagerAttributes) {\n /**\n * An object containing MathType editor parameters. See\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\n * @type {Object}\n */\n this.editorAttributes = {};\n if (\"editorAttributes\" in contentManagerAttributes) {\n this.editorAttributes = contentManagerAttributes.editorAttributes;\n } else {\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\n }\n\n /**\n * CustomEditors instance. Contains the custom editors.\n * @type {CustomEditors}\n */\n this.customEditors = null;\n if (\"customEditors\" in contentManagerAttributes) {\n this.customEditors = contentManagerAttributes.customEditors;\n }\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @type {Object}\n * @property {String} editor - Editor name. Usually the HTML editor.\n * @property {String} mode - Save mode. Xml by default.\n * @property {String} version - Plugin version.\n */\n this.environment = {};\n if (\"environment\" in contentManagerAttributes) {\n this.environment = contentManagerAttributes.environment;\n } else {\n throw new Error(\"ContentManager constructor error: environment property missed\");\n }\n\n /**\n * ContentManager language.\n * @type {String}\n */\n this.language = \"\";\n if (\"language\" in contentManagerAttributes) {\n this.language = contentManagerAttributes.language;\n } else {\n throw new Error(\"ContentManager constructor error: language property missed\");\n }\n\n /**\n * {@link EditorListener} instance. Manages the changes inside the editor.\n * @type {EditorListener}\n */\n this.editorListener = new EditorListener();\n\n /**\n * MathType editor instance.\n * @type {JsEditor}\n */\n this.editor = null;\n\n /**\n * Navigator user agent.\n * @type {String}\n */\n this.ua = navigator.userAgent.toLowerCase();\n\n /**\n * Mobile device properties object\n * @type {DeviceProperties}\n */\n this.deviceProperties = {};\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\n this.deviceProperties.isIOS = ContentManager.isIOS();\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.toolbar = null;\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.dbclick = null;\n\n /**\n * Instance of the {@link ModalDialog} class associated with the current\n * {@link ContentManager} instance.\n * @type {ModalDialog}\n */\n this.modalDialogInstance = null;\n\n /**\n * ContentManager listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n /**\n * MathML associated to the ContentManager instance.\n * @type {String}\n */\n this.mathML = null;\n\n /**\n * Indicates if the edited element is a new one or not.\n * @type {Boolean}\n */\n this.isNewElement = true;\n\n /**\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n }\n\n /**\n * Adds a new listener to the current {@link ContentManager} instance.\n * @param {Object} listener - The listener to be added.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\n * instance.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\n */\n setModalDialogInstance(modalDialogInstance) {\n this.modalDialogInstance = modalDialogInstance;\n }\n\n /**\n * Inserts the content into the current {@link ModalDialog} instance updating\n * the title and inserting the JavaScript editor.\n */\n insert() {\n // Before insert the editor we update the modal object title to avoid weird render display.\n this.updateTitle(this.modalDialogInstance);\n this.insertEditor(this.modalDialogInstance);\n }\n\n /**\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\n * editor's JavaScript is loaded.\n */\n insertEditor() {\n if (ContentManager.isEditorLoaded()) {\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\n this.editor.focus();\n\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\n this.editor.action(\"rtl\");\n }\n // Setting div in rtl in case of it's activated.\n if (this.editor.getEditorModel().isRTL()) {\n this.editor.element.style.direction = \"rtl\";\n }\n\n // Editor listener: this object manages the changes logic of editor.\n this.editor.getEditorModel().addEditorListener(this.editorListener);\n\n // iOS events.\n if (this.modalDialogInstance.deviceProperties.isIOS) {\n setTimeout(function () {\n // Make sure the modalDialogInstance is available when the timeout is over\n // to avoid throw errors and stop execution.\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\n }, 400);\n\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\n }\n // Fire onLoad event. Necessary to set the MathML into the editor\n // after is loaded.\n this.listeners.fire(\"onLoad\", {});\n } else {\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\n }\n }\n\n /**\n * Initializes the current class by loading MathType script.\n */\n init() {\n if (!ContentManager.isEditorLoaded()) {\n this.addEditorAsExternalDependency();\n }\n }\n\n /**\n * Adds script element to the DOM to include editor externally.\n */\n addEditorAsExternalDependency() {\n const script = document.createElement(\"script\");\n script.type = \"text/javascript\";\n let editorUrl = Configuration.get(\"editorUrl\");\n\n // We create an object url for parse url string and work more efficiently.\n const anchorElement = document.createElement(\"a\");\n\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\n ContentManager.setProtocolToAnchorElement(anchorElement);\n\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\n\n // Load editor URL. We add stats as GET params.\n const stats = this.getEditorStats();\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\n\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n }\n\n /**\n * Sets the specified url to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\n * @param {String} url - URL to set.\n */\n static setHrefToAnchorElement(anchorElement, url) {\n anchorElement.href = url;\n }\n\n /**\n * Sets the current protocol to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\n */\n static setProtocolToAnchorElement(anchorElement) {\n // Change to https if necessary.\n if (window.location.href.indexOf(\"https://\") === 0) {\n // It check if browser is https and configuration is http.\n // If this is so, we will replace protocol.\n if (anchorElement.protocol === \"http:\") {\n anchorElement.protocol = \"https:\";\n }\n }\n }\n\n /**\n * Returns the url of the anchor element adding the current port\n * if it is needed.\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\n * @returns {String}\n */\n static getURLFromAnchorElement(anchorElement) {\n // Check protocol and remove port if it's standard.\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\n }\n\n /**\n * Returns object with editor stats.\n *\n * @typedef {Object} EditorStatsObject\n * @property {string} editor - Editor name.\n * @property {string} mode - Current configuration for formula save mode.\n * @property {string} version - Current plugins version.\n * @returns {EditorStatsObject}\n */\n getEditorStats() {\n // Editor stats. Use environment property to set it.\n const stats = {};\n if (\"editor\" in this.environment) {\n stats.editor = this.environment.editor;\n } else {\n stats.editor = \"unknown\";\n }\n\n if (\"mode\" in this.environment) {\n stats.mode = this.environment.mode;\n } else {\n stats.mode = Configuration.get(\"saveMode\");\n }\n\n if (\"version\" in this.environment) {\n stats.version = this.environment.version;\n } else {\n stats.version = Configuration.get(\"version\");\n }\n\n return stats;\n }\n\n /**\n * Returns true if device is iOS. Otherwise, false.\n * @returns {Boolean}\n */\n static isIOS() {\n return (\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\n // iPad on iOS 13 detection\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\n );\n }\n\n /**\n * Returns true if device is Mobile. Otherwise, false.\n * @returns {Boolean}\n */\n static isMobile() {\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n }\n\n /**\n * Returns true if editor is loaded. Otherwise, false.\n * @returns {Boolean}\n */\n static isEditorLoaded() {\n // To know if editor JavaScript is loaded we need to wait until\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\n return (\n window.com &&\n window.com.wiris &&\n window.com.wiris.jsEditor &&\n window.com.wiris.jsEditor.JsEditor &&\n window.com.wiris.jsEditor.JsEditor.newInstance\n );\n }\n\n /**\n * Sets the {@link ContentManager.editor} initial content.\n */\n setInitialContent() {\n if (!this.isNewElement) {\n this.setMathML(this.mathML);\n }\n }\n\n /**\n * Sets a MathML into {@link ContentManager.editor} instance.\n * @param {String} mathml - MathML string.\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\n * False by default.\n */\n setMathML(mathml, focusDisabled) {\n // By default focus is enabled.\n if (typeof focusDisabled === \"undefined\") {\n focusDisabled = false;\n }\n // Using setMathML method is not a change produced by the user but for the API\n // so we set to false the contentChange property of editorListener.\n this.editor.setMathMLWithCallback(mathml, () => {\n this.editorListener.setWaitingForChanges(true);\n });\n\n // We need to wait a little until the callback finish.\n setTimeout(() => {\n this.editorListener.setIsContentChanged(false);\n }, 500);\n\n // In some scenarios - like closing modal object - editor mustn't be focused.\n if (!focusDisabled) {\n this.onFocus();\n }\n }\n\n /**\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\n * {@link ModalDialog.focus}.\n */\n onFocus() {\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\n this.editor.focus();\n\n // On WordPress integration, the focus gets lost right after setting it.\n // To fix this, we enforce another focus some milliseconds after this behaviour.\n setTimeout(() => {\n this.editor.focus();\n }, 100);\n }\n }\n\n /**\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\n * Triggered by {@link ModalDialog.submitAction}.\n */\n submitAction() {\n if (!this.editor.isFormulaEmpty()) {\n let mathML = this.editor.getMathMLWithSemantics();\n // Add class for custom editors.\n if (this.customEditors.getActiveEditor() !== null) {\n const { toolbar } = this.customEditors.getActiveEditor();\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\n } else {\n // We need - if exists - the editor name from MathML\n // class attribute.\n Object.keys(this.customEditors.editors).forEach((key) => {\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\n });\n }\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\n } else {\n this.integrationModel.updateFormula(null);\n }\n\n this.customEditors.disable();\n this.integrationModel.notifyWindowClosed();\n\n // Set disabled focus to prevent lost focus.\n this.setEmptyMathML();\n this.customEditors.disable();\n }\n\n /**\n * Sets an empty MathML as {@link ContentManager.editor} content.\n * This will open the MT/CT editor with the hand mode.\n * It adds dir rtl in case of it's activated.\n */\n setEmptyMathML() {\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\n const isRTL = this.editor.getEditorModel().isRTL();\n\n if (isMobile || this.integrationModel.forcedHandMode) {\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\n const mathML = `[]`;\n this.setMathML(mathML, true);\n } else {\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\n const mathML = ``;\n this.setMathML(mathML, true);\n }\n }\n\n /**\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\n * - Updates the {@link ContentManager.editor} content\n * (with an empty MathML or an existing formula),\n * - Updates the {@link ContentManager.editor} toolbar.\n * - Recovers the the focus.\n */\n onOpen() {\n if (this.isNewElement) {\n this.setEmptyMathML();\n } else {\n this.setMathML(this.mathML);\n }\n const toolbar = this.updateToolbar();\n this.onFocus();\n\n if (this.deviceProperties.isIOS) {\n const zoom = document.documentElement.clientWidth / window.innerWidth;\n\n if (zoom !== 1) {\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\n this.setKeyboardMode();\n }\n }\n\n const trigger = this.dbclick ? \"formula\" : \"button\";\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\n toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\n }\n\n Core.globalListeners.fire(\"onModalOpen\", {});\n\n if (this.integrationModel.forcedHandMode) {\n this.hideHandModeButton();\n\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\n }\n }\n }\n\n /**\n * Change Editor in keyboard mode when is loaded\n */\n setKeyboardMode() {\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\n if (wrsEditor) {\n wrsEditor.classList.remove(\"wrs_handOpen\");\n wrsEditor.classList.remove(\"wrs_disablePalette\");\n } else {\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\n }\n }\n\n /**\n * Hides the hand <-> keyboard mode switch.\n *\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\n * any change on those classes will make this code stop working properly.\n *\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\n */\n hideHandModeButton(forced = true) {\n if (this.handSwitchHidden) {\n return; // hand <-> keyboard button already hidden.\n }\n\n // \"Open hand mode\" button takes a little bit to be available.\n // This selector gets the hand <-> keyboard mode switch\n const handModeButtonSelector =\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\n\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\n // We use an observer to ensure that the button it hidden as soon as it appears.\n if (forced) {\n const mutationInstance = new MutationObserver((mutations) => {\n const handModeButton = document.querySelector(handModeButtonSelector);\n if (handModeButton) {\n handModeButton.hidden = true;\n this.handSwitchHidden = true;\n mutationInstance.disconnect();\n }\n });\n mutationInstance.observe(document.body, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n }\n\n /**\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\n *\n * @param {String} mathml The original KeyBoard MathML\n * @param {Object} editor The editor object.\n */\n async openHandOnKeyboardMathML(mathml, editor) {\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\n await new Promise((resolve) => {\n editor.setMathMLWithCallback(mathml, resolve);\n });\n\n // We wait until the hand editor object exists.\n await this.waitForHand(editor);\n\n // Logic to get the hand traces and open the formula in hand mode.\n // This logic comes from the editor.\n const handEditor = editor.hand;\n editor.handTemporalMathML = editor.getMathML();\n const handCoordinates = editor.editorModel.getHandStrokes();\n handEditor.setStrokes(handCoordinates);\n handEditor.fitStrokes(true);\n editor.openHand();\n }\n\n /**\n * Waits until the hand editor object exists.\n * @param {Obect} editor The editor object.\n */\n async waitForHand(editor) {\n while (!editor.hand) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n\n /**\n * Sets the correct toolbar depending if exist other custom toolbars\n * at the same time (e.g: Chemistry).\n */\n updateToolbar() {\n this.updateTitle(this.modalDialogInstance);\n const customEditor = this.customEditors.getActiveEditor();\n\n let toolbar;\n if (customEditor) {\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\n\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n }\n } else {\n toolbar = this.getToolbar();\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n this.customEditors.disable();\n }\n }\n\n return toolbar;\n }\n\n /**\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\n * sets the custom editor title. Otherwise sets the default title.\n */\n updateTitle() {\n const customEditor = this.customEditors.getActiveEditor();\n if (customEditor) {\n this.modalDialogInstance.setTitle(customEditor.title);\n } else {\n this.modalDialogInstance.setTitle(\"MathType\");\n }\n }\n\n /**\n * Returns the editor toolbar, depending on the configuration local or server side.\n * @returns {String} - Toolbar identifier.\n */\n getToolbar() {\n let toolbar = \"general\";\n if (\"toolbar\" in this.editorAttributes) {\n ({ toolbar } = this.editorAttributes);\n }\n // TODO: Change global integration variable for integration custom toolbar.\n if (toolbar === \"general\") {\n // eslint-disable-next-line camelcase\n toolbar =\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\n ? \"general\"\n : _wrs_int_wirisProperties.toolbar;\n }\n\n return toolbar;\n }\n\n /**\n * Sets the current {@link ContentManager.editor} instance toolbar.\n * @param {String} toolbar - The toolbar name.\n */\n setToolbar(toolbar) {\n this.toolbar = toolbar;\n this.editor.setParams({ toolbar: this.toolbar });\n }\n\n /**\n * Sets the custom headers added on editor requests.\n * @returns {Object} headers - key value headers.\n */\n setCustomHeaders(headers) {\n let headersObj = {};\n\n // We control that we only get String or Object as the input.\n if (typeof headers === \"object\") {\n headersObj = headers;\n } else if (typeof headers === \"string\") {\n headersObj = Util.convertStringToObject(headers);\n }\n\n this.editor.setParams({ customHeaders: headersObj });\n return headersObj;\n }\n\n /**\n * Returns true if the content of the editor has been changed. The logic of the changes\n * is delegated to {@link EditorListener} class.\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\n */\n hasChanges() {\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n // Code to detect Esc event.\n // There should be only one element with class name 'wrs_pressed' at the same time.\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\n if (list.length === 0) {\n this.modalDialogInstance.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.modalDialogInstance.submitButton) {\n // Focus is on OK button.\n this.editor.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\n // Focus is on minimize button (_).\n this.modalDialogInstance.closeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\n // Focus on cancel button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n this.modalDialogInstance.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\n // Focus is on X button.\n this.modalDialogInstance.minimizeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\n // Focus on help button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n const element = document.querySelector('[title=\"Manual\"]');\n element.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n } else {\n // There should be only one element with class name 'wrs_formulaDisplay'.\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\n // Focus is on formuladisplay.\n this.modalDialogInstance.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n }\n }\n}\n","/**\n * A custom editor is MathType editor with a different\n * @typedef {Object} CustomEditor\n * @property {String} CustomEditor.name - Custom editor name.\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\n * @property {String} CustomEditor.icon - Custom editor icon.\n * @property {String} CustomEditor.confVariable - Configuration property to manage\n * the availability of the custom editor.\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\n */\n\nexport default class CustomEditors {\n /**\n * @classdesc\n * This class represents the MathType custom editors manager.\n * A custom editor is MathType editor with a custom toolbar.\n * This class associates a {@link CustomEditor} to:\n * - It's own formulas\n * - A custom toolbar\n * - An icon to open it from a HTML editor.\n * - A tooltip for the icon.\n * - A global variable to enable or disable it globally.\n * @constructs\n */\n constructor() {\n /**\n * The custom editors.\n * @type {Array.}\n */\n\n this.editors = [];\n /**\n * The active editor name.\n * @type {String}\n */\n this.activeEditor = \"default\";\n }\n\n /**\n * Adds a {@link CustomEditor} to editors array.\n * @param {String} editorName - The editor name.\n * @param {CustomEditor} editorParams - The custom editor parameters.\n */\n addEditor(editorName, editorParams) {\n const customEditor = {};\n customEditor.name = editorParams.name;\n customEditor.toolbar = editorParams.toolbar;\n customEditor.icon = editorParams.icon;\n customEditor.confVariable = editorParams.confVariable;\n customEditor.title = editorParams.title;\n customEditor.tooltip = editorParams.tooltip;\n this.editors[editorName] = customEditor;\n }\n\n /**\n * Enables a {@link CustomEditor}.\n * @param {String} customEditorName - The custom editor name.\n */\n enable(customEditorName) {\n this.activeEditor = customEditorName;\n }\n\n /**\n * Disables a {@link CustomEditor}.\n */\n disable() {\n this.activeEditor = \"default\";\n }\n\n /**\n * Returns the active editor.\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\n */\n getActiveEditor() {\n if (this.activeEditor !== \"default\") {\n return this.editors[this.activeEditor];\n }\n return null;\n }\n}\n","/**\n * Represents the configuration properties generated from the frontend (JavaScript variables).\n * @type {Object}\n * @property {string} imageClassName - Default MathType formula image class.\n * @property {string} imageClassName - Default MathType CAS image class.\n * @ignore\n */\nconst jsProperties = {\n imageCustomEditorName: \"data-custom-editor\",\n imageClassName: \"Wirisformula\",\n CASClassName: \"Wiriscas\",\n};\nexport default jsProperties;\n","export default class Event {\n /**\n * @classdesc\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\n *\n * ```js\n * let customEvent = new Event();\n * customEvent.properties = {};\n *\n * let listeners = new Listeners();\n * listeners.newListener(eventName, callback);\n *\n * listeners.fire(eventName, customEvent) *\n * ```\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the event should be cancelled.\n * @type {Boolean}\n */\n\n this.cancelled = false;\n /**\n * Indicates if the event should be prevented.\n * @type {Boolean}\n */\n this.defaultPrevented = false;\n }\n\n /**\n * Cancels the event.\n */\n cancel() {\n this.cancelled = true;\n }\n\n /**\n * Prevents the default action.\n */\n preventDefault() {\n this.defaultPrevented = true;\n }\n}\n","import IntegrationModel from \"./integrationmodel\";\n\n/**\n\n */\nexport default class PopUpMessage {\n /**\n * @classdesc\n * This class represents a dialog message overlaying a DOM element in order to\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\n * o canceled. In this last case a callback function should be called.\n * @constructs\n * @param {Object} popupMessageAttributes - Object containing popup properties.\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\n * methods for close and cancel actions.\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\n */\n constructor(popupMessageAttributes) {\n /**\n * Element to be overlaid when the popup appears.\n */\n this.overlayElement = popupMessageAttributes.overlayElement;\n\n this.callbacks = popupMessageAttributes.callbacks;\n\n /**\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\n */\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\n\n /**\n * HTMLElement to display the popup message, close button and cancel button.\n */\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n this.message.id = \"wrs_popupmessage\";\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\n this.message.setAttribute(\"role\", \"dialog\");\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\n const paragraph = document.createElement(\"p\");\n const text = document.createTextNode(popupMessageAttributes.strings.message);\n paragraph.appendChild(text);\n paragraph.id = \"description_txt\";\n this.message.appendChild(paragraph);\n\n /**\n * HTML element overlaying the overlayElement.\n */\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\n // We create a overlay that close popup message on click in there\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n /**\n * HTML element containing cancel and close buttons.\n */\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\n this.buttonArea.id = \"wrs_popup_button_area\";\n\n // Close button arguments.\n const buttonSubmitArguments = {\n class: \"wrs_button_accept\",\n innerHTML: popupMessageAttributes.strings.submitString,\n id: \"wrs_popup_accept_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-close-button\",\n };\n\n /**\n * Close button arguments.\n */\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\n this.buttonArea.appendChild(this.closeButton);\n\n // Cancel button arguments.\n const buttonCancelArguments = {\n class: \"wrs_button_cancel\",\n innerHTML: popupMessageAttributes.strings.cancelString,\n id: \"wrs_popup_cancel_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\n };\n\n /**\n * Cancel button.\n */\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\n this.buttonArea.appendChild(this.cancelButton);\n }\n\n /**\n * This method create a button with arguments and return button dom object\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\n * @param {String} parameters.id - Button id.\n * @param {String} parameters.class - Button class name.\n * @param {String} parameters.innerHTML - Button innerHTML text.\n * @param {Object} callback- Callback method to call on click event.\n * @returns {HTMLElement} HTML button.\n */\n // eslint-disable-next-line class-methods-use-this\n createButton(parameters, callback) {\n let element = {};\n element = document.createElement(\"button\");\n element.setAttribute(\"id\", parameters.id);\n element.setAttribute(\"class\", parameters.class);\n element.innerHTML = parameters.innerHTML;\n element.addEventListener(\"click\", callback);\n if (parameters[\"data-testid\"]) {\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\n }\n\n return element;\n }\n\n /**\n * Shows the popupmessage containing a message, and two buttons\n * to cancel the action or close the modal dialog.\n */\n show() {\n if (this.overlayWrapper.style.display !== \"block\") {\n // Clear focus with blur for prevent press any key.\n document.activeElement.blur();\n this.overlayWrapper.style.display = \"block\";\n this.closeButton.focus();\n } else {\n this.overlayWrapper.style.display = \"none\";\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\n }\n }\n\n /**\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\n * A callback method is called (if defined). For example a method to focus the overlaid element.\n */\n cancelAction() {\n this.overlayWrapper.style.display = \"none\";\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\n this.callbacks.cancelCallback();\n // Set temporal image to null to prevent loading\n // an existent formula when starting one from scratch. Make focus come back too.\n // IntegrationModel.setActionsOnCancelButtons();\n }\n }\n\n /**\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\n * For example to close the overlaid element.\n */\n closeAction() {\n this.cancelAction();\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\n this.callbacks.closeCallback();\n }\n IntegrationModel.setActionsOnCancelButtons();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Code to detect Esc event.\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n this.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.closeButton) {\n this.cancelButton.focus();\n } else {\n this.closeButton.focus();\n }\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n}\n","/**\n * This module provides protection against external focus management scripts\n * that might interfere with the MathType editor modal.\n */\n\n/**\n * focusProtection function creates and returns methods to prevent external scripts from\n * interfering with the focus of the MathType modal dialog.\n *\n * @returns {Object} An object with protect and unprotect methods.\n */\nconst focusProtection = () => {\n /**\n * Initialize focus protection on the given modal element.\n *\n * @param {HTMLElement} modalElement - The modal element to protect\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\n * @param {HTMLElement} editorElement - The editor element inside the modal\n */\n const protect = (modalElement, overlayElement, editorElement) => {\n if (!modalElement || !overlayElement || !editorElement) {\n console.warn(\"FocusProtection: Missing required elements\");\n return;\n }\n\n // Apply the focus protection\n overrideFocusBehavior(modalElement, editorElement);\n };\n\n /**\n * Apply focus protection by overriding focus-related methods\n *\n * @param {HTMLElement} modalElement - The modal element\n * @param {HTMLElement} editorElement - The editor element to keep focused\n * @private\n */\n const overrideFocusBehavior = (modalElement, editorElement) => {\n // Store original focus methods to be able to restore them\n const originalElementFocus = HTMLElement.prototype.focus;\n const originalElementBlur = HTMLElement.prototype.blur;\n\n // Override the focus method for all elements\n HTMLElement.prototype.focus = function (...args) {\n // If the modal is open and this is not part of the modal, prevent focus\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\n // If some external script is trying to focus another element, prevent it\n // and restore focus to the editor\n if (editorElement) {\n // Use the original focus method to avoid infinite recursion\n originalElementFocus.apply(editorElement, args);\n }\n return;\n }\n\n // Otherwise, allow the focus to happen\n originalElementFocus.apply(this, args);\n };\n\n // Store the methods to remove them when the modal is closed\n modalElement.originalElementFocus = originalElementFocus;\n modalElement.originalElementBlur = originalElementBlur;\n };\n\n /**\n * Remove focus protection from the modal\n *\n * @param {HTMLElement} modalElement - The modal element to unprotect\n */\n const unprotect = (modalElement) => {\n if (!modalElement) {\n return;\n }\n\n // Restore original focus methods\n if (modalElement.originalElementFocus) {\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\n delete modalElement.originalElementFocus;\n }\n\n if (modalElement.originalElementBlur) {\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\n delete modalElement.originalElementBlur;\n }\n };\n\n return {\n protect,\n unprotect,\n };\n};\n\nexport default focusProtection;\n","// eslint-disable-next-line max-classes-per-file\nimport PopUpMessage from \"./popupmessage\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport Listeners from \"./listeners\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Telemeter from \"./telemeter\";\nimport IntegrationModel from \"./integrationmodel\";\nimport Core from \"./core.src\";\nimport focusProtection from \"./focusprotection\";\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\nconst { unprotect, protect } = focusProtection();\n\n/**\n * @typedef {Object} DeviceProperties\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\n * False otherwise.\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\n * False otherwise.\n */\n\nexport default class ModalDialog {\n /**\n * @classdesc\n * This class represents a modal dialog. The modal dialog admits\n * a {@link ContentManager} instance to manage the content of the dialog.\n * @constructs\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\n */\n constructor(modalDialogAttributes) {\n this.attributes = modalDialogAttributes;\n\n // Metrics.\n const ua = navigator.userAgent.toLowerCase();\n const isAndroid = ua.indexOf(\"android\") > -1;\n const isIOS = ContentManager.isIOS();\n this.iosSoftkeyboardOpened = false;\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\n this.iosDivHeight = `auto`;\n\n const deviceWidth = window.outerWidth;\n const deviceHeight = window.outerHeight;\n\n const landscape = deviceWidth > deviceHeight;\n const portrait = deviceWidth < deviceHeight;\n\n // TODO: Detect isMobile without using editor metrics.\n const isLandscape = landscape && this.attributes.height > deviceHeight;\n const isPortrait = portrait && this.attributes.width > deviceWidth;\n const isMobile = ContentManager.isMobile();\n\n // Obtain number of current instance.\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\n\n // Device object properties.\n\n /**\n * @type {DeviceProperties}\n */\n this.deviceProperties = {\n orientation: landscape ? \"landscape\" : \"portrait\",\n isAndroid,\n isIOS,\n isMobile,\n isDesktop: !isMobile && !isIOS && !isAndroid,\n };\n\n this.properties = {\n created: false,\n state: \"\",\n previousState: \"\",\n position: { bottom: 0, right: 10 },\n size: { height: 338, width: 580 },\n };\n\n /**\n * Object to keep website's style before change it on lock scroll for mobile devices.\n * @type {Object}\n * @property {String} bodyStylePosition - Previous body style position.\n * @property {String} bodyStyleOverflow - Previous body style overflow.\n * @property {String} htmlStyleOverflow - Previous body style overflow.\n * @property {String} windowScrollX - Previous window's scroll Y.\n * @property {String} windowScrollY - Previous window's scroll X.\n */\n this.websiteBeforeLockParameters = null;\n\n let attributes = {};\n attributes.class = \"wrs_modal_overlay\";\n attributes.id = this.getElementId(attributes.class);\n this.overlay = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title_bar\";\n attributes.id = this.getElementId(attributes.class);\n this.titleBar = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title\";\n attributes.id = this.getElementId(attributes.class);\n this.title = Util.createElement(\"div\", attributes);\n this.title.innerHTML = \"offline\";\n\n attributes = {};\n attributes.class = \"wrs_modal_close_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"close\");\n attributes.style = {};\n this.closeDiv = Util.createElement(\"a\", attributes);\n this.closeDiv.setAttribute(\"role\", \"button\");\n this.closeDiv.setAttribute(\"tabindex\", 3);\n // Apply styles and events after the creation as createElement doesn't process them correctly\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\n // To identifiy the element in automated testing\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_stack_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"exit_fullscreen\");\n this.stackDiv = Util.createElement(\"a\", attributes);\n this.stackDiv.setAttribute(\"role\", \"button\");\n this.stackDiv.setAttribute(\"tabindex\", 2);\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\n // To identifiy the element in automated testing\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_maximize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"fullscreen\");\n this.maximizeDiv = Util.createElement(\"a\", attributes);\n this.maximizeDiv.setAttribute(\"role\", \"button\");\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\n // To identifiy the element in automated testing\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_minimize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"minimize\");\n this.minimizeDiv = Util.createElement(\"a\", attributes);\n this.minimizeDiv.setAttribute(\"role\", \"button\");\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\n // To identify the element in automated testing\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_dialogContainer\";\n attributes.id = this.getElementId(attributes.class);\n attributes.role = \"dialog\";\n this.container = Util.createElement(\"div\", attributes);\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\n\n attributes = {};\n attributes.class = \"wrs_modal_wrapper\";\n attributes.id = this.getElementId(attributes.class);\n this.wrapper = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_content_container\";\n attributes.id = this.getElementId(attributes.class);\n this.contentContainer = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_controls\";\n attributes.id = this.getElementId(attributes.class);\n this.controls = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_buttons_container\";\n attributes.id = this.getElementId(attributes.class);\n this.buttonContainer = Util.createElement(\"div\", attributes);\n\n // Buttons: all button must be created using createSubmitButton method.\n this.submitButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_accept\"),\n class: \"wrs_modal_button_accept\",\n innerHTML: StringManager.get(\"accept\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-insert-button\",\n },\n this.submitAction.bind(this),\n );\n\n this.cancelButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_cancel\"),\n class: \"wrs_modal_button_cancel\",\n innerHTML: StringManager.get(\"cancel\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cancel-button\",\n },\n this.cancelAction.bind(this),\n );\n\n this.contentManager = null;\n\n // Overlay popup.\n const popupStrings = {\n cancelString: StringManager.get(\"cancel\"),\n submitString: StringManager.get(\"close\"),\n message: StringManager.get(\"close_modal_warning\"),\n };\n\n const callbacks = {\n closeCallback: () => {\n this.close(\"mtc_close\");\n },\n cancelCallback: () => {\n this.focus();\n },\n };\n\n const popupupProperties = {\n overlayElement: this.container,\n callbacks,\n strings: popupStrings,\n };\n\n this.popup = new PopUpMessage(popupupProperties);\n\n /**\n * Indicates if directionality of the modal dialog is RTL. false by default.\n * @type {Boolean}\n */\n this.rtl = false;\n if (\"rtl\" in this.attributes) {\n this.rtl = this.attributes.rtl;\n }\n\n // Event handlers need modal instance context.\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\n }\n\n /**\n * This method sets an ContentManager instance to ModalDialog. ContentManager\n * manages the logic of ModalDialog content: submit, update, close and changes.\n * @param {ContentManager} contentManager - ContentManager instance.\n */\n setContentManager(contentManager) {\n this.contentManager = contentManager;\n }\n\n /**\n * Returns the modal contentElement object.\n * @returns {ContentManager} the instance of the ContentManager class.\n */\n getContentManager() {\n return this.contentManager;\n }\n\n /**\n * This method is called when the modal object has been submitted. Calls\n * contentElement submitAction method - if exists - and closes the modal\n * object. No logic about the content should be placed here,\n * contentElement.submitAction is the responsible of the content logic.\n */\n async submitAction() {\n if (typeof this.contentManager.submitAction !== \"undefined\") {\n this.contentManager.submitAction();\n }\n\n await this.close(\"mtc_insert\");\n }\n\n /**\n * Performs the cancel action.\n * If there are no changes in the content, it closes the modal.\n * Otherwise, it shows a pop-up message to confirm the cancel action.\n * @returns {Promise} - A promise that resolves when the modal is closed.\n */\n async cancelAction() {\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\n IntegrationModel.setActionsOnCancelButtons();\n await this.close(\"mtc_close\");\n } else {\n this.showPopUpMessage();\n }\n }\n\n /**\n * Returns a button element.\n * @param {Object} properties - Input button properties.\n * @param {String} properties.class - Input button class.\n * @param {String} properties.innerHTML - Input button innerHTML.\n * @param {Object} callback - Callback function associated to click event.\n * @returns {HTMLButtonElement} The button element.\n *\n */\n // eslint-disable-next-line class-methods-use-this\n createSubmitButton(properties, callback) {\n class SubmitButton {\n constructor() {\n this.element = document.createElement(\"button\");\n this.element.id = properties.id;\n this.element.className = properties.class;\n this.element.innerHTML = properties.innerHTML;\n this.element.dataset.testid = properties[\"data-testid\"];\n Util.addEvent(this.element, \"click\", callback);\n }\n\n getElement() {\n return this.element;\n }\n }\n return new SubmitButton(properties, callback).getElement();\n }\n\n /**\n * Creates the modal window object inserting a contentElement object.\n */\n create() {\n /* Modal Window Structure\n _____________________________________________________________________________________\n |wrs_modal_dialog_Container |\n | _________________________________________________________________________________ |\n | |title_bar minimize_button stack_button close_button | |\n | |_______________________________________________________________________________| |\n | |wrapper | |\n | | _____________________________________________________________________________ | |\n | | |content | | |\n | | | | | |\n | | | | | |\n | | |___________________________________________________________________________| | |\n | | _____________________________________________________________________________ | |\n | | |controls | | |\n | | | ___________________________________ | | |\n | | | |buttonContainer | | | |\n | | | | _______________________________ | | | |\n | | | | |button_accept | button_cancel| | | | |\n | | | |_|_____________ |______________|_| | | |\n | | |___________________________________________________________________________| | |\n | |_______________________________________________________________________________| |\n |___________________________________________________________________________________| */\n\n this.titleBar.appendChild(this.closeDiv);\n this.titleBar.appendChild(this.stackDiv);\n this.titleBar.appendChild(this.maximizeDiv);\n this.titleBar.appendChild(this.minimizeDiv);\n this.titleBar.appendChild(this.title);\n\n if (this.deviceProperties.isDesktop) {\n this.container.appendChild(this.titleBar);\n }\n\n this.wrapper.appendChild(this.contentContainer);\n this.wrapper.appendChild(this.controls);\n\n this.controls.appendChild(this.buttonContainer);\n\n this.buttonContainer.appendChild(this.submitButton);\n this.buttonContainer.appendChild(this.cancelButton);\n\n this.container.appendChild(this.wrapper);\n\n // Check if browser has scrollBar before modal has modified.\n this.recalculateScrollBar();\n\n document.body.appendChild(this.container);\n document.body.appendChild(this.overlay);\n\n if (this.deviceProperties.isDesktop) {\n // Desktop.\n this.createModalWindowDesktop();\n this.createResizeButtons();\n\n this.addListeners();\n // Maximize window only when the configuration is set and the device is not iOS or Android.\n if (Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n } else if (this.deviceProperties.isAndroid) {\n this.createModalWindowAndroid();\n } else if (this.deviceProperties.isIOS) {\n this.createModalWindowIos();\n }\n\n if (this.contentManager != null) {\n this.contentManager.insert(this);\n }\n\n this.properties.open = true;\n this.properties.created = true;\n\n // Checks language directionality.\n if (this.isRTL()) {\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\n this.container.className += \" wrs_modal_rtl\";\n }\n }\n\n /**\n * Creates a button in the modal object to resize it.\n */\n createResizeButtons() {\n // This is a definition of Resize Button Bottom-Right.\n this.resizerBR = document.createElement(\"div\");\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\n this.resizerBR.innerHTML = \"โ—ข\";\n // To identifiy the element in automated testing\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\n // This is a definition of Resize Button Top-Left.\n this.resizerTL = document.createElement(\"div\");\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\n // To identifiy the element in automated testing\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\n // Append resize buttons to modal.\n this.container.appendChild(this.resizerBR);\n this.titleBar.appendChild(this.resizerTL);\n // Add events to resize on click and drag.\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\n }\n\n /**\n * Initialize variables for Bottom-Right resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateBR(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, false);\n }\n\n /**\n * Initialize variables for Top-Left resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateTL(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, true);\n }\n\n /**\n * Common method to initialize variables at resize.\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n initializeResizeProperties(mouseEvent, leftOption) {\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n this.resizeDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Save Initial state of modal to compare on drag and obtain the difference.\n this.initialWidth = parseInt(this.container.style.width, 10);\n this.initialHeight = parseInt(this.container.style.height, 10);\n if (!leftOption) {\n this.initialRight = parseInt(this.container.style.right, 10);\n this.initialBottom = parseInt(this.container.style.bottom, 10);\n } else {\n this.leftScale = true;\n }\n if (!this.initialRight) {\n this.initialRight = 0;\n }\n if (!this.initialBottom) {\n this.initialBottom = 0;\n }\n // Disable mouse events on editor when we start to drag modal.\n document.body.style[\"user-select\"] = \"none\";\n }\n\n /**\n * This method opens the modal window, restoring the previous state, position and metrics,\n * if exists. By default the modal object opens in stack mode.\n */\n open() {\n // Removing close class.\n this.removeClass(\"wrs_closed\");\n // Hiding keyboard for mobile devices.\n const { isIOS } = this.deviceProperties;\n const { isAndroid } = this.deviceProperties;\n const { isMobile } = this.deviceProperties;\n if (isIOS || isAndroid || isMobile) {\n // Restore scale to 1.\n this.restoreWebsiteScale();\n this.lockWebsiteScroll();\n // Due to editor wait we need to wait until editor focus.\n setTimeout(() => {\n this.hideKeyboard();\n }, 400);\n }\n\n // New modal window. He need to create the whole object.\n if (!this.properties.created) {\n this.create();\n } else {\n // Previous state closed. Open method can be called even the previous state is open,\n // for example updating the content of the modal object.\n if (!this.properties.open) {\n this.properties.open = true;\n\n // Restoring the previous open state: if the modal object has been closed\n // re-open it should preserve the state and the metrics.\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\n this.restoreState();\n }\n }\n\n // Maximize window only when the configuration is set and the device is not iOs or Android.\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n\n // In iOS we need to recalculate the size of the modal object because\n // iOS keyboard is a float div which can overlay the modal object.\n if (this.deviceProperties.isIOS) {\n this.iosSoftkeyboardOpened = false;\n }\n }\n\n if (!ContentManager.isEditorLoaded()) {\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.displayEditor();\n });\n this.contentManager.addListener(listener);\n } else {\n this.displayEditor();\n }\n }\n\n /**\n * Prepares and displays the editor in the modal.\n *\n * This method is responsible for displaying the MathType editor inside the modal container.\n *\n * For Moodle environments, it applies focus protection to prevent external scripts\n * from hijacking focus away from the editor while it's open. This is particularly\n * important in Moodle which may have its own focus management scripts.\n * @returns {void}\n */\n displayEditor() {\n if (this.contentManager.integrationModel.isMoodle) {\n protect(this.container, this.overlay, this.contentContainer);\n }\n\n // Initialize and open the editor using the contentManager.\n this.contentManager.onOpen(this);\n }\n\n /**\n * Closes the modal.\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\n * @returns {Promise} A promise that resolves when the modal is closed.\n */\n async close(trigger) {\n // Remove focus protection before closing\n unprotect(this.container);\n\n this.removeClass(\"wrs_maximized\");\n this.removeClass(\"wrs_minimized\");\n this.removeClass(\"wrs_stack\");\n this.addClass(\"wrs_closed\");\n this.saveModalProperties();\n this.unlockWebsiteScroll();\n this.properties.open = false;\n\n if (trigger) {\n try {\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\n toolbar: this.contentManager.toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\n }\n }\n\n Core.globalListeners.fire(\"onModalClose\", {});\n }\n\n /**\n * Closes modal window and destroys the object.\n */\n destroy() {\n // Remove focus protection before destroying\n unprotect(this.container);\n\n // Close modal window.\n this.close();\n // Remove listeners and destroy the object.\n this.removeListeners();\n this.overlay.remove();\n this.container.remove();\n // Reset properties to allow open again.\n this.properties.created = false;\n }\n\n /**\n * Sets the website scale to one.\n */\n // eslint-disable-next-line class-methods-use-this\n restoreWebsiteScale() {\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\n // Let the equal symbols in order to search and make meta's final content.\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\n const contentAttr = viewportelement.getAttribute(\"content\");\n // If it exists, we need to maintain old values and put our values.\n if (contentAttr) {\n const attrArray = contentAttr.split(\",\");\n let finalContentMeta = \"\";\n const oldAttrs = [];\n for (let i = 0; i < attrArray.length; i += 1) {\n let isAttrToUpdate = false;\n let j = 0;\n while (!isAttrToUpdate && j < contentAttrs.length) {\n if (attrArray[i].indexOf(contentAttrs[j])) {\n isAttrToUpdate = true;\n }\n j += 1;\n }\n\n if (!isAttrToUpdate) {\n oldAttrs.push(attrArray[i]);\n }\n }\n\n for (let i = 0; i < contentAttrs.length; i += 1) {\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\n finalContentMeta += i === 0 ? attr : `,${attr}`;\n }\n\n for (let i = 0; i < oldAttrs.length; i += 1) {\n finalContentMeta += `,${oldAttrs[i]}`;\n }\n viewportelement.setAttribute(\"content\", finalContentMeta);\n // It needs to set to empty because setAttribute refresh only when attribute is different.\n viewportelement.setAttribute(\"content\", \"\");\n viewportelement.setAttribute(\"content\", contentAttr);\n } else {\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\n viewportelement.removeAttribute(\"content\");\n }\n };\n\n if (!viewportmeta) {\n viewportmeta = document.createElement(\"meta\");\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n viewportmeta.remove();\n } else {\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n }\n }\n\n /**\n * Locks website scroll for mobile devices.\n */\n lockWebsiteScroll() {\n this.websiteBeforeLockParameters = {\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\n windowScrollX: window.scrollX,\n windowScrollY: window.scrollY,\n };\n }\n\n /**\n * Unlocks website scroll for mobile devices.\n */\n unlockWebsiteScroll() {\n if (this.websiteBeforeLockParameters) {\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\n const { windowScrollX } = this.websiteBeforeLockParameters;\n const { windowScrollY } = this.websiteBeforeLockParameters;\n window.scrollTo(windowScrollX, windowScrollY);\n this.websiteBeforeLockParameters = null;\n }\n }\n\n /**\n * Util function to known if browser is IE11.\n * @returns {Boolean} true if the browser is IE11. false otherwise.\n */\n // eslint-disable-next-line class-methods-use-this\n isIE11() {\n if (\n navigator.userAgent.search(\"Msie/\") >= 0 ||\n navigator.userAgent.search(\"Trident/\") >= 0 ||\n navigator.userAgent.search(\"Edge/\") >= 0\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns if the current language type is RTL.\n * @return {Boolean} true if current language is RTL. false otherwise.\n */\n isRTL() {\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\n return true;\n }\n return this.rtl;\n }\n\n /**\n * Adds a class to all modal ModalDialog DOM elements.\n * @param {String} className - Class name.\n */\n addClass(className) {\n Util.addClass(this.overlay, className);\n Util.addClass(this.titleBar, className);\n Util.addClass(this.overlay, className);\n Util.addClass(this.container, className);\n Util.addClass(this.contentContainer, className);\n Util.addClass(this.stackDiv, className);\n Util.addClass(this.minimizeDiv, className);\n Util.addClass(this.maximizeDiv, className);\n Util.addClass(this.wrapper, className);\n }\n\n /**\n * Remove a class from all modal DOM elements.\n * @param {String} className - Class name.\n */\n removeClass(className) {\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.titleBar, className);\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.container, className);\n Util.removeClass(this.contentContainer, className);\n Util.removeClass(this.stackDiv, className);\n Util.removeClass(this.minimizeDiv, className);\n Util.removeClass(this.maximizeDiv, className);\n Util.removeClass(this.wrapper, className);\n }\n\n /**\n * Create modal dialog for desktop.\n */\n createModalWindowDesktop() {\n this.addClass(\"wrs_modal_desktop\");\n this.stack();\n }\n\n /**\n * Create modal dialog for non android devices.\n */\n createModalWindowAndroid() {\n this.addClass(\"wrs_modal_android\");\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\n }\n\n /**\n * Create modal dialog for iOS devices.\n */\n createModalWindowIos() {\n this.addClass(\"wrs_modal_ios\");\n // Refresh the size when the orientation is changed.\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\n }\n\n /**\n * Restore previous state, position and size of previous stacked modal dialog.\n */\n restoreState() {\n if (this.properties.state === \"maximized\") {\n // Reset states for prevent return to stack state.\n this.maximize();\n } else if (this.properties.state === \"minimized\") {\n // Reset states for prevent return to stack state.\n this.properties.state = this.properties.previousState;\n this.properties.previousState = \"\";\n this.minimize();\n } else {\n this.stack();\n }\n }\n\n /**\n * Stacks the modal object.\n */\n stack() {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"stack\";\n this.removeClass(\"wrs_maximized\");\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n this.addClass(\"wrs_stack\");\n\n // Change maximize/minimize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n this.restoreModalProperties();\n\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\n this.setResizeButtonsVisibility();\n }\n\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n this.focus();\n }\n\n /**\n * Minimizes the modal object.\n */\n minimize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n this.title.style.cursor = \"pointer\";\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\n this.stack();\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n // Setting css to prevent important tag into css style.\n this.container.style.height = \"30px\";\n this.container.style.width = \"250px\";\n this.container.style.bottom = \"0px\";\n this.container.style.right = \"10px\";\n\n this.removeListeners();\n this.properties.previousState = this.properties.state;\n this.properties.state = \"minimized\";\n this.setResizeButtonsVisibility();\n this.minimizeDiv.title = StringManager.get(\"maximize\");\n\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.removeClass(\"wrs_stack\");\n } else {\n this.removeClass(\"wrs_maximized\");\n }\n this.addClass(\"wrs_minimized\");\n\n // Change minimize icon to maximize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n }\n }\n\n /**\n * Maximizes the modal object.\n */\n maximize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n if (this.properties.state !== \"maximized\") {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"maximized\";\n }\n // Don't permit resize on maximize mode.\n this.setResizeButtonsVisibility();\n\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.container.style.left = null;\n this.container.style.top = null;\n this.removeClass(\"wrs_stack\");\n }\n\n this.addClass(\"wrs_maximized\");\n\n // Change maximize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n // Set size to 80% screen with a max size.\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\n if (this.container.clientHeight > 700) {\n this.container.style.height = \"700px\";\n }\n if (this.container.clientWidth > 1200) {\n this.container.style.width = \"1200px\";\n }\n\n // Setting modal position in center on screen.\n const { innerHeight } = window;\n const { innerWidth } = window;\n const { offsetHeight } = this.container;\n const { offsetWidth } = this.container;\n const bottom = innerHeight / 2 - offsetHeight / 2;\n const right = innerWidth / 2 - offsetWidth / 2;\n\n this.setPosition(bottom, right);\n this.recalculateScale();\n this.recalculatePosition();\n this.recalculateSize();\n this.focus();\n }\n\n /**\n * Expand again the modal object from a minimized state.\n */\n reExpand() {\n if (this.properties.state === \"minimized\") {\n if (this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n this.stack();\n }\n this.title.style.cursor = \"\";\n }\n }\n\n /**\n * Sets modal size.\n * @param {Number} height - Height of the ModalDialog\n * @param {Number} width - Width of the ModalDialog.\n */\n setSize(height, width) {\n this.container.style.height = `${height}px`;\n this.container.style.width = `${width}px`;\n this.recalculateSize();\n }\n\n /**\n * Sets modal position using bottom and right style attributes.\n * @param {number} bottom - bottom attribute.\n * @param {number} right - right attribute.\n */\n setPosition(bottom, right) {\n this.container.style.bottom = `${bottom}px`;\n this.container.style.right = `${right}px`;\n }\n\n /**\n * Saves position and size parameters of and open ModalDialog. This attributes\n * are needed to restore it on re-open.\n */\n saveModalProperties() {\n // Saving values of modal only when modal is in stack state.\n if (this.properties.state === \"stack\") {\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\n this.properties.position.right = parseInt(this.container.style.right, 10);\n this.properties.size.width = parseInt(this.container.style.width, 10);\n this.properties.size.height = parseInt(this.container.style.height, 10);\n }\n }\n\n /**\n * Restore ModalDialog position and size parameters.\n */\n restoreModalProperties() {\n if (this.properties.state === \"stack\") {\n // Restoring Bottom and Right values from last modal.\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\n // Restoring Height and Left values from last modal.\n this.setSize(this.properties.size.height, this.properties.size.width);\n }\n }\n\n /**\n * Sets the modal dialog initial size.\n */\n recalculateSize() {\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\n }\n\n /**\n * Enable or disable visibility of resize buttons in modal window depend on state.\n */\n setResizeButtonsVisibility() {\n if (this.properties.state === \"stack\") {\n this.resizerTL.style.visibility = \"visible\";\n this.resizerBR.style.visibility = \"visible\";\n } else {\n this.resizerTL.style.visibility = \"hidden\";\n this.resizerBR.style.visibility = \"hidden\";\n }\n }\n\n /**\n * Makes an object draggable adding mouse and touch events.\n */\n addListeners() {\n // Button events (maximize, minimize, stack and close).\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\n this.maximizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n }\n },\n true,\n );\n this.stackDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.minimizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.closeDiv.addEventListener(\"keypress\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n });\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\n\n // Overlay events (close).\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n // Mouse events.\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\n // Key events.\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\n }\n\n /**\n * Removes draggable events from an object.\n */\n removeListeners() {\n // Mouse events.\n Util.removeEvent(window, \"mousedown\", this.startDrag);\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\n Util.removeEvent(window, \"mousemove\", this.drag);\n Util.removeEvent(window, \"resize\", this.onWindowResize);\n // Key events.\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\n }\n\n /**\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\n * @param {MouseEvent} mouseEvent - Mouse event.\n * @return {Object} With the X and Y coordinates.\n */\n // eslint-disable-next-line class-methods-use-this\n eventClient(mouseEvent) {\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\n const client = {\n X: mouseEvent.changedTouches[0].clientX,\n Y: mouseEvent.changedTouches[0].clientY,\n };\n return client;\n }\n const client = {\n X: mouseEvent.clientX,\n Y: mouseEvent.clientY,\n };\n return client;\n }\n\n /**\n * Start drag function: set the object dragDataObject with the draggable\n * object offsets coordinates.\n * when drag starts (on touchstart or mousedown events).\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\n */\n startDrag(mouseEvent) {\n if (this.properties.state === \"minimized\") {\n return;\n }\n if (mouseEvent.target === this.title) {\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\n // Save first click mouse point on screen.\n this.dragDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Reset last drag position when start drag.\n this.lastDrag = {\n x: \"0px\",\n y: \"0px\",\n };\n // Init right and bottom values for window modal if it isn't exist.\n if (this.container.style.right === \"\") {\n this.container.style.right = \"0px\";\n }\n if (this.container.style.bottom === \"\") {\n this.container.style.bottom = \"0px\";\n }\n\n // Needed for IE11 for apply disabled mouse events on editor because\n // internet explorer needs a dynamic object to apply this property.\n if (this.isIE11()) {\n // this.iframe.style['position'] = 'relative';\n }\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n // Obtain screen limits for prevent overflow.\n this.limitWindow = this.getLimitWindow();\n }\n }\n }\n\n /**\n * Updates dragDataObject with the draggable object coordinates when\n * the draggable object is being moved.\n * @param {MouseEvent} mouseEvent - The mouse event.\n */\n drag(mouseEvent) {\n if (this.dragDataObject) {\n mouseEvent.preventDefault();\n // Calculate max and min between actual mouse position and limit of screeen.\n // It restric the movement of modal into window.\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\n // Subtract limit with first position to obtain relative pixels increment\n // to the anchor point.\n const dragX = `${limitX - this.dragDataObject.x}px`;\n const dragY = `${limitY - this.dragDataObject.y}px`;\n // Save last valid position of modal before window overflow.\n this.lastDrag = {\n x: dragX,\n y: dragY,\n };\n // This move modal with hardware acceleration.\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\n }\n if (this.resizeDataObject) {\n const { innerWidth } = window;\n const { innerHeight } = window;\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\n if (limitX < 0) {\n limitX = 0;\n }\n\n if (limitY < 0) {\n limitY = 0;\n }\n\n let scaleMultiplier;\n if (this.leftScale) {\n scaleMultiplier = -1;\n } else {\n scaleMultiplier = 1;\n }\n\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\n if (!this.leftScale) {\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\n } else {\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\n this.container.style.width = \"580px\";\n }\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\n } else {\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\n this.container.style.height = \"338px\";\n }\n }\n this.recalculateScale();\n this.recalculatePosition();\n }\n }\n\n /**\n * Returns the boundaries of actual window to limit modal movement.\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\n */\n getLimitWindow() {\n // Obtain dimensions of window page.\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight;\n\n // Calculate relative position of mouse point into window.\n const { offsetHeight } = this.container;\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\n const contStyleRight = parseInt(this.container.style.right, 10);\n\n const { pageXOffset } = window;\n const dragY = this.dragDataObject.y;\n const dragX = this.dragDataObject.x;\n\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\n\n // Calculate limits with sizes of window, modal and mouse position.\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\n const minPointer = { x: minPointerX, y: minPointerY };\n const maxPointer = { x: maxPointerX, y: maxPointerY };\n return { minPointer, maxPointer };\n }\n\n /**\n * Returns the scrollbar width size of browser\n * @returns {Number} The scrollbar width.\n */\n // eslint-disable-next-line class-methods-use-this\n getScrollBarWidth() {\n // Create a paragraph with full width of page.\n const inner = document.createElement(\"p\");\n inner.style.width = \"100%\";\n inner.style.height = \"200px\";\n\n // Create a hidden div to compare sizes.\n const outer = document.createElement(\"div\");\n outer.style.position = \"absolute\";\n outer.style.top = \"0px\";\n outer.style.left = \"0px\";\n outer.style.visibility = \"hidden\";\n outer.style.width = \"200px\";\n outer.style.height = \"150px\";\n outer.style.overflow = \"hidden\";\n outer.appendChild(inner);\n\n document.body.appendChild(outer);\n const widthOuter = inner.offsetWidth;\n\n // Change type overflow of paragraph for measure scrollbar.\n outer.style.overflow = \"scroll\";\n let widthInner = inner.offsetWidth;\n\n // If measure is the same, we compare with internal div.\n if (widthOuter === widthInner) {\n widthInner = outer.clientWidth;\n }\n document.body.removeChild(outer);\n\n return widthOuter - widthInner;\n }\n\n /**\n * Set the dragDataObject to null.\n */\n stopDrag() {\n // Due to we have multiple events that call this function, we need only to execute\n // the next modifiers one time,\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\n if (this.dragDataObject || this.resizeDataObject) {\n // If modal doesn't change, it's not necessary to set position with interpolation.\n this.container.style.transform = \"\";\n if (this.dragDataObject) {\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\n }\n // We make focus on editor after drag modal windows to prevent lose focus.\n this.focus();\n // Restore mouse events on iframe.\n // this.iframe.style['pointer-events'] = 'auto';\n document.body.style[\"user-select\"] = \"\";\n // Restore static state of iframe if we use Internet Explorer.\n if (this.isIE11()) {\n // this.iframe.style['position'] = null;\n }\n // Active text select event.\n Util.removeClass(document.body, \"wrs_noselect\");\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\n }\n this.dragDataObject = null;\n this.resizeDataObject = null;\n this.initialWidth = null;\n this.leftScale = null;\n }\n\n /**\n * Recalculates scale for modal when resize browser window.\n */\n onWindowResize() {\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n }\n\n /**\n * Triggers keyboard events:\n * - Tab key tab to go to submit button.\n * - Esc key to close the modal dialog.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Popupmessage is not oppened.\n if (this.popup.overlayWrapper.style.display !== \"block\") {\n // Code to detect Esc event\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n if (this.properties.open) {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.cancelButton) {\n this.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.submitButton) {\n this.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n }\n } else {\n // Popupmessage oppened.\n this.popup.onKeyDown(keyboardEvent);\n }\n }\n }\n\n /**\n * Recalculating position for modal dialog when the browser is resized.\n */\n recalculatePosition() {\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\n if (parseInt(this.container.style.right, 10) < 0) {\n this.container.style.right = \"0px\";\n }\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\n if (parseInt(this.container.style.bottom, 10) < 0) {\n this.container.style.bottom = \"0px\";\n }\n }\n\n /**\n * Recalculating scale for modal when the browser is resized.\n */\n recalculateScale() {\n let sizeModified = false;\n\n if (parseInt(this.container.style.width, 10) > 580) {\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\n sizeModified = true;\n } else {\n this.container.style.width = \"580px\";\n sizeModified = true;\n }\n\n if (parseInt(this.container.style.height, 10) > 338) {\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\n sizeModified = true;\n } else {\n this.container.style.height = \"338px\";\n sizeModified = true;\n }\n\n if (sizeModified) {\n this.recalculateSize();\n }\n }\n\n /**\n * Recalculating width of browser scroll bar.\n */\n recalculateScrollBar() {\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\n if (this.hasScrollBar) {\n this.scrollbarWidth = this.getScrollBarWidth();\n } else {\n this.scrollbarWidth = 0;\n }\n }\n\n /**\n * Hide soft keyboards on iOS devices.\n */\n // eslint-disable-next-line class-methods-use-this\n hideKeyboard() {\n // iOS keyboard can't be detected or hide directly from JavaScript.\n // So, this method simulates that user focus a text input and blur\n // the selection.\n const inputField = document.createElement(\"input\");\n this.container.appendChild(inputField);\n inputField.focus();\n inputField.blur();\n // Is removed to not see it.\n inputField.remove();\n }\n\n /**\n * Focus to contentManager object.\n */\n focus() {\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\n this.contentManager.onFocus();\n }\n }\n\n /**\n * Returns true when the device is on portrait mode.\n */\n // eslint-disable-next-line class-methods-use-this\n portraitMode() {\n return window.innerHeight > window.innerWidth;\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is opened.\n */\n handleOpenedIosSoftkeyboard() {\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\n if (this.portraitMode()) {\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\n }\n }\n this.iosSoftkeyboardOpened = true;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is closed.\n */\n handleClosedIosSoftkeyboard() {\n this.iosSoftkeyboardOpened = false;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Change container sizes when orientation is changed on iOS.\n */\n orientationChangeIosSoftkeyboard() {\n if (this.iosSoftkeyboardOpened) {\n if (this.portraitMode()) {\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\n }\n } else {\n this.wrapper.style.flexGrow = \"1\";\n }\n }\n\n /**\n * Change container sizes when orientation is changed on Android.\n */\n orientationChangeAndroidSoftkeyboard() {\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Set iframe container height.\n * @param {Number} height - New height.\n */\n setContainerHeight(height) {\n this.iosDivHeight = height;\n this.wrapper.style.height = height;\n }\n\n /**\n * Check content of editor before close action.\n */\n showPopUpMessage() {\n if (this.properties.state === \"minimized\") {\n this.stack();\n }\n this.popup.show();\n }\n\n /**\n * Sets the title of the modal dialog.\n * @param {String} title - Modal dialog title.\n */\n setTitle(title) {\n this.title.innerHTML = title;\n }\n\n /**\n * Returns the id of an element, adding the instance number to\n * the element class name:\n * className --> className[idNumber]\n * @param {String} className - The element class name.\n * @returns {String} A string appending the instance id to the className.\n */\n getElementId(className) {\n return `${className}[${this.instanceId}]`;\n }\n}\n","/* eslint-disable */\nvar polyfills;\nexport default polyfills;\n\n// Polyfills.\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\nif (!String.prototype.codePointAt) {\n (function () {\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\n var codePointAt = function (position) {\n if (this == null) {\n throw TypeError();\n }\n var string = String(this);\n var size = string.length;\n // `ToInteger`\n var index = position ? Number(position) : 0;\n if (index != index) {\n // better `isNaN`\n index = 0;\n }\n // Account for out-of-bounds indices:\n if (index < 0 || index >= size) {\n return undefined;\n }\n // Get the first code unit\n var first = string.charCodeAt(index);\n var second;\n if (\n // check if itโ€™s the start of a surrogate pair\n first >= 0xd800 &&\n first <= 0xdbff && // high surrogate\n size > index + 1 // there is a next code unit\n ) {\n second = string.charCodeAt(index + 1);\n if (second >= 0xdc00 && second <= 0xdfff) {\n // low surrogate\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\n }\n }\n return first;\n };\n if (Object.defineProperty) {\n Object.defineProperty(String.prototype, \"codePointAt\", {\n value: codePointAt,\n configurable: true,\n writable: true,\n });\n } else {\n String.prototype.codePointAt = codePointAt;\n }\n })();\n}\n\n// Object.assign polyfill.\nif (typeof Object.assign != \"function\") {\n // Must be writable: true, enumerable: false, configurable: true\n Object.defineProperty(Object, \"assign\", {\n value: function assign(target, varArgs) {\n // .length of function is 2\n \"use strict\";\n if (target == null) {\n // TypeError if undefined or null\n throw new TypeError(\"Cannot convert undefined or null to object\");\n }\n\n var to = Object(target);\n\n for (var index = 1; index < arguments.length; index++) {\n var nextSource = arguments[index];\n\n if (nextSource != null) {\n // Skip over if undefined or null\n for (var nextKey in nextSource) {\n // Avoid bugs when hasOwnProperty is shadowed\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\n to[nextKey] = nextSource[nextKey];\n }\n }\n }\n }\n return to;\n },\n writable: true,\n configurable: true,\n });\n}\n\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\nif (!Array.prototype.includes) {\n Object.defineProperty(Array.prototype, \"includes\", {\n value: function (searchElement, fromIndex) {\n if (this == null) {\n throw new TypeError('\"this\" s null or is not defined');\n }\n\n // 1. Let O be ? ToObject(this value).\n var o = Object(this);\n\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\n var len = o.length >>> 0;\n\n // 3. if len is 0, return false.\n if (len === 0) {\n return false;\n }\n\n // 4. Let n be ? ToInteger(fromIndex).\n // (if fromIndex is undefinedo, this step generates the value 0.)\n var n = fromIndex | 0;\n\n // 5. if n โ‰ฅ 0, then\n // a. Let k be n.\n // 6. Else n < 0,\n // a. Let k be len + n.\n // b. if k < 0, let k be 0.\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\n\n function sameValueZero(x, y) {\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\n }\n\n // 7. Repeat while k < len\n while (k < len) {\n // a. let element k be the result of ? Get(O, ! ToString(k)).\n // b. if SameValueZero(searchElement, elementK) is true, return true.\n if (sameValueZero(o[k], searchElement)) {\n return true;\n }\n // c. Increase k by 1.\n k++;\n }\n\n // 8. Return false\n return false;\n },\n });\n}\n\nif (!String.prototype.includes) {\n String.prototype.includes = function (search, start) {\n \"use strict\";\n\n if (search instanceof RegExp) {\n throw TypeError(\"first argument must not be a RegExp\");\n }\n if (start === undefined) {\n start = 0;\n }\n return this.indexOf(search, start) !== -1;\n };\n}\n\nif (!String.prototype.startsWith) {\n Object.defineProperty(String.prototype, \"startsWith\", {\n value: function (search, rawPos) {\n var pos = rawPos > 0 ? rawPos | 0 : 0;\n return this.substring(pos, pos + search.length) === search;\n },\n });\n}\n","import Parser from \"./parser\";\nimport Util from \"./util\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport CustomEditors from \"./customeditors\";\nimport Configuration from \"./configuration\";\nimport jsProperties from \"./jsvariables\";\nimport Event from \"./event\";\nimport Listeners from \"./listeners\";\nimport Image from \"./image\";\nimport ServiceProvider from \"./serviceprovider\";\nimport ModalDialog from \"./modal\";\nimport Telemeter from \"./telemeter\";\nimport \"./polyfills\";\nimport \"../styles/styles.css\";\n\n/**\n * @typedef {Object} CoreProperties\n * @property {ServiceProviderProperties} serviceProviderProperties\n * - The ServiceProvider class properties. *\n */\nexport default class Core {\n /**\n * @classdesc\n * This class represents MathType integration Core, managing the following:\n * - Integration initialization.\n * - Event managing.\n * - Insertion of formulas into the edit area.\n * ```js\n * let core = new Core();\n * core.addListener(listener);\n * core.language = 'en';\n *\n * // Initializing Core class.\n * core.init(configurationService);\n * ```\n * @constructs\n * Core constructor.\n * @param {CoreProperties}\n */\n constructor(coreProperties) {\n /**\n * Language. Needed for accessibility and locales. 'en' by default.\n * @type {String}\n */\n this.language = \"en\";\n\n /**\n * Edit mode, 'images' by default. Admits the following values:\n * - images\n * - latex\n * @type {String}\n */\n this.editMode = \"images\";\n\n /**\n * Modal dialog instance.\n * @type {ModalDialog}\n */\n this.modalDialog = null;\n\n /**\n * The instance of {@link CustomEditors}. By default\n * the only custom editor is the Chemistry editor.\n * @type {CustomEditors}\n */\n this.customEditors = new CustomEditors();\n\n /**\n * Chemistry editor.\n * @type {CustomEditor}\n */\n const chemEditorParams = {\n name: \"Chemistry\",\n toolbar: \"chemistry\",\n icon: \"chem.png\",\n confVariable: \"chemEnabled\",\n title: \"ChemType\",\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\n };\n\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @typedef IntegrationEnvironment\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\n * @property {String} IntegrationEnvironment.version - Integration version.\n *\n */\n\n /**\n * The environment properties object.\n * @type {IntegrationEnvironment}\n */\n this.environment = {};\n\n /**\n * @typedef EditionProperties\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\n * False otherwise.\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\n * Null if the formula is new.\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\n * @property {Range} editionProperties.range - The range that contains the image element.\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\n */\n\n /**\n * The properties of the current edition process.\n * @type {EditionProperties}\n */\n this.editionProperties = {};\n\n this.editionProperties.isNewElement = true;\n this.editionProperties.temporalImage = null;\n this.editionProperties.latexRange = null;\n this.editionProperties.range = null;\n this.editionProperties.editionStartTime = null;\n\n /**\n * The {@link IntegrationModel} instance.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n\n /**\n * The {@link ContentManager} instance.\n * @type {ContentManager}\n */\n this.contentManager = null;\n\n /**\n * The current browser.\n * @type {String}\n */\n this.browser = (() => {\n const ua = navigator.userAgent;\n let browser = \"none\";\n if (ua.search(\"Edge/\") >= 0) {\n browser = \"EDGE\";\n } else if (ua.search(\"Chrome/\") >= 0) {\n browser = \"CHROME\";\n } else if (ua.search(\"Trident/\") >= 0) {\n browser = \"IE\";\n } else if (ua.search(\"Firefox/\") >= 0) {\n browser = \"FIREFOX\";\n } else if (ua.search(\"Safari/\") >= 0) {\n browser = \"SAFARI\";\n }\n return browser;\n })();\n\n /**\n * Plugin listeners.\n * @type {Array.}\n */\n this.listeners = new Listeners();\n\n /**\n * Service provider properties.\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = {};\n if (\"serviceProviderProperties\" in coreProperties) {\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\n } else {\n throw new Error(\"serviceProviderProperties property missing.\");\n }\n }\n\n /**\n * Static property.\n * Core listeners.\n * @private\n * @type {Listeners}\n */\n static get globalListeners() {\n return Core._globalListeners;\n }\n\n /**\n * Static property setter.\n * Set core listeners.\n * @param {Listeners} value - The property value.\n * @ignore\n */\n static set globalListeners(value) {\n Core._globalListeners = value;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * True when Core.init was called. Otherwise, false.\n * @private\n * @type {Boolean}\n */\n static get initialized() {\n return Core._initialized;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\n * @ignore\n */\n static set initialized(value) {\n Core._initialized = value;\n }\n\n /**\n * Sets the {@link Core.integrationModel} property.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link Core.environment} property.\n * @param {IntegrationEnvironment} integrationEnvironment -\n * The {@link IntegrationEnvironment} object.\n */\n setEnvironment(integrationEnvironment) {\n if (\"editor\" in integrationEnvironment) {\n this.environment.editor = integrationEnvironment.editor;\n }\n if (\"mode\" in integrationEnvironment) {\n this.environment.mode = integrationEnvironment.mode;\n }\n if (\"version\" in integrationEnvironment) {\n this.environment.version = integrationEnvironment.version;\n }\n }\n\n /**\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\n * @returns {Object} headers - key value headers.\n */\n setHeaders(headers) {\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\n Configuration.set(\"customHeaders\", headerObject);\n }\n\n /**\n * Returns the current {@link ModalDialog} instance.\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\n */\n getModalDialog() {\n return this.modalDialog;\n }\n\n /**\n * Inits the {@link Core} class, doing the following:\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\n * - Updates {@link Configuration} class with the previous configuration properties.\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\n * - Loads language strings.\n * - Fires onLoad event.\n * @param {Object} serviceParameters - Service parameters.\n */\n init() {\n if (!Core.initialized) {\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\n const jsonConfiguration = JSON.parse(jsConfiguration);\n Configuration.addConfiguration(jsonConfiguration);\n // Adding JavaScript (not backend) configuration variables.\n Configuration.addConfiguration(jsProperties);\n // Fire 'onLoad' event:\n // All integration must listen this event in order to know if the plugin\n // has been properly loaded.\n StringManager.language = this.language;\n this.listeners.fire(\"onLoad\", {});\n });\n\n ServiceProvider.addListener(serviceProviderListener);\n ServiceProvider.init(this.serviceProviderProperties);\n\n Core.initialized = true;\n } else {\n // Case when there are more than two editor instances.\n // After the first editor all the other editors don't need to load any file or service.\n this.listeners.fire(\"onLoad\", {});\n }\n }\n\n /**\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\n * @param {Listener} listener - The listener object.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Adds the global {@link Listener} instance to {@link Core} class.\n * @param {Listener} listener - The event listener to be added.\n * @static\n */\n static addGlobalListener(listener) {\n Core.globalListeners.add(listener);\n }\n\n beforeUpdateFormula(mathml, wirisProperties) {\n /**\n * This event is fired before updating the formula.\n * @type {Object}\n * @property {String} mathml - MathML to be transformed.\n * @property {String} editMode - Edit mode.\n * @property {Object} wirisProperties - Extra attributes for the formula.\n * @property {String} language - Formula language.\n */\n const beforeUpdateEvent = new Event();\n\n beforeUpdateEvent.mathml = mathml;\n\n // Cloning wirisProperties object\n // We don't want wirisProperties object modified.\n beforeUpdateEvent.wirisProperties = {};\n\n if (wirisProperties != null) {\n Object.keys(wirisProperties).forEach((attr) => {\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\n });\n }\n\n // Read only.\n beforeUpdateEvent.language = this.language;\n beforeUpdateEvent.editMode = this.editMode;\n\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n return {\n mathml: beforeUpdateEvent.mathml,\n wirisProperties: beforeUpdateEvent.wirisProperties,\n };\n }\n\n /**\n * Converts a MathML into it's correspondent image and inserts the image is\n * inserted in a HTMLElement target by creating\n * a new image or updating an existing one.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\n * @param {Window} windowTarget - The window element where the editable content is.\n * @param {String} mathml - The MathML.\n * @param {Array.} wirisProperties - The extra attributes for the formula.\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n /**\n * It is the object with the information of the node or latex to insert.\n * @typedef ReturnObject\n * @property {Node} [node] - The DOM node to insert.\n * @property {String} [latex] - The latex to insert.\n */\n const returnObject = {};\n\n if (!mathml) {\n this.insertElementOnSelection(null, focusElement, windowTarget);\n } else if (this.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n // this.integrationModel.getNonLatexNode is an integration wrapper\n // to have special behaviours for nonLatex.\n // Not all the integrations have special behaviours for nonLatex.\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.latex = returnObject.latex;\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\n } else {\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n }\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n } else {\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\n\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n }\n\n return returnObject;\n }\n\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\n /**\n * This event is fired after update the formula.\n * @type {Event}\n * @param {String} editMode - edit mode.\n * @param {Object} windowTarget - target window.\n * @param {Object} focusElement - target element to be focused after update.\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\n */\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.node = node;\n afterUpdateEvent.latex = latex;\n\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n return {};\n }\n\n /**\n * Sets the caret after a given Node and set the focus to the owner document.\n * @param {Node} node - The Node element.\n */\n placeCaretAfterNode(node) {\n if (node === null) return;\n\n this.integrationModel.getSelection();\n const nodeDocument = node.ownerDocument;\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\n const range = nodeDocument.createRange();\n range.setStartAfter(node);\n range.collapse(true);\n const selection = nodeDocument.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n nodeDocument.body.focus();\n }\n }\n\n /**\n * Replaces a Selection object with an HTMLElement.\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\n * @param {Window} windowTarget - The window target.\n */\n insertElementOnSelection(element, focusElement, windowTarget) {\n let mathmlOrigin = null;\n if (this.editionProperties.isNewElement) {\n if (element) {\n if (focusElement.type === \"textarea\") {\n Util.updateTextArea(focusElement, element.textContent);\n } else if (document.selection && document.getSelection === 0) {\n let range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n\n if (!(\"parentElement\" in range)) {\n windowTarget.document.execCommand(\"delete\", false);\n range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n }\n\n if (\"parentElement\" in range) {\n const temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\n temporalObject.parentNode.replaceChild(element, temporalObject);\n } else {\n // IE9 fix: parentNode() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML(Util.createObjectCode(element));\n }\n }\n } else {\n let range = null;\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n if (this.editionProperties.range) {\n ({ range } = this.editionProperties);\n this.editionProperties.range = null;\n } else {\n const editorSelection = this.integrationModel.getSelection();\n range = editorSelection.getRangeAt(0);\n }\n\n // Delete if something was surrounded.\n range.deleteContents();\n\n let node = range.startContainer;\n const position = range.startOffset;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n node = node.splitText(position);\n node.parentNode.insertBefore(element, node);\n } else if (node.nodeType === 1) {\n // ELEMENT_NODE.\n node.insertBefore(element, node.childNodes[position]);\n }\n\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n focusElement.focus();\n } else {\n const editorSelection = this.integrationModel.getSelection();\n editorSelection.removeAllRanges();\n\n if (this.editionProperties.range) {\n const { range } = this.editionProperties;\n this.editionProperties.range = null;\n editorSelection.addRange(range);\n }\n }\n } else if (this.editionProperties.latexRange) {\n if (document.selection && document.getSelection === 0) {\n this.editionProperties.isNewElement = true;\n this.editionProperties.latexRange.select();\n this.insertElementOnSelection(element, focusElement, windowTarget);\n } else {\n this.editionProperties.latexRange.deleteContents();\n this.editionProperties.latexRange.insertNode(element);\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n let item;\n // Wrapper for some integrations that can have special behaviours to show latex.\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n item = this.integrationModel.getSelectedItem(focusElement, false);\n } else {\n item = Util.getSelectedItemOnTextarea(focusElement);\n }\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\n } else {\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\n if (element && element.nodeName.toLowerCase() === \"img\") {\n // Editor empty, formula has been erased on edit.\n // There are editors (e.g: CKEditor) that put attributes in images.\n // We don't allow that behaviour in our images.\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\n // Clone is needed to maintain event references to temporalImage.\n Image.clone(element, this.editionProperties.temporalImage);\n } else {\n this.editionProperties.temporalImage.remove();\n }\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const mathml = element?.dataset?.mathml;\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove the desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n }\n\n /**\n * Opens a modal dialog containing MathType editor..\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\n */\n openModalDialog(target, isIframe) {\n // Count the time since the editor is open\n this.editionProperties.editionStartTime = Date.now();\n\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\n this.editMode = \"images\";\n\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n try {\n if (isIframe) {\n // Is needed focus the target first.\n target.contentWindow.focus();\n const selection = target.contentWindow.getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n } else {\n // Is needed focus the target first.\n target.focus();\n const selection = getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n }\n } catch (e) {\n this.editionProperties.range = null;\n }\n\n if (isIframe === undefined) {\n isIframe = true;\n }\n\n this.editionProperties.latexRange = null;\n\n if (target) {\n let selectedItem;\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\n } else {\n selectedItem = Util.getSelectedItem(target, isIframe);\n }\n\n // Check LaTeX if and only if the node is a text node (nodeType==3).\n if (selectedItem) {\n // Case when image was selected and button pressed.\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\n this.editionProperties.temporalImage = selectedItem.node;\n this.editionProperties.isNewElement = false;\n } else if (selectedItem.node.nodeType === 3) {\n // If it's a text node means that editor is working with LaTeX.\n if (this.integrationModel.getMathmlFromTextNode) {\n // If integration has this function it isn't set range due to we don't\n // know if it will be put into a textarea as a text or image.\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (mathml) {\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n }\n } else {\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (latexResult) {\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n const windowTarget = isIframe ? target.contentWindow : window;\n\n if (target.tagName.toLowerCase() !== \"textarea\") {\n if (document.selection) {\n let leftOffset = 0;\n let previousNode = latexResult.startNode.previousSibling;\n\n while (previousNode) {\n leftOffset += Util.getNodeLength(previousNode);\n previousNode = previousNode.previousSibling;\n }\n\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\n } else {\n this.editionProperties.latexRange = windowTarget.document.createRange();\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\n }\n }\n }\n }\n }\n } else if (target.tagName.toLowerCase() === \"textarea\") {\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\n this.editMode = \"latex\";\n }\n }\n\n // Setting an object with the editor parameters.\n // Editor parameters can be customized in several ways:\n // 1 - editorAttributes: Contains the default editor attributes,\n // usually the metrics in a comma separated string. Always exists.\n // 2 - editorParameters: Object containing custom editor parameters.\n // These parameters are defined in the backend. So they affects all integration instances.\n\n // The backend send the default editor attributes in a coma separated\n // with the following structure: key1=value1,key2=value2...\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\n const defaultEditorAttributes = {};\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\n const key = tempAttribute[0];\n const value = tempAttribute[1];\n defaultEditorAttributes[key] = value;\n }\n // Custom editor parameters.\n const editorAttributes = {\n language: this.language, // Default language value\n };\n // Editor parameters in backend, usually configuration.ini.\n const serverEditorParameters = Configuration.get(\"editorParameters\");\n // Editor parameters through JavaScript configuration.\n const clientEditorParameters = this.integrationModel.editorParameters;\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\n\n // Now, update backwards: if user has set a custom language, pass that back to core properties\n this.language = editorAttributes.language;\n StringManager.language = this.language;\n\n editorAttributes.rtl = this.integrationModel.rtl;\n\n const customHeaders = Configuration.get(\"customHeaders\");\n // This is not being used in the code, we are keeping it just in case it's needed.\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\n editorAttributes.customHeaders =\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\n\n const contentManagerAttributes = {};\n contentManagerAttributes.editorAttributes = editorAttributes;\n contentManagerAttributes.language = this.language;\n contentManagerAttributes.customEditors = this.customEditors;\n contentManagerAttributes.environment = this.environment;\n\n if (this.modalDialog == null) {\n this.modalDialog = new ModalDialog(editorAttributes);\n this.contentManager = new ContentManager(contentManagerAttributes);\n // When an instance of ContentManager is created we need to wait until\n // the ContentManager is ready by listening 'onLoad' event.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n });\n this.contentManager.addListener(listener);\n this.contentManager.init();\n this.modalDialog.setContentManager(this.contentManager);\n this.contentManager.setModalDialogInstance(this.modalDialog);\n } else {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n }\n this.contentManager.setIntegrationModel(this.integrationModel);\n this.modalDialog.open();\n }\n\n /**\n * Returns the {@link CustomEditors} instance.\n * @return {CustomEditors} The current {@link CustomEditors} instance.\n */\n getCustomEditors() {\n return this.customEditors;\n }\n}\n\n/**\n * Core static listeners.\n * @type {Listeners}\n * @private\n */\nCore._globalListeners = new Listeners();\n\n/**\n * Resources state. Says if they were loaded or not.\n * @type {Boolean}\n * @private\n */\nCore._initialized = false;\n","// eslint-disable-next-line no-unused-vars, import/named\nimport Core from \"./core.src\";\nimport Image from \"./image\";\nimport Listeners from \"./listeners\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Telemeter from \"./telemeter\";\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\n\n/**\n * @typedef {Object} IntegrationModelProperties\n * @property {string} configurationService - Configuration service path.\n * This parameter is needed to determine all services paths.\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\n * @property {string} integrationModelProperties.scriptName - Integration script name.\n * Usually the name of the integration script.\n * @property {Object} integrationModelProperties.environment - integration environment properties.\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\n * callback method arguments.\n * @property {string} [integrationModelProperties.version] - integration version number.\n * @property {Object} [integrationModelProperties.editorObject] - object containing\n * the integration editor instance.\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\n * false otherwise.\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\n * - The service parameters.\n * @property {Object} [integrationModelProperties.integrationParameters]\n * - Overwritten integration parameters.\n */\n\nexport default class IntegrationModel {\n /**\n * @classdesc\n * This class represents an integration model, allowing the integration script to\n * communicate with Core class. Each integration must extend this class.\n * @constructs\n * @param {IntegrationModelProperties} integrationModelProperties\n */\n constructor(integrationModelProperties) {\n /**\n * Language. Needed for accessibility and locales. English by default.\n */\n this.language = \"en\";\n\n /**\n * Service parameters\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\n\n /**\n * Configuration service path. The integration service is needed by Core class to\n * load all the backend configuration into the frontend and also to create the paths\n * of all services (all services lives in the same route). Mandatory property.\n */\n this.configurationService = \"\";\n if (\"configurationService\" in integrationModelProperties) {\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\n integrationModelProperties.configurationService,\n ]);\n }\n\n /**\n * Plugin version. Needed to stats and caching.\n * @type {string}\n */\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\n\n /**\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\n * Mandatory property.\n */\n this.target = null;\n if (\"target\" in integrationModelProperties) {\n this.target = integrationModelProperties.target;\n } else {\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\n }\n\n /**\n * Integration script name. Needed to know the plugin path.\n */\n if (\"scriptName\" in integrationModelProperties) {\n this.scriptName = integrationModelProperties.scriptName;\n }\n\n /**\n * Object containing the arguments needed by the callback function.\n */\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\n\n /**\n * Contains information about the integration environment:\n * like the name of the editor, the version, etc.\n */\n this.environment = integrationModelProperties.environment ?? {};\n\n /**\n * Indicates if the DOM target is - or not - and iframe.\n */\n this.isIframe = false;\n if (this.target != null) {\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Instance of the integration editor object. Usually the entry point to access the API\n * of a HTML editor.\n */\n this.editorObject = integrationModelProperties.editorObject ?? null;\n\n /**\n * Specifies if the direction of the text is RTL. false by default.\n */\n this.rtl = integrationModelProperties.rtl ?? false;\n\n /**\n * Specifies if the integration model exposes the locale strings. false by default.\n */\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\n\n /**\n * Specify if editor will open in hand mode only\n */\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\n\n /**\n * Indicates if an image is selected. Needed to resize the image to the original size in case\n * the image is resized.\n * @type {boolean}\n */\n this.temporalImageResizing = false;\n\n /**\n * The Core class instance associated to the integration model.\n * @type {Core}\n */\n this.core = null;\n\n /**\n * Integration model listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n // Parameters overwrite.\n if (\"integrationParameters\" in integrationModelProperties) {\n IntegrationModel.integrationParameters.forEach((parameter) => {\n if (parameter in integrationModelProperties.integrationParameters) {\n // Don't add empty parameters.\n const value = integrationModelProperties.integrationParameters[parameter];\n if (Object.keys(value).length !== 0) {\n this[parameter] = value;\n }\n }\n });\n }\n }\n\n /**\n * Init function. Usually called from the integration side once the core.js file is loaded.\n */\n init() {\n // Check if language is an object and select the property necessary\n this.language = this.getLanguage();\n\n // We need to wait until Core class is loaded ('onLoad' event) before\n // call the callback method.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.callbackFunction(this.callbackMethodArguments);\n });\n\n // Backwards compatibility.\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\n const uri = this.serviceProviderProperties.URI;\n const server = ServiceProvider.getServerLanguageFromService(uri);\n this.serviceProviderProperties.server = server;\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\n this.serviceProviderProperties.URI = subsTring;\n }\n\n let serviceParametersURI = this.serviceProviderProperties.URI;\n serviceParametersURI =\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\n ? serviceParametersURI\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\n\n this.serviceProviderProperties.URI = serviceParametersURI;\n\n const coreProperties = {};\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\n\n this.setCore(new Core(coreProperties));\n this.core.addListener(listener);\n this.core.language = this.language;\n\n // Initializing Core class.\n this.core.init();\n // TODO: Move to Core constructor.\n this.core.setEnvironment(this.environment);\n\n // No internet connection modal.\n let attributes = {};\n attributes.class = attributes.id = \"wrs_modal_offline\";\n this.offlineModal = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_content_offline\";\n this.offlineModalContent = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_close\";\n this.offlineModalClose = Util.createElement(\"span\", attributes);\n this.offlineModalClose.innerHTML = \"×\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_warn\";\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_container\";\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_warn\";\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text\";\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\n\n // Append offline modal elements\n this.offlineModalContent.appendChild(this.offlineModalClose);\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\n this.offlineModalContent.appendChild(this.offlineModalMessage);\n this.offlineModalContent.appendChild(this.offlineModalWarn);\n this.offlineModal.appendChild(this.offlineModalContent);\n document.body.appendChild(this.offlineModal);\n\n const modal = document.getElementById(\"wrs_modal_offline\");\n this.offlineModalClose.addEventListener(\"click\", () => {\n modal.style.display = \"none\";\n });\n\n // Store editor name for telemetry purposes.\n let editorName = this.environment.editor;\n editorName = editorName.slice(0, -1); // Remove version number from editors\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\n let solutionTelemeter = editorName;\n\n // If moodle, add information to hosts and solution.\n const isMoodle = !!(typeof M === \"object\" && M !== null);\n let lms;\n\n if (isMoodle) {\n solutionTelemeter = \"Moodle\";\n lms = {\n nam: \"moodle\",\n fam: \"lms\",\n ver: this.environment.moodleVersion,\n category: this.environment.moodleCourseCategory,\n course: this.environment.moodleCourseName,\n };\n if (!editorName.includes(\"TinyMCE\")) {\n editorName = \"Atto\";\n }\n }\n\n // Get the OS and its version.\n const OSData = this.getOS();\n\n // Get the broswer and its version.\n const broswerData = this.getBrowser();\n\n // Create list of hosts to send to telemetry.\n let hosts = [\n {\n nam: broswerData.detectedBrowser,\n fam: \"browser\",\n ver: broswerData.versionBrowser,\n },\n {\n nam: editorName.toLowerCase(),\n fam: \"html-editor\",\n ver: this.environment.editorVersion,\n },\n {\n nam: OSData.detectedOS,\n fam: \"os\",\n ver: OSData.versionOS,\n },\n {\n nam: window.location.hostname,\n fam: \"domain\",\n },\n lms,\n ];\n\n // Filter hosts to remove empty objects and empty keys.\n hosts = hosts.filter((element) => {\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\n return element !== undefined;\n });\n\n // Initialize telemeter\n Telemeter.init({\n solution: {\n name: `MathType for ${solutionTelemeter}`,\n version: this.version,\n },\n hosts,\n config: {\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\n },\n url: undefined,\n });\n }\n\n /**\n * Returns the Browser name and its version.\n * @return {array} - detectedBrowser = Operating System name.\n * versionBrowser = Operating System version.\n */\n getBrowser() {\n // default value for OS just in case nothing is detected\n let detectedBrowser = \"unknown\";\n let versionBrowser = \"unknown\";\n\n const userAgent = window.navigator.userAgent;\n\n if (/Brave/.test(userAgent)) {\n detectedBrowser = \"brave\";\n if (userAgent.indexOf(\"Brave/\")) {\n const start = userAgent.indexOf(\"Brave\") + 6;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\n detectedBrowser = \"edge_chromium\";\n const start = userAgent.indexOf(\"Edg/\") + 4;\n versionBrowser = userAgent\n .substring(start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Edg/.test(userAgent)) {\n detectedBrowser = \"edge\";\n let start = userAgent.indexOf(\"Edg\") + 3;\n start += userAgent.substring(start).indexOf(\"/\");\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\n detectedBrowser = \"firefox\";\n let start = userAgent.indexOf(\"Firefox\");\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/OPR/.test(userAgent)) {\n detectedBrowser = \"opera\";\n const start = userAgent.indexOf(\"OPR/\") + 4;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\n detectedBrowser = \"chrome\";\n let start = userAgent.indexOf(\"Chrome\");\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/Safari/.test(userAgent)) {\n detectedBrowser = \"safari\";\n let start = userAgent.indexOf(\"Version/\");\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n }\n\n return { detectedBrowser, versionBrowser };\n }\n\n /**\n * Returns the operating system and its version.\n * @return {array} - detectedOS = Operating System name.\n * versionOS = Operating System version.\n */\n getOS() {\n // default value for OS just in case nothing is detected\n let detectedOS = \"unknown\";\n let versionOS = \"unknown\";\n\n // Retrieve properties to easily detect the OS\n const userAgent = window.navigator.userAgent;\n const platform = window.navigator.platform;\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\n\n // Find OS and their respective versions\n if (macosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"macos\";\n if (userAgent.indexOf(\"OS X\") !== -1) {\n const start = userAgent.indexOf(\"OS X\") + 5;\n const end = userAgent.substring(start).indexOf(\" \");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (iosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"ios\";\n if (userAgent.indexOf(\"OS \") !== -1) {\n const start = userAgent.indexOf(\"OS \") + 3;\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"windows\";\n const start = userAgent.indexOf(\"Windows\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Android/.test(userAgent)) {\n detectedOS = \"android\";\n const start = userAgent.indexOf(\"Android\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/CrOS/.test(userAgent)) {\n detectedOS = \"chromeos\";\n let start = userAgent.indexOf(\"CrOS \") + 5;\n start += userAgent.substring(start).indexOf(\" \");\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\n detectedOS = \"linux\";\n }\n\n return { detectedOS, versionOS };\n }\n\n /**\n * Returns the absolute path of the integration script.\n * @return {string} - Absolute path for the integration script.\n */\n getPath() {\n if (typeof this.scriptName === \"undefined\") {\n throw new Error(\"scriptName property needed for getPath.\");\n }\n const col = document.getElementsByTagName(\"script\");\n let path = \"\";\n for (let i = 0; i < col.length; i += 1) {\n const j = col[i].src.lastIndexOf(this.scriptName);\n if (j >= 0) {\n path = col[i].src.substr(0, j - 1);\n }\n }\n return path;\n }\n\n /**\n * Returns integration model plugin version\n * @param {string} - Plugin version\n */\n getVersion() {\n return this.version;\n }\n\n /**\n * Sets the language property.\n * @param {string} language - language code.\n */\n setLanguage(language) {\n this.language = language;\n }\n\n /**\n * Sets a Core instance.\n * @param {Core} core - instance of Core class.\n */\n setCore(core) {\n this.core = core;\n core.setIntegrationModel(this);\n }\n\n /**\n * Returns the Core instance.\n * @returns {Core} instance of Core class.\n */\n getCore() {\n return this.core;\n }\n\n /**\n * Sets the object target and updates the iframe property.\n * @param {HTMLElement} target - target object.\n */\n setTarget(target) {\n this.target = target;\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Sets the editor object.\n * @param {Object} editorObject - The editor object.\n */\n setEditorObject(editorObject) {\n this.editorObject = editorObject;\n }\n\n /**\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openNewFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.dbclick = false;\n this.core.editionProperties.isNewElement = true;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openExistingFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.isNewElement = false;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Wrapper to Core.updateFormula method.\n * Transform a MathML into a image formula.\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\n * or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n */\n updateFormula(mathml) {\n if (this.editorParameters) {\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\n mathml,\n \"application/vnd.wiris.mtweb-params+json\",\n JSON.stringify(this.editorParameters),\n );\n }\n let focusElement;\n let windowTarget;\n const wirisProperties = null;\n\n if (this.isIframe) {\n focusElement = this.target.contentWindow;\n windowTarget = this.target.contentWindow;\n } else {\n focusElement = this.target;\n windowTarget = window;\n }\n\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\n }\n\n /**\n * Wrapper to Core.insertFormula method.\n * Inserts the formula in the specified target, creating\n * a new image (new formula) or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\n\n // Delete temporal image when inserted\n this.core.editionProperties.temporalImage = null;\n\n return obj;\n }\n\n /**\n * Returns the target selection.\n * @returns {Selection} target selection.\n */\n getSelection() {\n if (this.isIframe) {\n this.target.contentWindow.focus();\n return this.target.contentWindow.getSelection();\n }\n this.target.focus();\n return window.getSelection();\n }\n\n /**\n * Add events to formulas in the DOM target. The events added are the following:\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\n * to edit them.\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\n * in case the the formula is resized.\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\n * in case the formula is resized.\n */\n addEvents() {\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\n Util.addElementEvents(\n eventTarget,\n (element, event) => {\n this.doubleClickHandler(element, event);\n // Avoid creating the double click listener more than once for each element.\n // This also allows CKEditor4 to add their own double click listener.\n event.preventDefault();\n },\n (element, event) => {\n this.mousedownHandler(element, event);\n },\n (element, event) => {\n this.mouseupHandler(element, event);\n },\n );\n }\n\n /**\n * Remove events to formulas in the DOM target.\n */\n removeEvents() {\n const eventTarget =\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\n\n if (!eventTarget) {\n return;\n }\n\n Util.removeElementEvents(eventTarget);\n }\n\n /**\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\n */\n destroy() {\n this.removeEvents();\n // Destroy modal dialog if exists.\n if (this.core.modalDialog) {\n this.core.modalDialog.destroy();\n }\n\n // Remove offline modal dialog if exists.\n if (this.offlineModal) {\n this.offlineModal.remove();\n }\n\n this.editorObject = null;\n }\n\n /**\n * Handles a Double-click on the target element. Opens an editor\n * to re-edit the double-clicked formula.\n * @param {HTMLElement} element - DOM object target.\n */\n doubleClickHandler(element) {\n this.core.editionProperties.dbclick = true;\n if (element.nodeName.toLowerCase() === \"img\") {\n this.core.getCustomEditors().disable();\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (element.hasAttribute(customEditorAttributeName)) {\n const customEditor = element.getAttribute(customEditorAttributeName);\n this.core.getCustomEditors().enable(customEditor);\n }\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.core.editionProperties.temporalImage = element;\n this.core.editionProperties.isNewElement = true;\n this.openExistingFormulaEditor();\n }\n }\n }\n\n /**\n * Handles a mouse up event on the target element. Restores the image size to avoid\n * resizing formulas.\n */\n mouseupHandler() {\n if (this.temporalImageResizing) {\n setTimeout(() => {\n Image.fixAfterResize(this.temporalImageResizing);\n }, 10);\n }\n }\n\n /**\n * Handles a mouse down event on the target element. Saves the formula size to avoid\n * resizing formulas.\n * @param {HTMLElement} element - target element.\n */\n mousedownHandler(element) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.temporalImageResizing = element;\n }\n }\n }\n\n /**\n * Returns the integration language. By default the browser agent. This method\n * should be overwritten to obtain the integration language, for example using the\n * plugin API of an HTML editor.\n * @returns {string} integration language.\n */\n getLanguage() {\n return this.getBrowserLanguage();\n }\n\n /**\n * Returns the browser language.\n * @returns {string} the browser language.\n */\n // eslint-disable-next-line class-methods-use-this\n getBrowserLanguage() {\n let language = \"en\";\n if (navigator.userLanguage) {\n language = navigator.userLanguage.substring(0, 2);\n } else if (navigator.language) {\n language = navigator.language.substring(0, 2);\n } else {\n language = \"en\";\n }\n return language;\n }\n\n /**\n * This function is called once the {@link Core} is loaded. IntegrationModel class\n * will fire this method when {@link Core} 'onLoad' event is fired.\n * This method should content all the logic to init\n * the integration.\n */\n callbackFunction() {\n // It's needed to wait until the integration target is ready. The event is fired\n // from the integration side.\n const listener = Listeners.newListener(\"onTargetReady\", () => {\n this.addEvents(this.target);\n });\n this.listeners.add(listener);\n }\n\n /**\n * Function called when the content submits an action.\n */\n // eslint-disable-next-line class-methods-use-this\n notifyWindowClosed() {\n // Nothing.\n }\n\n /**\n * Wrapper.\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\n * @param {string} textNode - text node to extract the MathML.\n * @param {int} caretPosition - caret position inside the text node.\n * @returns {string} MathML inside the text node.\n */\n\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getMathmlFromTextNode(textNode, caretPosition) {}\n\n /**\n * Wrapper\n * It fills wrs event object of nonLatex with the desired data.\n * @param {Object} event - event object.\n * @param {Object} window dom window object.\n * @param {string} mathml valid mathml.\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n fillNonLatexNode(event, window, mathml) {}\n\n /**\n Wrapper.\n * Returns selected item from the target.\n * @param {HTMLElement} target - target element\n * @param {boolean} iframe\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getSelectedItem(target, isIframe) {}\n\n // Set temporal image to null and make focus come back.\n static setActionsOnCancelButtons() {\n // Make focus come back on the previous place it was when click cancel button\n const currentInstance = WirisPlugin.currentInstance;\n const editorSelection = currentInstance.getSelection();\n editorSelection.removeAllRanges();\n\n if (currentInstance.core.editionProperties.range) {\n const { range } = currentInstance.core.editionProperties;\n currentInstance.core.editionProperties.range = null;\n editorSelection.addRange(range);\n if (range.startOffset !== range.endOffset) {\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\n }\n }\n\n // eslint-disable-next-line no-undef\n if (WirisPlugin.currentInstance) {\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\n }\n }\n}\n\n// To know if the integration that extends this class implements\n// wrapper methods, they are set as undefined.\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\nIntegrationModel.prototype.fillNonLatexNode = undefined;\nIntegrationModel.prototype.getSelectedItem = undefined;\n\n/**\n * An object containing a list with the overwritable class constructor properties.\n * @type {Object}\n */\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\n","/* eslint-disable */\nvar md5;\nexport default md5;\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n delete Array.prototype.__class__;\n})();\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n})();\ndelete Array.prototype.__class__;\n// @codingStandardsIgnoreEnd\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\n\n/**\n * This class represents the MathType integration for CKEditor5.\n * @extends {IntegrationModel}\n */\nexport default class CKEditor5Integration extends IntegrationModel {\n constructor(ckeditorIntegrationModelProperties) {\n const editor = ckeditorIntegrationModelProperties.editorObject;\n\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\n }\n /**\n * CKEditor5 Integration.\n *\n * @param {integrationModelProperties} integrationModelAttributes\n */\n super(ckeditorIntegrationModelProperties);\n\n /**\n * Folder name used for the integration inside CKEditor plugins folder.\n */\n this.integrationFolderName = \"ckeditor_wiris\";\n }\n\n /**\n * @inheritdoc\n * @returns {string} - The CKEditor instance language.\n * @override\n */\n getLanguage() {\n // Returns the CKEDitor instance language taking into account that the language can be an object.\n // Try to get editorParameters.language, fail silently otherwise\n try {\n return this.editorParameters.language;\n } catch (e) {}\n const languageObject = this.editorObject.config.get(\"language\");\n if (languageObject != null) {\n if (typeof languageObject === \"object\") {\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\n return languageObject.ui;\n }\n return this.editorObject.locale.uiLanguage;\n }\n return languageObject;\n }\n return super.getLanguage();\n }\n\n /**\n * Adds callbacks to the following CKEditor listeners:\n * - 'focus' - updates the current instance.\n * - 'contentDom' - adds 'doubleclick' callback.\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\n * - 'setData' - parses the data converting MathML into images.\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\n * - 'mode' - recalculates the active element.\n */\n addEditorListeners() {\n const editor = this.editorObject;\n\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\n this.checkElement();\n }\n }\n\n /**\n * Checks the current container and assign events in case that it doesn't have them.\n * CKEditor replaces several times the element element during its execution,\n * so we must assign the events again to editor element.\n */\n checkElement() {\n const editor = this.editorObject;\n const newElement = editor.sourceElement;\n\n // If the element wasn't treated, add the events.\n if (!newElement.wirisActive) {\n this.setTarget(newElement);\n this.addEvents();\n // Set the element as treated\n newElement.wirisActive = true;\n }\n }\n\n /**\n * @inheritdoc\n * @param {HTMLElement} element - HTMLElement target.\n * @param {MouseEvent} event - event which trigger the handler.\n */\n doubleClickHandler(element, event) {\n this.core.editionProperties.dbclick = true;\n if (this.editorObject.isReadOnly === false) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\n // doubleclick event ends here.\n if (typeof event.stopPropagation !== \"undefined\") {\n // old I.E compatibility.\n event.stopPropagation();\n } else {\n event.returnValue = false;\n }\n this.core.getCustomEditors().disable();\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\n if (customEditorAttr) {\n this.core.getCustomEditors().enable(customEditorAttr);\n }\n this.core.editionProperties.temporalImage = element;\n this.openExistingFormulaEditor();\n }\n }\n }\n }\n\n /** @inheritdoc */\n static getCorePath() {\n return null; // TODO\n }\n\n /** @inheritdoc */\n callbackFunction() {\n super.callbackFunction();\n this.addEditorListeners();\n }\n\n openNewFormulaEditor() {\n // Store the editor selection as it will be lost upon opening the modal\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\n\n // Focus on the selected editor when multiple editor instances are present\n WirisPlugin.currentInstance = this;\n\n return super.openNewFormulaEditor();\n }\n\n /**\n * Replaces old formula with new MathML or inserts it in caret position if new\n * @param {String} mathml MathML to update old one or insert\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\n */\n insertMathml(mathml) {\n // This returns the value returned by the callback function (writer => {...})\n return this.editorObject.model.change((writer) => {\n const core = this.getCore();\n const selection = this.editorObject.model.document.selection;\n\n const modelElementNew = writer.createElement(\"mathml\", {\n formula: mathml,\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\n });\n\n // Obtain the DOM object corresponding to the formula\n if (core.editionProperties.isNewElement) {\n // Don't bother inserting anything at all if the MathML is empty.\n if (!mathml) return;\n\n const viewSelection =\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\n\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\n\n // Remove selection\n if (!viewSelection.isCollapsed) {\n for (const range of viewSelection.getRanges()) {\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\n }\n }\n\n // Set carret after the formula\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\n writer.setSelection(position);\n } else {\n const img = core.editionProperties.temporalImage;\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\n\n // Insert the new and remove the old one\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\n\n // If the given MathML is empty, don't insert a new formula.\n if (mathml) {\n this.editorObject.model.insertObject(modelElementNew, position);\n }\n writer.remove(modelElementOld);\n }\n\n // eslint-disable-next-line consistent-return\n return modelElementNew;\n });\n }\n\n /**\n * Finds the text node corresponding to given DOM text element.\n * @param {element} viewElement Element to find corresponding text node of.\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\n */\n findText(viewElement) {\n // eslint-disable-line consistent-return\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\n let pivot = viewElement;\n let element;\n while (!element) {\n element = this.editorObject.editing.mapper.toModelElement(\n this.editorObject.editing.view.domConverter.domToView(pivot),\n );\n pivot = pivot.parentElement;\n }\n\n // Navigate through all the subtree under `pivot` in order to find the correct text node\n const range = this.editorObject.model.createRangeIn(element);\n const descendants = Array.from(range.getItems());\n for (const node of descendants) {\n let viewElementData = viewElement.data;\n if (viewElement.nodeType === 3) {\n // Remove invisible white spaces\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\n }\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\n return node.textNode;\n }\n }\n }\n\n /** @inheritdoc */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n // eslint-disable-line no-unused-vars\n const returnObject = {};\n\n let mathmlOrigin;\n if (!mathml) {\n this.insertMathml(\"\");\n } else if (this.core.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n\n this.editorObject.model.change((writer) => {\n const { latexRange } = this.core.editionProperties;\n\n const startNode = this.findText(latexRange.startContainer);\n const endNode = this.findText(latexRange.endContainer);\n\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\n\n let range = writer.createRange(startPosition, endPosition);\n\n // When Latex is next to image/formula.\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\n // Get the position of the latex to be replaced.\n const latexEdited = `$$${Latex.getLatexFromMathML(\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\n )}$$`;\n let data = latexRange.startContainer.data;\n\n // Remove invisible characters.\n data = data.replaceAll(String.fromCharCode(8288), \"\");\n\n // Get to the start of the latex we are editing.\n const offset = data.indexOf(latexEdited);\n const dataOffset = data.substring(offset);\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\n const substring = dataOffset.substr(0, second$);\n data = data.replace(substring, \"\");\n\n if (!data) {\n startPosition = writer.createPositionBefore(startNode);\n range = startNode;\n } else {\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\n range = writer.createRange(startPosition, endPosition);\n }\n }\n\n writer.remove(range);\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\n });\n } else {\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\n try {\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\n windowTarget.document,\n );\n } catch (e) {\n const x = e.toString();\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\n this.core.modalDialog.cancelAction();\n }\n }\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.core.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\n We probably should add it here as well, but we should look further into how */\n // this.editorObject.fire('change');\n\n // Remove temporal image of inserted formula\n this.core.editionProperties.temporalImage = null;\n\n return returnObject;\n }\n\n /**\n * Function called when the content submits an action.\n */\n notifyWindowClosed() {\n this.editorObject.editing.view.focus();\n }\n}\n","/* eslint-disable max-classes-per-file */\nimport { Command } from \"ckeditor5/src/core.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\n/**\n * Command for opening the MathType editor\n */\nexport class MathTypeCommand extends Command {\n execute(options = {}) {\n // Check we get a valid integration\n // eslint-disable-next-line no-prototype-builtins\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\n }\n\n // Save the integration instance as a property of the command.\n this.integration = options.integration;\n\n // Set custom editor or disable it\n this.setEditor();\n\n // Open the editor\n this.openEditor();\n }\n\n /**\n * Sets the appropriate custom editor, if any, or disables them.\n */\n setEditor() {\n // It's possible that a custom editor was last used.\n // We need to disable it to avoid wrong behaviors.\n this.integration.core.getCustomEditors().disable();\n }\n\n /**\n * Checks whether we are editing an existing formula or a new one and opens the editor.\n */\n openEditor() {\n this.integration.core.editionProperties.dbclick = false;\n const image = this._getSelectedImage();\n if (\n typeof image !== \"undefined\" &&\n image !== null &&\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\n ) {\n this.integration.core.editionProperties.temporalImage = image;\n this.integration.openExistingFormulaEditor();\n } else {\n this.integration.openNewFormulaEditor();\n }\n }\n\n /**\n * Gets the currently selected formula image\n * @returns {Element} selected image, if any, undefined otherwise\n */\n _getSelectedImage() {\n const { selection } = this.editor.editing.view.document;\n\n // If we can not extract the formula, fall back to default behavior.\n if (selection.isCollapsed || selection.rangeCount !== 1) {\n return;\n }\n\n // Look for the wrapping the formula and then for the inside\n\n const range = selection.getFirstRange();\n\n let image;\n\n for (const span of range) {\n if (span.item.name !== \"span\") {\n return;\n }\n image = span.item.getChild(0);\n break;\n }\n\n if (!image) {\n return;\n }\n\n // eslint-disable-next-line consistent-return\n return this.editor.editing.view.domConverter.mapViewToDom(image);\n }\n}\n\n/**\n * Command for opening the ChemType editor\n */\nexport class ChemTypeCommand extends MathTypeCommand {\n setEditor() {\n this.integration.core.getCustomEditors().enable(\"chemistry\");\n }\n}\n","// CKEditor imports\nimport { Plugin } from \"ckeditor5/src/core.js\";\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\n\n// MathType API imports\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\n\n// Local imports\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\n\nimport packageInfo from \"../package.json\";\n\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\n\nexport default class MathType extends Plugin {\n static get requires() {\n return [Widget];\n }\n\n static get pluginName() {\n return \"MathType\";\n }\n\n init() {\n // Create the MathType API Integration object\n const integration = this._addIntegration();\n currentInstance = integration;\n\n // Add the MathType and ChemType commands to the editor\n this._addCommands();\n\n // Add the buttons for MathType and ChemType\n this._addViews(integration);\n\n // Registers the element in the schema\n this._addSchema();\n\n // Add the downcast and upcast converters\n this._addConverters(integration);\n\n // Expose the WirisPlugin variable to the window\n this._exposeWiris();\n }\n\n /**\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\n */\n destroy() {\n // eslint-disable-line class-methods-use-this\n currentInstance?.destroy();\n }\n\n /**\n * Create the MathType API Integration object\n * @returns {CKEditor5Integration} the integration object\n */\n _addIntegration() {\n const { editor } = this;\n\n /**\n * Integration model constructor attributes.\n * @type {integrationModelProperties}\n */\n const integrationProperties = {};\n integrationProperties.environment = {};\n integrationProperties.environment.editor = \"CKEditor5\";\n integrationProperties.environment.editorVersion = \"5.x\";\n integrationProperties.version = packageInfo.version;\n integrationProperties.editorObject = editor;\n integrationProperties.serviceProviderProperties = {};\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\n integrationProperties.serviceProviderProperties.server = \"java\";\n integrationProperties.target = editor.sourceElement;\n integrationProperties.scriptName = \"bundle.js\";\n integrationProperties.managesLanguage = true;\n // etc\n\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\n let integration;\n if (integrationProperties.target) {\n // Instance of the integration associated to this editor instance\n integration = new CKEditor5Integration(integrationProperties);\n integration.init();\n integration.listeners.fire(\"onTargetReady\", {});\n\n integration.checkElement();\n\n this.listenTo(\n editor.editing.view.document,\n \"click\",\n (evt, data) => {\n // Is Double-click\n if (data.domEvent.detail === 2) {\n integration.doubleClickHandler(data.domTarget, data.domEvent);\n evt.stop();\n }\n },\n { priority: \"highest\" },\n );\n }\n\n return integration;\n }\n\n /**\n * Add the MathType and ChemType commands to the editor\n */\n _addCommands() {\n const { editor } = this;\n\n // Add command to open the formula editor\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\n\n // Add command to open the chemistry formula editor\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\n }\n\n /**\n * Add the buttons for MathType and ChemType\n * @param {CKEditor5Integration} integration the integration object\n */\n _addViews(integration) {\n const { editor } = this;\n\n // Check if MathType editor is enabled\n if (Configuration.get(\"editorEnabled\")) {\n // Add button for the formula editor\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\n view.set({\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\n icon: mathIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"MathType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Check if ChemType editor is enabled\n if (Configuration.get(\"chemEnabled\")) {\n // Add button for the chemistry formula editor\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\n\n view.set({\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\n icon: chemIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"ChemType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Observer for the Double-click event\n editor.editing.view.addObserver(ClickObserver);\n }\n\n /**\n * Registers the element in the schema\n */\n _addSchema() {\n const { schema } = this.editor.model;\n\n schema.register(\"mathml\", {\n inheritAllFrom: \"$inlineObject\",\n allowAttributes: [\"formula\", \"htmlContent\"],\n });\n }\n\n /**\n * Add the downcast and upcast converters\n */\n _addConverters(integration) {\n const { editor } = this;\n\n // Editing view -> Model\n editor.conversion.for(\"upcast\").elementToElement({\n view: {\n name: \"span\",\n classes: \"ck-math-widget\",\n },\n model: (viewElement, { writer: modelWriter }) => {\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\n return modelWriter.createElement(\"mathml\", {\n formula,\n });\n },\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // When element was already consumed then skip it.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // If we encounter any with a LaTeX annotation inside,\n // convert it into a \"$$...$$\" string.\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\n\n // Get the formula of the (which is all its children).\n const processor = new XmlDataProcessor(editor.editing.view.document);\n\n // Only god knows why the following line makes viewItem lose all of its children,\n // so we obtain isLatex before doing this because we need viewItem's children for that.\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\n\n // and obtain the attributes of too!\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n\n // We process the document fragment\n let formula = processor.toData(viewDocumentFragment) || \"\";\n\n // And obtain the complete formula\n formula = Util.htmlSanitize(`${formula}`);\n\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\n\n /* Model node that contains what's going to actually be inserted. This can be either:\n - A element with a formula attribute set to the given formula, or\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\n const modelNode = isLatex\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\n : writer.createElement(\"mathml\", { formula });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\n \"element:img\",\n (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // Only upcast when is wiris formula\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\n return;\n }\n\n // The following code ensures that the element's name, attributes, and classes are consumed.\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\n\n // Check if we can consume the element name.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // Consume the name, attributes, and classes so nothing else processes it.\n consumable.consume(viewItem, { name: true });\n for (const attrName of viewItem.getAttributes()) {\n consumable.consume(viewItem, { attributes: [attrName] });\n }\n\n for (const className of viewItem.getClassNames()) {\n consumable.consume(viewItem, { classes: [className] });\n }\n\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n const htmlContent = Util.htmlSanitize(``);\n\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n },\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\n { priority: \"high\" },\n );\n\n /**\n * Whether the given view element has a LaTeX annotation element.\n * @param {*} math\n * @returns {bool}\n */\n function mathIsLatex(math) {\n const semantics = math.getChild(0);\n if (!semantics || semantics.name !== \"semantics\") return false;\n for (const child of semantics.getChildren()) {\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\n return true;\n }\n }\n return false;\n }\n\n function createViewWidget(modelItem, { writer: viewWriter }) {\n const widgetElement = viewWriter.createContainerElement(\"span\", {\n class: \"ck-math-widget\",\n });\n\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\n\n if (mathUIElement) {\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\n }\n\n return toWidget(widgetElement, viewWriter);\n }\n\n function createViewImage(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n const formula = modelItem.getAttribute(\"formula\");\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\n\n if (!formula && !htmlContent) {\n return null;\n }\n\n let imgElement = null;\n\n if (htmlContent) {\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\n } else if (formula) {\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\n\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\n\n // Add HTML element () to model\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\n }\n\n /* Although we use the HtmlDataProcessor to obtain the attributes,\n * we must create a new EmptyElement which is independent of the\n * DataProcessor being used by this editor instance\n */\n if (imgElement) {\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\n renderUnsafeAttributes: [\"src\"],\n });\n }\n\n return null;\n }\n\n // Model -> Editing view\n editor.conversion.for(\"editingDowncast\").elementToElement({\n model: \"mathml\",\n view: createViewWidget,\n });\n\n // Model -> Data view\n editor.conversion.for(\"dataDowncast\").elementToElement({\n model: \"mathml\",\n view: createDataString, // eslint-disable-line no-use-before-define\n });\n\n /**\n * Makes a copy of the given view node.\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\n * @returns {module:engine/view/node~Node} Copy of the node.\n */\n function clone(viewWriter, sourceNode) {\n if (sourceNode.is(\"text\")) {\n return viewWriter.createText(sourceNode.data);\n }\n if (sourceNode.is(\"element\")) {\n if (sourceNode.is(\"emptyElement\")) {\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\n }\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\n for (const child of sourceNode.getChildren()) {\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\n }\n return element;\n }\n\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\n }\n\n function createDataString(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n // Load img element\n const mathString =\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\n\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\n\n return clone(viewWriter, sourceMathElement);\n }\n\n // This stops the view selection getting into the s and messing up caret movement\n editor.editing.mapper.on(\n \"viewToModelPosition\",\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\n );\n\n // Keep a reference to the original get and set function.\n const { get, set } = editor.data;\n\n /**\n * Hack to transform $$latex$$ into in editor.getData()'s output.\n */\n editor.data.on(\n \"get\",\n (e) => {\n const output = e.return;\n const parsedResult = Parser.endParse(output);\n\n // Cleans all the semantics tag for safexml\n // including the handwritten data points\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\n },\n { priority: \"low\" },\n );\n\n /**\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\n */\n editor.data.on(\n \"set\",\n (e, args) => {\n // Retrieve the data to be set on the CKEditor.\n let modifiedData = args[0];\n // Regex to find all mathml formulas.\n const regexp = /(]*>)|()/gm;\n const formulas = [];\n let formula;\n\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\n while ((formula = regexp.exec(modifiedData)) !== null) {\n formulas.push(formula[0]);\n }\n\n // Loop to find LaTeX and formula images and replace the MathML for the both.\n formulas.forEach((formula) => {\n if (formula.includes('encoding=\"LaTeX\"')) {\n // LaTeX found.\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\n modifiedData = modifiedData.replace(formula, latex);\n } else if (formula.includes(\" 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return func.apply(thisArg, args);\n };\n}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\r\n * This class represents all the constants needed in a MathType integration among different classes.\r\n * If a constant should be used across different classes should be defined using attribute\r\n * accessors.\r\n */\r\nexport default class Constants {\r\n /**\r\n * Safe XML entities.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharactersEntities() {\r\n return {\r\n tagOpener: \"«\",\r\n tagCloser: \"»\",\r\n doubleQuote: \"¨\",\r\n realDoubleQuote: \""\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard invalid safe characters.\r\n * @type {Object}\r\n */\r\n static get safeBadBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยป<ยซ/moยป\",\r\n gtElement: \"ยซmoยป>ยซ/moยป\",\r\n ampElement: \"ยซmoยป&ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard valid safe characters.\r\n * @type{Object}\r\n */\r\n static get safeGoodBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\r\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\r\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Standard XML special characters.\r\n * @type {Object}\r\n */\r\n static get xmlCharacters() {\r\n return {\r\n id: \"xmlCharacters\",\r\n tagOpener: \"<\", // Hex: \\x3C.\r\n tagCloser: \">\", // Hex: \\x3E.\r\n doubleQuote: '\"', // Hex: \\x22.\r\n ampersand: \"&\", // Hex: \\x26.\r\n quote: \"'\", // Hex: \\x27.\r\n };\r\n }\r\n\r\n /**\r\n * Safe XML special characters. This characters are used instead the standard\r\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\r\n * special character have a UTF-8 representation.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharacters() {\r\n return {\r\n id: \"safeXmlCharacters\",\r\n tagOpener: \"ยซ\", // Hex: \\xAB.\r\n tagCloser: \"ยป\", // Hex: \\xBB.\r\n doubleQuote: \"ยจ\", // Hex: \\xA8.\r\n ampersand: \"ยง\", // Hex: \\xA7.\r\n quote: \"`\", // Hex: \\x60.\r\n realDoubleQuote: \"ยจ\",\r\n };\r\n }\r\n}\r\n","import Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a class to manage MathML objects.\r\n */\r\nexport default class MathML {\r\n /**\r\n * Checks if the mathml at position i is inside an HTML attribute or not.\r\n * @param {string} content - a string containing MathML code.\r\n * @param {number} i - search index.\r\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\r\n */\r\n static isMathmlInAttribute(content, i) {\r\n // Regex =\r\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\r\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\r\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\r\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\r\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\r\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\r\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\r\n const expression = new RegExp(regex);\r\n\r\n const actualContent = content.substring(0, i);\r\n const reversed = actualContent.split(\"\").reverse().join(\"\");\r\n const exists = expression.test(reversed);\r\n\r\n return exists;\r\n }\r\n\r\n /**\r\n * Decodes an encoded MathML with standard XML tags.\r\n * We use these entities because IE doesn't support html entities\r\n * on its attributes sometimes. Yes, sometimes.\r\n * @param {string} input - string to be decoded.\r\n * @return {string} decoded string.\r\n */\r\n static safeXmlDecode(input) {\r\n let { tagOpener } = Constants.safeXmlCharactersEntities;\r\n let { tagCloser } = Constants.safeXmlCharactersEntities;\r\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\r\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\r\n // Decoding entities.\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n // Added to fix problem due to import from 1.9.x.\r\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\r\n\r\n // Blackboard.\r\n const { ltElement } = Constants.safeBadBlackboardCharacters;\r\n const { gtElement } = Constants.safeBadBlackboardCharacters;\r\n const { ampElement } = Constants.safeBadBlackboardCharacters;\r\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\r\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\r\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\r\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\r\n }\r\n\r\n ({ tagOpener } = Constants.safeXmlCharacters);\r\n ({ tagCloser } = Constants.safeXmlCharacters);\r\n ({ doubleQuote } = Constants.safeXmlCharacters);\r\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\r\n const { ampersand } = Constants.safeXmlCharacters;\r\n const { quote } = Constants.safeXmlCharacters;\r\n\r\n // Decoding characters.\r\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.xmlCharacters.quote);\r\n\r\n // We are replacing $ by & when its part of an entity for retro-compatibility.\r\n // Now, the standard is replace ยง by &.\r\n let returnValue = \"\";\r\n let currentEntity = null;\r\n\r\n for (let i = 0; i < input.length; i += 1) {\r\n const character = input.charAt(i);\r\n if (currentEntity == null) {\r\n if (character === \"$\") {\r\n currentEntity = \"\";\r\n } else {\r\n returnValue += character;\r\n }\r\n } else if (character === \";\") {\r\n returnValue += `&${currentEntity}`;\r\n currentEntity = null;\r\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\r\n // Character is part of an entity.\r\n currentEntity += character;\r\n } else {\r\n returnValue += `$${currentEntity}`; // Is not an entity.\r\n currentEntity = null;\r\n i -= 1; // Parse again the current character.\r\n }\r\n }\r\n\r\n return returnValue;\r\n }\r\n\r\n /**\r\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\r\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\r\n * @param {string} input - input string to be encoded\r\n * @returns {string} encoded string.\r\n */\r\n static safeXmlEncode(input) {\r\n const { tagOpener } = Constants.xmlCharacters;\r\n const { tagCloser } = Constants.xmlCharacters;\r\n const { doubleQuote } = Constants.xmlCharacters;\r\n const { ampersand } = Constants.xmlCharacters;\r\n const { quote } = Constants.xmlCharacters;\r\n\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\r\n\r\n return input;\r\n }\r\n\r\n /**\r\n * Converts special symbols (> 128) to entities and replaces all textual\r\n * entities by its number entities.\r\n * @param {string} mathml - MathML string containing - or not - special symbols\r\n * @returns {string} MathML with all textual entities replaced.\r\n */\r\n static mathMLEntities(mathml) {\r\n let toReturn = \"\";\r\n\r\n for (let i = 0; i < mathml.length; i += 1) {\r\n const character = mathml.charAt(i);\r\n\r\n // Parsing > 128 characters.\r\n if (mathml.codePointAt(i) > 128) {\r\n toReturn += `&#${mathml.codePointAt(i)};`;\r\n // For UTF-32 characters we need to move the index one position.\r\n if (mathml.codePointAt(i) > 0xffff) {\r\n i += 1;\r\n }\r\n } else if (character === \"&\") {\r\n const end = mathml.indexOf(\";\", i + 1);\r\n if (end >= 0) {\r\n const container = document.createElement(\"span\");\r\n container.innerHTML = mathml.substring(i, end + 1);\r\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\r\n i = end;\r\n } else {\r\n toReturn += character;\r\n }\r\n } else {\r\n toReturn += character;\r\n }\r\n }\r\n\r\n return toReturn;\r\n }\r\n\r\n /**\r\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\r\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} MathML string with his class containing the editor toolbar string.\r\n */\r\n static addCustomEditorClassAttribute(mathml, customEditor) {\r\n let toReturn = \"\";\r\n\r\n const start = mathml.indexOf(\"\");\r\n if (mathml.indexOf(\"class\") === -1) {\r\n // Adding custom editor type.\r\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\r\n toReturn += mathml.substr(end + 1, mathml.length);\r\n return toReturn;\r\n }\r\n }\r\n return mathml;\r\n }\r\n\r\n /**\r\n * Remove a custom editor name from the MathML class attribute.\r\n * @param {string} mathml - a MathML string.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} The input MathML without customEditor name in his class.\r\n */\r\n static removeCustomEditorClassAttribute(mathml, customEditor) {\r\n // Discard MathML without the specified class.\r\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\r\n return mathml;\r\n }\r\n\r\n // Trivial case: class attribute value equal to editor name. Then\r\n // class attribute is removed.\r\n // First try to remove it with a space before if there is one\r\n // Otherwise without the space\r\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n\r\n // Non Trivial case: class attribute contains editor name.\r\n return mathml.replace(`wrs_${customEditor}`, \"\");\r\n }\r\n\r\n /**\r\n * Adds annotation tag in MathML element.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} content - value to put inside annotation tag.\r\n * @param {String} annotationEncoding - annotation encoding.\r\n * @returns {String} - 'mathml' with an annotation that contains\r\n * 'content' and encoding 'encoding'.\r\n */\r\n static addAnnotation(mathml, content, annotationEncoding) {\r\n // If contains annotation, also contains semantics tag.\r\n const containsAnnotation = mathml.indexOf(\"\");\r\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\r\n } else if (MathML.isEmpty(mathml)) {\r\n const endIndexInline = mathml.indexOf(\"/>\");\r\n const endIndexNonInline = mathml.indexOf(\">\");\r\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\r\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\r\n } else {\r\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\r\n const endMathmlContent = mathml.lastIndexOf(\"\");\r\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\r\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\r\n }\r\n\r\n return mathmlWithAnnotation;\r\n }\r\n\r\n /**\r\n * Removes specific annotation tag in MathML element.\r\n * In case of remove the unique annotation, also is removed semantics tag.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} annotationEncoding - annotation encoding to remove.\r\n * @returns {String} - 'mathml' without the annotation encoding specified.\r\n */\r\n static removeAnnotation(mathml, annotationEncoding) {\r\n let mathmlWithoutAnnotation = mathml;\r\n const openAnnotationTag = ``;\r\n const closeAnnotationTag = \"\";\r\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\r\n if (startAnnotationIndex !== -1) {\r\n let differentAnnotationFound = false;\r\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\r\n\r\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Removes semantics tag to element that contains mathml.\r\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\r\n * @param {string} element - Inner HTML text string.\r\n * @returns {string} - 'mathml' without semantics tag.\r\n */\r\n static removeSafeXMLSemantics(element) {\r\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\r\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\r\n\r\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Transforms all xml mathml occurrences that contain semantics to the same\r\n * xml mathml occurrences without semantics.\r\n * @param {string} text - string that can contain xml mathml occurrences.\r\n * @param {Constants} [characters] - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * xmlCharacters by default.\r\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\r\n */\r\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\r\n const mathTagStart = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const mathTagEndline = `/${characters.tagCloser}`;\r\n const { tagCloser } = characters;\r\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\r\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\r\n\r\n let output = \"\";\r\n let start = text.indexOf(mathTagStart);\r\n let end = 0;\r\n while (start !== -1) {\r\n output += text.substring(end, start);\r\n\r\n // MathML can be written as '' or ''.\r\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\r\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\r\n const firstTagCloser = text.indexOf(tagCloser, start);\r\n if (mathTagEndIndex !== -1) {\r\n end = mathTagEndIndex;\r\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\r\n end = mathTagEndlineIndex;\r\n }\r\n\r\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\r\n if (semanticsIndex !== -1) {\r\n const mmlTagStart = text.substring(start, semanticsIndex);\r\n const annotationIndex = text.indexOf(annotationTagStart, start);\r\n if (annotationIndex !== -1) {\r\n const startIndex = semanticsIndex + semanticsTagStart.length;\r\n const mmlContent = text.substring(startIndex, annotationIndex);\r\n output += mmlTagStart + mmlContent + mathTagEnd;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n end += mathTagEnd.length;\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n }\r\n\r\n output += text.substring(end, text.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Returns true if a MathML contains a certain class.\r\n * @param {string} mathML - input MathML.\r\n * @param {string} className - className.\r\n * @returns {boolean} true if the input MathML contains the input class.\r\n * false otherwise.\r\n * @static\r\n */\r\n static containClass(mathML, className) {\r\n const classIndex = mathML.indexOf(\"class\");\r\n if (classIndex === -1) {\r\n return false;\r\n }\r\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\r\n const classTag = mathML.substring(classIndex, classTagEndIndex);\r\n if (classTag.indexOf(className) !== -1) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns true if mathml is empty. Otherwise, false.\r\n * @param {string} mathml - valid MathML with standard XML tags.\r\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\r\n */\r\n static isEmpty(mathml) {\r\n // MathML can have the shape or ''.\r\n const closeTag = \">\";\r\n const closeTagInline = \"/>\";\r\n const firstCloseTagIndex = mathml.indexOf(closeTag);\r\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\r\n let empty = false;\r\n // MathML is always empty in the second shape.\r\n if (firstCloseTagInlineIndex !== -1) {\r\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\r\n empty = true;\r\n }\r\n }\r\n\r\n // MathML is always empty in the first shape when there aren't elements\r\n // between math tags.\r\n if (!empty) {\r\n const mathTagEndRegex = new RegExp(\"\");\r\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\r\n if (mathTagEndArray) {\r\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\r\n }\r\n }\r\n\r\n return empty;\r\n }\r\n\r\n /**\r\n * Encodes html entities inside properties.\r\n * @param {String} mathml - valid MathML with standard XML tags.\r\n * @returns {String} - 'mathml' with property entities encoded.\r\n */\r\n static encodeProperties(mathml) {\r\n // Search all the properties.\r\n const regex = /\\w+=\".*?\"/g;\r\n // Encode html entities.\r\n const replacer = (match) => {\r\n // It has the shape:\r\n // .\r\n const quoteIndex = match.indexOf('\"');\r\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\r\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\r\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\r\n return matchEncoded;\r\n };\r\n\r\n const mathmlEncoded = mathml.replace(regex, replacer);\r\n return mathmlEncoded;\r\n }\r\n}\r\n","/**\r\n * This class represents the configuration class.\r\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\r\n */\r\nexport default class Configuration {\r\n /**\r\n * Adds a properties object to {@link Configuration.properties}.\r\n * @param {Object} properties - properties to append to current properties.\r\n */\r\n static addConfiguration(properties) {\r\n Object.assign(Configuration.properties, properties);\r\n }\r\n\r\n /**\r\n * Static property.\r\n * The configuration properties object.\r\n * @private\r\n * @type {Object}\r\n */\r\n static get properties() {\r\n return Configuration._properties;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set configuration properties.\r\n * @param {Object} value - The property value.\r\n * @ignore\r\n */\r\n static set properties(value) {\r\n Configuration._properties = value;\r\n }\r\n\r\n /**\r\n * Returns the value of a property key.\r\n * @param {String} key - Property key\r\n * @returns {String} Property value\r\n */\r\n static get(key) {\r\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\r\n // Backwards compatibility.\r\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\r\n return Configuration.properties[`_wrs_conf_${key}`];\r\n }\r\n return false;\r\n }\r\n return Configuration.properties[key];\r\n }\r\n\r\n /**\r\n * Adds a new property to Configuration class.\r\n * @param {String} key - Property key.\r\n * @param {Object} value - Property value.\r\n */\r\n static set(key, value) {\r\n Configuration.properties[key] = value;\r\n }\r\n\r\n /**\r\n * Updates a property object value with new values.\r\n * @param {String} key - The property key to be updated.\r\n * @param {Object} propertyValue - Object containing the new values.\r\n */\r\n static update(key, propertyValue) {\r\n if (!Configuration.get(key)) {\r\n Configuration.set(key, propertyValue);\r\n } else {\r\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\r\n Configuration.set(key, updateProperty);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Static properties object. Stores all configuration properties.\r\n * Needed to attribute accessors.\r\n * @private\r\n * @type {Object}\r\n */\r\nConfiguration._properties = {};\r\n","export default class TextCache {\r\n /**\r\n * @classdesc\r\n * This class represent a client-side text cache class. Contains pairs of\r\n * strings (key/value) which can be retrieved in any moment. Usually used\r\n * to store AJAX responses for text services like mathml2latex\r\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Cache array property storing the cache entries.\r\n * @type {Array.}\r\n */\r\n this.cache = [];\r\n }\r\n\r\n /**\r\n * This method populates a key/value pair into the {@link this.cache} property.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @param {String} value - Cache value, usually the service response.\r\n */\r\n populate(key, value) {\r\n this.cache[key] = value;\r\n }\r\n\r\n /**\r\n * Returns the cache value associated to certain cache key.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @return {String} value - Cache value, if exists. False otherwise.\r\n */\r\n get(key) {\r\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\r\n return this.cache[key];\r\n }\r\n return false;\r\n }\r\n}\r\n","/**\r\n * This object represents a custom listener.\r\n * @typedef {Object} Listener\r\n * @property {String} Listener.eventName - The listener name.\r\n * @property {Function} Listener.callback - The listener callback function.\r\n */\r\n\r\nexport default class Listeners {\r\n /**\r\n * @classdesc\r\n * This class represents a custom listeners manager.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Array containing all custom listeners.\r\n * @type {Object[]}\r\n */\r\n this.listeners = [];\r\n }\r\n\r\n /**\r\n * Add a listener to Listener class.\r\n * @param {Object} listener - A listener object.\r\n */\r\n add(listener) {\r\n this.listeners.push(listener);\r\n }\r\n\r\n /**\r\n * Fires MathType event listeners\r\n * @param {String} eventName - event name\r\n * @param {Event} event - event object.\r\n * @return {boolean} false if event has been prevented. true otherwise.\r\n */\r\n fire(eventName, event) {\r\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\r\n if (this.listeners[i].eventName === eventName) {\r\n // Calling listener.\r\n this.listeners[i].callback(event);\r\n }\r\n }\r\n return event.defaultPrevented;\r\n }\r\n\r\n /**\r\n * Creates a new listener object.\r\n * @param {string} eventName - Event name.\r\n * @param {Object} callback - Callback function.\r\n * @returns {object} the listener object.\r\n */\r\n static newListener(eventName, callback) {\r\n const listener = {};\r\n listener.eventName = eventName;\r\n listener.callback = callback;\r\n return listener;\r\n }\r\n}\r\n","import Util from \"./util\";\r\nimport Listeners from \"./listeners\";\r\nimport Configuration from \"./configuration\";\r\n\r\n/**\r\n * @typedef {Object} ServiceProviderProperties\r\n * @property {String} URI - Service URI.\r\n * @property {String} server - Service server language.\r\n */\r\n\r\n/**\r\n * @classdesc\r\n * Class representing a serviceProvider. A serviceProvider is a class containing\r\n * an arbitrary number of services with the correspondent path.\r\n */\r\nexport default class ServiceProvider {\r\n /**\r\n * Returns Service Provider listeners.\r\n * @type {Listeners}\r\n */\r\n static get listeners() {\r\n return ServiceProvider._listeners;\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\r\n * @param {Listener} listener - Instance of {@link Listener}.\r\n */\r\n static addListener(listener) {\r\n ServiceProvider.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Fires events in Service Provider.\r\n * @param {String} eventName - Event name.\r\n * @param {Event} event - Event object.\r\n */\r\n static fireEvent(eventName, event) {\r\n ServiceProvider.listeners.fire(eventName, event);\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @type {ServiceProviderProperties}\r\n *\r\n */\r\n static get parameters() {\r\n return ServiceProvider._parameters;\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @private\r\n * @type {ServiceProviderProperties}\r\n */\r\n static set parameters(parameters) {\r\n ServiceProvider._parameters = parameters;\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Return service provider paths.\r\n * @private\r\n * @type {String}\r\n */\r\n static get servicePaths() {\r\n return ServiceProvider._servicePaths;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service paths.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set servicePaths(value) {\r\n ServiceProvider._servicePaths = value;\r\n }\r\n\r\n /**\r\n * Adds a new service to the ServiceProvider.\r\n * @param {String} service - Service name.\r\n * @param {String} path - Service path.\r\n * @static\r\n */\r\n static setServicePath(service, path) {\r\n ServiceProvider.servicePaths[service] = path;\r\n }\r\n\r\n /**\r\n * Returns the service path for a certain service.\r\n * @param {String} serviceName - Service name.\r\n * @returns {String} The service path.\r\n * @static\r\n */\r\n static getServicePath(serviceName) {\r\n return ServiceProvider.servicePaths[serviceName];\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Service provider integration path.\r\n * @type {String}\r\n */\r\n static get integrationPath() {\r\n return ServiceProvider._integrationPath;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service provider integration path.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set integrationPath(value) {\r\n ServiceProvider._integrationPath = value;\r\n }\r\n\r\n /**\r\n * Returns the server URL in the form protocol://serverName:serverPort.\r\n * @return {String} The client side server path.\r\n */\r\n static getServerURL() {\r\n const url = window.location.href;\r\n const arr = url.split(\"/\");\r\n const result = `${arr[0]}//${arr[2]}`;\r\n return result;\r\n }\r\n\r\n /**\r\n * Inits {@link this} class. Uses {@link this.integrationPath} as\r\n * base path to generate all backend services paths.\r\n * @param {Object} parameters - Function parameters.\r\n * @param {String} parameters.integrationPath - Service path.\r\n */\r\n static init(parameters) {\r\n ServiceProvider.parameters = parameters;\r\n // Services path (tech dependant).\r\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\r\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\r\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\r\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\r\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\r\n\r\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\r\n // for example: /app/service. For them we calculate the absolute URL path, i.e\r\n // protocol://domain:port/app/service\r\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\r\n const serverPath = ServiceProvider.getServerURL();\r\n configurationURI = serverPath + configurationURI;\r\n showImageURI = serverPath + showImageURI;\r\n createImageURI = serverPath + createImageURI;\r\n getMathMLURI = serverPath + getMathMLURI;\r\n serviceURI = serverPath + serviceURI;\r\n }\r\n\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\r\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\r\n ServiceProvider.setServicePath(\"service\", serviceURI);\r\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n\r\n ServiceProvider.listeners.fire(\"onInit\", {});\r\n }\r\n\r\n /**\r\n * Gets the content from an URL.\r\n * @param {String} url - Target URL.\r\n * @param {Object} [postVariables] - Object containing post variables.\r\n * null if a GET query should be done.\r\n * @returns {String} Content of the target URL.\r\n * @private\r\n * @static\r\n */\r\n static getUrl(url, postVariables) {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n const httpRequest = Util.createHttpRequest();\r\n\r\n if (httpRequest) {\r\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\r\n httpRequest.open(\"GET\", url, false);\r\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\r\n httpRequest.open(\"POST\", url, false);\r\n } else {\r\n httpRequest.open(\"POST\", currentPath + url, false);\r\n }\r\n\r\n let header = Configuration.get(\"customHeaders\");\r\n if (header) {\r\n if (typeof header === \"string\") {\r\n header = Util.convertStringToObject(header);\r\n }\r\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\r\n }\r\n\r\n if (typeof postVariables !== \"undefined\" && postVariables) {\r\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\r\n httpRequest.send(Util.httpBuildQuery(postVariables));\r\n } else {\r\n httpRequest.send(null);\r\n }\r\n\r\n return httpRequest.responseText;\r\n }\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Returns the response text of a certain service.\r\n * @param {String} service - Service name.\r\n * @param {String} postVariables - Post variables.\r\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\r\n * @returns {String} Service response text.\r\n */\r\n static getService(service, postVariables, get) {\r\n let response;\r\n if (get === true) {\r\n const getVariables = postVariables ? `?${postVariables}` : \"\";\r\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\r\n response = ServiceProvider.getUrl(serviceUrl);\r\n } else {\r\n const serviceUrl = ServiceProvider.getServicePath(service);\r\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\r\n }\r\n return response;\r\n }\r\n\r\n /**\r\n * Returns the server language of a certain service. The possible values\r\n * are: php, aspx, java and ruby.\r\n * This method has backward compatibility purposes.\r\n * @param {String} service - The configuration service.\r\n * @returns {String} - The server technology associated with the configuration service.\r\n */\r\n static getServerLanguageFromService(service) {\r\n if (service.indexOf(\".php\") !== -1) {\r\n return \"php\";\r\n }\r\n if (service.indexOf(\".aspx\") !== -1) {\r\n return \"aspx\";\r\n }\r\n if (service.indexOf(\"wirispluginengine\") !== -1) {\r\n return \"ruby\";\r\n }\r\n return \"java\";\r\n }\r\n\r\n /**\r\n * Returns the URI associated with a certain service.\r\n * @param {String} service - The service name.\r\n * @return {String} The service path.\r\n */\r\n static createServiceURI(service) {\r\n const extension = ServiceProvider.serverExtension();\r\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\r\n }\r\n\r\n static serverExtension() {\r\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\r\n return \".php\";\r\n }\r\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\r\n return \".aspx\";\r\n }\r\n return \"\";\r\n }\r\n}\r\n\r\n/**\r\n * @property {String} service - The service name.\r\n * @property {String} path - The service path.\r\n * @static\r\n */\r\nServiceProvider._servicePaths = {};\r\n\r\n/**\r\n * The integration path. Contains the path of the configuration service.\r\n * Used to define the path for all services.\r\n * @type {String}\r\n * @private\r\n */\r\nServiceProvider._integrationPath = \"\";\r\n\r\n/**\r\n * ServiceProvider static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nServiceProvider._listeners = new Listeners();\r\n\r\n/**\r\n * Service provider parameters.\r\n * @type {ServiceProviderParameters}\r\n */\r\nServiceProvider._parameters = {};\r\n","import TextCache from \"./textcache\";\r\nimport MathML from \"./mathml\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a LaTeX parser. Manages the services which allows to convert\r\n * LaTeX into MathML and MathML into LaTeX.\r\n */\r\nexport default class Latex {\r\n /**\r\n * Static property.\r\n * Return latex cache.\r\n * @private\r\n * @type {Cache}\r\n */\r\n static get cache() {\r\n return Latex._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set latex cache.\r\n * @param {Cache} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Latex._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\r\n * we call a text service with the param mathml2latex.\r\n * @param {String} mathml - MathML String.\r\n * @return {String} LaTeX string generated by the MathML argument.\r\n */\r\n static getLatexFromMathML(mathml) {\r\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\r\n /**\r\n * @type {TextCache}\r\n */\r\n const { cache } = Latex;\r\n\r\n const data = {\r\n service: \"mathml2latex\",\r\n mml: mathmlWithoutSemantics,\r\n };\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n // TODO: Error handling.\r\n let latex = \"\";\r\n\r\n if (jsonResponse.status === \"ok\") {\r\n latex = jsonResponse.result.text;\r\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\r\n // Inserting LaTeX semantics.\r\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\r\n cache.populate(latex, mathmlWithSemantics);\r\n }\r\n\r\n return latex;\r\n }\r\n\r\n /**\r\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\r\n * we call a text service with the param latex2mathml.\r\n * @param {String} latex - String containing a LaTeX formula.\r\n * @param {Boolean} includeLatexOnSemantics\r\n * - If true LaTeX would me included into MathML semantics.\r\n * @return {String} MathML string generated by the LaTeX argument.\r\n */\r\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\r\n /**\r\n * @type {TextCache}\r\n */\r\n const latexCache = Latex.cache;\r\n\r\n if (Latex.cache.get(latex)) {\r\n return Latex.cache.get(latex);\r\n }\r\n const data = {\r\n service: \"latex2mathml\",\r\n latex,\r\n };\r\n\r\n if (includeLatexOnSemantics) {\r\n data.saveLatex = \"\";\r\n }\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n let output;\r\n if (jsonResponse.status === \"ok\") {\r\n let mathml = jsonResponse.result.text;\r\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\r\n\r\n // Populate LatexCache.\r\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\r\n const content = Util.htmlEntities(latex);\r\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\r\n output = mathml;\r\n } else {\r\n output = mathml;\r\n }\r\n if (!latexCache.get(latex)) {\r\n latexCache.populate(latex, mathml);\r\n }\r\n } else {\r\n output = `$$${latex}$$`;\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML code to LaTeX.\r\n * The MathML code should containing to be converted.\r\n * @param {String} content - A string containing MathML valid code.\r\n * @param {Object} characters - An object containing special characters.\r\n * @return {String} A string containing all MathML annotated occurrences\r\n * replaced by the corresponding LaTeX code.\r\n */\r\n static parseMathmlToLatex(content, characters) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\r\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n let mathml;\r\n let startAnnotation;\r\n let closeAnnotation;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n mathml = content.substring(start, end);\r\n\r\n startAnnotation = mathml.indexOf(openTarget);\r\n if (startAnnotation !== -1) {\r\n startAnnotation += openTarget.length;\r\n closeAnnotation = mathml.indexOf(closeTarget);\r\n let latex = mathml.substring(startAnnotation, closeAnnotation);\r\n if (characters === Constants.safeXmlCharacters) {\r\n latex = MathML.safeXmlDecode(latex);\r\n }\r\n output += `$$${latex}$$`;\r\n // Populate latex into cache.\r\n\r\n Latex.cache.populate(latex, mathml);\r\n } else {\r\n output += mathml;\r\n }\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Extracts the latex of a determined position in a text.\r\n * @param {Node} textNode - textNode to extract the LaTeX\r\n * @param {Number} caretPosition - Starting position to find LaTeX.\r\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\r\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\r\n * \"$$\" by default.\r\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\r\n * @static\r\n */\r\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\r\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\r\n // Tags used for LaTeX formulas.\r\n const defaultLatexTags = {\r\n open: \"$$\",\r\n close: \"$$\",\r\n };\r\n // latexTags is an optional parameter. If is not set, use default latexTags.\r\n if (typeof latexTags === \"undefined\" || latexTags == null) {\r\n latexTags = defaultLatexTags;\r\n }\r\n // Looking for the first textNode.\r\n let startNode = textNode;\r\n\r\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n startNode = startNode.previousSibling;\r\n }\r\n\r\n /**\r\n * Returns the next latex position and node from a specific node and position.\r\n * @param {Node} currentNode - Node where searching latex.\r\n * @param {Number} currentPosition - Current position inside the currentNode.\r\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\r\n * \"$$\" by default.\r\n * @param {Boolean} tag - Tag containing the current search.\r\n * @returns {Object} Object containing the current node and the position.\r\n */\r\n function getNextLatexPosition(currentNode, currentPosition, tag) {\r\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\r\n\r\n while (position === -1) {\r\n currentNode = currentNode.nextSibling;\r\n\r\n if (!currentNode) {\r\n // TEXT_NODE.\r\n return null; // Not found.\r\n }\r\n\r\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\r\n }\r\n\r\n return {\r\n node: currentNode,\r\n position,\r\n };\r\n }\r\n\r\n /**\r\n * Determines if a node is previous, or not, to a second one.\r\n * @param {Node} node - Start node.\r\n * @param {Number} position - Start node position.\r\n * @param {Node} endNode - End node.\r\n * @param {Number} endPosition - End node position.\r\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\r\n */\r\n function isPrevious(node, position, endNode, endPosition) {\r\n if (node === endNode) {\r\n return position <= endPosition;\r\n }\r\n while (node && node !== endNode) {\r\n node = node.nextSibling;\r\n }\r\n\r\n return node === endNode;\r\n }\r\n\r\n let start;\r\n let end = {\r\n node: startNode,\r\n position: 0,\r\n };\r\n // Is supposed that open and close tags has the same length.\r\n const tagLength = latexTags.open.length;\r\n do {\r\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\r\n\r\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\r\n return null;\r\n }\r\n\r\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\r\n\r\n if (end == null) {\r\n return null;\r\n }\r\n\r\n end.position += tagLength;\r\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\r\n\r\n // Isolating latex.\r\n let latex;\r\n\r\n if (start.node === end.node) {\r\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\r\n } else {\r\n const index = start.position + tagLength;\r\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\r\n let currentNode = start.node;\r\n\r\n do {\r\n currentNode = currentNode.nextSibling;\r\n if (currentNode === end.node) {\r\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\r\n } else {\r\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\r\n }\r\n } while (currentNode !== end.node);\r\n }\r\n\r\n return {\r\n latex,\r\n startNode: start.node,\r\n startPosition: start.position,\r\n endNode: end.node,\r\n endPosition: end.position,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\r\n * @type {Cache}\r\n * @static\r\n */\r\nLatex._cache = new TextCache();\r\n","import translations from \"../lang/strings.json\";\r\n/**\r\n * This class represents a string manager. It's used to load localized strings.\r\n */\r\nexport default class StringManager {\r\n constructor() {\r\n throw new Error(\"Static class StringManager can not be instantiated.\");\r\n }\r\n\r\n /**\r\n * Returns the associated value of certain string key. If the associated value\r\n * doesn't exits returns the original key.\r\n * @param {string} key - string key\r\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\r\n * @returns {string} correspondent value. If doesn't exists original key.\r\n */\r\n static get(key, lang) {\r\n // Default language definition\r\n let { language } = this;\r\n\r\n // If parameter language, use it\r\n if (lang) {\r\n language = lang;\r\n }\r\n\r\n // Cut down on strings. e.g. en_US -> en\r\n if (language && language.length > 2) {\r\n language = language.slice(0, 2);\r\n }\r\n\r\n // Check if we support the language\r\n if (!this.strings.hasOwnProperty(language)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown language ${language} set in StringManager.`);\r\n language = \"en\";\r\n }\r\n\r\n // Check if the key is supported in the given language\r\n if (!this.strings[language].hasOwnProperty(key)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\r\n return key;\r\n }\r\n\r\n return this.strings[language][key];\r\n }\r\n}\r\n\r\n/**\r\n * Dictionary of dictionaries:\r\n * Key: language code\r\n * Value: Key: id of the string\r\n * Value: translation of the string\r\n */\r\nStringManager.strings = translations;\r\n\r\n/**\r\n * Language of the translations; English by default\r\n */\r\nStringManager.language = \"en\";\r\n","/* eslint-disable no-bitwise */\r\nimport DOMPurify from \"dompurify\";\r\nimport MathML from \"./mathml\";\r\nimport Configuration from \"./configuration\";\r\nimport Latex from \"./latex\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * This class represents an utility class.\r\n */\r\nexport default class Util {\r\n /**\r\n * Fires an event in a target.\r\n * @param {EventTarget} eventTarget - target where event should be fired.\r\n * @param {string} eventName event to fire.\r\n * @static\r\n */\r\n static fireEvent(eventTarget, eventName) {\r\n if (document.createEvent) {\r\n const eventObject = document.createEvent(\"HTMLEvents\");\r\n eventObject.initEvent(eventName, true, true);\r\n return !eventTarget.dispatchEvent(eventObject);\r\n }\r\n\r\n const eventObject = document.createEventObject();\r\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\r\n }\r\n\r\n /**\r\n * Cross-browser addEventListener/attachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - callback function.\r\n * @static\r\n */\r\n static addEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.addEventListener) {\r\n eventTarget.addEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.attachEvent) {\r\n // Backwards compatibility.\r\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * Cross-browser removeEventListener/detachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - function to remove from the event target.\r\n * @static\r\n */\r\n static removeEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.removeEventListener) {\r\n eventTarget.removeEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.detachEvent) {\r\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * A map from event target to event handlers so we can remove the event\r\n * listeners in removeElementEvents\r\n *\r\n * @type {Map}\r\n * @static\r\n */\r\n static elementEventsMap = new Map();\r\n\r\n /**\r\n * Adds the a callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\r\n * @param {Function} mousedownHandler - function to run when on mousedown event.\r\n * @param {Function} mouseupHandler - function to run when on mouseup event.\r\n * @static\r\n */\r\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\r\n // Make sure not to leak event listeners if we've already added events to\r\n // this element\r\n Util.removeElementEvents(eventTarget);\r\n\r\n let entry = {};\r\n Util.elementEventsMap.set(eventTarget, entry);\r\n\r\n if (doubleClickHandler) {\r\n entry.callbackDblclick = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n doubleClickHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n }\r\n\r\n if (mousedownHandler) {\r\n entry.callbackMousedown = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mousedownHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n }\r\n\r\n if (mouseupHandler) {\r\n entry.callbackMouseup = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mouseupHandler(element, realEvent);\r\n };\r\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\r\n // while the mouse is outside the editor text field.\r\n // This is a workaround: we trigger the event independently of where the mouse\r\n // is when we release its button.\r\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n }\r\n\r\n /**\r\n * Remove all callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @static\r\n */\r\n static removeElementEvents(eventTarget) {\r\n let entry = Util.elementEventsMap.get(eventTarget);\r\n if (!entry) {\r\n return;\r\n }\r\n\r\n Util.elementEventsMap.delete(eventTarget);\r\n\r\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n\r\n /**\r\n * Adds a class name to a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static addClass(element, className) {\r\n if (!Util.containsClass(element, className)) {\r\n element.className += ` ${className}`;\r\n }\r\n }\r\n\r\n /**\r\n * Checks if a HTMLElement contains a certain class.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the className.\r\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\r\n * @static\r\n */\r\n static containsClass(element, className) {\r\n if (element == null || !(\"className\" in element)) {\r\n return false;\r\n }\r\n\r\n const currentClasses = element.className.split(\" \");\r\n\r\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\r\n if (currentClasses[i] === className) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Remove a certain class for a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static removeClass(element, className) {\r\n let newClassName = \"\";\r\n const classes = element.className.split(\" \");\r\n\r\n for (let i = 0; i < classes.length; i += 1) {\r\n if (classes[i] !== className) {\r\n newClassName += `${classes[i]} `;\r\n }\r\n }\r\n element.className = newClassName.trim();\r\n }\r\n\r\n /**\r\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\r\n * used to parse old applets.\r\n * @param {string} text - string containing safeXml characters\r\n * @returns {string} a string with safeXml characters parsed.\r\n * @static\r\n */\r\n static convertOldXmlinitialtextAttribute(text) {\r\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\r\n // This could be removed in future.\r\n const val = \"value=\";\r\n\r\n const xitpos = text.indexOf(\"xmlinitialtext\");\r\n const valpos = text.indexOf(val, xitpos);\r\n const quote = text.charAt(valpos + val.length);\r\n const startquote = valpos + val.length + 1;\r\n const endquote = text.indexOf(quote, startquote);\r\n\r\n const value = text.substring(startquote, endquote);\r\n\r\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\r\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\r\n newvalue = newvalue.split(\"&\").join(\"ยง\");\r\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\r\n\r\n text = text.split(value).join(newvalue);\r\n return text;\r\n }\r\n\r\n /**\r\n * Convert a string representation of key-value pairs to an object.\r\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\r\n * @returns {Object} - Object containing the key-value pairs\r\n */\r\n static convertStringToObject(keyValueString) {\r\n if (!keyValueString || typeof keyValueString !== \"string\") {\r\n return {};\r\n }\r\n\r\n return keyValueString\r\n .split(\",\")\r\n .map((pair) => pair.trim().split(\"=\"))\r\n .reduce((resultObject, [key, value]) => {\r\n if (key && value) {\r\n resultObject[key] = value;\r\n }\r\n return resultObject;\r\n }, {});\r\n }\r\n\r\n /**\r\n * Cross-browser solution for creating new elements.\r\n * @param {string} tagName - tag name of the wished element.\r\n * @param {Object} attributes - an object where each key is a wished\r\n * attribute name and each value is its value.\r\n * @param {Object} [creator] - if supplied, this function will use\r\n * the \"createElement\" method from this param. Otherwise\r\n * document will be used as creator.\r\n * @returns {Element} The DOM element with the specified attributes assigned.\r\n * @static\r\n */\r\n static createElement(tagName, attributes, creator) {\r\n if (attributes === undefined) {\r\n attributes = {};\r\n }\r\n\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n let element;\r\n\r\n /*\r\n * Internet Explorer fix:\r\n * If you create a new object dynamically, you can't set a non-standard attribute.\r\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\r\n * Other browsers will throw an exception and will run the standard code.\r\n */\r\n try {\r\n let html = `<${tagName}`;\r\n\r\n Object.keys(attributes).forEach((attributeName) => {\r\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\r\n });\r\n html += \">\";\r\n element = creator.createElement(html);\r\n } catch (e) {\r\n element = creator.createElement(tagName);\r\n Object.keys(attributes).forEach((attributeName) => {\r\n element.setAttribute(attributeName, attributes[attributeName]);\r\n });\r\n }\r\n return element;\r\n }\r\n\r\n /**\r\n * Creates new HTML from it's HTML code as string.\r\n * @param {string} objectCode - html code\r\n * @returns {Element} the HTML element.\r\n * @static\r\n */\r\n static createObject(objectCode, creator) {\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\r\n objectCode = objectCode\r\n .split(\"\").join(\"\").split(\"\").join(\"\");\r\n\r\n objectCode = objectCode\r\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\r\n\r\n const container = Util.createElement(\"div\", {}, creator);\r\n container.innerHTML = objectCode;\r\n\r\n function recursiveParamsFix(object) {\r\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const param = Util.createElement(\"param\", attributesParsed, creator);\r\n\r\n // IE fix.\r\n if (param.NAME) {\r\n param.name = param.NAME;\r\n param.value = param.VALUE;\r\n }\r\n\r\n param.removeAttribute(\"wirisObject\");\r\n object.parentNode.replaceChild(param, object);\r\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\r\n applet.removeAttribute(\"wirisObject\");\r\n\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n\r\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\r\n applet.appendChild(object.childNodes[i]);\r\n i -= 1; // When we insert the object child into the applet, object loses one child.\r\n }\r\n }\r\n\r\n object.parentNode.replaceChild(applet, object);\r\n } else {\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n }\r\n }\r\n }\r\n\r\n recursiveParamsFix(container);\r\n return container.firstChild;\r\n }\r\n\r\n /**\r\n * Converts an Element to its HTML code.\r\n * @param {Element} element - entry element.\r\n * @return {string} the HTML code of the input element.\r\n * @static\r\n */\r\n static createObjectCode(element) {\r\n // In case that the image was not created, the object can be null or undefined.\r\n if (typeof element === \"undefined\" || element === null) {\r\n return null;\r\n }\r\n\r\n if (element.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let output = `<${element.tagName}`;\r\n\r\n for (let i = 0; i < element.attributes.length; i += 1) {\r\n if (element.attributes[i].specified) {\r\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\r\n }\r\n }\r\n\r\n if (element.childNodes.length > 0) {\r\n output += \">\";\r\n\r\n for (let i = 0; i < element.childNodes.length; i += 1) {\r\n output += Util.createObject(element.childNodes[i]);\r\n }\r\n\r\n output += ``;\r\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\r\n output += `>`;\r\n } else {\r\n output += \"/>\";\r\n }\r\n\r\n return output;\r\n }\r\n\r\n if (element.nodeType === 3) {\r\n // TEXT_NODE.\r\n return Util.htmlEntities(element.nodeValue);\r\n }\r\n\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Concatenates two URL paths.\r\n * @param {string} path1 - first URL path\r\n * @param {string} path2 - second URL path\r\n * @returns {string} new URL.\r\n */\r\n static concatenateUrl(path1, path2) {\r\n let separator = \"\";\r\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\r\n separator = \"/\";\r\n }\r\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all HTML special characters by their correspondent entities.\r\n * @param {string} input - text to be parsed.\r\n * @returns {string} the input text with all their special characters replaced by their entities.\r\n * @static\r\n */\r\n static htmlEntities(input) {\r\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\r\n }\r\n\r\n /**\r\n * Sanitize HTML to prevent XSS injections.\r\n * @param {string} html - html to be sanitize.\r\n * @returns {string} html sanitized.\r\n * @static\r\n */\r\n static htmlSanitize(html) {\r\n const annotationRegex = /\\/;\r\n // Get all the annotation content including the tags.\r\n const annotation = html.match(annotationRegex);\r\n // Sanitize html code without removing our supported MathML tags and attributes.\r\n html = DOMPurify.sanitize(html, {\r\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\r\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\r\n });\r\n // Readd old annotation content.\r\n return html.replace(annotationRegex, annotation);\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all the HTML entities by their characters.\r\n * @param {string} input - text to be parsed\r\n * @returns {string} the input text with all their entities replaced by characters.\r\n * @static\r\n */\r\n static htmlEntitiesDecode(input) {\r\n // Textarea element decodes when inner html is set.\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.innerHTML = input;\r\n return textarea.value;\r\n }\r\n\r\n /**\r\n * Returns a cross-browser http request.\r\n * @return {object} httpRequest request object.\r\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\r\n */\r\n static createHttpRequest() {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n if (currentPath.substr(0, 7) === \"file://\") {\r\n throw StringManager.get(\"exception_cross_site\");\r\n }\r\n\r\n if (typeof XMLHttpRequest !== \"undefined\") {\r\n return new XMLHttpRequest();\r\n }\r\n\r\n try {\r\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\r\n } catch (e) {\r\n try {\r\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\r\n } catch (oc) {\r\n return null;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Converts a hash to a HTTP query.\r\n * @param {Object[]} properties - a key/value object.\r\n * @returns {string} a HTTP query containing all the key value pairs with\r\n * all the special characters replaced by their entities.\r\n * @static\r\n */\r\n static httpBuildQuery(properties) {\r\n let result = \"\";\r\n\r\n Object.keys(properties).forEach((i) => {\r\n if (properties[i] != null) {\r\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\r\n }\r\n });\r\n\r\n // Deleting last '&' empty character.\r\n if (result.substring(result.length - 1) === \"&\") {\r\n result = result.substring(0, result.length - 1);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Convert a hash to string sorting keys to get a deterministic output\r\n * @param {Object[]} hash - a key/value object.\r\n * @returns {string} a string with the form key1=value1...keyn=valuen\r\n * @static\r\n */\r\n static propertiesToString(hash) {\r\n // 1. Sort keys. We sort the keys because we want a deterministic output.\r\n const keys = [];\r\n Object.keys(hash).forEach((key) => {\r\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\r\n keys.push(key);\r\n }\r\n });\r\n\r\n const n = keys.length;\r\n for (let i = 0; i < n; i += 1) {\r\n for (let j = i + 1; j < n; j += 1) {\r\n const s1 = keys[i];\r\n const s2 = keys[j];\r\n if (Util.compareStrings(s1, s2) > 0) {\r\n // Swap.\r\n keys[i] = s2;\r\n keys[j] = s1;\r\n }\r\n }\r\n }\r\n\r\n // 2. Generate output.\r\n let output = \"\";\r\n for (let i = 0; i < n; i += 1) {\r\n const key = keys[i];\r\n output += key;\r\n output += \"=\";\r\n let value = hash[key];\r\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\r\n value = value.replace(\"\\n\", \"\\\\n\");\r\n value = value.replace(\"\\r\", \"\\\\r\");\r\n value = value.replace(\"\\t\", \"\\\\t\");\r\n\r\n output += value;\r\n output += \"\\n\";\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Compare two strings using charCodeAt method\r\n * @param {string} a - first string to compare.\r\n * @param {string} b - second string to compare.\r\n * @returns {number} the difference between a and b\r\n * @static\r\n */\r\n static compareStrings(a, b) {\r\n let i;\r\n const an = a.length;\r\n const bn = b.length;\r\n const n = an > bn ? bn : an;\r\n for (i = 0; i < n; i += 1) {\r\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\r\n if (c !== 0) {\r\n return c;\r\n }\r\n }\r\n return a.length - b.length;\r\n }\r\n\r\n /**\r\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\r\n * @param {string} string - input string\r\n * @param {number} idx - an integer greater than or equal to 0\r\n * and less than the length of the string\r\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\r\n * @static\r\n */\r\n static fixedCharCodeAt(string, idx) {\r\n idx = idx || 0;\r\n const code = string.charCodeAt(idx);\r\n let hi;\r\n let low;\r\n\r\n /* High surrogate (could change last hex to 0xDB7F to treat high\r\n private surrogates as single characters) */\r\n\r\n if (code >= 0xd800 && code <= 0xdbff) {\r\n hi = code;\r\n low = string.charCodeAt(idx + 1);\r\n if (Number.isNaN(low)) {\r\n throw StringManager.get(\"exception_high_surrogate\");\r\n }\r\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\r\n }\r\n\r\n if (code >= 0xdc00 && code <= 0xdfff) {\r\n // Low surrogate.\r\n /* We return false to allow loops to skip this iteration since should have\r\n already handled high surrogate above in the previous iteration. */\r\n return false;\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Returns an URL with it's query params converted into array.\r\n * @param {string} url - input URL.\r\n * @returns {Object[]} an array containing all URL query params.\r\n * @static\r\n */\r\n static urlToAssArray(url) {\r\n let i;\r\n i = url.indexOf(\"?\");\r\n if (i > 0) {\r\n const query = url.substring(i + 1);\r\n const ss = query.split(\"&\");\r\n const h = {};\r\n for (i = 0; i < ss.length; i += 1) {\r\n const s = ss[i];\r\n const kv = s.split(\"=\");\r\n if (kv.length > 1) {\r\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\r\n }\r\n }\r\n return h;\r\n }\r\n return {};\r\n }\r\n\r\n /**\r\n * Returns an encoded URL by replacing each instance of certain characters by\r\n * one, two, three or four escape sequences using encodeURIComponent method.\r\n * !'()* . will not be encoded.\r\n *\r\n * @param {string} clearString - URL string to be encoded\r\n * @returns {string} URL with it's special characters replaced.\r\n * @static\r\n */\r\n static urlEncode(clearString) {\r\n let output = \"\";\r\n // Method encodeURIComponent doesn't encode !'()*~ .\r\n output = encodeURIComponent(clearString);\r\n return output;\r\n }\r\n\r\n // TODO: To parser?\r\n /**\r\n * Converts the HTML of a image into the output code that WIRIS must return.\r\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\r\n * or the Wiriscas attribute of a WIRIS applet.\r\n * @param {string} imgCode - the html code from a formula or a CAS image.\r\n * @param {boolean} convertToXml - true if the image should be converted to XML.\r\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\r\n * @returns {string} the XML or safeXML of a WIRIS image.\r\n * @static\r\n */\r\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\r\n const imgObject = Util.createObject(imgCode);\r\n if (imgObject) {\r\n if (\r\n imgObject.className === Configuration.get(\"imageClassName\") ||\r\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\r\n ) {\r\n if (!convertToXml) {\r\n return imgCode;\r\n }\r\n\r\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n // To handle annotations, first we need the MathML in XML.\r\n let mathML = MathML.safeXmlDecode(dataMathML);\r\n\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\r\n }\r\n\r\n if (mathML == null) {\r\n mathML = imgObject.getAttribute(\"alt\");\r\n }\r\n\r\n if (convertToSafeXml) {\r\n const safeMathML = MathML.safeXmlEncode(mathML);\r\n return safeMathML;\r\n }\r\n\r\n return mathML;\r\n }\r\n }\r\n return imgCode;\r\n }\r\n\r\n /**\r\n * Gets the node length in characters.\r\n * @param {Node} node - HTML node.\r\n * @returns {number} node length.\r\n * @static\r\n */\r\n static getNodeLength(node) {\r\n const staticNodeLengths = {\r\n IMG: 1,\r\n BR: 1,\r\n };\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return node.nodeValue.length;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\r\n\r\n if (length === undefined) {\r\n length = 0;\r\n }\r\n\r\n for (let i = 0; i < node.childNodes.length; i += 1) {\r\n length += Util.getNodeLength(node.childNodes[i]);\r\n }\r\n return length;\r\n }\r\n return 0;\r\n }\r\n\r\n /**\r\n * Gets a selected node or text from an editable HTMLElement.\r\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\r\n * @param {HTMLElement} target - the editable HTMLElement.\r\n * @param {boolean} isIframe - specifies if the target is an iframe or not\r\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\r\n * the current selection and uses window.getSelection()\r\n * @returns {object} an object with the 'node' key set if the item is an\r\n * element or the keys 'node' and 'caretPosition' if the element is text.\r\n * @static\r\n */\r\n static getSelectedItem(target, isIframe, forceGetSelection) {\r\n let windowTarget;\r\n\r\n if (isIframe) {\r\n windowTarget = target.contentWindow;\r\n windowTarget.focus();\r\n } else {\r\n windowTarget = window;\r\n target.focus();\r\n }\r\n\r\n if (document.selection && !forceGetSelection) {\r\n const range = windowTarget.document.selection.createRange();\r\n\r\n if (range.parentElement) {\r\n if (range.htmlText.length > 0) {\r\n if (range.text.length === 0) {\r\n return Util.getSelectedItem(target, isIframe, true);\r\n }\r\n\r\n return null;\r\n }\r\n\r\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\r\n let temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\r\n // IE9 fix: parentElement() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML('');\r\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\r\n }\r\n\r\n let node;\r\n let caretPosition;\r\n\r\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = temporalObject.nextSibling;\r\n caretPosition = 0;\r\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\r\n node = temporalObject.previousSibling;\r\n caretPosition = node.nodeValue.length;\r\n } else {\r\n node = windowTarget.document.createTextNode(\"\");\r\n temporalObject.parentNode.insertBefore(node, temporalObject);\r\n caretPosition = 0;\r\n }\r\n\r\n temporalObject.parentNode.removeChild(temporalObject);\r\n\r\n return {\r\n node,\r\n caretPosition,\r\n };\r\n }\r\n\r\n if (range.length > 1) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: range.item(0),\r\n };\r\n }\r\n\r\n if (windowTarget.getSelection) {\r\n let range;\r\n const selection = windowTarget.getSelection();\r\n\r\n try {\r\n range = selection.getRangeAt(0);\r\n } catch (e) {\r\n range = windowTarget.document.createRange();\r\n }\r\n\r\n const node = range.startContainer;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return {\r\n node,\r\n caretPosition: range.startOffset,\r\n };\r\n }\r\n\r\n if (node !== range.endContainer) {\r\n return null;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n const position = range.startOffset;\r\n\r\n if (node.childNodes[position]) {\r\n // In case that a formula is detected but not selected,\r\n // we create an empty span where we could insert the new formula.\r\n if (range.startOffset === range.endOffset) {\r\n if (\r\n position !== 0 &&\r\n node.childNodes[position - 1].localName === \"span\" &&\r\n node.childNodes[position].classList?.contains(\"Wirisformula\")\r\n ) {\r\n node.childNodes[position - 1].remove();\r\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\r\n }\r\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\r\n if (\r\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\r\n position === 0\r\n ) {\r\n const emptySpan = document.createElement(\"span\");\r\n node.insertBefore(emptySpan, node.childNodes[position]);\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Returns null if there isn't any item or if it is malformed.\r\n * Otherwise returns an object containing the node with the MathML image\r\n * and the cursor position inside the textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea element.\r\n * @returns {Object} An object containing the node, the index of the\r\n * beginning of the selected text, caret position and the start and end position of the\r\n * text node.\r\n * @static\r\n */\r\n static getSelectedItemOnTextarea(textarea) {\r\n const textNode = document.createTextNode(textarea.value);\r\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\r\n if (textNodeValues === null) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: textNode,\r\n caretPosition: textarea.selectionStart,\r\n startPosition: textNodeValues.startPosition,\r\n endPosition: textNodeValues.endPosition,\r\n };\r\n }\r\n\r\n /**\r\n * Looks for elements that match the given name in a HTML code string.\r\n * Important: this function is very concrete for WIRIS code.\r\n * It takes as preconditions lots of behaviors that are not the general case.\r\n * @param {string} code - HTML code.\r\n * @param {string} name - element name.\r\n * @param {boolean} autoClosed - true if the elements are autoClosed.\r\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\r\n * @static\r\n */\r\n static getElementsByNameFromString(code, name, autoClosed) {\r\n const elements = [];\r\n code = code.toLowerCase();\r\n name = name.toLowerCase();\r\n let start = code.indexOf(`<${name} `);\r\n\r\n while (start !== -1) {\r\n // Look for nodes.\r\n let endString;\r\n\r\n if (autoClosed) {\r\n endString = \">\";\r\n } else {\r\n endString = ``;\r\n }\r\n\r\n let end = code.indexOf(endString, start);\r\n\r\n if (end !== -1) {\r\n end += endString.length;\r\n elements.push({\r\n start,\r\n end,\r\n });\r\n } else {\r\n end = start + 1;\r\n }\r\n\r\n start = code.indexOf(`<${name} `, end);\r\n }\r\n\r\n return elements;\r\n }\r\n\r\n /**\r\n * Returns the numeric value of a base64 character.\r\n * @param {string} character - base64 character.\r\n * @returns {number} base64 character numeric value.\r\n * @static\r\n */\r\n static decode64(character) {\r\n const PLUS = \"+\".charCodeAt(0);\r\n const SLASH = \"/\".charCodeAt(0);\r\n const NUMBER = \"0\".charCodeAt(0);\r\n const LOWER = \"a\".charCodeAt(0);\r\n const UPPER = \"A\".charCodeAt(0);\r\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\r\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\r\n const code = character.charCodeAt(0);\r\n\r\n if (code === PLUS || code === PLUS_URL_SAFE) {\r\n return 62; // Char '+'.\r\n }\r\n if (code === SLASH || code === SLASH_URL_SAFE) {\r\n return 63; // Char '/'.\r\n }\r\n if (code < NUMBER) {\r\n return -1; // No match.\r\n }\r\n if (code < NUMBER + 10) {\r\n return code - NUMBER + 26 + 26;\r\n }\r\n if (code < UPPER + 26) {\r\n return code - UPPER;\r\n }\r\n if (code < LOWER + 26) {\r\n return code - LOWER + 26;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Converts a base64 string to a array of bytes.\r\n * @param {string} b64String - base64 string.\r\n * @param {number} length - dimension of byte array (by default whole string).\r\n * @return {Object[]} the resultant byte array.\r\n * @static\r\n */\r\n static b64ToByteArray(b64String, length) {\r\n let tmp;\r\n\r\n if (b64String.length % 4 > 0) {\r\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\r\n }\r\n\r\n const arr = [];\r\n\r\n let l;\r\n let placeHolders;\r\n if (!length) {\r\n // All b64String string.\r\n if (b64String.charAt(b64String.length - 2) === \"=\") {\r\n placeHolders = 2;\r\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\r\n placeHolders = 1;\r\n } else {\r\n placeHolders = 0;\r\n }\r\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\r\n } else {\r\n l = length;\r\n }\r\n\r\n let i;\r\n for (i = 0; i < l; i += 4) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 18) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\r\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\r\n Util.decode64(b64String.charAt(i + 3));\r\n\r\n arr.push((tmp >> 16) & 0xff);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n if (placeHolders) {\r\n if (placeHolders === 2) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\r\n arr.push(tmp & 0xff);\r\n } else if (placeHolders === 1) {\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 10) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\r\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n }\r\n return arr;\r\n }\r\n\r\n /**\r\n * Returns the first 32-bit signed integer from a byte array.\r\n * @param {Object[]} bytes - array of bytes.\r\n * @returns {number} the 32-bit signed integer.\r\n * @static\r\n */\r\n static readInt32(bytes) {\r\n if (bytes.length < 4) {\r\n return false;\r\n }\r\n const int32 = bytes.splice(0, 4);\r\n // @codingStandardsIgnoreStartยก\r\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read the first byte from a byte array.\r\n * @param {Object} bytes - input byte array.\r\n * @returns {number} first byte of the byte array.\r\n * @static\r\n */\r\n static readByte(bytes) {\r\n // @codingStandardsIgnoreStart\r\n return bytes.shift() << 0;\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\r\n * @param {Object[]} bytes - byte array.\r\n * @param {number} pos - start position.\r\n * @param {number} len - number of bytes to read.\r\n * @returns {Object[]} the byte array.\r\n * @static\r\n */\r\n static readBytes(bytes, pos, len) {\r\n return bytes.splice(pos, len);\r\n }\r\n\r\n /**\r\n * Inserts or modifies formulas or CAS on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea target.\r\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\r\n * call this function as (textarea, Parser.createImageSrc(mathml));\r\n * @static\r\n */\r\n static updateTextArea(textarea, text) {\r\n if (textarea && text) {\r\n textarea.focus();\r\n\r\n if (textarea.selectionStart != null) {\r\n const { selectionEnd } = textarea;\r\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\r\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\r\n textarea.value = selectionStart + text + selectionEndSub;\r\n textarea.selectionEnd = selectionEnd + text.length;\r\n } else {\r\n const selection = document.selection.createRange();\r\n selection.text = text;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Modifies existing formula on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - text area target.\r\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\r\n * to the image,you can call this function as\r\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\r\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\r\n * @param {number} end - ending index from textarea where it needs to be replaced by text\r\n * @static\r\n */\r\n static updateExistingTextOnTextarea(textarea, text, start, end) {\r\n textarea.focus();\r\n const textareaStart = textarea.value.substring(0, start);\r\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\r\n textarea.selectionEnd = start + text.length;\r\n }\r\n\r\n /**\r\n * Add a parameter with it's correspondent value to an URL (GET).\r\n * @param {string} path - URL path\r\n * @param {string} parameter - parameter\r\n * @param {string} value - value\r\n * @static\r\n */\r\n static addArgument(path, parameter, value) {\r\n let sep;\r\n if (path.indexOf(\"?\") > 0) {\r\n sep = \"&\";\r\n } else {\r\n sep = \"?\";\r\n }\r\n return `${path + sep + parameter}=${value}`;\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType Image class. Contains all the logic related\r\n * to MathType images manipulation.\r\n * All MathType images are generated using the appropriate MathType\r\n * integration service: showimage or createimage.\r\n *\r\n * There are two available image formats:\r\n * - svg (default)\r\n * - png\r\n *\r\n * There are two formats for the image src attribute:\r\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\r\n * - A link to the showimage service.\r\n */\r\nexport default class Image {\r\n /**\r\n * Removes data attributes from an image.\r\n * @param {HTMLImageElement} img - Image where remove data attributes.\r\n */\r\n static removeImgDataAttributes(img) {\r\n const attributesToRemove = [];\r\n const { attributes } = img;\r\n\r\n Object.keys(attributes).forEach((key) => {\r\n const attribute = attributes[key];\r\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\r\n // Is preferred keep an array and remove after the search\r\n // because when attribute is removed the array of attributes\r\n // is modified.\r\n attributesToRemove.push(attribute.name);\r\n }\r\n });\r\n\r\n attributesToRemove.forEach((attribute) => {\r\n img.removeAttribute(attribute);\r\n });\r\n }\r\n\r\n /**\r\n * @static\r\n * Clones all MathType image attributes from a HTMLImageElement to another.\r\n * @param {HTMLImageElement} originImg - The original image.\r\n * @param {HTMLImageElement} destImg - The destination image.\r\n */\r\n static clone(originImg, destImg) {\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (!originImg.hasAttribute(customEditorAttributeName)) {\r\n destImg.removeAttribute(customEditorAttributeName);\r\n }\r\n\r\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\r\n const imgAttributes = [\r\n mathmlAttributeName,\r\n customEditorAttributeName,\r\n \"alt\",\r\n \"height\",\r\n \"width\",\r\n \"style\",\r\n \"src\",\r\n \"role\",\r\n ];\r\n\r\n imgAttributes.forEach((iterator) => {\r\n const originAttribute = originImg.getAttribute(iterator);\r\n if (originAttribute) {\r\n destImg.setAttribute(iterator, originAttribute);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Determines whether an img src contains an SVG.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src contains an SVG, false otherwise\r\n */\r\n static isSvg(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;\");\r\n }\r\n\r\n /**\r\n * Determines whether an img src is encoded in base64 or not.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src is encoded in base64, false otherwise\r\n */\r\n static isBase64(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\r\n }\r\n\r\n /**\r\n * Calculates the metrics of a MathType image given the the service response and the image format.\r\n * @param {HTMLImageElement} img - The HTMLImageElement.\r\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\r\n * @param {Boolean} jsonResponse - True the response of the image service is a\r\n * JSON object. False otherwise.\r\n */\r\n static setImgSize(img, uri, jsonResponse) {\r\n let ar;\r\n let base64String;\r\n let bytes;\r\n let svgString;\r\n if (jsonResponse) {\r\n // Cleaning data:image/png;base64.\r\n if (Image.isSvg(img)) {\r\n // SVG format.\r\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\r\n if (!Image.isBase64(img)) {\r\n ar = Image.getMetricsFromSvgString(uri);\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n svgString = \"\";\r\n bytes = Util.b64ToByteArray(base64String, base64String.length);\r\n for (let i = 0; i < bytes.length; i += 1) {\r\n svgString += String.fromCharCode(bytes[i]);\r\n }\r\n ar = Image.getMetricsFromSvgString(svgString);\r\n }\r\n // PNG format: we store all metrics information in the first 88 bytes.\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n bytes = Util.b64ToByteArray(base64String, 88);\r\n ar = Image.getMetricsFromBytes(bytes);\r\n }\r\n // Backwards compatibility: we store the metrics into createimage response.\r\n } else {\r\n ar = Util.urlToAssArray(uri);\r\n }\r\n let width = ar.cw;\r\n if (!width) {\r\n return;\r\n }\r\n let height = ar.ch;\r\n let baseline = ar.cb;\r\n const { dpi } = ar;\r\n if (dpi) {\r\n width = (width * 96) / dpi;\r\n height = (height * 96) / dpi;\r\n baseline = (baseline * 96) / dpi;\r\n }\r\n img.width = width;\r\n img.height = height;\r\n img.style.verticalAlign = `-${height - baseline}px`;\r\n }\r\n\r\n /**\r\n * Calculates the metrics of an image which has been resized. Is used to restore the original\r\n * metrics of a resized image.\r\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\r\n */\r\n static fixAfterResize(img) {\r\n img.removeAttribute(\"style\");\r\n img.removeAttribute(\"width\");\r\n img.removeAttribute(\"height\");\r\n // In order to avoid resize with max-width css property.\r\n img.style.maxWidth = \"none\";\r\n\r\n const processImg = (img) => {\r\n if (img.src.indexOf(\"data:image\") !== -1) {\r\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\r\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\r\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\r\n // (which would actually make more sense for readibility and efficiency).\r\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\r\n // 'data:image/svg+xml;base64,'.length === 26\r\n const base64String = img.getAttribute(\"src\").substring(26);\r\n const svgString = window.atob(base64String);\r\n const encodedSvgString = encodeURIComponent(svgString);\r\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n // Return src to base64!\r\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\r\n } else {\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n }\r\n } else {\r\n // 'data:image/png;base64,' === 22.\r\n const base64 = img.src.substring(22, img.src.length);\r\n Image.setImgSize(img, base64, true);\r\n }\r\n } else {\r\n Image.setImgSize(img, img.src);\r\n }\r\n };\r\n\r\n // If the image doesn't contain a blob, just process it normally\r\n if (img.src.indexOf(\"blob:\") === -1) {\r\n processImg(img);\r\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = function () {\r\n img.setAttribute(\"src\", reader.result);\r\n processImg(img);\r\n };\r\n fetch(img.src)\r\n .then((r) => r.blob())\r\n .then((blob) => {\r\n reader.readAsDataURL(blob);\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\r\n * by the MathType image service. This image contains as an extra custom attribute:\r\n * the baseline (wrs:baseline).\r\n * @param {String} svgString - The SVG image.\r\n * @return {Array} - The image metrics.\r\n */\r\n static getMetricsFromSvgString(svgString) {\r\n let first = svgString.indexOf('height=\"');\r\n let last = svgString.indexOf('\"', first + 8, svgString.length);\r\n const height = svgString.substring(first + 8, last);\r\n\r\n first = svgString.indexOf('width=\"');\r\n last = svgString.indexOf('\"', first + 7, svgString.length);\r\n const width = svgString.substring(first + 7, last);\r\n\r\n first = svgString.indexOf('wrs:baseline=\"');\r\n last = svgString.indexOf('\"', first + 14, svgString.length);\r\n const baseline = svgString.substring(first + 14, last);\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n if (typeof baseline !== \"undefined\") {\r\n arr.cb = baseline;\r\n }\r\n return arr;\r\n }\r\n return [];\r\n }\r\n\r\n /**\r\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\r\n * @param {Array.} bytes - png byte array.\r\n * @return {Array} The png metrics.\r\n */\r\n static getMetricsFromBytes(bytes) {\r\n Util.readBytes(bytes, 0, 8);\r\n let width;\r\n let height;\r\n let typ;\r\n let baseline;\r\n let dpi;\r\n while (bytes.length >= 4) {\r\n typ = Util.readInt32(bytes);\r\n if (typ === 0x49484452) {\r\n width = Util.readInt32(bytes);\r\n height = Util.readInt32(bytes);\r\n // Read 5 bytes.\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n } else if (typ === 0x62615345) {\r\n // Baseline: 'baSE'.\r\n baseline = Util.readInt32(bytes);\r\n } else if (typ === 0x70485973) {\r\n // Dpis: 'pHYs'.\r\n dpi = Util.readInt32(bytes);\r\n dpi = Math.round(dpi / 39.37);\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n }\r\n Util.readInt32(bytes);\r\n }\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n arr.dpi = dpi;\r\n if (baseline) {\r\n arr.cb = baseline;\r\n }\r\n\r\n return arr;\r\n }\r\n return [];\r\n }\r\n}\r\n","import TextCache from \"./textcache\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport MathML from \"./mathml\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\r\n * the associated client-side cache.\r\n */\r\nexport default class Accessibility {\r\n /**\r\n * Static property.\r\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\r\n * @type {TextCache}\r\n */\r\n static get cache() {\r\n return Accessibility._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set accessibility cache.\r\n * @param {TextCahe} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Accessibility._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML strings to its accessible text representation.\r\n * @param {String} mathML - MathML to be converted to accessible text.\r\n * @param {String} [language] - Language of the accessible text. 'en' by default.\r\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\r\n * @return {String} Accessibility text.\r\n */\r\n static mathMLToAccessible(mathML, language, data) {\r\n if (typeof language === \"undefined\") {\r\n language = \"en\";\r\n }\r\n // Check MathML class. If the class is chemistry,\r\n // we add chemistry to data to force accessibility service\r\n // to load chemistry grammar.\r\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\r\n data.mode = \"chemistry\";\r\n }\r\n // Ignore accesibility styles\r\n data.ignoreStyles = true;\r\n let accessibleText = \"\";\r\n\r\n if (Accessibility.cache.get(mathML)) {\r\n accessibleText = Accessibility.cache.get(mathML);\r\n } else {\r\n data.service = \"mathml2accessible\";\r\n data.lang = language;\r\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n if (accessibleJsonResponse.status !== \"error\") {\r\n accessibleText = accessibleJsonResponse.result.text;\r\n Accessibility.cache.populate(mathML, accessibleText);\r\n } else {\r\n accessibleText = StringManager.get(\"error_convert_accessibility\");\r\n }\r\n }\r\n\r\n return accessibleText;\r\n }\r\n}\r\n\r\n/**\r\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\r\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\r\n * @private\r\n * @type {TextCache}\r\n */\r\nAccessibility._cache = new TextCache();\r\n","import Util from \"./util\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport Image from \"./image\";\r\nimport Accessibility from \"./accessibility\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Configuration from \"./configuration\";\r\nimport Constants from \"./constants\";\r\n// eslint-disable-next-line no-unused-vars\r\nimport md5 from \"./md5\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represent a MahML parser. Converts MathML into formulas depending on the\r\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\r\n * in the backend.\r\n */\r\nexport default class Parser {\r\n /**\r\n * Converts a MathML string to an img element.\r\n * @param {Document} creator - Document object to call createElement method.\r\n * @param {string} mathml - MathML code\r\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\r\n * @param {language} language - custom language for accessibility.\r\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\r\n * @static\r\n */\r\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\r\n const imgObject = creator.createElement(\"img\");\r\n imgObject.align = \"middle\";\r\n imgObject.style.maxWidth = \"none\";\r\n let data = wirisProperties || {};\r\n\r\n // Take into account the backend config\r\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\r\n data = { ...wirisEditorProperties, ...data };\r\n\r\n data.mml = mathml;\r\n data.lang = language;\r\n // Request metrics of the generated image.\r\n data.metrics = \"true\";\r\n data.centerbaseline = \"false\";\r\n\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n // Render js params: _wrs_int_wirisProperties contains some js render params.\r\n // Since MathML can support render params, js params should be send only to editor.\r\n\r\n imgObject.className = Configuration.get(\"imageClassName\");\r\n\r\n if (mathml.indexOf('class=\"') !== -1) {\r\n // We check here if the MathML has been created from a customEditor (such chemistry)\r\n // to add custom editor name attribute to img object (if necessary).\r\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\r\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\r\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\r\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\r\n }\r\n\r\n // Performance enabled.\r\n if (\r\n Configuration.get(\"wirisPluginPerformance\") &&\r\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\r\n ) {\r\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\r\n if (result.status === \"warning\") {\r\n // POST call.\r\n // if the mathml is malformed, this function will throw an exception.\r\n try {\r\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n ({ result } = result);\r\n if (result.format === \"png\") {\r\n imgObject.src = `data:image/png;base64,${result.content}`;\r\n } else {\r\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\r\n }\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n Image.setImgSize(imgObject, result.content, true);\r\n\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n if (typeof result.alt === \"undefined\") {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n } else {\r\n imgObject.alt = result.alt;\r\n }\r\n }\r\n } else {\r\n const result = Parser.createImageSrc(mathml, data);\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n imgObject.src = result;\r\n Image.setImgSize(\r\n imgObject,\r\n result,\r\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\r\n );\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n }\r\n }\r\n\r\n if (typeof Parser.observer !== \"undefined\") {\r\n Parser.observer.observe(imgObject);\r\n }\r\n\r\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\r\n imgObject.setAttribute(\"role\", \"math\");\r\n return imgObject;\r\n }\r\n\r\n /**\r\n * Returns the source to showimage service by calling createimage service. The\r\n * output of the createimage service is a URL path pointing to showimage service.\r\n * This method is called when performance is disabled.\r\n * @param {string} mathml - MathML code.\r\n * @param {Object[]} data - data object containing service parameters.\r\n * @returns {string} the showimage path.\r\n */\r\n static createImageSrc(mathml, data) {\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n let result = ServiceProvider.getService(\"createimage\", data);\r\n\r\n if (result.indexOf(\"@BASE@\") !== -1) {\r\n // Replacing '@BASE@' with the base URL of createimage.\r\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\r\n baseParts.pop();\r\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\r\n * this data would be converted as following:\r\n *
\r\n   * MathML code: Image containing the corresponding MathML formulas.\r\n   * MathML code with LaTeX annotation : LaTeX string.\r\n   * 
\r\n * @param {string} code - HTML code containing MathML data.\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\r\n */\r\n static initParse(code, language) {\r\n /* Note: The code inside this function has been inverted.\r\n If you invert again the code then you cannot use correctly LaTeX\r\n in Moodle.\r\n */\r\n code = Parser.initParseSaveMode(code, language);\r\n return Parser.initParseEditMode(code);\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the save mode. Transforms all MathML\r\n * occurrences for it's correspondent image or LaTeX.\r\n * @param {string} code - HTML code to be parsed\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static initParseSaveMode(code, language) {\r\n if (Configuration.get(\"saveMode\")) {\r\n // Converting XML to tags.\r\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\r\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\r\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\r\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\r\n code = Parser.codeImgTransform(code, \"base642showimage\");\r\n }\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the edit mode.\r\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\r\n * be converted into a LaTeX string instead of an image.\r\n * @param {string} code - HTML code containing MathML.\r\n * @returns {string} parsed HTML code.\r\n */\r\n static initParseEditMode(code) {\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\r\n const token = 'encoding=\"LaTeX\">';\r\n // While replacing images with latex, the indexes of the found images changes\r\n // respecting the original code, so this carry is needed.\r\n let carry = 0;\r\n\r\n for (let i = 0; i < imgList.length; i += 1) {\r\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\r\n\r\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\r\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\r\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n\r\n if (mathmlStart === -1) {\r\n mathmlStartToken = ' alt=\"';\r\n mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n }\r\n\r\n if (mathmlStart !== -1) {\r\n mathmlStart += mathmlStartToken.length;\r\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\r\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\r\n let latexStartPosition = mathml.indexOf(token);\r\n\r\n if (latexStartPosition !== -1) {\r\n latexStartPosition += token.length;\r\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\r\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\r\n\r\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\r\n const start = code.substring(0, imgList[i].start + carry);\r\n const end = code.substring(imgList[i].end + carry);\r\n code = start + replaceText + end;\r\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code. The end HTML code is HTML code with embedded images\r\n * or LaTeX formulas created with MathType.
\r\n * By default this method converts the formula images and LaTeX strings in MathML.
\r\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\r\n * @param {string} code - HTML to be parsed\r\n * @returns {string} the HTML code parsed.\r\n */\r\n static endParse(code) {\r\n // Transform LaTeX ocurrences to MathML elements.\r\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\r\n // Transform img elements to MathML elements.\r\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\r\n return codeEndParseSaveMode;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the edit mode.\r\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\r\n * @param {string} code - HTML code to be parsed.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseEditMode(code) {\r\n // Converting LaTeX to images.\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n let startPosition = code.indexOf(\"$$\");\r\n while (startPosition !== -1) {\r\n output += code.substring(endPosition, startPosition);\r\n endPosition = code.indexOf(\"$$\", startPosition + 2);\r\n\r\n if (endPosition !== -1) {\r\n // Before, it was a condition here to execute the next codelines\r\n // 'latex.indexOf('<') == -1'.\r\n // We don't know why it was used, but seems to have a conflict with\r\n // latex formulas that contains '<'.\r\n const latex = code.substring(startPosition + 2, endPosition);\r\n const decodedLatex = Util.htmlEntitiesDecode(latex);\r\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n // Remove hand traces.\r\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\r\n }\r\n output += mathml;\r\n endPosition += 2;\r\n } else {\r\n output += \"$$\";\r\n endPosition = startPosition + 2;\r\n }\r\n\r\n startPosition = code.indexOf(\"$$\", endPosition);\r\n }\r\n\r\n output += code.substring(endPosition, code.length);\r\n code = output;\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the save mode. Converts all\r\n * images into the element determined by the save mode:\r\n * - xml: Parses images formulas into MathML.\r\n * - safeXml: Parses images formulas into safeMAthML\r\n * - base64: Parses images into base64 images.\r\n * - image: Parse images into images (no parsing)\r\n * @param {string} code - HTML code to be parsed\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseSaveMode(code) {\r\n const savemode = Configuration.get(\"saveMode\");\r\n const base64savemode = Configuration.get(\"base64savemode\");\r\n\r\n if (savemode) {\r\n if (savemode === \"safeXml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"xml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\r\n code = Parser.codeImgTransform(code, \"img264\");\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Auxiliar function that builds the data object to send to the showimage endpoint\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object with the data to send to showimage.\r\n */\r\n static createShowImageSrcData(data, language) {\r\n const dataMd5 = {};\r\n const renderParams = [\r\n \"mml\",\r\n \"color\",\r\n \"centerbaseline\",\r\n \"zoom\",\r\n \"dpi\",\r\n \"fontSize\",\r\n \"fontFamily\",\r\n \"defaultStretchy\",\r\n \"backgroundColor\",\r\n \"format\",\r\n ];\r\n renderParams.forEach((param) => {\r\n if (typeof data[param] !== \"undefined\") {\r\n dataMd5[param] = data[param];\r\n }\r\n });\r\n // Data variables to get.\r\n const dataObject = {};\r\n Object.keys(data).forEach((key) => {\r\n // We don't need mathml in this request we try to get cached.\r\n // Only need the formula md5 calculated before.\r\n if (key !== \"mml\") {\r\n dataObject[key] = data[key];\r\n }\r\n });\r\n\r\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\r\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\r\n dataObject.version = Configuration.get(\"version\");\r\n\r\n return dataObject;\r\n }\r\n\r\n /**\r\n * Returns the result to call showimage service with the formula md5 as parameter.\r\n * The result could be:\r\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\r\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object containing showimage response.\r\n */\r\n static createShowImageSrc(data, language) {\r\n const dataObject = this.createShowImageSrcData(data, language);\r\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\r\n return result;\r\n }\r\n\r\n /**\r\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\r\n * or showimage img tags (i.e with showimage.php on src)\r\n * @param {string} code - HTML code\r\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\r\n * @returns {string} html - code transformed.\r\n */\r\n static codeImgTransform(code, mode) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n const pattern = /\") {\r\n endPosition = i + 1;\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n if (endPosition < startPosition) {\r\n // The img tag is stripped.\r\n output += code.substring(startPosition, code.length);\r\n return output;\r\n }\r\n let imgCode = code.substring(startPosition, endPosition);\r\n const imgObject = Util.createObject(imgCode);\r\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n let convertToXml;\r\n let convertToSafeXml;\r\n\r\n if (mode === \"base642showimage\") {\r\n if (xmlCode == null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\r\n output += Util.createObjectCode(imgCode);\r\n } else if (mode === \"img2mathml\") {\r\n if (Configuration.get(\"saveMode\")) {\r\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\r\n convertToXml = true;\r\n convertToSafeXml = true;\r\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\r\n convertToXml = true;\r\n convertToSafeXml = false;\r\n }\r\n }\r\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\r\n } else if (mode === \"img264\") {\r\n if (xmlCode === null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n\r\n const properties = {};\r\n properties.base64 = \"true\";\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\r\n // Metrics.\r\n Image.setImgSize(imgCode, imgCode.src, true);\r\n output += Util.createObjectCode(imgCode);\r\n }\r\n }\r\n output += code.substring(endPosition, code.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML to the corresponding image.\r\n * @param {string} content - string with valid MathML code.\r\n * The MathML code doesn't contain semantics.\r\n * @param {Constants} characters - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * @param {string} language - a valid language code\r\n * in order to generate formula accessibility.\r\n * @returns {string} The input string with all the MathML\r\n * occurrences replaced by the corresponding image.\r\n */\r\n static parseMathmlToImg(content, characters, language) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n // Avoid WIRIS images to be parsed.\r\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else if (imageMathmlAtrribute !== -1) {\r\n // First close tag of img attribute\r\n // If a mathmlAttribute exists should be inside a img tag.\r\n end += content.indexOf(\"/>\", start);\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\r\n let mathml = content.substring(start, end);\r\n mathml =\r\n characters.id === Constants.safeXmlCharacters.id\r\n ? MathML.safeXmlDecode(mathml)\r\n : MathML.mathMLEntities(mathml);\r\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\r\n } else {\r\n output += content.substring(start, end);\r\n }\r\n\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n}\r\n\r\n// Mutation observers to avoid wiris image formulas class be removed.\r\nif (typeof MutationObserver !== \"undefined\") {\r\n const mutationObserver = new MutationObserver((mutations) => {\r\n mutations.forEach((mutation) => {\r\n if (\r\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\r\n mutation.attributeName === \"class\" &&\r\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\r\n ) {\r\n mutation.target.className = Configuration.get(\"imageClassName\");\r\n }\r\n });\r\n });\r\n\r\n Parser.observer = Object.create(mutationObserver);\r\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\r\n // We use own default config.\r\n Parser.observer.observe = function (target) {\r\n Object.getPrototypeOf(this).observe(target, this.Config);\r\n };\r\n}\r\n","/* eslint-disable class-methods-use-this */\r\n/* eslint-disable no-unused-vars */\r\n/* eslint-disable no-extra-semi */\r\n\r\n// The rules above are disabled because we are implementing\r\n// an external interface.\r\n\r\nexport default class EditorListener {\r\n /**\r\n * @classdesc\r\n * Determines if the content of the\r\n * MathType Editor has changes.\r\n * @implements {EditorListeners}\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the content of the editor has changed.\r\n * @type {Boolean}\r\n */\r\n this.isContentChanged = false;\r\n\r\n /**\r\n * Indicates if the listener should be waiting for changes in the editor.\r\n * @type {Boolean}\r\n */\r\n this.waitingForChanges = false;\r\n }\r\n\r\n /**\r\n * Sets {@link EditorListener.isContentChanged} property.\r\n * @param {Boolean} value - The new vlue.\r\n */\r\n setIsContentChanged(value) {\r\n this.isContentChanged = value;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed, false otherwise.\r\n * @return {Boolean}\r\n */\r\n getIsContentChanged() {\r\n return this.isContentChanged;\r\n }\r\n\r\n /**\r\n * Determines if the EditorListener should wait for any changes.\r\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\r\n */\r\n setWaitingForChanges(value) {\r\n this.waitingForChanges = value;\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite.\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n caretPositionChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n clipboardChanged(_editor) {}\r\n\r\n /**\r\n * Determines if the content of an editor has been changed.\r\n * @param {JsEditor} editor - editor object.\r\n */\r\n contentChanged(_editor) {\r\n if (this.waitingForChanges === true && this.isContentChanged === false) {\r\n this.isContentChanged = true;\r\n }\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} editor - The editor instance.\r\n */\r\n styleChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} - The editor instance.\r\n */\r\n transformationReceived(_editor) {}\r\n}\r\n","let wasm;\r\n\r\nconst cachedTextDecoder =\r\n typeof TextDecoder !== \"undefined\"\r\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\r\n : {\r\n decode: () => {\r\n throw Error(\"TextDecoder not available\");\r\n },\r\n };\r\n\r\nif (typeof TextDecoder !== \"undefined\") {\r\n cachedTextDecoder.decode();\r\n}\r\n\r\nlet cachedUint8Memory0 = null;\r\n\r\nfunction getUint8Memory0() {\r\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\r\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\r\n }\r\n return cachedUint8Memory0;\r\n}\r\n\r\nfunction getStringFromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\r\n}\r\n\r\nconst heap = new Array(128).fill(undefined);\r\n\r\nheap.push(undefined, null, true, false);\r\n\r\nlet heap_next = heap.length;\r\n\r\nfunction addHeapObject(obj) {\r\n if (heap_next === heap.length) heap.push(heap.length + 1);\r\n const idx = heap_next;\r\n heap_next = heap[idx];\r\n\r\n heap[idx] = obj;\r\n return idx;\r\n}\r\n\r\nfunction getObject(idx) {\r\n return heap[idx];\r\n}\r\n\r\nfunction dropObject(idx) {\r\n if (idx < 132) return;\r\n heap[idx] = heap_next;\r\n heap_next = idx;\r\n}\r\n\r\nfunction takeObject(idx) {\r\n const ret = getObject(idx);\r\n dropObject(idx);\r\n return ret;\r\n}\r\n\r\nlet WASM_VECTOR_LEN = 0;\r\n\r\nconst cachedTextEncoder =\r\n typeof TextEncoder !== \"undefined\"\r\n ? new TextEncoder(\"utf-8\")\r\n : {\r\n encode: () => {\r\n throw Error(\"TextEncoder not available\");\r\n },\r\n };\r\n\r\nconst encodeString =\r\n typeof cachedTextEncoder.encodeInto === \"function\"\r\n ? function (arg, view) {\r\n return cachedTextEncoder.encodeInto(arg, view);\r\n }\r\n : function (arg, view) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n view.set(buf);\r\n return {\r\n read: arg.length,\r\n written: buf.length,\r\n };\r\n };\r\n\r\nfunction passStringToWasm0(arg, malloc, realloc) {\r\n if (realloc === undefined) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n const ptr = malloc(buf.length, 1) >>> 0;\r\n getUint8Memory0()\r\n .subarray(ptr, ptr + buf.length)\r\n .set(buf);\r\n WASM_VECTOR_LEN = buf.length;\r\n return ptr;\r\n }\r\n\r\n let len = arg.length;\r\n let ptr = malloc(len, 1) >>> 0;\r\n\r\n const mem = getUint8Memory0();\r\n\r\n let offset = 0;\r\n\r\n for (; offset < len; offset++) {\r\n const code = arg.charCodeAt(offset);\r\n if (code > 0x7f) break;\r\n mem[ptr + offset] = code;\r\n }\r\n\r\n if (offset !== len) {\r\n if (offset !== 0) {\r\n arg = arg.slice(offset);\r\n }\r\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\r\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\r\n const ret = encodeString(arg, view);\r\n\r\n offset += ret.written;\r\n }\r\n\r\n WASM_VECTOR_LEN = offset;\r\n return ptr;\r\n}\r\n\r\nfunction isLikeNone(x) {\r\n return x === undefined || x === null;\r\n}\r\n\r\nlet cachedInt32Memory0 = null;\r\n\r\nfunction getInt32Memory0() {\r\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\r\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\r\n }\r\n return cachedInt32Memory0;\r\n}\r\n\r\nlet cachedFloat64Memory0 = null;\r\n\r\nfunction getFloat64Memory0() {\r\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\r\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\r\n }\r\n return cachedFloat64Memory0;\r\n}\r\n\r\nlet cachedBigInt64Memory0 = null;\r\n\r\nfunction getBigInt64Memory0() {\r\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\r\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\r\n }\r\n return cachedBigInt64Memory0;\r\n}\r\n\r\nfunction debugString(val) {\r\n // primitive types\r\n const type = typeof val;\r\n if (type == \"number\" || type == \"boolean\" || val == null) {\r\n return `${val}`;\r\n }\r\n if (type == \"string\") {\r\n return `\"${val}\"`;\r\n }\r\n if (type == \"symbol\") {\r\n const description = val.description;\r\n if (description == null) {\r\n return \"Symbol\";\r\n } else {\r\n return `Symbol(${description})`;\r\n }\r\n }\r\n if (type == \"function\") {\r\n const name = val.name;\r\n if (typeof name == \"string\" && name.length > 0) {\r\n return `Function(${name})`;\r\n } else {\r\n return \"Function\";\r\n }\r\n }\r\n // objects\r\n if (Array.isArray(val)) {\r\n const length = val.length;\r\n let debug = \"[\";\r\n if (length > 0) {\r\n debug += debugString(val[0]);\r\n }\r\n for (let i = 1; i < length; i++) {\r\n debug += \", \" + debugString(val[i]);\r\n }\r\n debug += \"]\";\r\n return debug;\r\n }\r\n // Test for built-in\r\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\r\n let className;\r\n if (builtInMatches.length > 1) {\r\n className = builtInMatches[1];\r\n } else {\r\n // Failed to match the standard '[object ClassName]'\r\n return toString.call(val);\r\n }\r\n if (className == \"Object\") {\r\n // we're a user defined class or Object\r\n // JSON.stringify avoids problems with cycles, and is generally much\r\n // easier than looping through ownProperties of `val`.\r\n try {\r\n return \"Object(\" + JSON.stringify(val) + \")\";\r\n } catch (_) {\r\n return \"Object\";\r\n }\r\n }\r\n // errors\r\n if (val instanceof Error) {\r\n return `${val.name}: ${val.message}\\n${val.stack}`;\r\n }\r\n // TODO we could test for more things here, like `Set`s and `Map`s.\r\n return className;\r\n}\r\n\r\nfunction makeClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n try {\r\n return f(state.a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\r\n state.a = 0;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction makeMutClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n const a = state.a;\r\n state.a = 0;\r\n try {\r\n return f(a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\r\n } else {\r\n state.a = a;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_49(arg0, arg1) {\r\n wasm.__wbindgen_export_4(arg0, arg1);\r\n}\r\n\r\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction handleError(f, args) {\r\n try {\r\n return f.apply(this, args);\r\n } catch (e) {\r\n wasm.__wbindgen_export_6(addHeapObject(e));\r\n }\r\n}\r\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\r\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\r\n}\r\n\r\n/**\r\n */\r\nexport function main_js() {\r\n wasm.main_js();\r\n}\r\n\r\nfunction getArrayU8FromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\r\n}\r\n/**\r\n */\r\nexport const Level = Object.freeze({\r\n Err: 0,\r\n 0: \"Err\",\r\n Warn: 1,\r\n 1: \"Warn\",\r\n Info: 2,\r\n 2: \"Info\",\r\n Debug: 3,\r\n 3: \"Debug\",\r\n});\r\n/**\r\n */\r\nexport class Telemeter {\r\n __destroy_into_raw() {\r\n const ptr = this.__wbg_ptr;\r\n this.__wbg_ptr = 0;\r\n\r\n return ptr;\r\n }\r\n\r\n free() {\r\n const ptr = this.__destroy_into_raw();\r\n wasm.__wbg_telemeter_free(ptr);\r\n }\r\n /**\r\n * @param {any} solution\r\n * @param {any} hosts\r\n * @param {any} config\r\n */\r\n constructor(solution, hosts, config) {\r\n try {\r\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\r\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\r\n var r0 = getInt32Memory0()[retptr / 4 + 0];\r\n var r1 = getInt32Memory0()[retptr / 4 + 1];\r\n var r2 = getInt32Memory0()[retptr / 4 + 2];\r\n if (r2) {\r\n throw takeObject(r1);\r\n }\r\n this.__wbg_ptr = r0 >>> 0;\r\n return this;\r\n } finally {\r\n wasm.__wbindgen_add_to_stack_pointer(16);\r\n }\r\n }\r\n /**\r\n * @param {string} sender_id\r\n * @returns {Promise}\r\n */\r\n identify(sender_id) {\r\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {string} event_type\r\n * @param {any} event_payload\r\n * @returns {Promise}\r\n */\r\n track(event_type, event_payload) {\r\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {any} level\r\n * @param {string} message\r\n * @param {any} payload\r\n * @returns {Promise}\r\n */\r\n log(level, message, payload) {\r\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @returns {Promise}\r\n */\r\n finish() {\r\n const ptr = this.__destroy_into_raw();\r\n const ret = wasm.telemeter_finish(ptr);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {boolean | undefined} [new_debug_status]\r\n */\r\n debug(new_debug_status) {\r\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\r\n }\r\n}\r\n\r\nasync function __wbg_load(module, imports) {\r\n if (typeof Response === \"function\" && module instanceof Response) {\r\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\r\n try {\r\n return await WebAssembly.instantiateStreaming(module, imports);\r\n } catch (e) {\r\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\r\n console.warn(\r\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\r\n e,\r\n );\r\n } else {\r\n throw e;\r\n }\r\n }\r\n }\r\n\r\n const bytes = await module.arrayBuffer();\r\n return await WebAssembly.instantiate(bytes, imports);\r\n } else {\r\n const instance = await WebAssembly.instantiate(module, imports);\r\n\r\n if (instance instanceof WebAssembly.Instance) {\r\n return { instance, module };\r\n } else {\r\n return instance;\r\n }\r\n }\r\n}\r\n\r\nfunction __wbg_get_imports() {\r\n const imports = {};\r\n imports.wbg = {};\r\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\r\n const ret = getStringFromWasm0(arg0, arg1);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\r\n const ret = new Object();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\r\n const ret = getObject(arg0).status;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\r\n const ret = getObject(arg0).headers;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\r\n const ret = new Date();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\r\n const ret = getObject(arg0).getTime();\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\r\n takeObject(arg0);\r\n };\r\n imports.wbg.__wbindgen_is_object = function (arg0) {\r\n const val = getObject(arg0);\r\n const ret = typeof val === \"object\" && val !== null;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\r\n const ret = getObject(arg0).crypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\r\n const ret = getObject(arg0).process;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\r\n const ret = getObject(arg0).versions;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\r\n const ret = getObject(arg0).node;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_string = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"string\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\r\n const ret = getObject(arg0).msCrypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\r\n return handleError(function () {\r\n const ret = module.require;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\r\n const ret = new Uint8Array(arg0 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[arg1 >>> 0];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).next();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\r\n const ret = getObject(arg0).done;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\r\n const ret = getObject(arg0).value;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\r\n const ret = Symbol.iterator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\r\n const ret = getObject(arg0).next;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_function = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"function\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = getObject(arg0).call(getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\r\n const ret = getObject(arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\r\n return handleError(function () {\r\n const ret = self.self;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\r\n return handleError(function () {\r\n const ret = window.window;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\r\n return handleError(function () {\r\n const ret = globalThis.globalThis;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\r\n return handleError(function () {\r\n const ret = global.global;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\r\n const ret = getObject(arg0) === undefined;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\r\n const ret = new Function(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\r\n const ret = Array.isArray(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\r\n const ret = Number.isSafeInteger(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\r\n try {\r\n var state0 = { a: arg0, b: arg1 };\r\n var cb0 = (arg0, arg1) => {\r\n const a = state0.a;\r\n state0.a = 0;\r\n try {\r\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\r\n } finally {\r\n state0.a = a;\r\n }\r\n };\r\n const ret = new Promise(cb0);\r\n return addHeapObject(ret);\r\n } finally {\r\n state0.a = state0.b = 0;\r\n }\r\n };\r\n imports.wbg.__wbindgen_memory = function () {\r\n const ret = wasm.memory;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\r\n const ret = getObject(arg0).buffer;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\r\n const ret = new Uint8Array(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\r\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\r\n };\r\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"string\" ? obj : undefined;\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\r\n return handleError(function (arg0) {\r\n const ret = JSON.stringify(getObject(arg0));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\r\n const ret = getObject(arg0).fetch(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\r\n const ret = fetch(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\r\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\r\n return handleError(function () {\r\n const ret = new AbortController();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\r\n const ret = getObject(arg0).signal;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\r\n getObject(arg0).abort();\r\n };\r\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\r\n const ret = new Error(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) == getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\r\n const v = getObject(arg0);\r\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"number\" ? obj : undefined;\r\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Uint8Array;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof ArrayBuffer;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\r\n const ret = Object.entries(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\r\n const ret = String(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\r\n console.warn(getObject(arg0), getObject(arg1));\r\n };\r\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\r\n const ret = getObject(arg0).readyState;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\r\n console.warn(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\r\n return handleError(function (arg0) {\r\n getObject(arg0).close();\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_new_b9b318679315404f = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\r\n getObject(arg0).binaryType = takeObject(arg1);\r\n };\r\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\r\n console.log(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\r\n console.error(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\r\n console.info(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\r\n const ret = getObject(arg0).document;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\r\n const ret = getObject(arg0).visibilityState;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[getObject(arg1)];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"bigint\";\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\r\n const ret = arg0;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\r\n const ret = getObject(arg0) in getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\r\n const ret = BigInt.asUintN(64, arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) === getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).localStorage;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\r\n const ret = getObject(arg0).navigator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).mediaDevices;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).enumerateDevices();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\r\n const obj = takeObject(arg0).original;\r\n if (obj.cnt-- == 1) {\r\n obj.a = 0;\r\n return true;\r\n }\r\n const ret = false;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\r\n const ret = getObject(arg1).deviceId;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Response;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).randomFillSync(takeObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).getRandomValues(getObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\r\n const v = getObject(arg1);\r\n const ret = typeof v === \"bigint\" ? v : undefined;\r\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\r\n const ret = debugString(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\r\n throw new Error(getStringFromWasm0(arg0, arg1));\r\n };\r\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\r\n const ret = getObject(arg0).then(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\r\n queueMicrotask(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\r\n const ret = getObject(arg0).queueMicrotask;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\r\n const ret = Promise.resolve(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\r\n const ret = getObject(arg1).url;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_send_2860805104507701 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Window;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\r\n return handleError(function () {\r\n const ret = new Headers();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\r\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\r\n return addHeapObject(ret);\r\n };\r\n\r\n return imports;\r\n}\r\n\r\nfunction __wbg_init_memory(imports, maybe_memory) {}\r\n\r\nfunction __wbg_finalize_init(instance, module) {\r\n wasm = instance.exports;\r\n __wbg_init.__wbindgen_wasm_module = module;\r\n cachedBigInt64Memory0 = null;\r\n cachedFloat64Memory0 = null;\r\n cachedInt32Memory0 = null;\r\n cachedUint8Memory0 = null;\r\n\r\n wasm.__wbindgen_start();\r\n return wasm;\r\n}\r\n\r\nfunction initSync(module) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n const imports = __wbg_get_imports();\r\n\r\n __wbg_init_memory(imports);\r\n\r\n if (!(module instanceof WebAssembly.Module)) {\r\n module = new WebAssembly.Module(module);\r\n }\r\n\r\n const instance = new WebAssembly.Instance(module, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nasync function __wbg_init(input) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n if (typeof input === \"undefined\") {\r\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\r\n }\r\n const imports = __wbg_get_imports();\r\n\r\n if (\r\n typeof input === \"string\" ||\r\n (typeof Request === \"function\" && input instanceof Request) ||\r\n (typeof URL === \"function\" && input instanceof URL)\r\n ) {\r\n input = fetch(input);\r\n }\r\n\r\n __wbg_init_memory(imports);\r\n\r\n const { instance, module } = await __wbg_load(await input, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nexport { initSync };\r\nexport default __wbg_init;\r\n","/* eslint-disable-next-line */\r\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\r\n\r\n/**\r\n * @classdesc\r\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\r\n * to Telemetry in a more comfortable and homogeneous way.\r\n */\r\nexport default class Telemeter {\r\n /**\r\n * Inits Telemeter class.\r\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\r\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\r\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\r\n * @param {Object} telemeterAttributes.config - Configuration parameters.\r\n */\r\n static init(telemeterAttributes) {\r\n if (!this.telemeter && !this.waitingForInit) {\r\n this.waitingForInit = true;\r\n init(telemeterAttributes.url)\r\n .then(() => {\r\n this.telemeter = new TelemeterWASM(\r\n telemeterAttributes.solution,\r\n telemeterAttributes.hosts,\r\n telemeterAttributes.config,\r\n );\r\n })\r\n .catch((error) => {\r\n console.log(error);\r\n })\r\n .finally(() => (this.waitingForInit = false));\r\n }\r\n }\r\n\r\n /**\r\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\r\n */\r\n static async finish() {\r\n if (!this.telemeter) return;\r\n\r\n try {\r\n const local_telemeter = this.telemeter;\r\n this.telemeter = undefined;\r\n await local_telemeter.finish();\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Core from \"./core.src\";\r\nimport EditorListener from \"./editorlistener\";\r\nimport Listeners from \"./listeners\";\r\nimport MathML from \"./mathml\";\r\nimport Util from \"./util\";\r\nimport Telemeter from \"./telemeter\";\r\n\r\nexport default class ContentManager {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog, managing the following:\r\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\r\n * - The actions to be done once the modal object has been submitted\r\n * (submitAction} method).\r\n * - The update of the content when the {@link ModalDialog} class is also updated,\r\n * for example when ModalDialog is re-opened.\r\n * - The communication between the {@link ModalDialog} class and itself, if the content\r\n * has been changed (hasChanges} method).\r\n * @constructs\r\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\r\n * create a new instance.\r\n */\r\n constructor(contentManagerAttributes) {\r\n /**\r\n * An object containing MathType editor parameters. See\r\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\r\n * @type {Object}\r\n */\r\n this.editorAttributes = {};\r\n if (\"editorAttributes\" in contentManagerAttributes) {\r\n this.editorAttributes = contentManagerAttributes.editorAttributes;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\r\n }\r\n\r\n /**\r\n * CustomEditors instance. Contains the custom editors.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = null;\r\n if (\"customEditors\" in contentManagerAttributes) {\r\n this.customEditors = contentManagerAttributes.customEditors;\r\n }\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @type {Object}\r\n * @property {String} editor - Editor name. Usually the HTML editor.\r\n * @property {String} mode - Save mode. Xml by default.\r\n * @property {String} version - Plugin version.\r\n */\r\n this.environment = {};\r\n if (\"environment\" in contentManagerAttributes) {\r\n this.environment = contentManagerAttributes.environment;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: environment property missed\");\r\n }\r\n\r\n /**\r\n * ContentManager language.\r\n * @type {String}\r\n */\r\n this.language = \"\";\r\n if (\"language\" in contentManagerAttributes) {\r\n this.language = contentManagerAttributes.language;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: language property missed\");\r\n }\r\n\r\n /**\r\n * {@link EditorListener} instance. Manages the changes inside the editor.\r\n * @type {EditorListener}\r\n */\r\n this.editorListener = new EditorListener();\r\n\r\n /**\r\n * MathType editor instance.\r\n * @type {JsEditor}\r\n */\r\n this.editor = null;\r\n\r\n /**\r\n * Navigator user agent.\r\n * @type {String}\r\n */\r\n this.ua = navigator.userAgent.toLowerCase();\r\n\r\n /**\r\n * Mobile device properties object\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {};\r\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\r\n this.deviceProperties.isIOS = ContentManager.isIOS();\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.toolbar = null;\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.dbclick = null;\r\n\r\n /**\r\n * Instance of the {@link ModalDialog} class associated with the current\r\n * {@link ContentManager} instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialogInstance = null;\r\n\r\n /**\r\n * ContentManager listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * MathML associated to the ContentManager instance.\r\n * @type {String}\r\n */\r\n this.mathML = null;\r\n\r\n /**\r\n * Indicates if the edited element is a new one or not.\r\n * @type {Boolean}\r\n */\r\n this.isNewElement = true;\r\n\r\n /**\r\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n }\r\n\r\n /**\r\n * Adds a new listener to the current {@link ContentManager} instance.\r\n * @param {Object} listener - The listener to be added.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\r\n * instance.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\r\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\r\n */\r\n setModalDialogInstance(modalDialogInstance) {\r\n this.modalDialogInstance = modalDialogInstance;\r\n }\r\n\r\n /**\r\n * Inserts the content into the current {@link ModalDialog} instance updating\r\n * the title and inserting the JavaScript editor.\r\n */\r\n insert() {\r\n // Before insert the editor we update the modal object title to avoid weird render display.\r\n this.updateTitle(this.modalDialogInstance);\r\n this.insertEditor(this.modalDialogInstance);\r\n }\r\n\r\n /**\r\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\r\n * editor's JavaScript is loaded.\r\n */\r\n insertEditor() {\r\n if (ContentManager.isEditorLoaded()) {\r\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\r\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\r\n this.editor.focus();\r\n\r\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\r\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\r\n this.editor.action(\"rtl\");\r\n }\r\n // Setting div in rtl in case of it's activated.\r\n if (this.editor.getEditorModel().isRTL()) {\r\n this.editor.element.style.direction = \"rtl\";\r\n }\r\n\r\n // Editor listener: this object manages the changes logic of editor.\r\n this.editor.getEditorModel().addEditorListener(this.editorListener);\r\n\r\n // iOS events.\r\n if (this.modalDialogInstance.deviceProperties.isIOS) {\r\n setTimeout(function () {\r\n // Make sure the modalDialogInstance is available when the timeout is over\r\n // to avoid throw errors and stop execution.\r\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\r\n }, 400);\r\n\r\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\r\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\r\n }\r\n // Fire onLoad event. Necessary to set the MathML into the editor\r\n // after is loaded.\r\n this.listeners.fire(\"onLoad\", {});\r\n } else {\r\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Initializes the current class by loading MathType script.\r\n */\r\n init() {\r\n if (!ContentManager.isEditorLoaded()) {\r\n this.addEditorAsExternalDependency();\r\n }\r\n }\r\n\r\n /**\r\n * Adds script element to the DOM to include editor externally.\r\n */\r\n addEditorAsExternalDependency() {\r\n const script = document.createElement(\"script\");\r\n script.type = \"text/javascript\";\r\n let editorUrl = Configuration.get(\"editorUrl\");\r\n\r\n // We create an object url for parse url string and work more efficiently.\r\n const anchorElement = document.createElement(\"a\");\r\n\r\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\r\n ContentManager.setProtocolToAnchorElement(anchorElement);\r\n\r\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\r\n\r\n // Load editor URL. We add stats as GET params.\r\n const stats = this.getEditorStats();\r\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\r\n\r\n document.getElementsByTagName(\"head\")[0].appendChild(script);\r\n }\r\n\r\n /**\r\n * Sets the specified url to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\r\n * @param {String} url - URL to set.\r\n */\r\n static setHrefToAnchorElement(anchorElement, url) {\r\n anchorElement.href = url;\r\n }\r\n\r\n /**\r\n * Sets the current protocol to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\r\n */\r\n static setProtocolToAnchorElement(anchorElement) {\r\n // Change to https if necessary.\r\n if (window.location.href.indexOf(\"https://\") === 0) {\r\n // It check if browser is https and configuration is http.\r\n // If this is so, we will replace protocol.\r\n if (anchorElement.protocol === \"http:\") {\r\n anchorElement.protocol = \"https:\";\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the url of the anchor element adding the current port\r\n * if it is needed.\r\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\r\n * @returns {String}\r\n */\r\n static getURLFromAnchorElement(anchorElement) {\r\n // Check protocol and remove port if it's standard.\r\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\r\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\r\n }\r\n\r\n /**\r\n * Returns object with editor stats.\r\n *\r\n * @typedef {Object} EditorStatsObject\r\n * @property {string} editor - Editor name.\r\n * @property {string} mode - Current configuration for formula save mode.\r\n * @property {string} version - Current plugins version.\r\n * @returns {EditorStatsObject}\r\n */\r\n getEditorStats() {\r\n // Editor stats. Use environment property to set it.\r\n const stats = {};\r\n if (\"editor\" in this.environment) {\r\n stats.editor = this.environment.editor;\r\n } else {\r\n stats.editor = \"unknown\";\r\n }\r\n\r\n if (\"mode\" in this.environment) {\r\n stats.mode = this.environment.mode;\r\n } else {\r\n stats.mode = Configuration.get(\"saveMode\");\r\n }\r\n\r\n if (\"version\" in this.environment) {\r\n stats.version = this.environment.version;\r\n } else {\r\n stats.version = Configuration.get(\"version\");\r\n }\r\n\r\n return stats;\r\n }\r\n\r\n /**\r\n * Returns true if device is iOS. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isIOS() {\r\n return (\r\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\r\n // iPad on iOS 13 detection\r\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\r\n );\r\n }\r\n\r\n /**\r\n * Returns true if device is Mobile. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isMobile() {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\r\n }\r\n\r\n /**\r\n * Returns true if editor is loaded. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isEditorLoaded() {\r\n // To know if editor JavaScript is loaded we need to wait until\r\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\r\n return (\r\n window.com &&\r\n window.com.wiris &&\r\n window.com.wiris.jsEditor &&\r\n window.com.wiris.jsEditor.JsEditor &&\r\n window.com.wiris.jsEditor.JsEditor.newInstance\r\n );\r\n }\r\n\r\n /**\r\n * Sets the {@link ContentManager.editor} initial content.\r\n */\r\n setInitialContent() {\r\n if (!this.isNewElement) {\r\n this.setMathML(this.mathML);\r\n }\r\n }\r\n\r\n /**\r\n * Sets a MathML into {@link ContentManager.editor} instance.\r\n * @param {String} mathml - MathML string.\r\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\r\n * False by default.\r\n */\r\n setMathML(mathml, focusDisabled) {\r\n // By default focus is enabled.\r\n if (typeof focusDisabled === \"undefined\") {\r\n focusDisabled = false;\r\n }\r\n // Using setMathML method is not a change produced by the user but for the API\r\n // so we set to false the contentChange property of editorListener.\r\n this.editor.setMathMLWithCallback(mathml, () => {\r\n this.editorListener.setWaitingForChanges(true);\r\n });\r\n\r\n // We need to wait a little until the callback finish.\r\n setTimeout(() => {\r\n this.editorListener.setIsContentChanged(false);\r\n }, 500);\r\n\r\n // In some scenarios - like closing modal object - editor mustn't be focused.\r\n if (!focusDisabled) {\r\n this.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\r\n * {@link ModalDialog.focus}.\r\n */\r\n onFocus() {\r\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\r\n this.editor.focus();\r\n\r\n // On WordPress integration, the focus gets lost right after setting it.\r\n // To fix this, we enforce another focus some milliseconds after this behaviour.\r\n setTimeout(() => {\r\n this.editor.focus();\r\n }, 100);\r\n }\r\n }\r\n\r\n /**\r\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\r\n * Triggered by {@link ModalDialog.submitAction}.\r\n */\r\n submitAction() {\r\n if (!this.editor.isFormulaEmpty()) {\r\n let mathML = this.editor.getMathMLWithSemantics();\r\n // Add class for custom editors.\r\n if (this.customEditors.getActiveEditor() !== null) {\r\n const { toolbar } = this.customEditors.getActiveEditor();\r\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\r\n } else {\r\n // We need - if exists - the editor name from MathML\r\n // class attribute.\r\n Object.keys(this.customEditors.editors).forEach((key) => {\r\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\r\n });\r\n }\r\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\r\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\r\n } else {\r\n this.integrationModel.updateFormula(null);\r\n }\r\n\r\n this.customEditors.disable();\r\n this.integrationModel.notifyWindowClosed();\r\n\r\n // Set disabled focus to prevent lost focus.\r\n this.setEmptyMathML();\r\n this.customEditors.disable();\r\n }\r\n\r\n /**\r\n * Sets an empty MathML as {@link ContentManager.editor} content.\r\n * This will open the MT/CT editor with the hand mode.\r\n * It adds dir rtl in case of it's activated.\r\n */\r\n setEmptyMathML() {\r\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\r\n const isRTL = this.editor.getEditorModel().isRTL();\r\n\r\n if (isMobile || this.integrationModel.forcedHandMode) {\r\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\r\n const mathML = `[]`;\r\n this.setMathML(mathML, true);\r\n } else {\r\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\r\n const mathML = ``;\r\n this.setMathML(mathML, true);\r\n }\r\n }\r\n\r\n /**\r\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\r\n * - Updates the {@link ContentManager.editor} content\r\n * (with an empty MathML or an existing formula),\r\n * - Updates the {@link ContentManager.editor} toolbar.\r\n * - Recovers the the focus.\r\n */\r\n onOpen() {\r\n if (this.isNewElement) {\r\n this.setEmptyMathML();\r\n } else {\r\n this.setMathML(this.mathML);\r\n }\r\n const toolbar = this.updateToolbar();\r\n this.onFocus();\r\n\r\n if (this.deviceProperties.isIOS) {\r\n const zoom = document.documentElement.clientWidth / window.innerWidth;\r\n\r\n if (zoom !== 1) {\r\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\r\n this.setKeyboardMode();\r\n }\r\n }\r\n\r\n const trigger = this.dbclick ? \"formula\" : \"button\";\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\r\n toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalOpen\", {});\r\n\r\n if (this.integrationModel.forcedHandMode) {\r\n this.hideHandModeButton();\r\n\r\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\r\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\r\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Change Editor in keyboard mode when is loaded\r\n */\r\n setKeyboardMode() {\r\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\r\n if (wrsEditor) {\r\n wrsEditor.classList.remove(\"wrs_handOpen\");\r\n wrsEditor.classList.remove(\"wrs_disablePalette\");\r\n } else {\r\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Hides the hand <-> keyboard mode switch.\r\n *\r\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\r\n * any change on those classes will make this code stop working properly.\r\n *\r\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\r\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\r\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\r\n */\r\n hideHandModeButton(forced = true) {\r\n if (this.handSwitchHidden) {\r\n return; // hand <-> keyboard button already hidden.\r\n }\r\n\r\n // \"Open hand mode\" button takes a little bit to be available.\r\n // This selector gets the hand <-> keyboard mode switch\r\n const handModeButtonSelector =\r\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\r\n\r\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\r\n // We use an observer to ensure that the button it hidden as soon as it appears.\r\n if (forced) {\r\n const mutationInstance = new MutationObserver((mutations) => {\r\n const handModeButton = document.querySelector(handModeButtonSelector);\r\n if (handModeButton) {\r\n handModeButton.hidden = true;\r\n this.handSwitchHidden = true;\r\n mutationInstance.disconnect();\r\n }\r\n });\r\n mutationInstance.observe(document.body, {\r\n attributes: true,\r\n childList: true,\r\n characterData: true,\r\n subtree: true,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\r\n *\r\n * @param {String} mathml The original KeyBoard MathML\r\n * @param {Object} editor The editor object.\r\n */\r\n async openHandOnKeyboardMathML(mathml, editor) {\r\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\r\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\r\n await new Promise((resolve) => {\r\n editor.setMathMLWithCallback(mathml, resolve);\r\n });\r\n\r\n // We wait until the hand editor object exists.\r\n await this.waitForHand(editor);\r\n\r\n // Logic to get the hand traces and open the formula in hand mode.\r\n // This logic comes from the editor.\r\n const handEditor = editor.hand;\r\n editor.handTemporalMathML = editor.getMathML();\r\n const handCoordinates = editor.editorModel.getHandStrokes();\r\n handEditor.setStrokes(handCoordinates);\r\n handEditor.fitStrokes(true);\r\n editor.openHand();\r\n }\r\n\r\n /**\r\n * Waits until the hand editor object exists.\r\n * @param {Obect} editor The editor object.\r\n */\r\n async waitForHand(editor) {\r\n while (!editor.hand) {\r\n await new Promise((resolve) => setTimeout(resolve, 100));\r\n }\r\n }\r\n\r\n /**\r\n * Sets the correct toolbar depending if exist other custom toolbars\r\n * at the same time (e.g: Chemistry).\r\n */\r\n updateToolbar() {\r\n this.updateTitle(this.modalDialogInstance);\r\n const customEditor = this.customEditors.getActiveEditor();\r\n\r\n let toolbar;\r\n if (customEditor) {\r\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\r\n\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n }\r\n } else {\r\n toolbar = this.getToolbar();\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n this.customEditors.disable();\r\n }\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\r\n * sets the custom editor title. Otherwise sets the default title.\r\n */\r\n updateTitle() {\r\n const customEditor = this.customEditors.getActiveEditor();\r\n if (customEditor) {\r\n this.modalDialogInstance.setTitle(customEditor.title);\r\n } else {\r\n this.modalDialogInstance.setTitle(\"MathType\");\r\n }\r\n }\r\n\r\n /**\r\n * Returns the editor toolbar, depending on the configuration local or server side.\r\n * @returns {String} - Toolbar identifier.\r\n */\r\n getToolbar() {\r\n let toolbar = \"general\";\r\n if (\"toolbar\" in this.editorAttributes) {\r\n ({ toolbar } = this.editorAttributes);\r\n }\r\n // TODO: Change global integration variable for integration custom toolbar.\r\n if (toolbar === \"general\") {\r\n // eslint-disable-next-line camelcase\r\n toolbar =\r\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\r\n ? \"general\"\r\n : _wrs_int_wirisProperties.toolbar;\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Sets the current {@link ContentManager.editor} instance toolbar.\r\n * @param {String} toolbar - The toolbar name.\r\n */\r\n setToolbar(toolbar) {\r\n this.toolbar = toolbar;\r\n this.editor.setParams({ toolbar: this.toolbar });\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setCustomHeaders(headers) {\r\n let headersObj = {};\r\n\r\n // We control that we only get String or Object as the input.\r\n if (typeof headers === \"object\") {\r\n headersObj = headers;\r\n } else if (typeof headers === \"string\") {\r\n headersObj = Util.convertStringToObject(headers);\r\n }\r\n\r\n this.editor.setParams({ customHeaders: headersObj });\r\n return headersObj;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed. The logic of the changes\r\n * is delegated to {@link EditorListener} class.\r\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\r\n */\r\n hasChanges() {\r\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n // Code to detect Esc event.\r\n // There should be only one element with class name 'wrs_pressed' at the same time.\r\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\r\n if (list.length === 0) {\r\n this.modalDialogInstance.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.modalDialogInstance.submitButton) {\r\n // Focus is on OK button.\r\n this.editor.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\r\n // Focus is on minimize button (_).\r\n this.modalDialogInstance.closeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\r\n // Focus on cancel button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n this.modalDialogInstance.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\r\n // Focus is on X button.\r\n this.modalDialogInstance.minimizeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\r\n // Focus on help button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n const element = document.querySelector('[title=\"Manual\"]');\r\n element.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n } else {\r\n // There should be only one element with class name 'wrs_formulaDisplay'.\r\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\r\n // Focus is on formuladisplay.\r\n this.modalDialogInstance.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * A custom editor is MathType editor with a different\r\n * @typedef {Object} CustomEditor\r\n * @property {String} CustomEditor.name - Custom editor name.\r\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\r\n * @property {String} CustomEditor.icon - Custom editor icon.\r\n * @property {String} CustomEditor.confVariable - Configuration property to manage\r\n * the availability of the custom editor.\r\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\r\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\r\n */\r\n\r\nexport default class CustomEditors {\r\n /**\r\n * @classdesc\r\n * This class represents the MathType custom editors manager.\r\n * A custom editor is MathType editor with a custom toolbar.\r\n * This class associates a {@link CustomEditor} to:\r\n * - It's own formulas\r\n * - A custom toolbar\r\n * - An icon to open it from a HTML editor.\r\n * - A tooltip for the icon.\r\n * - A global variable to enable or disable it globally.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * The custom editors.\r\n * @type {Array.}\r\n */\r\n\r\n this.editors = [];\r\n /**\r\n * The active editor name.\r\n * @type {String}\r\n */\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Adds a {@link CustomEditor} to editors array.\r\n * @param {String} editorName - The editor name.\r\n * @param {CustomEditor} editorParams - The custom editor parameters.\r\n */\r\n addEditor(editorName, editorParams) {\r\n const customEditor = {};\r\n customEditor.name = editorParams.name;\r\n customEditor.toolbar = editorParams.toolbar;\r\n customEditor.icon = editorParams.icon;\r\n customEditor.confVariable = editorParams.confVariable;\r\n customEditor.title = editorParams.title;\r\n customEditor.tooltip = editorParams.tooltip;\r\n this.editors[editorName] = customEditor;\r\n }\r\n\r\n /**\r\n * Enables a {@link CustomEditor}.\r\n * @param {String} customEditorName - The custom editor name.\r\n */\r\n enable(customEditorName) {\r\n this.activeEditor = customEditorName;\r\n }\r\n\r\n /**\r\n * Disables a {@link CustomEditor}.\r\n */\r\n disable() {\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Returns the active editor.\r\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\r\n */\r\n getActiveEditor() {\r\n if (this.activeEditor !== \"default\") {\r\n return this.editors[this.activeEditor];\r\n }\r\n return null;\r\n }\r\n}\r\n","/**\r\n * Represents the configuration properties generated from the frontend (JavaScript variables).\r\n * @type {Object}\r\n * @property {string} imageClassName - Default MathType formula image class.\r\n * @property {string} imageClassName - Default MathType CAS image class.\r\n * @ignore\r\n */\r\nconst jsProperties = {\r\n imageCustomEditorName: \"data-custom-editor\",\r\n imageClassName: \"Wirisformula\",\r\n CASClassName: \"Wiriscas\",\r\n};\r\nexport default jsProperties;\r\n","export default class Event {\r\n /**\r\n * @classdesc\r\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\r\n *\r\n * ```js\r\n * let customEvent = new Event();\r\n * customEvent.properties = {};\r\n *\r\n * let listeners = new Listeners();\r\n * listeners.newListener(eventName, callback);\r\n *\r\n * listeners.fire(eventName, customEvent) *\r\n * ```\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the event should be cancelled.\r\n * @type {Boolean}\r\n */\r\n\r\n this.cancelled = false;\r\n /**\r\n * Indicates if the event should be prevented.\r\n * @type {Boolean}\r\n */\r\n this.defaultPrevented = false;\r\n }\r\n\r\n /**\r\n * Cancels the event.\r\n */\r\n cancel() {\r\n this.cancelled = true;\r\n }\r\n\r\n /**\r\n * Prevents the default action.\r\n */\r\n preventDefault() {\r\n this.defaultPrevented = true;\r\n }\r\n}\r\n","import IntegrationModel from \"./integrationmodel\";\r\n\r\n/**\r\n\r\n */\r\nexport default class PopUpMessage {\r\n /**\r\n * @classdesc\r\n * This class represents a dialog message overlaying a DOM element in order to\r\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\r\n * o canceled. In this last case a callback function should be called.\r\n * @constructs\r\n * @param {Object} popupMessageAttributes - Object containing popup properties.\r\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\r\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\r\n * methods for close and cancel actions.\r\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\r\n */\r\n constructor(popupMessageAttributes) {\r\n /**\r\n * Element to be overlaid when the popup appears.\r\n */\r\n this.overlayElement = popupMessageAttributes.overlayElement;\r\n\r\n this.callbacks = popupMessageAttributes.callbacks;\r\n\r\n /**\r\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\r\n */\r\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\r\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\r\n\r\n /**\r\n * HTMLElement to display the popup message, close button and cancel button.\r\n */\r\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n this.message.id = \"wrs_popupmessage\";\r\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\r\n this.message.setAttribute(\"role\", \"dialog\");\r\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\r\n const paragraph = document.createElement(\"p\");\r\n const text = document.createTextNode(popupMessageAttributes.strings.message);\r\n paragraph.appendChild(text);\r\n paragraph.id = \"description_txt\";\r\n this.message.appendChild(paragraph);\r\n\r\n /**\r\n * HTML element overlaying the overlayElement.\r\n */\r\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\r\n // We create a overlay that close popup message on click in there\r\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n /**\r\n * HTML element containing cancel and close buttons.\r\n */\r\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\r\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\r\n this.buttonArea.id = \"wrs_popup_button_area\";\r\n\r\n // Close button arguments.\r\n const buttonSubmitArguments = {\r\n class: \"wrs_button_accept\",\r\n innerHTML: popupMessageAttributes.strings.submitString,\r\n id: \"wrs_popup_accept_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-close-button\",\r\n };\r\n\r\n /**\r\n * Close button arguments.\r\n */\r\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\r\n this.buttonArea.appendChild(this.closeButton);\r\n\r\n // Cancel button arguments.\r\n const buttonCancelArguments = {\r\n class: \"wrs_button_cancel\",\r\n innerHTML: popupMessageAttributes.strings.cancelString,\r\n id: \"wrs_popup_cancel_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\r\n };\r\n\r\n /**\r\n * Cancel button.\r\n */\r\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\r\n this.buttonArea.appendChild(this.cancelButton);\r\n }\r\n\r\n /**\r\n * This method create a button with arguments and return button dom object\r\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\r\n * @param {String} parameters.id - Button id.\r\n * @param {String} parameters.class - Button class name.\r\n * @param {String} parameters.innerHTML - Button innerHTML text.\r\n * @param {Object} callback- Callback method to call on click event.\r\n * @returns {HTMLElement} HTML button.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createButton(parameters, callback) {\r\n let element = {};\r\n element = document.createElement(\"button\");\r\n element.setAttribute(\"id\", parameters.id);\r\n element.setAttribute(\"class\", parameters.class);\r\n element.innerHTML = parameters.innerHTML;\r\n element.addEventListener(\"click\", callback);\r\n if (parameters[\"data-testid\"]) {\r\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\r\n }\r\n\r\n return element;\r\n }\r\n\r\n /**\r\n * Shows the popupmessage containing a message, and two buttons\r\n * to cancel the action or close the modal dialog.\r\n */\r\n show() {\r\n if (this.overlayWrapper.style.display !== \"block\") {\r\n // Clear focus with blur for prevent press any key.\r\n document.activeElement.blur();\r\n this.overlayWrapper.style.display = \"block\";\r\n this.closeButton.focus();\r\n } else {\r\n this.overlayWrapper.style.display = \"none\";\r\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\r\n }\r\n }\r\n\r\n /**\r\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\r\n * A callback method is called (if defined). For example a method to focus the overlaid element.\r\n */\r\n cancelAction() {\r\n this.overlayWrapper.style.display = \"none\";\r\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\r\n this.callbacks.cancelCallback();\r\n // Set temporal image to null to prevent loading\r\n // an existent formula when starting one from scratch. Make focus come back too.\r\n // IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n }\r\n\r\n /**\r\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\r\n * For example to close the overlaid element.\r\n */\r\n closeAction() {\r\n this.cancelAction();\r\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\r\n this.callbacks.closeCallback();\r\n }\r\n IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Code to detect Esc event.\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n this.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.closeButton) {\r\n this.cancelButton.focus();\r\n } else {\r\n this.closeButton.focus();\r\n }\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * This module provides protection against external focus management scripts\r\n * that might interfere with the MathType editor modal.\r\n */\r\n\r\n/**\r\n * focusProtection function creates and returns methods to prevent external scripts from\r\n * interfering with the focus of the MathType modal dialog.\r\n *\r\n * @returns {Object} An object with protect and unprotect methods.\r\n */\r\nconst focusProtection = () => {\r\n /**\r\n * Initialize focus protection on the given modal element.\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to protect\r\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\r\n * @param {HTMLElement} editorElement - The editor element inside the modal\r\n */\r\n const protect = (modalElement, overlayElement, editorElement) => {\r\n if (!modalElement || !overlayElement || !editorElement) {\r\n console.warn(\"FocusProtection: Missing required elements\");\r\n return;\r\n }\r\n\r\n // Apply the focus protection\r\n overrideFocusBehavior(modalElement, editorElement);\r\n };\r\n\r\n /**\r\n * Apply focus protection by overriding focus-related methods\r\n *\r\n * @param {HTMLElement} modalElement - The modal element\r\n * @param {HTMLElement} editorElement - The editor element to keep focused\r\n * @private\r\n */\r\n const overrideFocusBehavior = (modalElement, editorElement) => {\r\n // Store original focus methods to be able to restore them\r\n const originalElementFocus = HTMLElement.prototype.focus;\r\n const originalElementBlur = HTMLElement.prototype.blur;\r\n\r\n // Override the focus method for all elements\r\n HTMLElement.prototype.focus = function (...args) {\r\n // If the modal is open and this is not part of the modal, prevent focus\r\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\r\n // If some external script is trying to focus another element, prevent it\r\n // and restore focus to the editor\r\n if (editorElement) {\r\n // Use the original focus method to avoid infinite recursion\r\n originalElementFocus.apply(editorElement, args);\r\n }\r\n return;\r\n }\r\n\r\n // Otherwise, allow the focus to happen\r\n originalElementFocus.apply(this, args);\r\n };\r\n\r\n // Store the methods to remove them when the modal is closed\r\n modalElement.originalElementFocus = originalElementFocus;\r\n modalElement.originalElementBlur = originalElementBlur;\r\n };\r\n\r\n /**\r\n * Remove focus protection from the modal\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to unprotect\r\n */\r\n const unprotect = (modalElement) => {\r\n if (!modalElement) {\r\n return;\r\n }\r\n\r\n // Restore original focus methods\r\n if (modalElement.originalElementFocus) {\r\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\r\n delete modalElement.originalElementFocus;\r\n }\r\n\r\n if (modalElement.originalElementBlur) {\r\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\r\n delete modalElement.originalElementBlur;\r\n }\r\n };\r\n\r\n return {\r\n protect,\r\n unprotect,\r\n };\r\n};\r\n\r\nexport default focusProtection;\r\n","// eslint-disable-next-line max-classes-per-file\r\nimport PopUpMessage from \"./popupmessage\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport Listeners from \"./listeners\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Telemeter from \"./telemeter\";\r\nimport IntegrationModel from \"./integrationmodel\";\r\nimport Core from \"./core.src\";\r\nimport focusProtection from \"./focusprotection\";\r\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\r\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\r\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\r\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\r\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\r\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\r\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\r\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\r\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\r\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\r\nconst { unprotect, protect } = focusProtection();\r\n\r\n/**\r\n * @typedef {Object} DeviceProperties\r\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\r\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\r\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\r\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\r\n * False otherwise.\r\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\r\n * False otherwise.\r\n */\r\n\r\nexport default class ModalDialog {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog. The modal dialog admits\r\n * a {@link ContentManager} instance to manage the content of the dialog.\r\n * @constructs\r\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\r\n */\r\n constructor(modalDialogAttributes) {\r\n this.attributes = modalDialogAttributes;\r\n\r\n // Metrics.\r\n const ua = navigator.userAgent.toLowerCase();\r\n const isAndroid = ua.indexOf(\"android\") > -1;\r\n const isIOS = ContentManager.isIOS();\r\n this.iosSoftkeyboardOpened = false;\r\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\r\n this.iosDivHeight = `auto`;\r\n\r\n const deviceWidth = window.outerWidth;\r\n const deviceHeight = window.outerHeight;\r\n\r\n const landscape = deviceWidth > deviceHeight;\r\n const portrait = deviceWidth < deviceHeight;\r\n\r\n // TODO: Detect isMobile without using editor metrics.\r\n const isLandscape = landscape && this.attributes.height > deviceHeight;\r\n const isPortrait = portrait && this.attributes.width > deviceWidth;\r\n const isMobile = ContentManager.isMobile();\r\n\r\n // Obtain number of current instance.\r\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\r\n\r\n // Device object properties.\r\n\r\n /**\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {\r\n orientation: landscape ? \"landscape\" : \"portrait\",\r\n isAndroid,\r\n isIOS,\r\n isMobile,\r\n isDesktop: !isMobile && !isIOS && !isAndroid,\r\n };\r\n\r\n this.properties = {\r\n created: false,\r\n state: \"\",\r\n previousState: \"\",\r\n position: { bottom: 0, right: 10 },\r\n size: { height: 338, width: 580 },\r\n };\r\n\r\n /**\r\n * Object to keep website's style before change it on lock scroll for mobile devices.\r\n * @type {Object}\r\n * @property {String} bodyStylePosition - Previous body style position.\r\n * @property {String} bodyStyleOverflow - Previous body style overflow.\r\n * @property {String} htmlStyleOverflow - Previous body style overflow.\r\n * @property {String} windowScrollX - Previous window's scroll Y.\r\n * @property {String} windowScrollY - Previous window's scroll X.\r\n */\r\n this.websiteBeforeLockParameters = null;\r\n\r\n let attributes = {};\r\n attributes.class = \"wrs_modal_overlay\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.overlay = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title_bar\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.titleBar = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.title = Util.createElement(\"div\", attributes);\r\n this.title.innerHTML = \"offline\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_close_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"close\");\r\n attributes.style = {};\r\n this.closeDiv = Util.createElement(\"a\", attributes);\r\n this.closeDiv.setAttribute(\"role\", \"button\");\r\n this.closeDiv.setAttribute(\"tabindex\", 3);\r\n // Apply styles and events after the creation as createElement doesn't process them correctly\r\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\r\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\r\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\r\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\r\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\r\n // To identifiy the element in automated testing\r\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_stack_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"exit_fullscreen\");\r\n this.stackDiv = Util.createElement(\"a\", attributes);\r\n this.stackDiv.setAttribute(\"role\", \"button\");\r\n this.stackDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\r\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\r\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\r\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\r\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\r\n // To identifiy the element in automated testing\r\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_maximize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"fullscreen\");\r\n this.maximizeDiv = Util.createElement(\"a\", attributes);\r\n this.maximizeDiv.setAttribute(\"role\", \"button\");\r\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\r\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\r\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\r\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\r\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\r\n // To identifiy the element in automated testing\r\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_minimize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"minimize\");\r\n this.minimizeDiv = Util.createElement(\"a\", attributes);\r\n this.minimizeDiv.setAttribute(\"role\", \"button\");\r\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\r\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\r\n // To identify the element in automated testing\r\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_dialogContainer\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.role = \"dialog\";\r\n this.container = Util.createElement(\"div\", attributes);\r\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_wrapper\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.wrapper = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_content_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.contentContainer = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_controls\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.controls = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_buttons_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.buttonContainer = Util.createElement(\"div\", attributes);\r\n\r\n // Buttons: all button must be created using createSubmitButton method.\r\n this.submitButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_accept\"),\r\n class: \"wrs_modal_button_accept\",\r\n innerHTML: StringManager.get(\"accept\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-insert-button\",\r\n },\r\n this.submitAction.bind(this),\r\n );\r\n\r\n this.cancelButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_cancel\"),\r\n class: \"wrs_modal_button_cancel\",\r\n innerHTML: StringManager.get(\"cancel\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cancel-button\",\r\n },\r\n this.cancelAction.bind(this),\r\n );\r\n\r\n this.contentManager = null;\r\n\r\n // Overlay popup.\r\n const popupStrings = {\r\n cancelString: StringManager.get(\"cancel\"),\r\n submitString: StringManager.get(\"close\"),\r\n message: StringManager.get(\"close_modal_warning\"),\r\n };\r\n\r\n const callbacks = {\r\n closeCallback: () => {\r\n this.close(\"mtc_close\");\r\n },\r\n cancelCallback: () => {\r\n this.focus();\r\n },\r\n };\r\n\r\n const popupupProperties = {\r\n overlayElement: this.container,\r\n callbacks,\r\n strings: popupStrings,\r\n };\r\n\r\n this.popup = new PopUpMessage(popupupProperties);\r\n\r\n /**\r\n * Indicates if directionality of the modal dialog is RTL. false by default.\r\n * @type {Boolean}\r\n */\r\n this.rtl = false;\r\n if (\"rtl\" in this.attributes) {\r\n this.rtl = this.attributes.rtl;\r\n }\r\n\r\n // Event handlers need modal instance context.\r\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\r\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\r\n }\r\n\r\n /**\r\n * This method sets an ContentManager instance to ModalDialog. ContentManager\r\n * manages the logic of ModalDialog content: submit, update, close and changes.\r\n * @param {ContentManager} contentManager - ContentManager instance.\r\n */\r\n setContentManager(contentManager) {\r\n this.contentManager = contentManager;\r\n }\r\n\r\n /**\r\n * Returns the modal contentElement object.\r\n * @returns {ContentManager} the instance of the ContentManager class.\r\n */\r\n getContentManager() {\r\n return this.contentManager;\r\n }\r\n\r\n /**\r\n * This method is called when the modal object has been submitted. Calls\r\n * contentElement submitAction method - if exists - and closes the modal\r\n * object. No logic about the content should be placed here,\r\n * contentElement.submitAction is the responsible of the content logic.\r\n */\r\n async submitAction() {\r\n if (typeof this.contentManager.submitAction !== \"undefined\") {\r\n this.contentManager.submitAction();\r\n }\r\n\r\n await this.close(\"mtc_insert\");\r\n }\r\n\r\n /**\r\n * Performs the cancel action.\r\n * If there are no changes in the content, it closes the modal.\r\n * Otherwise, it shows a pop-up message to confirm the cancel action.\r\n * @returns {Promise} - A promise that resolves when the modal is closed.\r\n */\r\n async cancelAction() {\r\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\r\n IntegrationModel.setActionsOnCancelButtons();\r\n await this.close(\"mtc_close\");\r\n } else {\r\n this.showPopUpMessage();\r\n }\r\n }\r\n\r\n /**\r\n * Returns a button element.\r\n * @param {Object} properties - Input button properties.\r\n * @param {String} properties.class - Input button class.\r\n * @param {String} properties.innerHTML - Input button innerHTML.\r\n * @param {Object} callback - Callback function associated to click event.\r\n * @returns {HTMLButtonElement} The button element.\r\n *\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createSubmitButton(properties, callback) {\r\n class SubmitButton {\r\n constructor() {\r\n this.element = document.createElement(\"button\");\r\n this.element.id = properties.id;\r\n this.element.className = properties.class;\r\n this.element.innerHTML = properties.innerHTML;\r\n this.element.dataset.testid = properties[\"data-testid\"];\r\n Util.addEvent(this.element, \"click\", callback);\r\n }\r\n\r\n getElement() {\r\n return this.element;\r\n }\r\n }\r\n return new SubmitButton(properties, callback).getElement();\r\n }\r\n\r\n /**\r\n * Creates the modal window object inserting a contentElement object.\r\n */\r\n create() {\r\n /* Modal Window Structure\r\n _____________________________________________________________________________________\r\n |wrs_modal_dialog_Container |\r\n | _________________________________________________________________________________ |\r\n | |title_bar minimize_button stack_button close_button | |\r\n | |_______________________________________________________________________________| |\r\n | |wrapper | |\r\n | | _____________________________________________________________________________ | |\r\n | | |content | | |\r\n | | | | | |\r\n | | | | | |\r\n | | |___________________________________________________________________________| | |\r\n | | _____________________________________________________________________________ | |\r\n | | |controls | | |\r\n | | | ___________________________________ | | |\r\n | | | |buttonContainer | | | |\r\n | | | | _______________________________ | | | |\r\n | | | | |button_accept | button_cancel| | | | |\r\n | | | |_|_____________ |______________|_| | | |\r\n | | |___________________________________________________________________________| | |\r\n | |_______________________________________________________________________________| |\r\n |___________________________________________________________________________________| */\r\n\r\n this.titleBar.appendChild(this.closeDiv);\r\n this.titleBar.appendChild(this.stackDiv);\r\n this.titleBar.appendChild(this.maximizeDiv);\r\n this.titleBar.appendChild(this.minimizeDiv);\r\n this.titleBar.appendChild(this.title);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n this.container.appendChild(this.titleBar);\r\n }\r\n\r\n this.wrapper.appendChild(this.contentContainer);\r\n this.wrapper.appendChild(this.controls);\r\n\r\n this.controls.appendChild(this.buttonContainer);\r\n\r\n this.buttonContainer.appendChild(this.submitButton);\r\n this.buttonContainer.appendChild(this.cancelButton);\r\n\r\n this.container.appendChild(this.wrapper);\r\n\r\n // Check if browser has scrollBar before modal has modified.\r\n this.recalculateScrollBar();\r\n\r\n document.body.appendChild(this.container);\r\n document.body.appendChild(this.overlay);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n // Desktop.\r\n this.createModalWindowDesktop();\r\n this.createResizeButtons();\r\n\r\n this.addListeners();\r\n // Maximize window only when the configuration is set and the device is not iOS or Android.\r\n if (Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n } else if (this.deviceProperties.isAndroid) {\r\n this.createModalWindowAndroid();\r\n } else if (this.deviceProperties.isIOS) {\r\n this.createModalWindowIos();\r\n }\r\n\r\n if (this.contentManager != null) {\r\n this.contentManager.insert(this);\r\n }\r\n\r\n this.properties.open = true;\r\n this.properties.created = true;\r\n\r\n // Checks language directionality.\r\n if (this.isRTL()) {\r\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\r\n this.container.className += \" wrs_modal_rtl\";\r\n }\r\n }\r\n\r\n /**\r\n * Creates a button in the modal object to resize it.\r\n */\r\n createResizeButtons() {\r\n // This is a definition of Resize Button Bottom-Right.\r\n this.resizerBR = document.createElement(\"div\");\r\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\r\n this.resizerBR.innerHTML = \"โ—ข\";\r\n // To identifiy the element in automated testing\r\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\r\n // This is a definition of Resize Button Top-Left.\r\n this.resizerTL = document.createElement(\"div\");\r\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\r\n // To identifiy the element in automated testing\r\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\r\n // Append resize buttons to modal.\r\n this.container.appendChild(this.resizerBR);\r\n this.titleBar.appendChild(this.resizerTL);\r\n // Add events to resize on click and drag.\r\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\r\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\r\n }\r\n\r\n /**\r\n * Initialize variables for Bottom-Right resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateBR(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, false);\r\n }\r\n\r\n /**\r\n * Initialize variables for Top-Left resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateTL(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, true);\r\n }\r\n\r\n /**\r\n * Common method to initialize variables at resize.\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n initializeResizeProperties(mouseEvent, leftOption) {\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n this.resizeDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Save Initial state of modal to compare on drag and obtain the difference.\r\n this.initialWidth = parseInt(this.container.style.width, 10);\r\n this.initialHeight = parseInt(this.container.style.height, 10);\r\n if (!leftOption) {\r\n this.initialRight = parseInt(this.container.style.right, 10);\r\n this.initialBottom = parseInt(this.container.style.bottom, 10);\r\n } else {\r\n this.leftScale = true;\r\n }\r\n if (!this.initialRight) {\r\n this.initialRight = 0;\r\n }\r\n if (!this.initialBottom) {\r\n this.initialBottom = 0;\r\n }\r\n // Disable mouse events on editor when we start to drag modal.\r\n document.body.style[\"user-select\"] = \"none\";\r\n }\r\n\r\n /**\r\n * This method opens the modal window, restoring the previous state, position and metrics,\r\n * if exists. By default the modal object opens in stack mode.\r\n */\r\n open() {\r\n // Removing close class.\r\n this.removeClass(\"wrs_closed\");\r\n // Hiding keyboard for mobile devices.\r\n const { isIOS } = this.deviceProperties;\r\n const { isAndroid } = this.deviceProperties;\r\n const { isMobile } = this.deviceProperties;\r\n if (isIOS || isAndroid || isMobile) {\r\n // Restore scale to 1.\r\n this.restoreWebsiteScale();\r\n this.lockWebsiteScroll();\r\n // Due to editor wait we need to wait until editor focus.\r\n setTimeout(() => {\r\n this.hideKeyboard();\r\n }, 400);\r\n }\r\n\r\n // New modal window. He need to create the whole object.\r\n if (!this.properties.created) {\r\n this.create();\r\n } else {\r\n // Previous state closed. Open method can be called even the previous state is open,\r\n // for example updating the content of the modal object.\r\n if (!this.properties.open) {\r\n this.properties.open = true;\r\n\r\n // Restoring the previous open state: if the modal object has been closed\r\n // re-open it should preserve the state and the metrics.\r\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\r\n this.restoreState();\r\n }\r\n }\r\n\r\n // Maximize window only when the configuration is set and the device is not iOs or Android.\r\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n\r\n // In iOS we need to recalculate the size of the modal object because\r\n // iOS keyboard is a float div which can overlay the modal object.\r\n if (this.deviceProperties.isIOS) {\r\n this.iosSoftkeyboardOpened = false;\r\n }\r\n }\r\n\r\n if (!ContentManager.isEditorLoaded()) {\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.displayEditor();\r\n });\r\n this.contentManager.addListener(listener);\r\n } else {\r\n this.displayEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Prepares and displays the editor in the modal.\r\n *\r\n * This method is responsible for displaying the MathType editor inside the modal container.\r\n *\r\n * For Moodle environments, it applies focus protection to prevent external scripts\r\n * from hijacking focus away from the editor while it's open. This is particularly\r\n * important in Moodle which may have its own focus management scripts.\r\n * @returns {void}\r\n */\r\n displayEditor() {\r\n if (this.contentManager.integrationModel.isMoodle) {\r\n protect(this.container, this.overlay, this.contentContainer);\r\n }\r\n\r\n // Initialize and open the editor using the contentManager.\r\n this.contentManager.onOpen(this);\r\n }\r\n\r\n /**\r\n * Closes the modal.\r\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\r\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\r\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\r\n * @returns {Promise} A promise that resolves when the modal is closed.\r\n */\r\n async close(trigger) {\r\n // Remove focus protection before closing\r\n unprotect(this.container);\r\n\r\n this.removeClass(\"wrs_maximized\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.removeClass(\"wrs_stack\");\r\n this.addClass(\"wrs_closed\");\r\n this.saveModalProperties();\r\n this.unlockWebsiteScroll();\r\n this.properties.open = false;\r\n\r\n if (trigger) {\r\n try {\r\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\r\n toolbar: this.contentManager.toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\r\n }\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalClose\", {});\r\n }\r\n\r\n /**\r\n * Closes modal window and destroys the object.\r\n */\r\n destroy() {\r\n // Remove focus protection before destroying\r\n unprotect(this.container);\r\n\r\n // Close modal window.\r\n this.close();\r\n // Remove listeners and destroy the object.\r\n this.removeListeners();\r\n this.overlay.remove();\r\n this.container.remove();\r\n // Reset properties to allow open again.\r\n this.properties.created = false;\r\n }\r\n\r\n /**\r\n * Sets the website scale to one.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n restoreWebsiteScale() {\r\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\r\n // Let the equal symbols in order to search and make meta's final content.\r\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\r\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\r\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\r\n const contentAttr = viewportelement.getAttribute(\"content\");\r\n // If it exists, we need to maintain old values and put our values.\r\n if (contentAttr) {\r\n const attrArray = contentAttr.split(\",\");\r\n let finalContentMeta = \"\";\r\n const oldAttrs = [];\r\n for (let i = 0; i < attrArray.length; i += 1) {\r\n let isAttrToUpdate = false;\r\n let j = 0;\r\n while (!isAttrToUpdate && j < contentAttrs.length) {\r\n if (attrArray[i].indexOf(contentAttrs[j])) {\r\n isAttrToUpdate = true;\r\n }\r\n j += 1;\r\n }\r\n\r\n if (!isAttrToUpdate) {\r\n oldAttrs.push(attrArray[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < contentAttrs.length; i += 1) {\r\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\r\n finalContentMeta += i === 0 ? attr : `,${attr}`;\r\n }\r\n\r\n for (let i = 0; i < oldAttrs.length; i += 1) {\r\n finalContentMeta += `,${oldAttrs[i]}`;\r\n }\r\n viewportelement.setAttribute(\"content\", finalContentMeta);\r\n // It needs to set to empty because setAttribute refresh only when attribute is different.\r\n viewportelement.setAttribute(\"content\", \"\");\r\n viewportelement.setAttribute(\"content\", contentAttr);\r\n } else {\r\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\r\n viewportelement.removeAttribute(\"content\");\r\n }\r\n };\r\n\r\n if (!viewportmeta) {\r\n viewportmeta = document.createElement(\"meta\");\r\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n viewportmeta.remove();\r\n } else {\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n }\r\n }\r\n\r\n /**\r\n * Locks website scroll for mobile devices.\r\n */\r\n lockWebsiteScroll() {\r\n this.websiteBeforeLockParameters = {\r\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\r\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\r\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\r\n windowScrollX: window.scrollX,\r\n windowScrollY: window.scrollY,\r\n };\r\n }\r\n\r\n /**\r\n * Unlocks website scroll for mobile devices.\r\n */\r\n unlockWebsiteScroll() {\r\n if (this.websiteBeforeLockParameters) {\r\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\r\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\r\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\r\n const { windowScrollX } = this.websiteBeforeLockParameters;\r\n const { windowScrollY } = this.websiteBeforeLockParameters;\r\n window.scrollTo(windowScrollX, windowScrollY);\r\n this.websiteBeforeLockParameters = null;\r\n }\r\n }\r\n\r\n /**\r\n * Util function to known if browser is IE11.\r\n * @returns {Boolean} true if the browser is IE11. false otherwise.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n isIE11() {\r\n if (\r\n navigator.userAgent.search(\"Msie/\") >= 0 ||\r\n navigator.userAgent.search(\"Trident/\") >= 0 ||\r\n navigator.userAgent.search(\"Edge/\") >= 0\r\n ) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns if the current language type is RTL.\r\n * @return {Boolean} true if current language is RTL. false otherwise.\r\n */\r\n isRTL() {\r\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\r\n return true;\r\n }\r\n return this.rtl;\r\n }\r\n\r\n /**\r\n * Adds a class to all modal ModalDialog DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n addClass(className) {\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.titleBar, className);\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.container, className);\r\n Util.addClass(this.contentContainer, className);\r\n Util.addClass(this.stackDiv, className);\r\n Util.addClass(this.minimizeDiv, className);\r\n Util.addClass(this.maximizeDiv, className);\r\n Util.addClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Remove a class from all modal DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n removeClass(className) {\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.titleBar, className);\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.container, className);\r\n Util.removeClass(this.contentContainer, className);\r\n Util.removeClass(this.stackDiv, className);\r\n Util.removeClass(this.minimizeDiv, className);\r\n Util.removeClass(this.maximizeDiv, className);\r\n Util.removeClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Create modal dialog for desktop.\r\n */\r\n createModalWindowDesktop() {\r\n this.addClass(\"wrs_modal_desktop\");\r\n this.stack();\r\n }\r\n\r\n /**\r\n * Create modal dialog for non android devices.\r\n */\r\n createModalWindowAndroid() {\r\n this.addClass(\"wrs_modal_android\");\r\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Create modal dialog for iOS devices.\r\n */\r\n createModalWindowIos() {\r\n this.addClass(\"wrs_modal_ios\");\r\n // Refresh the size when the orientation is changed.\r\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Restore previous state, position and size of previous stacked modal dialog.\r\n */\r\n restoreState() {\r\n if (this.properties.state === \"maximized\") {\r\n // Reset states for prevent return to stack state.\r\n this.maximize();\r\n } else if (this.properties.state === \"minimized\") {\r\n // Reset states for prevent return to stack state.\r\n this.properties.state = this.properties.previousState;\r\n this.properties.previousState = \"\";\r\n this.minimize();\r\n } else {\r\n this.stack();\r\n }\r\n }\r\n\r\n /**\r\n * Stacks the modal object.\r\n */\r\n stack() {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"stack\";\r\n this.removeClass(\"wrs_maximized\");\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.addClass(\"wrs_stack\");\r\n\r\n // Change maximize/minimize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n this.restoreModalProperties();\r\n\r\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\r\n this.setResizeButtonsVisibility();\r\n }\r\n\r\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Minimizes the modal object.\r\n */\r\n minimize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n this.title.style.cursor = \"pointer\";\r\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\r\n this.stack();\r\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n // Setting css to prevent important tag into css style.\r\n this.container.style.height = \"30px\";\r\n this.container.style.width = \"250px\";\r\n this.container.style.bottom = \"0px\";\r\n this.container.style.right = \"10px\";\r\n\r\n this.removeListeners();\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"minimized\";\r\n this.setResizeButtonsVisibility();\r\n this.minimizeDiv.title = StringManager.get(\"maximize\");\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.removeClass(\"wrs_stack\");\r\n } else {\r\n this.removeClass(\"wrs_maximized\");\r\n }\r\n this.addClass(\"wrs_minimized\");\r\n\r\n // Change minimize icon to maximize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n }\r\n }\r\n\r\n /**\r\n * Maximizes the modal object.\r\n */\r\n maximize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n if (this.properties.state !== \"maximized\") {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"maximized\";\r\n }\r\n // Don't permit resize on maximize mode.\r\n this.setResizeButtonsVisibility();\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.container.style.left = null;\r\n this.container.style.top = null;\r\n this.removeClass(\"wrs_stack\");\r\n }\r\n\r\n this.addClass(\"wrs_maximized\");\r\n\r\n // Change maximize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n // Set size to 80% screen with a max size.\r\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\r\n if (this.container.clientHeight > 700) {\r\n this.container.style.height = \"700px\";\r\n }\r\n if (this.container.clientWidth > 1200) {\r\n this.container.style.width = \"1200px\";\r\n }\r\n\r\n // Setting modal position in center on screen.\r\n const { innerHeight } = window;\r\n const { innerWidth } = window;\r\n const { offsetHeight } = this.container;\r\n const { offsetWidth } = this.container;\r\n const bottom = innerHeight / 2 - offsetHeight / 2;\r\n const right = innerWidth / 2 - offsetWidth / 2;\r\n\r\n this.setPosition(bottom, right);\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n this.recalculateSize();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Expand again the modal object from a minimized state.\r\n */\r\n reExpand() {\r\n if (this.properties.state === \"minimized\") {\r\n if (this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n this.stack();\r\n }\r\n this.title.style.cursor = \"\";\r\n }\r\n }\r\n\r\n /**\r\n * Sets modal size.\r\n * @param {Number} height - Height of the ModalDialog\r\n * @param {Number} width - Width of the ModalDialog.\r\n */\r\n setSize(height, width) {\r\n this.container.style.height = `${height}px`;\r\n this.container.style.width = `${width}px`;\r\n this.recalculateSize();\r\n }\r\n\r\n /**\r\n * Sets modal position using bottom and right style attributes.\r\n * @param {number} bottom - bottom attribute.\r\n * @param {number} right - right attribute.\r\n */\r\n setPosition(bottom, right) {\r\n this.container.style.bottom = `${bottom}px`;\r\n this.container.style.right = `${right}px`;\r\n }\r\n\r\n /**\r\n * Saves position and size parameters of and open ModalDialog. This attributes\r\n * are needed to restore it on re-open.\r\n */\r\n saveModalProperties() {\r\n // Saving values of modal only when modal is in stack state.\r\n if (this.properties.state === \"stack\") {\r\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\r\n this.properties.position.right = parseInt(this.container.style.right, 10);\r\n this.properties.size.width = parseInt(this.container.style.width, 10);\r\n this.properties.size.height = parseInt(this.container.style.height, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Restore ModalDialog position and size parameters.\r\n */\r\n restoreModalProperties() {\r\n if (this.properties.state === \"stack\") {\r\n // Restoring Bottom and Right values from last modal.\r\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\r\n // Restoring Height and Left values from last modal.\r\n this.setSize(this.properties.size.height, this.properties.size.width);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the modal dialog initial size.\r\n */\r\n recalculateSize() {\r\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\r\n }\r\n\r\n /**\r\n * Enable or disable visibility of resize buttons in modal window depend on state.\r\n */\r\n setResizeButtonsVisibility() {\r\n if (this.properties.state === \"stack\") {\r\n this.resizerTL.style.visibility = \"visible\";\r\n this.resizerBR.style.visibility = \"visible\";\r\n } else {\r\n this.resizerTL.style.visibility = \"hidden\";\r\n this.resizerBR.style.visibility = \"hidden\";\r\n }\r\n }\r\n\r\n /**\r\n * Makes an object draggable adding mouse and touch events.\r\n */\r\n addListeners() {\r\n // Button events (maximize, minimize, stack and close).\r\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\r\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\r\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\r\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\r\n this.maximizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n }\r\n },\r\n true,\r\n );\r\n this.stackDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.minimizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.closeDiv.addEventListener(\"keypress\", (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n });\r\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\r\n\r\n // Overlay events (close).\r\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n // Mouse events.\r\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\r\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\r\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\r\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\r\n // Key events.\r\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\r\n }\r\n\r\n /**\r\n * Removes draggable events from an object.\r\n */\r\n removeListeners() {\r\n // Mouse events.\r\n Util.removeEvent(window, \"mousedown\", this.startDrag);\r\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\r\n Util.removeEvent(window, \"mousemove\", this.drag);\r\n Util.removeEvent(window, \"resize\", this.onWindowResize);\r\n // Key events.\r\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\r\n }\r\n\r\n /**\r\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n * @return {Object} With the X and Y coordinates.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n eventClient(mouseEvent) {\r\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\r\n const client = {\r\n X: mouseEvent.changedTouches[0].clientX,\r\n Y: mouseEvent.changedTouches[0].clientY,\r\n };\r\n return client;\r\n }\r\n const client = {\r\n X: mouseEvent.clientX,\r\n Y: mouseEvent.clientY,\r\n };\r\n return client;\r\n }\r\n\r\n /**\r\n * Start drag function: set the object dragDataObject with the draggable\r\n * object offsets coordinates.\r\n * when drag starts (on touchstart or mousedown events).\r\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\r\n */\r\n startDrag(mouseEvent) {\r\n if (this.properties.state === \"minimized\") {\r\n return;\r\n }\r\n if (mouseEvent.target === this.title) {\r\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\r\n // Save first click mouse point on screen.\r\n this.dragDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Reset last drag position when start drag.\r\n this.lastDrag = {\r\n x: \"0px\",\r\n y: \"0px\",\r\n };\r\n // Init right and bottom values for window modal if it isn't exist.\r\n if (this.container.style.right === \"\") {\r\n this.container.style.right = \"0px\";\r\n }\r\n if (this.container.style.bottom === \"\") {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n\r\n // Needed for IE11 for apply disabled mouse events on editor because\r\n // internet explorer needs a dynamic object to apply this property.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = 'relative';\r\n }\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n // Obtain screen limits for prevent overflow.\r\n this.limitWindow = this.getLimitWindow();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Updates dragDataObject with the draggable object coordinates when\r\n * the draggable object is being moved.\r\n * @param {MouseEvent} mouseEvent - The mouse event.\r\n */\r\n drag(mouseEvent) {\r\n if (this.dragDataObject) {\r\n mouseEvent.preventDefault();\r\n // Calculate max and min between actual mouse position and limit of screeen.\r\n // It restric the movement of modal into window.\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\r\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\r\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\r\n // Subtract limit with first position to obtain relative pixels increment\r\n // to the anchor point.\r\n const dragX = `${limitX - this.dragDataObject.x}px`;\r\n const dragY = `${limitY - this.dragDataObject.y}px`;\r\n // Save last valid position of modal before window overflow.\r\n this.lastDrag = {\r\n x: dragX,\r\n y: dragY,\r\n };\r\n // This move modal with hardware acceleration.\r\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\r\n }\r\n if (this.resizeDataObject) {\r\n const { innerWidth } = window;\r\n const { innerHeight } = window;\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\r\n if (limitX < 0) {\r\n limitX = 0;\r\n }\r\n\r\n if (limitY < 0) {\r\n limitY = 0;\r\n }\r\n\r\n let scaleMultiplier;\r\n if (this.leftScale) {\r\n scaleMultiplier = -1;\r\n } else {\r\n scaleMultiplier = 1;\r\n }\r\n\r\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\r\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\r\n if (!this.leftScale) {\r\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\r\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\r\n } else {\r\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\r\n this.container.style.width = \"580px\";\r\n }\r\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\r\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\r\n } else {\r\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\r\n this.container.style.height = \"338px\";\r\n }\r\n }\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boundaries of actual window to limit modal movement.\r\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\r\n */\r\n getLimitWindow() {\r\n // Obtain dimensions of window page.\r\n const maxWidth = window.innerWidth;\r\n const maxHeight = window.innerHeight;\r\n\r\n // Calculate relative position of mouse point into window.\r\n const { offsetHeight } = this.container;\r\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\r\n const contStyleRight = parseInt(this.container.style.right, 10);\r\n\r\n const { pageXOffset } = window;\r\n const dragY = this.dragDataObject.y;\r\n const dragX = this.dragDataObject.x;\r\n\r\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\r\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\r\n\r\n // Calculate limits with sizes of window, modal and mouse position.\r\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\r\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\r\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\r\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\r\n const minPointer = { x: minPointerX, y: minPointerY };\r\n const maxPointer = { x: maxPointerX, y: maxPointerY };\r\n return { minPointer, maxPointer };\r\n }\r\n\r\n /**\r\n * Returns the scrollbar width size of browser\r\n * @returns {Number} The scrollbar width.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getScrollBarWidth() {\r\n // Create a paragraph with full width of page.\r\n const inner = document.createElement(\"p\");\r\n inner.style.width = \"100%\";\r\n inner.style.height = \"200px\";\r\n\r\n // Create a hidden div to compare sizes.\r\n const outer = document.createElement(\"div\");\r\n outer.style.position = \"absolute\";\r\n outer.style.top = \"0px\";\r\n outer.style.left = \"0px\";\r\n outer.style.visibility = \"hidden\";\r\n outer.style.width = \"200px\";\r\n outer.style.height = \"150px\";\r\n outer.style.overflow = \"hidden\";\r\n outer.appendChild(inner);\r\n\r\n document.body.appendChild(outer);\r\n const widthOuter = inner.offsetWidth;\r\n\r\n // Change type overflow of paragraph for measure scrollbar.\r\n outer.style.overflow = \"scroll\";\r\n let widthInner = inner.offsetWidth;\r\n\r\n // If measure is the same, we compare with internal div.\r\n if (widthOuter === widthInner) {\r\n widthInner = outer.clientWidth;\r\n }\r\n document.body.removeChild(outer);\r\n\r\n return widthOuter - widthInner;\r\n }\r\n\r\n /**\r\n * Set the dragDataObject to null.\r\n */\r\n stopDrag() {\r\n // Due to we have multiple events that call this function, we need only to execute\r\n // the next modifiers one time,\r\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\r\n if (this.dragDataObject || this.resizeDataObject) {\r\n // If modal doesn't change, it's not necessary to set position with interpolation.\r\n this.container.style.transform = \"\";\r\n if (this.dragDataObject) {\r\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\r\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\r\n }\r\n // We make focus on editor after drag modal windows to prevent lose focus.\r\n this.focus();\r\n // Restore mouse events on iframe.\r\n // this.iframe.style['pointer-events'] = 'auto';\r\n document.body.style[\"user-select\"] = \"\";\r\n // Restore static state of iframe if we use Internet Explorer.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = null;\r\n }\r\n // Active text select event.\r\n Util.removeClass(document.body, \"wrs_noselect\");\r\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\r\n }\r\n this.dragDataObject = null;\r\n this.resizeDataObject = null;\r\n this.initialWidth = null;\r\n this.leftScale = null;\r\n }\r\n\r\n /**\r\n * Recalculates scale for modal when resize browser window.\r\n */\r\n onWindowResize() {\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n }\r\n\r\n /**\r\n * Triggers keyboard events:\r\n * - Tab key tab to go to submit button.\r\n * - Esc key to close the modal dialog.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Popupmessage is not oppened.\r\n if (this.popup.overlayWrapper.style.display !== \"block\") {\r\n // Code to detect Esc event\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n if (this.properties.open) {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.cancelButton) {\r\n this.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.submitButton) {\r\n this.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n } else {\r\n // Popupmessage oppened.\r\n this.popup.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating position for modal dialog when the browser is resized.\r\n */\r\n recalculatePosition() {\r\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\r\n if (parseInt(this.container.style.right, 10) < 0) {\r\n this.container.style.right = \"0px\";\r\n }\r\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\r\n if (parseInt(this.container.style.bottom, 10) < 0) {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating scale for modal when the browser is resized.\r\n */\r\n recalculateScale() {\r\n let sizeModified = false;\r\n\r\n if (parseInt(this.container.style.width, 10) > 580) {\r\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.width = \"580px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (parseInt(this.container.style.height, 10) > 338) {\r\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.height = \"338px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (sizeModified) {\r\n this.recalculateSize();\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating width of browser scroll bar.\r\n */\r\n recalculateScrollBar() {\r\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\r\n if (this.hasScrollBar) {\r\n this.scrollbarWidth = this.getScrollBarWidth();\r\n } else {\r\n this.scrollbarWidth = 0;\r\n }\r\n }\r\n\r\n /**\r\n * Hide soft keyboards on iOS devices.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n hideKeyboard() {\r\n // iOS keyboard can't be detected or hide directly from JavaScript.\r\n // So, this method simulates that user focus a text input and blur\r\n // the selection.\r\n const inputField = document.createElement(\"input\");\r\n this.container.appendChild(inputField);\r\n inputField.focus();\r\n inputField.blur();\r\n // Is removed to not see it.\r\n inputField.remove();\r\n }\r\n\r\n /**\r\n * Focus to contentManager object.\r\n */\r\n focus() {\r\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\r\n this.contentManager.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Returns true when the device is on portrait mode.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n portraitMode() {\r\n return window.innerHeight > window.innerWidth;\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is opened.\r\n */\r\n handleOpenedIosSoftkeyboard() {\r\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\r\n }\r\n }\r\n this.iosSoftkeyboardOpened = true;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is closed.\r\n */\r\n handleClosedIosSoftkeyboard() {\r\n this.iosSoftkeyboardOpened = false;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on iOS.\r\n */\r\n orientationChangeIosSoftkeyboard() {\r\n if (this.iosSoftkeyboardOpened) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\r\n }\r\n } else {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on Android.\r\n */\r\n orientationChangeAndroidSoftkeyboard() {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Set iframe container height.\r\n * @param {Number} height - New height.\r\n */\r\n setContainerHeight(height) {\r\n this.iosDivHeight = height;\r\n this.wrapper.style.height = height;\r\n }\r\n\r\n /**\r\n * Check content of editor before close action.\r\n */\r\n showPopUpMessage() {\r\n if (this.properties.state === \"minimized\") {\r\n this.stack();\r\n }\r\n this.popup.show();\r\n }\r\n\r\n /**\r\n * Sets the title of the modal dialog.\r\n * @param {String} title - Modal dialog title.\r\n */\r\n setTitle(title) {\r\n this.title.innerHTML = title;\r\n }\r\n\r\n /**\r\n * Returns the id of an element, adding the instance number to\r\n * the element class name:\r\n * className --> className[idNumber]\r\n * @param {String} className - The element class name.\r\n * @returns {String} A string appending the instance id to the className.\r\n */\r\n getElementId(className) {\r\n return `${className}[${this.instanceId}]`;\r\n }\r\n}\r\n","/* eslint-disable */\r\nvar polyfills;\r\nexport default polyfills;\r\n\r\n// Polyfills.\r\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\r\nif (!String.prototype.codePointAt) {\r\n (function () {\r\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\r\n var codePointAt = function (position) {\r\n if (this == null) {\r\n throw TypeError();\r\n }\r\n var string = String(this);\r\n var size = string.length;\r\n // `ToInteger`\r\n var index = position ? Number(position) : 0;\r\n if (index != index) {\r\n // better `isNaN`\r\n index = 0;\r\n }\r\n // Account for out-of-bounds indices:\r\n if (index < 0 || index >= size) {\r\n return undefined;\r\n }\r\n // Get the first code unit\r\n var first = string.charCodeAt(index);\r\n var second;\r\n if (\r\n // check if itโ€™s the start of a surrogate pair\r\n first >= 0xd800 &&\r\n first <= 0xdbff && // high surrogate\r\n size > index + 1 // there is a next code unit\r\n ) {\r\n second = string.charCodeAt(index + 1);\r\n if (second >= 0xdc00 && second <= 0xdfff) {\r\n // low surrogate\r\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\r\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\r\n }\r\n }\r\n return first;\r\n };\r\n if (Object.defineProperty) {\r\n Object.defineProperty(String.prototype, \"codePointAt\", {\r\n value: codePointAt,\r\n configurable: true,\r\n writable: true,\r\n });\r\n } else {\r\n String.prototype.codePointAt = codePointAt;\r\n }\r\n })();\r\n}\r\n\r\n// Object.assign polyfill.\r\nif (typeof Object.assign != \"function\") {\r\n // Must be writable: true, enumerable: false, configurable: true\r\n Object.defineProperty(Object, \"assign\", {\r\n value: function assign(target, varArgs) {\r\n // .length of function is 2\r\n \"use strict\";\r\n if (target == null) {\r\n // TypeError if undefined or null\r\n throw new TypeError(\"Cannot convert undefined or null to object\");\r\n }\r\n\r\n var to = Object(target);\r\n\r\n for (var index = 1; index < arguments.length; index++) {\r\n var nextSource = arguments[index];\r\n\r\n if (nextSource != null) {\r\n // Skip over if undefined or null\r\n for (var nextKey in nextSource) {\r\n // Avoid bugs when hasOwnProperty is shadowed\r\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\r\n to[nextKey] = nextSource[nextKey];\r\n }\r\n }\r\n }\r\n }\r\n return to;\r\n },\r\n writable: true,\r\n configurable: true,\r\n });\r\n}\r\n\r\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\r\nif (!Array.prototype.includes) {\r\n Object.defineProperty(Array.prototype, \"includes\", {\r\n value: function (searchElement, fromIndex) {\r\n if (this == null) {\r\n throw new TypeError('\"this\" s null or is not defined');\r\n }\r\n\r\n // 1. Let O be ? ToObject(this value).\r\n var o = Object(this);\r\n\r\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\r\n var len = o.length >>> 0;\r\n\r\n // 3. if len is 0, return false.\r\n if (len === 0) {\r\n return false;\r\n }\r\n\r\n // 4. Let n be ? ToInteger(fromIndex).\r\n // (if fromIndex is undefinedo, this step generates the value 0.)\r\n var n = fromIndex | 0;\r\n\r\n // 5. if n โ‰ฅ 0, then\r\n // a. Let k be n.\r\n // 6. Else n < 0,\r\n // a. Let k be len + n.\r\n // b. if k < 0, let k be 0.\r\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\r\n\r\n function sameValueZero(x, y) {\r\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\r\n }\r\n\r\n // 7. Repeat while k < len\r\n while (k < len) {\r\n // a. let element k be the result of ? Get(O, ! ToString(k)).\r\n // b. if SameValueZero(searchElement, elementK) is true, return true.\r\n if (sameValueZero(o[k], searchElement)) {\r\n return true;\r\n }\r\n // c. Increase k by 1.\r\n k++;\r\n }\r\n\r\n // 8. Return false\r\n return false;\r\n },\r\n });\r\n}\r\n\r\nif (!String.prototype.includes) {\r\n String.prototype.includes = function (search, start) {\r\n \"use strict\";\r\n\r\n if (search instanceof RegExp) {\r\n throw TypeError(\"first argument must not be a RegExp\");\r\n }\r\n if (start === undefined) {\r\n start = 0;\r\n }\r\n return this.indexOf(search, start) !== -1;\r\n };\r\n}\r\n\r\nif (!String.prototype.startsWith) {\r\n Object.defineProperty(String.prototype, \"startsWith\", {\r\n value: function (search, rawPos) {\r\n var pos = rawPos > 0 ? rawPos | 0 : 0;\r\n return this.substring(pos, pos + search.length) === search;\r\n },\r\n });\r\n}\r\n","import Parser from \"./parser\";\r\nimport Util from \"./util\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport CustomEditors from \"./customeditors\";\r\nimport Configuration from \"./configuration\";\r\nimport jsProperties from \"./jsvariables\";\r\nimport Event from \"./event\";\r\nimport Listeners from \"./listeners\";\r\nimport Image from \"./image\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport ModalDialog from \"./modal\";\r\nimport Telemeter from \"./telemeter\";\r\nimport \"./polyfills\";\r\nimport \"../styles/styles.css\";\r\n\r\n/**\r\n * @typedef {Object} CoreProperties\r\n * @property {ServiceProviderProperties} serviceProviderProperties\r\n * - The ServiceProvider class properties. *\r\n */\r\nexport default class Core {\r\n /**\r\n * @classdesc\r\n * This class represents MathType integration Core, managing the following:\r\n * - Integration initialization.\r\n * - Event managing.\r\n * - Insertion of formulas into the edit area.\r\n * ```js\r\n * let core = new Core();\r\n * core.addListener(listener);\r\n * core.language = 'en';\r\n *\r\n * // Initializing Core class.\r\n * core.init(configurationService);\r\n * ```\r\n * @constructs\r\n * Core constructor.\r\n * @param {CoreProperties}\r\n */\r\n constructor(coreProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. 'en' by default.\r\n * @type {String}\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Edit mode, 'images' by default. Admits the following values:\r\n * - images\r\n * - latex\r\n * @type {String}\r\n */\r\n this.editMode = \"images\";\r\n\r\n /**\r\n * Modal dialog instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialog = null;\r\n\r\n /**\r\n * The instance of {@link CustomEditors}. By default\r\n * the only custom editor is the Chemistry editor.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = new CustomEditors();\r\n\r\n /**\r\n * Chemistry editor.\r\n * @type {CustomEditor}\r\n */\r\n const chemEditorParams = {\r\n name: \"Chemistry\",\r\n toolbar: \"chemistry\",\r\n icon: \"chem.png\",\r\n confVariable: \"chemEnabled\",\r\n title: \"ChemType\",\r\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\r\n };\r\n\r\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @typedef IntegrationEnvironment\r\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\r\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\r\n * @property {String} IntegrationEnvironment.version - Integration version.\r\n *\r\n */\r\n\r\n /**\r\n * The environment properties object.\r\n * @type {IntegrationEnvironment}\r\n */\r\n this.environment = {};\r\n\r\n /**\r\n * @typedef EditionProperties\r\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\r\n * False otherwise.\r\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\r\n * Null if the formula is new.\r\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\r\n * @property {Range} editionProperties.range - The range that contains the image element.\r\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\r\n */\r\n\r\n /**\r\n * The properties of the current edition process.\r\n * @type {EditionProperties}\r\n */\r\n this.editionProperties = {};\r\n\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.temporalImage = null;\r\n this.editionProperties.latexRange = null;\r\n this.editionProperties.range = null;\r\n this.editionProperties.editionStartTime = null;\r\n\r\n /**\r\n * The {@link IntegrationModel} instance.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n\r\n /**\r\n * The {@link ContentManager} instance.\r\n * @type {ContentManager}\r\n */\r\n this.contentManager = null;\r\n\r\n /**\r\n * The current browser.\r\n * @type {String}\r\n */\r\n this.browser = (() => {\r\n const ua = navigator.userAgent;\r\n let browser = \"none\";\r\n if (ua.search(\"Edge/\") >= 0) {\r\n browser = \"EDGE\";\r\n } else if (ua.search(\"Chrome/\") >= 0) {\r\n browser = \"CHROME\";\r\n } else if (ua.search(\"Trident/\") >= 0) {\r\n browser = \"IE\";\r\n } else if (ua.search(\"Firefox/\") >= 0) {\r\n browser = \"FIREFOX\";\r\n } else if (ua.search(\"Safari/\") >= 0) {\r\n browser = \"SAFARI\";\r\n }\r\n return browser;\r\n })();\r\n\r\n /**\r\n * Plugin listeners.\r\n * @type {Array.}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * Service provider properties.\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = {};\r\n if (\"serviceProviderProperties\" in coreProperties) {\r\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\r\n } else {\r\n throw new Error(\"serviceProviderProperties property missing.\");\r\n }\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Core listeners.\r\n * @private\r\n * @type {Listeners}\r\n */\r\n static get globalListeners() {\r\n return Core._globalListeners;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set core listeners.\r\n * @param {Listeners} value - The property value.\r\n * @ignore\r\n */\r\n static set globalListeners(value) {\r\n Core._globalListeners = value;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * True when Core.init was called. Otherwise, false.\r\n * @private\r\n * @type {Boolean}\r\n */\r\n static get initialized() {\r\n return Core._initialized;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\r\n * @ignore\r\n */\r\n static set initialized(value) {\r\n Core._initialized = value;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.integrationModel} property.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.environment} property.\r\n * @param {IntegrationEnvironment} integrationEnvironment -\r\n * The {@link IntegrationEnvironment} object.\r\n */\r\n setEnvironment(integrationEnvironment) {\r\n if (\"editor\" in integrationEnvironment) {\r\n this.environment.editor = integrationEnvironment.editor;\r\n }\r\n if (\"mode\" in integrationEnvironment) {\r\n this.environment.mode = integrationEnvironment.mode;\r\n }\r\n if (\"version\" in integrationEnvironment) {\r\n this.environment.version = integrationEnvironment.version;\r\n }\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setHeaders(headers) {\r\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\r\n Configuration.set(\"customHeaders\", headerObject);\r\n }\r\n\r\n /**\r\n * Returns the current {@link ModalDialog} instance.\r\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\r\n */\r\n getModalDialog() {\r\n return this.modalDialog;\r\n }\r\n\r\n /**\r\n * Inits the {@link Core} class, doing the following:\r\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\r\n * - Updates {@link Configuration} class with the previous configuration properties.\r\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\r\n * - Loads language strings.\r\n * - Fires onLoad event.\r\n * @param {Object} serviceParameters - Service parameters.\r\n */\r\n init() {\r\n if (!Core.initialized) {\r\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\r\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\r\n const jsonConfiguration = JSON.parse(jsConfiguration);\r\n Configuration.addConfiguration(jsonConfiguration);\r\n // Adding JavaScript (not backend) configuration variables.\r\n Configuration.addConfiguration(jsProperties);\r\n // Fire 'onLoad' event:\r\n // All integration must listen this event in order to know if the plugin\r\n // has been properly loaded.\r\n StringManager.language = this.language;\r\n this.listeners.fire(\"onLoad\", {});\r\n });\r\n\r\n ServiceProvider.addListener(serviceProviderListener);\r\n ServiceProvider.init(this.serviceProviderProperties);\r\n\r\n Core.initialized = true;\r\n } else {\r\n // Case when there are more than two editor instances.\r\n // After the first editor all the other editors don't need to load any file or service.\r\n this.listeners.fire(\"onLoad\", {});\r\n }\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\r\n * @param {Listener} listener - The listener object.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Adds the global {@link Listener} instance to {@link Core} class.\r\n * @param {Listener} listener - The event listener to be added.\r\n * @static\r\n */\r\n static addGlobalListener(listener) {\r\n Core.globalListeners.add(listener);\r\n }\r\n\r\n beforeUpdateFormula(mathml, wirisProperties) {\r\n /**\r\n * This event is fired before updating the formula.\r\n * @type {Object}\r\n * @property {String} mathml - MathML to be transformed.\r\n * @property {String} editMode - Edit mode.\r\n * @property {Object} wirisProperties - Extra attributes for the formula.\r\n * @property {String} language - Formula language.\r\n */\r\n const beforeUpdateEvent = new Event();\r\n\r\n beforeUpdateEvent.mathml = mathml;\r\n\r\n // Cloning wirisProperties object\r\n // We don't want wirisProperties object modified.\r\n beforeUpdateEvent.wirisProperties = {};\r\n\r\n if (wirisProperties != null) {\r\n Object.keys(wirisProperties).forEach((attr) => {\r\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\r\n });\r\n }\r\n\r\n // Read only.\r\n beforeUpdateEvent.language = this.language;\r\n beforeUpdateEvent.editMode = this.editMode;\r\n\r\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {\r\n mathml: beforeUpdateEvent.mathml,\r\n wirisProperties: beforeUpdateEvent.wirisProperties,\r\n };\r\n }\r\n\r\n /**\r\n * Converts a MathML into it's correspondent image and inserts the image is\r\n * inserted in a HTMLElement target by creating\r\n * a new image or updating an existing one.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\r\n * @param {Window} windowTarget - The window element where the editable content is.\r\n * @param {String} mathml - The MathML.\r\n * @param {Array.} wirisProperties - The extra attributes for the formula.\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n /**\r\n * It is the object with the information of the node or latex to insert.\r\n * @typedef ReturnObject\r\n * @property {Node} [node] - The DOM node to insert.\r\n * @property {String} [latex] - The latex to insert.\r\n */\r\n const returnObject = {};\r\n\r\n if (!mathml) {\r\n this.insertElementOnSelection(null, focusElement, windowTarget);\r\n } else if (this.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n // this.integrationModel.getNonLatexNode is an integration wrapper\r\n // to have special behaviours for nonLatex.\r\n // Not all the integrations have special behaviours for nonLatex.\r\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.latex = returnObject.latex;\r\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\r\n } else {\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n }\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n } else {\r\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\r\n\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n }\r\n\r\n return returnObject;\r\n }\r\n\r\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\r\n /**\r\n * This event is fired after update the formula.\r\n * @type {Event}\r\n * @param {String} editMode - edit mode.\r\n * @param {Object} windowTarget - target window.\r\n * @param {Object} focusElement - target element to be focused after update.\r\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\r\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\r\n */\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.node = node;\r\n afterUpdateEvent.latex = latex;\r\n\r\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {};\r\n }\r\n\r\n /**\r\n * Sets the caret after a given Node and set the focus to the owner document.\r\n * @param {Node} node - The Node element.\r\n */\r\n placeCaretAfterNode(node) {\r\n if (node === null) return;\r\n\r\n this.integrationModel.getSelection();\r\n const nodeDocument = node.ownerDocument;\r\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\r\n const range = nodeDocument.createRange();\r\n range.setStartAfter(node);\r\n range.collapse(true);\r\n const selection = nodeDocument.getSelection();\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n nodeDocument.body.focus();\r\n }\r\n }\r\n\r\n /**\r\n * Replaces a Selection object with an HTMLElement.\r\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\r\n * @param {Window} windowTarget - The window target.\r\n */\r\n insertElementOnSelection(element, focusElement, windowTarget) {\r\n let mathmlOrigin = null;\r\n if (this.editionProperties.isNewElement) {\r\n if (element) {\r\n if (focusElement.type === \"textarea\") {\r\n Util.updateTextArea(focusElement, element.textContent);\r\n } else if (document.selection && document.getSelection === 0) {\r\n let range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n\r\n if (!(\"parentElement\" in range)) {\r\n windowTarget.document.execCommand(\"delete\", false);\r\n range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n }\r\n\r\n if (\"parentElement\" in range) {\r\n const temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\r\n temporalObject.parentNode.replaceChild(element, temporalObject);\r\n } else {\r\n // IE9 fix: parentNode() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML(Util.createObjectCode(element));\r\n }\r\n }\r\n } else {\r\n let range = null;\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n if (this.editionProperties.range) {\r\n ({ range } = this.editionProperties);\r\n this.editionProperties.range = null;\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n range = editorSelection.getRangeAt(0);\r\n }\r\n\r\n // Delete if something was surrounded.\r\n range.deleteContents();\r\n\r\n let node = range.startContainer;\r\n const position = range.startOffset;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = node.splitText(position);\r\n node.parentNode.insertBefore(element, node);\r\n } else if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n node.insertBefore(element, node.childNodes[position]);\r\n }\r\n\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n focusElement.focus();\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (this.editionProperties.range) {\r\n const { range } = this.editionProperties;\r\n this.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n }\r\n }\r\n } else if (this.editionProperties.latexRange) {\r\n if (document.selection && document.getSelection === 0) {\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.latexRange.select();\r\n this.insertElementOnSelection(element, focusElement, windowTarget);\r\n } else {\r\n this.editionProperties.latexRange.deleteContents();\r\n this.editionProperties.latexRange.insertNode(element);\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n let item;\r\n // Wrapper for some integrations that can have special behaviours to show latex.\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n item = this.integrationModel.getSelectedItem(focusElement, false);\r\n } else {\r\n item = Util.getSelectedItemOnTextarea(focusElement);\r\n }\r\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\r\n } else {\r\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\r\n if (element && element.nodeName.toLowerCase() === \"img\") {\r\n // Editor empty, formula has been erased on edit.\r\n // There are editors (e.g: CKEditor) that put attributes in images.\r\n // We don't allow that behaviour in our images.\r\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\r\n // Clone is needed to maintain event references to temporalImage.\r\n Image.clone(element, this.editionProperties.temporalImage);\r\n } else {\r\n this.editionProperties.temporalImage.remove();\r\n }\r\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const mathml = element?.dataset?.mathml;\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove the desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n }\r\n\r\n /**\r\n * Opens a modal dialog containing MathType editor..\r\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\r\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\r\n */\r\n openModalDialog(target, isIframe) {\r\n // Count the time since the editor is open\r\n this.editionProperties.editionStartTime = Date.now();\r\n\r\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\r\n this.editMode = \"images\";\r\n\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n try {\r\n if (isIframe) {\r\n // Is needed focus the target first.\r\n target.contentWindow.focus();\r\n const selection = target.contentWindow.getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n } else {\r\n // Is needed focus the target first.\r\n target.focus();\r\n const selection = getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n }\r\n } catch (e) {\r\n this.editionProperties.range = null;\r\n }\r\n\r\n if (isIframe === undefined) {\r\n isIframe = true;\r\n }\r\n\r\n this.editionProperties.latexRange = null;\r\n\r\n if (target) {\r\n let selectedItem;\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\r\n } else {\r\n selectedItem = Util.getSelectedItem(target, isIframe);\r\n }\r\n\r\n // Check LaTeX if and only if the node is a text node (nodeType==3).\r\n if (selectedItem) {\r\n // Case when image was selected and button pressed.\r\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\r\n this.editionProperties.temporalImage = selectedItem.node;\r\n this.editionProperties.isNewElement = false;\r\n } else if (selectedItem.node.nodeType === 3) {\r\n // If it's a text node means that editor is working with LaTeX.\r\n if (this.integrationModel.getMathmlFromTextNode) {\r\n // If integration has this function it isn't set range due to we don't\r\n // know if it will be put into a textarea as a text or image.\r\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (mathml) {\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n }\r\n } else {\r\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (latexResult) {\r\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n const windowTarget = isIframe ? target.contentWindow : window;\r\n\r\n if (target.tagName.toLowerCase() !== \"textarea\") {\r\n if (document.selection) {\r\n let leftOffset = 0;\r\n let previousNode = latexResult.startNode.previousSibling;\r\n\r\n while (previousNode) {\r\n leftOffset += Util.getNodeLength(previousNode);\r\n previousNode = previousNode.previousSibling;\r\n }\r\n\r\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\r\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\r\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\r\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\r\n } else {\r\n this.editionProperties.latexRange = windowTarget.document.createRange();\r\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\r\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n } else if (target.tagName.toLowerCase() === \"textarea\") {\r\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\r\n this.editMode = \"latex\";\r\n }\r\n }\r\n\r\n // Setting an object with the editor parameters.\r\n // Editor parameters can be customized in several ways:\r\n // 1 - editorAttributes: Contains the default editor attributes,\r\n // usually the metrics in a comma separated string. Always exists.\r\n // 2 - editorParameters: Object containing custom editor parameters.\r\n // These parameters are defined in the backend. So they affects all integration instances.\r\n\r\n // The backend send the default editor attributes in a coma separated\r\n // with the following structure: key1=value1,key2=value2...\r\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\r\n const defaultEditorAttributes = {};\r\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\r\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\r\n const key = tempAttribute[0];\r\n const value = tempAttribute[1];\r\n defaultEditorAttributes[key] = value;\r\n }\r\n // Custom editor parameters.\r\n const editorAttributes = {\r\n language: this.language, // Default language value\r\n };\r\n // Editor parameters in backend, usually configuration.ini.\r\n const serverEditorParameters = Configuration.get(\"editorParameters\");\r\n // Editor parameters through JavaScript configuration.\r\n const clientEditorParameters = this.integrationModel.editorParameters;\r\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\r\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\r\n\r\n // Now, update backwards: if user has set a custom language, pass that back to core properties\r\n this.language = editorAttributes.language;\r\n StringManager.language = this.language;\r\n\r\n editorAttributes.rtl = this.integrationModel.rtl;\r\n\r\n const customHeaders = Configuration.get(\"customHeaders\");\r\n // This is not being used in the code, we are keeping it just in case it's needed.\r\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\r\n editorAttributes.customHeaders =\r\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\r\n\r\n const contentManagerAttributes = {};\r\n contentManagerAttributes.editorAttributes = editorAttributes;\r\n contentManagerAttributes.language = this.language;\r\n contentManagerAttributes.customEditors = this.customEditors;\r\n contentManagerAttributes.environment = this.environment;\r\n\r\n if (this.modalDialog == null) {\r\n this.modalDialog = new ModalDialog(editorAttributes);\r\n this.contentManager = new ContentManager(contentManagerAttributes);\r\n // When an instance of ContentManager is created we need to wait until\r\n // the ContentManager is ready by listening 'onLoad' event.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n });\r\n this.contentManager.addListener(listener);\r\n this.contentManager.init();\r\n this.modalDialog.setContentManager(this.contentManager);\r\n this.contentManager.setModalDialogInstance(this.modalDialog);\r\n } else {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n }\r\n this.contentManager.setIntegrationModel(this.integrationModel);\r\n this.modalDialog.open();\r\n }\r\n\r\n /**\r\n * Returns the {@link CustomEditors} instance.\r\n * @return {CustomEditors} The current {@link CustomEditors} instance.\r\n */\r\n getCustomEditors() {\r\n return this.customEditors;\r\n }\r\n}\r\n\r\n/**\r\n * Core static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nCore._globalListeners = new Listeners();\r\n\r\n/**\r\n * Resources state. Says if they were loaded or not.\r\n * @type {Boolean}\r\n * @private\r\n */\r\nCore._initialized = false;\r\n","// eslint-disable-next-line no-unused-vars, import/named\r\nimport Core from \"./core.src\";\r\nimport Image from \"./image\";\r\nimport Listeners from \"./listeners\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Telemeter from \"./telemeter\";\r\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\r\n\r\n/**\r\n * @typedef {Object} IntegrationModelProperties\r\n * @property {string} configurationService - Configuration service path.\r\n * This parameter is needed to determine all services paths.\r\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\r\n * @property {string} integrationModelProperties.scriptName - Integration script name.\r\n * Usually the name of the integration script.\r\n * @property {Object} integrationModelProperties.environment - integration environment properties.\r\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\r\n * callback method arguments.\r\n * @property {string} [integrationModelProperties.version] - integration version number.\r\n * @property {Object} [integrationModelProperties.editorObject] - object containing\r\n * the integration editor instance.\r\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\r\n * false otherwise.\r\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\r\n * - The service parameters.\r\n * @property {Object} [integrationModelProperties.integrationParameters]\r\n * - Overwritten integration parameters.\r\n */\r\n\r\nexport default class IntegrationModel {\r\n /**\r\n * @classdesc\r\n * This class represents an integration model, allowing the integration script to\r\n * communicate with Core class. Each integration must extend this class.\r\n * @constructs\r\n * @param {IntegrationModelProperties} integrationModelProperties\r\n */\r\n constructor(integrationModelProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. English by default.\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Service parameters\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\r\n\r\n /**\r\n * Configuration service path. The integration service is needed by Core class to\r\n * load all the backend configuration into the frontend and also to create the paths\r\n * of all services (all services lives in the same route). Mandatory property.\r\n */\r\n this.configurationService = \"\";\r\n if (\"configurationService\" in integrationModelProperties) {\r\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\r\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\r\n integrationModelProperties.configurationService,\r\n ]);\r\n }\r\n\r\n /**\r\n * Plugin version. Needed to stats and caching.\r\n * @type {string}\r\n */\r\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\r\n\r\n /**\r\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\r\n * Mandatory property.\r\n */\r\n this.target = null;\r\n if (\"target\" in integrationModelProperties) {\r\n this.target = integrationModelProperties.target;\r\n } else {\r\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\r\n }\r\n\r\n /**\r\n * Integration script name. Needed to know the plugin path.\r\n */\r\n if (\"scriptName\" in integrationModelProperties) {\r\n this.scriptName = integrationModelProperties.scriptName;\r\n }\r\n\r\n /**\r\n * Object containing the arguments needed by the callback function.\r\n */\r\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\r\n\r\n /**\r\n * Contains information about the integration environment:\r\n * like the name of the editor, the version, etc.\r\n */\r\n this.environment = integrationModelProperties.environment ?? {};\r\n\r\n /**\r\n * Indicates if the DOM target is - or not - and iframe.\r\n */\r\n this.isIframe = false;\r\n if (this.target != null) {\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Instance of the integration editor object. Usually the entry point to access the API\r\n * of a HTML editor.\r\n */\r\n this.editorObject = integrationModelProperties.editorObject ?? null;\r\n\r\n /**\r\n * Specifies if the direction of the text is RTL. false by default.\r\n */\r\n this.rtl = integrationModelProperties.rtl ?? false;\r\n\r\n /**\r\n * Specifies if the integration model exposes the locale strings. false by default.\r\n */\r\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\r\n\r\n /**\r\n * Specify if editor will open in hand mode only\r\n */\r\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\r\n\r\n /**\r\n * Indicates if an image is selected. Needed to resize the image to the original size in case\r\n * the image is resized.\r\n * @type {boolean}\r\n */\r\n this.temporalImageResizing = false;\r\n\r\n /**\r\n * The Core class instance associated to the integration model.\r\n * @type {Core}\r\n */\r\n this.core = null;\r\n\r\n /**\r\n * Integration model listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n // Parameters overwrite.\r\n if (\"integrationParameters\" in integrationModelProperties) {\r\n IntegrationModel.integrationParameters.forEach((parameter) => {\r\n if (parameter in integrationModelProperties.integrationParameters) {\r\n // Don't add empty parameters.\r\n const value = integrationModelProperties.integrationParameters[parameter];\r\n if (Object.keys(value).length !== 0) {\r\n this[parameter] = value;\r\n }\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Init function. Usually called from the integration side once the core.js file is loaded.\r\n */\r\n init() {\r\n // Check if language is an object and select the property necessary\r\n this.language = this.getLanguage();\r\n\r\n // We need to wait until Core class is loaded ('onLoad' event) before\r\n // call the callback method.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.callbackFunction(this.callbackMethodArguments);\r\n });\r\n\r\n // Backwards compatibility.\r\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\r\n const uri = this.serviceProviderProperties.URI;\r\n const server = ServiceProvider.getServerLanguageFromService(uri);\r\n this.serviceProviderProperties.server = server;\r\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\r\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\r\n this.serviceProviderProperties.URI = subsTring;\r\n }\r\n\r\n let serviceParametersURI = this.serviceProviderProperties.URI;\r\n serviceParametersURI =\r\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\r\n ? serviceParametersURI\r\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\r\n\r\n this.serviceProviderProperties.URI = serviceParametersURI;\r\n\r\n const coreProperties = {};\r\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\r\n\r\n this.setCore(new Core(coreProperties));\r\n this.core.addListener(listener);\r\n this.core.language = this.language;\r\n\r\n // Initializing Core class.\r\n this.core.init();\r\n // TODO: Move to Core constructor.\r\n this.core.setEnvironment(this.environment);\r\n\r\n // No internet connection modal.\r\n let attributes = {};\r\n attributes.class = attributes.id = \"wrs_modal_offline\";\r\n this.offlineModal = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_content_offline\";\r\n this.offlineModalContent = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_close\";\r\n this.offlineModalClose = Util.createElement(\"span\", attributes);\r\n this.offlineModalClose.innerHTML = \"×\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_warn\";\r\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\r\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\r\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_container\";\r\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_warn\";\r\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text\";\r\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\r\n\r\n // Append offline modal elements\r\n this.offlineModalContent.appendChild(this.offlineModalClose);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\r\n this.offlineModalContent.appendChild(this.offlineModalMessage);\r\n this.offlineModalContent.appendChild(this.offlineModalWarn);\r\n this.offlineModal.appendChild(this.offlineModalContent);\r\n document.body.appendChild(this.offlineModal);\r\n\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n this.offlineModalClose.addEventListener(\"click\", () => {\r\n modal.style.display = \"none\";\r\n });\r\n\r\n // Store editor name for telemetry purposes.\r\n let editorName = this.environment.editor;\r\n editorName = editorName.slice(0, -1); // Remove version number from editors\r\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\r\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\r\n let solutionTelemeter = editorName;\r\n\r\n // If moodle, add information to hosts and solution.\r\n const isMoodle = !!(typeof M === \"object\" && M !== null);\r\n let lms;\r\n\r\n if (isMoodle) {\r\n solutionTelemeter = \"Moodle\";\r\n lms = {\r\n nam: \"moodle\",\r\n fam: \"lms\",\r\n ver: this.environment.moodleVersion,\r\n category: this.environment.moodleCourseCategory,\r\n course: this.environment.moodleCourseName,\r\n };\r\n if (!editorName.includes(\"TinyMCE\")) {\r\n editorName = \"Atto\";\r\n }\r\n }\r\n\r\n // Get the OS and its version.\r\n const OSData = this.getOS();\r\n\r\n // Get the broswer and its version.\r\n const broswerData = this.getBrowser();\r\n\r\n // Create list of hosts to send to telemetry.\r\n let hosts = [\r\n {\r\n nam: broswerData.detectedBrowser,\r\n fam: \"browser\",\r\n ver: broswerData.versionBrowser,\r\n },\r\n {\r\n nam: editorName.toLowerCase(),\r\n fam: \"html-editor\",\r\n ver: this.environment.editorVersion,\r\n },\r\n {\r\n nam: OSData.detectedOS,\r\n fam: \"os\",\r\n ver: OSData.versionOS,\r\n },\r\n {\r\n nam: window.location.hostname,\r\n fam: \"domain\",\r\n },\r\n lms,\r\n ];\r\n\r\n // Filter hosts to remove empty objects and empty keys.\r\n hosts = hosts.filter((element) => {\r\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\r\n return element !== undefined;\r\n });\r\n\r\n // Initialize telemeter\r\n Telemeter.init({\r\n solution: {\r\n name: `MathType for ${solutionTelemeter}`,\r\n version: this.version,\r\n },\r\n hosts,\r\n config: {\r\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\r\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\r\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\r\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\r\n },\r\n url: undefined,\r\n });\r\n }\r\n\r\n /**\r\n * Returns the Browser name and its version.\r\n * @return {array} - detectedBrowser = Operating System name.\r\n * versionBrowser = Operating System version.\r\n */\r\n getBrowser() {\r\n // default value for OS just in case nothing is detected\r\n let detectedBrowser = \"unknown\";\r\n let versionBrowser = \"unknown\";\r\n\r\n const userAgent = window.navigator.userAgent;\r\n\r\n if (/Brave/.test(userAgent)) {\r\n detectedBrowser = \"brave\";\r\n if (userAgent.indexOf(\"Brave/\")) {\r\n const start = userAgent.indexOf(\"Brave\") + 6;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\r\n detectedBrowser = \"edge_chromium\";\r\n const start = userAgent.indexOf(\"Edg/\") + 4;\r\n versionBrowser = userAgent\r\n .substring(start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Edg/.test(userAgent)) {\r\n detectedBrowser = \"edge\";\r\n let start = userAgent.indexOf(\"Edg\") + 3;\r\n start += userAgent.substring(start).indexOf(\"/\");\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\r\n detectedBrowser = \"firefox\";\r\n let start = userAgent.indexOf(\"Firefox\");\r\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/OPR/.test(userAgent)) {\r\n detectedBrowser = \"opera\";\r\n const start = userAgent.indexOf(\"OPR/\") + 4;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\r\n detectedBrowser = \"chrome\";\r\n let start = userAgent.indexOf(\"Chrome\");\r\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/Safari/.test(userAgent)) {\r\n detectedBrowser = \"safari\";\r\n let start = userAgent.indexOf(\"Version/\");\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n }\r\n\r\n return { detectedBrowser, versionBrowser };\r\n }\r\n\r\n /**\r\n * Returns the operating system and its version.\r\n * @return {array} - detectedOS = Operating System name.\r\n * versionOS = Operating System version.\r\n */\r\n getOS() {\r\n // default value for OS just in case nothing is detected\r\n let detectedOS = \"unknown\";\r\n let versionOS = \"unknown\";\r\n\r\n // Retrieve properties to easily detect the OS\r\n const userAgent = window.navigator.userAgent;\r\n const platform = window.navigator.platform;\r\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\r\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\r\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\r\n\r\n // Find OS and their respective versions\r\n if (macosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"macos\";\r\n if (userAgent.indexOf(\"OS X\") !== -1) {\r\n const start = userAgent.indexOf(\"OS X\") + 5;\r\n const end = userAgent.substring(start).indexOf(\" \");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (iosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"ios\";\r\n if (userAgent.indexOf(\"OS \") !== -1) {\r\n const start = userAgent.indexOf(\"OS \") + 3;\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"windows\";\r\n const start = userAgent.indexOf(\"Windows\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Android/.test(userAgent)) {\r\n detectedOS = \"android\";\r\n const start = userAgent.indexOf(\"Android\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/CrOS/.test(userAgent)) {\r\n detectedOS = \"chromeos\";\r\n let start = userAgent.indexOf(\"CrOS \") + 5;\r\n start += userAgent.substring(start).indexOf(\" \");\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\r\n detectedOS = \"linux\";\r\n }\r\n\r\n return { detectedOS, versionOS };\r\n }\r\n\r\n /**\r\n * Returns the absolute path of the integration script.\r\n * @return {string} - Absolute path for the integration script.\r\n */\r\n getPath() {\r\n if (typeof this.scriptName === \"undefined\") {\r\n throw new Error(\"scriptName property needed for getPath.\");\r\n }\r\n const col = document.getElementsByTagName(\"script\");\r\n let path = \"\";\r\n for (let i = 0; i < col.length; i += 1) {\r\n const j = col[i].src.lastIndexOf(this.scriptName);\r\n if (j >= 0) {\r\n path = col[i].src.substr(0, j - 1);\r\n }\r\n }\r\n return path;\r\n }\r\n\r\n /**\r\n * Returns integration model plugin version\r\n * @param {string} - Plugin version\r\n */\r\n getVersion() {\r\n return this.version;\r\n }\r\n\r\n /**\r\n * Sets the language property.\r\n * @param {string} language - language code.\r\n */\r\n setLanguage(language) {\r\n this.language = language;\r\n }\r\n\r\n /**\r\n * Sets a Core instance.\r\n * @param {Core} core - instance of Core class.\r\n */\r\n setCore(core) {\r\n this.core = core;\r\n core.setIntegrationModel(this);\r\n }\r\n\r\n /**\r\n * Returns the Core instance.\r\n * @returns {Core} instance of Core class.\r\n */\r\n getCore() {\r\n return this.core;\r\n }\r\n\r\n /**\r\n * Sets the object target and updates the iframe property.\r\n * @param {HTMLElement} target - target object.\r\n */\r\n setTarget(target) {\r\n this.target = target;\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Sets the editor object.\r\n * @param {Object} editorObject - The editor object.\r\n */\r\n setEditorObject(editorObject) {\r\n this.editorObject = editorObject;\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openNewFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.dbclick = false;\r\n this.core.editionProperties.isNewElement = true;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openExistingFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.isNewElement = false;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Wrapper to Core.updateFormula method.\r\n * Transform a MathML into a image formula.\r\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\r\n * or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n */\r\n updateFormula(mathml) {\r\n if (this.editorParameters) {\r\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\r\n mathml,\r\n \"application/vnd.wiris.mtweb-params+json\",\r\n JSON.stringify(this.editorParameters),\r\n );\r\n }\r\n let focusElement;\r\n let windowTarget;\r\n const wirisProperties = null;\r\n\r\n if (this.isIframe) {\r\n focusElement = this.target.contentWindow;\r\n windowTarget = this.target.contentWindow;\r\n } else {\r\n focusElement = this.target;\r\n windowTarget = window;\r\n }\r\n\r\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\r\n }\r\n\r\n /**\r\n * Wrapper to Core.insertFormula method.\r\n * Inserts the formula in the specified target, creating\r\n * a new image (new formula) or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\r\n\r\n // Delete temporal image when inserted\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return obj;\r\n }\r\n\r\n /**\r\n * Returns the target selection.\r\n * @returns {Selection} target selection.\r\n */\r\n getSelection() {\r\n if (this.isIframe) {\r\n this.target.contentWindow.focus();\r\n return this.target.contentWindow.getSelection();\r\n }\r\n this.target.focus();\r\n return window.getSelection();\r\n }\r\n\r\n /**\r\n * Add events to formulas in the DOM target. The events added are the following:\r\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\r\n * to edit them.\r\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\r\n * in case the the formula is resized.\r\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\r\n * in case the formula is resized.\r\n */\r\n addEvents() {\r\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\r\n Util.addElementEvents(\r\n eventTarget,\r\n (element, event) => {\r\n this.doubleClickHandler(element, event);\r\n // Avoid creating the double click listener more than once for each element.\r\n // This also allows CKEditor4 to add their own double click listener.\r\n event.preventDefault();\r\n },\r\n (element, event) => {\r\n this.mousedownHandler(element, event);\r\n },\r\n (element, event) => {\r\n this.mouseupHandler(element, event);\r\n },\r\n );\r\n }\r\n\r\n /**\r\n * Remove events to formulas in the DOM target.\r\n */\r\n removeEvents() {\r\n const eventTarget =\r\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\r\n\r\n if (!eventTarget) {\r\n return;\r\n }\r\n\r\n Util.removeElementEvents(eventTarget);\r\n }\r\n\r\n /**\r\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\r\n */\r\n destroy() {\r\n this.removeEvents();\r\n // Destroy modal dialog if exists.\r\n if (this.core.modalDialog) {\r\n this.core.modalDialog.destroy();\r\n }\r\n\r\n // Remove offline modal dialog if exists.\r\n if (this.offlineModal) {\r\n this.offlineModal.remove();\r\n }\r\n\r\n this.editorObject = null;\r\n }\r\n\r\n /**\r\n * Handles a Double-click on the target element. Opens an editor\r\n * to re-edit the double-clicked formula.\r\n * @param {HTMLElement} element - DOM object target.\r\n */\r\n doubleClickHandler(element) {\r\n this.core.editionProperties.dbclick = true;\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (element.hasAttribute(customEditorAttributeName)) {\r\n const customEditor = element.getAttribute(customEditorAttributeName);\r\n this.core.getCustomEditors().enable(customEditor);\r\n }\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.core.editionProperties.temporalImage = element;\r\n this.core.editionProperties.isNewElement = true;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse up event on the target element. Restores the image size to avoid\r\n * resizing formulas.\r\n */\r\n mouseupHandler() {\r\n if (this.temporalImageResizing) {\r\n setTimeout(() => {\r\n Image.fixAfterResize(this.temporalImageResizing);\r\n }, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse down event on the target element. Saves the formula size to avoid\r\n * resizing formulas.\r\n * @param {HTMLElement} element - target element.\r\n */\r\n mousedownHandler(element) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.temporalImageResizing = element;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the integration language. By default the browser agent. This method\r\n * should be overwritten to obtain the integration language, for example using the\r\n * plugin API of an HTML editor.\r\n * @returns {string} integration language.\r\n */\r\n getLanguage() {\r\n return this.getBrowserLanguage();\r\n }\r\n\r\n /**\r\n * Returns the browser language.\r\n * @returns {string} the browser language.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getBrowserLanguage() {\r\n let language = \"en\";\r\n if (navigator.userLanguage) {\r\n language = navigator.userLanguage.substring(0, 2);\r\n } else if (navigator.language) {\r\n language = navigator.language.substring(0, 2);\r\n } else {\r\n language = \"en\";\r\n }\r\n return language;\r\n }\r\n\r\n /**\r\n * This function is called once the {@link Core} is loaded. IntegrationModel class\r\n * will fire this method when {@link Core} 'onLoad' event is fired.\r\n * This method should content all the logic to init\r\n * the integration.\r\n */\r\n callbackFunction() {\r\n // It's needed to wait until the integration target is ready. The event is fired\r\n // from the integration side.\r\n const listener = Listeners.newListener(\"onTargetReady\", () => {\r\n this.addEvents(this.target);\r\n });\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n notifyWindowClosed() {\r\n // Nothing.\r\n }\r\n\r\n /**\r\n * Wrapper.\r\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\r\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\r\n * @param {string} textNode - text node to extract the MathML.\r\n * @param {int} caretPosition - caret position inside the text node.\r\n * @returns {string} MathML inside the text node.\r\n */\r\n\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getMathmlFromTextNode(textNode, caretPosition) {}\r\n\r\n /**\r\n * Wrapper\r\n * It fills wrs event object of nonLatex with the desired data.\r\n * @param {Object} event - event object.\r\n * @param {Object} window dom window object.\r\n * @param {string} mathml valid mathml.\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n fillNonLatexNode(event, window, mathml) {}\r\n\r\n /**\r\n Wrapper.\r\n * Returns selected item from the target.\r\n * @param {HTMLElement} target - target element\r\n * @param {boolean} iframe\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getSelectedItem(target, isIframe) {}\r\n\r\n // Set temporal image to null and make focus come back.\r\n static setActionsOnCancelButtons() {\r\n // Make focus come back on the previous place it was when click cancel button\r\n const currentInstance = WirisPlugin.currentInstance;\r\n const editorSelection = currentInstance.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (currentInstance.core.editionProperties.range) {\r\n const { range } = currentInstance.core.editionProperties;\r\n currentInstance.core.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n if (range.startOffset !== range.endOffset) {\r\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\r\n }\r\n }\r\n\r\n // eslint-disable-next-line no-undef\r\n if (WirisPlugin.currentInstance) {\r\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\r\n }\r\n }\r\n}\r\n\r\n// To know if the integration that extends this class implements\r\n// wrapper methods, they are set as undefined.\r\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\r\nIntegrationModel.prototype.fillNonLatexNode = undefined;\r\nIntegrationModel.prototype.getSelectedItem = undefined;\r\n\r\n/**\r\n * An object containing a list with the overwritable class constructor properties.\r\n * @type {Object}\r\n */\r\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\r\n","/* eslint-disable */\r\nvar md5;\r\nexport default md5;\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n delete Array.prototype.__class__;\r\n})();\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n})();\r\ndelete Array.prototype.__class__;\r\n// @codingStandardsIgnoreEnd\r\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\r\n\r\n/**\r\n * This class represents the MathType integration for CKEditor5.\r\n * @extends {IntegrationModel}\r\n */\r\nexport default class CKEditor5Integration extends IntegrationModel {\r\n constructor(ckeditorIntegrationModelProperties) {\r\n const editor = ckeditorIntegrationModelProperties.editorObject;\r\n\r\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\r\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\r\n }\r\n /**\r\n * CKEditor5 Integration.\r\n *\r\n * @param {integrationModelProperties} integrationModelAttributes\r\n */\r\n super(ckeditorIntegrationModelProperties);\r\n\r\n /**\r\n * Folder name used for the integration inside CKEditor plugins folder.\r\n */\r\n this.integrationFolderName = \"ckeditor_wiris\";\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @returns {string} - The CKEditor instance language.\r\n * @override\r\n */\r\n getLanguage() {\r\n // Returns the CKEDitor instance language taking into account that the language can be an object.\r\n // Try to get editorParameters.language, fail silently otherwise\r\n try {\r\n return this.editorParameters.language;\r\n } catch (e) {}\r\n const languageObject = this.editorObject.config.get(\"language\");\r\n if (languageObject != null) {\r\n if (typeof languageObject === \"object\") {\r\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\r\n return languageObject.ui;\r\n }\r\n return this.editorObject.locale.uiLanguage;\r\n }\r\n return languageObject;\r\n }\r\n return super.getLanguage();\r\n }\r\n\r\n /**\r\n * Adds callbacks to the following CKEditor listeners:\r\n * - 'focus' - updates the current instance.\r\n * - 'contentDom' - adds 'doubleclick' callback.\r\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\r\n * - 'setData' - parses the data converting MathML into images.\r\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\r\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\r\n * - 'mode' - recalculates the active element.\r\n */\r\n addEditorListeners() {\r\n const editor = this.editorObject;\r\n\r\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\r\n this.checkElement();\r\n }\r\n }\r\n\r\n /**\r\n * Checks the current container and assign events in case that it doesn't have them.\r\n * CKEditor replaces several times the element element during its execution,\r\n * so we must assign the events again to editor element.\r\n */\r\n checkElement() {\r\n const editor = this.editorObject;\r\n const newElement = editor.sourceElement;\r\n\r\n // If the element wasn't treated, add the events.\r\n if (!newElement.wirisActive) {\r\n this.setTarget(newElement);\r\n this.addEvents();\r\n // Set the element as treated\r\n newElement.wirisActive = true;\r\n }\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @param {HTMLElement} element - HTMLElement target.\r\n * @param {MouseEvent} event - event which trigger the handler.\r\n */\r\n doubleClickHandler(element, event) {\r\n this.core.editionProperties.dbclick = true;\r\n if (this.editorObject.isReadOnly === false) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\r\n // doubleclick event ends here.\r\n if (typeof event.stopPropagation !== \"undefined\") {\r\n // old I.E compatibility.\r\n event.stopPropagation();\r\n } else {\r\n event.returnValue = false;\r\n }\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\r\n if (customEditorAttr) {\r\n this.core.getCustomEditors().enable(customEditorAttr);\r\n }\r\n this.core.editionProperties.temporalImage = element;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n static getCorePath() {\r\n return null; // TODO\r\n }\r\n\r\n /** @inheritdoc */\r\n callbackFunction() {\r\n super.callbackFunction();\r\n this.addEditorListeners();\r\n }\r\n\r\n openNewFormulaEditor() {\r\n // Store the editor selection as it will be lost upon opening the modal\r\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\r\n\r\n // Focus on the selected editor when multiple editor instances are present\r\n WirisPlugin.currentInstance = this;\r\n\r\n return super.openNewFormulaEditor();\r\n }\r\n\r\n /**\r\n * Replaces old formula with new MathML or inserts it in caret position if new\r\n * @param {String} mathml MathML to update old one or insert\r\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\r\n */\r\n insertMathml(mathml) {\r\n // This returns the value returned by the callback function (writer => {...})\r\n return this.editorObject.model.change((writer) => {\r\n const core = this.getCore();\r\n const selection = this.editorObject.model.document.selection;\r\n\r\n const modelElementNew = writer.createElement(\"mathml\", {\r\n formula: mathml,\r\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\r\n });\r\n\r\n // Obtain the DOM object corresponding to the formula\r\n if (core.editionProperties.isNewElement) {\r\n // Don't bother inserting anything at all if the MathML is empty.\r\n if (!mathml) return;\r\n\r\n const viewSelection =\r\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\r\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\r\n\r\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\r\n\r\n // Remove selection\r\n if (!viewSelection.isCollapsed) {\r\n for (const range of viewSelection.getRanges()) {\r\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\r\n }\r\n }\r\n\r\n // Set carret after the formula\r\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\r\n writer.setSelection(position);\r\n } else {\r\n const img = core.editionProperties.temporalImage;\r\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\r\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\r\n\r\n // Insert the new and remove the old one\r\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\r\n\r\n // If the given MathML is empty, don't insert a new formula.\r\n if (mathml) {\r\n this.editorObject.model.insertObject(modelElementNew, position);\r\n }\r\n writer.remove(modelElementOld);\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return modelElementNew;\r\n });\r\n }\r\n\r\n /**\r\n * Finds the text node corresponding to given DOM text element.\r\n * @param {element} viewElement Element to find corresponding text node of.\r\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\r\n */\r\n findText(viewElement) {\r\n // eslint-disable-line consistent-return\r\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\r\n let pivot = viewElement;\r\n let element;\r\n while (!element) {\r\n element = this.editorObject.editing.mapper.toModelElement(\r\n this.editorObject.editing.view.domConverter.domToView(pivot),\r\n );\r\n pivot = pivot.parentElement;\r\n }\r\n\r\n // Navigate through all the subtree under `pivot` in order to find the correct text node\r\n const range = this.editorObject.model.createRangeIn(element);\r\n const descendants = Array.from(range.getItems());\r\n for (const node of descendants) {\r\n let viewElementData = viewElement.data;\r\n if (viewElement.nodeType === 3) {\r\n // Remove invisible white spaces\r\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\r\n }\r\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\r\n return node.textNode;\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n // eslint-disable-line no-unused-vars\r\n const returnObject = {};\r\n\r\n let mathmlOrigin;\r\n if (!mathml) {\r\n this.insertMathml(\"\");\r\n } else if (this.core.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n\r\n this.editorObject.model.change((writer) => {\r\n const { latexRange } = this.core.editionProperties;\r\n\r\n const startNode = this.findText(latexRange.startContainer);\r\n const endNode = this.findText(latexRange.endContainer);\r\n\r\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\r\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\r\n\r\n let range = writer.createRange(startPosition, endPosition);\r\n\r\n // When Latex is next to image/formula.\r\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\r\n // Get the position of the latex to be replaced.\r\n const latexEdited = `$$${Latex.getLatexFromMathML(\r\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\r\n )}$$`;\r\n let data = latexRange.startContainer.data;\r\n\r\n // Remove invisible characters.\r\n data = data.replaceAll(String.fromCharCode(8288), \"\");\r\n\r\n // Get to the start of the latex we are editing.\r\n const offset = data.indexOf(latexEdited);\r\n const dataOffset = data.substring(offset);\r\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\r\n const substring = dataOffset.substr(0, second$);\r\n data = data.replace(substring, \"\");\r\n\r\n if (!data) {\r\n startPosition = writer.createPositionBefore(startNode);\r\n range = startNode;\r\n } else {\r\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\r\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\r\n range = writer.createRange(startPosition, endPosition);\r\n }\r\n }\r\n\r\n writer.remove(range);\r\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\r\n });\r\n } else {\r\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\r\n try {\r\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\r\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\r\n windowTarget.document,\r\n );\r\n } catch (e) {\r\n const x = e.toString();\r\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\r\n this.core.modalDialog.cancelAction();\r\n }\r\n }\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.core.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n\r\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\r\n We probably should add it here as well, but we should look further into how */\r\n // this.editorObject.fire('change');\r\n\r\n // Remove temporal image of inserted formula\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return returnObject;\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n notifyWindowClosed() {\r\n this.editorObject.editing.view.focus();\r\n }\r\n}\r\n","/* eslint-disable max-classes-per-file */\r\nimport { Command } from \"ckeditor5/src/core.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\n/**\r\n * Command for opening the MathType editor\r\n */\r\nexport class MathTypeCommand extends Command {\r\n execute(options = {}) {\r\n // Check we get a valid integration\r\n // eslint-disable-next-line no-prototype-builtins\r\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\r\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\r\n }\r\n\r\n // Save the integration instance as a property of the command.\r\n this.integration = options.integration;\r\n\r\n // Set custom editor or disable it\r\n this.setEditor();\r\n\r\n // Open the editor\r\n this.openEditor();\r\n }\r\n\r\n /**\r\n * Sets the appropriate custom editor, if any, or disables them.\r\n */\r\n setEditor() {\r\n // It's possible that a custom editor was last used.\r\n // We need to disable it to avoid wrong behaviors.\r\n this.integration.core.getCustomEditors().disable();\r\n }\r\n\r\n /**\r\n * Checks whether we are editing an existing formula or a new one and opens the editor.\r\n */\r\n openEditor() {\r\n this.integration.core.editionProperties.dbclick = false;\r\n const image = this._getSelectedImage();\r\n if (\r\n typeof image !== \"undefined\" &&\r\n image !== null &&\r\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\r\n ) {\r\n this.integration.core.editionProperties.temporalImage = image;\r\n this.integration.openExistingFormulaEditor();\r\n } else {\r\n this.integration.openNewFormulaEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Gets the currently selected formula image\r\n * @returns {Element} selected image, if any, undefined otherwise\r\n */\r\n _getSelectedImage() {\r\n const { selection } = this.editor.editing.view.document;\r\n\r\n // If we can not extract the formula, fall back to default behavior.\r\n if (selection.isCollapsed || selection.rangeCount !== 1) {\r\n return;\r\n }\r\n\r\n // Look for the wrapping the formula and then for the inside\r\n\r\n const range = selection.getFirstRange();\r\n\r\n let image;\r\n\r\n for (const span of range) {\r\n if (span.item.name !== \"span\") {\r\n return;\r\n }\r\n image = span.item.getChild(0);\r\n break;\r\n }\r\n\r\n if (!image) {\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return this.editor.editing.view.domConverter.mapViewToDom(image);\r\n }\r\n}\r\n\r\n/**\r\n * Command for opening the ChemType editor\r\n */\r\nexport class ChemTypeCommand extends MathTypeCommand {\r\n setEditor() {\r\n this.integration.core.getCustomEditors().enable(\"chemistry\");\r\n }\r\n}\r\n","// CKEditor imports\r\nimport { Plugin } from \"ckeditor5/src/core.js\";\r\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\r\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\r\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\r\n\r\n// MathType API imports\r\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\r\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\r\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\r\n\r\n// Local imports\r\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\r\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\r\n\r\nimport packageInfo from \"../package.json\";\r\n\r\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\r\n\r\nexport default class MathType extends Plugin {\r\n static get requires() {\r\n return [Widget];\r\n }\r\n\r\n static get pluginName() {\r\n return \"MathType\";\r\n }\r\n\r\n init() {\r\n // Create the MathType API Integration object\r\n const integration = this._addIntegration();\r\n currentInstance = integration;\r\n\r\n // Add the MathType and ChemType commands to the editor\r\n this._addCommands();\r\n\r\n // Add the buttons for MathType and ChemType\r\n this._addViews(integration);\r\n\r\n // Registers the element in the schema\r\n this._addSchema();\r\n\r\n // Add the downcast and upcast converters\r\n this._addConverters(integration);\r\n\r\n // Expose the WirisPlugin variable to the window\r\n this._exposeWiris();\r\n }\r\n\r\n /**\r\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\r\n */\r\n destroy() {\r\n // eslint-disable-line class-methods-use-this\r\n currentInstance?.destroy();\r\n }\r\n\r\n /**\r\n * Create the MathType API Integration object\r\n * @returns {CKEditor5Integration} the integration object\r\n */\r\n _addIntegration() {\r\n const { editor } = this;\r\n\r\n /**\r\n * Integration model constructor attributes.\r\n * @type {integrationModelProperties}\r\n */\r\n const integrationProperties = {};\r\n integrationProperties.environment = {};\r\n integrationProperties.environment.editor = \"CKEditor5\";\r\n integrationProperties.environment.editorVersion = \"5.x\";\r\n integrationProperties.version = packageInfo.version;\r\n integrationProperties.editorObject = editor;\r\n integrationProperties.serviceProviderProperties = {};\r\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\r\n integrationProperties.serviceProviderProperties.server = \"java\";\r\n integrationProperties.target = editor.sourceElement;\r\n integrationProperties.scriptName = \"bundle.js\";\r\n integrationProperties.managesLanguage = true;\r\n // etc\r\n\r\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\r\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\r\n let integration;\r\n if (integrationProperties.target) {\r\n // Instance of the integration associated to this editor instance\r\n integration = new CKEditor5Integration(integrationProperties);\r\n integration.init();\r\n integration.listeners.fire(\"onTargetReady\", {});\r\n\r\n integration.checkElement();\r\n\r\n this.listenTo(\r\n editor.editing.view.document,\r\n \"click\",\r\n (evt, data) => {\r\n // Is Double-click\r\n if (data.domEvent.detail === 2) {\r\n integration.doubleClickHandler(data.domTarget, data.domEvent);\r\n evt.stop();\r\n }\r\n },\r\n { priority: \"highest\" },\r\n );\r\n }\r\n\r\n return integration;\r\n }\r\n\r\n /**\r\n * Add the MathType and ChemType commands to the editor\r\n */\r\n _addCommands() {\r\n const { editor } = this;\r\n\r\n // Add command to open the formula editor\r\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\r\n\r\n // Add command to open the chemistry formula editor\r\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\r\n }\r\n\r\n /**\r\n * Add the buttons for MathType and ChemType\r\n * @param {CKEditor5Integration} integration the integration object\r\n */\r\n _addViews(integration) {\r\n const { editor } = this;\r\n\r\n // Check if MathType editor is enabled\r\n if (Configuration.get(\"editorEnabled\")) {\r\n // Add button for the formula editor\r\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\r\n view.set({\r\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\r\n icon: mathIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"MathType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Check if ChemType editor is enabled\r\n if (Configuration.get(\"chemEnabled\")) {\r\n // Add button for the chemistry formula editor\r\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\r\n\r\n view.set({\r\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\r\n icon: chemIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"ChemType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Observer for the Double-click event\r\n editor.editing.view.addObserver(ClickObserver);\r\n }\r\n\r\n /**\r\n * Registers the element in the schema\r\n */\r\n _addSchema() {\r\n const { schema } = this.editor.model;\r\n\r\n schema.register(\"mathml\", {\r\n inheritAllFrom: \"$inlineObject\",\r\n allowAttributes: [\"formula\", \"htmlContent\"],\r\n });\r\n }\r\n\r\n /**\r\n * Add the downcast and upcast converters\r\n */\r\n _addConverters(integration) {\r\n const { editor } = this;\r\n\r\n // Editing view -> Model\r\n editor.conversion.for(\"upcast\").elementToElement({\r\n view: {\r\n name: \"span\",\r\n classes: \"ck-math-widget\",\r\n },\r\n model: (viewElement, { writer: modelWriter }) => {\r\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\r\n return modelWriter.createElement(\"mathml\", {\r\n formula,\r\n });\r\n },\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // When element was already consumed then skip it.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // If we encounter any with a LaTeX annotation inside,\r\n // convert it into a \"$$...$$\" string.\r\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\r\n\r\n // Get the formula of the (which is all its children).\r\n const processor = new XmlDataProcessor(editor.editing.view.document);\r\n\r\n // Only god knows why the following line makes viewItem lose all of its children,\r\n // so we obtain isLatex before doing this because we need viewItem's children for that.\r\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\r\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\r\n\r\n // and obtain the attributes of too!\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n\r\n // We process the document fragment\r\n let formula = processor.toData(viewDocumentFragment) || \"\";\r\n\r\n // And obtain the complete formula\r\n formula = Util.htmlSanitize(`${formula}`);\r\n\r\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\r\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\r\n\r\n /* Model node that contains what's going to actually be inserted. This can be either:\r\n - A element with a formula attribute set to the given formula, or\r\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\r\n const modelNode = isLatex\r\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\r\n : writer.createElement(\"mathml\", { formula });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\r\n \"element:img\",\r\n (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // Only upcast when is wiris formula\r\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\r\n return;\r\n }\r\n\r\n // The following code ensures that the element's name, attributes, and classes are consumed.\r\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\r\n\r\n // Check if we can consume the element name.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // Consume the name, attributes, and classes so nothing else processes it.\r\n consumable.consume(viewItem, { name: true });\r\n for (const attrName of viewItem.getAttributes()) {\r\n consumable.consume(viewItem, { attributes: [attrName] });\r\n }\r\n\r\n for (const className of viewItem.getClassNames()) {\r\n consumable.consume(viewItem, { classes: [className] });\r\n }\r\n\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n const htmlContent = Util.htmlSanitize(``);\r\n\r\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n },\r\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\r\n { priority: \"high\" },\r\n );\r\n\r\n /**\r\n * Whether the given view element has a LaTeX annotation element.\r\n * @param {*} math\r\n * @returns {bool}\r\n */\r\n function mathIsLatex(math) {\r\n const semantics = math.getChild(0);\r\n if (!semantics || semantics.name !== \"semantics\") return false;\r\n for (const child of semantics.getChildren()) {\r\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n function createViewWidget(modelItem, { writer: viewWriter }) {\r\n const widgetElement = viewWriter.createContainerElement(\"span\", {\r\n class: \"ck-math-widget\",\r\n });\r\n\r\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\r\n\r\n if (mathUIElement) {\r\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\r\n }\r\n\r\n return toWidget(widgetElement, viewWriter);\r\n }\r\n\r\n function createViewImage(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n const formula = modelItem.getAttribute(\"formula\");\r\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\r\n\r\n if (!formula && !htmlContent) {\r\n return null;\r\n }\r\n\r\n let imgElement = null;\r\n\r\n if (htmlContent) {\r\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\r\n } else if (formula) {\r\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\r\n\r\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\r\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\r\n\r\n // Add HTML element () to model\r\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\r\n }\r\n\r\n /* Although we use the HtmlDataProcessor to obtain the attributes,\r\n * we must create a new EmptyElement which is independent of the\r\n * DataProcessor being used by this editor instance\r\n */\r\n if (imgElement) {\r\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\r\n renderUnsafeAttributes: [\"src\"],\r\n });\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // Model -> Editing view\r\n editor.conversion.for(\"editingDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createViewWidget,\r\n });\r\n\r\n // Model -> Data view\r\n editor.conversion.for(\"dataDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createDataString, // eslint-disable-line no-use-before-define\r\n });\r\n\r\n /**\r\n * Makes a copy of the given view node.\r\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\r\n * @returns {module:engine/view/node~Node} Copy of the node.\r\n */\r\n function clone(viewWriter, sourceNode) {\r\n if (sourceNode.is(\"text\")) {\r\n return viewWriter.createText(sourceNode.data);\r\n }\r\n if (sourceNode.is(\"element\")) {\r\n if (sourceNode.is(\"emptyElement\")) {\r\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\r\n }\r\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\r\n for (const child of sourceNode.getChildren()) {\r\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\r\n }\r\n return element;\r\n }\r\n\r\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\r\n }\r\n\r\n function createDataString(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n // Load img element\r\n const mathString =\r\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\r\n\r\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\r\n\r\n return clone(viewWriter, sourceMathElement);\r\n }\r\n\r\n // This stops the view selection getting into the s and messing up caret movement\r\n editor.editing.mapper.on(\r\n \"viewToModelPosition\",\r\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\r\n );\r\n\r\n // Keep a reference to the original get and set function.\r\n const { get, set } = editor.data;\r\n\r\n /**\r\n * Hack to transform $$latex$$ into in editor.getData()'s output.\r\n */\r\n editor.data.on(\r\n \"get\",\r\n (e) => {\r\n const output = e.return;\r\n const parsedResult = Parser.endParse(output);\r\n\r\n // Cleans all the semantics tag for safexml\r\n // including the handwritten data points\r\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\r\n },\r\n { priority: \"low\" },\r\n );\r\n\r\n /**\r\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\r\n */\r\n editor.data.on(\r\n \"set\",\r\n (e, args) => {\r\n // Retrieve the data to be set on the CKEditor.\r\n let modifiedData = args[0];\r\n // Regex to find all mathml formulas.\r\n const regexp = /(]*>)|()/gm;\r\n const formulas = [];\r\n let formula;\r\n\r\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\r\n while ((formula = regexp.exec(modifiedData)) !== null) {\r\n formulas.push(formula[0]);\r\n }\r\n\r\n // Loop to find LaTeX and formula images and replace the MathML for the both.\r\n formulas.forEach((formula) => {\r\n if (formula.includes('encoding=\"LaTeX\"')) {\r\n // LaTeX found.\r\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\r\n modifiedData = modifiedData.replace(formula, latex);\r\n } else if (formula.includes(\"ga_D;}>5~=5|D!^`LP7|7oeAwzer~f$jE9$We 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%0AU2B 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ⅈ2XYf?cWNqHhI-JGRIh*J50XF7? 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≻_*L>?udRB-IHlJztm|2Q{&Wn9DZ2h$MobAP~xXd!@7iJ z5{J^0|8~rJ8sV_i>HMEEwLW28vQCHfNu=e-p2)5Y$0hNe`e}5Q)XR0b$VG1|E|rtOjzfCXPou4{>pLxX!$Vj&yJf~CUyLzkUNb=j~?YX0HVsnbgSdoTQ3FZ??$_3yXvZ#4e~OBBw$UK2%K$49B8j#XJ5uL`QH zYO1bNbgJC06Lolo>gjAX)Oor<7pa9VQERnTdv(&4>ZTsLMt#&@gEUmvX@o}WCXLmt z8n20QeeW`TkEZ5GJ_e<>Rn~10F28?xWlJxH_ z6HhU`FCW$`{EcEUpVbnD_eI0|qf1RMcfPCmf?hJcmak~N%2?(#zOHhn-{2;BUo`PH z!+T@lebCL07v2jE@00oaMPjSN-dA`p^n}dyN?mV>4{~I_$M#Y5)9B~XFQZ>acSpaA z{t!J7{W`06Rp%n z?bJb?)kWPimkEyxiJr#2)K>#ESi^E={zE?;e!cNX-JmhLMYrh=O^WArx9KUGrWu;0 z2lcS#W;w6Nji1yaE!J~d`ulk$*JnlMII%kVQgm%RuU8%CHEq-;y`wF9UmwP0a>d8v zywUv8g3-d!BcsLQd)*)B%#rw9yR=6?=%CVayoj^vd?N+Tz++~%;(J)E%UNok?SP!59{)}<^0VfoTlDCVut;1I9^+A^P`q;eB z9mn;V_>!*2uuMkyd&$?ilDFaT_xtd|Ap7oIzy13nu{&4t^-RWhnTFF(z1~axy+83o zu8hRbxpIg3i5$5z5=Z1tPG=mMJNb8njAFSnPoqTcL`F#+lQ}<8I(Oc1oYeV-(@wok zPE^UA{G3Aa@>DflO(*IkRacGN*)mS%DY>&}oNBtZPBRXlV+h9$pCgE$Gf4a!$4{PL zcpVi!GZ9`Bg}-Hm*G}Q@Wru%WBD_Y5pOHvymqeY+%a$4kGfuaTXXW;0rR5vMZE>!7 zjm`6S;pBEZ-*oErpTFTJUr+gLM)I(5nVQCJmgVyq$-|oE_E`tVy(Av)bxm^q+3{~U zsc~5M@L7rQn$UVAmx-SXNnYmgIxt+`OPyXDwbkYFx-PkF2j_9J+e~=f7wnvQIevS6 z89!4JmJRcciEpgip3Lo)I&N~C|ApIV;(M3v2kqBS`dPmyEsrZf>B>;9 zJjjzhBcBSYh>EF%N~yHUsk}FRE2)}lsFv#JOf^tLHBr+%$;ZTu7LId?TC1(vtCOx& zH}%jp>ZAS|q@lV_BQ#n!X{>J5cumw@x<^xWU!L$shQH-AOMlz>cnnJ3X710E`4~6P z<$S_*v>;FNu`uyep8s%LO5H9J&)F7BwL+`)lGf@~y{3)Yq<6GM@9RT-tR4D7U+Ej| z(f9gMKk1Otj__WJGL&0+RX~MRR7a_#j#XJ5uL`QHYO1bNbgJs;4As-wNBs6WD0$m% zctqxF#q*9xB+hqu(<3sUznaDQEu*cX;oq#UDE#}?zYW8EF3~0))-D=8s}LRoIylWv zx+2c+8oerdb+mW1Uvyw}Nc7t1@aU-MjnSK<WqD!L7qAQ~>MAt;uMb}3+MBj+M72O=&8r>G%9{nWxS#)P~SM=NH-srwe zlehQ%ao)k`FPWxp-{E=<)8W5AGLbEB^8WEhhyAnT{FV7(-NJvt)Vichq-UOIB4^&@ zZ8(u9Z}vn!6;u%wQwfz)X_ZrXRa6z7s2ZxN+N!HFRbS_*k(%g2HB(EqQX92X2X$5# zbyrXIQeO?wU=7pt8mSvJMz`oT-JwakTT?VmGc-#N>S4{*<9bqyv{=t+sa9yUUea2< zs@JqpoAi#h=zV>tkF`Tz=qr7rJ^Efh>L(piT0W08%200QRRI-NQ5~g{I#y+Myeg=& zs;Rn8(W$DVGgMD!tD(-*1-eKrbctH4t=g-Tu2eVm&^79#{u-pAx=tfBS~qE|Zq;~A z)LptqQ+1#2*8`fPM>S7RXrZ3gvwB|3wMsAQWxb+*=ykoRxAm^x(+B!UpXzgcsjszL z-{}V((9il+iTqyeE0>N?eihP@Dz2k-jLPUZouEpps*`lGYUwncuCvrY=c=*JS5r0D z#ky3N>2h_{73!+1bhUb`p9X4(uGMgj(v7-Ve^xR7yA{`8D#`IEB~GxKMp?&pbz@+bek9F7+bOg!ea=4*kT(lc73 zWm>5hv_|W+UK{j=-qL1m)i!O{C;Ch~wM*Y>ul8xb4(bZQIKpurlZ z>orn0XpC;rZMs8~bhoBxnr3L09@N8{tH<@E7HP4b(^9R-T-W6DLU>FKkDsf}e@Scg zs$SDZg~!-Ud`DaKzC6wze!XXWSRhBnr`oA+^quzWkP-!RWaLy{6;d&kR2h|5Wu2s& zs-v@Xj?Pn4wNz_euFmSFtJSw)u8g4tbA{Vc#wg=4x>XZ&x29^Q9@L|nuSI%R%d}cA zYrQt=ZEe+u`cymhjlR=<9a5r@%c8t0q+%+mGAggiI;l{OM9o5O9}17vbqi(Qcg{4f zU&!YUb*>ueJe{u#b&;B@r7qE>YNNKgT;aYF?jzy85$+eM+kUvsr*7|mbX)J>d^@X) zx~r#psjmiTu!iY+jnoYqqgx7PB<|4Nas3a!txU1ZG|f=d@HS6rNXC^Chj-t9nfvwMp+N>uq(5@%#EvA8Uuc z&{z6Kd-T12v>gxVpbqI*W%K4scI8k`<<=3(rvfUZA}U%qJyAl(sI1DXlB%hOYN<}) z9Er1Zj?QzO^9y^-W;3=_E45KObx>z@QFrxJFZI;`4c0JSuaUYzV|0sd(;b?myER4A zG()rWpdQv-J+3FUNQ?EHmTHAo>m{w#t9nfvwMp-2i{96Vh5b9aaNfio9a8Qhxf6wp zK!^Zu9Upwpv<~XuI z;>aWOCdwR{J5fm|>vZGB=3mMyjIY)}4L>p?aihj*f+m|cQ;%5o8LqVaded7?f8y|+ z`quat^YaufkSI|!ccL6?6wOG~HeFv$)Kcx#rD)znUk-ElNZz7Jnr8mPd{WP8wbR&W z-ussO*z_L$Xq>%R-b8_7xf4gJtSTFyS}Y@RhUtc;TbOQLtU#ihai3!8i2)j->oig~ zYOHS41l^@6#k?=0*?L&>v_OmXyjJQZy`l};q<6JV9~X1q@FwHu+G%`JJPVLpv{43Z+-E@`l)#`1!F9&F_h8bVS5yicKWj(_>jx)Yp6LhC0 z7tfivuXwJ+Lk@dbkD51+^Yx?_8b8fv6nsw4Ynk~gxmv*&jbARF`8@xM@p`?c*NbO< zX6|j%o3%ynX`4P&SpSdoiFW97r}HJlcH3q8TkS2Le1AFdz3Cs#KcIs;q+eaO^rLbl zavznR$frUosuDUzWmR64RLycHtG3Qi12xhGYOYpltB&fT9_pon$L@$gsi?)ominfWii*|^1j&_N5kM@lAiuR2Thz^bpJIw3j zcx3d35}se<^exfbqIX0mWt!aXcgJ}?6Op{#PKo1b(HYTM(FdasN9RT#k3Jb)6kQyB zE*ka=3ZKUapTk&MBKi4?aN9!1s(Q!IKl~h$H>15T?X*ykJse#T_W1X+2YOafQsV>vy>ZmK! zRafb1^;SO()DT^(;TokIb+g9lc1_Tonyh;@T{AUX59tv-rukZ+r}T`LXqi^(1+CFK zt=9&cpE`6);9>-pWe}nAfejU^= z%2v|*0LrO6%BO-VqGBqcQYx)-DzA#Fq7zj^HC0=6b*Ad;95qrCU8rVisa9&EcIu$c z>Z0!Isa^{2dG+N04c0JSuaUYzV|0sd(;W)$|4icDnxbi%p;>xR4{NR-*OOYL#d=Ol zwL+`)lGf@~y{3)Yq<6GM@9RT_?enpA=nH+NZ?s3>>qq^hLrN>sdXou&tJJm0r}# zdPV=x>v~gf>s`I45A=~f)#v(BUu(C%(+>*Q#R2}TUzIo}^L@P>%%vlgUxjp}itA_{ zqcS>9Cn)?Iz7nhIB%Q2UI!&kREHzNLp3h}tov)^9u8Vc4F4N`es4LV}S1G(ldNq5i zp9X4(uGMgj(v7-V<8-?wD7-&>CnxJ(P1j7#)RZ=qWv;C0eGH3h(>A!0?{$ z8m`m&V=`ZFZHT@Ream5+wN=}+U7zSP?bI%LUphPvCBBWv*&E&Gu>EoRpy^+f?O2a5 z%Beicr-CY?Vk)6hDy?!VuZpUo6BYh`P=hs9TXl7&>gyaeQWIUMW@@QcYNK`v@3C}X zXLV6`h4)!{vX}a5fCekv-iPsejnoYqqg!;F?$9LNttpzO8JeYV*&pP?nybh44FM>7YUC&;eJuXxR}B=EWxlHOR==dDcmN?v!bf#MAcBZ zoz!G))zz7*uX7Z(X(KjK*tQokY}00Jsa9&Eb_(0I1KpMqof)=qc#Q30I&ACi4BNUV zd#SGmXt2U|54X)>ro;BXp5Zze$?)8FL+SLy&AL^0m^VpxYl`u7-LD6YA1 zNX*lGJ!$?TEiRq;Z_{TjyHqRnqSls9eik9IzI5{CQ{r{=HaXsvLe$Ed8zt5Td_En368 zT5-CL=`-VW1Je!FL`~H~t#p~%tFyYQM;YHWqF(Bwej1=b8lqvkPQx`)qjjUkXspKR zHjUQ=P10SOtSOqR>6)SYHCqps`Te!y9OFkcSMwD1S)9)&wNOuKv7WWAOSMujYOU7m zb#2mS%WUB`eH7>I;7)z5J=&)OI;3o6ZFA*TJ{3|?mC!LNtMaO(YO0}Hs-rX2Kn>MI zP1QoJbeY<#v%0E>da0iVX_$s8mgr_I#UhQP)+Pa*|eO$ zS*W!xS7&uAmm_ht!}};)9|IV!r*NGNF@2rF^)ZsE>*Pk$W0lYP-Nr&JstKm=(lMr| zuq?|fwXL#lqr@G`Uo}N^=tWPO*c~iHoXUz+jXDJWA zZLKsfo72ys+zOZDMf1XXS5h_AP%YI_12t3=HB}3>(pty4jP2D~UDZRqG)TiVT%$Ec z<1}8AG+DDXM~k&Y%e7i-^oHKn7H!k|a5?z3_Gq6Dziqs3nbhrKlj*~66PwLTZIf`F zhV78g>7*|Ed&c3ieaKJrxl-#B)*)OE;XJ}N52y82xs1df{is96*^aYKm0S5#NJUjb z$Ed8ztCFgzhH9yf&Qt?6R1-B-3$;>Mm&@2*oz+!;_xT;ZK4BY$?bFY8X{gj~BkOYE zutDl^T>5YSChT=wMq+@5X{5&JHciqL&Cr9It0%QsOSM{S^_n(mi$2s2eWgA6QHPZM z_>4qu6;M%?R9RI}HJze5s;7p!KrPf-?RBMksE-EeI*ry?jXyr~J>|PhPu2aJqj_4W zXSG}}>J`1NxAmSr(&zeG-|2vURgUsL1FS+Su48nZD(NKE(&=iT#%ijt{$brOHhq~o zs;jP6zi5~@M8kEX#%Y4W;bGf^?Ks)=^zxaPX}0M{G{1bV#9}Si8oj2swM{$9XJmX; zzHCO`6AC0so{%eby@t;rXB_8CmySOn`FbUz!U@?je9ufqMOHGu@(KRkn0Z)uot}Ju zpt^adoZ$1wmWiK7&ZuqrG|Sg<*cqy)v(-@NDSkFIae?WJ)Iyh>kUgwNMr+4yBcGp6 zo@Und3F{Kp$-0DfN;vO-cAgoRS*ESpDST%7a<*3o`RsI9_l%CFJE^m-I3aW1mDZ)( z3F#Rn>$FWDX~zl4*LkVW znp)>2O8Q9cz^5&ggRH}7KBxr9np$o%)v|Dn$(B_Fr`$!C<3hy9b!DJ2j4C!bYH z9(MTWl}cAgB+BZz3W*Hg1DPmqx`Haj%U{*DI7ug~mQK^@IxF+=)MM0D_y4c>a4Enp2lnyjgsq1l?FxtgzqTC62nuGLzjSM-|R z(A(OgZTd(%v{PSekM`++4k=qDw?*YvJ{3|?mC!LNtMaO(YO0}Hs#EDd+^(`-_S9uP z{IZAn|7>3BIEOFu&z3)Y+h;xA9~~!kU8fAog#Z3*oLVlcsq;=9H)U#>)cDT~%m3Lj zfA`_3%aB^P)Ht;shmTY9QyzZ!;p4Den3r1q&&;}PYC3VI+g}4UR1-B-3$@Z^YOl`f zsvhd4ej21<8m`eAqj4IqNt&#wnxWa6qq&-|g<7m7m6EHRS{E1PFJG3_W&JD14a`hm#M4zX?W%2zJFmI!}5u7 zmYJlf4x6KeTK*rGNiF+F{$2k@`~TDVgzcQ%Px!y!`|$t2xZRT5C+<_7oVJg8(zv~n z(}(?bKyu!n{C2?M+bVgS9PzZnzV>l{^27JJPcD~bANbV%_rHDb&|$w(aQON71LJ?o zZz25M=abwH*c8?kKZ#c= z``b$8%+KZ=WnY2Rcl(9!|MRDnL^&NVe`5J7zm2Ie?R%Z{kD2h zBJ;85FaDkNul_yu|I~8fy@2rFzcReO4gdXDn|j|Ob=rS4P92umvYxAIcls6)Jzwvb(Q2M$hsX; z>wLJseXbyUmcVBS!f`VH{%;&UTM#}s5IzTR_~!(|=L^DT3Bub*55i{- zQlC!2x+=ac31lkTBI8PRRd@B!)#{}_>Zbu!lArng+rGzbP?em?f1if$f)4vEU!QsY zf7@&C5$=m&-|A8H{kn-8s@T6EuESXK#_6^=9-nErF2goYOsL}XWAeQS$?G?K7t(ac zpQ+gj-(T_&A5r*@lE*k-3l#Q~d5X_yiI!=lUQqZBqcvQo_1d5}^p?VRU4-vM*=#y| zw?z0(l<@r$TaCka18w7WeWK5_Q{j6p!gpWnGX1UgYG0Mib>3ekcOqNW+=)CYqEafa z6IEOFtL9E#wkB2WD^N9goWFW|`+sG*zxuo$wnO;uub60Nd$m+6wW*q(xSU}>n~uCf zU3Hbhem7UMxB6Kw?1MAN^iW-=5$4^%F$%YnTa0fvJwbPBa+vQp_nIHRN9;c1`}Kh4 zC|uu=|!izmapnHZ8Uz1o3&Njv|XR* zGwswaeXG6Nr~L~1&>ZA1%2v(ivK00$%E>&+r?AgdK^9Rll~5^_Rymbd#cJt^sys<2 zE8M?o@id+8uzEVjbR#v&s&UBfJ*Nucm9JX6qq6qQ@-%1Q+UQJ*%)E=JV!cp#!hT6% zzteoC3#y2Usf0?Y^od@F@dQ;;Rh^`h%|DfObcX8bY&Fz*x=)L8msq|H+o^*( zE9@)Qh27Otz0_9&G+4tF_E8P{MqO`uq;Aj{r!|hZYl7}n*yn7r`BP2br~4K5D|&!) z^r+_P3Ck?vVm+s&#w+=P)@Yr={#Wa{!C`Oe9c?w<#*g@^KG&D}T47(eu)o%B)8FX_ z9kA>nrk!M8Q)MXZ%axmXPfAY|WRWn>^wB!jbXkS{ypCrDRsLV3y?0zw*Z=r$_p&N( zts@<`LTizw;~opbKFH#b}hJO+dV5kNH9ZGiL%NaNSf ztmh;sQ}2U41L^aie;Lx(fY%}a7Vs|Qw*uRN3}6?q2gm`acgH?}v=G=290ERuHY`ZF zkW%l?3L!0qvJ}z^pazgbxdEWwFH=HU4YUEBfC2PofO_wYdY`Kg@;1N;d0gJe*}I3euMH~ zklqvy>l?TOxEr`P9KKhEbRn<=SPrZNXuyL|w+46=^6MaFKpF*U9Hfr})O&GHK{*k4 z2FlMv`ZA=iLHY)yZvpQB?*pkoIfJ%=-!^<4>YfH&B9H<+2fPTnSAo|dzZrNN zcn`{{kW%j&rbB)gkOk#jARpKZ<%5uZ1bhmx0UjU(#DE0q%OR}-Y5_Tv8zF55)Icks z16?;@f_yJv1sqWR4ALWzQtvPhL;g7AMt&I0Fw3&17d3gCu%>ODsi@_yhOAOQRT z{0w!!LHZ}8H!dRG}=ia?If2GK|9GBb~>2w#18%YU91eH6|bVPZi z`H1RB%MtYv%@OU9)+234+K=dtbR6kC(siW!i2jJ-i1CQ&i1|p*k=`SgBi19fBlaVX zBhDlJ1qFkwLCzp=P%tPO6c3gRN(ajZD+a3uYX)V5@;I20I74 zGfxUKcMtXq_6^zw9fSQ@hx>-uL);<$knp%8XGlC$G9(=;8>$$p8mbwR4atWTLybeq zA=Qw2NITRvq#NoS>K-x-nTC3X`i=|?*@hfL{Ua4eIY)UTl}Cjm)gv`Wi-#2>vJv^w z@{y(y)kxb&>rvTA_lR+%=cw|i>ZtlS^JwdF&e6`}!lU}*MaRWQ%}0BWmmjqrb*2yX zXYJ0}JuDe69WEcP9IhU&9j+U$A8s604y%UM!`k7tVcl@&aCiEl;hv*aM+b(j!}ek4 z@W3(FG0rjGF~Kp>G4ZjIW71<~$109h9jiGeJ0?G-IM#Ty;h5@}`k3}u+cDj-&STxj z4983^gZh}`Xr0TT8DSq~9v2-i9ubU)M#Li}BhnGgh;_t%oJBjyr=1kgP8QKlw$e^= zXeY(Tt4=acvQAc<6vAX$PZ>^`j$2Rl(MGNR8Z*H>gR{amRRL{4v3pa7;8-G$tOa8Iz64#}s3Y zW6Ckrn0ib*);6Xa>m2JIvy9nK*-tr6Imb)K%f`#cE5<9wtH!IxYsPEGW#e_@^6~m{ z#dyPb<7xeA!)fDb(`obRp3}Xj`%YU$rW~IX*DKnh;Nv zOh_lnCMqVXCTb>R6Y>egMB{{VLN%eD&`z{X=q5TRx+e@1riq@3z6tAueZo00aE5(` zdxn2Tc&6w~@fpdPvNIKDs?OA$k)4sBQJiT!qdcQJqducO({@I8rt?hq8N(UVnVvI! zXRK%JXPjpS&a%#O&hpL*&Wg^8&z78(o-I3DaklDg%~{!5`B}x;#pEOJwCry*)$)3sH$-YU; zq;=9ZX`ggVIw$)l2hK6ivCgs2an5ni@y_ul)#un#+G+E&aN2xP0BO;*`ASRGl@`a9 zmg=Ca3CjAQR0d^pQ0juRo01yk6%EMdpzNk(tMN)JNJ~&w2W3rA)(54Ik{!w`9Uz-2 zX)U>8FvMea8gHj%p%q#spSDc0`PGeA- zuk;(Q^n)^3q%x*p(L~ikaYpu+Q*Ha}gHjoknxNDLWhwOOGHYGtPQO2nx`oNC0WL7ZB|ks(eU;>Zz4fjA9_(}*}th@(WD zX2ek;P7C6w5l4eKTEuBZoHoR1M;smEbRZ5Kt_yLx5l4?W2E;KUjtOzhh|_~Oy@=C? zI2OdQB90AloQTtpI0GZ4BW1|U9;rv>YGkfKW(6{K~^cUmLY2evQ{B$4YJCRRgSC*WNk!NC9UJtX5>TBdZfx2at`0Y#e0cA)5f%M93yawi0BMB3l`hXSL zQzDxR+0@9UMYc9%(;-_YvUMYy0ohE*)`M()$Yw=0JF+>EZ2;L>$j(7_9DYBO#dj+yrA$tw7%aC1;>ztB_rd>{?`RLv|gqcOrW?vKx@ygzP=Y z-iPc~WVa)`6WIrlgNYn0F=e49`zfLQ%^ft zK|95U>&__%#S9jGKo=McP|i4wpaheXOiIATtl|`Vv=j}%FGSl*J6TE9R8lpSP@_i! zO=tiDx1j+wbuD9VLr%x2bxZ_Es7|RvJ1wAu1}Uo#Z|B z)KmB0|J03E;gM0%F|l!vx`gFVgofo8q^?g(U0-lmpO&^hH*Nji!}=XLxdqvU8Ciwd z=S$C*oi9IMu_N_>OISwTGR06LjuP~6N=7g!LFJ;TToiRj6%|PdN*5hP2`U#$Rm4)o z*ceJsy3F9uzI~bdGO6Fp%mW894`gPBLsf)JsQd5VE|>5>ziy_`%@lzLbUK3`L64+I z(WB`x^jLZvBRrHI7Rm?0zN6VWB(1LU)FR?g|Ud3=7>I7P==aG%GALJ1jIO zEHpPPG%qalgRs#2u+W0A(893Ly5VSE$)z{D^gS+puS?(O(nFQirMJ2Cc9-7a(mP%HewTj0Wnj7tESG`pGH_f5 zuFJr48Tc-Pz-17+3?i4I$Yl__48<-(iOV2y8Kf>lsmoC2GL*Xv6)r=i%TVPqRJ#l{ zE<>%$Aafbu22AcU)VmA{m!ZLBP>$$Ok8{N7GPJr3?Jh%y%K&$3y~hSdm@XsBW#qVw zJeN`6GKySAvCCNEGD=;>GMBN!Wvp@;Yg|T|%P4ml6)t0=%cyi2RW762Wz@QiZ7!qE zW$bhryIn?u%V=^LdtAmom(l7n+FeGc%Q)aNv0NsO%fxe;1TK@vWfHqgB`%ZHWh!%- zDqN;2m#M~OlDSNBmr3C=HM&enmr3O^b+}AjE|baCqa?aZeEp3c3)5q1^;mj6mI04d z=CScSwqlR1$z!wO+yR`Y#(6rN&&K)HIA4bgns7l6E@a|Ga$Ic1i>vVBI=t9|m$2}X zdc33!m#}e3FD@10QWY+*!Rs4wl@4!V<1IyaOBLRt#nmRfvlQ=Q;@uWpFU9rExZZ*r zx^RO5?~~(wYP=6J{kYYLTWz>?0JocQdq3_FRXA9CZe;M=(G}DCZbzRbXOAHazZa9^p%9( zOtN&Ou$>h4l0rKv93VvkQY0fqaP(fEm_$}R<@Frda_zfR+p2q zUQ*^H>$=Fg0aD&YHp4L(J)PtoRU6#E)GeT^nx6U*1! z>Ql*msz#rt+NY8EG#$S7R-dlcr)%_esC^yXz7C_W!{O_!^O@8>Ymd)5;IpZHHoMQx z@!3m!_A;N{>a#n24vEiE;d3SX`ae0WZM|=a~8xh}x_-4fKLEJjTp;9t#Op%5Zp70go&oWUh-X4PGvdLqDiOB|2?R(WM1o=@kRU+?64W4p3~}p` zK!F5}h^s){2E=Vb+-Ag8A+8p2+YncWxE+WKfpj5mH{u!*w-@o4h$lci=*NHraFiOv zt3^B+;&TxnA{8Qj8RC~CekI~JAimOL5#e&V$5Q06bb2geveM|W$UGJn-qMEG>qBAG z!a%So!J!0?5`0PsC?TSRm=eX5kWfNOiBd|GQKCF7)UStLzGByc5VwF(I6!EHAoP`C zp@qw2%Onrp|F&pw!RUHHEIw| z?NkNyN4E4sMQI7-MAooSuTcp(HPuxSwDs6kkShbX@J$f4+Qz%jfg7#+L>+#uDI z8xH6m&Pz&|!tg37Zgr4d(!9b92A#oRL@**5QH*Ft3?r5i7ZDyok6=VZL_|hJ!MMSw z!I;5_!Fa)F!C1ja!8pMv!5F~^!T7-Fe66?mO-(RDUn^?AuG7N^K>QFrgbl$!C=dvQ z0YN|r5CFJOYGEtt^Re1+ZaZG0CrqurCLVZ-^JPS*3h&e4QWIWm$Gd7EI0y{_gRmed zyrn)OIwB?_HX<%EJdz&Ch>VDgjEstmj*N+njf{&5r^664fCwNGhytR47$6pi1L)zD zpe*T>>8_m_`8zZAWfi8o+0AZti<_-+vs>Nlb~jt+W_P;T-EOvlI+58uZg!uWZFRHl zZno3S9&mG5ZVuPY;lnxZ<`lU(VmGJQ%_(tnByNt>%_(s_yDfzgLyDu1;c@(fD(cN=iv}wETW2#ffy+XK7aw# zLI!G^g0Vwy!9k#$ASiGT zjy|&+LzN;pl`zpWZF_?Lvx2R_lfIzO&@+so{d(2)3>a>0xFzL0=j?B8(pRR!L1FxJm^zQw;h>>B0GmpfWh`*XII`3q@71 z2WV1F9h6#}+Uu5-88ob=Vx$%-L=D}-YH?DMnjl!5urfgjo`5De`&0%-%;M(Af*EKC zV+Bz_S8$a2pgm+@J%dIOl;8;zXZoMXKou;8nc;=G2O7*s-&V4hlLaLRz387SxhmOTvDj088p?1lwHom1cj_%o9MdpMEd)BrQ+WM)!8 z33(VC$W;F0Hnf5P1PyfF6qL{ePB&w)E%-^b4JX-FI=}!TfJh(;hz4SSSRf9d2MsAp zI%P_yZ0VFSowBA==5)%QPBoxYEf`b_MzC+H1%qnApjzz8OHEHr-Mc+CKX+eVT6%VR zHX}VdB6BZpH4(y6qP7z@2)G}GEt9B~5Jo#}x41*rnQtLy#%lb!gO)m7SHIwI^#wc24%FBRe}g zEqf10Mp`x_o3RH(c0^isM0Ug;5H4X=K~OOEReo8GUncX*vjJ6PJey3zux4p@A237`RlFzdb_{g>8~H~D_DL7 z$FJb|6#~CPQ|Kc6%~F(m0wZgSIGPdxnH61D;oU@rC*`)E7X34)~{&u zD|CKEr(eyy3XO-w9?t{)Rq(gVo<)_cu8G4Fmp0=&BO+O8kvNe`Arq zvDe?x=kF{(UV!{RwS_@i5*ECNa942ek2({QYMnJkd%$2935A(D!avyT8A zr0|$lfus#c+K8l0NUB8AW+YW1X$z97kyL}ES|n{n(l#V*M^YV>bG~hj@#DLCQQ{q*(5Mub$R_kqQ#O*~yeGSpmPKq5q zj>OZ?$N7D@xPhqP61oafSW3#Z-des_R`2cW_SGAGo!!1Z8`)e;st3s4Ca+ZFE9QE2 z)wrq=cW_BVlb6*`ikeAPk=Mlb$@_3sEpBKgS}TaYI>I3(&E2G>&nFY(;&Lxrj0@^< zK{44{PxkhEr3Rm@$J@mu#8R)W-(#`j-CBZKPD%u1l^!pyAysxy{{Y_K=CPCzredPX zLYkX#DF;_Jko7EYnaJDTPITD_qmU5yd&O!((nMCNJ+>~K*M@3bv(M{n#;s<8UrGp>L^=4cB6%XR zTtsS%$Ud`|)$Mg?ycJzOrpi}RgbQo%A{AclBsoH_3VIgd13g5Eo#^T!_5EZ?4|QKB zx8l7vf>}>WjAR+iW1W}9_X*8Dd9P0)^flFb`o*NC%f~nS>MR6D>9O%~P95Ieh3f~1 zY8fezkTSNn!r@~%h&rLKwhJ#35ehreXd-%8gsq+|6_HgkQmG@&r9Nc?A?)(WEI3n# zn`J~_yVuc<_lpT0muRZ-)$ws*8(u2K4Qiscf>20^=2pU_CQJEbO*84VdR0=dO6ij~ zh9HhE| zRI5pi)TgK>js4!*a<9(fV_JM1r%%=GQ#pJst-fBfkJI3*>+|$?;T$c_E5XGgT-<_} zz?q`JYx%gW6<0Fx=2qO$La-|dUMs@ zGud25szsz)NUBRobv3CeAvF@RjY+n%$ZjTS5s_9F=`?zoJg=bB3mC_UQM;P zgXQh8dQBBxv(Ve$>yxW|jRU?WJHhG+w1~UB!dj0-gY$cEUaO~{iSu|kpNWeFxY&kQ zRpHe#yrvm%HsNY6uCBz}n(>ZS++L2`Ww^5q@9!iywFFN>aD@b~i4d9zDVHd%C(6o+ zs%oNMPAIAgyPU9#NoE7tpe2PpWKlO+W*{s1WTl;~Rgkrfq+CwQ&7`7;R4_?ZG1+b) z4R*3ePCEL%CFNe&bQD&vn&s8-z3l^Do!G1E^mY|_9Tp$U=;Oc&_h!^ZCm2^7HlQ73W*ex1Vo2&jQ%z+s-+t97y*0Qm`sLX8|1N z%YbsA0;mM4fNG!?kO6go9H<8xfF?i*sDKtg189L(fa<*+s5)nbeg~l40N4z`wST&`Y7D6fj_yCnJ1)b<(F<5i} zolxQdMN_R%ZiRB|RM%81=vtwdR?xSDz8&=Kpl=6#JLua%uLHdf$~q|PpxgmA9Z&{2 z)d^`Aq+L+zg1iaxCdivWV}f!I_STVO?IA z)Ob*NN>bwko&;AYwH~2uaAm?83;IY&YW+d~AUmKZY7J5=kg}wX6pSj^A7x4TP3b`k zawg{B+5~+U%okNdtw30llwDB5+6>xLl3J(WRZvoEk?IA`Zt#;D=NSp-@^uY0XE3&u z9war7RG#u+CQpqpWdr9f=&8|zd8H&33neLg&{3M;xd17pq5OlhG$<)QsntYjsBbTHXuxBdHqTo4C?NXGU@+8<6@;%esi{;QiNLWK$YL!FEq0S)a zhw8hVvZ0Pjt+;7nFi)NFf@vX?snLgeDl$sa{^^7UErM3Gf1AOC(ZCmO&_pn0fL#=h zxYo^NZMgp_H?!K!tZ^~nE7XduAAam+R=Sy0Zf2>QS>|RIxtU@&v)IinaWf@uru3cn z9(?$b#~y$3-G{KJY13jj-Ndjtz}7%*3~(M&TLYYk)aC$ZBDFm*sa>KRlJY6#RP&T- zs%1(IXSo*QY@2Ei?#8g!Laeac!2V!@8R~)g>6@~^nzg~`v0-GXIihv|VQ{pYArcj! zrXqS^&HDF0`oyK-xis*k5uQE50}z#)t)`y#u;Eb#JOzU1Z14yL9#g=h40xg6V?l`PDtah>6T$OAWztqJqbMebv{0difldDD?C@pOT+JJUI2Xp|PKo`&r=m7&@ z1WbS#=mC0xKEMK40UKZk9Doz(2L=MAWlVqtumKLh1$Y1-5CB3z1QY?_PgyZg0!RR9 zptPbEkO6go9H<8rKm*VSGyzJW8BhT&fEv&MS^(m!fcPpPz6yx10^+NH_$na23W%=) z;;Vr8Dj>cJ69Dm5KztPtUj@Wh0r6Epd=(I11;l59cuWwF3F0w9JSK?81o4<49uvf4 zf_O|2j|t*2K|Cgi#{}`1ARZIM19wHGrZS)$r~oQ~Dxeyu0U%xz#A||hO%SgM;x$3M zCWzMr@tPoB6U1wRcuf$m3F0+Dye5d(1o4_6UK7M?f_P03uLcpcp6tB!Cns1H!7N05k@wtxbRuXa-b33!nxxfEH*4+JJUI2Xp|PKo`&r=m7&@1WbS#=mC0xKEMK4 z0UKZk9Doz(2L=KPUJalIYX2wgxzvw(IHjG7tLtM;FZh zqlbR~t?wQ*9P4ul1uh{w(9qHhsDKtg4QK!@&OPyD%^4MC4 z+9r~##6=}|e=*K4A*%JbP(k*vy~Z|NrN>LHgrkxusv=6Ne5@j(SU`v>NDYf@gHN17 z!p0;kO}NO2SNHmOuuWGI^&P<-yb`{6yPmPZ4i0;ICEj&i1J6%qG|<&dhLqP_w-GC@ zS5;9OydS*n_E?-Ai^XGQ1)8|c9%qxM-;OicILn5!WjH*wE^i1l)!J|#e7n_x^ZEi! zmG$m&E6&pfnySP&zXs>GcoA4SoUSEyZ*Wj`;96md%y6{E`-e|^~O7I3T-ekm^40v-#Kv~g(tBkm+ z4{zb)EzP*PIiRd-4JhU9xV8gtmE)~iTw9B`Y25NIyhDh0^aqq$A>PfwyKC`oQ$X1P z$pEhByBk=zp$RuJabrn9+1G;ia3Im(JzaQ@4)1Z`eM-Eq+1=EQ_c?J(D{gV();71Y zInd1KxRnmvrpIk2+}0atm%wMoVuM%6!X3?S*lkormVic3hr^Q=r_QZX;r(^^KnXrj zgAZ5-Gl(1alq3VIU!OIMFv7#NtBcZ znmdY#VkaSX5GAdIghNPM0?pk9qO_Ti@(4+ryS0lbYa`0~iE;%|*5_{PAgVe7DrON; zqax%MLMC!|$lM)5x2^@}m*e~@qMk|A)qB~U9;u9|R}zW_LQw%9eL3z<5z%NP8hC`V zJfJG;B$_2eQzOw*NvPR`Mn!1biT2ijs!m1dAYK3GGlPszVwA90cil$1k#70WQZX5goiP~+mkYxh0te!0AkrhR5hn%cz3AE^Y z$;$pfixFm$-$>R%C;dvm5>T_a?g58~DGaDNCcqd_b7g>utcNFbjbww^!{U3`)d4j; zOj1fb992MFs|l#<>H=yxkJRcs+-|b1h-{Mv)M`gS-LCb(&ZzFvk==ZW&{v|v%y63{q0JXH!$l`^2|Hw3hta!-}c zD=YQNv|d@8w+?=V-g=2w-t4V!^(u7U2EMmZ>1|?pYOG#mQ$P!Lsv57V!BgAgRoUF- z_JFpzE1*?tJazpZxxm{FHpW)3QRg+Wyk@z#r!Sy&bO)N6xL$aU*X!{1=>tuw2Cuy` z&{|Utbo$tpK90f1Eeo{Pclo$-pU~nH*Z9P>K9R#Gwfah{0N{c-0#z0$nwXdly&;}d3s@c1?eKWoWC^r$d4YDJB+y<|=IN>S+53GC zp3fomIjRHgwaoRqa@A%GV5JP?+51GP<{^bOKvE?0QviF zDCa;K%wA7$G1-bdO+#Ubx z&+YI6&5iy%1Z52R&Nz^lm6?`VNQ18qGt>U#|Ay=K|L-rr>XX;Z63NrGk5u)u#&CJS3-$2XGOs75m z1g#(=Ysb2*jGWN0|Mw8{GV;?hata^I$bp&3%-Q)EV_n4cShF&>2Uq*IZ->l+eJP`{|!oriW@Dz+5j?w8D zoq^FKFnT0LkHYBD7(E7~$71w2j2@5C6EJ!rMo+@%$rwEaV}xT2I>umNj0lVoi7}!u zMl{BV!5Fa^BMxK4V~hlhk%%#pFh(-QNWmh)u?RXA!N4LSu!u-3A_|L$#v)>{h*&Hl z4vUD#A`-BOL@Xi+i%7;IQn1KyERv2zGO)-9EHV;{jKU(LvB(%KG8T)B!y@Cc$OJ4h z5sOU1B9pPm6f7zni=tyu3@j=Fi;BdeqOhoFEGhHqUl&P1B;Hpq9d{BC@eY}i;lsfW3lKsEIJ;GPQaoQvFIc$IvI;j!D7O(7&;ci zz+xh>m`E%p3X6%xVq&nESS%(Ei;2f#60n#=EG7wyNycJQu-I@cmX5_Tu-FJJHWG`C z!eXPb*cdD}7K@F;V&k#c1S~cYi%r5}ld;$oEG`_2qhoOlEG`0zi^Srhu()U}E(VK> z#p2?yxOgls0gFq-;*zkqWGpTPix0=*=~z4ii;uwKBeD1>EIt~GkHO+&vG_PFJ|2rt zz~U3J_#`Yo8H-QB62h?rI+nn|5+bmKNGu@=ONho2Vz7i*EFlg{h{qBVu!KY`Aqh)J z#u8Gn#BeN;jwLd%#0V@g5=)H25~H!i7%VXsON_%3JU~9) zsfDR2xqHDRm9{T6A3U3hFCDI!YapMIpPiWlm!);I=fGd+ZEpePPhoB@ZF}ZU+77rT zQClJ{53XHM$bRad&bH^~X3cc9Ked4BDx3zk^y}3*Fb4Y|v~=3_s|YP^S8D$2`I+Ds zj0*hmNkPVcObT~A7W?mpujB=H(e%uMwERrUxqn>#zmJoVvo{+qpZ|w3?Md3+2*!pD z*{KJ%f-ieBLc^$m&CSo;3B6_ohlU2724`e{2268sTg^yMNKL0DZcL=@-vt{vEj{C( zX@U)RQ${}AHGmAxTkwiVOU}>F&7Y}A`nT;3Rl)k}o#o}{rezcqKu@%7X}MWhFjHIM zE@vApeJ{+-&VR3nVDrtnIfc*tv*upTE!>jvpJGP(KTc#M1lLXqtebzUHf4P9VsQPu zkrP}h|M4s1jhufLNODg44SERr&EWd@Z(RaR^M7j+VIu!q^G4y0*#Fkd%;A6Ql3}9$ zS2J@J`)_Sxep+xaK_tR*&&>{&k}_rri8F(?sW3k{tohWLQ9!NswA8#*xE(qeT)J6s z3z;!%mT+?Bn!n`sS+nkfbN!$4dMMw2L-}#Iu-{n!3e;2g1pnA?f%4)T%J2Ul<%e&m z-wyT5ZYb~kAA72Nqy3)$vH#$I%K86OE`jpHH}qfkf0X}qLwzIEue_nG{U3X(e4~BG z|JXOc;H-l7|L-?2Fkky&zNov%f8R#Jo|LEb?f%jf{eWd8TmP>*%^6oH@81GKb_jT^HK|U!6M5)xFbJxC$;-u zk72`x;0oLW_wnF-LGE6-HQT;7GmE;b&!D!1pnx+vhq_aPTmG4qO4Y!&nw6Eie@302 znGd_bv#-DYDy^Upu0RFUCO}fGo-gJUKsp$ z{I4H}+Klrv3i5JuV58lhn||;{pQs!6>rUNZG2`#vyq)mpfRt-#x$t=`ZRVC1dV?To zp|fVSW`xYT`-bCfpv{^!2s+A#H#WXVn;BT@N=9AiQmHs$N1Yj$jWc7gF=Hm?O~G)X z-EOMC>-0mK0RARXK4!cT^dDKo>ZxPF<$KluaAWzi>$=DtA+tV*{9(WZ z90AM#WlOcA82q2|&~;rNv^7BfC_uHP@>E+MK>bi{hk+Z*$FA!-psg12BS0HK<=X+O z%?&@aI@y2yEL+s|3_7&%KjO=WZf8{`inGYy+rFE4Y5TnDvv=)!AnU*(_1eT0U%8@} z>k@PL_XVz{as#{E!k%9jJ`nv@>~lX1o1;s<>1q1q=)BzzmV7p6qeig#*=|SO_ve=j zUpc=+9dBv899JCfk{n!ghi3lj*KT@@Fjk&>*n7{qke0h%JHgu3b*|6mJU@N;qx<|D zw9S#%Hfm?l;fQILzAj+^)meii!`u zjHoTm^WP+YRLPff7bkxHN!S(55BJL#y-7B7-!eZYiaz($d0ytD4$)MNabm@_(zl;F ze>vuUQT5rBl%M7-`K{pDlgY$ZvPqXXG5%iu{P+0x9(w$V9qP!AiQZR6I4cbe=PT1{ zR=xJ#g9+ucmGw(Lf8pRI_T^75eEf=h&fRAB{9PUIY#*;pf210Jf5|*r+^S6h7fXAk z_iEB>#`#a19$VV>@GGMmUn+lVw^q_BJG^;8e9I^Lz7Jnr(?0d_g`ZA;zbf+8jL)`? zXg1mxoah>S`tZ_HjbHZS*{|k|%zga(=(^Db%)3L5bZ$Cmh+nqt)&8ILt9Ra6c(eG6 z$3EZrj7js?`)@DX*c29(tA8kx-+1_|zg~ED;l*WFOTXW;vb5pTU80P0ar3r@>CG#T z{QhDML)$v};Lpp9wk_`&qN<)j-w|LddvTcX+-?XA_Z>)W2G--a-;=`wIS+)76 zs8RcZZ1;whI^9k8+_JRekw>T2?<;y``;s?Bk-0ZtaoFEj@WWuv?4J3e)^7}_PY#!V zyUKF*`9oJ%-eKD;dHU`%7r%d@VqWg=-z~ZQ*yRsqO}2jYQ|{agZ~d`m!MWKxd=Yl- zZ}u0BVUlf{RUzl$9iu-sR*k46uivwrw{%X&=?U9NYx^CXseQR!iUsbD<*VNR;=(2M z_-C7q`t7S+adhvc*SmQ5?qOipgeK5^j`dQ%;-c;SbvFZPFgxcPC{ zc~a*;95U~bv29z}?|*+Z`e98+`>nmp>}uvSyO#d6XXv%B-h4w*wy>Hz>-KLBKJwtW z_JPZnJ}#X1<{gpnH=~!vS+mFK)qiYSo}BfwcX@$F8~KFx$D}{!E^2u5*G;a_m7CtH z+4$b>n(2AlH60xNh$63akFnqUa>Xp^P}e$4c~Ix8_(U+U#D2S%y()}@|UsEG5hbx z{Htrp{LkYTr!=R1{^nb6<=jS~0X4Jh=-@AnS+uCbC=w;(@`T+1&OnI%T)obRqZx@ff}ew|cx$0PZnntOg6c3fKeTV22Ul{ow7{8i`P zy}ib>;OAe$?^<^F{rdCaf#1$ObawTxTi?E8;V%5SgC!5Izs%XUY#V`Yn@4*sXZY@o z1Nhkc3zzd)np ze)saClf6YdFJ}IEoOt5M)XN`#8gjX4fBw+i^}6%xZ_5z7=vMfMn|GZ*6_v(vgwxVlEuK>p1Vn;lqrlpIC4)!8h>p;nucX+w6wE9miI=<+hWLj%~XC;i|k( z)^2QC%6_gYPn6%b+;ZvRj6k7jfnu+4`CRVaUy9d1c_%60tzPne(}(%dWn1rlxc2t1 z8)KGT_AIM@=hvr~O>$Fi3w*kE;fwxfblpd1p?NX4jTg%vocGM7r;=Z6**Qu+G--ZD zI}4U32#f$yYBPT(o9^R`U2g*3ajhDlSx=SrhL zSbk`MUCUZwvZ?p{Fg5l5nnml|s=lsshi-pOB<;NN!q@Yb?EUu2FR^nJ-Z?LJ*L5to z@8Ba_mRCGBrQei)4s*TPwrJfO&pbP~|6s`%pMTlMN`5`$Bl|rUKB<;2s;sXm9=p?a zU(Nb+3;zz$?BA!qd@etG-Qt7AUnc&oeeGJ=55@yOIr~E-oqtaJ_0)>PE57b~`+>Cd z6;nm&35*E+O-lk>7xnb?!pX^zOAc?jsbdRqsdVts`Knvuwl2B#XZWXeRDUc#JAWmo z{;?N3GT+a4%2ynJHZfsIy}Rg~==G&P{pnhK=N*~y`A2WlIp@A!a&`Tl?`BUG4z0Xx z&jWwu?>paL{J`gVDUXaVxz+LViO`|>f1i7KXUQJ>y$hPIeSDzsU$32b=#!}r=WqRJ z)k~>=T?-qz_pUyA=O6K(rY*gjb)xeAv*(+J);x4P@+5n~{?P-kk68zH=FZMeyuUMM z^|cQkyQHo>xq3@p(b!9yuPq(^(fRRfm(G29=m+IfPj>va;GV1NN4)F$|2|@=XZ(5a zm(}MU`k{$7K;IGl+v3B24wo-Gu=3sI%TKPcy#KrIrW09v7jE->^kLUsn<|!$ICeG% z-kT?DsvOcMe6{Mw!pf(1lk1lwV&AQ6ZWiJ1K5~g6T>ZmB@~cM`ywRpU{Z)Nv)2D~$ zOpTAa+Jzrf_ zyyMZ#2M_x{-|+q6uRmP5^2>9pL^k%QJ#zayH*c<9vUKU?7w*^|rT*h*v+JggFXwOh zA^qS~z3^X^-w9%(7tmLspE$!Q&u9Pf#oM^E_+sot6IUu^jbmN$Isx$#VaLEi&J=GvYqg2T!M7cyf__#jD6x z+4lG~_K&jVi~gSXXsSs*Xxlq18K)h2&rZKjv^;F_?yA9wd*+b44BJ0nr2NnpBHVxa zV(ycx1#8$lL*(V}t$2EXxt2()ee$W(Q};c$*YjM&$q46by(t-g&wgg{+4o+}k)D$t z*S>$-YbWz2LN<+<+%IfimNflm&dZ%7XGPnFpV@aEeC8HZgQ<(Y9$ReteQ?*=ZCPW? zgIs@3;HmkKT)ShAAo37i_tTkqFD#E(`9NjdJz1seUUNN?c;U`P?=Ii@#hs)>`+~k|I@`y7@y@*MUzA zYMSeT1#P#rHSgX!@$s6Pp60sfhE=bR)7ZjKFV?K?_~qqA<)19wuVs$jzT=~VFFts5 z>QZ6tg75CR$#Zw!W75t&sx{TGzrFhHbF*G;-ty4w{%?C$ADFjj*`nmSY1YD(d4GNX zi}Bj@yoLLBf4ESdxa70H-Z>h&?!$ZMeV{ha>OG4I@+ z>r{Sl{DI$Bgu_Tuu-K7Dmtp!>-W zoqcWR=B3`fC}PLc%pV3$RMo7#`eniL$$1;|PVLyZqvPARp3$dUORp}!xTVE1hxg#a z&t(13`o(Ll zEBLk89rG5i{c!(JHcZk{^F_mgRau&l1w(H=@aSD19~yYTbBPt-nHN^@}*1j=Wj6lk^IhkX__y%bEdj~_pdls627PV`4v3< z=_eQbu>FB0XU{+E_&&#d`r!*F_8#`K=JShNj{280n$L%&h!ooTm3#7Dz3tn}OCL$O zdaH5?U%y23!?Xf3U3q|kjN_TT`l0(PBaXdkN?G7q zFeiHD`K7%tC5AN}kj8#t{-UMq`O6E6y7#p1So7)I+wMvJoYKKB#vuX}&}*Rppq&n-ON^!$Re?=_rgxwd}cM*`ivmu?E#xM?Kq@`iT_ z>$Q72mVdDGLg8HHie>BdtA772_svD0Z+!NVX{Npt`|O4F>wgFdiG1zec1a|;=&n=q zjh}q=%6)_PesJ~4_g3q&@17gV5KS*qw3LW5mMn8@zH>G$^z5OK$_*dQfBgF&*KWD@ z%E@W#FZU%@RbTw$$0LC&Pnh!1@Ycn*gv?)D^Ir7Z%TN6DFe{D-xz)QO@X#M`mX?0` zz&%k?)r!+!yNg$bU!K;yz;1r;Dc|A?4|1L`Z21&@)jayLf${qMcjIE$_Z&X4>)`mu zo|n0sR=OYCHt=55xlNVY^jGt5*|%hcn8+|Za-ePB*S#9a*x>x%lFsM$v_Cxey9MI& z9~Z3n+n2s9{8T1<`C`WE`tL=r(qb1pfA7iA!smx_Gb;RZ z%@Loi`d7RRyT=Gj+%ce``%sN@MYto15f<=Hz)CGLDDyyXloW0Dl2M^o~$4HPI}9C zb6;9LZ{y8YtmT>1Pgj_3^M2ZOV%glN;XdEQOD7kP|oPqVx9Ko-1l} ze|t&$N{C^D>(H^g?`%!{JC<0lG2Zv%I`8js&2gJD|E^f|12=T>d*6S({&(~6UcujU zjsG8WZvhw8+O_`=Vka#jFgB=2r=lPsASEJ*MGm2q3~3;C3$`8$+hZ4YU}Iw(J20>V zyXAkaz1QgIInVQ+_c`zL|NKTTzU#i`-uK?KXU~px?|7@aQ@!ou&iAJ@zS5*)<;3!1`%SsJ$~nL# z-RR+#oL3#jc3XON-Po9pl0Ld)`b3&kCSHHEu18Yf$vHzbm(GZx$U8)hH+IVJk$DPqpr%D+--*v40D4@YwlHR zvbE>joLS8mH88i>wt2-d{b7Bzy(8Cb^SjdH^@jt-$=Y+fxYrog^vI>PWpAh1-fyPf zdd>L`bE`RrdE11HZsMG5uzTF~6WR;DmL(ljuQvSnOYP`IcT?1Lzin=o;A%g&-DLH% zbI%Pe3X2|HBg3}&;lnzO>ffzzJw@x$*>eT&1ML@N>(pt~G5>1j&h(6WM_k(Iep1gq zVp(3$?ESM{UQ6Q2!VWevtJZ6qi(#7y$IcfS`>m_euvyi?;ira#q+Dpw`hMz!c#Y~w zM`n8+tH^z}N!{6BBi(S}o&{yq7ZuMAJ+R{I()tAr?;r2tcP>xTIcM(E`z{ZjU*(QlQ&YG2q0U;?!>k5= z9TM+v*9g1OR+ehpPX`vvIOgKB=g5vOrd=QE9jUWodDaE%nwIB|ri{6|L#uzSr$$FI zO$#U0KfmwXy6I}SuC0k#652`P-Ee!QVeS}xy}L~x7u`}hRj3g$XQQQ~_PU}*W2g9@ zPW8*UzqaM%Pil9%PhVcN_OAZ=U8&x6hqN+ynB_L=Q0SvUuc9W^aGteGd(21Ms{L}t z9GhHJyL!y6DD?pcc2`%+UzTxiY}Um@*;#{@w#WM~U$*W2qw`Z&R~WkL7#~`++sNoh z`11x0LMClIr7k;PB{C*@Nu|!_jpMum1~=+@eXd_%qeoV^OeSBbe&v`>{UP21l@xzy8GYOP$}CU+>;<(d>~nT|V}x zwQo)0!HGJnHI#Cz6e>=?QP^UlTu~CcW`2|;F2+g)gkv>-F88C;RerqkMp9SEk5{S2 z&-F)MqSJ+bykw`S8{_*+8eaI*^ghh=`lH5m`UCQTKDAzAAI<%X*q7*Ni7H|90 z=|}v^E3?lzsIDkUB!!nph1w|9FY%8{+E1r1@ymHi#66t&q!uPSI)#oO$ zhCcJ&j2SL`f01u@tlqb@_raH@(e(Wl_I3EHl3vxN;LD^gL(YU9d4caEa7wknErSLu=e6wgyOzz?O2I?;Bz48lPZw#ni{PjlWxkzobYx6#zvLzr>%4jyki-t=9<*pZB_ZVx7#)R?)B^tqcyeU`FX8{xz=%^ z5%XXA4bj^=@Nvzno_!^*N!zO?FS>5LY2s85ucuChUhc0C+`IQk^QQUxke(~NOL}T= zcx~ACp6>4~Jt`L(N-W zYd%X`I>h|SgyhkKD*Jw}==$P|zRkKlaSf~4tPMAqZ#PZ*rrLhJwXS`t51VZt%`;3r+Nah| zjj7kp7M1vQEmv*s>sT})zF}67=Ut~o%Wk^2T{vd(x=x1UU)MQ(Xk^PePX-^H-h8dM zMpDIOm3Ok2r<(VE^D$ZXy3W`&uZGF@CApf*2W%g2G_BDetL-gU&bl}D>jTftIIVj=`>46-w_iVNQU}e}F(*>h zJ7s8>$E(UWy=P%wUZS1LYLhanb7B$s} zf3mJ~GyeBW*S=0a(%HgCyXi;CWbK2~P6atTZ4cW$Pk-y&;_PQ(gNwVxHq+exc9GhW z0~HtJQ|E4YIP_V=7V_g-zOUER9pQW6`kh-3=f7z1Rj+~W(+6i$J|25EVdH_frXAEY zjUGfEIg!==(epmm@-2Q^v&$nsZQDCz!jgWrl7hyOdf$vbevq{tlA1uWa?tMy>1F z?NMK!eE&(YiP@9Ky)`omMzp!uvvygFmuVlCHMh~7mcDe`gqL+^r>@Pen@d9N%j+*`XFh%2c%3r?Z{FHzHfq7S3ANkpa5HPPzwVP0 z)7xt>Mzfff7?+932$fV)`z`Sb)LH|%w1Xk^tuVYEAKY38EKF)zG;Bz zF&Do}=H91g6k4fkBt2a$|6CwFHRzh_F0Y~5A!Aoe2y`lnFYXzn@mT+szFm=5Xqx)~ z*X`NzF6UBDsExRz|7`M%Q(fWkYkcuqgjx4$TCUc8 zO`JlT7{-M(-d?-+#6S8}nH4rN(^f-ejcIH>`3s-72fm~cDrQw!+r2(twZ+iXt zMB8!lMiG-sv+k%*SR9Z&q~jjj67`Q84{7(hXf`4GV(2>hI$F_ZbmyDcKzD3(*u7*XcblHe4=OyKp zMokQAE~?Y_w|L`ofg`0a!n?gy^BFh!ZE>d+)n&^bKDJ+Jr9Hp(E2{yTUA^|6UR>@G zG*rJ+uS@${tg(7#w|~_7{?qc+G^|dgScDE;Inpw2u4ndUty#In6TY-rc&_}bOQ#+o zWqL*dJuVpzUb1BUf_T&Swr$kzN4529-fQB-UPlhRPIt4{TKGAq@cDrCDX+3NTLfu& z=#3dT+pC}d+&VA1m+g)Xn`u1VH$16|x@%mKZC$&v_j@#V8gHxV)?Iyn6SsV~%Nc8R zS3bCReA=q+1Db|UbUq^Vg_j3OhwZK^Y@}YGzRKG6m*`?Jtmd_>o zva7KzzHF~>I#utMRDbgQCMUflv**h%x);Vz2v)NyY+cz?@@=GA-H?(_>k_m&jD5Fv zK%Vn*t^EE2&+CoU^KZ4K?c}nPXRf4G5A%|i8>jweaid4wvc@GfmfD-yywI$-f3Q|c zgC4DR)|xcRLFI{V;LEvJd^DeLG4ZWfK6-z1<8zrRCG}5g4VYsdV)ABomgeXSDg!kt z!%hv|)!)i;Xm{O}-ote-o+>?nS+uz1j?q9A00eN9e&$cpmFa0oq>l} zY%;rk_~Q|mM>Y*d7tX!1wX3mXH~Z}$3Yu0i40c!X(wMecb$S!|T&Ix68VNI0u3pS4 ztm^nApwqF{zv~?QG_2s{j1rF;_i^=$PH>~0?YmxG*k}HPLn)`**-qY~{%}HhX^ox$|e7=K`6W1YLtjShR?X|BIya+;a7(JBMK?$!p$YeOrV&x!kTE67KEQ7g&fW3!VI zQ-%)vJnO+cZExGW+F!nn?cC*7E*RZsVNu=u)Ywx*JzX<+!(BW zL$bg5O3g3s%|k((EhK@6vct<((DRCh6%M_||aix3c*OJP0Nl(YJ751rwwVkR{!GCj9o)bh}ojb0c`dEEE1oqA8V5t=3M zZVbp!w_BDudE^WWtr*>b>D^DC(;nfkwW)k(d%YnGW}kgLth0f}M&JB1Zw{N+kFGpm zwXv>x$PHt;YTCA4XWQ+Q54{nfz2?G$fRX;oS{1MZIIQpTbsNLS3Moqy7N_4e{#peu5DyvZavyeW-=@#A4?}BZwgH4@&Z)n}@>i$oS_Vr3%X0dAX*lC7^ zAtpN-rG@T@kL&A`YCBlND>3cC67*DcQhC?=WG91$J!=Hq_FX)C(X?UK=|eT|G@2cA zwr;Ad(*27?;YxGgdK!E8R8u?Ip<_rp=aQ-I78dBVHLIaDwEmga<9!Ea)jn#|$a(O{ z#0CkGy)LG#86W$lP~CU#vd-NOI=(tFA=E6dT`e8sD*1XQHYLyZCa-mf7(c8}`1j$9?@e#%<)j<3)X6VBCbb@M%U%DE7yGhT zb*%nzYZr&l8|(B}JO1Q_&Wtmiw=L~kec*!AqqO$iU)_7)LA`RncVl!tJFU>OOMQ~l zM&Dw6#mC??{m0c*bMuXzy-oV%YNA7{gGW}^)(XBgG`HMjm-^TvZ{*uU>glz*WPH=; zwW&_44=)R1*9005yno*3wf$8;{dTkTi~CR2)GHfnSaCS;c3Caw#v7_<=^E=kX_okY zsa}}xMAP0S(~UG=Xv|uoUt`0z_3!66er=?AdDW%CzPD@6TQ)aCGPeD_hLdO1o3~J} zj%t3DX_8f4ZW!ha8M|M#;(p|u+r3G#_1`mCb5 zx{-DIn)b$n8Xfch8ti=g_T!feOP-8+eNuhU;`Xa1hW#dcespJSoP~>y{)rP8Uc8W0 zKFUdO8P>kkprUW(y|*(WI$Aw-Eh%jtrM||lV_*3$hcg}GfDs1^^9%co zt2}Vf>1ArLHEL1gw4i(Hzeg0@eRlbyTFkCmtLn$~2|1sg-rF?5LEE|2#NczqZ(7%! zHD&YbsL}edzgzJ`FMJ z{dH0M2YMwF^&>Vkxv)EVURa0nQ5I^S%o}I-Ze1&JylTayzAM^meHdgq(>k+GjijoH zmUd^m=)JqTc~Eh;(_Uk%9o;kH@B-tj@qvCl-sG8OM9&LJYj;pnZN$=uWXs#xL4)0% z`q$o~Yth!iQYsU#{lHZV(xk{#$?5`iA3L;QXu&c01al;QrKIK7w7 z@sG;EMS#MUY5ayXq7rrK{PQzc@L*Yxt{=f|j-?TZb*KFNUTk6tE)Kxyxv431F-L?A z-lv3_VpzJ)A`;hs(9pqnua44t;si(~sqkySe~aMw| z&VxtiS|`i0(sNRA-AZ~|Ms&JFvH?C_o2a-xr3B@gDAWA8j93*hU7v9eF`eJb|mFl-s0>Q(#M~PF0sQK;85VTx=s;vO+Gdv4md_6vzGJ4IB704Ux$?;z!7Ez?G!( z^k{Q?N13C&rCqe6gN3cVMO17IPAIXnlUYYu+C@g&I#|bASlT%_+F6<7(z&!qTxm)t zD@O}gHu9wzxSCIH9)%OWW#;%u=5+3N4xObQig89@oT>P->}cG7y7n(2CR^HG>gF2g z>gF9B_TQMGWWK!hA*Q7lrXe3EMbk3$=V_6}#L3#m%M#+^)1}UQJt7y)q?U@|=kf50 za^b&h4;SQ$*E3RUG4gb=pct+Z7OZNR4n=t$zWAm}<-%Xe*HLcvx2Ie9)ITO(sofh` z*y!{3ffUyT97lWtIteMCuRRjwHkw+J0I)W$b)W>Za=Ecm`5Qir^7~Y#_dNnU71#9O zVkY`QMoJTL#qwYH$PRD;B7VeeBIxw~h(3R|hWxN_ zc{5fcii^R1X{||w;`%I^99PcB6eavvqi_j?@K>6GOV)5y}&G{;(-nwG(DjVxrO|fv6XZ`qV-3&#&dd zWhybU@P2Y!6mHoT7xY*-2q+{}D9U$Grr)Td{OFhWU!mMe z>HT*ow?Ww-pN1|-GeK-KTfFXC@wvMR8&?yFFMQgzDSTJD23#5-`~ao!f4nL>ig7a+ zkzpcT@*!2}F0EJrUh_Ng#FKEe5IE z?jlItb#>h&LN}eOo7g><=_V)>TyRFDw>}GrH zK1JwWlX}<+-D<%eRfO)eaUNnfTCs=NefGxVvCwU1>M3@Yg?ly>y2++_irr%+o?^Gy zH&3xU%)0Ykp&Kl|v)KK$ptIQRb*{76-BrVDnb6JU?DatC-b(X|6uPyRd*uk-S=YUM z#cnKbvHQx?TkN(P?A=@FuG;7=7rLp+y}5hJ=Qp8Ss;iIK9W}~F?1tLuBX&Pk_?#BH zotkxV6}p>xcKKcCW-92SDReI#>LPY4ee5E3Cz<<--AK{CV)xN(Ujw1r=#1|ap}R=c z@2${HV zv0KMCKIVhfnxVeaiG{O^EPmh&>hpN zYm(3n6VbIq=zf{rb)eAga;&S^-69DRyIE|4#O{@ZAhBCzVUVHFopL^?nb3_=v)fvs z`=mp+x!yjy_KU9zHEw9rj*qnnY~JrXQ-i*ybayF-Qq4-vXSHU*2_ANPZ~+oStK zp}V7N_r*dt$LQ{2_r|X79ffXsBp%aDfh|_w?j$~vAbbO53!r!at~Xfd!c^V zexX~zElliA$PN>`5!Qu?-3NEVxZ9wo*j?b)(@N+j7~WIt9@y4%fzU1Rw5Qk|(4?2x z4G_|6qLBWd*h@_N@9!n1```8Q6Vm*xds_(U{m9;fg|z;R-o1o${_);9LKVtT$-UokE3(pOB!r}sT6q~U+>J5)%&-|EY0_wZgq zy4@>0O-QrnhKuR-&EYSFwEBbaXJR@%LQJCvMTqJ1G3a!qwE3P0FT=q_LMqe-qNzSE3h+Y3mpGmuAMqQ7v~TT%^+LMWB|%K{rYF1< z(z~k?#I$Z%0;hBPiD_K#esUpwJG5U-A#J<4Uu_{>`>-FUY5Q9U>Dg}m#kB0${$e_I zZ+|fj`?|lFer=g3rd|6ais{y=iARJq>(N9pz4|q=sgPE+N)pqlu}NYYbzV}ckUl+| zR98rwR!`0s(xvT^#Wd-FWHCLuELlv8UQ2ExrbAQ2G^j_4nEo7?;w+>+H>8{t(w+BG zhKgy*7IF+faLP9AVqNK+mha9v1G zz8}y{NK2ZfT@ccdQEB6ZG~}$b)k6C5R9YP&?N~)FrW@_#!-O zElW(-S!Hh*(sXgzVtQ_VwwRVXmo28_YUB(T(s1o_EQR!2T8@}@Tb|=5q}#6Nq>5>_ zffI%Fn&-e2A+0uOpqNhEIB=+tMk^o4>9axCgtS@UpmRdHY}6o6Ax*Y(kgt#)s~8k6 zq{W&I7Smy2gFgvru!6y2`s>i(qe9y2<6tq}Wj;hqb43rSEu^<*4-wN^r-y71(pjpx z^Mo{(eXgpIzDmlyBc!bs=Ux@kRhM$bG*!K!VtT66P*Wi-l{r*QN39uJD5Rlo4^=*v z>x-Q#<#IU67Dk7O9BW z_$$gsP^J^36y-B0cTg&yK-pTU{0GWTO63++ejdI9%FXe<%Kz~C)78=#=I_2R3IET( z6!UXA@)hQo+RFdsOQKRmwQ9BMHEP!40c+Q(Td#hDf8EOe^!~pd^8c52IB$nL#rSj! z@E22fibMb%&!56PLW)SxX;8U*24(V^EmxP6f@h&2&^ag?QeIzBF6cZ&xuE|_J}xpd zfzk+cq9>Rhx_2LK4=4wy($phv-0D*=zFprrJ2Tiy zt>X5%gNiy!@jm5|mCMe*l<5Rk#rw+;Q=5wNYQ%KHta6!7z*UsjAU0Jhul=RG5@qG# zsjNJ{-+!rp8)Z7dRM9?_DRH4(rhJH^T!ENQ*i|mKMtM%o@Se(A^yEsivaato$XC|I z{ZYr2l_B0qNQ!#`%JGCV%IMJ@46_yEvBb)kmIc>ny1|iqk8Bauo<+$?ANk=2)h4d4mD_& zlAM?$jl~_N?izVFQO4eVB&!2%i%gFlE{1ZNf($rE$Hu8JYQ~(bIZ`>eBj+a>&#kdGsFDr#7kYqb5Y_6!NGJoxo1*(l{uldK6Rp zw|-ZbynrOD)Fm2F6UYkkfI^`dC=D6|O@kIdo1qfu5>yU-fQ(kFOG2O&Xf!kxnhPz1 zilCEFIrJKmVBTrb{DbTv52!oTAIgI!K#QPt&<^PTU}N;xxo^JXZ1y^JVOul@J1$Ky zPZZ^RlxhBtg6Kv2s~4dE+VmhrPx~V^*Ngijb9#+O;eP9 zQ2Z3A_#eDzyZrz9OEEthV}7_`Vw=Q9CSv20hMO)@|4HvAy?ghL>K)rVxp!LcbiDru zM85-kosx^tCFn9lD+u(C$BSDAbW+LT4Rdo+QzLWW zhbP2fMUB7m6x;|cF&uXE2#fFfO41-Z;pmQIYA<@mN!%m&{2jd4uVq z^^8~tx7)-fAb~b8==F<5KN@=Iuw9`os5l@7k?3wx7y62RZWs4_lwt#xB$t|)I!S#| zht_E_P!*&D8=<~CZpi7MlJ1$1E)+v?uTOs&rk)I;&ra?anJv5}ucCOVs^SP1jr{Ub zSXHq8#!LMBOPzmH{Co8kBl(Ml{>@u|QTu;y^}`^FW$`NFYtVH_*}sAEP3RV+ye#iP z`G0&hZql?_^AdL+X=QC=YiIA^$U{a(MaRfu4}R8k@4$p; zI-h{dKnlSt{#Lhn6o=2&7ytV{c`Vc;CF+C~t-~LR%o^bsZg_DVOOOnY?=# zHUf-(+yvU0#4e!HAqoyX zVldBpY*m-^#I$^bXg({-Mwp3hQT}B$jgu^JE5@{RbNZHPE&T<<8>1fW?}b9yW|650 zW=M!2R=h{YjkG=YNX9-iovaY7Soh-qE0gwp6Xos+@_+p%-8UCEESAND%QCTD$w~d$ zz^#CX3_|s9;>4*Tu~?FDZVP>lF=;8Osj?V1>_5<< zCEY?)it`9*TiHg6HjHTP{JZ9yzhedY5&|+o|7Cc#d8(tunu>~ z6`P~;XQBsW;6M^bdTCN@$Z;$aLjy)&I9fKlr3>TdX`*HOPh-LACXEhyaY`VjPdKQT zNM>VrI}ES=&uTjCGZU8HEV+}pxunTX@m!DL>f$_A z%-in+21)G+3|b5>gFHP5B0N?(E?n?(Wg4d*^VE5D)+0Zo#fW z!LWiod_DX;f`h`syLoi$<{jYg>Fw{`&Fin;arf@#<{IRVw}ckLJ9)UdcJJm99^e_? z&AX?EUx2%}r#Ig1=Hc(|5ftw2POC~s*D(OUM|vd{-Q?z1pJC* z$v^7gGj`mK<5ZfTU^7s0PL^Uh%e2IOZ}DCgyw{c+XdY(PJjtwijI_CzQ*%G3=G~gN zwT_kk81^jc)9+o;mZGd!h80x!%FA2bJ>t2sydT~TCyUXZeS|v}w_y4yx0bqRh-oOC z35ef6&VIvPcK`Y6H#(m3J=n>xX=^R+WMJOPXzgL1ltR5lI4>B}7LCtUjGaL`cK9(- zW`8+Riuy0OzVf_{sjg8=l?>2|H;1|A^+rG z&mRBzzrMZy$-juc|M|a|sDJWLSiX^eqfQ>Ys`EYpC0A)>pB5W!ZEfvHPa+V0WV8{) zZ=T-bB!c+h33{T8;&;>(E1~*mOHuaVvhs0QIo?O&!c%w<4K?~>d%&v{x0K}AggE8> zx_`BAp11$6_s#EP9F#}I&sy>TdI$xoin+o^h$E4-OidU6n$fwzNT_g0c+3JmBo`BL zesKoU-dq$;HEvQF;XdMK*j1sEn(^#~zkXjlBKqt1kT#A_iTUgI_>mAl zj}rM{Jle?YuiHd^!Hg$aX)5{N>;GaH#k=B^(9m>q^vJ|?GkIooWCkV=>0}{=6OG;u znc}mH1=`ifkVnTPoBjOe_j|O{oWi^@!yY`Q8_QP;o)IKkiutW5D`xojvhb*g?osS%ylF}dDKFR5{z+-WU zc8wCo%+`YvcOqa6Pa=li#lnt_9@ zXgU-#OHII0+s{Si&n`?CdbV(gm?i+^Ifo)Xay*h~X&52x`(qxOecx3z6Mqapd$MeN z!_<9-T%2-UOv%p%COSO@rrPIS5%PH`8HK+Q0e@Ev2F zL?^-HsaEtBKE(IOPSIl$Xz@?RMLP7f02u>k*9!y7FcXBQ9msL;mXnzfiSIQTKlrSy zte<9Xwy=!TQcFvRS&H!F7kRc>OtRc8m1Y>ee8tZIUl{dQ{iu|UA0B;B@eu_9fx+0h z?AF;MI541FaA5afckFludw6-cx(5fjx_P(-`1^af1qZqYyLtWd{Um(6KRw2R4lekR zgLag%j-amt>%nc1B83$ZDIAc+CP&b-8PH3mIP9bJZ6Y=SDarJd1-eE;#_Js^mXC1k zb*CxH@sXKAVwt>1^vfxHMOk>rknm_9?8{~0ixQtO6oYFUC|x9v`SFMv%B=pm!hdn@ zDE1*|92Kt%(89c-ePn!%=oqHQ;vsvSiMfJJmQsftEgb*Fsp4f7Us~%D@w|p~OcZU~ zd`s1ZbDF$S_EakSqudE)#WAdKfG!-KJ4uVt27RthLS`H9Af==9bVKUBRvddEdH3T8 z_D4hyJpBG^{J!6PAMLuMU18e~W<#{?B~eaDxl9>R^4cF$mka_2Lqj0Sg3`YGKbs;& zh6>|E`yObI@-dX7dJLfpMVO{1;8W=P=Mv5;5f0RmVNXt%V}9Sm@N}F^eUOx|P(B9w z^I@@*RPDIfZ=o2DhOGyMB%qrBm+PQk0DBs^T8S1Q_+F?_?JDo9JVW_8q}X?P@k_Y^ z<(E)BA{QhtZB^6>9bR^HG0cX`aem{;Ge@C14~rf>)*Q9Ge7 zoJ&XZ-4;^3{>J*{@2AXMvm&uhLBD#2@KiK@po^IJFcF>!COnNy+(V6zM8YbW&U=w# z+Gt-=F^x2Rl%rIXgAltw;`fUkQ(;e-_j-lQf*2>n=16&i@O>WqScJqAe|Q-Y_Lxzh z_M3SSoL6;H?DL`Jot6;Xt6iKy2Q?`M8rhQvikF+BZdXn0q2am7e~ zttAzfW#LoJe=G2qzukI`Ckk_m4^?)xd*gL{L`4}^oC!?Ir~rJ#DhjL z^uHTeF}+(cEd5r*d8)WfPyBf3<-=9Sc%&uXpMFZr3n_*>^_RmXro{2AQ6BarhHZ@c zieal^Jxb$L{@wef#AIN*BrIpjxq=UR#hEmCl5T7YWkiJ)iNhInVY@a0pN-ace`Eb2Q4B+U8%EI22bpx{ z63%@R#wGs;lPT^g+`{y^VjTVuty{!)%@Se|E8c5|SPK0oBU7BvsMyZadMgUDw6ezq zjku)IHPBnQrWE^jLM9#uGdOHU+8#S@$jSd$m55J?MCKLAcIu{%!CpOu_#8OJitZeM z-8Et3Cyp}+&pM`+(Jxio&?6X=#Y{gQ75TkYr0i)=qPuYEERQuUr={>%#+*3uY&=YB zH%iJYt>e;=q84-WQZc72&OoWRJD2GwOD>fJo)!Du`-5~|!yu5h$<)tHzZ2T-RY9zr zV^Bp|`SPg<)bIw5V1D}k{-b9= zV^psFzRUHz5lfTFd837 z7_l4&h(A)MlG$k25ABvBKhhXfF1J-GU%>m)U&_?4@P9NtIMfw8xlGQh3%@BF%!~SH zUr}~Nxsg)26Uv54WjBneWe{qij{F*x8-ULFg zQi~SCCDpjQ9?s}f4BMibM7XbjqHK>ctrPy-R!VFvj&7xmo$*LgG{r@+wI95g|{TN@JG2^8)fo*@jT2Gc)t$f-$9C3LI3A{pRF#w(|S;Sh+g!&RlKlsjun!S`7)>L_4}{bt}%bD`q{iKlH!01 z79|B}3$s8n9jrkEC1xfj$>XrnckSfn?%~dqg_f&Z1Cf(2mci9mhtBh@)6tnuyW$`?ZzrAezhc~zRzHOph zyg6sX@#D0)qAm%DW6A$KGKK&f$#1 zE?Ky6+CsRnGbY?wN$Mu-XNvo5w%_kCCDFJhepcEKQ{-71;PM+4J?6KUy$+Mj$)_{#O@ z`RvN&Uytu-KQtmEIV&wPH9VbCn-Nm#7L&!mKdrANoNILwZMgh0ei{xZz6dwmqPyAf zvS&I9TxetKTOhr=yyV*I<)^p`;*iT?K{yi5&5()L84)$ zEE={k)C8j8nnKMXTIbUCjJ9#)(|!S|I@E?m->n&oeA?!lL*!dPj!-qoiA8=p$OWP{ zT_HMVqtDq3ih#(Ega$(74}x+b@`pkrA^KeOy^!d0jfNIN|$scM4xLp z^gBfUDi)1vHM9mIe=UpJTnDX($lm~Mgs7cO&}OI_w1q`=idfVpQJrGwB1HZr7WtQiMJCq(`(a6kJ8z%%TZf@j%32i{`8 z47|sFIrxnI=OBsNe*spqPcu6OqGc`>ltWY}9n64e8ZyBw_6LGPIL-x0RA(qiqG3mZ zqu3t}lE@zelBoVza6J1HK@!!O3@+k$3HX-dcOZ#|eGd-Df(?1?G$CpCB zB^bhS7&wmO@gRxXnFLPeI3Fa@@=yShXy0cFxCf&4_ktwy_kkqxi$N0k`@sY39|TF{ z9|8}ve*`3ve-tE9J0)Nx`}6~?2C2ZW4%UEZnrnh2des8y9%D4kbo~X1UUfkdW{0F6 zNTOGLumMEvsDb(rjn@G5f#}r*3}-(AOn_(_`hg@GSAQ^({Uk62qE{+NqBiL{^M9~^ z5xma+4Uj}_-ULZBJ-5Jn5Di-n-iPS*0DQ>)BalS(AA`@>e-6HY=v4uJgfJT=pFlO5 zV7$~p61A@ZnzL^K`mo;xT*m%#a0B}r!F}u(gO4Hl?w)|JA!_pt_?G>5;75q2^Aq@m z{jVU2>VE^Zv7x1QbU;0be0`8az5&>j{bnGE{N`Xw_Dw(%`KF*5`*i(H5B9^rUhMY< z!`Y7j`$6>T52mo63QlG}A0$!x1>h8jzPqX5EcR!E3n7~RLhu+wujAlt_V0is8ul(& z1wT@%uL?Rs^qY0U9}@ZPKob5-+T#z2d}q)F!heY?{&+#;dxO3Z{!9GuheWbSXOKkx7w{|l-#`+zqk^5ADiFo0APH|ts)4mR zZUCxrtO=TM+zPbd*b21f*cPSTKd-RB!-9_0zy~_A@{d`I#Vzh8+eDXMY4pB7YP(iQ{~53dhsHl^m}E zH*mZO+{|$ixP#-}Ac@Ai2P|fPKX{n^qhJZg$3PNI!wK*t$EQFNwNna`s7@Jpo8vp+ zU5M)32P@cr36f}e_z03{*iT?3MDnLVPnB8_J@Nc@<)IqYG)KUp5w`20moCp863|6f8%%_NTOjEfD1WZ0xsov zCAf;?b>I$;_k#O4E(S?7J^R4}>>mM3I6e-ZY_=@A#;0K7d zeIG#*EhnEq68VY)9%@qs2R^D0wNn)&kzWn0%YJ=OjbjbagyUAABggGQ5)JDNlBmrN zpbPu1pga4Xpcnf-pdZKG!4QsnfIT5S?I0B61I0}r0XuPpt3j0|giRx#Axe$H! zp`OE^9bp5XW-cnYHW ze}EU+zX9IlxD33_@g49kMD_23FW7$xzUG+TRDq~Y75u3S(Pys@s&T9VI&s_%Y!BhT z#2J5FAgb>Qxlv z@#hvqb?$>NIDQ4b;aCL=iv>h=EI|_9l30P(?Aw4&>~{o7^jmWQJs}#lGw2V|umNB% z_J@JPIUWg;sQzehJjWBkB^)mUNmOSAxPjx1Ac@-F1a4-33%Hy8Js^pOEd~#9d;}zs zUjm+nsGU;q3j5c=TO3n@upvbCbwNFd>g$6x?AwAQ^6fwp)wc&pQ@*P1E`A(oS zgwaVlfSn<#;|(TqJOIpvsQyrpM0N7Ok?fBGN#u_P$FM&ZoXGwpa2ES>z}+101rI|s z>=BSe<2nkSWdAgHm1F8uZ39t#GtdH}`j#Mxd@InNeFu<4z9Z1 z?Dqh}*zXDUVZSdJ!G0u|%KiW_o&5}O82iIP5>4j_a3uSqz|riF0VlFQ2_#Yd$>2Qp z=Yu5j7l4P^KMIoQGoAuT6qkZt=p>@p2lV4O5DekC2S}niJwbZHBE=ElSdPbolQ=E_ zH*mZOEaG@OxR>K%kVNeq01tCq0$$|!GN^_QVyZ(={nX}I7wpM#A21xE&l>>_WE2)~}G-2NqY|Xv}Xvwh^NTOk_K?n97!FKGs zfF%0t?w|+9o?sG0(~t_LbDRgR=6C~mjN?-9D#tg$N=TTu`dDTmYNsKn3(@rGf%@zl zfF$yb!0zmafF$xmK@zpo1MJIw1W2MfksyicM1dsoqrn*VWnd!vNnjfLa&R#FLqQS^ zn+J}AsLcX!4#$PyT8=k>ry;6OodT*5#Z^ITh=#QRN#xstB=YS*C-&QcB=XyXB&zQW zy0YI1B$4k1`m!Gg_T#ueD2J#{I+)9T9ypBs;ou_n7lX^#UkQ|_P1|a4 zE&J=ha`x|oBpUAnkVN$#f{)mL3_fN58Azh~&p{H^e*wN^{}uR}{V!l^bhc2PHlQ^` z!`gth?Aw9%?6(8G+4lp-a$ErJ^U1}h;NmO7iX(aA)w> zsRP!7XnkHEB+)cC07;ZbRRdc>G^`0oqG3%z61rw2tw0jeDU#M83A+fAHXsSRe-bm0 zghMb%Tabj!xx^eKQU2cow1H@wWed7NG+uX*M0GsCz7YB0Ac>|Y0*ry^v&4cV9KuWD zKoWK*B=H~#DP2hdNWyNGq#u|9(Xgo?iE{D-KvFHl>0k~-{y=aLgp|ExFqjL`^b7?_ zR6h?K2T}d;-~@>3PXs4JRKEb62T}b(@C-zsy%el~gk`%i_In_zUj-yleO0hJ`!zrk z`8A2`*9JA&*8)k@jy6c5`Z}O4`+A@u`$k|>_M3qus^1(WQ9CU_68SAb68R<|iF{L# zM1CvKjQzGCi9WA6=*Yek*bbul?ZJ-hyMXTOdw|~T`+$Dz`-1`O2ZG`3M}TA59|ulg zzW`KkB92P~B++-I36f~qv_RXYVjVkhAVi;Q5ICLv86b)LnP7`%Vtp6ThvNuv498=^ zxezTI^T0I0a1QcqmA! zhdO!SOo-|ig3CEx39jRK1Gtsr?ciRHi@_rtmw=}^E(I@gd>OpSaT!<%)x)?XZLvOw z=(FU3b2y$0zUG)tB=`fWk2)3LXO4vv3oOL-j0#v4qB_;U8W4S!nxK^Z#-J(ttw1w~ zrlBop&b|dmqIN7nTlVchd-ff`_Ut=@9ocsUXR<#B{0*Y^=YsPfYI8og5EAAySjhe& z@Dux=K@!#f0#>qL%@WfI(Xf`_WR45KeH<5qcOe?K0&HX@);9!=Ao8VP0Q-SpIQtRc zAod4?x$F-GH?Y4E+{b<~c!K?t;2HKy!Axs$yjkEth{ihzyutoW@D}@J;5YUwK@!!c zlO*@~iR~1Fm5|{3V|~3;^b5cv5c#EG-Q}WR4{QLDuLibe-vvzOI0a0D=sS{wB&wed z4q-nR9LjzkI3J?SfiKyA1%6|{ z5+qT5I%%glMB{A%nnC2Z1wEl!h&zK_A@YO3Zcr`6;oxA7^T6Q{)foY9Vt+G8B7X~5 z#Qs)r2m3q0UF`1$_prYgJjMPQ@CwH_!FwE+gCuJIKKPA&Itj@Hss-N^Yzt8xbI^i) zOVEmaYtV*$ThNYu7qAoiZXk);aRx3FIXZe@QPxSjnS;2!q(g8SGn21(S;evm}%9{@?@Ujv_T{1ki!QJv@D zTlU|9B=S{OIa z>`T63oea@<)j&On>Pta$_AS5w_5(puRn+MUlBj+VNFqNNB$3}8B#|Ei_GLdDB++^) z0*quo3XF#6^TvR&?8k#7YBK@s2hp(oK@$0?;5Ud~l^{tKu}US@!w}U`1*<_cY;~|U z`*lDPjkhjHqG_%NlE|+QHeg>3)PrchR39W!eFIPm(YP9eP7rZe!Hyie zfD<9AKM9=7egQa-{X&pLbxOf65ViRgB+>GmhYQf=LF5;Lr5u-mB&u^8{0!0eC{b0B zG=-?X8R)}tAeg~%CO8lh+6U*ezYsjgaS8Yb$C9e30}0ar{?73R@I1$t!Ot8^s;Nj? zLR8-ZjOI869LVuta6HEo!KoZi2WN0R3tYtU5^y8Oo539%?*eyoEUB&{vBQNzG+r05 zJI4`V9LIU!D2~T~8#vws?&O#*G%~8CBCOMlK@xpOQc!}6ifCP@0;)nZ537PC@~eT> z*{=cCVZScefPFR4o_z<IUyvKeyc%S_TpaU*yqVYO{9U+=d7qAi+ z#*j}J$26r2WbkSRwuGon6VM!@Iu@WKM75m2a9m77egv2ckv|k%#r|q=4Mgp%1;4QW z6|7`mf)n)3A)$TH1EQBF7zoj@UBNK+dxFCtsy`eY1yP;RU?uy)3I90hpT1uekVJK= zfcES=fS&Ak2L0F%0%Z`*pI9)3{Zx=d?F<0Z*q4Lp>}P-^8t+hW82iJ)P3&(5x3a$t z+|K?Ea3}k_!2RqW07*37Qy_`jF9jcRTmgRMm@aUr0#SWcumwc*TY|0GZv&FZHv?_h zw*xz~9|)##EC)%{W;&S3ejYfC{o!B{`&+@i?C%3h*gpoIWd9U+n*B2%iP|p(Z?S(H ze8aH>7fPr@G|ie|TaL{^ONfTG0*Qi$PQ%s$T_E zg=oChz`7ha0M$6w1Wh<@1zK=y1zK}#3p#S#4r~um`_5oT_FX^{`L3Wl`<+2Aj=O+< z90!6Vnzm4oL~$Q5n&Vh75u!Fz!E}x#xWL68qWTVCM~LdUfUfL!0^Qm707+Ee6C_dn z&R{S0`+y{>(-$OBop3Oc{XB3J$D6_J5Y^cMl4#hSAc_24;9mCkfyM0a2T4@_0CL`1Us?s29n5k2R+#j1j9Hk1Xpmp3Ovv81@IC? z!(IiiaeN)T1<`oRKoZrt4c=p4qL1x1M0Hfa>JZhb0oG){7Fe78I-nK%)}RCXj-V6! z?Z6J~cLY7z?+lWt{XmdJFmz{N#xH2Ni?omAc_3hAc_1! z@OO^Oa5MQ=5c#jcHxSi-3w~fq1xj{g9!L$ofv0hU2@YQusq?w8&8KQPl!E}!E zK)2?iUto^U4pAM61*X|Xj9oyIFwLMR`<=n=>`UC?d-AaH_-*#%VZpu}M}P?&Cxd%A zE(VWrTnawpxB`3w(Rkm2Bw^b6O26WD4)Oo>^;X_E`<;h zYJejN5L)1<1_Xpa=uLW;-n(>A5kU}95wQdtpdv~y7A*88z1I^%aS6R6Sn&PKK6kIb z|LHP2^UQo_o_Wgc?LN<@HMDc+yeqU<=)4bfeCYfI=v$%ly2CBCfWyHb!Eaj*Id27( zL-1QeXNKf&LFa|!??68d$+My7L+2Nua!4Cpgvue9m!NV8=56SmkX((l)E_fLG0fsO z@|@858_X3PVtad)rS5Ua`2*-*q4R&BPeSKvwWX5R7@f*cIYhrTv`y%|JyZ^n_k|7! zosWe6wbsbhPnH_YAvz$G^N*l%i2ldWPdKEGpF-sj`9kPt9CF`D z9kf&$hv)>L4MXRxq1!k_pPLuz0f%#bnD1f^IWGh4A37fZ9mF9xgQ25C^5>zGIK|$yY*iLh>uns~pm%>ImbPL(Vgx4LAfp6Do(u zH$#tdh_7tuz0kQj%J&Y3==6k6;Be}A%u?+*i=lFe&KJ-nq4Tex z%R}cYp<yKSJdY{hy)RLgzc6xsDsT7aAElkAWtH&htZygwBgWYlP09hSmw4*M&9+ zoi~KaA-`iD49QzT>O$wc-Kud(qOF_$q&I8b49D?~4R1U#W=lLzqA@TsUI)})gg4Pbn>q47^&RarT zh0f<*U|evB?MkQ|V!Ii7EhN7Vy%mz*hCU9-|AopS-&2P!q0b>W*P*vK1m`yNaY&wh znfp;3qLTx?7COHHy%{>c4Sf zbUlZBz7cxE$Sr7X4*Ar9{u`33o5ai^pWDzo9MZ?`LjMiP)h*6BBp!8}-wqt26M()G zI$sD~89HAB-4r_C0zDWyKMZ{kI#+iXrySC6qoA=If|&bh%i@<5*so!5mn4xP7#J{vmk3GK%rw(1_g1sI6*?<)J{S6P=zJ4&N9gt%1yV-?eu?HP4~~gu_gwUhx0t4bLJD* z`<6Vf8NpMVRZ#QvhUfmQ%!_b%c{fKxe8f2K>d40`y-eOtTOAzpaY5#DbmWL24jJ|N zd0yh;9UU6fBG_{M&*&%sm&kcMhxy1H;YC`(VOciBM>}i(j23+qYsry(xEkkQ7yoIF zVw^{SDl*CRL=JZdI~V05<-5fx*UlQkYK~nl=NVQmbY0F{M^NP4e0<1!P7MSx!a2+9 z_^;uVh^8R__u&f6ktE_dn$?LOG;(n+T2gzVEJ=6dVioDEc$Ao9$)HgAxZt?|4~^sr zl#eqhiQN6%l0j*~ zSPuDU2R+8|Bk`C|L2Sj6>rwK$%R(Jbg#inaI-`!{Wfz`+nI{Xsk!Do6%JT3D_CrPNrP_(V5~DE{|j>g;l2 z{7){HKBt~08`2(Z+EXHyvk3l`K4B1yzVrk$ubAE@ zqt&$6e`7`>kZ~k^@_(h_nIt@t#j~lI)Li^XY%(Imqj3N6g89EaLHPeZrWTTO!3@{f z@ba(7!#aXuBtnf>$+($E%;*zjlP8hdO|`QBOjBq6EUJPvw7T+lQRQn9sq90=mHkdG z736`s%?5?w;#|ru^+fr&8CGa3@~a+Yk6ogIEn_YF+ENv)%q+^LY`dOUof>u63Z^|# z_T8e&KF4u>sY>_}`{SG+h*0+A!fN`MSY_KwmA{Zjd3k_fPb{qL?jBX-4&}wOEt^GY z!GF*%2~MLcmOZwx^8Juo+5AXI8! zve!M^3YLDNtW(s7g;T*6#LEj=>^Lx31Z5ZJJcq+W9xkORA2+tVd*Uqnc6k*%NxgXx zZC5&P`D*4-!Kb|{rxSX-+|*u!A0F!1Y!I<;5^F8w$MM07VQg;hSoa27-k&5U>Trf* z2)gAWl#h*c_SvE;_xfDw!mz`Z#SH+vIkvacl=T$!`FP8FC9f6STv%CG!K zj|EWndCv2L!%YSIWO3zhMIIgy7aO$gU&t{JXYHS;+oUDR=4L|h<|WJK$CmwRE_IGg zVs=N`nVaGsZUEU;@K=U7*sS7XGp*eh+yc~rmps`+wTfCpo3nYw_G*>%tVaa{lo#UY zfX_?B(Z;84XQF>6jogCW8Tm47*r;T)7~9K>Gp0=ecQCoTv{c!DMX2btlqccyuQZiY z7_4+g{?XzpSQgo!cx&2d^tTpPL2jJbZ06mQ3O*ZU?8dQ{kB8tk8=-S>kp zkh1GwFGK%2{`p}T`wc#JkpmZfhYh_pKQ!&%B2 zuzU@QT0Rzk`q-!yWFbP3moNo8;NvFxH_}wm7Wh6=K>66*Z)aYyd_2&zHRUX>&1Rvt z%@1~aV7%qyL78=t@$eaSycMB>@8P>N`DsO7S)h7~n^!iA=ewU5@5h-da)0{umqph{14FU!vd3kA< zotM0RluLOtidw-RXtVXibA++P%a-iXpCQI83XhpKUwmj|wPsTj)+ zl6U^F)wCC4xra^t*pMAO%2hQR+=7*8>%7ENhI;%79xt1+e=Vv`^&>A=uq#F$tI>~c z6jfb$VTGL+{$iS%HW{pQwDmA-?_+z6cxqwG!(w|mG4CeMYt*CMSIW!61mEU3%c@Qc zQ_0IQ^7SD)7FfJs%Brf>sYLq65&UUnx9I~#1(&>y1}{OgCbl4Blk?x{D^=nwn@!60 z_`+&H4f-AnVSK!3BAABFJaBJe$4jB?^U#j;n{vom`FDp(S| zCX}ZzH?Y9VW+TL@5n#vCc5GO*FA#HGuk!J5@f0r=2;L%ZHnN%M909*6ec%*vuu0Rt zihLq5Oh?8G0;sH;6_M;;>BY$UatVgIyNDZH%B?hk%R`XVoK z3bNqW$ID*qUy7>yy!0-32pnEc7G$BEw^pPIPQiC&;wwd4u<#*x8th_}zd{}O%^hU( zVDKfrgSZjx>xrKxSFB)6q~+tGZ?F*k>#|%6fd3J=|4{z{_p1A`DYi+wtOrIx6gW1J6_o0VARLYmM!7WV{s3zHx!?#G-0II%DBu z_)(1OF3=tLc#T}L2_e`Rzh{wc2eUZm7ndr#;T7xDNOH*HXM0~^b+spX*hFkw3agy* z%%Ojzsd3+fS()+w1gvLh+iUpel0|aM_t{I@Aj0RXZ&|#T>i{FwgKG26b-dL*6u<^z^gP$12eL3Pd8KDwy zgAwFD!*_f)uvx+8VS&wy{Os(a>f8!)bO(PdstO*@vFzO7+#;6S9Pd+>N?UT1G+2c1 z&3u%xxZKWbR}2^729p9}AQ0;mF+Jq;L%6 z`~?0Qkgp7U%;5JE3&Vn^xW~cOZ&bB7)%}rlqKDkzud4;jUjn`mj?DK%* zhAySwoWN!sF^#0%c?n^VjSBW@Fscr*g1mslj>ON6;wpz5w>CF=Z61CE+tC(x7-wC< zxJX{>5JxWhD+^ET-NZJX@6sY*R5@?u{hl1uMehUZ%0nVMNGvQ)30~y;?U^E0kOg{I zx>0_XIF{3g4>Om4!*>WTo7t0$?`HJTZTRSd&UNM;7C;SY#yrJK27?ulbtRX{jGd$C zEWl^ZQsxIJFZ{HJQKx-!okrbj7E%#xc(Pf{ZC^l_jm5zy%zg3X;u!gEiGDTa7Z$Yo zSe$473dZ50%1TE*WeL~d^p&@=EiVf@Y##pD+?@CEa)V zNmFtg;>tz*HL-s{P6{x7*yQYEfrfpUK3EWWGII=zSnNEUC*f~`wt}CNuUEifL&2Uy z*zwRRc$l(P$bT=YBG|ZP_d}l-{n>-SZ|qf*eqo#xKno!EF2{1Vw!Y)%mW zWw4{@i#$B`vROTwjdDX8V}FYF{hs)FNrL@2O%-M{f!zlTUV>?t04EEKPdIq7rH_sA zc0ppk0u~RKgDfhwkActT5c_xhHNb|2r03X-7pw%vEMmTa%mWUaM1pOR<>z{B4>r$G zCtl2Bv-v8>y;=Kra(bP--MnC3T8Y0=^usdfRz>eyV!KQ~cmceB$QLg<#1FqAkhdeJ z^57!$>CW|Xcjp(V+!dV3@9k=qDU@H;=bgV@=3-N4gnG+)f3%oYa%_k3Zxw#AHisIE z2>zOx2R>7kb$-!`n%tLTM-4|F@O<3gp+9p^DSUUgwo_i#`PWj#9eugqEO!r+k@n(n zJNF^YuYB{%JO&KzErV$82NYDD9E3<-%OS|}ds_B;=i}dK=bj1+grIT{PwpWkL5+^N zb0|@|ojV9Il$-kwrJVbta#tdJN7>vbfyJ|>7{7Ar_>*W1CrI*4NTQHCUGkB;Wu{a( za~D9)@*|Zfq)f6>($SB^J}fnR){TZ}$suvdnbce&@;G-qX;pan#Bk45?xIOVZq6kV ziK7s_$hj^h9{Dgb94gP10d8t|Wgt6sO$L4rfDpM7VY3?xozRlDOsWx!d_yG{X|X zHXqYgruK5bIv(vpco2Cw3Zof~Hxs4tY3|;c`$ZuzCuf2pcU!lKp$-0R)d_PPu+)n>C^_Nl^Vdf)Pk~h(kzG1Rv z+WEh8qnkU_FU=h|ks0sCgW;t{QYWdaL?mq`ahV*8Zx=n<x0Y>lyA3;S5V`W?Y&(zNTlG-f5nCgnPWyRQ@yl(B#V0)btHOG?_9vmdr&uz1++i zrj}ugaD-vr%rD_wgfk*FH|K&ZSu(t-tz^MGpEGt+E)sKJR$`UdO&b_%bDulhHYTH{ z--cV#=otig`e*8F`hwAyf5RU^GIb8;(`3u^BC-Bo8_^ES{_7!-LJ}ZS2248~D|+AMRg?ypqmP9_%7?F}F>8CBc(NoRxGN)dQNTANv15 zFI|sL9+NDzadH!=r8oHxAkPZy4D13gMUPW2q3_irRY$0$pY^w=ytE#xp2u%iplhHT zdXeDoAX6!Nk#!NhIO57r{9fHw^@NYsLB&!TQrSu!fq3-@RgrRwyxm9c)4kMZ4t|6` z8hlG{(5vWHmagJ%OZ=I-xPLBkukNihdYM}LUxAMx-_zl}x{I3Z#Iq%^m3Vx_@f-5? zo>Wgs+ATqE()AqN0qOyImi|=FaqzFIP3T4IKF;6l;dZEB-mhcnc(=xHif*PVQ*P;r zimjc{@#+=$Or6{RrsKb{Dg~bvSQ5C8U!`5vYDa&ls_E1#Q~luZHT<>FkE8!YQExZ> z=BCqcdZ`2GDV@ikmvW`k-Q69(ja3QkswKuH=H(%`MgQ@jm!jLLp~yA;r!>4vFDkYI zK0;S>KZ~80Jf?zAm-V~A^Xg%$o})iby#P(s&#ULKbCdVt@E$13Q>o7|{|fXq?eYNg zO7y#GlvkpEJ_l8LwRH_zEio!Fnt0M^-*NDf`1_sWtU$xSeCl5QyJI zQIx!UIF|Tgs9%a6t{OS|gVZhjr-54to_=8Ug<86xzX<*;?eoV#Gj*!}eW;7^_qJ2s zS^etNv$?7XpQ-!$KX=ME6x-$SCH(W@Wdlq;_*mM_hu=zij2hwCH}*HDyb^ftI{Ib& zS@4!VrVhco^>%fR`dj*a{TlU&mT~Uzaen7LQTo0*j$SJDFAZ)Y^;$%^TkljXbs`U0 z`fKbm^#LvKQ?h8sXVCX)ZLNd4(7#W3(X?kT2X`!cL`83qf0(2Hm3|BRbX{BZOctD- z{$0ow{r6?;J-W232(*$Os-A{s1y%%Bl5dy(zSvF&zr8w(`oWmfF+nbH-TDiC1^Wd3 z6&@t+F{%%KOEJC+5`Pr!JraIFU_@XPIH~lXr{Dw36Jx=%h_fqvh7S0vWAByz2Y)0^rv21$>q0KUslZf!^Sz5dUiq-UHPFs`TF~2u>{HAp(6D z?Q|1yrcUv{1D+z!N70MW&D@XaN13{U{}V^vPvuY?A@eZQ(xv_79eF{2VQ5?Cjb!Ex zi+QI6e7e@jZu0169;kvJrEB<~MqZNsZ-MVa{s&a+Rn|?YMf==CEros^1D_RW6=+TT zncDKdjGa&KQCpEm=p*_V?PTfJ{&vXOE9=+LO!E9UG(xAk?-RF+Ja?m>7V~#IsE2-& z08K&O8yt@wp#1RZdR+1Z;#ABBt+C4rd=vNkO43u_mt9n4Q0v!XLocevIw_z7e zI~;P#hpUEAub!Y@2Z!6meh=lE`Q#RSD*Ygta-ZI+mcpmeA8SLi0xbe9!PDTBp*)4W z-bU`?`{NklSi9$QS+IPBIx%) z;z=Pt8<1xOCI_aV7meLv?A)|V67pz0SeEjk*Ir$MxA=ZK3r%4jybo1;e{P0k1vUhJ zz&?WShuhT4BEMa*TM#%EID_0r-Y=sc0A~y|mHyCy@(BHu`#1Cz{c1DvbUi<5Ws=0d zMz5wm8C+KkfTrtO$@R!nR^UkBD0rEAh<_P!FXJHK#8*!(#NG$K4UNLzDCAjzwt;r& zX9ccmQE1l#|0oTHBKgWNPKNadCA8eUG-cr?v zYUp9q74zL0M}L#vM}Jg0pL5Ba!1qQkAGbcC%A&_34!@i7tiZv*A#gm5lao&WiAs(JU(p_Sv5U|J z+~<%-^F7lT`)KN0+lhOSHPI>0<32*Ur59Rf;A3e&x!AOr=i8&F8J|BPua+2-nA`dO z&THj|cj>08D&>mnkqJ&5odaEnBZ~gg8hL5@CtJsf?^NJ4@=RUM{~>youIq0AjisOE z!cV4Nskb}wfz||YOEDiOBG2IaQ{LH^sYm!%IptsIH_2xv--EBgE86umB${~l<0qBt zie2zt-B1++$I{>E73kNAX%y3pc|VSLG`JD^xN1qcqCKX7EIk`L zFXQPg^xV2jv2IZMh02HhM&B-<{5_6piQdEr@)2NMr%}Fu@$v%o%nIxd`~pqa>yob} zOB}1MD~`X3?57djj_OrtY3wQzcM8{g^WZb|4sGM#NB#~td>Ma5r=AO~Abh4S>tEo+ z*TQNGpRNxj?N1V%_G&aZmGnUM9`b0YpExbrxd!rd{c7?o;z-~+u?hAb`dbdP1J@yq z8TVO%v4L^K>(*V1b$9#>_J2)zD%WSdvDeH`>ygJ}-xzrY`HyvQ^0*I?H>F!x&rO!8tbw?TOT+H)6co*|eHK?V3mu05(m*-U>@H6!o|5o&T^s67Sk0!4#P@b+! zCYK_Pc)o`QQl3h`?ucAbk0>Xe>h52OJCj*<7JMbH%e~mA>*tflVwa*js2AZ=XqSCZ zx31s{;IEzMxMwrptMSZB3Oh~tabPXJ_j19zi6b6ADU6@*vD+B6GfMuxLb~pkJPOO6Ro_DEBh%8$csyzf@=(c)9VTY2Sh1BDs%F6Vwk zK2nJ%6Tg|dkN-2uJzUS%K%Rhq3%(TnqcHxxjMpLHSd6Cw#FfDOSA+7bz?#5X{JCh~ zzlh@m*GKX<39y(H{4jW>zoX?P&i?)-4jttG3hLEet$xIn!SzrDM?TWO)}gWfx1g1Xe>mmo zdUEp1_)FI_lV5{oP&+T>5sZ^%)F)H-^M3)axE?;@|^O)ag=8THU&0Q&j@|ReT{mg>sOLzP_Fb|eF1%o@3)N(UT0MqeXe)a4T`e@ z&jos7=cV3}l)Lo4VyB>)d=I^iU7U;y^yBao4^_l@)rqgOzbn+OFUeL44vL9Tex-mvI$^TKsOAgnbI*HWz#*v%0*y zHHCTij#J*#-xoeB&_6JMxYKpt*-R+BXq!hlz7wiwB+edo;&-yBhO$y$qh}R9i!2&kf&sx zQh6k=Z|e=nEAe~0s^fPMtHvqUja6BAi+RrLwFW1FxZBY_!Aub(Sb4GXEN?S zfKS)slP8jwbUi6~D%8bwauIMMh-)wSCFJ`H{7hZm{}KAB#8sVg7uR10D0eX*vOSYH z+Ne(GaUG`4BacQe2e}Wu-A;T%RUJpKg{lkh*42xpJ9LLW4{jvi=UMRae4mttPoq8~ z;T7MC&ETsgW+cixuj9#Ead=DLQWYt;^kwCPM(DxTB&cTGZg=d~=#l8P^BnOUWj@LZ z91omu>Rr+QDRM=AMng+5-kwL_!*$>Z`bji-O(aei<7W)iqZbzYmhw#P_0M$tE_MEf zqKkOPQ=Z1SZAN)C-^YAdJk9PbDs`$38?BESimEiea zPQFO~6ZE@`XsK5je;Ty3j9UlyTmN_PZmy5YyCklF`!MlF@crEidlzvocW?*#2cxIR z;~obmQ~d(#X8tM%&Pl%K_R%lh#M=Tsf^mC-@{LiuqV^(>roGwfM?IG7_1I_7Uk^eT za2%3Q{t9w0xD|*qgRAG~u+Ivt3arN7t!;IY za_(E|{fvtUerM0XE`j-`5_&1L$M5hyu8$u&c=KGh(DxA6P{+>Z{ZL{z#F|WbEPkT# z6VG*SUFZr$`$t!+amWd-`s#tV|+G+`l$awr@Wv$#+e_!N?u9+Gnwa> zqF*hsR-(LnU4fbbpQ)q#uR}e=oefQ6-pGsn@#sC#^7nhRzN&V>d-y$-8$3(zQW5ac z%zrNuk5_)5LE~so5AiDe--ToaehmCXULyEDy@Xv>;MqVM_!NGhK81Y({XGePk>u$E z_!Qc4FH|$mE;{yi0*}$pV|dUawN#x#`&h2(P_e7ZiCbRbE}d;1r|yTBPs9BzKE zoPcuw(JBC5R$yRY5I7m!J?oENii~UI@$^S|&$yy~Ti`SJe!T49)=zFgzOwkfnTTCH z*A?l=1C00b=y~xo6KXM@u3(-DUK#jw{Y~->>@tY=Tl785kKaQr-9pu&+{gU37+Nhc zSE7gf^E*-vM?Zr8FoAeA_ietRJeB#lG&F_#RrR4+fxdx$#1Wx8xI2JE0adL2uE&qvzIFE!qC3`2LE7cj5OctRjy; zz+2ouZ;yVacKN5l`}lpZ7G5*1208ws{c{|>y?V7X{>G^HDOcoUH}Z7-Lh^a?okG8T z2YDR*G?sdmhF)|07WXHkrF(dr>qtMAzbmECdkjg}>yxje7Xkk_d;;U} zS#Z6&r_~?-9-ZI41-nG%iB%5nvgB3hE8W~`O?d{h%TwrQ>Qesqpy~SU?AM8QwA-}C0|9`7f(5%4Sz@Ok` z>NNkyj(u~h4fYX?`%CC&GENsF&(x9r*-#I1jh#Z?8(wiA>#Cz)$9ot8dLBNS@mI_x z{p+gQ=J0&~<6m>FpTc!Q7s?g$^Aczp-;1N55jxF%oj8BP z&pG@=Fb`};Emn_lk3(*8zxr?NE!we~Q@>vRKG3cFZjryCW`Xy)gR_GfO85)v3-}DK zhB_F1l?`4T{VbBWQyIT0;CY!R-b62x@5_DY`H24*yk^{IJN8ff^0x>qUDzE>9MQzv z4?9IaJ`0WDe&ZYHx#5q)yJ&|C6uan0bCK8bRq<8jcUvOgQwu4NqyBmEueolxgI+A{ z?{@OI%DV30WXfy7@R#H-iC?#_=&D4yhxzpsd?miW8l%Va8-GoBkIc8|d-z^RgJx=v z|5Yd6CTc%witCuE=w}6P2OiN5(cI_ji5}NgY9AykFgox&a>Z3wS?s*DQ!@HK>VM9O z=bCIWM&75`Aovve>3*oDeYRni$-MIucBSFnj^03(LU|?X*95wUyZXO6?b%7qfoB}4 zEso#qfgSiwVVt>FIYBeh%}Rb0`uYLt52is9-- zes8$c4AuojDmULUNn)tJMkaT*zCp))U_cn4WJLpDd>X0B4y}c)d_+T51_?FX0ac!} zq8w%wLmZxqD6dmG7(H1@GQ#0X@^7lM>L8i3pEzi4KC)tk`GaF7M@@9(32=Un1Td4J z=DBY^Rf-b279UF$P=!LA)Knspm~uJ)@|J7P`o#__k^WyfZWYDvx9>_qgbz7CeG zHpr!smZsg3k;=*j zDdT+)9F@>;tKyW)I+-NuVV?JMnc&ng7jfi9Lq5`Dq_=U!=A6lDtnf!-k+`_bcBsgW zmFP%`to|~6%&f)HPG0g*3)5F5LlT+v1F?;FYVM*Bh^^#NvTSzHhOr&hP;U%pL2@MP zQsfhdwd7AYK{sm}#EZ#*w4B5imi#xWq;<@SIq@u)>|VO*5vcD$L@d7=^H)J^gsNa9P@%Za7Q zlIa7v(35dv*0zM(!-JN5VjT?Ap9`R8>fjVPR?-(W2-1>Xd`L!RteBm%G8QCzvMSH? zM=6&aN$td!$t`bgb1Y5$q@5&c^N}`{kszONTS*TQ&*l^EPho1>MSRFdG)<%tuCpe8S#L>&p0-kKh=5GY?2Tj0ZuJK4jWH zyiUmEUUX!p6D&_?lt|8{CgxK(e1czzMQR~&399)>JmTH-a*@gilooT*BBE{joM@QQ zB&8-^)2e1&o77ciM4<*SWY(+`6pRF?%zTrWTpDD_w`mQTx#VMF za8CJ0R{qJq1wz@Bm`$BzZK`OSK5c3$Rt80Ej5iruA~U_h#3gf^)WUpBOPSFTmI#fY z4di1GW!?zaRwQ9-X%Wej$c(;RC5Ue$lNK@27z>d~jU}FNyNM6;4P)vTUX>|5N><$# zM_263cWD?AAs<-yQ!%I7ebZwe*f$LzY%J*!n!8wq8V;6>G$S+6DAH*M>ArK?{Jjib~io3 zU`Sl@F>}9j8u};P{*r_63}=u8HGC$@vJz5O1WR8tbBUlzP9$2>m(4eoi8&X&!gw{f z@~_FFpvlLSn)CnuHCGd+|AqDcx82OSWI-Y}eL!RqgIP~0-+vOBw2X->Tod_rF_>ca zzo;e_na`zMN=zL&K_ck#uc@)|X6kP2Oqu8!TR{(7gzIUpB20;y!^BE*Wab+)76d~q z40_n3)I)rmdBl_oFRf-WAXsLUn~1`>5j1lyQJYWLuZ$T%mbwa+2n5YUEOU+c6N%`^ zneqR>NG2A8BIjXUnG@t(P~>Vv;y00sSJM|z;A1?>Tq^Aqu8(M#D*>saAd5uaAR<&G zGV7XbI|4@*J~Q!(z0^!bgtVquO4g*-CQGLNq9gyAZ*##2M<_BWF_D{y{_hA5dlV{> z{&#I6-VBbEh5d!kjYsLf;cHf@q48s~F0o0j%*XT|IhXHalNq!BBc$MHN(+WtCtOeY z4mC29m2iC|cP6Xh>o=1ZQ(N;((8O(8AY8ZqvKRJjXgE^CnN{avAHKdd^)ZN2FVi-r z9YrfVGEGZJxrt8jBqRUz4O0*CC+k=j@Njhy@0ZGAQJFly{))wT^87f`qg8Q?mIyXC z3~e614X)d( zm8^G~j$c_X`J%)i$J;1GkULW-ShL%T9)?Jn$ zt~%hvgVO_A1zHUHC-#4UCx7$cIsAEuvl@6i$wQ;Rl)A`Gu-=r<1LsxrJ7ZT7zj?6x z75o_F&vRi`0{wj0y~Mi$4na$?KX4Cv%g~ec-f!Yxo-5Sn;c*stzhJi&{(bVc3!FcQ z_b2e*0$&$%Y<7-;I7R7T6`QXKy}s1F)0z+D)nVVDt;3*NKbD?eNLyx4~a+aOH1k z$bHj;q-Th+NhItY=Z?1LSXlC(nCVllSuUqi-U$nn^z_#A2XZ z;2i?*0r+fPch>2apkDGkzXt8M5BcBtmA|o+LEKZ(?}fe>nnQdYsrO9ky#YTriSsyo zF7zK$eg?h8@JA?b245cjMfjEQet21YbRD`MD(hI65Vx#T?*d(c|F-xqfd59|^}_CL z?24lQMDjvBgNWxLFx{w^tnXP$J2a-=%^7#spjWBCJeTZC{XYfgAUHYT6omc;&Sw0s zqWmygUWr?Pr#M?@*`=l*4R%aj$gog3{Da3{vnUZDQd@Us>_ z$+Vx`CGC!09rX60_cwZjp$nidQ{Ub6&$3DUk|iE_ep#1x{0jZq=>JR{bBUuEcrC%Z zhI}9Lr{D*`F9GKSI2qLQ0`;vJ$2)t8OP;HCWnS1!z2w{S40@61e}moc&@J$Huv-t_ zyWq)s&{Ftq178I`0>4+F@_b?t^Np-8`+|j*c`3g~oN>r^(mt4|wfwUIyer@p;a!By z>5t#Q{|-NkxTev+z6bAh>QNtB5ZVB|GsN{e<+CV1Ph4@>O##0q8EjpJSRF!KiLM}d*I3XfXc+tnDPnKZ!fh@0AJQ0)R6fSy`ku3P(GUSanL2u zS@;=*9~-^j!0Q0ceeC2p#hb+SgnCp!Pu8JcCBMbsTT(CC&(WKHAgue+d&jrttA6aKJo_@3p`B~(5h%*Vg5xh^q`yQGFm30Qw$m4Is z*%mw5uj8T~^4w%AbT;-sV3&j5CF-?|xL+jhuFwEf*7uA-uN3i@Bo0k`SD`)fQhpo! zOW^h<4=1qShURKt$<#}p%f+HMmAH41Z&}~-HvV5D zAFZH=@YkAtxE%f6ayt<<-IM53a1Uy$Y_Zqc}^xCW6}>T-j$Z5q=bQi^zXz zXeX#Vhg^!E66809@^$FHivDHr3Q#WVl=st*WPQMN^y10WB>ZfqxGE0OO%Bc$?whftP(buaIxdRS)E=!JkMTWq-td@=yx<4%o~3+N1P8 zS?~N2@{gI%0_0^F?NtT)(v<&6ziW^F4D=@=uaEp&{Je&rYB!H_(rw{QAA+!%0wq`hUG5bHx!4d$PN%s*WypG^5`>i;(N{}=p<*vWpa zFOZK1=S%oMiDxnMMse^LU|#_JN6ZheK?h@(8+r@AD!9AAeGQxq#MvME9<(3yU1&vc zHdC+MjPp9g-wV95Z!>W36X#;&W0BVYcM!Pku^U8wD^tFi^4r9HnE0>K&JBs{77FvJdK2`jPB!EQP+TgBc1h z>)NM6Wj#hIet)D8Z(IDyy6SewuYgksUiLXQ#$QkPx!`UF=Lzj9>nTD@b(eudMT_M0?4)k2})7=m9$EMIKJlc3yC2;4)OQB;{e<#S zl()n#2mP_&b;5r`1Xn{7G)j&5pci4?lgA!(4ULGtfSrrZX@ijQ_pP5^N@!Z zv40kOSqHO?`p%>LJ#Yt5{yz5dw|G|&#|zZ22ld*3{v7lhQa+CI7tkMy{%+(SB6s6& zH2!2?-%{$Ghq&qz*I4X3U_Thz3|bC-`Eq+6zpH5Hjwz1~@9G%* z3?~n=F7QPsPUctQx`TWM{xyE8;O7Q7H_6K_=v}YntcO}n`^$c#tK@kee*59~bK*Hh zJiDpiSKus!z6_Oh$Q$va;V&_MWxr?x{Q1zYfxegWo|LZve=>d!Be(DyhkP9JBgns} z|Mg&;$1u+85!WcndxKXW{M+bFBi?Li2dJ!beV%r1Ogmhl-VLyCNFD;%^~6rrKMn@B z4t|E?e?I!3qA%dm|^N2juZv##(^oF9h9eQ8%(et7w`}&KMALnn|QQv6d zxq+Wo(fbxXS)aL`JVeNQPk%;?NbN-B>iMH_Sx81MjnrU8~gs$uLt;VQm>OxFXeTSpQApq z{&oO)l>Hm^@h9so=TMI?@HYW}pCNC=d@umq_py5i+%e$30nX3h-JqW~r@cIsSEu{~ z?1o@x!B>L+1N(pI2i^F+v=7{#lvksC5BW-m?+q`119lsD_rTjjT!o3ZHGV4LN8a~P zmijILZx47Qp$nm1(4UQdJma)J?fN?UL(z|?ygua{m>1KjUm@gwlAj;Iy-h!vi`{1I z+9I!tzO08m!?%3><=QwfaLLLLHjh_MFGzRA= zdP~r|i{E?X@m2i1h@Ujdb5s5Y{5;}$4ZUvYjls|7_-O;32HlU|O7z;nPluOv+@F(| zEl`X8cNW?R3WWNBJl99>dGyvm&qKe*?`#<-TvtCL&gRIcAP?X#7ye{_vqpduU_yKuq>NA!4bi@8Od<*Q7v3m!-DEieH_(t%Z;YTx{%HN7;K^{gTuaCSS z^gi*=0{?sPzk^KzRq^d6js+ zhMs}eqdc4PX!u(2y}_Rcet+cE=@*Zn^GQw<8cjy-&UyaC@ysuz0b^-9Wf?Ex`6Ds>f-vMVaLHV zqn8TqAH>y%yfh&07SIaNy7c?pe9!F1&p-IFh`$|nN1+wbmvzM>!Il44Q;#X|wV>s( zkHJ1KdK1vE!hE!Y`REQj~aY=+*37N-0X zLe zKNZIHDmaT6PqIF^CiX$ff2O<~<$o~#E5OIXU%-Di>irgYo4{*MUZ#+j>ELz*_ab~e z`9FdFcj(KyRTuaruv<(Xx=}s{{Uz|<(l3@me}c+7=#%7OJa(P1+Ya9Q;5`kMzl-h0 zt}1rRD4#>Q>`RZ9{oCYc3;Fq-xbnfj0$oRZbFs6?^9hcVw3Bd&k>9$Se0^Tg2y zym!Ep_cR?P-?D$cFa8UI*A%>5;B_M|S)advJoN(SCE7WW{52!KZ1nO#Yg2xV^2&_s zZS=1%uve_D_s8EG@Gy&q96?`4aLmk$kL#A4Q%%haV3w>*mW+z6E|h{9Dl8(C*AD2guW5{H`S~*`Lvp zcpAf(ftP(aC*V(`zZ`uYRI7&Q52L&#<+2ZEFzqDoImtjS`~FLl7eDe3u=^Cd(b&m; zrfS5WOx$gVdo1&GDauE|FM`ht%@8?y&!G1V@zx+-*{`t|{xsvMEcsf7-dXe}f!hMy ze8lmP`WMCDJG9R={LjIE1bXS{y-EHylE1d-mqvdH`ZI|;n(|tdKY*5`+(kdAO#8nK z&KT+wKrarxp7?(m|5u48hen@GJ&RG#ZrIJi{uks+kdJ`>6h0DvH3bh^8Cnf_KjbZl z%O?Kb{7xvqbJY~^-Qd4R`A*8^eFXn7p6?<*id^2awE@4f|8N)dMdZ&Rzkt8vjL+-j zqbYbz;9Js<-=Kaysow|S?FR1~>T!{NJD)hV6Gu7xe~*4{{Olt?1K|^(vQK0vd0vR! zA?(_KlL}64?1o`?20!Jo+YVkN^U1r=kI2se?B+^;LhlZG=ZJ3;@zn-r7&tfJ55R8% zZytEEKdlFOi-Et1d<}Fu@;}ipi@ofdxJ`dZ#O`m}vo*Yn_?yC)Lw_#%ebApy|7b!! z7E->HI0jLV{M18kkeX5wtY^WZ%CFe-GhH!4E{g z3Hn2^ZwT%wK~ zxPJ%tH1J7%lNE|-bmyxKvzIpqW2)4OO z-$MLk;D0@Km#~xfI6OqJD)xP`kD@<6O}$Uye;Ij`eJ4MXw+G;!0Jj->lhNw|UJ2sf z3I7oOY4C=C_bm7~@%J}zMqpP1oZR5}(CQ|p$Hp!{`aeLgVm}VL1SrE*Azc~{Cq_mr;&RouMVEPPi6~o{D}M- z^0Ck_parRCW9s=D{08`$;0(l1Bz85i`v86-{6uIMXbkiwev+XYeh&4!!*$kE&=mS} zW8(dtcx7MyO#D>_w>P*)h`T&_KTJCmBkt1hU(-s#!*cY3==Fk+gg-<*KBFEM z{k}2nQjdO{3f|B7{RF?aiLW^Nnc(Dra~k;%$iKpF3U<|?kx+RL&S+^@^tPjyie3bI z+u*&#a}J!`;4H@f5omqNOHlq6<#WmNarkfHr$PHbTT{<&%ono%%}f7#nR&Q8xHIwB z1%EBUc@dmtl=q{&HuAj4PgAe{%unx8zLWCV;J*rfC(6?(m;H|KG7t8m{B6n`Fi*xw z+{g=)*QNM>4gY<>$xVKmk>_jVS>Eq)0=-e-wFd73`AsLk_s~Cv{wLJ$0PVOAdJ)r3%RFCDc-rSf z?1p1E71|cM5Be!o-ZPO-{7<8I481YX4&-qNaeheLDew!Ck43Knda~d647|MmVhZtp zn8bAr{c<|;N!T4E?$yNI0NzHgFmw*IAMwm4?`0^Dq`VRRxg2)#e$K_u_(||{sP|9Qe-ZRBbT{Sssb5Fpc}RUXp?3?tdgS3Od3X%oa`4_oe+T*%(A$XK zxA=V@zdvDrpE#4C9}!nQ>_=grAH8|#wZ*<0_VcjcihW<&vncI(9=$mDvE;2Wd2@kx zk$E5jdIeg5@&R*jDgg7nq zFA(3G;6A25`k3!((2sV5_Xxb)*o`C)P0%X~ZVvu3z^#RS9QN0#M-lYSLZhGs>Brf` zCHux_;AaYYtz>qBoVdWVsJOaJ~8e`WD|AG_n&6-IuFan%j|V#GZJ zoVwtgLoYXaJBjB@;yFV-2hq=SqrVaT$;i7Q-v<30T7dHT#G46U3jPW96|g@6ZaVt% ze$n-`=TFG56Mqx(lZO9e*mWYl+sKQfUkTca^0Lq_QeXW2Lfn&ytEJSB`md!uZ$bT( zx58flfA28=RUm(v*o{Rl`rzu;Sd{{r}5LQg_-VgCjB3eX=n(H|}1xPbmG`a^5-yP1BLMn5Zr zeiQWfAkT|@WfH$Hsb2;BeapB#L;VlJPepGvdX=HQp|bCHDD8Nec;^ytSLClFzm5C@ z>bHxy{vfV_;MAf&L=*1~>e+&LZV}IY^h;1K`|qpq+%J~$+mwHb{2(|Bh`%J`W)^XG zC4ZZUw=D6>`!3%gotMm_vl2a0qbA}1Ao~BJKNyNlQrf;NF31$RBTqbP4c zc}4JI_&zudUOn(Cl82wkLlE3(^w%TLCf<_h|AXByx7JlBtkG$8XHG1vg@1nO; z>cRX%3e|SX#}ntf#F9O_q!@+aUwhIe6CmH4)U z`!?~9M&6VD9LKm{4*v#xY3w^;e;WNA*k6NBB#*B{e?-29_|L=tg#O3qmj(Y{=oIqz zA@~o#|A>4|zQ{?6T;Ns)_XN~~T;7*bn0~VZyAQA< z6>2(qo$yx!KO4!%Bl>wH{51ycW6BrePu_!d6T6Q19YKHbpx1+Z9EO%5Z}*Ttq(0@~ zS0n!(dY$=xEPlVhe+GCd;15RceeAQroxuFtf_PsbE_n~w-{9v#zb^XK(JPMLJ@^#L z<$c=yiL)0t8Q?sR{4?}kq&^AUAB{tPmw0zluPo~IG3EOy?+N}~?581bk9-9Dr|?z4 zF95#0*Ju)S0P)tK-lxg)e)6~gyvg{H_m%X6rz+}u@*wYT4G>33aN2`25Zv0}*2UjQ z{K@-}>QIll;LVfzgTIb;lJ_FVk(X!C8-d<9@H>J(9R3sd*5uynfDXd$UE*GW-ka!+$F3W8Pf_1!>RT0gKIE(6UxB|$ zo_b;T5q|dJCzX8NB>r;XSmbvie!jv_JLq(1G4S3aA1lFm3!LAv%ZFUv<6edQHOH<4 zcJ(Mvro1Y4eJTG8emcCoCwwD%A7Zx`f2*O{&{x2nK%CdnFGA<;3C;`DBZcpS)#wkW zyeZ`s;^^Dtt2_PrE^>MA=o#Yu8M_ywTV##;!2)QB#SNdex&|C-K{p_RK+V zFzqbwMRAj-4D9QI(;d4vu)B%geBwKb9}V9E{l{GIoS=V}rrqB|zX1KCAh?ei7ptIi z!T$^Wa_IL&zchJ}_r*5^{~yZFQGN~lM5!m`0pcu2zgkD0vcWq{z4B3>iG3nC&8Xk6 z$oC@u7T$(`4ZG>!ZUAoqcfsVs(6XM8Ez8aD*c~4+-+Ed<7a}4{psrP)^;}ZHi z(EkAcetw7Nfxkn)I7Pe>)T=181#!#!0n_L=XK9xT8A(3hMSmRj7ogjqDd=6J zf69A)^5gFS@>R%h;BO-GXv)8b76G?8xboh(FNuFHdZW-ANclUA2YK(zJo?9X=v_u{ z2<6Qw_ffB_(0|FpO6rk^^177Yhw|qN)l1ZO74_|mzk<|nBkR_3aoso+`xe+O=enUf zamB;mr=BtB*G4}T`;Oo|1wQ~@-VgQ}aor@2+r+U5{b|^@12;Fg!;yc+xciQHW|QZq z>2F8rZ+E~+B97|ZH_0YHuc2q7w-38w%(tB=pC$Dn{=&rH0DcVoWBitZl48{yyNBq{ zMgKF(k0O7W@-@WOoCkXU;-@ftQ}|ygkHKGG;;2C!tC0VO{08!($gdLjUgG|U=ZFum zYlYvdwBID;U6D6IZzA|ziPy$&b>sz*_khoVUjW`7@S0JNw$x)f<^3ohMtqIQLvHNr zU|%+cc}eogc&LrvwZt=nc;x-@oxpKn{}Jt88MznvIQVa9=V91?jQy9;lhDt=T?y_F z`1ue&`OrIz-74rb;=M;4v&sK9^cSH2GkLf{9{l+Kocw)B zKdMSUx{u%E_?7oA{)t^9^v0vt6uZ{s>n(6Mfjb<%R_NK#UC@ir?a&73k3l~kS|9oi z@y;UNud(k%eYY^)$e#0_-v;F6cjUhkj~D%?(0>m47W8TGih%cs_&%ZDgNXkb=C#Y{ zpJ!ZmgipYJA9(q}`;fTy5|_Lm{uAQ<2b?tcB_?geTv<~Q(Lw_R|DydxG{)qlH^e;gZz%L|uC2wiqb%!sHotr$J z$6s~)eT&>i{s#EN!B2obpkEaN_W}F?=pyjD5a-|M%io=;P5U)RFOBl;;C)1Wf5^vJ zV%&d^-yHl~^vg;z9^eau{{e9HGKf_x9SOKE5MyB|eppXc%45dWts_kmv@{ZoPi?ly2!p&g-p(Bn_^DtS+J8g^cA zmJ!#F#CMna*2n&N@CTq*3q5%+>NxcBqPH8p``|91|2&2M9@;4u{b$fGNcl0w!+7)u zqR-G#!||{18^G@>@ctiH_W`)o{Qi&s-3~?3KtkEG6j>o;WY0o_R2ougD1?lxtTIdX z-Xkk}kC2sYvgZedkNM&Mc)rg$KYjlov|f_xrxjeG-3YrwNUc^~$9Zr>DkU-LVM->&S2v-?f| z+U)j$?-29wR(2<_yMX^0;?2k3gnv>0&BmiMzA?TH`YS)*^Y;q6KK;AZ%l|d^so!}1 zZ-{?(`B9Ot)$4TfztLOK;o{DOYj1W9tke8Xz)9+IEq{~w>t?@t$GU2-p0|_d_YAtC zm$RG8ZasQU=zXjnz4;%@@A~}i!rvkwHm5g~UY-lyjol6K9s~De^Z@ir_Rq7oWc^;F z^Emks@-gK5k#}Z4fc=yBYZ8aw1C8Sw^m~f)s{Y(0t_$Z~a3VE_^0Ozse~inT^8T5A zejle3`I^+fW4DpI^jDYf*{{rgfBw7hKZ4&?^p-ude)d zG)_yB55Z4B`>Ef$><(hL0lSCUJ%j%RpXY;jq2EWGt;A`E|H3?eOZ-OSkHX(%Ubet@ zm+#5y&{G}W7w1C$pW**zc#eZ7 z&rdxBj{FY59hADmy$<@1_(R0c^N2_3U$dQYupcw6nMA$=`aU{={{#7d8Lr#qxvaR` ziQk3)rTBjw|06!nfxL*HrP-az?ric$$otBpopIdWd3AYpSqFbBx;4M6@cRtCU+B#h ze+9UXh5r^H9wh&U{7L%tjmHfA&Giq2<23bY$o@-s-$EbYXIuHySQDXcr)sHk)>37x zrHxDK` zk!bWnqZb;z(5gTB?8Hp*!PaKcu8R(9}2M5gI-?!-s~C-|(T~gDZS!_{7--jo)D<-4#u| z!%Mmwnz{}zY54Sq4-KFE!iQ#_M?do;G<@{JhlY<{_|QGk;LUsq4IjMWL&Lj_tmQ-Z z!N>3Dl7^4p@S)-3H+<;6X!OUFG<@<69~wS(;X}j6E_~>IrF?8j!$%%IbbmDU&pZwd zpFF~chEE>hL&Iksh7S#&yuycu&-jE7?S%&C_>yMbhtE0*4c{0&2u+^Hlr(&!k`E0Z zyy*{3UE83kb7UqH1j2V=1*lG8vD@LhsM6*gR81VbsW7M2Wxbz<4Run zha{*Xv+qQneJ3=0nrlCuh8(3hY!s-MxL<^ z4Ig><(5$aD(X6k~tf$bdr_k(ksbBWF(BzjfOpQX5&su2m2@Sq#?1Qm7jy&tJ8n5a& z^7x5vXvJ50k>xn+Dm3dXG-HC|r0frkpU!Cf^ep);Npq+gZv>ymCz z(%nm%`ovH23k@GX;X}h`zYia}Rw>`9r0bP**OIQ)(Av>L_tSPRY54uMT}uAVl0H$= z*Gszg67Cy@-aNuNU>)k5FWuF@XXt}f{vCGF(C zzpJ)cNn4e)bxBt$>3SvIX(@9?3*EY;z0h`ASFL?Xr=f3Y&uWiq&y_TM$_}GUJ4Ie+@L&I-S@}W6jWc_8Y$vLAsuH=bd$#Wce>>{h|JC=NC z`V%L6OmwQ_9UV6!JFKKR|0hR^s^eW9M|OBg!zW(&(D12a_|V--`S6lfd~&E{IgUK~kyUo7SNPD~(ZoHX zq~Viy_|Wi)A3ikKN0I0HC^UTJ;j8|{2_KsN#0ej|Ct43p%#n*7SMt=OlIJ+`b+IKzkLIw&;PK@}g|;X~6O8h@26$B`$W$U<{{6#HBsg@%t^ z_|WjdQR!62k>`4;l2^x-eI<{N9Ot?#G}m1fpT6*+=?|Xpq2W{K@S(Y`TOG}HU1<2s z$MB)yqZhu?uZIub$g1P$r4AXB$gAU?rOr_$4IfnL&H}!iL5%V z?@(!Ybri^;X~73VU4Uh zuH?a5$#Y!ED<9Ec+)us5^m82j#EoA3RL7C$em~lJ`Mn5#NijS{KRvlMp1r=~uogztiF zJbtgxA`gvy#mA&|L_LY2b`{Yu|b9|i^e-(|7>Ns}Q zadcnM;yW}p6(5^QRvlLuDtUZW`jxNv$ni~D^g|=7`1q=1)p6ywlE-hQU-_#1-lZ>g zq0zgyF zKRzoy{mHN5s~p2uds1~=#jWI3obZdst@^7tRsZ7r;&H3~3P0636}OUCajU+? z`Ni|C`m4B={o-*J&-dcR;^w%DTgj_9Ro~+L;&H3~DsE+8#jW})KI2^Fo8u~OC9mRC zeT(yp$F2ISxRw3laTd?_rp4msxQbiJt2kBP;{4)qtNto(Wnaau`YZk}v2yJgn)`{+ z%6E>dxmfv&EVdP`=3{(J)#4*GK0;#`8ojK!8+FD0eyxg=<7$3YaU!eqDy%sd#wIj+ z8N2YI6(2v3`FnQcp^=BiN8$%(XmEz+{vx{7arARs>8C%()3w-!#x^u|p|Sb9j2T+- zv3o{~U1;n=tC-bs^q`UD>S}BgCR6NuF_(g8b=-)`61{FXk


tK-B=Uua}GH^nA2dYL=nLn}Ud_Yl`zRhO2&yibyQm)xtJ*UtNx=(LhvP|{aR zI6nrpRnqY#J*K3`mh_~Oo?OyXOL|&KuP*70CB3Pnx0G~ZNslY()RIms>D?urRnmJ( zdT&V^cXVy7eXwqk@2-xgYrB_p&ywz4(tS(1e@T0l^uUt#E@|JA9$eD?B^_AO!%8}& zq$3xjGaQdD`Oxs=N0>2*qNGok^x2X=U(#0=qrvrh$%lr2v*bg=zg_a7;omFy(D2un{0}Alv7`%2 zx~Qc8E9uT>`D~asO6Q#8GiZ)q^Cn&=Z|HsQ&8nGK6m4%eRe}`qeaJtXb@jH&+`rhDd z<4v{=*sgrJ_w)MtoVpv*mnVDg_zZi*q4GS@XN~xr)6d_Bym32>&%fBr72{MlxcBeP zzc=ij_Ga@NVm8zFB}{cD*~9qlqy2EUTrPI6d#TR@U*@}D-atCR&Eb{DiE%O<=i|kn zf2Qx%c$2CA!8J99O!VwCJ`Uw?4*e(99t5*I}8$ z?tQpk(pK_j)1&$qiZf3Cq$xggCa=`zZ}B!)i!Xcoe##;0s!pTaK(|(#KgE1ZemB9= zl+H`u#GT;HjrM#!rN18>b-Yp5LtSs?=Rvi+^IV^2r1v-fpPwYZ0p1Vc?{@Y>VR=D2 zafo#*AoZo5#;XE^t%&s^lv4^UQ<%kzs~0SdCqeBYjWZo04hhT!e(zb->cPJ1s$O@K4;5#|3D&U~8&5MQ)p%!Pz5FG{ zVNX6_xJQl;kblhPRDRyw3l=rs#yC!wYsO+O%-bDhZoxPa#B-S4M->Cm{W7v@YPxwDtZm+1#$J$Hs_=Mdy`xqW7@vVLAP2Uk7Txlt`2A1!C~K46Mtwdi7AZUILl?RvF;1OEB+CacfN?C*hV zOZayae|P?;SeI|m>n7L7_xGNvc{rBNhxm2nHRmXQ3sm!d+92&*t*7yLl+Q=xvh1n$ z2;;n&8vjrGLEZk+)}AibW&DucFV5}8?#sQz?CHH*t((|4Y6JWD zn}c?N@g1VZ=V*(xJ&o-r+En-^tLu3Atf+47N77ff^+r2ZyQve*GdO!_J8OGsJ+;2t z+1fc;H#Iy_dt4nerE_Pe`}c+?`c14yU_2Zy`=7U zl{G!q{OGN&sqNNfP4_W3f6$ITO>DJ1wXEr5=1=C(i(2ZNee^$C>U^e_I&Y@Vqt$u( zRDYkCW=&sI)I4+XJuS6gO6^nsWiKtxZ@aARGq&2B9uRrimiCZtd}=-R@!9G=zVmZ@ z-J0#MP(SUR2Ii}_NV{MAT6?0TPnPuI#bh~-{8cUbp%0WaeXnVuub1?VlFlpXnnqvpO3p>-IE zqt@#+&$ZVM`2W!ni^)bVc06ja<0BV49_@Ix7GDpRwBqBdlI6IP$8ROi@mALCX4wzez&V)R<= zCBAP(vp0M+)#t9!diH~l=x>ScsEyXfYb`e^_KRxY%)ZgU-q}#gwM7ptd+9;i80|Ri zgvHwWzB@2#lr>BCkv710H@-y|Y7O^;U)xOESsS8Vt^HSPW&{pZT$eMd+_uL;Ku{TKnvG+KoP_ z|Bd#Y<2LrcwY0Y#ABKM4#rZ{R+}plVw&z#y{T#<{Yq|FNNn75Jko|);bROB)e%T&9 zaZ~#wnsdwJ+S%G?ZS4VleHIWMfgY|+)IQce(K>AAyoRoA@7*f8PIrF%42Kuu0miD`LqP=~K z&CMU}0>|@wvEw;(XZ!nZTK4_WW3=r3cWc*bcW57Ki?m$JH*+p%qxIG9(&lMzwkrQleN#Zh2-s>GoEpLleXa&^2HyoU8arKhLhzyJX~9{qwf@>zd673&{o*8 zI5#&&KchFzoW4-Y2Q+5tzipCzcKd(NMeWG@Y5QtV*`s@~`@`{MZ6D|Noa2w!*fY4% zRkRjE;h?h}dX4stcH=O|T|9#dUB0W&E;+B=u$k}JqkG^-qgQA--#w(Ay@h8N8S6jL zJzSSP+|_pn(U-NsS}W(ot+iZRisVImfSU?Yqp+6sI7K9;ufcwvjaV!V30lD@r~#x^k8%GO*VHsu6LsAitg;tdk-?V277jjc0Qe% zTAh>JUuu`1YdzDO>UfHFnKo-5>n>D#MEgSPdbsbGqHRw$=GtxAYT6^(ms+kh_eJMu zFKO>Jc!Z>+N8j()meiHJRR+x-|pQ^+)=yr@sE$U5;NL#m4borxy3jgVCYd zP1@<&(x=(q&|HhHtsSnNs=dYLW3&LWpwEHgfOe3`OuK4N3U3*gdTboPP z@lxNvy3{HMJ0-A&%w;L99e>3M378(if(SXcYLsrJz2uDy4M7yVmnaD{tCt(CT= z<_8yRIt&?k#GnqN$Bh^o@QESX}<_n zx8y@TF{|d{+NWXJZ|a1%cT@&7^=k~5?b*i(QUzBg?6MBXFhMt3$qq}%Fv1;1XJYLhNCYYAh zV&&V>b)I)*OQ`*AWmot58}r${&U<aH}IsciJnqh^fw|om~5Hi zI|j8?BJwLMMVys=M(>Q;_si?otdb6%r~7TYrqJbAt5>M?oZsWE$7ZRTb!tBou{S+; zAU?Nrn4=46Rw?% z4QlggTpJ_OjK`|A8Y|X%Tho}@y-72k#jkxgsI6sIO;N>+KuayJ#LiLj&&Xw#MYhCZ zEp|b%rhoIwx@cl9N8SaJ!c+Q+IhWB1=Je-PpT{80zfEhsN9tK>{>?6wS5dWM#^v|C z^Q-U8acvR-ZG`HT1J#eTWg85W-XGelE$_64rm9{QnE@$71HFBxarGC{QFv> zRxe1GHb*n=c`c(gHqJ}KlwYLJEXyh^s<*u3R<+}jUA36o@txl6NkLTK9L%eE?d;1@ z@U*jvyf>z|4$sACG&GkoXJeCflrc}mS1tFj*ad&8T2**;d^{$4aD|J_a%I?sJR#8z`d!kjlbqieZ7oHpPpPD8@fim-Dh1&iK z$ug{)k`xtQg71uC@hW5u%+c&V$up7*Jf3p&&Uc4et`#9tgnWAS0k5oSZNhC z2(BC@gC*pXJtbpW&5mTB@rbXSVG94rHAm6&)CK<*sM;G~<=>pC+z-V>oQ7s;YE_I+;ijrnFBco7Aw6#(;8RIsvs$_7DvT;PoC9L{c=TT z23L4uQ;bWF@``nn+L|_%d4(l03cs-|a!CcUVw(H6*xjp0k$d8>mi3==bt=`+epR8* z`dG%CNR?8tVgz;9zUo^CMLiQKqnHsXyhXS0Uto(yu0G;Fp7P3WUDP?_n*Ktb9j#bF zxyHz>k8Y&J^+bC|MV^K4WqB!%l3(G&!T);|Bat~ zj6jiJv9IJRA)3iOo;?k_HmB6M*e#1yh@q2eXO=Z{{ZizZ8B**k(P*MSC>yC$aaIqe z_%D1fS&P;asA}Uyn&_!gaV;C)kyc|+oPWX>`+bwz6&hd3C9Ac#mJN-sL@g>(%-mqi zUXfLm-12J9E>}n4n)Ywq&RsM z+1DP{a+!U_tFX)*3g$-Th-DTQW0BRDsA1ch*>;U+t+yPDOvMS*>MqEFxwO7O$%QEyP)>c2E7c2GNLS zR$Pt}qgm~2P7cYgkQG`%R;;ktWgkcdvRZNlUCge`x@uilmCkuL$4k-9IxOaAfwS5_ zG7F-U+E&>X$5lqLE6yBQBQiIxgQC$C32(t3>D$ZP0 z{WD6rGD=@n!}w3e!TIlflHP3tY**njwW=qDB&5#e6G9zPK~4 zDpvSNj3Q>S4<*yQ3R!W@UGT+d6uZm6XY|}bRxz^E$FA@Ze1(1T%Pa3=`6+W`AbDR;%*cI0gg;tSk!oG0O`4MTH7PMO};I zWFCCs3QzHpSAn$hkyVroit0t0Y8JbBF-wY?>Oqr{Z~^B)JtM6iIT6=lok-WB98~ zEnlJkw?4(0EVkA9tMV@FGa~;NWl__--%)5rukc&cw~8E(sbcn@0#CFODKu3sta6kw zuc9PgF$1HS`IoDh0&z9t|Lsq{|JoKasPJ2?gd7($q>2~+l}0S9Xo(-qV!tfvS>;kh z`M)C&S~yjYKWDLMQ7XIz{IH&f{bp?X0S*%2lLTrk=$-NpCUt ziYgW(P zdjrv{Ud6Z+*GiEWnSB1H-@No=k{##y&0?S5yYer8t6$2qYyIHCvObfy|KBw=3p^}z z^#6Q*rK#WZVVPQN+1Y1V8#So8{ZDsXo-eu<{a1aq?}*iX7OJji0qgJ5sD8~1^6mLK zk^QCY8~x+6NXvNsI(ZB7yE@oc>wCVn_}4f0J6Rji!+*!`R=(`hjQkC_#k-%b{e@KS+N7{RZ;7NS)r2 z|MK#0P5;__)(@UKb?elur2lFCC+XkS=PL$^*HpX<<*|=ERyQuk)swS$!^B%coR{F= z9`4KFzF2*?ljn)_-lew-|4W)T%klHD_*bF3@HbZ8E3rEcT?0KC&f|^aYT~ZYs9w#1 z@_&`y0sPhDZ&Q9ggL5FikMMhdygxPnzK3HB9K-bY6z4E_)|U72#%p)=-HV^+`1ub% zm+*5jy*oYFYbqRDkUxSRhVE{D9bkUlEdDLN1lNS#IpXak-j(8Y6lYiUe^z}4F75fg z>iHOdQ^^VF!(2mXKJT+iQu@Yfe_EdA~2FKfI`L*Jyo5&eezAI#r_jXZN#J%7Hz zXNt`C$HZBQd?tVW`0L8wm;77}S3PxGb&)Y`<(`84LGm}r4S+^|z)!8}3%(+{Ishx8+Lui|i8{s_$Fsu{*rK!Fw*hzwz7E zIL|VUo74N7-zn&9`ZLty8vZV%m*0K51OEVi_(JcCw(^-Q{<^Z;RDJ#+KS{h#jpw5^ zxgYd=^!4Qj$Mf<(4DRXh-Ush-^3U%?pGSWo{igCijNYSg4TkSo<8>r_o5^PZyPM!? zYTlfue@p!vi1(v-tJ9lrTz;_)4_C)?^>3m74)TN8_hZ*d9>0n|-GlhPN?hyqCH5z? z>wqrer0O8J9)0=z zA)lxD8Ocu{e!t{5&%tRA-#6+tm7hD=4`cr*y`l8pfotU@JeQmOaP#LAc}$VVZT#-f z@9pA#D(<`Jmd2+IdaLzysJcC*ZcDOz))!5GSBE3Sc|zX%tJi4#FX}(S{Fq>VtVge_ zb=b@Nf1!?jLY(^IbZwi@rT9EK949-!9IwBv{jAMGwQpIkW~}@-X=&epdqueaFdqgx zkIa(a-p1!(e4e}hf_Ux4IY_)g=uPwo(%%T4@8RB@ygT`w;=L>0Px^1x|B8Ip&_9Wv zulX5)?eal3-*L81y3^kRzWgrw3*@88yTbP^Tvy9ucX>3lzE`lWci^`%zXRws zq4$S6U#HI7%Kv!z?*jK5*5eWQ*~aTh_J^}S4$gDUmr>%ZY9D)ud?5L`{A|Y07yMqv z@3O|_L3uZT_X_i=jr#nfe+l~ch;uam_3dB()!$p4Cb3^f|7QIECC?A}xsace)Z=XR z*qQ#T=1D*FYjh?4YUH&79N#x=ShEeB6X33=zI}}6Ve)A%&QIzz%0sbE#IKM4R{RU# z-D!cjC0-r&Q#|bQMs{_?xzxV!WL^2ezr1<1!Xm$g(@I_7sSnTB{Pl+KW$||R1?Hx3 z-^X8n`b*Hii2W?~t?0jlKac-m)^SgI|Iph-+$Y36oSy@X-(F}dbToWz;hW3Pk?fX* z=Wuykh~El7U7hN>-q{iVy}I77PMey)Js&C`9z{~+HM-tAlYYnAw~v+IV|gL|QQ zKh*q~VElJ7{=?OwH9T7{bna5e)!}&5dbvn_H|FOMcJ0}{KyNg?j`HZ?ym=v9-@~=C ze4Zm;8SYo3>0fIQbp?58{8m@$0PrXLzQ;H=2B7@;CT9p1&sS zu4Q*Pe68R+NFH_J?M43+_=g#%{J#Bea4!S*SpIf3A7-&z&v;GK|EK;-jKg8-audJ% z@p~V=<-}c+-`DU%ZxNqfNBn#E;q3BzGb7b~`Q$CHL*#X_xSPQFP+Q;WGro@)r}xdb z-T3`Y{MFU}5^-K9U&{X3MZSHllVumWhO}>O%5ECFPL|X1u1hc1|Bw0|ByI!w{Ra2x zu2UzQKYg69j#R%naQ0W1wbW%R@t>3TzVP&e=OyE?iu%;!=Pvv0Hu_)CzZX1z!Sgl0 z)A${Su8QWfnTLzNwf-OU_u*$SKew`PYFu7cmvzPaLY+@m=fm_Lss9M^JBa^={*L;; z*MFw|@#?c`>VyBEx_rp*8R~aFx;45UyCvD3YJ7GU?@0L_ZvOlZ=M~O>!^FK${`1wV zrF!jvUTWUIV}I?TKA#zfjjY2SaO?+1Q+{3*caue~4O)pOuhZl;-VI`YSF9(#4t^_j zzF3|=;-}*`7VlAYnTgN$la9jwZNI&P-aGW3F&=BG_vZ5XME_@SY-%6<5B-bqZ=)xo zC#v@;*3ZlA50~$n^j{(0dx2{Tby#2jTw0f zJHyXv_T_Epf1_S6sM}cdFm<_GUGl?=XR+H2&gu6(PR4yz^_Z$2?~1dj`L#CvSJn4s_1PJ{4bFz*-e?^kPVXmn z2ebP`yut7;kN?JbWfSvlIdNZxX9E8R>%ZAPI7t6L=GC6+c%wRgDNc8Bj#7^i>h*y* z$BHut-$uQsh;xv2ya)Vs?C)pryA!|1!MT#_kWuQhg8Dq5&ZE`&I(Dz|Q-}O!b=_Co zY2u#4@A~}q)SutW8A^XW`WL`6&iVWk^Sgujy|Z}v-Syw;9WVYYZ%^j;dY_Z`s`5UH zeh1@wihchab-YdedWthioz})*!tX)+ZVT^^a9&6MGWsXe+mPOa#;cq0Y6kygdA(0Q znfx(!1XnPUQQm!+G-AP(DrhpRL{p!Jprwy8+G` zI3FT^Tim7iyNCY|tmD1qaUp(d{!dr;{9f;F@?2UyrW>d3;(UtVQ{E5Z55u3u-{;2X zZ+6$QTOZCJ~>wgm+Lf$NL^K&vB%aA`n{(^DX&iwsAJ&%WDA^$V^ zf0DmrH%b{%iENLZ9HLrTlNOPkgB^ zlli%b-PY{#-0%M8**oH$Lhpa8cwa*u@1ozUX#C zY~+D_o0?yL7{5vM-=Y7J{)_aFk?;HF$8-7zn}0itd$qWClDCm(edBbSdY(vsd-}cE z-@|@mdESyd&7W@c+v;DSu5;zPnm9MYcU|N9HJ{1rZ1&IbGm8Fw@>t0@Esy?TANpO~ zv&3zL&R{oGzRmf$$UM2+Jo%2@`RqRAcYFO~**$CBHm83(em1%l`GpYXMV z|8{xRf$M4hni{W#{G1KfK=Z!~yWi+{XZN=}$Ee%N>Nb)8`3)M>oF;BZ`fd3;ioZGh zoGAZJ_$m19@eT3MsQUnQ-*l0&YGwSy&F^AuD9$#G8rHm|{_D^`)I9mZb?JTb>j%%J z^y<@_Yu^3gympR!r_1*b@lP<$@1cjFpYwk*|J$N3E^XZ4JQL3U@;}5pnvCy=pC+H@ zows|p&9^A%*oV#+=NNjM%l9e$z18U)@{7p_z_Aqi5x>Xr-$(!Y?6)_r`A+b$^4m&& z%j>^a+-KBd1o^vguL<{M;_fW&0rX!XA11%wte;EhZHwPjy<4_)&8)r;s_&8Vn<&4- z#MwohtMwnG|8)NH`_uQBCwo*p;QvAMX#)NH zKK3MW_Y`+ic|RfV?Tu>>`)VEi7g}#u(XR{NJMf+aZzK9E(qA$12K8$?%KshmW9Wa% z-#EA@!QDlDuBNxY@p#Mm^=fN@iz12H`m8U!rd9}(e$2#?^u3U zSJxr*_ENvT;x`s&Gx>k1j)xhC5A;tIr;YR9sp_zi@tMqSL-MWAFX->fzB&7|;O-62 zn((aVyxW@o9PvgPmyP(lQ+>Wv&lTXi&%9YfK6lHftNMMWekbUkq<<0|>+!o2`48m% z`TJRZU-CCi{EgtcQT(aq-3;?>4S1h3UajDHj9qj7Z!2PtkZ)SYHr$5NJ+^By?abJPwD0uqHYaV(M{SDY( zV%%mMxA*BCL+?BC3&~f8b3B}v;djRG&)=%*cCk9|r_PV5(=hQ)Fdlmtk9)|6k#9!t z3pj6p|26m^Hy4H0))I$GT^gGMvLw*h> z&+o$Cs9sB07q8$OSSNp}!ws&Ruc5aGybl_;L)o1z-bLus zjo*#<+0DHC70%1`|4IK^`aRU+8uhrG{=xK{qx1M{q#hI1?_hQ8hHl319qjg$*ZuI! zP}g29jX(W==-&qK((n$!uglLV=H0pG-J$3a=umk-Den{Iv79^_u>YFB>+stf-v`;R zZhriTj>~=k=Oq1)^EZLNYv^rDuWjNqXjJnY{G;H1WtH4A)T?<+ehci&%d>le{5*BZ za~$f({{i;R=s%$olC{q)kt56<`DpRS%)!8Z?%ar9;ypJB#lCw|U! z|8%-}I?Z~Rt-rti;p&&)9`C~bWA+ELAI5%)as1i*ytk(K`~M2`-lzYjcw^Lg4*fCe zaycAl!o4m2JN9>=2aumG-q+&oCy&Rio3q)i$8ITp=JL~s-re-qgkvuG0rIOOzdhhv z7QTV}{$)P?LjP9yo;5#Kf$M$sJB?jGdYzn4y0HI{{WI#=-uyXDe<%G@<$bWc_u}_= zc<$6+$8}kI3OGZ&n{?tC~aUFG>GHakhlx6LHVrcbL3?Ha;Waf1dn6^JO*rCm6m7ivK0)51`*&9Zyro5&UlBJUm(6XQ|Im&En1yx0QG!#oLJ9yXv(DIv4$!zy0mo zztA7#`sXtAbaqd}v5a}Y5_-FRv$_0d8;2v@ zagp=K?eGnOuk9jhP97)Vx5f8jSHsW4>br~zx8v_6_Mfpok^KwmI#|3@tgr1_s-3#N ztFHa!H(Y+(@PDiEzg!)jSBDkYKh1tV|7Y|6I6Q~rkCpd4c`wD!58~}8?ho*OE3bp) z_menN#rYXO#{0O(;2YYX`tbj@dHp~B?&0qSby`;4yTVbwY2BJ(V5^>JM%vdT^T)+|JCe|JB#}m zelGbqbdd3D3eT1L&(*)R@#4rELdHk( zvjRUG&U2rE9>f1?^t-FuT4OX)T{yoR|CKm&9KT z?;v;|U&Y@Y>3_=KCi-Ww>m%+x@;jJ)Yk54VKI_V3j&(ka{c`Njq_+vZ&5hHY_^UEoX9bumSEzUIY4nen4&(E!=S^6K+zqkBvHm?rke{FSIN8I=1xv4x4 zao%f*e(%0@ka@Y0^Wi4qe<<#3_D`^X!+0ObUswJ+S!b8>_cMQop^u^C;2&>(ZVc~7 z^6Bh-kNocNgX;X5Iv=EeP5l?}f1C4gU-cZKo=d>-7#tUi*G+zVs?U?o3xDFT#t)$1 z0iL?@xmo=F^3U%(e@AZ@`{b|euNLoi{QZp^)SMx|3*@&M`;X!8sBSm#v+g2uxRuXz zzn?{IbAi@Z1a zQO0AAb$E8h#k_nNUE6g`=Q{4M)O82>?CE^DwQ;}2cpt`Zey92jxITev!-ejf%#Vfg zxDo!T;$JKNzT*6;jt%8=h3lFz>f6Kl?shoehVxc_TAN?@=$~WWw-EOm^WsT#WAW$E zUyJ|F>hd|iztJ0E9J=%S9lyVdcRTr6=FNrjnL~aA+S>fN$A0lK{~Pk(37&V&^EK4# zRrTs7ua)I>3%wrvyg@!uU2jyMz162LyJzLMDjaRh*X!U|U>?m9?+Ed36YoB8ro%G{ z?k?(ZE}Ub<87A-R;N44{r^r`Q-{A|adw$>Hub*|^UH{Mgw8O8=|FZaJ@pIuBp1k-u z#(16t$G31i0AGLjE-+p*`TJa+CyDUn~0+yCdX1tETvD^RxO_ zgRh7D7V$H~`dDCn+$*1v;(jRZL~*|q=REy8!+jO~Pxa5xf4%cu-8WOjq@es+w=R5IOD}x1wV)0Fn%}ScL{hdmv4Xm znvj1i?j_>>4DY4tHbmSf#GOTdDE&j#^BwX>$yg$~ z>CNVE41W(AuldI7B>0c1TfgQ``Uj}vKzK*;^BVbv;=G4$hCU3>V0bQKcMQAp@LS^F zMJJ>C@!Oi;)!}Lg*T(8_r8-2Qq3uZUlMk!!S8aERB-y!wIN zMeNq#?<@Ut)qT8iI2qn;;oTFCXZ4?~e+O~K@Y9CgO6b#YEs}R1>+M*1EpNQ*t8;7l zUCYm&{EQUmVf#ie^L2Ieei44WJa^Q;5UyqUeUrZ#>iHP^wHC_F`Ee z9p!z6yqBc^GX1ydZ-PG_{}Io@0q<=0C*P|3Me=(L-@v>*0KQeNr-AG{I)8LEuFo#54(f4+{j|xt{=QSMe$74f zHgx@P5ju~4BRJ>q-;3T1bv#CWd%*do_tg)B=WsZhyPkSg|NiQFyF3TV^G)$5iGLe@ z0DeXF8>3ze#68})U4#D%f4BZ2`hQWE16=P9mdA(k7^`k4sM`Ya4eS$p(_4k!HuC+F z{UH6L^lv5ZX!B?T<1k;nE=9YiesGUc-~Hsjvhll9yd~(bO}{h!E!6Wu_?N?fk@LK` z@3Z@d-F@sf=H~>Uv|CuY}_v5=Lfhq=l3i7^={_N|D0Felh@I3U8ufy8lTn4 zpEi%)XMZyLefgh*&S3u&`~K>?rTW%I&oe%^$gjWruCQ<4sNSdIyW=m_|0%s?-n1U! zI!D~g<+B|AIsBgJpXeC#r;+p4LHy6+|15Dg5pOWs484V)<&0|`{RipahM%9|f7bZ@ z=>7Z0&9|ZQy+pn1q~7Y=N4=JS<3H@rWPcwVyQ}{%@?3}C*XiFQ?|tysvTMrUY<>?pfvA5`7!Jl>RmJKVW|<`xD^X9==12 z)2-&|f1N)Dv~vF>-e<;RA2|M`x0k$blGkJK^o8dD_VvjBfcxB~m#rDW&x+>h-thmf zzm@#2hPQ89d6@?f7`K+}`}+LBx%{?)=RFvYcOWqIrUHz{$ z&o7|g(>Sb;{}BCxzwPPWLBFwlo`AoT>xEhVj*=6eAF^xB z?ld?%h(A;wE#$EY{sa1dvb)ZC;}3aX)k5Cz-^AY{^7HiXAkOLPbf*5!`p3clA^cN~ z;~wIC4fnS8jm^!M>#XAg=+CsSPeZ?Acbxj44)5)7{AnIvB5pT0E|S-_^1KtC1K}BJ zJ}=;Z{Y9=RjmLKMz7hW+bO8Of=rj0w>>gB)kJ+8c?m2aDX}%w#zcIbOaIFDXOLe%| ze0V_}C($3wZdvh;XSWW!|G>RF+(X3uNZjje`E9Y z9C=)BUCa}=gSeNdQx~{ykZ)uA)lG2h4aaEps4w4x)N_`4u0{R|z1#3_;Sb|~8U8=w zudDI7ouA$GZ-PFywEYa;J>cC7-apZG(Kpc#< z&QNt-UtPOcZyQ)|kF4Ul+5Gww&g<#VMhBw3)TymHO)>5l8TViC6MgPwBL7R8XP5H( zu=7a^@_XQD!)|WIhyQi?KU2N?sed>5eWf0I!95$!=ioS=-c0*ho>SYJ{+}&eyDfCC z@qYi0{9G#jEaTA1x_DFm6ZH=i_XqVnho4{hX(awB>U049349-V4d@L~r(fi?1O9t; z+k^kM@VsQbts##S$=9XVRbD&cKa_tx@0&cLzZHI-_$P?(&Gnl5;hZGSN$Pivd@i<6 z)Fq!y{u+Ee&6A7e|CTzPG0!y*`F#0~m)BG5nySnB=EcqC&$Gs3Bs^!UdmrQ25bmqo z-_C^NT5+#x>-x`pYQz31_4-)8C!mA)*(r5KFPHDtYNXSNUIP{;q{iLa(7$Uw*&XR}XDv zKes<^$Np6EFWI-IH;Ud%>M>ruIxaGI>D7a4x^*^J-s|b_Nbd~v5Ovy2omMl>Pgu{L z*nOs6E3<3QZeKX&`kdyaa6ThWeR{{(SKH`cMgKnX{?Pbu$xmI^Q~mjUkl#F>?nu`M zM;hnOEnSz|FNd)I%s5Xm&VBWFl;^1VYO5}t`FVt&IaC#fiyH7p~;MvwV z?`E85>fb{DDf)NRf0?*{t7k`XKEZbq=STDAVDUFG?#Ie&OL;X}#<=h^lHQ8&+$`R{ z;=QkKYnVr$s&_ZIH&m}q>UBT=NAmX?{fYFihjULj_mJNW^loRLXFxAfuVMU7g5zU2 zwubi-{13))M|G{c&^?iH*bnZH)M)^{JLp|%o^Ee`ox*`tF3*+Zxv%)QivJpaORzhW|4sNmSzWGD{|VOj za`x@L+5N$8Jo$0tN6U9h`S#WSp7neh`EBH9ig%%S+vu;W|7H2FiT@FQ1O7nq?iTN0 z^;nX>^Ti!)olk>jPk2W8T>t6T*;4#nZCuW@eh;*MC#lD>#_=aOW~ggZ{nzW?1>XPi zJDtDo{7s|Rk=}0TTkfCsCI4GKyDsqF2fGL855OM;&r0lii~p7Dhco!?q<@k4XNZ3d zJiXzWsP3z)`%&_2Vqdu)zX!e-`j0qA)7zZh?ff3d??`_09PoFo-(BtVyUYK2yrBK|1+$@um0U-5SZf9H^IPTo}9d&TW8uOIMF z^V5!>eZ;v1J%@aAb!bZe33i8@5A)>nqW;y5Lnrp{vcCo0S02NS*Ld<#>b$KwpAF|@ z#&N9r{iS{z$@_Bhf%KcuzZCvc;2)-*AH%o$BI~-9a{-*s>+eBtA^)4AAED#;?auyD z{`%m*LQh41Q|GhbdQ_f+`MIpE&vBX;_t{4u<>zR4Uzg9a{I}%)BkOYq@4sIquifPR z0)7SK-(3BFVSfc&XPHMQ%lA8eH(F?qXzA~7aNH^0<>GclH%FgZ+P+GEUwMp_?;!b3 z;kOIFKcSbS2gCCKJRQZEZ(sU@{#g5LZ}C>=cY?b2G9Pw=|3~%yjow6f2CM(F_>0K5 zfvdUvSC)Twcz$BHm3R&9r=zUvM_um?M<1}hpBHzGxM#87l>MLdu3EZr&3)*`=FNrt z4B%%4`hUAmnlAps;_m~`s{FohzU7&nOB=6~>CNTuX#P&)uM>ZD;kgAppP&BvTarJn zzlk~zQs>F$!FlGvy2kN8>VGW%hw%TsdYz?ila0%{VAH9UwKo7>|+S93t)w z=$q(r{N2g_uIQWgi?fYG7k)QX*ZJZc4#(4Qe8Il6`2XeaHgRs0*IoQhQHSr$|2}Q) zYu4K^_1@ID%;x`h{^zr6&+b?CIo$edtbQ}q?>Tl`8^=@N?Evpp@;T6WPtZTfdY**e z8o!1-){)13_~G~_=#TLAaesM^y7l4z8FWMbc5pqitGG?XeGZP*;XXv%Ma~bK%4>l* zui_i2!+G?srgtZQhw*!rxW9-yPX7k>l{}{NK=w2EuLu9S{BO?xP3V%Y^G2(~#_BK+ zor1Q5a}79;RkzjDWk>b6Qa$d%ABgWl?|1YP_P?vw_Tv9#yf;GMMvqp9)#2-HejH|g zEDP^L`02)FQ{(U&Iue}$-%0RY&))!X--NFnd`nuVCs? z$e$xW-?+_$qo@8m^dDzE?#6B{_}7E~0(fp#mpAx7lmEf$vb(x0A&+3UqH<#xH@@yurcddtZ;?043D*esH*+5=z(|??NDE;NcY3079<3i^Ib$?Twv&6ZZ zpZEE>1g?c}4HKs&yYa?xKl|94__yhe!|#EAKpv0tza4)&`g_G`zI;5w{y9sWKH?lD zuN~yINc>CTc~~AhTHn{R|CIeab!=uHyeLoK81R-T7_Aej)p# z%*%b)^<}pl91p>vy62ca#4q{9Mb=PwsaPp?{|M-ikC*Q-=f%U#k3ZnvjrerbAd zz;%H6au}S=;k?NZ&1S>{Pc#!f9{u_HAervd%m*-*ZmS)!ieHhN&)&0Nhf8qBS z`MwNa9qWE5yG@*@?jc{^d8oHI4e2c>&Qx{ZU)`TFo;Mr!dB%M{|6}=kL|#MWwZj5? zN(A&yvS4_Sbph9ZUXzQ^=0&2GMUu|4q0zG>`94r=jY!2>v5n4{s;lLUp;& zJiko;aQdz3FU{|b^1EN%j?llKeEt%DLPPbo&wVf6_UhJ+{*U}UDzE(Bz-@5uO0S7H zuc*Un^zMY~3-VLQui~c%`B?d`BHu3Zn+j(``5rCbjnrcg=eYyLT}|B0_#NiD>M3~* zm)E`4$0O|Tl;76uy2#@b>uuvjbX&Ow7IzbPwvo>+@;O!>XBgN0@CV~J;By zsPp;e#|HY})L)O^uh>6BzA}F+zar2}ne>mcUiS8W;Qr#*h=061y74>G zJlWPfS&rUtdY$FDsXVU`cNgPyvHtt)cUNp^U)TQue21E6Ys33Kybr-YQ2f`?qtTD` zAEy6Oads5vR`tAB9nKFf>wjZ$&gSPleytAfPtt$Nc=wn84DtT7-d-?{zndSAsP~uZJ(hhJ_G92!8IF(D>o_=`B0rYA z2l>zPd!3(A{50U_es$ke-e2o~2j0!)*UdQXs;*b~T;*ndN#Ou~YWQg+-#htzoBqlC z^pn^9@|wxd|KK^-xa`)_H7fio!~eu8{*L1PjW+C_X19+1#`^oRyN}(D@L%qIx0ld= zy?dUzBe?{@@v+e%irGcwdUtBezq~~R)XhwIJ()72B`b@OS^8C*Ol`8 zg58$(tuyc&_spp|^zRK?ieuvV(*F5V7=Mr#k zuKzFi@5LX2A1F?D?-$-le-HX^tIO)fZ6|fxN8LWfcf>CT_vvsSY~IWlX9%2)<+q1= z{FmKL@b3fv5%lL*+Ta_wQ9u@5|4_uZlkc-ahKGs<_XKdy71J%3~3F8QMp_*BZa=_VDz{wLN+L-Z2#PW{dGZ>{e4vcJWAs1H{Ycuq7P^Tp{*Zy^3#{K@dF2hTn5G_{Xk zK>t7H&wuzC%}*zE@0{1j?Bz{WNA1a@%g?p*k52Dy_eq7;(a5J)9F2r zZ-?(E-iOYU4cOmCekS>+{I!*5otEy|=pRUaf;z8jJkQ5(gntD+0{zo?472{`>pwyN zKK#wLe($$_KUTk87P{}S&%aM^ZR36cy=~|nK<_1Sx59sA{OaIG;WtIEk^kfBwvX%S zYpjRe)N4QfZsYG_=a(zhWr#fQk>`c@%ki`2J5Sx~s^fTgI^cJ3p5KbUn^RBvpV4nA zzY+4=*SNPbAMRv7n|){Uv&c`7S2Ne`--U8}!=@fyefT>d9|zR7;*?f6FOzdnES)NvvGBjmfB zdOWHgWB3`u&xLUIg7X{nD)cAsQ=V!5e=h!6;!lRR4ZMr=-=x1E`z6%(S^XpR*OUKP z>+a-L)l(fh^ZUMia4UF^x1N8%4>s=};HM`X|AY4=cz0$0Gd~l}p8?jxW$JXjy!SDG zU*NZy`SCM)oIK{s=UVvxmhTqqhnknYth+wc)wHq_7-n>d9@VpZtJN& z{d=5;FQzw<-Y4>yE|0n7dpU0mHE&**?_ct|4xa1C8^Sk({buk!YF>P0UYsYte)4;V z{u<`l1o+#*_ZGWJ>>k4p!QTkylIlEFyy5n}-teps&tQ3WwlDlh?_zqV!ao)MX7U>< zzfbKS=NOmI>F=vv2hpoX?|0*IHhh!xuc!agdGZUaG!B)pr8B)!6m?|Jb_=uq(@SZ}_xefB~o&U=q?TC@Gb$@Ib3fO1eH_mI_Wq9jzTZCHW3QRN=M`sP=XKrp zy4T`a{QOD&R}tr#;v6eqo5|O@{5*$$tMmI=c6KrUmH8En?_>Nk@@JE8Dj!Fv!`;Q% z(mGth-`CYy2mQs>@FdG3=Iqvr_rcOt(S`JU!mng5X7i0I|dvGUgcJKwpI z?*aVn!R|KVdO=(b^p_$35x-8>-wS`6@Vn3J$?reP%SHTMj^2NSAN>%$9p85Nt~Ecw z{AB+8z@LZJ-%jFRQ(T9$|C8|}JWuG$P8W8cp`yQff7f^~_c;%WcM*B~gMMdx_lRSv zI1YW;I#{3m*jbsK_wbF!chD+6k9KZ%)_+o-K4Sk=_SgQ_c`5E5_+MrJa`B9dzvAd2 zj$83VUi_TKpT*>VRkW?Ru4V64b^4C$+GgtD z5&HI|Zw&sMk#%{5yu>My6|@s`hP|L+w>n# z-$UegC;xZ+bFA+K`)V=g<<|1BGe1{-S-p{K!oLgE#~|aQj1MILK6_7#??>n3rPg&W zzBBM|u5NZx|6T3#CHeKJ{yzA+SkKuI49;V~9BZAiiVhUzvO(^0ylA zgLITxmi7Bd#-z&>PV% z{C$bOo~!c1{3`rzLVkaFpTplB$baRz&zJoCRq9gzS@%@t=zS0(So4zThJMHgi4tHUCGk_lWWS>iSl+HF_%lFXjK9_ScX0*ZKU|f*<$G z--hxxU;dWl&rbYpA-|8Z|Ajg@k=!}t{%ZVE(xnKo|mUf z3Ik}kYD%lXMOS9$IdSD&{v#Gi}M-%ef9hC z^8@`$=zUCFTkxwPf1aT4kMuR*$C=L2sp4%Z-m&Pn$U7J zV;$eL&IgNY4dY#mpU;mG{8^QrXX)vL?+*DIzY59KO6A3%eU_N ztxJ1(`Hgj2_B+pu+R6pK4*0&6hdqo>H$U0_dP^M-lIN!E+-$xFe_QgmqxskIbtm@@ zzdP~&bN2e1Z*Bf_e$Ev40pwPbuh#s#N**?%m zfL?(P!v7xr;o|*_p5xJ8;$DLO=j^9(^!z~X7T1lT@_)5;yTrQfgKs5#UHSO|KU=7i zL#>B5`3+`yU(i^b2j%_yO1{rS?mPVF@5JA``Fp8+y((yy zYajg)-+}n@EyzlE$D4W zZ&Q5xxqkmsJTt}f89G27R}#` zrmu_nZua#x+--6`~~A+LLr|BKIW zZ{kNg@%1uirbKJ(Ay?zc0FfY)9|O^#00xN7tDh`M)Or zH>T$=^c+CXqvVIG!>^1#!LL2VG0u8#VLh(q-vs_GDnFN6m;Kbw1nV+@og3pP`32-B z^Y3YS`T^e&@_Z0_tbATh-v;zOD$lFQ#~;NtPh7|IZ%cJ_IsN15KTRG#mB%C1#e3@S zR(yNnYp32HGykOgEzADO`cLWKrvIh$@mO}QW9J-s-&vfk#dV*!UKQ`Y@^KRTec9ii z{^#kxT|F((KLP(%_!nXCKKr%}J$KSSpZ=Z5_mz+Ldv=^au1J z@o$9wS^o`mj{g1pYD~|u?7huiKlGHuB|o3Z&ouG2=Kl%udzSp}jeloxY%AWC#5<0^ z-}Czo{2i_LNd6t|e7(aukCf+~$+sdull;Nt-@?~fUHz7wj{4hJ?-%WdebGf-*A5rg z+46IQe2;Ox+>ri1(*Lx0_Tl$?;yXhBG2^?-%Z=={vOlL8|6bhv^tWMufpuSA9QU%* znw=}~kHg=V-nZ@RwdCtH`C3Xm=kaGfd>7$=iGS_IF_^uN_}wb&B|oF_{|Wyo^nY!= zFLa&g?>Y0;D_JLT-eU$f0e#p7P&kk^J1x z=XM(zf8F>fets#wuj$)IT;1qDf&PB-H%uHO=sDefUzR@?xel#m9bcB0;rK4Vw>|l$ z;%v&>yTnSNT^~7gXOa|x)*wZeD#&DxnHT*HrA4V z`&j4Q`TYpLcX-EpWO{x_?~(G-1ihF3i`jofy=|sG?o%hL@b3fhT}97!^z2IigY>`b zR%2)W&cS~y{`1UFG~eF*0a;gm9z_2#^7n%E-+`akdcU)e`De}VX}q=leY7~|SdU%g z?OW^dEBo&(_I}Vm$NQmU_}_b>m;Bb*qz<~uH;P~F>3M;k*~V97?`-@p;NQ{wBIft8 zUM<{LJ}MuR>0J%~EBFr)|2XH^b>e8u{>AjKPwzzi8^tvTUpMv|8owUBj{eQ)-;us| zsfqL;J(7xvFGzYhDSp#9Oo_-p{P}dajTA z%kRK8Y@w6U@y73QeZPwRP4GXg|B>f|lk^wYZ^+Im*5^ff4yC8LcvcY4sm32;zoWeO zw4P(wU6fxJS=T46>$q>dhjicc4|3O#TNHiJ{ISl9ubs2Y^Y3B$Te0_$_ba>8w*`F{ zS)Yfj`w;Xy`*vshAldmg4Kf-Vf+c^v!Voys3XC{om-X@BZQ~dONz` z`V&3-@&6-qBDy(0{)zAH_IVrU?}LbEf_QGSZsX;7U3z=~;BN@$QAMK$JF)*N`<>Z+ z!Tg={{?<9YB|8s_<5qH;7;h||>&5da{bz~yU3nN{eb2I=F5>5@^7M^!bbb0Br0*nr z1MqE6-xuD$t?j(%tv>c*zX$v4tB=dculk+)fwtl#f0X*ZkeyNF4^+=b<3HCrjbQgl z{&wf@Ky`Dac&;}-(Rc@O-zkrWk$>C%UtOMGmWQ|XccyPM^DD~t-Qw6%9MkDvmi`0y z)yMg8iMYq`e|h!tqw(dxb#1Z#j^O7Aey&9RX8Qlh|3S{XDf04+ybQwkX&d{Uo`cla zarjPSXFGB$lbg%l81~xfzeWGO^zV&-BYF9o`A6BEW_&gCAE9rOAIk4j%@2~dEsQUR z?Q&jfmUh<|LZoBVs-dLG7~md>$_ z@h^>k19|wK{I2pmEBjVli>R~d{A$ae)6juv1AKGw^&x4NDU*Z+&W9mtR8t;<>HpVY_agrYd zYisk3=)H%Yzth`&CHJxR$GP|#;5!N5uialzx^2WGzSpcz6Z~h%*AV{gD_?ukJDA>2**}f_{mdUE|DEXXq>dZO z`wjBElYD;O(!DeLx3GT-zK8KGM$c2Omshg;BfG!n{{Z@5Lc5`(#ot2wOVamP&IS6~ z$ir{-8}onvR`TIKbw2w$v45g?zgKTP=sTXiS@_npo(J-C8+m#XUBmb>^p2r-KXf4a z5WWxa9YpRW^M7UMa(31-|CPM#iw?jyK^$+1;{kDeDvl%6!>`msNAsWIYfgR=`9bfv z?`-WlL+|?Z?jV27%*L~6E?Cr*{O>uYG-@>`KJ-yB79V1Vd z@p}Y)f1__G`SZjxhrTxSJw|Rix#Q(&v^;%E-*o5Ucz!I&|4qrYv#(Y(zNpXTziRJ3 z$9^7belhdw%fof@^q%!RS-x9a;N{x7M&sXwUrqV59Di2+)-}R;Inew%<_GiREPmW1 zzV`C+IzNu$#~1Q&y!gB7ze?Yl^mL==Oyh%%w`T8E_uB{3JC@#4jSnzBTRk2oFGumC zJGsBw$Gfm|yJH}~H1{w&Td~tyK2Da8<@o;wxx>kKMEmgXJ@H@4UPJLu!9UzS-CG{p z$>R;|-tIauUmc7hzmzyy)4vC~=DrWGzx8;>y8lgHFL%E5(=QH|d&^M7^4fr*W z{a45@z_*R{zD1ndi2E@9pTPfH@NI|hZ1a80w`aGx>&D{zxC7s0{7axM_2;9@vhzCs zm$XjjSl@Q&b?8L%Kbh}K?@)T1({rtJWH|lb^6NO~-f85nXJ-R;I*X^9ct*eDJ&kxD zvF|!qpEc!qb?d!8_dCer$Mmf0{8@1o&rRijE9-a*zJJnt9ltN2Z*BT|$lqt`c1eER z&5x<#zF*uc^Y2#vU1a`K`Q4xX(eymcUMG6)QHL|zh|jv*NzY;O`mwwYV0Tyh?PPox zI(N?}{}%bF=os{Rb|&(BMeA|BI{T-*AFWOfqHhp=dx+~j^JDn;Tub`o@jd?TjPGXG zo2~e@raEtI{6^yk$m<|^T9hBR^5Y72{?5*NEBU-c9MAKk8-I83eTvig_Yd+%i}xM; zhsf)q?0&`Gy7E6({?Ft8Va}Q7@pZ&EUEZD`-$VQz#ebdgH`pJzwVsppR~N^4@~@E}Y@NQ) zUtj+P&uiw1cXR90o1g3Q^I>`1gWng@`w_hlv$ulrKk|RF`{s45`&@QE!FMXYp#SgmHGD$x-NQ`JWQAWnc|x)zCF=x(Cg`KN^c`|w=_S$x6Ye7&kivD0=bFS zu{xYSi}5b=BKmMlbvqyTi$l(*V6pjg&)1WA30Fm zt6SGz?Db=B5F z`K{U6*8HRVnoO<}`=jM?g1D|He~a^C{co*>@w55;gF5SDd?LNA$e(2&oXF4K{5&q> z>SI68`)1i^&l;a;{8;O>l5_eKc28$_7`Y$FEsCCq9&aC?j_*NvSf1TG#W_iw*OK4F z_zCQO#_oaQJ4$>F*g4kskSEIDjq-Pg{4DAH{f6YPlE*3hpPqG5Uu~@Omd0;3{sw#N ziuV$E*hwCa=T9I0EXU4+_Q&S0`Es&>k>A%GIbLKmt zZTb5YeGU0J&;8IGeqPMaw)Eal?8@3iTu5v zzelsTy!@|9UsG|-GQWcPZ`j$wez@gj`-PtK=^rlN-TD8CJZ(+ybNoJ;oq^V)qkO%H zeoo&B{OC)5Yv0paQ(V7sUacoD8_P$3aoj-v-PY%Q>+>18Gv)a!^j!3D@;j5?Q2)>L z-7B6S#nZ=nb@D#B1-hbm_NTv>yzfNs9`v5X&yDzbn>ZJ>-p}hFr~fp+r}O&;elPF( z_$zUa73VGNY|PGQ^lvYo{jAq>@^im@cXh7bA`jc}^DpANf*-TV@16Za-^1iCC-*eJ zR*;8N_;V}1?eXm-uTRNqcXpOwXK((FFn=$9_TkSz_L^!(lUt;RdC_k#HU zN$-jL`HT23)9**`$L9OW*QxR~mAx^!LzTiv6ctCr=R1ZQ^+q|8~xs)5ZUp_|LQ7F2lb^>cw^FVYI#Rmh3#tj|KGY zZ2n^MUyyIZ?g{L+;s5>oy`KMX%gcG_ZO)%v&<)TT-P9937t%9^zYUG=<2+oSU+ws{ zJUu7T^D29N@r@Awm-w3S_gwq=JnMX^b$(giX35*0{Cx~xGx>XpKP%yX68|gsmzLi} z=>5F4_Y>xi;djGt<=_4~LwpzDzn`5I$SG?Z4m7Q+ARL+$D!|_`{M79e@k_=PQxY*mSy*0alR{$C!yC{hqmgv zCH`IUuOyGB$>WOh@RM`mDC2J$e+2(@{IiXBvTp{9_cMBr71xR0e@vx!N%6cRjvT!_0pkd-(t4I=nW$XV5#0p0DYdA->;>ZyEmFs(v3Qcj!{~+IQ|f z{QIG!DO1=gjuW5cV6gKbOAOtjnt8p6AzU^c=$Pjrg-QeJ9HM z8qUAQ^v$F14D0c|>&Xw~4!}Q@zVqq33w;mW4gYuQW19ZM`WL9DE9GYee*VqAx`v%a ztj8Pl9c26vdJba$D&q}}565?k`@C!9@gsR`%ied+_aXc`mtPl>TaR1^dAUJe+RD?f zouAv{yWIW4N3Ync@^`U#$B4JJy!{0oO7C&rr@V}BYxUGk{(8yZ@$B9#U$4o>Y4UNM z{*wH7oL|d`Z#sR;)3-J~ZRP(&d>i6>p8oymy}N<$ONg@rKknkkV%G0Y{4@Fc6o3DQ z{}%k)>EEG0kNw5jzk>gZyRT?ud|Tt~$=^YK8oQf04_+33FXN}9SIWa4Q<>nQvAWcq%Pk8j-fZ7;4(@t-CiAM)pvoIB#!l3!=x`w`z1b{Eipx%HVR zpUvo_d7wfUFJg;Q@b?bS6bw5yl75&Nl>F$221-|3)FJ(U+Yd>AAUanRz6U5tE zydCI2kp4^AAIJXw?61oHo&4UB-)-o-kKXn4zcjz%cVzhe9shSk2hsl-{RfM0u>E+K zbs1z`9wE0IKi)9Dn)e}#>)%dK7y931_X6v*m34ZLpF`Qd*!*bxe~^zU^1ctg_Waln z-vE3U>#wiB9{U^GhXbs`N5(G|_mJo%|024v__h(>A>tdt&aP-@bYt^>Hh&rZq3B$G z&E@a;@_ISDOSAKXc+cR^HP-PQ`4}$VdE$FZo|hNb{p#!!ew@jV_V^pKe>#0V=zF4} z>#cL>$ZuE{oKR13kyJxxI9EI;2dS=qoo1Ufg7w8{ieqDZz;CEAg z_ojbAOM74?*A?|UP<$iBcMSc|-p0=M3#=j=N-oSSTd;RJA(0q^Y+&8wh=KMa3|9`bEhgp~F(8bW3=wHyh zS%VMwFrqvtE}TqT}U@$JU%DfF~tZ@&KX`rF8FQ+jWq?^E_R zCx4dnQYxn-RjlR zPW99+dR^b8!1uW;e-g_9aJGJQjH>5J9@9^*)4mn%f~)G0v9vS)I6MgVz(epboCkNn z6)?4_>i~50K6D=#TTh>;ryJDM@hz<-JPWtNJUG9eUWP8#$~f!|N5E}0?KhXSxB2@x zjDX~D6r9q~vmA6Rl=U5_oi!N`8^Ok~s`xiSH-*h%B5VnhU=wo_pppGCSwHJC6}C3E zEo=w7>hA%2!8kT&pqm?SOJ-S^slN~G3oGf*Ft?xf8rsS2ZE!mzmrp=yFl!zD@D-lq zxA3gngunDmF+UOg28}PfpgW?IU@Ghe(_nWvn7xb8z2F$_OVGVxCd`7_un+7D2f!RS z3g(IF1oT8$k=(D)lOT0^8hSdM0jZJX?0)UkR;k5hjCas4bVPkW^;6oJa}53)Uuvw# z6rR>KJZTr6vJH(HHtbe;5D*VQp9omR{9$8SMm}VLC)EXJ9$={xkBE$fe#>(>W_r z_c@bN?>W;_^RbZ{&zkQ8*+*$dwg*h9X=i*S++0sH7oO-Z?bQ8g4J)3}+Sy;(!v(S5 z(R}uFko_EAHmXN-Z2}V@z7%YxoqfMK?W}S5(=Pm}m-I89b{S9o6xp;3PulS@{Am~dlJ{StTi`3meA}H*F#+E!!u4$)!%XsRzw5QgzbFWawbHaWljCWSoGyGVO|Fuo zuE|P=9o|qGBVoYp_DRsE;nKZ~)kb8k&LhQ!pN!{FYbKsIobT(2`&>x1v)^H4* z1ed}+FjVkUVGo!C=fFI82mS;}%mwgQXd^-GU?2>K?O+Kz&WLx+A@E2&co%?kd2-9J2cw>9tg@te7Cz!Q^yujh`NB9XA+tK|eYyy*D zHar5&El__L2R$rMUpNlVg)8B1cn3a&Z!BJOi`NM{$k}8#5N?B~;bnLOTGp74EI0thA1gi>cD#Ejm;#5Lz+ZR~d~vG5fRpSkmY9>Kkr@d8hdIFL1%B z?)TshIQ=yD0H=Ge1JA$aX=09HIl?ZDme2~0TGzx##BFb$rDFX4M=c%Enb@CLjM z=bZ07C0q~J{@U|%cpKKZz}iD^SO>O&4==ReE|wFx3hsqR;8}P9{t7=r%S+T1bb!II z0c;8Jqape3||C1EL88YaPHm;yV)RM-V}h23Bp><)Xt zp0F28hZ!&v_J&z78}@;HVJBz}ZQu!b5}twI!b|Wfyap@6Tktll1S>-aSQS=-j?fv_ zg09dFx4gFyljDZc|TlfyPgsos}I2w+DnDed$ys?Fxq1|9H z=xqHSu@lj(S!X2etXXMi%}P7#R@zyk(q5#dowX|CS+CMwyr!MC zE8|(i(#~3zcGk4Cm#%5Y&#ZHqi?6l)_*>hL&$a#dUE7cEwf*>C+fNL&{lrn*Pb{_l z#8cZ(Ott;QRohQ&wf*Ry4kfY}t$AEkX(&G=pu zejaaJGrmX7`1Up9ITwnaTwh8%*N)Q8wWPFj{VDC7jisGyXldtqRoY#4`?=ksMs~=7 zKaVfrMyBu|=$cg8mpQLXd;1#x+c*7r{Qa6(4ynUXzEt zYHTf4!=LL?@h8`(v^SpUGsYdN>-MP!SMU9~(O?wxhFq}H2R8;(1PeoJNmN6vahn2vVY2XoV}5HOf9DFQ|q0%b`@U(X;iq>douAOv)HOV{aK5oA z?z3vo=8xR>eUEQ2oU86qd+)2c)LLpRwY4IheT0#94$V4;W}QRVhOA-MIkZr8CAvamFEo6i;R}tf(C7+{uF&WTjh^T$?V>ld=pL($ zweA_#teW-~+8e^=kY_5FQ*G;XZf(9)d^VamYQ( zZ{TTo2A+lA!gKHX$nM=HxkM6{mc4B=5;&=LqHFHH*bZ0EOGM|3h$>9z4{W4zqPaBVX z`X55kSLQ!Ami3FS!uO?i=0i&k;#+(!<5{cl$Jgj6<6mo+7#5aYxF6a0pYiBOJ;jgY zq}UFPoo696nD*Up8${+e_3ezOp0cK?(da08(~q63U-%M7YB;$l_G35p3twr+$J+eF zc4|EQ(UJMI<4cJrKBeA+l8fiG1VF+7QXOgtoT{lna}=-&cu>>EB)w9 zJ2}YOq@A(wr5)M$`f+{xZM5jfT>7c8LZhR^6Fu2O^XkWH+leFc3-7THjK%-VM^EWT zSI*0vW08x^Qg@k4KWmnDsf&__(6ZKrQt=Zu2nbmbDG}oNa zT+c#tJqykCEHu}%&|J?#b3F^q^(?ep&q_P}(k{9)R`jP`Y@}W6q+M*KUF@Y@Y^Gi8 zMt4t$t+X>&+Qn{Yv72_W>lUWLCNK%|Y&CQ^OoYkx{aNS?$TgRVe|rYFmv;GFAv#Mt zcGidJnC)J6Ns798-jr)xo`dCnr=|YA?t^j<)oMrY^WEFzJ}UZSe|kO5cOcS_tx%$M zbR0SY#@Ev^^)$~XCjr%UytV!49u3hQ8r_BF9wRnFV^-DYUN;@`7TWJ5* zj^5ahzR={Q_!BDhy1Dofp74hj#OJi*M;QxEj>}xyWvuX(v5aR83UAtFtnikxHmh2r z`uWn1o>CLxFYWO)?ZjTjXK5$riL8i6u0? zg~qSY@NWs>4-J24_(Q`V8vfFsizofi#LykWADa5w8>ZACF^+`97JaFc^s_%AlljOc zKgzl8*?9D(PLi)_5dX&2AoincAE>RH@TDK!v6J=z5dS9E)95QQ;Y&MqVlVlKPbHp= z2eB8rDJ0KXpUq%DNM52l`Am$F9bVJU*^>7@S-;Re^)%;6`Z>cw``6O}^)%;J#&c$c zMo-V0cF`MJbf;bPr(JBMUF@V?Y^7c7rCn^MUF=48v72`0;!oPeZfLQacCnjwv72_W zn|86AcCnjwu^WBGZra6e);0B!b=#nc_cSo$nyRL9Hcfyv8|LpqIXjIHn5R}6Sv&M( zG-pXcY9a0DT6nCC&o-AjD|DZx)*V9g{ie`8;4moTduhK1`OZMTEATSB0dK-v@Cked zYtb8?jPKp7!og@Q4%4s25&E?_NWT_`>(}By{aRe9UyG~tYjL!GEsoN!#j*OeI8MJ7 zC+i0%)#DU2IKCd|p~30(n2XlpCjDC6reBL&^lNdCesJ$XctwA6DD)xiTHLQ+ix>3^ zBA0gP4GSUPT?-zs$CGG~v3$qv>3Ted22a)Fd9)Vm>j(J`T-^+MyJHD3jf7Pz_f8q8s*Y`ss9~!-( zWj^typLj<=_(C%un)%SokA&1~XzYfDKQwwn*Q=+IPe1aZkS^ZE@8G=O5S>up9$(YmsHVMfO?#7?cI2WbHgaY}e`)uwX&0ZuQ({cJ_?UL& zvR|SjH1eU54UJrAWJ1H=1;QU1{?PD;hCej?p&LNoP)JEzl`hKatGX6_-)UjSB^YJ16g~q?oGM;u2`=RkK?UMha}@zD4eT3bVDhc7gIq2UXSp3vkk?b@8B zUviXo)<5&1(G!~a&@vxALHLT!&{A6&-vA;X8u`%3hlV%wU)4+aYU?8XE)co0{-NoY zYf)(WeIfjz>HkumGQV&<3;Q<=&qgo-(k^FO+T~2kbu4=-H1fHAr5~Dpu3zbgrXL^E z4^2NlrXQMq@|AwcTk@EG$!GGIe#vKQH2u)X=Q^8yXwe_p=#S3O$cC02 z`OyE{<4Yd?z5LYHRBEZ*w`4qWp~-7#$?GraD{|$G=!kzD6xu~Qa;46rGyRM&+%7z^ zSK6V4KkdjwHtp~g-9=a7Nk8q>N@=HlN?egEv1Y!s3s2FL@rBz(Hse9zNjr5GeZj)* z!e44O{e|1n9iK}bm)wW9wq0b4&eU~mmOWB(nDNpsYZbmeX*7C(k?W5 zN;|Z)V>j*0N4B&JPxKerVyp0^pM6&PrJa7c9@VxJSH_FJUuqYg$i=sE?q|ICTINE7 za*vYvE)e~-?Z{?4{+98y%XrZjp0qPwd<=iu$xZEe+SwyTPuUmYFZG{xso}KCI+k{^ zn|}0%2F3rh3r}cims-kv$#dFeywpHh&r;{%$@R79DfusaB|pW_@D={FYsYJ4OMaq1 zx{CjiDedfwVmIp*zFL1WSGzZp&x{2n&*3k2(+*F3dJz(PY3H0yJ4j8YpEzPS*U9KC zG563edP<(c6PsfpIx-#_o6(o?%x5gRihOFKww?E13;U9>h4p1@VV%ia^k=TLi>~+( zAD@9T))ikv4=@|SAnn1h3Jis|#+HRVYhD=^O zEDDQ3b7%pJ!xGRET7j>^H1PG9I=}O8&N^uS5-s*Zi|_F*e#ig#9slFszw&7{^J_vE z$g|CEunr7?bzwVL9oB$h&;fEC&plyi`Wx2M@%3~gwA@Q(KKGI3elq>sOO|`e^e5E! zL(|{1z8|^@WPLVUh;Clr56!yQ((q5LpASvVW&U5$@CTh1LTCM;%RybJP^1d|4zNkfC{aR#y)FS6&Epn}{Mef0Bk-bxkT!(9sbEXzK7iy7n zpcdI${bd?cTXlyf9|D*Gw> zD*JL642Kbr&(_w1Q7{_DKt9`AA2xt-up#6#t&Lz~*aY&q$)>OwYz`A)3)m92f~{d2 z$a#`|oHNBo)pcjoPUs|<3{zldmAvzFaq57-m(IoWi`XK*uNZIJOcSn z#O09hgyj2P*TQu$4{m^b$0Of~xDD=rdFHYBPnl{=D{R_MM zY3~oI>4VVb#t%gggCpQtxEgll(>3Vvu(w0uRP-{q9L|FW*?$NghDGUE8HUq20{(aJ z^GR$>hX39B`M*Ov|5azxe}`E9E6>`*Q|c_&k#hZ5_`31`cFkD0mU3PA_xzk`K6~ds zay|IJG3VLe3tz)a?*G-b``=rGh4Vj^{{J1<=CT$G`@TMV3;Un@j{imTd#)RL%Nqdy|H^`$ls?q3(kgf;9SVx z>^dKQ4Hv+La1mS#mq7lW&1De2E8t4F3a*B0;9AJ{K^Ixo`y{md&8pkA^S8k6g!z!a z5p_4@?}gn9`M&9Wa6dc%55hz6FgyZ}!edbW?$wjpPeCJgqBr`YEAx?y{Qt!HKkNBF zb^G6Uf4*stz6Bmz{D+=-{kP|9A`a*ceV{M&gZ?l82Erg14C}xU7z)E+IE;XiupW$p z(J%(a!Z;WY8^H-z`#TBf7O)k33OB)X&=dZnJ{0@0{jcoid)2XhI>g>Y*b(Nz1b*Ct z<{4_9apzfeo*o1rp55k|ZJtBtS#h2R=b3Y!1Ls+Fo=4}o zc%Fmj*>;{~=NWXKGv`_IbhrmDgBeil=DBa|{YPyrY$rCNKQRBXr76Oc7oi8dJJ6G&im)m9$C{~ucn>%d4*?mO*`-V%J|rt z_WCvLyq7IJd7fU{8`iY*e7%h4c}Z#KeQ#-RQq!JL)6R4F!jt#NrJd*UrJd*VrJd*W zrJd*XrJd*YrJd*ZrJd*aY3H-V=+Ebg$y4lxCQs25zW-DA_sMnQNZvx@SNsSKlB*zd z@hyIax6tsWpBU248XYXIaggWs<=K7y*3*_g#CV3%^6z@{H$K`)(EBII3wot~hh2PL z=poek9+r%!^dujQo@|kB)?Z0~gbyI{cbev&>FsjKm< zQ`RhcOFO=o_6g!D?fiSj(vGdtes#Z}+i$T^#uJ>RM;ib;c8l z?BVqIw-&HhSiN4ywLIP4*)(Y8lc8n&l4kTYulv+$ySmQ|>RJ7Mm)|MCnb~}z6pfeG zohpksW17~TC-JX^@q7~HuU6N!@@ZS-;#q#TEuZR*Z}2y0++f89Z?Wt5fa-Qi*cXGQ zX6{5=fxq?dztt~2RiB}+LDQ1ZhAqF&g!||^j#dp8H8P;i!f07r+Gxqf>LTC3Z2HM{ z6OlH8gX=y8TuOV9I=Nmf`)f{+S3kbBY_M{JhpMjsHpRIjnhdnSd$Ld8Hs`}q4cePq zmfoz-7_G(YKF6=tDRKkxw2>nhA##2d0?lhPGRdpoAJA)6H=pq)&f?1ZrJVX%&F~b@ z{Av!KHg(s7N>lWvRw|1_>pYB?_GX>ttk$B?xUL3TYeiytI^#_<8C_aCxsIjsYD{*O zI-X_f*P&X~mURpR&334>xTriuQ(lYK$B=mP3T@J0xjL%0@I)RS}Dcta*K|G`*s=yxQ}plTJ0(T!wyrWvudY5c%eHo-F%sW21;u=k(p#W>pr+X{&*Sr?$&CNIxibr-(s^FnW`Q$8Y@fJ zee$izkM`C!o`$>1O5t<^Ek>(9jQK`tE=a;M-N#?4ts$W%M zMyeC4s_Wz;W0i&U7J>G4tKXzSa-7UAQ#Thct6EPrx6_Lku@pN^>UZf<_3Krw zPxe?Vb5(B zqtz>?MWV0#PUTeIE?=i<88Y!awNZTWTSs*=kxYG7wU>%r5_kBkvnczsl5bKc9r@(0 zs)QCQBz9xjuNL`l;!X7?hw-7RxXiVv>l>=mpVdw6R+81ammJ3@zqhT*Gh3Yy(N#s> z@!#~cF;eZZDwEaf=j>dRROT|$6qHAmZKwg9F?u?h03Q&cUHH$S|*}ordr8LN3>=i zR(r0>Mx>MH?Buj6E2SzDbCs!_36;L8-V%9ut5>O*`mXUM>-1Iil+m2CmDhNIawS4%msifly_adn=VAQRo` zSN>GBQgKFK#aUHYRgGDtM4$W?nUX0(b))e(cEgz!Eftqu#`3?+X7#JOEV`1RY6Y6| zvpQEY7j4-A)hg-yn{--Lb{DJjDcte5@;Nlu!K#{Lwdxfg43Mt;OHVV7n#pXHv8q+^R<%@(RMwM|R8h%F z<}y>Ae$kNV@~ZMz>=r9keAT^hWjoTzK{(2~#Cq8uRZK-&xWieUz}4M=Zrzz)d6-^h zE%!1-cU1?8xZ1Uqg>c4B_$ysiuG6n-pt=HP?MjtYURM&u%W6l(|EhM1bXg(l>RwfC zS8EV`dBy9rN@Qh4em-5|TR3u$Sb3cPmP)96t#~Ty)lR4)i{6T(x>{EIF?F9cD09_) zwZXbq*;`#TF}$_k zpVhx*MZcYH>?XJLZ|WcEchUMT?>7hiuG(r+cF5J94LVphoy(th{O%yxJG@cd$6Lpr zy7^4~T7RRwVZ#RRH1fM^blih4$KTbj`+Yw_toajLA6(e=pvHym4yx zoZqFL;Wts)8KvEW4;#Z^e*KyL2k85pjf)rX3IAQ}ckRh1Ha2#&?7)xVbg#$WdUUV8 zyyvFc54w%*#&%DEKSJL{_*T_kj*UiSI~jXg98c5vzIG!vX7Qt;--GMh)^GLE@mv1R zPp*s|jBiUiHo?2s3jXd69UqYyVs3eU9En~brbp!Uw03^?knC)^+=blBpZMLr7yQ;{ zU2dg^55J{nZS;O)FKRbuZ#dfD@6bI=*BX59zlYzyq1?%~=$vDuW|*6e@eLB!*1cOWxRe?FaG7@I&(3wTz0fpx!wzehv|Kc=JC$@_U^ ze(w>;V&eOa_`kvXnDzqk?WrzymWR$B?mbAhr}lm9Y>2OsbzRrkmml~YLVh-3cTIk^ zrSBPY<6D`#&i9+e)5!Xaz}wZ_6XxzWzKXTGko_U;tr34V!?SH-Wjnv0+DAU#7volB z)-*O8-|~FgiVt5KTM6#9rtO#YTaohkDj#@-8{6=6v%Y>On_pjxX@t2A)y!w) zdh>G*JyY}_!Q`t8ro-qz&^ScmRc zI`7T0Fzg0mi;HH;kRzj5m_Q(VzM~VX>~yr|vL> zuZzA{NrZ~62rzbBf1PhQs{*Ab4H;mi}` zCH&k?P5wcgz2QOpH?uvK?T&07(Ae)1^XWS^HqiL(>R=F?@5tYV=+5+fVSOJ^1AX8N zKK3Iwey+2HogQR6X+Pn7S>76Lu0MjEzUFS`-{1Juo316utYPd|a(TP>pTN6-+=+Z! z#XdPoJlEsjL>%3;e@Dl=e7#?++uC=2TGL)q$6}gkeaF%FmbjbipN@YyYcZ7Xr)j@G zPkozzh3xC%ngac-!^-ThZ2S{E58xfm-V1!&*Vq^DHEeK>82-j)U$*w2p}xg`3Eq3y zc}N`Zo9p?mI+KHIjX%Plf%1EsylhFPi?Qp)wjW>K;r}f2mut@=e;eI@CVwaXDeTRp z|1WHh;ll^^$4kzoUe@*zJ})631KRnWQT)r&`y{#+-h0HbjoR&}4)!#D6P<&_u_S#L z)BCP<+Rk`m{_TQ3YJLeZpGen}e47jJ%H`_xuPOJd=(r1AmF;i4`n?DGdOA<~ znqQM&z3uIf`S>W^TN>M4eP4ziNY6v!&+pp4P4)3PU$gFKI^@VNa z@P2!w9i6ABtM%3NopL^m?!EXkho2kLJ=FYA^S{#{C0|?N`GY(@jP}L*$CdrAs`<0o zA0XBb**gM`(LV+5k*C4@AI!&b?2qN!I&?iH&ipp;n&xhn>xq0{$2xpO_p)McLAI}Y z=_=nJ$!{z42Qp8wyN=k_p=)zIZ_u@jTs_RU&zw_#F!lxc9r4T%M@Mpx8Jh!lu+s#+ zpMQn!QpbXmAQxcupQcyp6ki2F6KY5 zd!M}DF4kk6vyJF!WPS%ett;-O_%}iSc9>7^?P9o`><#?urXGKV-p59Jd{0@kYvkc` zb`Cba8~JC{ZcldZP%|I1(S@yb+qvJcK9A8i1^t1aH`BW=o;l(gM%Pd=HDL24IqD}L z`&4?n&?4#!sUozqx!?JHKbl-vP<7@x86>uh{J^ zr{|kHR=+VD`@5#xCf{x3^%6Fom1C_C)DF^^82xMe;?0=d>J6N zb>V$^`_^}STv)O)tTqpjm zjrSOO?zJwR@&3`ee?r#_&hd5FS<(D5;{UDrdHnpyI=*YYhm!rf+&(Mj{(KrNcU`pa zl>4#v$X9p>*H^Z5F-Sk5=LHQq1yeLkIA;p;^14t&ds z>pT9gWBr$+a~e7`Hq_Hb)_N&&7m~kQ&PS;6F2+tr@4>SNbhT#v**X^gu5h{bkJhRU zxua&_M?16837u*EZWs5xd>SJ!d$Y9y9h3Oem(G{vdI;Wr^4^nw+nVc(u21J^xtfCS z2<^q~n||WBlb$R2bt(K)zV5-ZHq68`OpbO$-?R=-(J@vY`?5WfA6?aEe((4e^Q&2x zo%neX`TQ30S8}j4JCDlEhUhxhx3jqs{JK1`I0Lri`wM)0+AzF^8ts$$cnJD5T|>!!Zf+euZE0*vv0X2gujxAve||fDE#pVX`J3V&C#K!- zd~>z$uO|oOdwWlEo%sv-Gv55-_?Ji5$hEtjHP_ym%**`VpWI||jFOk9+3X=VFXLZS z4sTM^%UHiZUFZH9?-pbqz`LRTLC&^+vVT9B?mgX$uzL}i-Rb?oeZ+Nie8$%m<$i5` z+{L%PYUT~G^)!BzSo<3vD?e{o%P;ZwlHZ}uhdbyvOHPiG%V+UDYOQ+V86n4=@bqS9 z6hFJL+mGCne0T!i4cZON4dBZRF^;6;27B;N{Q0N+EpINr5B?0B*Si*+WPZ_huJz*S zp{5_C=PWvQ6!Rj+x6z&r9oc-64;|!Sdvj0A!CS^h+CMGCH=Mpr&3}NeGyThGFK7OB zKJU)vcjWT>-4oQ>#oB*y4zv{K2WS_z&%pbl+C5SXPiPM`w;%ecTwR2IjCQAYCAL;q z@4eLLqkKKo+`)AAW%mwi)eO&m`cLw&9Y5EXzi-&PRo;j4^$s~(5A9~`C2KcJT`fk( z5Bzus|KVz2AM#`5WhCEws-uSTFph5r^K(_Pe1U&VUvm8THNAf$`>?Uq)%P*3MVsP( zR=+Xcd#@#9yl2*z{x)!}@!sb9kRQR`@A3gw!2*b?Jy4p2de)dzJOE~ABGB!nCH;ip{afo%8D8?m>AI;yJ z#L!H8IeI@}do6Uj{y_WtCbjdNwHz z5dV+rf2vr2&BoPu*P;I+xoFMae7>|d{t3N<<>VIIsvF*}ku~-zo{Q-Ha-P3WKvxq! zeUHDP`nq2o_7ZC!`B(&hehWQ+kK{xB*Z6Ul`RmCKG2THQJ|*{8@oz=XE5`RiH$cbp z^-}&UO2;Q;carmS^mib4toeKCd6jQ}+`VCgxqP1^?%T|F!G9$>7~l5p3;M~=U9iA> zQ~PI*x|wHv8e5xh#j=6eF310cSXSiAdu+9&?`=M>>P+}Z|2Vc!m7`PJd2d3`M&_H* z^*sLHi>b9d50m>f`L!9I3F>8QGL!Iq>HYK}>>Y5mYkyCF8$x`&@U2O9U3R}0XDfBO zg`A%#Z?CcU4!=6%87Myu)Z8esG?L@{tm{azG&6s<_Tu#a4qq$2^v1h6yW{NdF4_n3 zcPBcx7RMkuUU3a-$W~uqUJc~t&tlku566*vkUwXdKUkdW@%KG-{5SJ!!DVXY3jUA4yNtPA__!x~Z;tDC=HJ|Bd)=;+UnO`-0CKvenT(yTDpL zNdMR3Zi(khYt)mj>%{Z`x*Xqcf_8AEwZ7VTBlhO<;~xBt>1(S0z@qM3>3V>!(R43m z?oxIJ;ah{9CeDXf_;#SPtGPHj@a@-f`iNXKlkbUqyP52K>-Vy_j+>D>cArAeTJ*kW zE$1ywh-xc=2yDzceGe87V8^)T^s+s{8>|f%_V$$gwI{+KbCLZ>08gUi1~Q> z%gGq=EvD8tXX^$re?fjveCwI}t$p~WoP11Xe|Xhe?to6TUjG#1Z^#Z-9}V$-?96Xy z|DI3p5PmjfdmS;Jt9>zD6|q9gQsYX3mjCg?Z#=gZMBc6MUtPwHf-H8?>2e!}}9 z-M_Vlo8s$k9ec=mcXfR@-G9V;1=@=b-;nv(i*6_cG>xl7p}CUS;kq zXu|H-^o}yulFczNS&mxJKbYKo@^PniS@Jpew|MWyHx?Z)pO4Y~vRsd{Hp^LyedJ-J z*bnFL@8~?$8upZjli2d?umDBawgVi@Vw8?C3Fsy+y3&? z8DC?5JS&cQ^smkDFLG@b=Mwxpf&5xMU5ohEi_Nw8ao7vu(0<+-xQTebr~6HQbjQ1) z`Nr~gtF^pC4Ze4+dt!bJDCWv-&o^zp0!%%8(m`;rN5=xSOfosVwlR8qwqJ!bGNk`C+2*AU}+qmCSXbzmxd|#%{9? z^T};wZ5FV%3%k9I-)L+kT_f3AT7LxFL*=oXy8Ou8iuk@{e>nTMTl=xbR)aI;be8<} zm+vX)7Ub6E|JUN1Xa4!Zr-?#SYw&osYYiTxKk52kG5X%rX zGD{3&)yAjt|B(FMXl!$GYpbP!bbZdJLB?9~eV(=cjhG&>AC8>s{f2fQ*MJY_u}Aj+ zF*l`uOFH|iunNy{`J{D5maoe!-<4%kU5CH4&q~cfK9c9-?a|@ZW;bhc zA>aGEpBiqi2Vb|~%K)+)^7Ur2duabre!I!Rd+1=c{z~RSiLS4Z69pyTyOM zygiNQNc}gQAJ^a;C!UY!`4j)HWOp^^#9DlNjc=XhqldhX$J-F!lWe|#_f&izUTYsg zFK6Jl{5eCuf240bo^fmqk<&K#hUl-t=JRsa1^VGxjog*|Ud8-&aG<;$MgCoR8#C8^ z4gZ?ib1#Ya7`3$}UnUzn2hVMC)lZ!)Pwr4-jrcoSoa;R&x82n2b;M z`I<(@j`A>!?Kk<*jh#>Mj@5sPuT$9H&YJX-|A*=7N`4voUl;ePa_~OhxvpJv%|9cS zE!(MWYcqiSrg)a%>+RxkBhug+KE7}4d%RcEu_?PvVUF=x`u*4*DW2WQe@ozfb zTe6Pi{!V{y_6M84o4!x*?_quq{&nU1AngV8?5uwbo>6Fjd+!^%T4|q8{x$O}vw0Pp zUDfzy`j5jV#x_+)@6+?HnEnA9kZB@!Q{?gld1{DnJe<+aXLfY;;@>cSylre1HP%Qy zzN@_p^u_ZGU%!y^$<}!hYtfdjule{H*`@Fw#)oUz7$A>(k@-rUb;7r;`3? z*Im^MeM9xv;@5-MT4()T$zDg_46;X~-^s&Va_j1kq^rNN#@av9cMls+s*(1_?%?ky z^sdFvljZ(3e!eA_ZSn1Qwf8ybj&xs3<^s3~dXYW7ooCm0$JyIk!uE8ZO1`1|{6>wx zOYWit_7Oium|t0cYkE4cb1uIc@_iuwZNzvE+38}qia)F9Pe5O!;|6q){ucOdG5?&H z$GCrOU~F|~eO~c=opEKi5A;eJzdm zu4~25x2x5}C)R9Z{MXt8Ym0G?+)O7k$U2|S_l|5HK=<~>df@BN#?9)arI>CI)9=WS zp!-L(hZ_HY-b2g}mfx{*umI0j?48TjUFz>vysN?~^lSxt(Az6MvNgoHb`PHJYViVk z9uogZykGKdAM5gscqa4VKKjC7BeG}wp4SM&M&!Ntxv3TdZS9rvJ_{x1rU%A=K*r)S650LwQ z@^hWohVr)s+e7I(k?eMS_??q{Jb0Qq4Kzm`E!juOYa@}i;#Ok{|L4Z zNj=EZ)8=k7w}u=HqQ4`$fg1P=+GDpFdrj>zYv1Y9r_SopeafU+yUyq_YwE7kX74lW zz#g+_P3bXh`p#1i=ssoEqea7j&tALq*mwH0DKmDS z+HLxbS$j>|bJ{_Z_L(+gdbgh4dv)*KyH3N*sk3&OI%S_7cbhbOx9+oM^Q(F5y1&!5 zmZMQQ{#)O+toye{Qzq@X=hU4$?6m3B+57If&!9p3PTzmlq?w&M@6=(&^bV7zci3sv ztXVsC*l*IF`%Z0e44J%Cblty*oHTp()LHSM!!DDi(K4vR?5TV1(tX@;oX13Le&*3KnHj^d2t4I&07B=M4o!I6l|Pj(UW*xB9LM=*(B7(2#m z*})JQYv}2&*=cW2_oTaLA65=)CrDhl1tFvyA|((jIV2_~7{MXAkf0D`<&X==k#b9J zk&wfIJbqtI_iXRlGM2q#|26%u>ZU&l1?Cfv9Qn0>FyF1ZgmdLuL1VbA^b#+%lB)J=lF~0{V@E0 zyuP?(eaesv;8G^T1?tDBKQ~Y?`5J_e@B)7(DZ`3 z{pJR9(W}YzQNYG4Ilc*gFvlN)=hwXompNoI2|tqK+9d^>S(cUBULl9 zXWBYQ28UGYX+)^7o2YN9FjCw4GKfEx-}lSLco*K+TZW=aUQ41QFRtR;YU&TsHka79h%G2-$jqJnO_!B(Vg4j!EwRkrI!I*xN?wbf;5Z{i+phhZY^Gtit?PaQj~vX!w_!)BXT zS1Fq%41zEzHgwT0%01@B>l90GZm8qn?@O;Fs%0~PZRV(w5FedTAFFm4X_d6_@uTX% z9%aMmT*cjvOi;V~Wo4dVVGg>1O%w>DgT2`vr8>Pl*qIyM-JAAuNo{4xKC^;*vnd}- z=k-+NKiyM>J0UF@J$kp)36ASTM41j&)X>ND91+hC=fzjvet2Es!F{h`+;?AR zcA+!tb2{{kREIL1x{z{P?epS}Z!apZ-SIKdATjEsBAs-jfN;yA8-r2ZKreI%*xL0M zuZgc&-1;$tlEPS7te);j&|Mv?)pJL}KxeV~5#sGMZ9c{*Bp$5w;)un_>U^@HVC?D` zV~D+pee#yQma_VDlt}%_Q&ZNbUoDvWtnRNcQo?>;>yVjL&#Ej`p2_anM%n$jdsND8 zlr|{#tk@@$QeiW-&1Ko1hnf^&qjWPgs60_N}Ih`!=J($N)M`f==z?RdDUFjaQOyLey0$VfUF zCr){MQcsQ#*PO|T%Gh|N(R7EKwOUP&G=^)oGd3~OtPIyCCTrC)N*$4tl}Y*tTb(5*_c zJWE6&O!e$Jv#Tq_1ktmM{~9jyzFE0Um_@0x_7`X`dUdny3ETa=_Vu*wDcW-cGI}Q8 zl){mM&*x(c!_|?|vD)~=q}^~_-CP5E?s{%zstx|il6=B2Og%~n$IQb?evf6 zUt+%OiOl6td_vAKgKU}cK^)1fyEgXn-k|IIsuP;vPZq<;#+6_x))i@|8^sc(yM$c) z3*I|yoW^S_S0~1KtNE7awA8$rysvH3wj0EDlgR6PHc7z@iG}HoXAA77_zVHZRZD!? zmR#9ZuEpQw%eKRJfX9RBz|mPUwH(7kN^gH1=k2&%Y;X*n(Lu4y>Pv7f5B=o-g&?=+{*Y1TtAfK zuXAmD+-)ZfpCwmf$FpTo0ADAq2;DI2x8Z?;`TH4LkVEWhCwnV@&tMP?fvmnsovg9r zhcUZ>63vCP=w@FjeZoSd>?TfDHONtClpBSej&^&=qOu)_q?G*cJ7#coSPpFJcoLGC z>)yd$xm|IN%l)_-t?xRLW!$RgjT6^sOmE6%b4-+pS2p$XlB>)~^r8{Ei%z6nawN_* zaf|iQQQT&!LYJtdV{ay~{jlwkpF4in)e=s~Onc_(gaIKjkySCd`!S7%#>w904keur z)uH3Y^4GvXbfXwAicO4^YGY=GO+*qqDPuGBYXxA^|6$Ja%vJ22t+ncP$pjW!HqHgN z((+uUKj+0>!`Dw3a-v5Xe%P?1$X;x8n@yI5Lr5`#%=1+T?*08=&7kEcR!=-7?%iRP9J=&V7`y;&rDGKt=QVm^u5Y^5y&}r z3y_#AT<#IbIZwEpzooCse30tjK;9p;H(1l)`{0-0eee$;b%Kq22>Ad=z|Vl>$>Qrb z06E{ub;kL)g>^UhTw3-my}>#HPJ%}{7tZp&0G&yBh=617If@0WyY+D>k|r z2!HSG0qaBXG5FFu16C1auY0(VHJ9fZ@={6zl3ylY00`v&h>Zv zz+8G?uh-|(*jG>8ZCxIQ@=SGjY@~YnM)kEyb@oQ}6C9mgxfJiM-C|?$&UN#)Pd>7a$GwxK%Dwrxb6=%YElo_Q!AfJS KS<%|54*efIpBRt; diff --git a/packages/ckeditor5/dist/index.js b/packages/ckeditor5/dist/index.js index 081f05a7a..c6366ee94 100644 --- a/packages/ckeditor5/dist/index.js +++ b/packages/ckeditor5/dist/index.js @@ -354,11 +354,11 @@ import Telemeter from '@wiris/mathtype-html-integration-devkit/src/telemeter.js' } } -var mathIcon = "\n\n\n\n\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n"; +var mathIcon = "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n"; -var chemIcon = "\n\n\n\n\n\n"; +var chemIcon = "\r\n\r\n\r\n\r\n\r\n\r\n"; -var version = "8.13.4"; +var version = "8.14.0"; var packageInfo = { version: version}; diff --git a/packages/ckeditor5/dist/index.js.map b/packages/ckeditor5/dist/index.js.map index 5bee29146..64c2f4288 100644 --- a/packages/ckeditor5/dist/index.js.map +++ b/packages/ckeditor5/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../src/integration.js","../src/commands.js","../src/plugin.js"],"sourcesContent":["import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\n\n/**\n * This class represents the MathType integration for CKEditor5.\n * @extends {IntegrationModel}\n */\nexport default class CKEditor5Integration extends IntegrationModel {\n constructor(ckeditorIntegrationModelProperties) {\n const editor = ckeditorIntegrationModelProperties.editorObject;\n\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\n }\n /**\n * CKEditor5 Integration.\n *\n * @param {integrationModelProperties} integrationModelAttributes\n */\n super(ckeditorIntegrationModelProperties);\n\n /**\n * Folder name used for the integration inside CKEditor plugins folder.\n */\n this.integrationFolderName = \"ckeditor_wiris\";\n }\n\n /**\n * @inheritdoc\n * @returns {string} - The CKEditor instance language.\n * @override\n */\n getLanguage() {\n // Returns the CKEDitor instance language taking into account that the language can be an object.\n // Try to get editorParameters.language, fail silently otherwise\n try {\n return this.editorParameters.language;\n } catch (e) {}\n const languageObject = this.editorObject.config.get(\"language\");\n if (languageObject != null) {\n if (typeof languageObject === \"object\") {\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\n return languageObject.ui;\n }\n return this.editorObject.locale.uiLanguage;\n }\n return languageObject;\n }\n return super.getLanguage();\n }\n\n /**\n * Adds callbacks to the following CKEditor listeners:\n * - 'focus' - updates the current instance.\n * - 'contentDom' - adds 'doubleclick' callback.\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\n * - 'setData' - parses the data converting MathML into images.\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\n * - 'mode' - recalculates the active element.\n */\n addEditorListeners() {\n const editor = this.editorObject;\n\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\n this.checkElement();\n }\n }\n\n /**\n * Checks the current container and assign events in case that it doesn't have them.\n * CKEditor replaces several times the element element during its execution,\n * so we must assign the events again to editor element.\n */\n checkElement() {\n const editor = this.editorObject;\n const newElement = editor.sourceElement;\n\n // If the element wasn't treated, add the events.\n if (!newElement.wirisActive) {\n this.setTarget(newElement);\n this.addEvents();\n // Set the element as treated\n newElement.wirisActive = true;\n }\n }\n\n /**\n * @inheritdoc\n * @param {HTMLElement} element - HTMLElement target.\n * @param {MouseEvent} event - event which trigger the handler.\n */\n doubleClickHandler(element, event) {\n this.core.editionProperties.dbclick = true;\n if (this.editorObject.isReadOnly === false) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\n // doubleclick event ends here.\n if (typeof event.stopPropagation !== \"undefined\") {\n // old I.E compatibility.\n event.stopPropagation();\n } else {\n event.returnValue = false;\n }\n this.core.getCustomEditors().disable();\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\n if (customEditorAttr) {\n this.core.getCustomEditors().enable(customEditorAttr);\n }\n this.core.editionProperties.temporalImage = element;\n this.openExistingFormulaEditor();\n }\n }\n }\n }\n\n /** @inheritdoc */\n static getCorePath() {\n return null; // TODO\n }\n\n /** @inheritdoc */\n callbackFunction() {\n super.callbackFunction();\n this.addEditorListeners();\n }\n\n openNewFormulaEditor() {\n // Store the editor selection as it will be lost upon opening the modal\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\n\n // Focus on the selected editor when multiple editor instances are present\n WirisPlugin.currentInstance = this;\n\n return super.openNewFormulaEditor();\n }\n\n /**\n * Replaces old formula with new MathML or inserts it in caret position if new\n * @param {String} mathml MathML to update old one or insert\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\n */\n insertMathml(mathml) {\n // This returns the value returned by the callback function (writer => {...})\n return this.editorObject.model.change((writer) => {\n const core = this.getCore();\n const selection = this.editorObject.model.document.selection;\n\n const modelElementNew = writer.createElement(\"mathml\", {\n formula: mathml,\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\n });\n\n // Obtain the DOM object corresponding to the formula\n if (core.editionProperties.isNewElement) {\n // Don't bother inserting anything at all if the MathML is empty.\n if (!mathml) return;\n\n const viewSelection =\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\n\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\n\n // Remove selection\n if (!viewSelection.isCollapsed) {\n for (const range of viewSelection.getRanges()) {\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\n }\n }\n\n // Set carret after the formula\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\n writer.setSelection(position);\n } else {\n const img = core.editionProperties.temporalImage;\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\n\n // Insert the new and remove the old one\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\n\n // If the given MathML is empty, don't insert a new formula.\n if (mathml) {\n this.editorObject.model.insertObject(modelElementNew, position);\n }\n writer.remove(modelElementOld);\n }\n\n // eslint-disable-next-line consistent-return\n return modelElementNew;\n });\n }\n\n /**\n * Finds the text node corresponding to given DOM text element.\n * @param {element} viewElement Element to find corresponding text node of.\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\n */\n findText(viewElement) {\n // eslint-disable-line consistent-return\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\n let pivot = viewElement;\n let element;\n while (!element) {\n element = this.editorObject.editing.mapper.toModelElement(\n this.editorObject.editing.view.domConverter.domToView(pivot),\n );\n pivot = pivot.parentElement;\n }\n\n // Navigate through all the subtree under `pivot` in order to find the correct text node\n const range = this.editorObject.model.createRangeIn(element);\n const descendants = Array.from(range.getItems());\n for (const node of descendants) {\n let viewElementData = viewElement.data;\n if (viewElement.nodeType === 3) {\n // Remove invisible white spaces\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\n }\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\n return node.textNode;\n }\n }\n }\n\n /** @inheritdoc */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n // eslint-disable-line no-unused-vars\n const returnObject = {};\n\n let mathmlOrigin;\n if (!mathml) {\n this.insertMathml(\"\");\n } else if (this.core.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n\n this.editorObject.model.change((writer) => {\n const { latexRange } = this.core.editionProperties;\n\n const startNode = this.findText(latexRange.startContainer);\n const endNode = this.findText(latexRange.endContainer);\n\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\n\n let range = writer.createRange(startPosition, endPosition);\n\n // When Latex is next to image/formula.\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\n // Get the position of the latex to be replaced.\n const latexEdited = `$$${Latex.getLatexFromMathML(\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\n )}$$`;\n let data = latexRange.startContainer.data;\n\n // Remove invisible characters.\n data = data.replaceAll(String.fromCharCode(8288), \"\");\n\n // Get to the start of the latex we are editing.\n const offset = data.indexOf(latexEdited);\n const dataOffset = data.substring(offset);\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\n const substring = dataOffset.substr(0, second$);\n data = data.replace(substring, \"\");\n\n if (!data) {\n startPosition = writer.createPositionBefore(startNode);\n range = startNode;\n } else {\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\n range = writer.createRange(startPosition, endPosition);\n }\n }\n\n writer.remove(range);\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\n });\n } else {\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\n try {\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\n windowTarget.document,\n );\n } catch (e) {\n const x = e.toString();\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\n this.core.modalDialog.cancelAction();\n }\n }\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.core.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\n We probably should add it here as well, but we should look further into how */\n // this.editorObject.fire('change');\n\n // Remove temporal image of inserted formula\n this.core.editionProperties.temporalImage = null;\n\n return returnObject;\n }\n\n /**\n * Function called when the content submits an action.\n */\n notifyWindowClosed() {\n this.editorObject.editing.view.focus();\n }\n}\n","/* eslint-disable max-classes-per-file */\nimport { Command } from \"ckeditor5/src/core.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\n/**\n * Command for opening the MathType editor\n */\nexport class MathTypeCommand extends Command {\n execute(options = {}) {\n // Check we get a valid integration\n // eslint-disable-next-line no-prototype-builtins\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\n }\n\n // Save the integration instance as a property of the command.\n this.integration = options.integration;\n\n // Set custom editor or disable it\n this.setEditor();\n\n // Open the editor\n this.openEditor();\n }\n\n /**\n * Sets the appropriate custom editor, if any, or disables them.\n */\n setEditor() {\n // It's possible that a custom editor was last used.\n // We need to disable it to avoid wrong behaviors.\n this.integration.core.getCustomEditors().disable();\n }\n\n /**\n * Checks whether we are editing an existing formula or a new one and opens the editor.\n */\n openEditor() {\n this.integration.core.editionProperties.dbclick = false;\n const image = this._getSelectedImage();\n if (\n typeof image !== \"undefined\" &&\n image !== null &&\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\n ) {\n this.integration.core.editionProperties.temporalImage = image;\n this.integration.openExistingFormulaEditor();\n } else {\n this.integration.openNewFormulaEditor();\n }\n }\n\n /**\n * Gets the currently selected formula image\n * @returns {Element} selected image, if any, undefined otherwise\n */\n _getSelectedImage() {\n const { selection } = this.editor.editing.view.document;\n\n // If we can not extract the formula, fall back to default behavior.\n if (selection.isCollapsed || selection.rangeCount !== 1) {\n return;\n }\n\n // Look for the wrapping the formula and then for the inside\n\n const range = selection.getFirstRange();\n\n let image;\n\n for (const span of range) {\n if (span.item.name !== \"span\") {\n return;\n }\n image = span.item.getChild(0);\n break;\n }\n\n if (!image) {\n return;\n }\n\n // eslint-disable-next-line consistent-return\n return this.editor.editing.view.domConverter.mapViewToDom(image);\n }\n}\n\n/**\n * Command for opening the ChemType editor\n */\nexport class ChemTypeCommand extends MathTypeCommand {\n setEditor() {\n this.integration.core.getCustomEditors().enable(\"chemistry\");\n }\n}\n","// CKEditor imports\nimport { Plugin } from \"ckeditor5/src/core.js\";\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\n\n// MathType API imports\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\n\n// Local imports\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\n\nimport packageInfo from \"../package.json\";\n\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\n\nexport default class MathType extends Plugin {\n static get requires() {\n return [Widget];\n }\n\n static get pluginName() {\n return \"MathType\";\n }\n\n init() {\n // Create the MathType API Integration object\n const integration = this._addIntegration();\n currentInstance = integration;\n\n // Add the MathType and ChemType commands to the editor\n this._addCommands();\n\n // Add the buttons for MathType and ChemType\n this._addViews(integration);\n\n // Registers the element in the schema\n this._addSchema();\n\n // Add the downcast and upcast converters\n this._addConverters(integration);\n\n // Expose the WirisPlugin variable to the window\n this._exposeWiris();\n }\n\n /**\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\n */\n destroy() {\n // eslint-disable-line class-methods-use-this\n currentInstance?.destroy();\n }\n\n /**\n * Create the MathType API Integration object\n * @returns {CKEditor5Integration} the integration object\n */\n _addIntegration() {\n const { editor } = this;\n\n /**\n * Integration model constructor attributes.\n * @type {integrationModelProperties}\n */\n const integrationProperties = {};\n integrationProperties.environment = {};\n integrationProperties.environment.editor = \"CKEditor5\";\n integrationProperties.environment.editorVersion = \"5.x\";\n integrationProperties.version = packageInfo.version;\n integrationProperties.editorObject = editor;\n integrationProperties.serviceProviderProperties = {};\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\n integrationProperties.serviceProviderProperties.server = \"java\";\n integrationProperties.target = editor.sourceElement;\n integrationProperties.scriptName = \"bundle.js\";\n integrationProperties.managesLanguage = true;\n // etc\n\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\n let integration;\n if (integrationProperties.target) {\n // Instance of the integration associated to this editor instance\n integration = new CKEditor5Integration(integrationProperties);\n integration.init();\n integration.listeners.fire(\"onTargetReady\", {});\n\n integration.checkElement();\n\n this.listenTo(\n editor.editing.view.document,\n \"click\",\n (evt, data) => {\n // Is Double-click\n if (data.domEvent.detail === 2) {\n integration.doubleClickHandler(data.domTarget, data.domEvent);\n evt.stop();\n }\n },\n { priority: \"highest\" },\n );\n }\n\n return integration;\n }\n\n /**\n * Add the MathType and ChemType commands to the editor\n */\n _addCommands() {\n const { editor } = this;\n\n // Add command to open the formula editor\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\n\n // Add command to open the chemistry formula editor\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\n }\n\n /**\n * Add the buttons for MathType and ChemType\n * @param {CKEditor5Integration} integration the integration object\n */\n _addViews(integration) {\n const { editor } = this;\n\n // Check if MathType editor is enabled\n if (Configuration.get(\"editorEnabled\")) {\n // Add button for the formula editor\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\n view.set({\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\n icon: mathIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"MathType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Check if ChemType editor is enabled\n if (Configuration.get(\"chemEnabled\")) {\n // Add button for the chemistry formula editor\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\n\n view.set({\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\n icon: chemIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"ChemType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Observer for the Double-click event\n editor.editing.view.addObserver(ClickObserver);\n }\n\n /**\n * Registers the element in the schema\n */\n _addSchema() {\n const { schema } = this.editor.model;\n\n schema.register(\"mathml\", {\n inheritAllFrom: \"$inlineObject\",\n allowAttributes: [\"formula\", \"htmlContent\"],\n });\n }\n\n /**\n * Add the downcast and upcast converters\n */\n _addConverters(integration) {\n const { editor } = this;\n\n // Editing view -> Model\n editor.conversion.for(\"upcast\").elementToElement({\n view: {\n name: \"span\",\n classes: \"ck-math-widget\",\n },\n model: (viewElement, { writer: modelWriter }) => {\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\n return modelWriter.createElement(\"mathml\", {\n formula,\n });\n },\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // When element was already consumed then skip it.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // If we encounter any with a LaTeX annotation inside,\n // convert it into a \"$$...$$\" string.\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\n\n // Get the formula of the (which is all its children).\n const processor = new XmlDataProcessor(editor.editing.view.document);\n\n // Only god knows why the following line makes viewItem lose all of its children,\n // so we obtain isLatex before doing this because we need viewItem's children for that.\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\n\n // and obtain the attributes of too!\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n\n // We process the document fragment\n let formula = processor.toData(viewDocumentFragment) || \"\";\n\n // And obtain the complete formula\n formula = Util.htmlSanitize(`${formula}`);\n\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\n\n /* Model node that contains what's going to actually be inserted. This can be either:\n - A element with a formula attribute set to the given formula, or\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\n const modelNode = isLatex\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\n : writer.createElement(\"mathml\", { formula });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\n \"element:img\",\n (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // Only upcast when is wiris formula\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\n return;\n }\n\n // The following code ensures that the element's name, attributes, and classes are consumed.\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\n\n // Check if we can consume the element name.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // Consume the name, attributes, and classes so nothing else processes it.\n consumable.consume(viewItem, { name: true });\n for (const attrName of viewItem.getAttributes()) {\n consumable.consume(viewItem, { attributes: [attrName] });\n }\n\n for (const className of viewItem.getClassNames()) {\n consumable.consume(viewItem, { classes: [className] });\n }\n\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n const htmlContent = Util.htmlSanitize(``);\n\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n },\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\n { priority: \"high\" },\n );\n\n /**\n * Whether the given view element has a LaTeX annotation element.\n * @param {*} math\n * @returns {bool}\n */\n function mathIsLatex(math) {\n const semantics = math.getChild(0);\n if (!semantics || semantics.name !== \"semantics\") return false;\n for (const child of semantics.getChildren()) {\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\n return true;\n }\n }\n return false;\n }\n\n function createViewWidget(modelItem, { writer: viewWriter }) {\n const widgetElement = viewWriter.createContainerElement(\"span\", {\n class: \"ck-math-widget\",\n });\n\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\n\n if (mathUIElement) {\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\n }\n\n return toWidget(widgetElement, viewWriter);\n }\n\n function createViewImage(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n const formula = modelItem.getAttribute(\"formula\");\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\n\n if (!formula && !htmlContent) {\n return null;\n }\n\n let imgElement = null;\n\n if (htmlContent) {\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\n } else if (formula) {\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\n\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\n\n // Add HTML element () to model\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\n }\n\n /* Although we use the HtmlDataProcessor to obtain the attributes,\n * we must create a new EmptyElement which is independent of the\n * DataProcessor being used by this editor instance\n */\n if (imgElement) {\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\n renderUnsafeAttributes: [\"src\"],\n });\n }\n\n return null;\n }\n\n // Model -> Editing view\n editor.conversion.for(\"editingDowncast\").elementToElement({\n model: \"mathml\",\n view: createViewWidget,\n });\n\n // Model -> Data view\n editor.conversion.for(\"dataDowncast\").elementToElement({\n model: \"mathml\",\n view: createDataString, // eslint-disable-line no-use-before-define\n });\n\n /**\n * Makes a copy of the given view node.\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\n * @returns {module:engine/view/node~Node} Copy of the node.\n */\n function clone(viewWriter, sourceNode) {\n if (sourceNode.is(\"text\")) {\n return viewWriter.createText(sourceNode.data);\n }\n if (sourceNode.is(\"element\")) {\n if (sourceNode.is(\"emptyElement\")) {\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\n }\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\n for (const child of sourceNode.getChildren()) {\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\n }\n return element;\n }\n\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\n }\n\n function createDataString(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n // Load img element\n const mathString =\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\n\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\n\n return clone(viewWriter, sourceMathElement);\n }\n\n // This stops the view selection getting into the s and messing up caret movement\n editor.editing.mapper.on(\n \"viewToModelPosition\",\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\n );\n\n // Keep a reference to the original get and set function.\n const { get, set } = editor.data;\n\n /**\n * Hack to transform $$latex$$ into in editor.getData()'s output.\n */\n editor.data.on(\n \"get\",\n (e) => {\n const output = e.return;\n const parsedResult = Parser.endParse(output);\n\n // Cleans all the semantics tag for safexml\n // including the handwritten data points\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\n },\n { priority: \"low\" },\n );\n\n /**\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\n */\n editor.data.on(\n \"set\",\n (e, args) => {\n // Retrieve the data to be set on the CKEditor.\n let modifiedData = args[0];\n // Regex to find all mathml formulas.\n const regexp = /(]*>)|()/gm;\n const formulas = [];\n let formula;\n\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\n while ((formula = regexp.exec(modifiedData)) !== null) {\n formulas.push(formula[0]);\n }\n\n // Loop to find LaTeX and formula images and replace the MathML for the both.\n formulas.forEach((formula) => {\n if (formula.includes('encoding=\"LaTeX\"')) {\n // LaTeX found.\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\n modifiedData = modifiedData.replace(formula, latex);\n } else if (formula.includes(\" {...})\r\n return this.editorObject.model.change((writer) => {\r\n const core = this.getCore();\r\n const selection = this.editorObject.model.document.selection;\r\n\r\n const modelElementNew = writer.createElement(\"mathml\", {\r\n formula: mathml,\r\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\r\n });\r\n\r\n // Obtain the DOM object corresponding to the formula\r\n if (core.editionProperties.isNewElement) {\r\n // Don't bother inserting anything at all if the MathML is empty.\r\n if (!mathml) return;\r\n\r\n const viewSelection =\r\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\r\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\r\n\r\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\r\n\r\n // Remove selection\r\n if (!viewSelection.isCollapsed) {\r\n for (const range of viewSelection.getRanges()) {\r\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\r\n }\r\n }\r\n\r\n // Set carret after the formula\r\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\r\n writer.setSelection(position);\r\n } else {\r\n const img = core.editionProperties.temporalImage;\r\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\r\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\r\n\r\n // Insert the new and remove the old one\r\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\r\n\r\n // If the given MathML is empty, don't insert a new formula.\r\n if (mathml) {\r\n this.editorObject.model.insertObject(modelElementNew, position);\r\n }\r\n writer.remove(modelElementOld);\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return modelElementNew;\r\n });\r\n }\r\n\r\n /**\r\n * Finds the text node corresponding to given DOM text element.\r\n * @param {element} viewElement Element to find corresponding text node of.\r\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\r\n */\r\n findText(viewElement) {\r\n // eslint-disable-line consistent-return\r\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\r\n let pivot = viewElement;\r\n let element;\r\n while (!element) {\r\n element = this.editorObject.editing.mapper.toModelElement(\r\n this.editorObject.editing.view.domConverter.domToView(pivot),\r\n );\r\n pivot = pivot.parentElement;\r\n }\r\n\r\n // Navigate through all the subtree under `pivot` in order to find the correct text node\r\n const range = this.editorObject.model.createRangeIn(element);\r\n const descendants = Array.from(range.getItems());\r\n for (const node of descendants) {\r\n let viewElementData = viewElement.data;\r\n if (viewElement.nodeType === 3) {\r\n // Remove invisible white spaces\r\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\r\n }\r\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\r\n return node.textNode;\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n // eslint-disable-line no-unused-vars\r\n const returnObject = {};\r\n\r\n let mathmlOrigin;\r\n if (!mathml) {\r\n this.insertMathml(\"\");\r\n } else if (this.core.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n\r\n this.editorObject.model.change((writer) => {\r\n const { latexRange } = this.core.editionProperties;\r\n\r\n const startNode = this.findText(latexRange.startContainer);\r\n const endNode = this.findText(latexRange.endContainer);\r\n\r\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\r\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\r\n\r\n let range = writer.createRange(startPosition, endPosition);\r\n\r\n // When Latex is next to image/formula.\r\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\r\n // Get the position of the latex to be replaced.\r\n const latexEdited = `$$${Latex.getLatexFromMathML(\r\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\r\n )}$$`;\r\n let data = latexRange.startContainer.data;\r\n\r\n // Remove invisible characters.\r\n data = data.replaceAll(String.fromCharCode(8288), \"\");\r\n\r\n // Get to the start of the latex we are editing.\r\n const offset = data.indexOf(latexEdited);\r\n const dataOffset = data.substring(offset);\r\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\r\n const substring = dataOffset.substr(0, second$);\r\n data = data.replace(substring, \"\");\r\n\r\n if (!data) {\r\n startPosition = writer.createPositionBefore(startNode);\r\n range = startNode;\r\n } else {\r\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\r\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\r\n range = writer.createRange(startPosition, endPosition);\r\n }\r\n }\r\n\r\n writer.remove(range);\r\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\r\n });\r\n } else {\r\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\r\n try {\r\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\r\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\r\n windowTarget.document,\r\n );\r\n } catch (e) {\r\n const x = e.toString();\r\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\r\n this.core.modalDialog.cancelAction();\r\n }\r\n }\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.core.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n\r\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\r\n We probably should add it here as well, but we should look further into how */\r\n // this.editorObject.fire('change');\r\n\r\n // Remove temporal image of inserted formula\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return returnObject;\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n notifyWindowClosed() {\r\n this.editorObject.editing.view.focus();\r\n }\r\n}\r\n","/* eslint-disable max-classes-per-file */\r\nimport { Command } from \"ckeditor5/src/core.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\n/**\r\n * Command for opening the MathType editor\r\n */\r\nexport class MathTypeCommand extends Command {\r\n execute(options = {}) {\r\n // Check we get a valid integration\r\n // eslint-disable-next-line no-prototype-builtins\r\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\r\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\r\n }\r\n\r\n // Save the integration instance as a property of the command.\r\n this.integration = options.integration;\r\n\r\n // Set custom editor or disable it\r\n this.setEditor();\r\n\r\n // Open the editor\r\n this.openEditor();\r\n }\r\n\r\n /**\r\n * Sets the appropriate custom editor, if any, or disables them.\r\n */\r\n setEditor() {\r\n // It's possible that a custom editor was last used.\r\n // We need to disable it to avoid wrong behaviors.\r\n this.integration.core.getCustomEditors().disable();\r\n }\r\n\r\n /**\r\n * Checks whether we are editing an existing formula or a new one and opens the editor.\r\n */\r\n openEditor() {\r\n this.integration.core.editionProperties.dbclick = false;\r\n const image = this._getSelectedImage();\r\n if (\r\n typeof image !== \"undefined\" &&\r\n image !== null &&\r\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\r\n ) {\r\n this.integration.core.editionProperties.temporalImage = image;\r\n this.integration.openExistingFormulaEditor();\r\n } else {\r\n this.integration.openNewFormulaEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Gets the currently selected formula image\r\n * @returns {Element} selected image, if any, undefined otherwise\r\n */\r\n _getSelectedImage() {\r\n const { selection } = this.editor.editing.view.document;\r\n\r\n // If we can not extract the formula, fall back to default behavior.\r\n if (selection.isCollapsed || selection.rangeCount !== 1) {\r\n return;\r\n }\r\n\r\n // Look for the wrapping the formula and then for the inside\r\n\r\n const range = selection.getFirstRange();\r\n\r\n let image;\r\n\r\n for (const span of range) {\r\n if (span.item.name !== \"span\") {\r\n return;\r\n }\r\n image = span.item.getChild(0);\r\n break;\r\n }\r\n\r\n if (!image) {\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return this.editor.editing.view.domConverter.mapViewToDom(image);\r\n }\r\n}\r\n\r\n/**\r\n * Command for opening the ChemType editor\r\n */\r\nexport class ChemTypeCommand extends MathTypeCommand {\r\n setEditor() {\r\n this.integration.core.getCustomEditors().enable(\"chemistry\");\r\n }\r\n}\r\n","// CKEditor imports\r\nimport { Plugin } from \"ckeditor5/src/core.js\";\r\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\r\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\r\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\r\n\r\n// MathType API imports\r\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\r\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\r\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\r\n\r\n// Local imports\r\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\r\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\r\n\r\nimport packageInfo from \"../package.json\";\r\n\r\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\r\n\r\nexport default class MathType extends Plugin {\r\n static get requires() {\r\n return [Widget];\r\n }\r\n\r\n static get pluginName() {\r\n return \"MathType\";\r\n }\r\n\r\n init() {\r\n // Create the MathType API Integration object\r\n const integration = this._addIntegration();\r\n currentInstance = integration;\r\n\r\n // Add the MathType and ChemType commands to the editor\r\n this._addCommands();\r\n\r\n // Add the buttons for MathType and ChemType\r\n this._addViews(integration);\r\n\r\n // Registers the element in the schema\r\n this._addSchema();\r\n\r\n // Add the downcast and upcast converters\r\n this._addConverters(integration);\r\n\r\n // Expose the WirisPlugin variable to the window\r\n this._exposeWiris();\r\n }\r\n\r\n /**\r\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\r\n */\r\n destroy() {\r\n // eslint-disable-line class-methods-use-this\r\n currentInstance?.destroy();\r\n }\r\n\r\n /**\r\n * Create the MathType API Integration object\r\n * @returns {CKEditor5Integration} the integration object\r\n */\r\n _addIntegration() {\r\n const { editor } = this;\r\n\r\n /**\r\n * Integration model constructor attributes.\r\n * @type {integrationModelProperties}\r\n */\r\n const integrationProperties = {};\r\n integrationProperties.environment = {};\r\n integrationProperties.environment.editor = \"CKEditor5\";\r\n integrationProperties.environment.editorVersion = \"5.x\";\r\n integrationProperties.version = packageInfo.version;\r\n integrationProperties.editorObject = editor;\r\n integrationProperties.serviceProviderProperties = {};\r\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\r\n integrationProperties.serviceProviderProperties.server = \"java\";\r\n integrationProperties.target = editor.sourceElement;\r\n integrationProperties.scriptName = \"bundle.js\";\r\n integrationProperties.managesLanguage = true;\r\n // etc\r\n\r\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\r\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\r\n let integration;\r\n if (integrationProperties.target) {\r\n // Instance of the integration associated to this editor instance\r\n integration = new CKEditor5Integration(integrationProperties);\r\n integration.init();\r\n integration.listeners.fire(\"onTargetReady\", {});\r\n\r\n integration.checkElement();\r\n\r\n this.listenTo(\r\n editor.editing.view.document,\r\n \"click\",\r\n (evt, data) => {\r\n // Is Double-click\r\n if (data.domEvent.detail === 2) {\r\n integration.doubleClickHandler(data.domTarget, data.domEvent);\r\n evt.stop();\r\n }\r\n },\r\n { priority: \"highest\" },\r\n );\r\n }\r\n\r\n return integration;\r\n }\r\n\r\n /**\r\n * Add the MathType and ChemType commands to the editor\r\n */\r\n _addCommands() {\r\n const { editor } = this;\r\n\r\n // Add command to open the formula editor\r\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\r\n\r\n // Add command to open the chemistry formula editor\r\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\r\n }\r\n\r\n /**\r\n * Add the buttons for MathType and ChemType\r\n * @param {CKEditor5Integration} integration the integration object\r\n */\r\n _addViews(integration) {\r\n const { editor } = this;\r\n\r\n // Check if MathType editor is enabled\r\n if (Configuration.get(\"editorEnabled\")) {\r\n // Add button for the formula editor\r\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\r\n view.set({\r\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\r\n icon: mathIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"MathType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Check if ChemType editor is enabled\r\n if (Configuration.get(\"chemEnabled\")) {\r\n // Add button for the chemistry formula editor\r\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\r\n\r\n view.set({\r\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\r\n icon: chemIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"ChemType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Observer for the Double-click event\r\n editor.editing.view.addObserver(ClickObserver);\r\n }\r\n\r\n /**\r\n * Registers the element in the schema\r\n */\r\n _addSchema() {\r\n const { schema } = this.editor.model;\r\n\r\n schema.register(\"mathml\", {\r\n inheritAllFrom: \"$inlineObject\",\r\n allowAttributes: [\"formula\", \"htmlContent\"],\r\n });\r\n }\r\n\r\n /**\r\n * Add the downcast and upcast converters\r\n */\r\n _addConverters(integration) {\r\n const { editor } = this;\r\n\r\n // Editing view -> Model\r\n editor.conversion.for(\"upcast\").elementToElement({\r\n view: {\r\n name: \"span\",\r\n classes: \"ck-math-widget\",\r\n },\r\n model: (viewElement, { writer: modelWriter }) => {\r\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\r\n return modelWriter.createElement(\"mathml\", {\r\n formula,\r\n });\r\n },\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // When element was already consumed then skip it.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // If we encounter any with a LaTeX annotation inside,\r\n // convert it into a \"$$...$$\" string.\r\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\r\n\r\n // Get the formula of the (which is all its children).\r\n const processor = new XmlDataProcessor(editor.editing.view.document);\r\n\r\n // Only god knows why the following line makes viewItem lose all of its children,\r\n // so we obtain isLatex before doing this because we need viewItem's children for that.\r\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\r\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\r\n\r\n // and obtain the attributes of too!\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n\r\n // We process the document fragment\r\n let formula = processor.toData(viewDocumentFragment) || \"\";\r\n\r\n // And obtain the complete formula\r\n formula = Util.htmlSanitize(`${formula}`);\r\n\r\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\r\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\r\n\r\n /* Model node that contains what's going to actually be inserted. This can be either:\r\n - A element with a formula attribute set to the given formula, or\r\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\r\n const modelNode = isLatex\r\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\r\n : writer.createElement(\"mathml\", { formula });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\r\n \"element:img\",\r\n (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // Only upcast when is wiris formula\r\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\r\n return;\r\n }\r\n\r\n // The following code ensures that the element's name, attributes, and classes are consumed.\r\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\r\n\r\n // Check if we can consume the element name.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // Consume the name, attributes, and classes so nothing else processes it.\r\n consumable.consume(viewItem, { name: true });\r\n for (const attrName of viewItem.getAttributes()) {\r\n consumable.consume(viewItem, { attributes: [attrName] });\r\n }\r\n\r\n for (const className of viewItem.getClassNames()) {\r\n consumable.consume(viewItem, { classes: [className] });\r\n }\r\n\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n const htmlContent = Util.htmlSanitize(``);\r\n\r\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n },\r\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\r\n { priority: \"high\" },\r\n );\r\n\r\n /**\r\n * Whether the given view element has a LaTeX annotation element.\r\n * @param {*} math\r\n * @returns {bool}\r\n */\r\n function mathIsLatex(math) {\r\n const semantics = math.getChild(0);\r\n if (!semantics || semantics.name !== \"semantics\") return false;\r\n for (const child of semantics.getChildren()) {\r\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n function createViewWidget(modelItem, { writer: viewWriter }) {\r\n const widgetElement = viewWriter.createContainerElement(\"span\", {\r\n class: \"ck-math-widget\",\r\n });\r\n\r\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\r\n\r\n if (mathUIElement) {\r\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\r\n }\r\n\r\n return toWidget(widgetElement, viewWriter);\r\n }\r\n\r\n function createViewImage(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n const formula = modelItem.getAttribute(\"formula\");\r\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\r\n\r\n if (!formula && !htmlContent) {\r\n return null;\r\n }\r\n\r\n let imgElement = null;\r\n\r\n if (htmlContent) {\r\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\r\n } else if (formula) {\r\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\r\n\r\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\r\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\r\n\r\n // Add HTML element () to model\r\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\r\n }\r\n\r\n /* Although we use the HtmlDataProcessor to obtain the attributes,\r\n * we must create a new EmptyElement which is independent of the\r\n * DataProcessor being used by this editor instance\r\n */\r\n if (imgElement) {\r\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\r\n renderUnsafeAttributes: [\"src\"],\r\n });\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // Model -> Editing view\r\n editor.conversion.for(\"editingDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createViewWidget,\r\n });\r\n\r\n // Model -> Data view\r\n editor.conversion.for(\"dataDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createDataString, // eslint-disable-line no-use-before-define\r\n });\r\n\r\n /**\r\n * Makes a copy of the given view node.\r\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\r\n * @returns {module:engine/view/node~Node} Copy of the node.\r\n */\r\n function clone(viewWriter, sourceNode) {\r\n if (sourceNode.is(\"text\")) {\r\n return viewWriter.createText(sourceNode.data);\r\n }\r\n if (sourceNode.is(\"element\")) {\r\n if (sourceNode.is(\"emptyElement\")) {\r\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\r\n }\r\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\r\n for (const child of sourceNode.getChildren()) {\r\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\r\n }\r\n return element;\r\n }\r\n\r\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\r\n }\r\n\r\n function createDataString(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n // Load img element\r\n const mathString =\r\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\r\n\r\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\r\n\r\n return clone(viewWriter, sourceMathElement);\r\n }\r\n\r\n // This stops the view selection getting into the s and messing up caret movement\r\n editor.editing.mapper.on(\r\n \"viewToModelPosition\",\r\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\r\n );\r\n\r\n // Keep a reference to the original get and set function.\r\n const { get, set } = editor.data;\r\n\r\n /**\r\n * Hack to transform $$latex$$ into in editor.getData()'s output.\r\n */\r\n editor.data.on(\r\n \"get\",\r\n (e) => {\r\n const output = e.return;\r\n const parsedResult = Parser.endParse(output);\r\n\r\n // Cleans all the semantics tag for safexml\r\n // including the handwritten data points\r\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\r\n },\r\n { priority: \"low\" },\r\n );\r\n\r\n /**\r\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\r\n */\r\n editor.data.on(\r\n \"set\",\r\n (e, args) => {\r\n // Retrieve the data to be set on the CKEditor.\r\n let modifiedData = args[0];\r\n // Regex to find all mathml formulas.\r\n const regexp = /(]*>)|()/gm;\r\n const formulas = [];\r\n let formula;\r\n\r\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\r\n while ((formula = regexp.exec(modifiedData)) !== null) {\r\n formulas.push(formula[0]);\r\n }\r\n\r\n // Loop to find LaTeX and formula images and replace the MathML for the both.\r\n formulas.forEach((formula) => {\r\n if (formula.includes('encoding=\"LaTeX\"')) {\r\n // LaTeX found.\r\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\r\n modifiedData = modifiedData.replace(formula, latex);\r\n } else if (formula.includes(\" = { + singleNumber: { + altText: '1', + mathml: '1' + }, + styledSingleNumber: { + altText: 'bold italic 1', + mathml: '1' + + }, + squareRootY: { + altText: 'square root of y', + mathml: 'y', + latex: '\\sqrt{y}' + }, + squareRootYPlusFive: { + altText: 'square root of y plus 5', + mathml: 'y5', + latex: '\\sqrt y+5' + }, + OnePlusOne: { + altText: '1 plus 1', + mathml: '1+1' + }, + styledOnePlusOne: { + altText: 'bold italic 1 bold italic plus bold italic 1', + mathml: '1+1' + }, + specialCharacters: { + altText: 'ยซ less than ยป greater than ยง & ยจ " apostrophe apostrophe', + mathml: '«<»>§&¨"\'\'' + } +} + +export default Equations \ No newline at end of file diff --git a/tests/e2e/enums/toolbar.ts b/tests/e2e/enums/toolbar.ts new file mode 100644 index 000000000..5ed6fad4d --- /dev/null +++ b/tests/e2e/enums/toolbar.ts @@ -0,0 +1,6 @@ +enum Toolbar { + MATH = 'MathType', + CHEMISTRY = 'ChemType', +} + +export default Toolbar \ No newline at end of file diff --git a/tests/e2e/enums/typing_mode.ts b/tests/e2e/enums/typing_mode.ts new file mode 100644 index 000000000..3eaa5bd5b --- /dev/null +++ b/tests/e2e/enums/typing_mode.ts @@ -0,0 +1,7 @@ +enum TypingMode { + KEYBOARD = 'KEY', + HAND = 'HAND', + UNKNOWN = 'UNKNOWN', +} + +export default TypingMode \ No newline at end of file diff --git a/tests/e2e/helpers/network.ts b/tests/e2e/helpers/network.ts new file mode 100644 index 000000000..be0d463d5 --- /dev/null +++ b/tests/e2e/helpers/network.ts @@ -0,0 +1,20 @@ +import { Page } from '@playwright/test' + +export const captureTelemetryRequests = async (page: Page, foundEvents: string[]): Promise => { + // Enable request interception and handle requests + page.on('request', (request) => { + if (request.method() === 'POST' && request.url().includes('telemetry')) { + const requestData = request.postData() + if (requestData) { + const eventCaptured = getTelemetryEventCaptured(requestData) + foundEvents.push(eventCaptured) + } + } + }) +} + +export const getTelemetryEventCaptured = (requestData: string): string => { + const requestDataJSON = JSON.parse(requestData) + const eventCaptured: string = requestDataJSON.evs[0].typ + return eventCaptured +} \ No newline at end of file diff --git a/tests/e2e/helpers/test-setup.ts b/tests/e2e/helpers/test-setup.ts new file mode 100644 index 000000000..64e574447 --- /dev/null +++ b/tests/e2e/helpers/test-setup.ts @@ -0,0 +1,27 @@ +import { Page } from '@playwright/test' +import EditorManager from '../page-objects/editor_manager' +import WirisEditor from '../page-objects/wiris_editor' +import BaseEditor from '../page-objects/base_editor' + +export interface TestSetup { + editor: BaseEditor + wirisEditor: WirisEditor +} + +export async function setupEditor(page: Page, editorName: string): Promise { + const editorManager = new EditorManager() + const availableEditors = editorManager.getEditors(page) + const editor = availableEditors.find(e => e.getName() === editorName) + + if (!editor) { + throw new Error(`Editor ${editorName} not found`) + } + + const wirisEditor = new WirisEditor(page) + + return { editor, wirisEditor } +} + +export function getEditorsFromEnv(): string[] { + return process.env.HTML_EDITOR?.split('|') || ['generic', 'ckeditor5', 'tinymce8'] +} \ No newline at end of file diff --git a/tests/e2e/interfaces/equation.ts b/tests/e2e/interfaces/equation.ts new file mode 100644 index 000000000..48c4d85f9 --- /dev/null +++ b/tests/e2e/interfaces/equation.ts @@ -0,0 +1,5 @@ +export default interface Equation { + altText: string + mathml: string + latex?: string +} \ No newline at end of file diff --git a/tests/e2e/page-objects/base_editor.ts b/tests/e2e/page-objects/base_editor.ts new file mode 100644 index 000000000..ea073fc9a --- /dev/null +++ b/tests/e2e/page-objects/base_editor.ts @@ -0,0 +1,453 @@ +import { Page, Locator, expect, FrameLocator } from '@playwright/test' +import Toolbar from '../enums/toolbar' +import type Equation from '../interfaces/equation' +import BasePage from './page' +import page from './page' + +/** + * Abstract class used in each of the HTML editors which includes the methods for all the editors, and specifies the properties each editor needs. + */ +export default abstract class BaseEditor extends BasePage { + protected readonly abstract wirisEditorButtonMathType: string + protected readonly abstract wirisEditorButtonChemType: string + protected readonly abstract name: string + protected readonly abstract editField: string + protected readonly iframe?: string + + constructor(page: Page) { + super(page) + } + + public getName(): string { + return this.name + } + + public getIframe(): string | undefined { + return this.iframe + } + + /** + * Constructs the URL for the specific editor and opens it in the browser. + * @returns {Promise} The URL of the opened editor. Unused in the current implementation + */ + public async open(): Promise { + await this.page.goto(`${process.env.TEST_BRANCH}/html/${this.getName()}/`) + await this.page.waitForLoadState('domcontentloaded') + return this.page.url() + } + + /** + * Opens the Wiris editor based on a provided toolbar type. + * @param {Toolbar} toolbar - The type of the toolbar to open, either Math or Chemistry. + */ + public async openWirisEditor(toolbar: Toolbar): Promise { + switch (toolbar) { + case Toolbar.CHEMISTRY: + await this.page.locator(this.wirisEditorButtonChemType).waitFor({ state: 'visible' }) + await this.page.locator(this.wirisEditorButtonChemType).hover() + await this.page.locator(this.wirisEditorButtonChemType).click() + break + default: + + await this.page.locator(this.wirisEditorButtonMathType).waitFor({ state: 'visible' }) + await this.page.locator(this.wirisEditorButtonMathType).hover() + await this.page.locator(this.wirisEditorButtonMathType).click() + break + } + } + + /** + * Opens the source code editor + */ + public async clickSourceCodeEditor(): Promise { + const sourceCodeButton = this.getSourceCodeEditorButton?.() + if (sourceCodeButton) { + await this.page.locator(sourceCodeButton).waitFor({ state: 'visible' }) + await this.page.locator(sourceCodeButton).click() + } + } + + public async typeSourceText(text: string): Promise { + const sourceCodeEditField = this.getSourceCodeEditField?.() + if (!sourceCodeEditField) { + throw new Error('Source code edit field selector is not defined.') + } + await this.page.locator(sourceCodeEditField).waitFor({ state: 'visible' }) + await this.page.locator(sourceCodeEditField).click() + await this.pause(500) + await this.page.keyboard.type(text) + } + + /** + * Retrieves all equations from the editor using the alt text and data-mathml DOM attributes. + * @returns {Promise} Array of equation interface. + */ + public async getEquations(): Promise { + let frameOrPage: Page | FrameLocator + if (this.iframe) { + frameOrPage = this.page.frameLocator(this.iframe) + } else { + frameOrPage = this.page + } + + await this.page.waitForTimeout(500) + + const equationsInEditor = await frameOrPage.locator(`${this.editField} img[alt][data-mathml]`) + const count = await equationsInEditor.count() + const equations: Equation[] = [] + + for (let i = 0; i < count; i++) { + const equation = equationsInEditor.nth(i) + const altText = await equation.getAttribute('alt') || '' + const mathml = await equation.getAttribute('data-mathml') || '' + equations.push({ altText, mathml }) + } + + return equations + } + + /** + * Waits for a specific equation to appear in the editor. + * @param {Equation} equation - The equation to wait for. + */ + public async waitForEquation(equation: Equation): Promise { + await expect(async () => { + const equations = await this.getEquations() + expect(equations.some((eq) => eq.altText === equation.altText)).toBeTruthy() + }).toPass({ timeout: 10000 }) + } + + /** + * Gets a locator for the equation element within the DOM. + * @param {Equation} equation - The equation to find in the DOM. + * @returns {Locator} The Playwright locator representing the equation. + */ + public getEquationElement(equation: Equation): Locator { + if (this.iframe) { + return this.page.frameLocator(this.iframe).locator(`${this.editField} img[alt="${equation.altText}"]`) + } + + return this.page.locator(`${this.editField} img[alt="${equation.altText}"]`) + } + + /** + * Clicks on an element for a specified number of times. The reason for this function is to handle the iframe switching. Used for elements belonging to the editor. + * @param {Locator} elementToClick - The element to click on. + * @param {number} [numberOfTimes=1] - The number of times to click on the element. + */ + public async clickElement(elementToClick: Locator, numberOfTimes: number = 1): Promise { + switch (numberOfTimes) { + case 1: + await elementToClick.click() + break + case 2: + await elementToClick.dblclick() + break + default: + for (let i = 0; i < numberOfTimes; i++) { + await elementToClick.click() + await this.pause(500) + } + break + } + } + + /** + * Focuses the editing field within the editor. + */ + public async focus(): Promise { + let editFieldLocator: Locator + + if (this.iframe) { + editFieldLocator = this.page.frameLocator(this.iframe).locator(this.editField) + } else { + editFieldLocator = this.page.locator(this.editField) + } + await editFieldLocator.click() + await this.pause(1000) + } + + /** + * Appends text at the bottom of the editor field. This uses the keyboard to go to the end of the edit field and append. + * @param {string} textToInsert - The text to append. + */ + public async appendText(textToInsert: string): Promise { + await this.focus() + + await this.page.keyboard.press('Control+End') + await this.pause(500) + await this.page.keyboard.type(textToInsert) + } + + /** + * Open the wiris Editor to edit the last item inserted + * Uses selectItemAtCursor, but that's not compatible with froala, so in that case does a click in the contextual toolbar + * @param {Toolbar} toolbar - toolbar of the test + */ + public async openWirisEditorForLastInsertedFormula(toolbar: Toolbar, equation: Equation): Promise { + const isFroala = this.getName() === 'froala' + + if (isFroala) { + const equationElement = this.getEquationElement(equation) + await equationElement.click() + + const mathTypeButton = this.getContextualToolbarMathTypeButton?.() + if (mathTypeButton) { + await this.page.locator(mathTypeButton).click() + } + } else { + await this.selectItemAtCursor() + await this.openWirisEditor(toolbar) + } + } + + /** + * Selects the item at the current cursor position within the editor. This uses shift + the left arrow key to select. + */ + public async selectItemAtCursor(): Promise { + await this.page.keyboard.press('Shift+ArrowLeft') + } + + /** + * This gets all the text in the editor field + * @returns boolean indicating if text appears after equation + */ + public async isTextAfterEquation(typedText: string, altTextEquation: string): Promise { + let frameOrPage: Page | FrameLocator + if (this.iframe) { + frameOrPage = this.page.frameLocator(this.iframe) + } else { + frameOrPage = this.page + } + + const html = await frameOrPage.locator(this.editField).innerHTML() + const indexText = html.indexOf(typedText) + const indexEquation = html.indexOf(altTextEquation) + + return indexEquation < indexText + } + + /** + * This gets all the latex equations $$ expression $$ from the edit field + * It then trims whitespaces at beginning and end, + * and replaces instances of $$ for blank text so as to get only the latex. + * @returns an array of strings containing latex equations or undefined if there are none + */ + public async getLatexEquationsInEditField(): Promise { + let frameOrPage: Page | FrameLocator + if (this.iframe) { + frameOrPage = this.page.frameLocator(this.iframe) + } else { + frameOrPage = this.page + } + + const textContents = await frameOrPage.locator(this.editField).textContent() + + if (!textContents) { + return undefined + } + + const expressions = textContents.match(/\$\$.*?\$\$/g)?.map((latexEquation) => latexEquation.trim().replaceAll('$$', '')) + + return expressions + } + + public async waitForLatexExpression(latexExpression: string): Promise { + await expect(async () => { + const latexEquations = await this.getLatexEquationsInEditField() + expect(latexEquations?.some((eq: string) => eq === latexExpression)).toBeTruthy() + }).toPass({ timeout: 10000 }) + } + + public async copyAllEditorContent(): Promise { + await this.focus() + await this.page.keyboard.press('Control+a') + await this.setClipboardText('') + await this.pause(500) + await this.page.keyboard.press('Control+c') + } + + public async cutAllEditorContent(): Promise { + await this.focus() + await this.page.keyboard.press('Control+a') + await this.setClipboardText('') + await this.pause(500) + await this.page.keyboard.press('Control+x') + } + + async setClipboardText(text: string): Promise { + await this.page.evaluate(async (t) => { + await (globalThis as any).navigator.clipboard.writeText(t); + }, text); + } + + public async dragDropLastFormula(equation: Equation): Promise { + await this.focus() + + const equationElement = this.getEquationElement(equation) + let editDivElement: Locator + + if (this.iframe) { + editDivElement = this.page.frameLocator(this.iframe).locator(this.editField) + } else { + editDivElement = this.page.locator(this.editField) + } + + const equationBox = await equationElement.boundingBox() + const editDivBox = await editDivElement.boundingBox() + + if (equationBox && editDivBox) { + await this.page.mouse.move(equationBox.x + equationBox.width / 2, equationBox.y + equationBox.height / 2) + //await this.page.mouse.click(equationBox.x + equationBox.width / 2, equationBox.y + equationBox.height / 2) + await this.pause(500) + await this.page.mouse.down() + await this.pause(500) + await this.page.mouse.move(editDivBox.x, editDivBox.y) + await this.pause(500) + await this.page.mouse.up() + } + } + + public async paste(): Promise { + await this.focus() + await this.page.keyboard.press('Control+v') + } + + public async undo(): Promise { + await this.focus() + await this.page.keyboard.press('Control+z') + } + + public async redo(): Promise { + await this.focus() + await this.page.keyboard.press('Control+Shift+z') + } + + public async clear(): Promise { + await this.focus() + if (this.iframe) { await this.focus() } // avoids failing to clear in ckeditor4 if not focused + await this.page.keyboard.press('Control+a') + await this.pause(500) + await this.page.keyboard.press('Delete') + } + + public async isEditorCleared(): Promise { + let frameOrPage: Page | FrameLocator + if (this.iframe) { + frameOrPage = this.page.frameLocator(this.iframe) + } else { + frameOrPage = this.page + } + + await this.pause(1000) + + const element = frameOrPage.locator(this.editField) + const rawText = (await element.textContent()) ?? '' + const normalized = rawText.replace(/[\s\uFEFF\xA0]+/g, '') + const noTextInEditor = normalized === '' + const equationElements = frameOrPage.locator(`${this.editField} img`) + const noEquationsInEditor = (await equationElements.count()) === 0 + + return noEquationsInEditor && noTextInEditor + } + + public async getImageSize(equation: Equation): Promise<{ width: number; height: number } | null> { + await this.focus() + + const equationElement = this.getEquationElement(equation) + return await equationElement.boundingBox() + } + + public async resizeImageEquation(equation: Equation): Promise { + await this.focus() + + const equationElement = this.getEquationElement(equation) + await equationElement.click() + + const box = await equationElement.boundingBox() + if (box) { + await this.page.mouse.move(box.x + box.width / 2, box.y + box.height / 2) + await this.pause(500) + await this.page.mouse.down() + await this.pause(500) + await this.page.mouse.move(box.x - 10, box.y - 10) + await this.pause(500) + await this.page.mouse.up() + } + } + + public async applyStyle(): Promise { + await this.focus() + await this.page.keyboard.press('Control+b') // Bold + await this.pause(500) + await this.page.keyboard.press('Control+i') // Italic + } + + public async isTextBoldAndItalic(text: string): Promise { + await this.focus() + + let frameOrPage: Page | FrameLocator + if (this.iframe) { + frameOrPage = this.page.frameLocator(this.iframe) + } else { + frameOrPage = this.page + } + + const isCkeditor5 = this.getName() === 'ckeditor5' + if (isCkeditor5) { + await this.page.keyboard.press('Enter') + await this.pause(500) + await this.page.keyboard.press('Backspace') + } + + const elements = frameOrPage.locator(`${this.editField} >> text="${text}"`) + const count = await elements.count() + + for (let i = 0; i < count; i++) { + const element = elements.nth(i) + const fontWeight = await element.evaluate((el) => (globalThis as any).getComputedStyle(el).fontWeight) + const fontStyle = await element.evaluate((el) => (globalThis as any).getComputedStyle(el).fontStyle) + + const isBold = parseInt(fontWeight) >= 700 + const isItalic = fontStyle === 'italic' + + if (isBold && isItalic) { + return true + } + } + + return false + } + + public async moveCaret(): Promise { + await this.focus() + for (let i = 0; i < 8; i++) { + await this.page.keyboard.press('ArrowLeft') + } + } + + public async checkElementAlignment(): Promise { + await this.focus() + + let editContent: Locator + + if (this.iframe) { + editContent = this.page.frameLocator(this.iframe).locator(this.editField) + } else { + editContent = this.page.locator(this.editField) + } + + // Take screenshot for visual comparison + await editContent.screenshot({ + path: `screenshots/${this.getName()}_alignment.png` + }) + } + + public getContextualToolbarMathTypeButton?(): string + + public getContextualToolbarChemTypeButton?(): string + + public getSourceCodeEditorButton?(): string + + public getSourceCodeEditField?(): string +} \ No newline at end of file diff --git a/tests/e2e/page-objects/editor_manager.ts b/tests/e2e/page-objects/editor_manager.ts new file mode 100644 index 000000000..7208b5731 --- /dev/null +++ b/tests/e2e/page-objects/editor_manager.ts @@ -0,0 +1,42 @@ +import { Page } from '@playwright/test' +import Generic from './html/generic' +import CKEditor5 from './html/ckeditor5' +import CKEditor4 from './html/ckeditor4' +import Froala from './html/froala' +import TinyMCE5 from './html/tinymce5' +import TinyMCE6 from './html/tinymce6' +import TinyMCE7 from './html/tinymce7' +import TinyMCE8 from './html/tinymce8' + +import type BaseEditor from './base_editor' + +class EditorManager { + private editorsInConfiguration: string[] | undefined + + public getEditors(page: Page): BaseEditor[] { + + const availableEditors: BaseEditor[] = [ + new Generic(page), + new CKEditor5(page), + new CKEditor4(page), + new Froala(page), + new TinyMCE5(page), + new TinyMCE6(page), + new TinyMCE7(page), + new TinyMCE8(page), + ] + + this.editorsInConfiguration = process.env.HTML_EDITOR?.split('|') + const editorsToUse = availableEditors.filter((editor) => + ((this.editorsInConfiguration?.includes(editor.getName())) ?? false) + ) + + if (editorsToUse.length === 0) { + throw new Error(`No valid editors found in current configuration: ${process.env.HTML_EDITOR}`) + } + + return editorsToUse + } +} + +export default EditorManager \ No newline at end of file diff --git a/tests/e2e/page-objects/equation_entry_form.ts b/tests/e2e/page-objects/equation_entry_form.ts new file mode 100644 index 000000000..1d719fc3a --- /dev/null +++ b/tests/e2e/page-objects/equation_entry_form.ts @@ -0,0 +1,46 @@ +import { Page, Locator, expect } from '@playwright/test' +import BasePage from './page' + +class EquationEntryForm extends BasePage { + constructor(page: Page) { + super(page) + } + + get editField(): Locator { + return this.page.locator('textarea') + } + + get submitButton(): Locator { + return this.page.locator('input[type="submit"]') + } + + public async isOpen(): Promise { + const context = this.page.context() + const pages = context.pages() + if (pages.length !== 2) return false; + const editAreaVisible = await this.editField.isVisible() + const submitButtonVisible = await this.submitButton.isVisible() + return submitButtonVisible && editAreaVisible + } + + public async setEquation(text: string): Promise { + await this.editField.fill(text) + const closePromise = this.page.waitForEvent('close') + await this.submitButton.click({ force: true }).catch(() => {}) // Ignore any errors from closing the popup + await closePromise + } + + public async getText(): Promise { + await expect(async () => { + const text = await this.editField.inputValue() + expect(text).not.toBe('') + }).toPass({ timeout: 5000 }) + + const text = await this.editField.inputValue() + await this.submitButton.click() + + return text + } +} + +export default EquationEntryForm \ No newline at end of file diff --git a/tests/e2e/page-objects/html/ckeditor4.ts b/tests/e2e/page-objects/html/ckeditor4.ts new file mode 100644 index 000000000..a5d9a65e6 --- /dev/null +++ b/tests/e2e/page-objects/html/ckeditor4.ts @@ -0,0 +1,16 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class CKEditor4 extends BaseEditor { + protected readonly wirisEditorButtonMathType = "[title='Insert a math equation - MathType']" + protected readonly wirisEditorButtonChemType = "[title='Insert a chemistry formula - ChemType']" + protected readonly editField = 'body' + protected readonly iframe = "iframe[title='Editor, editor']" + protected readonly name = 'ckeditor4' + + constructor(page: Page) { + super(page) + } +} + +export default CKEditor4 \ No newline at end of file diff --git a/tests/e2e/page-objects/html/ckeditor5.ts b/tests/e2e/page-objects/html/ckeditor5.ts new file mode 100644 index 000000000..a098a3164 --- /dev/null +++ b/tests/e2e/page-objects/html/ckeditor5.ts @@ -0,0 +1,25 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class CKEditor5 extends BaseEditor { + protected readonly wirisEditorButtonMathType = "[data-cke-tooltip-text='Insert a math equation - MathType']" + protected readonly wirisEditorButtonChemType = "[data-cke-tooltip-text='Insert a chemistry formula - ChemType']" + protected readonly sourceCodeEditorButton = "[data-cke-tooltip-text='Source']" + protected readonly sourceCodeEditField = '.ck-source-editing-area' + protected readonly editField = '.ck-editor__editable' + protected readonly name = 'ckeditor5' + + constructor(page: Page) { + super(page) + } + + public getSourceCodeEditorButton(): string { + return this.sourceCodeEditorButton + } + + public getSourceCodeEditField(): string { + return this.sourceCodeEditField + } +} + +export default CKEditor5 \ No newline at end of file diff --git a/tests/e2e/page-objects/html/froala.ts b/tests/e2e/page-objects/html/froala.ts new file mode 100644 index 000000000..fffca1ad5 --- /dev/null +++ b/tests/e2e/page-objects/html/froala.ts @@ -0,0 +1,23 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class Froala extends BaseEditor { + protected readonly wirisEditorButtonMathType = '.fr-btn-grp.fr-float-left >> #wirisEditor-1' + protected readonly wirisEditorButtonChemType = '.fr-btn-grp.fr-float-left >> #wirisChemistry-1' + protected readonly editField = '.fr-element' + protected readonly name = 'froala' + + constructor(page: Page) { + super(page) + } + + public getContextualToolbarMathTypeButton(): string { + return '.fr-buttons #wirisEditor-1' + } + + public getContextualToolbarChemTypeButton(): string { + return '.fr-buttons #wirisChemistry-1' + } +} + +export default Froala \ No newline at end of file diff --git a/tests/e2e/page-objects/html/generic.ts b/tests/e2e/page-objects/html/generic.ts new file mode 100644 index 000000000..d8c72f686 --- /dev/null +++ b/tests/e2e/page-objects/html/generic.ts @@ -0,0 +1,15 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class Generic extends BaseEditor { + protected readonly wirisEditorButtonChemType = '#chemistryIcon' + protected readonly wirisEditorButtonMathType = '#editorIcon' + protected readonly editField = '#editable' + protected readonly name = 'generic' + + constructor(page: Page) { + super(page) + } +} + +export default Generic \ No newline at end of file diff --git a/tests/e2e/page-objects/html/tinymce5.ts b/tests/e2e/page-objects/html/tinymce5.ts new file mode 100644 index 000000000..0359f183a --- /dev/null +++ b/tests/e2e/page-objects/html/tinymce5.ts @@ -0,0 +1,16 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class TinyMCE5 extends BaseEditor { + protected readonly wirisEditorButtonChemType = "[aria-label='Insert a chemistry formula - ChemType']" + protected readonly wirisEditorButtonMathType = "[aria-label='Insert a math equation - MathType']" + protected readonly editField = 'body' + protected readonly iframe = "iframe[id='editor_ifr']" + protected readonly name = 'tinymce5' + + constructor(page: Page) { + super(page) + } +} + +export default TinyMCE5 \ No newline at end of file diff --git a/tests/e2e/page-objects/html/tinymce6.ts b/tests/e2e/page-objects/html/tinymce6.ts new file mode 100644 index 000000000..6d3d21259 --- /dev/null +++ b/tests/e2e/page-objects/html/tinymce6.ts @@ -0,0 +1,16 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class TinyMCE6 extends BaseEditor { + protected readonly wirisEditorButtonChemType = "[aria-label='Insert a chemistry formula - ChemType']" + protected readonly wirisEditorButtonMathType = "[aria-label='Insert a math equation - MathType']" + protected readonly editField = 'body' + protected readonly iframe = "iframe[id='editor_ifr']" + protected readonly name = 'tinymce6' + + constructor(page: Page) { + super(page) + } +} + +export default TinyMCE6 \ No newline at end of file diff --git a/tests/e2e/page-objects/html/tinymce7.ts b/tests/e2e/page-objects/html/tinymce7.ts new file mode 100644 index 000000000..5cc845559 --- /dev/null +++ b/tests/e2e/page-objects/html/tinymce7.ts @@ -0,0 +1,16 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class TinyMCE7 extends BaseEditor { + protected readonly wirisEditorButtonChemType = "[aria-label='Insert a chemistry formula - ChemType']" + protected readonly wirisEditorButtonMathType = "[aria-label='Insert a math equation - MathType']" + protected readonly editField = 'body' + protected readonly iframe = "iframe[id='editor_ifr']" + protected readonly name = 'tinymce7' + + constructor(page: Page) { + super(page) + } +} + +export default TinyMCE7 \ No newline at end of file diff --git a/tests/e2e/page-objects/html/tinymce8.ts b/tests/e2e/page-objects/html/tinymce8.ts new file mode 100644 index 000000000..a2e41d938 --- /dev/null +++ b/tests/e2e/page-objects/html/tinymce8.ts @@ -0,0 +1,16 @@ +import { Page } from '@playwright/test' +import BaseEditor from '../base_editor' + +class TinyMCE8 extends BaseEditor { + protected readonly wirisEditorButtonChemType = "[aria-label='Insert a chemistry formula - ChemType']" + protected readonly wirisEditorButtonMathType = "[aria-label='Insert a math equation - MathType']" + protected readonly editField = 'body' + protected readonly iframe = "iframe[id='editor_ifr']" + protected readonly name = 'tinymce8' + + constructor(page: Page) { + super(page) + } +} + +export default TinyMCE8 \ No newline at end of file diff --git a/tests/e2e/page-objects/page.ts b/tests/e2e/page-objects/page.ts new file mode 100644 index 000000000..eedb3d26b --- /dev/null +++ b/tests/e2e/page-objects/page.ts @@ -0,0 +1,26 @@ +import { Page } from '@playwright/test' + +export default class BasePage { + protected page: Page + + constructor(page: Page) { + this.page = page + } + + /** + * Take a screenshot + */ + async takeScreenshot(name?: string): Promise { + return await this.page.screenshot({ + fullPage: true, + path: name ? `screenshots/${name}.png` : undefined + }) + } + + /** + * Wait for a specific amount of time + */ + async pause(milliseconds: number): Promise { + await this.page.waitForTimeout(milliseconds) + } +} \ No newline at end of file diff --git a/tests/e2e/page-objects/wiris_editor.ts b/tests/e2e/page-objects/wiris_editor.ts new file mode 100644 index 000000000..436f0e60e --- /dev/null +++ b/tests/e2e/page-objects/wiris_editor.ts @@ -0,0 +1,213 @@ +import { Page, Locator, expect } from '@playwright/test' +import BasePage from './page' +import EquationEntryMode from '../enums/equation_entry_mode' +import TypingMode from '../enums/typing_mode' +import EquationEntryForm from './equation_entry_form' + +class WirisEditor extends BasePage { + private equationEntryForm: EquationEntryForm + + constructor(page: Page) { + super(page) + this.equationEntryForm = new EquationEntryForm(page) + } + + get modalTitle(): Locator { + return this.page.locator('.wrs_modal_title') + } + + get wirisEditorWindow(): Locator { + return this.page.locator('.wrs_content_container') + } + + get insertButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-insert-button']") + } + + get cancelButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-cancel-button']") + } + + get handModeButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-key2hand-button']") + } + + get closeButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-close-button']") + } + + get fullScreenButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-fullscreen-enable-button']") + } + + get exitFullScreenButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-minimize-button']") + } + + get modalOverlay(): Locator { + return this.page.locator('[id*=wrs_modal_overlay]') + } + + get minimizeButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-fullscreen-disable-button']") + } + + get mathInputField(): Locator { + return this.page.locator('input[aria-label="Math input"]') + } + + get confirmationDialog(): Locator { + return this.page.locator('#wrs_popupmessage') + } + + get confirmationDialogCancelButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-cd-cancel-button']") + } + + get confirmationDialogCloseButton(): Locator { + return this.page.locator("[data-testid='mtcteditor-cd-close-button']") + } + + get handCanvas(): Locator { + return this.page.locator('canvas.wrs_canvas') + } + + /** + * Checks if the wiris editor modal is open by checking for the presence of the modal window, cancel and insert buttons. + */ + public async isOpen(): Promise { + return (await this.insertButton.isVisible()) && (await this.closeButton.isVisible() && (await this.wirisEditorWindow.isVisible())) + } + + /** + * This waits for the wiris editor modal to be open and for the cancel button to be displayed + */ + public async waitUntilLoaded(typeMode?: TypingMode): Promise { + typeMode = typeMode ?? TypingMode.KEYBOARD + await this.wirisEditorWindow.waitFor({ state: 'visible' }) + await this.insertButton.waitFor({ state: 'visible' }) + await this.handModeButton.waitFor({ state: 'visible' }) + await this.cancelButton.waitFor({ state: 'visible' }) + if (typeMode === TypingMode.KEYBOARD) { + await this.mathInputField.waitFor({ state: 'visible' }) + } else { + await this.handCanvas.waitFor({ state: 'visible' }) + } + } + + /** + * This waits for the wiris editor modal to be closed + */ + public async waitUntilClosed(): Promise { + await this.wirisEditorWindow.waitFor({ state: 'hidden' }) + } + + /** + * This performs select all and delete using Playwright keyboard, cross platform + */ + public async deleteContents(): Promise { + await this.page.keyboard.press('Control+a') + await this.page.keyboard.press('Delete') + } + + /** + * This types an equation into the edit field + * @param keysToType This parameter specifies which keys will be typed in the editor. + */ + public async typeEquationViaKeyboard(keysToType: string): Promise { + await this.mathInputField.click() + await this.pause(500) // This wait is needed in order to simulate real typing + await this.page.keyboard.type(keysToType) + await this.pause(500) // If we don't wait, it crashes. This is typical also with a user that would type, wait a few seconds, then insert equation. + } + + /** + * The same as typeEquation, but this inserts the equation + */ + public async insertEquationViaKeyboard(keysToType: string): Promise { + await this.typeEquationViaKeyboard(keysToType) + await this.insertButton.click() + await this.waitUntilClosed() + } + + /** + * @param entryMode (EquationEntryMode): This parameter specifies which mode the equation entry form will be opened in. + * This presses ctrl shift X for MathML and L for latex to show the equation entry form. + */ + public async showEquationEntryForm(entryMode: EquationEntryMode): Promise { + const popupPromise = this.page.waitForEvent('popup'); + switch (entryMode) { + case EquationEntryMode.MATHML: + await this.page.keyboard.press('Control+Shift+KeyX') + break + case EquationEntryMode.LATEX: + await this.page.keyboard.press('Control+Shift+KeyL') + break + } + const popup = await popupPromise; + this.equationEntryForm = new EquationEntryForm(popup); + expect(await this.equationEntryForm.isOpen()).toBeTruthy() + } + + /** + * This allows insertion of an equation by typing text into equation entry form to insert the equation. and automatically hit the insert button + * @param text the text to type into the form + * If the equation entry form is not open, it will open by default using MathML mode. + */ + public async insertEquationUsingEntryForm(text: string): Promise { + await this.typeEquationUsingEntryForm(text) + await this.pause(500) // TODO: Used to avoid click insert without formula submit, should be investigated further + await this.insertButton.click() + await this.waitUntilClosed() + } + + /** + * This allows insertion of an equation by typing text into equation entry form to insert the equation. It does not hit the insert button. + * @param text the text to type into the form + * If the equation entry form is not open, it will open by default using MathML mode. + */ + public async typeEquationUsingEntryForm(text: string): Promise { + const entryFormVisible = await this.equationEntryForm.isOpen() + + if (!entryFormVisible) { + await this.showEquationEntryForm(EquationEntryMode.MATHML) + } + + await this.equationEntryForm.setEquation(text) + } + + /** + * This uses the equation entry form to get the MathML currently being used in the editor. + * @returns a string with the MathML currently used in the rendered equation. + */ + public async getMathML(): Promise { + const entryFormVisible = await this.equationEntryForm.isOpen() + + if (!entryFormVisible) { + await this.showEquationEntryForm(EquationEntryMode.MATHML) + } + + return await this.equationEntryForm.getText() + } + + /** + * Appends text at the bottom of the math input field. This uses the keyboard to go to the end of the edit field and append. + * @param {string} textToInsert - The text to append. + */ + public async appendText(textToInsert: string): Promise { + await this.mathInputField.click() + await this.pause(500) + await this.page.keyboard.press('End') + await this.pause(500) + await this.page.keyboard.type(textToInsert) + } + + public async getMode(): Promise { + const title = await this.handModeButton.getAttribute('title') + return title === 'Go to handwritten mode' ? TypingMode.KEYBOARD : + title === 'Use keyboard' ? TypingMode.HAND : + TypingMode.UNKNOWN + } +} + +export default WirisEditor \ No newline at end of file diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts new file mode 100644 index 000000000..f8ec7077b --- /dev/null +++ b/tests/e2e/playwright.config.ts @@ -0,0 +1,46 @@ +import { defineConfig, devices } from '@playwright/test' +import dotenv from 'dotenv' + +dotenv.config() + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 1, + workers: process.env.CI ? '80%' : undefined, + reporter: [ + ['html', { open: process.env.CI ? 'never' : 'on-failure' }], + ['junit', { outputFile: 'test-results/results.xml' }], + process.env.CI ? ['github'] : ['list'], + ], + use: { + baseURL: process.env.BASE_URL || 'https://integrations.wiris.kitchen/', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + }, + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + viewport: { width: 1920, height: 1080 }, + permissions: ['clipboard-read', 'clipboard-write'] + } + }, + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], + viewport: { width: 1920, height: 1080 } + } + } + ], + outputDir: 'test-results', + timeout: 60_000, + expect: { + timeout: 10_000 + }, + +}) \ No newline at end of file diff --git a/tests/e2e/tests/edit/edit_corner_cases.spec.ts b/tests/e2e/tests/edit/edit_corner_cases.spec.ts new file mode 100644 index 000000000..9ef90344a --- /dev/null +++ b/tests/e2e/tests/edit/edit_corner_cases.spec.ts @@ -0,0 +1,96 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { + test.describe(`Edit equation (corner cases) - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`MTHTML-81 Edit styled equation: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.styledSingleNumber.mathml) + await editor.waitForEquation(Equations.styledSingleNumber) + + await editor.openWirisEditorForLastInsertedFormula(Toolbar.MATH, Equations.styledSingleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.styledOnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + + test(`MTHTML-85 User edits a formula and continues typing text: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + await editor.openWirisEditorForLastInsertedFormula(Toolbar.MATH, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationViaKeyboard('+1') + + const textToType = 'I can keep typing in the Editor' + await editor.pause(500) + await page.keyboard.type(textToType) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + + const isTextAfterEquation = await editor.isTextAfterEquation(textToType, Equations.OnePlusOne.altText) + expect(isTextAfterEquation).toBeTruthy() + }) + + test(`MTHTML-100 User edits a formula deleted during edition: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + await editor.openWirisEditorForLastInsertedFormula(Toolbar.MATH, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await editor.clear() // Delete the formula in the background + + // We continue with the insert in the wiris editor + await wirisEditor.insertEquationViaKeyboard('+1') + + // No error should be visible, and the editor should be blank + expect(await editor.isEditorCleared()).toBeTruthy() + + // Again, we do an insert + edit so we assure everything works as expected + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + await editor.openWirisEditorForLastInsertedFormula(Toolbar.MATH, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.pause(1000) + await wirisEditor.insertEquationViaKeyboard('+1') + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/edit/edit_hand.spec.ts b/tests/e2e/tests/edit/edit_hand.spec.ts new file mode 100644 index 000000000..f88c6234e --- /dev/null +++ b/tests/e2e/tests/edit/edit_hand.spec.ts @@ -0,0 +1,38 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import TypingMode from '../../enums/typing_mode' + +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + test.describe(`Edit equation by hand - ${editorName} editor`,{ + tag: [`@${editorName}`, '@regression'], + }, () => { + for (const toolbar of toolbars) { + test(`@smoke MTHTML-8 Edit Hand equation with ${toolbar}: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.pause(1000) // Wait for the equation to be processed + await wirisEditor.handModeButton.click() + await wirisEditor.pause(500) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.openWirisEditorForLastInsertedFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded(TypingMode.HAND) + + const typingMode = await wirisEditor.getMode() + expect(typingMode).toBe(TypingMode.HAND) + }) + } + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/edit/edit_via_doble_click.spec.ts b/tests/e2e/tests/edit/edit_via_doble_click.spec.ts new file mode 100644 index 000000000..e36b803d6 --- /dev/null +++ b/tests/e2e/tests/edit/edit_via_doble_click.spec.ts @@ -0,0 +1,56 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { + test.describe(`Edit equation via double click - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + // Skip test for Froala as double click is not available + test.skip(editorName === 'froala', `Double click not available in ${editorName}`) + + test(`@smoke MTHTML-2 Edit Math equation: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + const equationInDOM = editor.getEquationElement(Equations.singleNumber) + await editor.clickElement(equationInDOM, 2) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationViaKeyboard('+1') + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + + test(`@smoke MTHTML-2 Edit Chemistry equation: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.CHEMISTRY) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + const equationInDOM = editor.getEquationElement(Equations.singleNumber) + await editor.clickElement(equationInDOM, 2) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationViaKeyboard('+1') + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/edit/edit_via_selection.spec.ts b/tests/e2e/tests/edit/edit_via_selection.spec.ts new file mode 100644 index 000000000..2d8913cc2 --- /dev/null +++ b/tests/e2e/tests/edit/edit_via_selection.spec.ts @@ -0,0 +1,56 @@ +import { test, expect } from '@playwright/test' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import { getEditorsFromEnv } from '../../helpers/test-setup' +import { setupEditor } from '../../helpers/test-setup' +import Equation from '../../interfaces/equation' + +// Configure which editors to test via environment variables +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + test.describe(`Edit equation via selection - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + for (const toolbar of toolbars) { + test(`@smoke MTHTML-8 Edit Math equation with ${toolbar}: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + await editor.openWirisEditorForLastInsertedFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationViaKeyboard('+1') + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + + test(`@smoke MTHTML-8 Edit Chemistry equation with ${toolbar}: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.CHEMISTRY) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + await editor.openWirisEditorForLastInsertedFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationViaKeyboard('+1') + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + } + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/editor/copy_cut_drop.spec.ts b/tests/e2e/tests/editor/copy_cut_drop.spec.ts new file mode 100644 index 000000000..293e06cf9 --- /dev/null +++ b/tests/e2e/tests/editor/copy_cut_drop.spec.ts @@ -0,0 +1,78 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { // TODO: review some flaky tests + test.describe(`Copy/Cut/Paste/Drag&Drop - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`MTHTML-95 Copy-paste math formula with ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + await page.keyboard.type('___') // Sending some text to force the copy/paste. Issue in Tiny + + await editor.copyAllEditorContent() + await editor.clear() + await editor.paste() + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationCopied = equationsInHTMLEditor.every((equation: Equation) => equation.altText === Equations.singleNumber.altText) && (equationsInHTMLEditor.length === 1) + expect(isEquationCopied).toBeTruthy() + }) + + test(`MTHTML-96 Cut-paste math formula with ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + await page.keyboard.type('___') // Sending some text to force the copy/paste. Issue in Tiny + + await editor.cutAllEditorContent() + await editor.paste() + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationCut = equationsInHTMLEditor.every((equation: Equation) => equation.altText === Equations.singleNumber.altText) && (equationsInHTMLEditor.length === 1) + expect(isEquationCut).toBeTruthy() + }) + + test(`MTHTML-86 Drag-drop math formula with ${editorName} editor`, async ({ page }) => { + test.fixme((editorName === 'ckeditor5' || editorName === 'generic') && test.info().project.name === 'firefox', `Drag and drop not working for ${editorName} in Firefox`) // TODO: fix drag and drop in Firefox for ckeditor5 and generic editor + + const unsupportedEditors = ['ckeditor4', 'tinymce5', 'tinymce6', 'tinymce7', 'tinymce8'] // WIP + + // Skip test for unsupported editors + test.skip(unsupportedEditors.includes(editorName), `Drag and drop not supported for ${editorName}`) + + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + const textToType = 'The equation will be relocated from after this text to before it' + await editor.appendText(textToType) + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await editor.waitForEquation(Equations.OnePlusOne) + + await editor.dragDropLastFormula(Equations.OnePlusOne) + await editor.waitForEquation(Equations.OnePlusOne) + + const isTextAfterEquation = await editor.isTextAfterEquation(textToType, Equations.OnePlusOne.altText) + expect(isTextAfterEquation).toBeTruthy() + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/editor/editor.spec.ts b/tests/e2e/tests/editor/editor.spec.ts new file mode 100644 index 000000000..7c8946aed --- /dev/null +++ b/tests/e2e/tests/editor/editor.spec.ts @@ -0,0 +1,125 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { + test.describe(`Editor functionality - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test.describe('Undo and Redo', () => { + const isKnownIssue = editorName === 'generic'; + test(`MTHTML-78 Undo and redo math formula with ${editorName} editor`, { tag: isKnownIssue ? ['@knownissue'] : [] } , async ({ page }) => { + test.fail(isKnownIssue, 'Known issue: generic editors fails to undo equation'); + + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + await editor.undo() + if ((await editor.getEquations()).length === 0) { + await editor.redo() + } + const isEquationRedone = ((await editor.getEquations()).length === 1) + expect(isEquationRedone).toBeTruthy() + }) + }) + + test.describe('Resize', () => { + test(`MTHTML-22 Formulas cannot be resized ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await editor.waitForEquation(Equations.OnePlusOne) + + const sizeBefore = await editor.getImageSize(Equations.OnePlusOne) + await editor.resizeImageEquation(Equations.OnePlusOne) + const sizeAfter = await editor.getImageSize(Equations.OnePlusOne) + + const sameSize = sizeBefore?.height === sizeAfter?.height && sizeBefore?.width === sizeAfter?.width + expect(sameSize).toBeTruthy() + }) + }) + + test.describe('Source code', () => { + test(`MTHTML-87 Edit source code of a math formula with ${editorName} editor`, async ({ page }) => { + const { editor } = await setupEditor(page, editorName) + + // Skip test if editor doesn't support source code editing + const hasSourceCodeButton = editor.getSourceCodeEditorButton !== undefined + test.skip(!hasSourceCodeButton, `Source code editing not supported for ${editorName}`) + + await editor.open() + await editor.clear() + //await editor.openWirisEditor(Toolbar.MATH) TODO: review if we want to open the wiris editor first, fails tests in some editors + await editor.clickSourceCodeEditor() + await editor.typeSourceText('1+1') + await editor.clickSourceCodeEditor() + const sourceCodeEquation = await editor.getEquations() + expect(sourceCodeEquation[0].altText).toBe('1 plus 1') + }) + }) + + test.describe('Styled text', () => { + test(`MTHTML-79 Validate formula insertion after typing text that contains styles: ${editorName} editor`, async ({ page }) => { + test.fixme((editorName === 'generic') && test.info().project.name === 'firefox', `Ctrl+B and Ctrl+I not working for generic editor on Firefox`) + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + const textBeginning = 'Text_Beginning' + const textEnd = 'Text_End' // The text with spaces brings some errors since the keys are sent without spaces + + await editor.applyStyle() // TODO: not applying style in generic editor on firefox, ctrl+B opens bookmarks menu and ctrl+I opens page info + await page.keyboard.type(textBeginning) + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await editor.waitForEquation(Equations.OnePlusOne) + + await page.keyboard.type(textEnd) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + + const isTextBeginningBoldAndItalic = await editor.isTextBoldAndItalic(textBeginning) + expect(isTextBeginningBoldAndItalic).toBeTruthy() + + const isTextEndBoldAndItalic = await editor.isTextBoldAndItalic(textEnd) + expect(isTextEndBoldAndItalic).toBeTruthy() + }) + }) + + test.describe('Text Alignment', () => { + test(`MTHTML-23 Validate formula alignment: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await page.keyboard.type('___') + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await editor.waitForEquation(Equations.OnePlusOne) + await page.keyboard.type('___') + + await editor.checkElementAlignment() + // Note: Playwright doesn't have a direct equivalent to visual comparison + // TODO: This would need to be implemented using screenshot comparison libraries + }) + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/insert/insert.spec.ts b/tests/e2e/tests/insert/insert.spec.ts new file mode 100644 index 000000000..ef2d25ca9 --- /dev/null +++ b/tests/e2e/tests/insert/insert.spec.ts @@ -0,0 +1,68 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + for (const toolbar of toolbars) { + test.describe(`Insert equation - ${editorName} editor - ${toolbar}`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`@smoke MTHTML-1 Insert equation with ${toolbar} via keyboard: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.insertEquationViaKeyboard('1') + await wirisEditor.pause(500) // Wait for the equation to be processed + await editor.waitForEquation(Equations.singleNumber) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + expect(isEquationPresent).toBeTruthy() + }) + + test(`Insert equation with ${toolbar} using MathML: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + expect(isEquationPresent).toBeTruthy() + }) + }) + } + + test.describe(`ALT Attribute - ${editorName} editor`, () => { + test(`MTHTML-19 Formula - ALT attribute: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.insertEquationViaKeyboard('1') + await wirisEditor.pause(500) // Wait for the equation to be processed + await editor.waitForEquation(Equations.singleNumber) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + expect(isEquationPresent).toBeTruthy() + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/insert/insert_corner_cases.spec.ts b/tests/e2e/tests/insert/insert_corner_cases.spec.ts new file mode 100644 index 000000000..47e15595e --- /dev/null +++ b/tests/e2e/tests/insert/insert_corner_cases.spec.ts @@ -0,0 +1,66 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + for (const toolbar of toolbars) { + test.describe(`Insert equation (corner cases) - ${editorName} editor - ${toolbar}`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`MTHTML-80 Insert styled equation: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.insertEquationUsingEntryForm(Equations.styledOnePlusOne.mathml) + await editor.waitForEquation(Equations.styledOnePlusOne) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.styledOnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + + test(`MTHTML-68 Insert equation with special characters: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.insertEquationUsingEntryForm(Equations.specialCharacters.mathml) + await editor.waitForEquation(Equations.specialCharacters) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.specialCharacters.altText) + expect(isEquationPresent).toBeTruthy() + }) + + test(`MTHTML-90 User inserts a formula when the editor input doesn't have focus: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + // Insert a formula using only the keyboard (click MT button > type > tab to insert > enter) and keep writing + await editor.open() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.typeEquationViaKeyboard('1+1') + await page.keyboard.press('Tab') + await wirisEditor.pause(500) + await page.keyboard.press('Enter') + + await editor.waitForEquation(Equations.OnePlusOne) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + expect(isEquationPresent).toBeTruthy() + }) + }) + } +} \ No newline at end of file diff --git a/tests/e2e/tests/insert/insert_hand.spec.ts b/tests/e2e/tests/insert/insert_hand.spec.ts new file mode 100644 index 000000000..89a1f5779 --- /dev/null +++ b/tests/e2e/tests/insert/insert_hand.spec.ts @@ -0,0 +1,37 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import EquationEntryMode from '../../enums/equation_entry_mode' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + for (const toolbar of toolbars) { + test.describe(`Insert equation via Hand - ${editorName} editor - ${toolbar}`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`@smoke MTHTML-20 Insert a handwritten equation using ${toolbar}: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.showEquationEntryForm(EquationEntryMode.MATHML) + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.pause(500) // Wait for the equation to be processed + await wirisEditor.handModeButton.click() + await wirisEditor.pause(500) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + expect(isEquationPresent).toBeTruthy() + }) + }) + } +} \ No newline at end of file diff --git a/tests/e2e/tests/latex/latex.spec.ts b/tests/e2e/tests/latex/latex.spec.ts new file mode 100644 index 000000000..7b9140b78 --- /dev/null +++ b/tests/e2e/tests/latex/latex.spec.ts @@ -0,0 +1,87 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import EquationEntryMode from '../../enums/equation_entry_mode' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { + test.describe(`LaTeX - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`Validate LaTeX formula detection: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.appendText('$$ ' + Equations.squareRootY.latex + '$$') + + await editor.selectItemAtCursor() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertButton.click() + await editor.waitForLatexExpression('\\sqrt y') + + const latexEquations = await editor.getLatexEquationsInEditField() + expect(latexEquations).toContain('\\sqrt y') + }) + + test(`Insert formula using LaTeX equation entry form: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.showEquationEntryForm(EquationEntryMode.LATEX) + await wirisEditor.insertEquationUsingEntryForm(Equations.squareRootY.latex ?? (() => { throw new Error('LaTeX equation is undefined') })()) + await wirisEditor.pause(500) // Wait for the equation to be processed + await editor.waitForEquation(Equations.squareRootY) + + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.squareRootY.altText) + expect(isEquationPresent).toBeTruthy() + }) + + test(`@smoke MTHTML-10 Edit LaTeX equation: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.clear() + await editor.appendText('$$ ' + Equations.squareRootY.latex + '$$') + await editor.selectItemAtCursor() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.appendText('+5') + await wirisEditor.pause(500) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.waitForLatexExpression(Equations.squareRootYPlusFive.latex ?? (() => { throw new Error('LaTeX equation is undefined') })()) + const latexEquations = await editor.getLatexEquationsInEditField() + expect(latexEquations).toContain(Equations.squareRootYPlusFive.latex ?? (() => { throw new Error('LaTeX equation is undefined') })()) + }) + + test(`Edit empty LaTeX equation: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + const INPUT_FORMULA = 'sin x' + + await editor.open() + await editor.clear() + await editor.appendText('$$$$') + await editor.selectItemAtCursor() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.insertEquationViaKeyboard(INPUT_FORMULA) + + await editor.waitForLatexExpression('\\sin\\;x') + const latexEquations = await editor.getLatexEquationsInEditField() + expect(latexEquations).toContain('\\sin\\;x') + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/modal/confirmation_dialog.spec.ts b/tests/e2e/tests/modal/confirmation_dialog.spec.ts new file mode 100644 index 000000000..b2fff3823 --- /dev/null +++ b/tests/e2e/tests/modal/confirmation_dialog.spec.ts @@ -0,0 +1,108 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Toolbar from '../../enums/toolbar' +import Equations from '../../enums/equations' +import Equation from '../../interfaces/equation' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { + test.describe(`Confirmation dialog - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`MTHTML-11 When wiris editor contains no changes and user clicks the Cancel button, modal closes: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.cancelButton.click() + + await expect(wirisEditor.wirisEditorWindow).not.toBeVisible() + }) + + test(`MTHTML-3 When wiris editor contains changes and user clicks the Cancel button, modal displays confirmation dialog: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + const EQUATION_TO_TYPE_VIA_KEYBOARD = '111' + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard(EQUATION_TO_TYPE_VIA_KEYBOARD) + + await wirisEditor.cancelButton.click() // confirmation dialog should be displayed after this click! + + await expect(wirisEditor.wirisEditorWindow).toBeVisible() + await expect(wirisEditor.confirmationDialog).toBeVisible() + }) + + test(`When confirmation dialog is displayed and user clicks cancel, confirmation dialog closes: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + const EQUATION_TO_TYPE_VIA_KEYBOARD = '221221' + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard(EQUATION_TO_TYPE_VIA_KEYBOARD) + await wirisEditor.cancelButton.click() // confirmation dialog should be displayed after this click! + + await wirisEditor.confirmationDialogCancelButton.click() + + await expect(wirisEditor.confirmationDialog).not.toBeVisible() + await expect(wirisEditor.wirisEditorWindow).toBeVisible() + }) + + test(`MTHTML-12 When confirmation dialog is displayed and user clicks the Close button, wiris editor closes: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + const EQUATION_TO_TYPE_VIA_KEYBOARD = '3' + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard(EQUATION_TO_TYPE_VIA_KEYBOARD) + await wirisEditor.closeButton.click() // confirmation dialog should be displayed after this click! + + await wirisEditor.confirmationDialogCloseButton.click() + + await expect(wirisEditor.confirmationDialog).not.toBeVisible() + await expect(wirisEditor.wirisEditorWindow).not.toBeVisible() + }) + + test(`MTHTML-14 When confirmation dialog is displayed and user press the ESC key, wiris editor closes: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + const EQUATION_TO_TYPE_VIA_KEYBOARD = '3' + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard(EQUATION_TO_TYPE_VIA_KEYBOARD) + await page.keyboard.press('Escape') // confirmation dialog should be displayed after this! + + await wirisEditor.confirmationDialogCloseButton.click() + + await expect(wirisEditor.confirmationDialog).not.toBeVisible() + await expect(wirisEditor.wirisEditorWindow).not.toBeVisible() + }) + + test(`MTHTML-29 Insert an equation after aborting the cancel: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + const EQUATION_TO_INSERT_VIA_KEYBOARD = '1' + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard(EQUATION_TO_INSERT_VIA_KEYBOARD) + + await wirisEditor.cancelButton.click() // confirmation dialog should be displayed after this click! + await wirisEditor.confirmationDialogCancelButton.click() + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.waitForEquation(Equations.singleNumber) + const equationsInHTMLEditor = await editor.getEquations() + const isEquationPresent = equationsInHTMLEditor.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + expect(isEquationPresent).toBeTruthy() + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/modal/toolbar.spec.ts b/tests/e2e/tests/modal/toolbar.spec.ts new file mode 100644 index 000000000..82db1d164 --- /dev/null +++ b/tests/e2e/tests/modal/toolbar.spec.ts @@ -0,0 +1,102 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Toolbar from '../../enums/toolbar' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { + test.describe(`Toolbar functions - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`MTHTML-13 When the modal is displayed and close button is clicked, wiris modal closes: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.closeButton.click() + await wirisEditor.waitUntilClosed() + + await expect(wirisEditor.wirisEditorWindow).not.toBeVisible() + }) + + test(`When the modal is displayed and enable full screen button is clicked, display changes to full-screen: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.fullScreenButton.click() + + await expect(wirisEditor.modalOverlay).toBeVisible() + }) + + test(`When the modal is displayed in full screen mode and disable full-screen button is clicked, display changes to normal: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.fullScreenButton.click() + + await wirisEditor.exitFullScreenButton.click() + + await expect(wirisEditor.modalOverlay).not.toBeVisible() + }) + + // WIP + test.fixme(`When the modal is displayed and minimize button is clicked, modal is minimized: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await wirisEditor.minimizeButton.click() + await editor.pause(1000) + + await expect(wirisEditor.wirisEditorWindow).not.toBeVisible() + }) + + // WIP + test.fixme(`When the modal is minimized and user double clicks the banner, modal opens: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.minimizeButton.click() + await wirisEditor.waitUntilClosed() + + await wirisEditor.modalTitle.click() + + await expect(wirisEditor.wirisEditorWindow).toBeVisible() + }) + + test.fixme(`When the modal is minimized from full-screen mode and user double clicks the banner, modal opens in full-screen mode: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.fullScreenButton.click() + + await wirisEditor.minimizeButton.click() + // TODO: This test seems incomplete in the original WebDriverIO version + }) + + test(`MTHTML-15 When the modal is displayed and ESC key is pressed, wiris modal closes: ${editorName} editor`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName) + + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + + await page.keyboard.press('Escape') + await wirisEditor.waitUntilClosed() + + await expect(wirisEditor.wirisEditorWindow).not.toBeVisible() + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tests/telemetry/telemetry.spec.ts b/tests/e2e/tests/telemetry/telemetry.spec.ts new file mode 100644 index 000000000..ca9abf388 --- /dev/null +++ b/tests/e2e/tests/telemetry/telemetry.spec.ts @@ -0,0 +1,45 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import { captureTelemetryRequests } from '../../helpers/network' +import Toolbar from '../../enums/toolbar' +import Equations from '../../enums/equations' + +const editors = getEditorsFromEnv() + +for (const editorName of editors) { + test.describe('Telemetry', { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`MTHTML-59 MathType all events testing: ${editorName} editor`, async ({ page, browserName }) => { + // Skip Firefox as mentioned in original test + test.skip(browserName === 'firefox', 'Telemetry tests are skipped on Firefox') + + const { editor, wirisEditor } = await setupEditor(page, editorName) + + // Enable Network Listener and capture all telemetry requests + const foundEvents: string[] = [] + await captureTelemetryRequests(page, foundEvents) + + // Perform the actions that will trigger the telemetry events + await editor.open() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.cancelButton.click() + await editor.clear() + await editor.openWirisEditor(Toolbar.MATH) + await wirisEditor.waitUntilLoaded() + await wirisEditor.insertEquationUsingEntryForm(Equations.singleNumber.mathml) + await editor.waitForEquation(Equations.singleNumber) + await editor.openWirisEditorForLastInsertedFormula(Toolbar.MATH, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.pause(1500) // This pause is needed to wait for the last event + + // Check that all events have been sent are the ones we expect, in the same order + const expectEvents = ['STARTED_TELEMETRY_SESSION', 'CLOSED_MTCT_EDITOR', 'OPENED_MTCT_EDITOR', 'INSERTED_FORMULA', 'CLOSED_MTCT_EDITOR', 'OPENED_MTCT_EDITOR', 'INSERTED_FORMULA', 'CLOSED_MTCT_EDITOR'] + expect(JSON.stringify(expectEvents) === JSON.stringify(foundEvents)).toBeTruthy() + }) + }) +} \ No newline at end of file diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json new file mode 100644 index 000000000..37aa9dd70 --- /dev/null +++ b/tests/e2e/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": false, + "outDir": "./dist", + "rootDir": ".", + "types": ["node", "@playwright/test"] + }, + "include": [ + "tests/**/*", + "page-objects/**/*", + "helpers/**/*", + "enums/**/*", + "interfaces/**/*", + "playwright.config.ts" + ], + "exclude": ["node_modules", "dist", "test-results"] +} \ No newline at end of file From f390e9984bd99b6d0ad2ec464b2931deb4fa0c0b Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 13 Nov 2025 20:03:54 +0100 Subject: [PATCH 02/29] test: enable e2e tests to run locally --- .gitignore | 7 ++- docs/tests/README.md | 10 ++++ package.json | 3 +- tests/e2e/.env.example | 14 +++-- tests/e2e/helpers/test-setup.ts | 13 +++-- tests/e2e/page-objects/base_editor.ts | 83 +++++++++++++++++---------- tests/e2e/playwright.config.ts | 34 +++++++++-- 7 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 docs/tests/README.md diff --git a/.gitignore b/.gitignore index e2b72db8e..6be366f05 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,10 @@ package-lock.json packages/*/yarn.lock -# Cypress -cypress/screenshots -cypress/videos +# Ignore Playwright test reports and results +playwright-report +test-results +screenshots node_modules diff --git a/docs/tests/README.md b/docs/tests/README.md new file mode 100644 index 000000000..14e606f7f --- /dev/null +++ b/docs/tests/README.md @@ -0,0 +1,10 @@ +ckeditor4 8001 +ckeditor5 8002 +froala 8004 +generic 8007 +tinymce5 8006 +tinymce6 8008 +tinymce7 8009 +tinymce8 8010 +viewer 8009 + diff --git a/package.json b/package.json index 7ee69a982..8906d3690 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "preinstall": "node packages/res/git-data.mjs", "postinstall": "rm -rf ~/.config/yarn/link/* && for d in packages/*/ ; do (cd $d && yarn link); done", "test-old": "node scripts/services/executeTests.js", - "test": "nx run-many --target=test --all --parallel", - "test:e2e": "playwright test", + "test:e2e": "playwright test --config=tests/e2e/playwright.config.ts", "build": "cd demos/html/generic && npm install && npm start &", "lint": "prettier --write . --ignore-path .gitignore --ignore-path .prettierignore --ignore-path .eslintignore && nx run-many --target=lint --all --parallel --fix" }, diff --git a/tests/e2e/.env.example b/tests/e2e/.env.example index c87144c93..78b939b27 100644 --- a/tests/e2e/.env.example +++ b/tests/e2e/.env.example @@ -1,12 +1,14 @@ -# Environment Configuration for WIRIS HTML Editors E2E Tests - -# Base URL for the application under test -BASE_URL=https://integrations.wiris.kitchen/ +# Environment configuration for HTML Editors E2E Tests +# File must be named .env in the root of the project # HTML Editor selection - pipe separated list of editors to test # Available options: generic|ckeditor4|ckeditor5|froala|tinymce5|tinymce6|tinymce7|tinymce8 -HTML_EDITOR=generic|ckeditor4|ckeditor5|froala|tinymce5|tinymce6|tinymce7|tinymce8 +HTML_EDITOR=ckeditor5 + +# Whether to use the staging environment or use localhost +USE_STAGING=false -# Branch for tests +# Branch to test. Only applies when USE_STAGING=true TEST_BRANCH=master + diff --git a/tests/e2e/helpers/test-setup.ts b/tests/e2e/helpers/test-setup.ts index 64e574447..0acaa1936 100644 --- a/tests/e2e/helpers/test-setup.ts +++ b/tests/e2e/helpers/test-setup.ts @@ -12,16 +12,19 @@ export async function setupEditor(page: Page, editorName: string): Promise e.getName() === editorName) - + if (!editor) { throw new Error(`Editor ${editorName} not found`) } - + const wirisEditor = new WirisEditor(page) - + return { editor, wirisEditor } } export function getEditorsFromEnv(): string[] { - return process.env.HTML_EDITOR?.split('|') || ['generic', 'ckeditor5', 'tinymce8'] -} \ No newline at end of file + if (!process.env.HTML_EDITOR) { + throw new Error('Environment variable HTML_EDITOR is not set') + } + return process.env.HTML_EDITOR.split('|') +} diff --git a/tests/e2e/page-objects/base_editor.ts b/tests/e2e/page-objects/base_editor.ts index ea073fc9a..33c49c99b 100644 --- a/tests/e2e/page-objects/base_editor.ts +++ b/tests/e2e/page-objects/base_editor.ts @@ -28,10 +28,35 @@ export default abstract class BaseEditor extends BasePage { /** * Constructs the URL for the specific editor and opens it in the browser. - * @returns {Promise} The URL of the opened editor. Unused in the current implementation - */ + * @returns {Promise} The URL of the opened editor. **/ public async open(): Promise { - await this.page.goto(`${process.env.TEST_BRANCH}/html/${this.getName()}/`) + const isStaging = process.env.USE_STAGING === 'true' + + let url: string + + if (isStaging) { + url = `${process.env.TEST_BRANCH}/html/${this.getName()}/` + } else { + // Use localhost with each editor's corresponding port + const editorPortMap = { + 'ckeditor4': 8001, + 'ckeditor5': 8002, + 'froala': 8003, + 'tinymce5': 8004, + 'tinymce6': 8005, + 'tinymce7': 8006, + 'generic': 8008, + } + + const port = editorPortMap[this.getName() as keyof typeof editorPortMap] + if (!port) { + throw new Error(`No port mapping found for editor: ${this.getName()}`) + } + + url = `http://localhost:${port}/` + } + + await this.page.goto(url) await this.page.waitForLoadState('domcontentloaded') return this.page.url() } @@ -89,7 +114,7 @@ export default abstract class BaseEditor extends BasePage { } else { frameOrPage = this.page } - + await this.page.waitForTimeout(500) const equationsInEditor = await frameOrPage.locator(`${this.editField} img[alt][data-mathml]`) @@ -126,7 +151,7 @@ export default abstract class BaseEditor extends BasePage { if (this.iframe) { return this.page.frameLocator(this.iframe).locator(`${this.editField} img[alt="${equation.altText}"]`) } - + return this.page.locator(`${this.editField} img[alt="${equation.altText}"]`) } @@ -157,7 +182,7 @@ export default abstract class BaseEditor extends BasePage { */ public async focus(): Promise { let editFieldLocator: Locator - + if (this.iframe) { editFieldLocator = this.page.frameLocator(this.iframe).locator(this.editField) } else { @@ -173,7 +198,7 @@ export default abstract class BaseEditor extends BasePage { */ public async appendText(textToInsert: string): Promise { await this.focus() - + await this.page.keyboard.press('Control+End') await this.pause(500) await this.page.keyboard.type(textToInsert) @@ -186,11 +211,11 @@ export default abstract class BaseEditor extends BasePage { */ public async openWirisEditorForLastInsertedFormula(toolbar: Toolbar, equation: Equation): Promise { const isFroala = this.getName() === 'froala' - + if (isFroala) { const equationElement = this.getEquationElement(equation) await equationElement.click() - + const mathTypeButton = this.getContextualToolbarMathTypeButton?.() if (mathTypeButton) { await this.page.locator(mathTypeButton).click() @@ -246,7 +271,7 @@ export default abstract class BaseEditor extends BasePage { if (!textContents) { return undefined } - + const expressions = textContents.match(/\$\$.*?\$\$/g)?.map((latexEquation) => latexEquation.trim().replaceAll('$$', '')) return expressions @@ -283,19 +308,19 @@ export default abstract class BaseEditor extends BasePage { public async dragDropLastFormula(equation: Equation): Promise { await this.focus() - + const equationElement = this.getEquationElement(equation) let editDivElement: Locator - + if (this.iframe) { editDivElement = this.page.frameLocator(this.iframe).locator(this.editField) } else { editDivElement = this.page.locator(this.editField) } - + const equationBox = await equationElement.boundingBox() const editDivBox = await editDivElement.boundingBox() - + if (equationBox && editDivBox) { await this.page.mouse.move(equationBox.x + equationBox.width / 2, equationBox.y + equationBox.height / 2) //await this.page.mouse.click(equationBox.x + equationBox.width / 2, equationBox.y + equationBox.height / 2) @@ -340,7 +365,7 @@ export default abstract class BaseEditor extends BasePage { } await this.pause(1000) - + const element = frameOrPage.locator(this.editField) const rawText = (await element.textContent()) ?? '' const normalized = rawText.replace(/[\s\uFEFF\xA0]+/g, '') @@ -353,17 +378,17 @@ export default abstract class BaseEditor extends BasePage { public async getImageSize(equation: Equation): Promise<{ width: number; height: number } | null> { await this.focus() - + const equationElement = this.getEquationElement(equation) return await equationElement.boundingBox() } public async resizeImageEquation(equation: Equation): Promise { await this.focus() - + const equationElement = this.getEquationElement(equation) await equationElement.click() - + const box = await equationElement.boundingBox() if (box) { await this.page.mouse.move(box.x + box.width / 2, box.y + box.height / 2) @@ -385,7 +410,7 @@ export default abstract class BaseEditor extends BasePage { public async isTextBoldAndItalic(text: string): Promise { await this.focus() - + let frameOrPage: Page | FrameLocator if (this.iframe) { frameOrPage = this.page.frameLocator(this.iframe) @@ -402,20 +427,20 @@ export default abstract class BaseEditor extends BasePage { const elements = frameOrPage.locator(`${this.editField} >> text="${text}"`) const count = await elements.count() - + for (let i = 0; i < count; i++) { const element = elements.nth(i) const fontWeight = await element.evaluate((el) => (globalThis as any).getComputedStyle(el).fontWeight) const fontStyle = await element.evaluate((el) => (globalThis as any).getComputedStyle(el).fontStyle) - + const isBold = parseInt(fontWeight) >= 700 const isItalic = fontStyle === 'italic' - + if (isBold && isItalic) { return true } } - + return false } @@ -428,18 +453,18 @@ export default abstract class BaseEditor extends BasePage { public async checkElementAlignment(): Promise { await this.focus() - + let editContent: Locator - + if (this.iframe) { editContent = this.page.frameLocator(this.iframe).locator(this.editField) } else { editContent = this.page.locator(this.editField) } - + // Take screenshot for visual comparison - await editContent.screenshot({ - path: `screenshots/${this.getName()}_alignment.png` + await editContent.screenshot({ + path: `screenshots/${this.getName()}_alignment.png` }) } @@ -450,4 +475,4 @@ export default abstract class BaseEditor extends BasePage { public getSourceCodeEditorButton?(): string public getSourceCodeEditField?(): string -} \ No newline at end of file +} diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index f8ec7077b..5f018dc37 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -3,6 +3,29 @@ import dotenv from 'dotenv' dotenv.config() +const enabledEditors = (process.env.HTML_EDITOR || '').split('|').filter(Boolean) + +const createWebServer = (editor: string, port: number) => ({ + command: `yarn nx start html-${editor}`, + port, + reuseExistingServer: true, +}) + +const editorPortMap = { + 'ckeditor4': 8001, + 'ckeditor5': 8002, + 'froala': 8003, + 'tinymce5': 8004, + 'tinymce6': 8005, + 'tinymce7': 8006, + 'tinymce8': 8007, + 'generic': 8008, +} + +const webServers = enabledEditors + .filter(editor => editorPortMap[editor]) + .map(editor => createWebServer(editor, editorPortMap[editor])) + export default defineConfig({ testDir: './tests', fullyParallel: true, @@ -10,20 +33,21 @@ export default defineConfig({ retries: process.env.CI ? 2 : 1, workers: process.env.CI ? '80%' : undefined, reporter: [ - ['html', { open: process.env.CI ? 'never' : 'on-failure' }], + ['html', { open: process.env.CI ? 'never' : 'on-failure', outputFolder: 'playwright-report/html' }], ['junit', { outputFile: 'test-results/results.xml' }], process.env.CI ? ['github'] : ['list'], ], use: { - baseURL: process.env.BASE_URL || 'https://integrations.wiris.kitchen/', + baseURL: process.env.USE_STAGING === 'TRUE' ? 'https://integrations.wiris.kitchen' : '', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, + webServer: webServers, projects: [ { name: 'chromium', - use: { + use: { ...devices['Desktop Chrome'], viewport: { width: 1920, height: 1080 }, permissions: ['clipboard-read', 'clipboard-write'] @@ -31,7 +55,7 @@ export default defineConfig({ }, { name: 'firefox', - use: { + use: { ...devices['Desktop Firefox'], viewport: { width: 1920, height: 1080 } } @@ -43,4 +67,4 @@ export default defineConfig({ timeout: 10_000 }, -}) \ No newline at end of file +}) From 15bc6712ba2e5b08b97e338ad4557bb29864f1ce Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Fri, 14 Nov 2025 07:42:02 +0100 Subject: [PATCH 03/29] test: remove cypress and add pw workflow --- .../cypress-Run-tests-with-npm-packages.yml | 68 ++++++ .gitignore | 5 - docs/development/cicd/README.md | 25 +-- docs/development/testing/README.md | 198 +++++++++++++++--- docs/tests/README.md | 10 - tests/e2e/.env.example | 2 +- tests/e2e/.gitignore | 3 +- tests/e2e/enums/equations.ts | 3 +- tests/e2e/page-objects/base_editor.ts | 5 +- tests/e2e/page-objects/page.ts | 12 +- tests/e2e/playwright.config.ts | 23 +- tests/e2e/tests/editor/editor.spec.ts | 16 +- 12 files changed, 272 insertions(+), 98 deletions(-) delete mode 100644 docs/tests/README.md diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index e69de29bb..06bef48f1 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -0,0 +1,68 @@ +name: E2E Tests - All Editors + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + # Matrix strategy to enable per html editor parallelization + e2e-tests: + name: E2E Tests - ${{ matrix.editor }} + runs-on: ubuntu-latest + strategy: + fail-fast: false # Continue other jobs even if one fails + matrix: + editor: + - generic + - ckeditor4 + - ckeditor5 + - froala + - tinymce5 + - tinymce6 + - tinymce7 + - tinymce8 + + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-node@v4 + with: + node-version: '23.x' + cache: 'yarn' + + - name: Install dependencies + run: | + yarn install --frozen-lockfile + yarn playwright install --with-deps + + - name: Build ${{ matrix.editor }} packages + run: | + yarn nx build ${{ matrix.editor }} + yarn nx build html-${{ matrix.editor }} + + - name: Run E2E tests for ${{ matrix.editor }} + id: e2e-${{ matrix.editor }} + run: HTML_EDITOR=${{ matrix.editor }} yarn test:e2e + continue-on-error: true + + - name: Publish test results for ${{ matrix.editor }} + uses: dorny/test-reporter@d61b558e8df85cb60d09ca3e5b09653b4477cea7 # v2.0.0 + if: always() + with: + name: E2E Tests - ${{ matrix.editor }} + path: tests/e2e/test-results/results.xml + reporter: java-junit + fail-on-error: ${{ steps.e2e.outcome == 'failure' }} + continue-on-error: true + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-${{ matrix.editor }} + path: tests/e2e/playwright-report/ + retention-days: 2 # TODO: adjust retention if needed. Be careful with storage limits. + diff --git a/.gitignore b/.gitignore index 6be366f05..0576f28bc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,6 @@ package-lock.json packages/*/yarn.lock -# Ignore Playwright test reports and results -playwright-report -test-results -screenshots - node_modules # Verdaccio diff --git a/docs/development/cicd/README.md b/docs/development/cicd/README.md index 9cdb40d03..b1ce13c25 100644 --- a/docs/development/cicd/README.md +++ b/docs/development/cicd/README.md @@ -15,28 +15,7 @@ This project uses [GitHub actions](https://github.com/features/actions) for the This job uses JSDoc library to generate a static site as an artifact called `mathtype-html-integration-devkit-docs.zip`, from the comments on the library code. -### Run Cypress tests with npm packages +### Run E2E tests +TODO -**[Deprecated]** -Builds the packages using the source code available at npmjs and runs all available Cypress tests. - -- **On schedule**: every Monday at 1AM. It sends the test data to [Cypress Dashboard][cypress-dashboard]. It can be run on any branch. - -- **On demand**: a manual trigger that allows the user to send data to [Cypress Dashboard][cypress-dashboard], optionally. - -## Actions secrets - -Secrets are GitHub environment variables that are encrypted. Anyone with collaborator access to this repository can use these secrets for Actions. - -| Name | Description | -| ------------------ | ----------------------------------------------------------------------------------------------------------- | -| GH_CICD_TOKEN | A GitHub token to allow detecting a build vs a re-run build. [More][cypress-action] | -| CYPRESS_PROJECT_ID | A 6 character string unique identifier for the project. | -| CYPRESS_RECORD_KEY | Cypress record key is an authentication key that allows to send record tests data to the Dashboard Service. | - -[Visit Secrets page at GitHub][secrets]. - -[secrets]: https://github.com/wiris/html-integrations/settings/secrets -[cypress-dashboard]: (https://cypress.io/dashboard/) -[cypress-action]: https://github.com/cypress-io/github-action diff --git a/docs/development/testing/README.md b/docs/development/testing/README.md index 5a129f87e..2aa4cd620 100644 --- a/docs/development/testing/README.md +++ b/docs/development/testing/README.md @@ -1,52 +1,194 @@ -# Testing +# E2E Testing Documentation -[MathType Web Integrations](../../../README.md) โ†’ [Documentation](../../README.md) โ†’ [Development guide](../README.md) โ†’ Testing +## Overview -This project uses [Cypress][Cypress] to run integration and validation tests in order to cover all published packages. +This project uses Playwright for end-to-end testing across multiple HTML editor integrations. The testing setup supports parallel execution across different editors with configurable environments. -[Cypress]: https://www.cypress.io/ +## Test Structure -## Table of contents +The E2E tests are located in `/tests/e2e/` with the following structure: -- [Run all tests at once](#run-all-tests-at-once) -- [Run all the tests for a specific demo](#run-all-the-tests-for-a-specific-demo) +``` +tests/e2e/ +โ”œโ”€โ”€ .env.example # Environment configuration template +โ”œโ”€โ”€ .env # (Optional) Local environment configuration; git-ignored +โ”œโ”€โ”€ playwright.config.ts # Playwright configuration +โ”œโ”€โ”€ enums/ # Shared enums for test logic +โ”œโ”€โ”€ helpers/ # Utility/helper functions +โ”œโ”€โ”€ interfaces/ # Shared TypeScript interfaces +โ”œโ”€โ”€ page-objects/ # Page object models +โ”‚ โ””โ”€โ”€ base_editor.ts # Base editor class +โ”‚ โ””โ”€โ”€ html/ # Page object for each editor test page +โ””โ”€โ”€ tests/ # Test specifications + โ”œโ”€โ”€ edit/ # Formula editing tests + โ”‚ โ”œโ”€โ”€ edit_corner_cases.spec.ts + โ”‚ โ”œโ”€โ”€ edit_hand.spec.ts + โ”‚ โ”œโ”€โ”€ edit_via_doble_click.spec.ts + โ”‚ โ””โ”€โ”€ edit_via_selection.spec.ts + โ”œโ”€โ”€ editor/ # Editor functionality tests + โ”‚ โ”œโ”€โ”€ copy_cut_drop.spec.ts + โ”‚ โ””โ”€โ”€ editor.spec.ts + โ”œโ”€โ”€ insert/ # Formula insertion tests + โ”‚ โ”œโ”€โ”€ insert_corner_cases.spec.ts + โ”‚ โ”œโ”€โ”€ insert_hand.spec.ts + โ”‚ โ””โ”€โ”€ insert.spec.ts + โ”œโ”€โ”€ latex/ # LaTeX functionality tests + โ”‚ โ””โ”€โ”€ latex.spec.ts + โ”œโ”€โ”€ modal/ # Modal dialog tests + โ”‚ โ”œโ”€โ”€ confirmation_dialog.spec.ts + โ”‚ โ””โ”€โ”€ toolbar.spec.ts + โ””โ”€โ”€ telemetry/ # Analytics tests + โ””โ”€โ”€ telemetry.spec.ts +``` -## Before you begin +## Supported Editors and Packages -Linux users will need to install `net-tools` to use Cypress. +The testing framework supports the following HTML editors with their corresponding localhost ports: -```bash -$ sudo apt install net-tools -``` +| Editor | Port | Status | +|------------|------|--------| +| ckeditor4 | 8001 | โœ… Active | +| ckeditor5 | 8002 | โœ… Active | +| froala | 8003 | โœ… Active | +| tinymce5 | 8004 | โœ… Active | +| tinymce6 | 8005 | โœ… Active | +| tinymce7 | 8006 | โœ… Active | +| tinymce8 | 8007 | โœ… Active | +| generic | 8008 | โœ… Active | +| viewer | 8009 | ๐Ÿ“‹ TODO | + + +## Environment Configuration + +### Local Setup +You can configure your environment using an optional `.env` file or by setting variables directly in the CLI command, as explained below. + +1. **Copy the environment template:** + ```bash + cp tests/e2e/.env.example tests/e2e/.env + ``` + +2. **Configure your environment:** + ```bash + # tests/e2e/.env -Also, you will need to allow non-local connections to control the X server on your computer. + # Select editors to test (pipe-separated) + HTML_EDITOR=generic|ckeditor4|ckeditor5 -Run this command: + # Environment selection + USE_STAGING=false + + # Branch for staging tests + TEST_BRANCH=master + ``` + +### Environment Variables + +| Variable | Description | Default | Example | Required | +|----------|-------------|---------|---------|----------| +| `HTML_EDITOR` | Pipe-separated list of editors to test | All editors | `generic\|ckeditor5` | Yes | +| `USE_STAGING` | Use staging environment vs localhost | `false` | `true\|false` | No | +| `TEST_BRANCH` | Git branch for staging tests | `master` | `feature-branch` | No | + + +## Running Tests + +### Prerequisites ```bash -$ xhost local:root +# Install dependencies +yarn install --frozen-lockfile + +# Install Playwright browsers +yarn playwright install --with-deps ``` -> This has to be executed once after each reboot +### Local Development +The `yarn test:e2e` script is defined in the main package.json and runs the E2E tests. -## Run all tests at once +Playwright is configured to pre-build both the package and test site (`demos` folder) for the configured +editors and deploy them in order to run the test. If the test page is already deployed, it will skip this step. -Before running the tests you will need to build all package and start all demos. +```bash +# Run tests for specific editors +HTML_EDITOR=ckeditor5 yarn test:e2e -All tests can be run with the commands: +# Run tests for multiple editors +HTML_EDITOR=generic|froala yarn test:e2e -```sh -$ nx run-many --target=start --all --parallel -$ nx run-many --target=test --all --parallel -``` +# Run all tests for all editors. If no HTML_EDITOR variable is set, all editors are tested +yarn test:e2e -## Run all the tests for a specific demo +# Run with staging environment +USE_STAGING=true yarn test:e2e -You can run all tests for a specific demo with the `nx test ` command. +# Run specific browser +yarn test:e2e --project=firefox -Before running the tests you will need to build the package and start a demo. For example to run all tests on the `ckeditor5` demo run: +# Run in headed mode +yarn test:e2e --headed +# Run specific test file +yarn test:e2e tests/insert/insert.spec.ts ``` -$ nx start html-ckeditor5 -$ nx test ckeditor5 +[See the official Playwright CLI documentation](https://playwright.dev/docs/test-cli) for more details on available commands and options. + + +**Example workflow:** +```bash +# Build and test CKEditor5 +yarn +HTML_EDITOR=ckeditor5 yarn test:e2e ``` + +## Playwright Configuration +See ([`playwright.config.ts`](../../../tests/e2e/playwright.config.ts)). + +### Key Features + +- **Conditional Server Startup**: Only starts servers for enabled editors via `HTML_EDITOR` +- **Server Reuse**: `reuseExistingServer: true` for faster local development +- **Environment Flexibility**: Supports both localhost and staging environments +- **Parallel Execution**: Tests run in parallel across different editors and browsers +- **Multiple Browsers**: Supports Chromium, Firefox, and WebKit. All three browsers are executed simultaneously by default +- **Comprehensive Reporting**: HTML and JUnit reports with artifacts. Screenshots and videos are attached to failed runs + + +## CI/CD Integration + +### GitHub Actions Workflow + +The E2E tests are automated via GitHub Actions ([`cypress-Run-tests-with-npm-packages.yml`](../../../.github/workflows/cypress-Run-tests-with-npm-packages.yml)): + +- **When tests run**: On pushes to `main`, pull requests, and manual workflow dispatch +- **Parallelization**: Each editor runs in a separate job using matrix strategy for maximum parallel execution +- **Reports**: + - Github reports appear in the GitHub Actions **Checks** tab + - Failed tests create GitHub Actions annotations with direct links for quick debugging. + - HTML reports and artifacts are available in the **Actions** tab under each workflow run + +## Test Coverage + +| Test File | Category | Description | +|-----------|----------|-------------| +| `edit/edit_corner_cases.spec.ts` | Edit | Edge cases and error conditions in formula editing | +| `edit/edit_hand.spec.ts` | Edit | Manual formula modifications and handwriting input | +| `edit/edit_via_doble_click.spec.ts` | Edit | Editing formulas by double-clicking on existing formulas | +| `edit/edit_via_selection.spec.ts` | Edit | Editing formulas via text selection and context menu | +| `editor/copy_cut_drop.spec.ts` | Editor | Clipboard operations (copy/cut/paste) and drag-drop functionality | +| `editor/editor.spec.ts` | Editor | General editor behavior and integration tests | +| `insert/insert_corner_cases.spec.ts` | Insert | Edge cases and error conditions in formula insertion | +| `insert/insert_hand.spec.ts` | Insert | Manual formula creation via handwriting input | +| `insert/insert.spec.ts` | Insert | Standard formula insertion workflows and toolbar interactions | +| `latex/latex.spec.ts` | LaTeX | LaTeX rendering, parsing, and conversion functionality | +| `modal/confirmation_dialog.spec.ts` | Modal | Confirmation dialog interactions and user workflows | +| `modal/toolbar.spec.ts` | Modal | Toolbar modal functionality and behavior | +| `telemetry/telemetry.spec.ts` | Telemetry | Usage metrics, event tracking, and analytics validation | + + +# TODOs +This project previously used cypress for E2E testing. There might still be some reference to Cypress in the code (e.g.: see test section in the demos `project.json` files). These must be deleteded. +- Remove cypress refereces in the `project.json` files +- Remove cypress dashboard secrets in the repository +- Remove old documentation cypress references. + diff --git a/docs/tests/README.md b/docs/tests/README.md deleted file mode 100644 index 14e606f7f..000000000 --- a/docs/tests/README.md +++ /dev/null @@ -1,10 +0,0 @@ -ckeditor4 8001 -ckeditor5 8002 -froala 8004 -generic 8007 -tinymce5 8006 -tinymce6 8008 -tinymce7 8009 -tinymce8 8010 -viewer 8009 - diff --git a/tests/e2e/.env.example b/tests/e2e/.env.example index 78b939b27..a767f7600 100644 --- a/tests/e2e/.env.example +++ b/tests/e2e/.env.example @@ -3,7 +3,7 @@ # HTML Editor selection - pipe separated list of editors to test # Available options: generic|ckeditor4|ckeditor5|froala|tinymce5|tinymce6|tinymce7|tinymce8 -HTML_EDITOR=ckeditor5 +HTML_EDITOR=generic|ckeditor4|ckeditor5|froala|tinymce5|tinymce6|tinymce7|tinymce8 # Whether to use the staging environment or use localhost USE_STAGING=false diff --git a/tests/e2e/.gitignore b/tests/e2e/.gitignore index 21d5079f3..2ee681b85 100644 --- a/tests/e2e/.gitignore +++ b/tests/e2e/.gitignore @@ -1,10 +1,9 @@ # Ignore node modules and environment files node_modules .env -.vscode .tmp # Ignore Playwright test reports and results playwright-report test-results -screenshots \ No newline at end of file +screenshots diff --git a/tests/e2e/enums/equations.ts b/tests/e2e/enums/equations.ts index 86ee5ed1c..71c68c796 100644 --- a/tests/e2e/enums/equations.ts +++ b/tests/e2e/enums/equations.ts @@ -8,7 +8,6 @@ const Equations: Record = { styledSingleNumber: { altText: 'bold italic 1', mathml: '1' - }, squareRootY: { altText: 'square root of y', @@ -34,4 +33,4 @@ const Equations: Record = { } } -export default Equations \ No newline at end of file +export default Equations diff --git a/tests/e2e/page-objects/base_editor.ts b/tests/e2e/page-objects/base_editor.ts index 33c49c99b..be674e79e 100644 --- a/tests/e2e/page-objects/base_editor.ts +++ b/tests/e2e/page-objects/base_editor.ts @@ -2,7 +2,7 @@ import { Page, Locator, expect, FrameLocator } from '@playwright/test' import Toolbar from '../enums/toolbar' import type Equation from '../interfaces/equation' import BasePage from './page' -import page from './page' +const path = require('path') /** * Abstract class used in each of the HTML editors which includes the methods for all the editors, and specifies the properties each editor needs. @@ -34,6 +34,7 @@ export default abstract class BaseEditor extends BasePage { let url: string + // Determine the URL based on the environment (staging or local) and the html editor name if (isStaging) { url = `${process.env.TEST_BRANCH}/html/${this.getName()}/` } else { @@ -464,7 +465,7 @@ export default abstract class BaseEditor extends BasePage { // Take screenshot for visual comparison await editContent.screenshot({ - path: `screenshots/${this.getName()}_alignment.png` + path: path.resolve(__dirname, '../screenshots', `${this.getName()}_alignment.png`) }) } diff --git a/tests/e2e/page-objects/page.ts b/tests/e2e/page-objects/page.ts index eedb3d26b..563b3bba2 100644 --- a/tests/e2e/page-objects/page.ts +++ b/tests/e2e/page-objects/page.ts @@ -7,20 +7,10 @@ export default class BasePage { this.page = page } - /** - * Take a screenshot - */ - async takeScreenshot(name?: string): Promise { - return await this.page.screenshot({ - fullPage: true, - path: name ? `screenshots/${name}.png` : undefined - }) - } - /** * Wait for a specific amount of time */ async pause(milliseconds: number): Promise { await this.page.waitForTimeout(milliseconds) } -} \ No newline at end of file +} diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 5f018dc37..6d3f3ebce 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -1,7 +1,8 @@ import { defineConfig, devices } from '@playwright/test' import dotenv from 'dotenv' +import path from 'path' -dotenv.config() +dotenv.config({path: path.resolve(__dirname, '.env')}) const enabledEditors = (process.env.HTML_EDITOR || '').split('|').filter(Boolean) @@ -9,8 +10,10 @@ const createWebServer = (editor: string, port: number) => ({ command: `yarn nx start html-${editor}`, port, reuseExistingServer: true, + setTimeout: 30_000 }) +// Map of editors to their ports, defined in their corresponding demo's webpack.config.js file const editorPortMap = { 'ckeditor4': 8001, 'ckeditor5': 8002, @@ -22,23 +25,24 @@ const editorPortMap = { 'generic': 8008, } +// Creates web servers only for enabled editors in the HTML_EDITOR env variable const webServers = enabledEditors - .filter(editor => editorPortMap[editor]) - .map(editor => createWebServer(editor, editorPortMap[editor])) + .filter(editor => editorPortMap[editor as keyof typeof editorPortMap]) + .map(editor => createWebServer(editor, editorPortMap[editor as keyof typeof editorPortMap])) export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 1, - workers: process.env.CI ? '80%' : undefined, + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? '90%' : undefined, reporter: [ ['html', { open: process.env.CI ? 'never' : 'on-failure', outputFolder: 'playwright-report/html' }], ['junit', { outputFile: 'test-results/results.xml' }], process.env.CI ? ['github'] : ['list'], ], use: { - baseURL: process.env.USE_STAGING === 'TRUE' ? 'https://integrations.wiris.kitchen' : '', + baseURL: process.env.USE_STAGING === 'true' ? 'https://integrations.wiris.kitchen' : '', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', @@ -59,6 +63,13 @@ export default defineConfig({ ...devices['Desktop Firefox'], viewport: { width: 1920, height: 1080 } } + }, + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + viewport: { width: 1920, height: 1080 } + } } ], outputDir: 'test-results', diff --git a/tests/e2e/tests/editor/editor.spec.ts b/tests/e2e/tests/editor/editor.spec.ts index 7c8946aed..4e970df55 100644 --- a/tests/e2e/tests/editor/editor.spec.ts +++ b/tests/e2e/tests/editor/editor.spec.ts @@ -16,7 +16,7 @@ for (const editorName of editors) { test.fail(isKnownIssue, 'Known issue: generic editors fails to undo equation'); const { editor, wirisEditor } = await setupEditor(page, editorName) - + await editor.open() await editor.clear() await editor.openWirisEditor(Toolbar.MATH) @@ -36,7 +36,7 @@ for (const editorName of editors) { test.describe('Resize', () => { test(`MTHTML-22 Formulas cannot be resized ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) - + await editor.open() await editor.clear() await editor.openWirisEditor(Toolbar.MATH) @@ -56,11 +56,11 @@ for (const editorName of editors) { test.describe('Source code', () => { test(`MTHTML-87 Edit source code of a math formula with ${editorName} editor`, async ({ page }) => { const { editor } = await setupEditor(page, editorName) - + // Skip test if editor doesn't support source code editing const hasSourceCodeButton = editor.getSourceCodeEditorButton !== undefined test.skip(!hasSourceCodeButton, `Source code editing not supported for ${editorName}`) - + await editor.open() await editor.clear() //await editor.openWirisEditor(Toolbar.MATH) TODO: review if we want to open the wiris editor first, fails tests in some editors @@ -76,7 +76,7 @@ for (const editorName of editors) { test(`MTHTML-79 Validate formula insertion after typing text that contains styles: ${editorName} editor`, async ({ page }) => { test.fixme((editorName === 'generic') && test.info().project.name === 'firefox', `Ctrl+B and Ctrl+I not working for generic editor on Firefox`) const { editor, wirisEditor } = await setupEditor(page, editorName) - + await editor.open() await editor.clear() const textBeginning = 'Text_Beginning' @@ -104,9 +104,9 @@ for (const editorName of editors) { }) test.describe('Text Alignment', () => { - test(`MTHTML-23 Validate formula alignment: ${editorName} editor`, async ({ page }) => { + test.only(`MTHTML-23 Validate formula alignment: ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) - + await editor.open() await editor.clear() await page.keyboard.type('___') @@ -122,4 +122,4 @@ for (const editorName of editors) { }) }) }) -} \ No newline at end of file +} From c49bfbc7e75c8376afb34d9d653e8ad2c4704157 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Fri, 14 Nov 2025 07:49:42 +0100 Subject: [PATCH 04/29] ci: fix e2e test execution --- .github/workflows/cypress-Run-tests-with-npm-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 06bef48f1..56e04bae3 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -44,7 +44,7 @@ jobs: yarn nx build html-${{ matrix.editor }} - name: Run E2E tests for ${{ matrix.editor }} - id: e2e-${{ matrix.editor }} + id: e2e run: HTML_EDITOR=${{ matrix.editor }} yarn test:e2e continue-on-error: true From 0bf9cd26cc46b7fcf8d2ebe246f9e57694671e23 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Fri, 14 Nov 2025 07:55:23 +0100 Subject: [PATCH 05/29] ci: fix e2e test execution --- .github/workflows/cypress-Run-tests-with-npm-packages.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 56e04bae3..ac7688991 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -31,11 +31,10 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '23.x' - cache: 'yarn' - name: Install dependencies run: | - yarn install --frozen-lockfile + yarn install yarn playwright install --with-deps - name: Build ${{ matrix.editor }} packages From 732612f16498717c18da6eeb5a70e65c818d4ed1 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Fri, 14 Nov 2025 07:59:48 +0100 Subject: [PATCH 06/29] test: remove committed .only --- tests/e2e/tests/editor/editor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/tests/editor/editor.spec.ts b/tests/e2e/tests/editor/editor.spec.ts index 4e970df55..d8020c934 100644 --- a/tests/e2e/tests/editor/editor.spec.ts +++ b/tests/e2e/tests/editor/editor.spec.ts @@ -104,7 +104,7 @@ for (const editorName of editors) { }) test.describe('Text Alignment', () => { - test.only(`MTHTML-23 Validate formula alignment: ${editorName} editor`, async ({ page }) => { + test(`MTHTML-23 Validate formula alignment: ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) await editor.open() From 0cb1eaba12394b948bf1a4fe2a0ba9bff9fc8391 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 14:36:24 +0100 Subject: [PATCH 07/29] test: fix staging execution --- .github/workflows/cypress-Run-tests-with-npm-packages.yml | 2 +- tests/e2e/playwright.config.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index ac7688991..e0e33f2cf 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -37,7 +37,7 @@ jobs: yarn install yarn playwright install --with-deps - - name: Build ${{ matrix.editor }} packages + - name: Build ${{ matrix.editor }} packages # TODO: check if build html-${{ matrix.editor }} is needed run: | yarn nx build ${{ matrix.editor }} yarn nx build html-${{ matrix.editor }} diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 6d3f3ebce..2dc6fc9a8 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -22,7 +22,7 @@ const editorPortMap = { 'tinymce6': 8005, 'tinymce7': 8006, 'tinymce8': 8007, - 'generic': 8008, + 'generic': 8007, } // Creates web servers only for enabled editors in the HTML_EDITOR env variable @@ -47,7 +47,7 @@ export default defineConfig({ screenshot: 'only-on-failure', video: 'retain-on-failure', }, - webServer: webServers, + webServer: process.env.USE_STAGING === 'true' ? undefined : webServers, projects: [ { name: 'chromium', From 9982cf305f544b85290ff9dafbe79740b011340f Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 14:39:46 +0100 Subject: [PATCH 08/29] test: add 1 worker in CI --- tests/e2e/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 2dc6fc9a8..803b7d381 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -35,7 +35,7 @@ export default defineConfig({ fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, - workers: process.env.CI ? '90%' : undefined, + workers: process.env.CI ? 1 : undefined, reporter: [ ['html', { open: process.env.CI ? 'never' : 'on-failure', outputFolder: 'playwright-report/html' }], ['junit', { outputFile: 'test-results/results.xml' }], From 55a379823de5e7eaa3e11883cb75f17ff4a46c0d Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 14:48:42 +0100 Subject: [PATCH 09/29] test: fix ports --- tests/e2e/page-objects/base_editor.ts | 11 ++++++----- tests/e2e/playwright.config.ts | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/e2e/page-objects/base_editor.ts b/tests/e2e/page-objects/base_editor.ts index be674e79e..b7f745eef 100644 --- a/tests/e2e/page-objects/base_editor.ts +++ b/tests/e2e/page-objects/base_editor.ts @@ -42,11 +42,12 @@ export default abstract class BaseEditor extends BasePage { const editorPortMap = { 'ckeditor4': 8001, 'ckeditor5': 8002, - 'froala': 8003, - 'tinymce5': 8004, - 'tinymce6': 8005, - 'tinymce7': 8006, - 'generic': 8008, + 'froala': 8004, + 'tinymce5': 8006, + 'tinymce6': 8008, + 'tinymce7': 8009, + 'tinymce8': 8007, + 'generic': 8007, } const port = editorPortMap[this.getName() as keyof typeof editorPortMap] diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 803b7d381..49cb78636 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -17,10 +17,10 @@ const createWebServer = (editor: string, port: number) => ({ const editorPortMap = { 'ckeditor4': 8001, 'ckeditor5': 8002, - 'froala': 8003, - 'tinymce5': 8004, - 'tinymce6': 8005, - 'tinymce7': 8006, + 'froala': 8004, + 'tinymce5': 8006, + 'tinymce6': 8008, + 'tinymce7': 8009, 'tinymce8': 8007, 'generic': 8007, } From 353b3b7351ff692e58f287d4cd30b9f20605ccbd Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 15:08:40 +0100 Subject: [PATCH 10/29] test: correct ports in readme --- docs/development/testing/README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/development/testing/README.md b/docs/development/testing/README.md index 2aa4cd620..0429fea5a 100644 --- a/docs/development/testing/README.md +++ b/docs/development/testing/README.md @@ -44,18 +44,17 @@ tests/e2e/ ## Supported Editors and Packages The testing framework supports the following HTML editors with their corresponding localhost ports: - | Editor | Port | Status | |------------|------|--------| | ckeditor4 | 8001 | โœ… Active | | ckeditor5 | 8002 | โœ… Active | -| froala | 8003 | โœ… Active | -| tinymce5 | 8004 | โœ… Active | -| tinymce6 | 8005 | โœ… Active | -| tinymce7 | 8006 | โœ… Active | -| tinymce8 | 8007 | โœ… Active | -| generic | 8008 | โœ… Active | -| viewer | 8009 | ๐Ÿ“‹ TODO | +| froala | 8004 | โœ… Active | +| tinymce5 | 8006 | โœ… Active | +| tinymce6 | 8008 | โœ… Active | +| tinymce7 | 8009 | โœ… Active | +| tinymce8 | ? | ๐Ÿ“‹ TODO | +| generic | 8007 | โœ… Active | +| viewer | ? | ๐Ÿ“‹ TODO | ## Environment Configuration From ccf5154a35bcf6ce6cb1bce4b7c98e4c833faaf4 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 16:36:03 +0100 Subject: [PATCH 11/29] test: delete always from test action --- .github/workflows/cypress-Run-tests-with-npm-packages.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index e0e33f2cf..7179b677a 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -49,7 +49,6 @@ jobs: - name: Publish test results for ${{ matrix.editor }} uses: dorny/test-reporter@d61b558e8df85cb60d09ca3e5b09653b4477cea7 # v2.0.0 - if: always() with: name: E2E Tests - ${{ matrix.editor }} path: tests/e2e/test-results/results.xml @@ -59,7 +58,6 @@ jobs: - name: Upload test results uses: actions/upload-artifact@v4 - if: always() with: name: test-results-${{ matrix.editor }} path: tests/e2e/playwright-report/ From 7e10f74e6bb00e8c35d4f1a260a64d8e2a751da4 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 17:25:44 +0100 Subject: [PATCH 12/29] test: deploy demos via serve-static to run e2e tests --- demos/html/ckeditor4/project.json | 6 ++++++ demos/html/ckeditor5/project.json | 6 ++++++ demos/html/froala/project.json | 6 ++++++ demos/html/generic/project.json | 6 ++++++ demos/html/tinymce5/project.json | 6 ++++++ demos/html/tinymce6/project.json | 6 ++++++ demos/html/tinymce7/project.json | 6 ++++++ demos/html/tinymce8/project.json | 6 ++++++ package.json | 9 ++++++--- tests/e2e/playwright.config.ts | 6 +++--- 10 files changed, 57 insertions(+), 6 deletions(-) diff --git a/demos/html/ckeditor4/project.json b/demos/html/ckeditor4/project.json index 7810740a5..5f7fe6693 100644 --- a/demos/html/ckeditor4/project.json +++ b/demos/html/ckeditor4/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/ckeditor4/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/ckeditor4 -l 8001" + } } } } diff --git a/demos/html/ckeditor5/project.json b/demos/html/ckeditor5/project.json index 689e722fa..8831a09cf 100644 --- a/demos/html/ckeditor5/project.json +++ b/demos/html/ckeditor5/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/ckeditor5/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/ckeditor5 -l 8002" + } } } } diff --git a/demos/html/froala/project.json b/demos/html/froala/project.json index 8aab2e250..da6e5f058 100644 --- a/demos/html/froala/project.json +++ b/demos/html/froala/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/froala/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/froala -l 8004" + } } } } diff --git a/demos/html/generic/project.json b/demos/html/generic/project.json index 0d4aabc02..e3a222525 100644 --- a/demos/html/generic/project.json +++ b/demos/html/generic/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/generic/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/generic -l 8007" + } } } } diff --git a/demos/html/tinymce5/project.json b/demos/html/tinymce5/project.json index e52f261d8..d1469013d 100644 --- a/demos/html/tinymce5/project.json +++ b/demos/html/tinymce5/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/tinymce5/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/tinymce5 -l 8006" + } } } } diff --git a/demos/html/tinymce6/project.json b/demos/html/tinymce6/project.json index b64f662b1..2d9cd3f69 100644 --- a/demos/html/tinymce6/project.json +++ b/demos/html/tinymce6/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/tinymce6/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/tinymce6 -l 8008" + } } } } diff --git a/demos/html/tinymce7/project.json b/demos/html/tinymce7/project.json index b6f405d2b..788c3c0c7 100644 --- a/demos/html/tinymce7/project.json +++ b/demos/html/tinymce7/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/tinymce7/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/tinymce7 -l 8009" + } } } } diff --git a/demos/html/tinymce8/project.json b/demos/html/tinymce8/project.json index 5667ad149..edfd99e19 100644 --- a/demos/html/tinymce8/project.json +++ b/demos/html/tinymce8/project.json @@ -51,6 +51,12 @@ "lintFilePatterns": ["demos/html/tinymce8/**/*.{ts,tsx,js,jsx}"] }, "outputs": ["{options.outputFile}"] + }, + "serve-static": { + "executor": "nx:run-commands", + "options": { + "command": "yarn serve -s demos/html/tinymce8 -l 8010" + } } } } diff --git a/package.json b/package.json index 8906d3690..87eb9274f 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,12 @@ "dotenv": "^16.3.1", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^10.0.1", "eslint-plugin-import": "^2.29.0", "eslint-plugin-jsdoc": "^48.2.3", + "eslint-plugin-prettier": "^5.2.3", "html-validate": "^8.18.1", "nx": "18.2.2", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-prettier": "^5.2.3", "prettier": "3.5.2", "typescript": "~5.4.4" }, @@ -43,5 +43,8 @@ "packages/**", "demos/**" ], - "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" + "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610", + "dependencies": { + "serve": "^14.2.5" + } } diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 49cb78636..218bead9e 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -7,7 +7,7 @@ dotenv.config({path: path.resolve(__dirname, '.env')}) const enabledEditors = (process.env.HTML_EDITOR || '').split('|').filter(Boolean) const createWebServer = (editor: string, port: number) => ({ - command: `yarn nx start html-${editor}`, + command: `yarn nx serve-static html-${editor}`, port, reuseExistingServer: true, setTimeout: 30_000 @@ -21,7 +21,7 @@ const editorPortMap = { 'tinymce5': 8006, 'tinymce6': 8008, 'tinymce7': 8009, - 'tinymce8': 8007, + 'tinymce8': 8010, 'generic': 8007, } @@ -35,7 +35,7 @@ export default defineConfig({ fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? '90%' : undefined, reporter: [ ['html', { open: process.env.CI ? 'never' : 'on-failure', outputFolder: 'playwright-report/html' }], ['junit', { outputFile: 'test-results/results.xml' }], From ac0b08a907c8d9ddb9a9579df7c1dd9d3627bde1 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 17:33:30 +0100 Subject: [PATCH 13/29] test: fix tinymce8 e2e test run on ci --- .../workflows/cypress-Run-tests-with-npm-packages.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 7179b677a..b4d6351b4 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -37,10 +37,15 @@ jobs: yarn install yarn playwright install --with-deps - - name: Build ${{ matrix.editor }} packages # TODO: check if build html-${{ matrix.editor }} is needed + - name: Build ${{ matrix.editor }} packages run: | - yarn nx build ${{ matrix.editor }} - yarn nx build html-${{ matrix.editor }} + {% if matrix.editor == 'tinymce8' %} + yarn nx build tinymce7 + yarn nx build html-tinymce8 + {% else %} + yarn nx build ${{ matrix.editor }} + yarn nx build html-${{ matrix.editor }} + {% endif %} - name: Run E2E tests for ${{ matrix.editor }} id: e2e From 41846274ec5b14c458341dcd79302a8a69a54752 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 17:37:34 +0100 Subject: [PATCH 14/29] test: fix action for tinymce8 --- .../cypress-Run-tests-with-npm-packages.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index b4d6351b4..816c61154 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -37,15 +37,17 @@ jobs: yarn install yarn playwright install --with-deps - - name: Build ${{ matrix.editor }} packages + - name: Build ${{ matrix.editor }} package + if: matrix.editor != 'tinymce8' run: | - {% if matrix.editor == 'tinymce8' %} - yarn nx build tinymce7 - yarn nx build html-tinymce8 - {% else %} - yarn nx build ${{ matrix.editor }} - yarn nx build html-${{ matrix.editor }} - {% endif %} + yarn nx build ${{ matrix.editor }} + yarn nx build html-${{ matrix.editor }} + + - name: Build tinymce8 packages + if: matrix.editor == 'tinymce8' + run: | + yarn nx build tinymce7 + yarn nx build html-tinymce8 - name: Run E2E tests for ${{ matrix.editor }} id: e2e From 65f097f72b57fd8f7be22b1ace368d35fe9e24c6 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 17:59:08 +0100 Subject: [PATCH 15/29] test: limit ci time execution --- .../cypress-Run-tests-with-npm-packages.yml | 1 + docs/development/testing/README.md | 21 ++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 816c61154..0fe616d45 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -12,6 +12,7 @@ jobs: e2e-tests: name: E2E Tests - ${{ matrix.editor }} runs-on: ubuntu-latest + timeout-minutes: 30 strategy: fail-fast: false # Continue other jobs even if one fails matrix: diff --git a/docs/development/testing/README.md b/docs/development/testing/README.md index 0429fea5a..7395e05e1 100644 --- a/docs/development/testing/README.md +++ b/docs/development/testing/README.md @@ -52,7 +52,7 @@ The testing framework supports the following HTML editors with their correspondi | tinymce5 | 8006 | โœ… Active | | tinymce6 | 8008 | โœ… Active | | tinymce7 | 8009 | โœ… Active | -| tinymce8 | ? | ๐Ÿ“‹ TODO | +| tinymce8 | 8010 | โœ… Active | | generic | 8007 | โœ… Active | | viewer | ? | ๐Ÿ“‹ TODO | @@ -96,7 +96,7 @@ You can configure your environment using an optional `.env` file or by setting v ```bash # Install dependencies -yarn install --frozen-lockfile +yarn install # Install Playwright browsers yarn playwright install --with-deps @@ -106,7 +106,7 @@ yarn playwright install --with-deps The `yarn test:e2e` script is defined in the main package.json and runs the E2E tests. Playwright is configured to pre-build both the package and test site (`demos` folder) for the configured -editors and deploy them in order to run the test. If the test page is already deployed, it will skip this step. +editors and deploy them in order to run the test. Don't pre-deploy the test page, Playwright will do it by itself. ```bash # Run tests for specific editors @@ -122,7 +122,7 @@ yarn test:e2e USE_STAGING=true yarn test:e2e # Run specific browser -yarn test:e2e --project=firefox +yarn test:e2e --project=webkit # Run in headed mode yarn test:e2e --headed @@ -143,28 +143,19 @@ HTML_EDITOR=ckeditor5 yarn test:e2e ## Playwright Configuration See ([`playwright.config.ts`](../../../tests/e2e/playwright.config.ts)). -### Key Features - -- **Conditional Server Startup**: Only starts servers for enabled editors via `HTML_EDITOR` -- **Server Reuse**: `reuseExistingServer: true` for faster local development -- **Environment Flexibility**: Supports both localhost and staging environments -- **Parallel Execution**: Tests run in parallel across different editors and browsers -- **Multiple Browsers**: Supports Chromium, Firefox, and WebKit. All three browsers are executed simultaneously by default -- **Comprehensive Reporting**: HTML and JUnit reports with artifacts. Screenshots and videos are attached to failed runs - ## CI/CD Integration ### GitHub Actions Workflow -The E2E tests are automated via GitHub Actions ([`cypress-Run-tests-with-npm-packages.yml`](../../../.github/workflows/cypress-Run-tests-with-npm-packages.yml)): +The E2E tests are automated via GitHub Actions ([`run-e2e-tests.yml`](../../../.github/workflows/cypress-Run-tests-with-npm-packages.yml)): - **When tests run**: On pushes to `main`, pull requests, and manual workflow dispatch - **Parallelization**: Each editor runs in a separate job using matrix strategy for maximum parallel execution - **Reports**: - Github reports appear in the GitHub Actions **Checks** tab - Failed tests create GitHub Actions annotations with direct links for quick debugging. - - HTML reports and artifacts are available in the **Actions** tab under each workflow run + - HTML reports and artifacts are attached to the workflow run. ## Test Coverage From 17c12dedc3103bac7fa0c46174cc8be2c244a727 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Thu, 29 Jan 2026 18:09:23 +0100 Subject: [PATCH 16/29] fix: renamed main by master in e2e workflow --- .github/workflows/cypress-Run-tests-with-npm-packages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 0fe616d45..3bb775a7a 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -3,7 +3,7 @@ name: E2E Tests - All Editors on: push: branches: - - main + - master pull_request: workflow_dispatch: @@ -31,7 +31,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: '23.x' + node-version: "23.x" - name: Install dependencies run: | From 67f91b32b42cdab8cb34dce71f883ef0779b6e78 Mon Sep 17 00:00:00 2001 From: sbaron-at-wiris Date: Thu, 12 Feb 2026 16:05:03 +0100 Subject: [PATCH 17/29] ci: add blob reporter to e2e workflow --- .../cypress-Run-tests-with-npm-packages.yml | 45 ++++++++++++++++--- tests/e2e/playwright.config.ts | 17 ++++--- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 3bb775a7a..5bb731600 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 strategy: - fail-fast: false # Continue other jobs even if one fails + fail-fast: false matrix: editor: - generic @@ -51,8 +51,9 @@ jobs: yarn nx build html-tinymce8 - name: Run E2E tests for ${{ matrix.editor }} + if: matrix.editor != 'ckeditor4' # CKEditor 4 tests are currently disabled due to licensing issues id: e2e - run: HTML_EDITOR=${{ matrix.editor }} yarn test:e2e + run: HTML_EDITOR=${{ matrix.editor }} PLAYWRIGHT_BLOB_OUTPUT_NAME=report-${{ matrix.editor }}.zip yarn test:e2e continue-on-error: true - name: Publish test results for ${{ matrix.editor }} @@ -64,10 +65,42 @@ jobs: fail-on-error: ${{ steps.e2e.outcome == 'failure' }} continue-on-error: true - - name: Upload test results + - name: Upload blob report for ${{ matrix.editor }} uses: actions/upload-artifact@v4 with: - name: test-results-${{ matrix.editor }} - path: tests/e2e/playwright-report/ - retention-days: 2 # TODO: adjust retention if needed. Be careful with storage limits. + name: blob-report-${{ matrix.editor }} + path: blob-report + retention-days: 1 + merge-reports: + if: always() + needs: [e2e-tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v4 + with: + node-version: "23.x" + + - name: Install dependencies + run: yarn install + + - name: Download all blobs + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: List downloaded blob reports + run: ls -R all-blob-reports + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload Final HTML Report + uses: actions/upload-artifact@v4 + with: + name: final-playwright-report + path: playwright-report/ + retention-days: 2 diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 218bead9e..aba5175ef 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -4,6 +4,8 @@ import path from 'path' dotenv.config({path: path.resolve(__dirname, '.env')}) +const isCI = !!process.env.CI; + const enabledEditors = (process.env.HTML_EDITOR || '').split('|').filter(Boolean) const createWebServer = (editor: string, port: number) => ({ @@ -33,19 +35,20 @@ const webServers = enabledEditors export default defineConfig({ testDir: './tests', fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 1 : 0, - workers: process.env.CI ? '90%' : undefined, + forbidOnly: isCI, + retries: isCI ? 1 : 0, + workers: isCI ? '90%' : undefined, reporter: [ - ['html', { open: process.env.CI ? 'never' : 'on-failure', outputFolder: 'playwright-report/html' }], + ['html', { open: isCI ? 'never' : 'on-failure', outputFolder: 'playwright-report/html' }], ['junit', { outputFile: 'test-results/results.xml' }], - process.env.CI ? ['github'] : ['list'], + isCI ? ['blob']: ['list'], + isCI ? ['github'] : ['list'], ], use: { baseURL: process.env.USE_STAGING === 'true' ? 'https://integrations.wiris.kitchen' : '', - trace: 'on-first-retry', + trace: isCI ? 'retain-on-failure' : 'on-first-retry', screenshot: 'only-on-failure', - video: 'retain-on-failure', + video: isCI ? 'off' : 'on-first-retry', }, webServer: process.env.USE_STAGING === 'true' ? undefined : webServers, projects: [ From b86b8504d409f38bebef24ecde44484a66eefadf Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 09:48:36 +0100 Subject: [PATCH 18/29] test: re-enable ckeditor4 testing on CI --- .github/workflows/cypress-Run-tests-with-npm-packages.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 5bb731600..6e4fda4e9 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -51,10 +51,12 @@ jobs: yarn nx build html-tinymce8 - name: Run E2E tests for ${{ matrix.editor }} - if: matrix.editor != 'ckeditor4' # CKEditor 4 tests are currently disabled due to licensing issues id: e2e run: HTML_EDITOR=${{ matrix.editor }} PLAYWRIGHT_BLOB_OUTPUT_NAME=report-${{ matrix.editor }}.zip yarn test:e2e continue-on-error: true + env: + CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }} + - name: Publish test results for ${{ matrix.editor }} uses: dorny/test-reporter@d61b558e8df85cb60d09ca3e5b09653b4477cea7 # v2.0.0 From b326550d873cead2fc9350459b88f4427935a667 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 11:20:42 +0100 Subject: [PATCH 19/29] test: update serve-static command --- demos/html/ckeditor4/project.json | 8 ++++++-- demos/html/ckeditor5/project.json | 8 ++++++-- demos/html/froala/project.json | 8 ++++++-- demos/html/generic/project.json | 8 ++++++-- demos/html/tinymce5/project.json | 8 ++++++-- demos/html/tinymce6/project.json | 8 ++++++-- demos/html/tinymce7/project.json | 8 ++++++-- demos/html/tinymce8/project.json | 8 ++++++-- 8 files changed, 48 insertions(+), 16 deletions(-) diff --git a/demos/html/ckeditor4/project.json b/demos/html/ckeditor4/project.json index 5f7fe6693..380718602 100644 --- a/demos/html/ckeditor4/project.json +++ b/demos/html/ckeditor4/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/ckeditor4 -l 8001" + "staticFilePath": "demos/html/ckeditor4/", + "port": 8001, + "spa": false, + "watch": false } } } diff --git a/demos/html/ckeditor5/project.json b/demos/html/ckeditor5/project.json index 8831a09cf..5b41a0d58 100644 --- a/demos/html/ckeditor5/project.json +++ b/demos/html/ckeditor5/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/ckeditor5 -l 8002" + "staticFilePath": "demos/html/ckeditor5/", + "port": 8002, + "spa": false, + "watch": false } } } diff --git a/demos/html/froala/project.json b/demos/html/froala/project.json index da6e5f058..4694ea35e 100644 --- a/demos/html/froala/project.json +++ b/demos/html/froala/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/froala -l 8004" + "staticFilePath": "demos/html/froala/", + "port": 8004, + "spa": false, + "watch": false } } } diff --git a/demos/html/generic/project.json b/demos/html/generic/project.json index e3a222525..0a24290a8 100644 --- a/demos/html/generic/project.json +++ b/demos/html/generic/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/generic -l 8007" + "staticFilePath": "demos/html/generic/", + "port": 8007, + "spa": false, + "watch": false } } } diff --git a/demos/html/tinymce5/project.json b/demos/html/tinymce5/project.json index d1469013d..c55e604eb 100644 --- a/demos/html/tinymce5/project.json +++ b/demos/html/tinymce5/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/tinymce5 -l 8006" + "staticFilePath": "demos/html/tinymce5/", + "port": 8006, + "spa": false, + "watch": false } } } diff --git a/demos/html/tinymce6/project.json b/demos/html/tinymce6/project.json index 2d9cd3f69..0bbc86e0e 100644 --- a/demos/html/tinymce6/project.json +++ b/demos/html/tinymce6/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/tinymce6 -l 8008" + "staticFilePath": "demos/html/tinymce6/", + "port": 8008, + "spa": false, + "watch": false } } } diff --git a/demos/html/tinymce7/project.json b/demos/html/tinymce7/project.json index 788c3c0c7..516a47b74 100644 --- a/demos/html/tinymce7/project.json +++ b/demos/html/tinymce7/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/tinymce7 -l 8009" + "staticFilePath": "demos/html/tinymce7/", + "port": 8009, + "spa": false, + "watch": false } } } diff --git a/demos/html/tinymce8/project.json b/demos/html/tinymce8/project.json index edfd99e19..a7da5967a 100644 --- a/demos/html/tinymce8/project.json +++ b/demos/html/tinymce8/project.json @@ -53,9 +53,13 @@ "outputs": ["{options.outputFile}"] }, "serve-static": { - "executor": "nx:run-commands", + "executor": "@nx/web:file-server", + "dependsOn": ["build"], "options": { - "command": "yarn serve -s demos/html/tinymce8 -l 8010" + "staticFilePath": "demos/html/tinymce8/", + "port": 8010, + "spa": false, + "watch": false } } } From 9a4dafb9c5863e1f3ca25d77fceb216121f890e8 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 11:21:30 +0100 Subject: [PATCH 20/29] chore: upload ck5 telemeter wasm file --- .../dist/browser/telemeter_wasm_bg.wasm | Bin 0 -> 590432 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/ckeditor5/dist/browser/telemeter_wasm_bg.wasm diff --git a/packages/ckeditor5/dist/browser/telemeter_wasm_bg.wasm b/packages/ckeditor5/dist/browser/telemeter_wasm_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4898d1ed40cad94055bc50b94fd6f2b292c85754 GIT binary patch literal 590432 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 KS<%|54*efIpBRt; literal 0 HcmV?d00001 From 0b6e318aa2576ad24ae891c28ee1e949524bc94b Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 12:41:09 +0100 Subject: [PATCH 21/29] ci: update ci tests ckeditor4 build --- .github/workflows/cypress-Run-tests-with-npm-packages.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 6e4fda4e9..51159442f 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -40,6 +40,9 @@ jobs: - name: Build ${{ matrix.editor }} package if: matrix.editor != 'tinymce8' + env: + CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }} + FROALA_API_KEY: ${{ secrets.FROALA_API_KEY }} run: | yarn nx build ${{ matrix.editor }} yarn nx build html-${{ matrix.editor }} @@ -54,9 +57,6 @@ jobs: id: e2e run: HTML_EDITOR=${{ matrix.editor }} PLAYWRIGHT_BLOB_OUTPUT_NAME=report-${{ matrix.editor }}.zip yarn test:e2e continue-on-error: true - env: - CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }} - - name: Publish test results for ${{ matrix.editor }} uses: dorny/test-reporter@d61b558e8df85cb60d09ca3e5b09653b4477cea7 # v2.0.0 From 41695aad4ef05cfe0a6e7cfa71ba3cb92545c2af Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 12:55:46 +0100 Subject: [PATCH 22/29] test: fix tinymce8 port --- tests/e2e/page-objects/base_editor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/page-objects/base_editor.ts b/tests/e2e/page-objects/base_editor.ts index b7f745eef..40c852663 100644 --- a/tests/e2e/page-objects/base_editor.ts +++ b/tests/e2e/page-objects/base_editor.ts @@ -46,7 +46,7 @@ export default abstract class BaseEditor extends BasePage { 'tinymce5': 8006, 'tinymce6': 8008, 'tinymce7': 8009, - 'tinymce8': 8007, + 'tinymce8': 8010, 'generic': 8007, } From 9f33763626f0f9d1d5a0ae6acafd9cb3e9a00299 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 13:04:14 +0100 Subject: [PATCH 23/29] test: update e2e tests ci env variables management --- .../cypress-Run-tests-with-npm-packages.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml index 51159442f..fd739cff2 100644 --- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml +++ b/.github/workflows/cypress-Run-tests-with-npm-packages.yml @@ -40,12 +40,12 @@ jobs: - name: Build ${{ matrix.editor }} package if: matrix.editor != 'tinymce8' - env: - CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }} - FROALA_API_KEY: ${{ secrets.FROALA_API_KEY }} run: | yarn nx build ${{ matrix.editor }} yarn nx build html-${{ matrix.editor }} + env: + CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }} + FROALA_API_KEY: ${{ secrets.FROALA_API_KEY }} - name: Build tinymce8 packages if: matrix.editor == 'tinymce8' @@ -57,6 +57,9 @@ jobs: id: e2e run: HTML_EDITOR=${{ matrix.editor }} PLAYWRIGHT_BLOB_OUTPUT_NAME=report-${{ matrix.editor }}.zip yarn test:e2e continue-on-error: true + env: + CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }} + FROALA_API_KEY: ${{ secrets.FROALA_API_KEY }} - name: Publish test results for ${{ matrix.editor }} uses: dorny/test-reporter@d61b558e8df85cb60d09ca3e5b09653b4477cea7 # v2.0.0 @@ -94,9 +97,6 @@ jobs: pattern: blob-report-* merge-multiple: true - - name: List downloaded blob reports - run: ls -R all-blob-reports - - name: Merge into HTML Report run: npx playwright merge-reports --reporter html ./all-blob-reports From b50921c9fafcc122688c2fde14c691e46fcec451 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 13:59:27 +0100 Subject: [PATCH 24/29] test: update e2e tests docs --- docs/development/cicd/README.md | 22 +++++++++++++++++++++- docs/development/testing/README.md | 10 ++++++++-- tests/e2e/.env.example | 4 +++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/development/cicd/README.md b/docs/development/cicd/README.md index b1ce13c25..5b6296287 100644 --- a/docs/development/cicd/README.md +++ b/docs/development/cicd/README.md @@ -16,6 +16,26 @@ This project uses [GitHub actions](https://github.com/features/actions) for the This job uses JSDoc library to generate a static site as an artifact called `mathtype-html-integration-devkit-docs.zip`, from the comments on the library code. ### Run E2E tests -TODO +This workflow runs end-to-end tests across all supported HTML editors using Playwright. The tests are executed in a matrix strategy to enable parallel execution for each editor. See [docs/testing/README.md](../testing/README.md) for further details. + +**Supported editors:** +- Generic HTML +- CKEditor 4 & 5 +- Froala +- TinyMCE 5, 6, 7 & 8 + +**Key features:** +- **Parallel execution**: Each editor runs in its own job for faster feedback +- **Multi-browser testing**: Tests run on Chromium, Firefox, and WebKit +- **Timeout protection**: Each job has a 30-minute timeout to prevent hanging +- **Test reporting**: Results are published using JUnit format with detailed reports +- **Artifact collection**: Test reports are collected as downloadable artifacts + +**Workflow triggers:** +- Push to master branch +- Pull requests +- Manual dispatch + +The workflow builds the necessary packages, starts static file servers for each editor demo, and runs the Playwright test suite against them. diff --git a/docs/development/testing/README.md b/docs/development/testing/README.md index 7395e05e1..ef2af6066 100644 --- a/docs/development/testing/README.md +++ b/docs/development/testing/README.md @@ -79,6 +79,10 @@ You can configure your environment using an optional `.env` file or by setting v # Branch for staging tests TEST_BRANCH=master + + # API Keys for commercial editors. Required to deploy the test page. + CKEDITOR4_API_KEY= + FROALA_API_KEY= ``` ### Environment Variables @@ -88,6 +92,8 @@ You can configure your environment using an optional `.env` file or by setting v | `HTML_EDITOR` | Pipe-separated list of editors to test | All editors | `generic\|ckeditor5` | Yes | | `USE_STAGING` | Use staging environment vs localhost | `false` | `true\|false` | No | | `TEST_BRANCH` | Git branch for staging tests | `master` | `feature-branch` | No | +| `CKEDITOR4_API_KEY` | API key for CKEditor 4 commercial features | None | `your-ckeditor4-key` | For all CKEditor 4 tests | +| `FROALA_API_KEY` | API key for Froala Editor commercial features | None | `your-froala-key` | For Froala licensed features only | ## Running Tests @@ -105,7 +111,7 @@ yarn playwright install --with-deps ### Local Development The `yarn test:e2e` script is defined in the main package.json and runs the E2E tests. -Playwright is configured to pre-build both the package and test site (`demos` folder) for the configured +Playwright is configured to pre-build and deploy both the package and test site (`demos` folder) for the configured editors and deploy them in order to run the test. Don't pre-deploy the test page, Playwright will do it by itself. ```bash @@ -155,7 +161,7 @@ The E2E tests are automated via GitHub Actions ([`run-e2e-tests.yml`](../../../. - **Reports**: - Github reports appear in the GitHub Actions **Checks** tab - Failed tests create GitHub Actions annotations with direct links for quick debugging. - - HTML reports and artifacts are attached to the workflow run. + - A single HTML report is generated and attached to the workflow run. Contains results for all Editors. ## Test Coverage diff --git a/tests/e2e/.env.example b/tests/e2e/.env.example index a767f7600..6fbf41fd5 100644 --- a/tests/e2e/.env.example +++ b/tests/e2e/.env.example @@ -11,4 +11,6 @@ USE_STAGING=false # Branch to test. Only applies when USE_STAGING=true TEST_BRANCH=master - +# API Keys for commercial editors. Required to deploy the test page. +CKEDITOR4_API_KEY= +FROALA_API_KEY= From b88018b6bb3b80d4ce55b40862119b04f1032048 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 14:06:10 +0100 Subject: [PATCH 25/29] chore: revert ckeditor5 undesired changed files --- packages/ckeditor5/dist/browser/index.css.map | 2 +- packages/ckeditor5/dist/browser/index.js | 28 +++++++++---------- packages/ckeditor5/dist/browser/index.js.map | 2 +- packages/ckeditor5/dist/browser/index.umd.js | 28 +++++++++---------- .../ckeditor5/dist/browser/index.umd.js.map | 2 +- packages/ckeditor5/dist/index.js | 6 ++-- packages/ckeditor5/dist/index.js.map | 2 +- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/ckeditor5/dist/browser/index.css.map b/packages/ckeditor5/dist/browser/index.css.map index 054e78e97..0057d38dd 100644 --- a/packages/ckeditor5/dist/browser/index.css.map +++ b/packages/ckeditor5/dist/browser/index.css.map @@ -1 +1 @@ -{"version":3,"sources":["../../../devkit/styles/styles.css"],"names":[],"mappings":"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","file":"index.css","sourcesContent":[".wrs_modal_overlay {\r\n position: fixed;\r\n font-family: arial, sans-serif;\r\n top: 0;\r\n right: 0;\r\n left: 0;\r\n bottom: 0;\r\n background: rgba(0, 0, 0, 0.8);\r\n z-index: 999998;\r\n opacity: 0.65;\r\n pointer-events: auto;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_ios {\r\n visibility: hidden;\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_android {\r\n visibility: hidden;\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_ios.moodle {\r\n position: fixed;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack {\r\n background: rgba(0, 0, 0, 0);\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_maximized {\r\n background: rgba(0, 0, 0, 0.8);\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_minimized {\r\n background: rgba(0, 0, 0, 0);\r\n display: none;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_closed {\r\n background: rgba(0, 0, 0, 0);\r\n display: none;\r\n}\r\n\r\n.wrs_modal_title {\r\n color: #fff;\r\n padding: 5px 0 5px 10px;\r\n -moz-user-select: none;\r\n -webkit-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n text-align: left;\r\n}\r\n\r\n.wrs_modal_close_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n padding: 5px 10px 5px 0;\r\n margin: 10px 7px 0 0;\r\n background-repeat: no-repeat;\r\n}\r\n\r\n.wrs_modal_minimize_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n padding: 5px 10px 5px 0;\r\n top: inherit;\r\n margin: 10px 7px 0 0;\r\n}\r\n\r\n.wrs_modal_stack_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n margin: 10px 7px 0 0;\r\n padding: 5px 10px 5px 0;\r\n top: inherit;\r\n}\r\n\r\n.wrs_modal_stack_button.wrs_stack {\r\n visibility: hidden;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.wrs_modal_stack_button.wrs_minimized {\r\n visibility: hidden;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.wrs_modal_maximize_button {\r\n float: right;\r\n cursor: pointer;\r\n color: #fff;\r\n margin: 10px 7px 0 0;\r\n padding: 5px 10px 5px 0;\r\n top: inherit;\r\n}\r\n\r\n.wrs_modal_maximize_button.wrs_maximized {\r\n visibility: hidden;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.wrs_modal_title_bar {\r\n display: block;\r\n background-color: #778e9a;\r\n}\r\n\r\n.wrs_modal_dialogContainer {\r\n border: none;\r\n background: #fafafa;\r\n z-index: 999999;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop {\r\n font-size: 14px;\r\n display: flex;\r\n flex-flow: column;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_maximized {\r\n position: fixed;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized {\r\n position: fixed;\r\n top: inherit;\r\n margin: 0;\r\n margin-right: 10px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_closed {\r\n visibility: hidden;\r\n display: none;\r\n opacity: 0;\r\n}\r\n\r\n/* Class that exists but hasn't got css properties defined\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized.wrs_drag {} */\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_stack {\r\n position: fixed;\r\n bottom: 0;\r\n right: 0;\r\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_drag {\r\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_drag {\r\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_ios,\r\n.wrs_modal_dialogContainer.wrs_modal_android {\r\n margin: 0px;\r\n position: fixed;\r\n height: auto;\r\n overflow: hidden;\r\n top: calc(env(safe-area-inset-top));\r\n right: calc(env(safe-area-inset-right));\r\n left: calc(env(safe-area-inset-left));\r\n bottom: 0px;\r\n transform: none;\r\n box-sizing: border-box;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n/* Class that exists but hasn't got css properties defined\r\n.wrs_content_container.wrs_maximized {} */\r\n\r\n.wrs_content_container.wrs_minimized {\r\n display: none;\r\n}\r\n\r\n/* .wrs_editor {\r\n flex-grow: 1;\r\n} */\r\n\r\n.wrs_content_container.wrs_modal_desktop > div:first-child {\r\n flex-grow: 1;\r\n}\r\n\r\n.wrs_modal_wrapper.wrs_modal_android,\r\n.wrs_modal_wrapper.wrs_modal_ios {\r\n margin: 0.5rem !important;\r\n flex-grow: 0.98;\r\n display: flex;\r\n}\r\n\r\n.wrs_content_container.wrs_modal_ios,\r\n.wrs_content_container.wrs_modal_android {\r\n flex-grow: 0.98;\r\n display: flex;\r\n}\r\n\r\n.wrs_content_container.wrs_modal_desktop {\r\n width: 100%;\r\n flex-grow: 1;\r\n display: flex;\r\n flex-direction: column;\r\n height: auto !important;\r\n}\r\n\r\n.wrs_modal_dialogContainer.wrs_modal_badStock {\r\n width: 100%;\r\n height: 280px;\r\n margin: 0 auto;\r\n border-width: 0;\r\n}\r\n\r\n.wrs_modal_wrapper.wrs_modal_badStock {\r\n width: 100%;\r\n height: 280px;\r\n margin: 0 auto;\r\n border-width: 0;\r\n}\r\n\r\n.wrs_noselect {\r\n -moz-user-select: none;\r\n -khtml-user-select: none;\r\n -webkit-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n.wrs_bottom_right_resizer {\r\n width: 10px;\r\n height: 10px;\r\n color: #778e9a;\r\n position: absolute;\r\n right: 4px;\r\n bottom: 8px;\r\n cursor: se-resize;\r\n -moz-user-select: none;\r\n -webkit-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n.wrs_bottom_left_resizer {\r\n width: 15px;\r\n height: 15px;\r\n color: #778e9a;\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n cursor: se-resize;\r\n}\r\n\r\n.wrs_modal_controls {\r\n height: 42px;\r\n line-height: normal;\r\n}\r\n\r\n.wrs_modal_links {\r\n margin: 10px auto;\r\n margin-bottom: 0;\r\n font-family: arial, sans-serif;\r\n padding: 6px;\r\n display: inline;\r\n float: right;\r\n text-align: right;\r\n}\r\n\r\n.wrs_modal_links > a {\r\n text-decoration: none;\r\n color: #778e9a;\r\n font-size: 16px;\r\n}\r\n\r\n.wrs_modal_button_cancel,\r\n.wrs_modal_button_cancel:hover,\r\n.wrs_modal_button_cancel:visited,\r\n.wrs_modal_button_cancel:active,\r\n.wrs_modal_button_cancel:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-left: 5px;\r\n margin-bottom: 0;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n background-color: #ddd;\r\n height: 32px;\r\n}\r\n\r\n.wrs_modal_button_accept,\r\n.wrs_modal_button_accept:hover,\r\n.wrs_modal_button_accept:visited,\r\n.wrs_modal_button_accept:active,\r\n.wrs_modal_button_accept:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-right: 5px;\r\n margin-bottom: 0;\r\n color: #fff;\r\n background: #778e9a;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n height: 32px;\r\n}\r\n\r\n.wrs_editor_vertical_bar {\r\n height: 20px;\r\n float: right;\r\n background: none;\r\n width: 20px;\r\n cursor: pointer;\r\n}\r\n\r\n.wrs_modal_buttons_container {\r\n display: inline;\r\n float: left;\r\n}\r\n\r\n.wrs_modal_buttons_container.wrs_modalDesktop {\r\n padding-left: 0;\r\n}\r\n\r\n.wrs_modal_buttons_container > button {\r\n line-height: normal;\r\n background-image: none;\r\n}\r\n\r\n.wrs_modal_wrapper {\r\n margin: 6px;\r\n display: flex;\r\n flex-direction: column;\r\n flex-grow: 1;\r\n}\r\n\r\n.wrs_modal_wrapper.wrs_modal_desktop.wrs_minimized {\r\n display: none;\r\n}\r\n\r\n.wrs_popupmessage_overlay_envolture {\r\n display: none;\r\n width: 100%;\r\n}\r\n\r\n.wrs_popupmessage_overlay {\r\n position: absolute;\r\n width: 100%;\r\n height: 100%;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n z-index: 4;\r\n cursor: pointer;\r\n}\r\n\r\n.wrs_popupmessage_panel {\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n position: absolute;\r\n background: white;\r\n max-width: 500px;\r\n width: 75%;\r\n border-radius: 2px;\r\n padding: 20px;\r\n font-family: sans-serif;\r\n font-size: 15px;\r\n text-align: left;\r\n color: #2e2e2e;\r\n z-index: 5;\r\n max-height: 75%;\r\n overflow: auto;\r\n}\r\n\r\n.wrs_popupmessage_button_area {\r\n margin: 10px 0 0 0;\r\n}\r\n\r\n.wrs_panelContainer * {\r\n border: 0;\r\n}\r\n\r\n.wrs_button_cancel,\r\n.wrs_button_cancel:hover,\r\n.wrs_button_cancel:visited,\r\n.wrs_button_cancel:active,\r\n.wrs_button_cancel:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-left: 5px;\r\n margin-bottom: 0;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n background-color: #ddd;\r\n background-image: none;\r\n height: 32px;\r\n}\r\n\r\n.wrs_button_accept,\r\n.wrs_button_accept:hover,\r\n.wrs_button_accept:visited,\r\n.wrs_button_accept:active,\r\n.wrs_button_accept:focus {\r\n min-width: 80px;\r\n font-size: 14px;\r\n border-radius: 3px;\r\n border: 1px solid #778e9a;\r\n padding: 6px 8px;\r\n margin: 10px auto;\r\n margin-right: 5px;\r\n margin-bottom: 0;\r\n color: #fff;\r\n background: #778e9a;\r\n cursor: pointer;\r\n font-family: arial, sans-serif;\r\n height: 32px;\r\n}\r\n\r\n.wrs_editor button {\r\n box-shadow: none;\r\n}\r\n\r\n.wrs_editor .wrs_header button {\r\n border-bottom: none;\r\n border-bottom-left-radius: 0;\r\n border-bottom-right-radius: 0;\r\n}\r\n\r\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack.wrs_overlay_active {\r\n display: block;\r\n}\r\n\r\n/* Fix selection in drupal style */\r\n.wrs_toolbar tr:focus {\r\n background: none;\r\n}\r\n\r\n.wrs_toolbar tr:hover {\r\n background: none;\r\n}\r\n\r\n/* End of fix drupal */\r\n.wrs_modal_rtl .wrs_modal_button_cancel {\r\n margin-right: 5px;\r\n margin-left: 0;\r\n}\r\n\r\n.wrs_modal_rtl .wrs_modal_button_accept {\r\n margin-right: 0;\r\n margin-left: 5px;\r\n}\r\n\r\n.wrs_modal_rtl .wrs_button_cancel {\r\n margin-right: 5px;\r\n margin-left: 0;\r\n}\r\n\r\n.wrs_modal_rtl .wrs_button_accept {\r\n margin-right: 0;\r\n margin-left: 5px;\r\n}\r\n\r\n/* The Offline Modal (background) */\r\n.wrs_modal_offline {\r\n display: none;\r\n /* Hidden by default */\r\n position: fixed;\r\n /* Stay in place */\r\n z-index: 2;\r\n /* Sit on top */\r\n padding-top: 150px;\r\n /* Location of the box */\r\n left: 0;\r\n top: 0;\r\n width: 100%;\r\n /* Full width */\r\n height: 100%;\r\n /* Full height */\r\n overflow: auto;\r\n /* Enable scroll if needed */\r\n background-color: rgb(0, 0, 0);\r\n /* Fallback color */\r\n background-color: rgba(0, 0, 0, 0.4);\r\n /* Black w/ opacity */\r\n margin: auto;\r\n}\r\n\r\n/* Modal Content */\r\n.wrs_modal_content_offline {\r\n margin: auto;\r\n padding: 16px;\r\n background: #fff7ed;\r\n border-radius: 6px;\r\n width: 517px;\r\n height: 100px;\r\n position: relative;\r\n}\r\n\r\n/* The Close Button */\r\n.wrs_modal_offline_close {\r\n color: #c2410c;\r\n font-size: 24px;\r\n font-weight: bold;\r\n left: 95.7%;\r\n right: 2.08%;\r\n top: 7.6%;\r\n bottom: 75%;\r\n font-family: \"Inter\";\r\n font-style: normal;\r\n font-weight: 400;\r\n position: absolute;\r\n}\r\n\r\n/* The Warn Icon */\r\n.wrs_modal_offline_warn {\r\n position: absolute;\r\n left: 2.08%;\r\n right: 94%;\r\n top: 11.6%;\r\n bottom: 75%;\r\n font-size: 24px;\r\n color: #fb923c;\r\n background-repeat: no-repeat;\r\n}\r\n\r\n.wrs_modal_offline_text_container {\r\n position: absolute;\r\n left: 6.8%;\r\n right: 6.08%;\r\n top: 10.4%;\r\n bottom: 2%;\r\n}\r\n\r\n.wrs_modal_offline_text {\r\n font-family: \"Inter\";\r\n font-style: normal;\r\n font-weight: 400;\r\n font-size: 14px;\r\n line-height: 20px;\r\n color: #c2410c;\r\n}\r\n\r\n.wrs_modal_offline_text_warn {\r\n height: 25px;\r\n font-family: \"Inter\";\r\n font-style: normal;\r\n font-size: 14px;\r\n line-height: 20px;\r\n font-weight: bold;\r\n color: #c2410c;\r\n}\r\n\r\n.wrs_modal_offline_close:hover,\r\n.wrs_modal_offline_close:focus {\r\n color: #000;\r\n text-decoration: none;\r\n cursor: pointer;\r\n}\r\n"]} \ No newline at end of file +{"version":3,"sources":["../../../devkit/styles/styles.css"],"names":[],"mappings":"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","file":"index.css","sourcesContent":[".wrs_modal_overlay {\n position: fixed;\n font-family: arial, sans-serif;\n top: 0;\n right: 0;\n left: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.8);\n z-index: 999998;\n opacity: 0.65;\n pointer-events: auto;\n}\n\n.wrs_modal_overlay.wrs_modal_ios {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_android {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_ios.moodle {\n position: fixed;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_maximized {\n background: rgba(0, 0, 0, 0.8);\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_minimized {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_closed {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_title {\n color: #fff;\n padding: 5px 0 5px 10px;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n text-align: left;\n}\n\n.wrs_modal_close_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n margin: 10px 7px 0 0;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_minimize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n top: inherit;\n margin: 10px 7px 0 0;\n}\n\n.wrs_modal_stack_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_stack_button.wrs_stack {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_stack_button.wrs_minimized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_maximize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_maximize_button.wrs_maximized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_title_bar {\n display: block;\n background-color: #778e9a;\n}\n\n.wrs_modal_dialogContainer {\n border: none;\n background: #fafafa;\n z-index: 999999;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop {\n font-size: 14px;\n display: flex;\n flex-flow: column;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_maximized {\n position: fixed;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized {\n position: fixed;\n top: inherit;\n margin: 0;\n margin-right: 10px;\n}\n\n.wrs_modal_dialogContainer.wrs_closed {\n visibility: hidden;\n display: none;\n opacity: 0;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized.wrs_drag {} */\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_stack {\n position: fixed;\n bottom: 0;\n right: 0;\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_ios,\n.wrs_modal_dialogContainer.wrs_modal_android {\n margin: 0px;\n position: fixed;\n height: auto;\n overflow: hidden;\n top: calc(env(safe-area-inset-top));\n right: calc(env(safe-area-inset-right));\n left: calc(env(safe-area-inset-left));\n bottom: 0px;\n transform: none;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_content_container.wrs_maximized {} */\n\n.wrs_content_container.wrs_minimized {\n display: none;\n}\n\n/* .wrs_editor {\n flex-grow: 1;\n} */\n\n.wrs_content_container.wrs_modal_desktop > div:first-child {\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_android,\n.wrs_modal_wrapper.wrs_modal_ios {\n margin: 0.5rem !important;\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_ios,\n.wrs_content_container.wrs_modal_android {\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_desktop {\n width: 100%;\n flex-grow: 1;\n display: flex;\n flex-direction: column;\n height: auto !important;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_modal_wrapper.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_noselect {\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_right_resizer {\n width: 10px;\n height: 10px;\n color: #778e9a;\n position: absolute;\n right: 4px;\n bottom: 8px;\n cursor: se-resize;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_left_resizer {\n width: 15px;\n height: 15px;\n color: #778e9a;\n position: absolute;\n left: 0;\n top: 0;\n cursor: se-resize;\n}\n\n.wrs_modal_controls {\n height: 42px;\n line-height: normal;\n}\n\n.wrs_modal_links {\n margin: 10px auto;\n margin-bottom: 0;\n font-family: arial, sans-serif;\n padding: 6px;\n display: inline;\n float: right;\n text-align: right;\n}\n\n.wrs_modal_links > a {\n text-decoration: none;\n color: #778e9a;\n font-size: 16px;\n}\n\n.wrs_modal_button_cancel,\n.wrs_modal_button_cancel:hover,\n.wrs_modal_button_cancel:visited,\n.wrs_modal_button_cancel:active,\n.wrs_modal_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n height: 32px;\n}\n\n.wrs_modal_button_accept,\n.wrs_modal_button_accept:hover,\n.wrs_modal_button_accept:visited,\n.wrs_modal_button_accept:active,\n.wrs_modal_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor_vertical_bar {\n height: 20px;\n float: right;\n background: none;\n width: 20px;\n cursor: pointer;\n}\n\n.wrs_modal_buttons_container {\n display: inline;\n float: left;\n}\n\n.wrs_modal_buttons_container.wrs_modalDesktop {\n padding-left: 0;\n}\n\n.wrs_modal_buttons_container > button {\n line-height: normal;\n background-image: none;\n}\n\n.wrs_modal_wrapper {\n margin: 6px;\n display: flex;\n flex-direction: column;\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_desktop.wrs_minimized {\n display: none;\n}\n\n.wrs_popupmessage_overlay_envolture {\n display: none;\n width: 100%;\n}\n\n.wrs_popupmessage_overlay {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 4;\n cursor: pointer;\n}\n\n.wrs_popupmessage_panel {\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n position: absolute;\n background: white;\n max-width: 500px;\n width: 75%;\n border-radius: 2px;\n padding: 20px;\n font-family: sans-serif;\n font-size: 15px;\n text-align: left;\n color: #2e2e2e;\n z-index: 5;\n max-height: 75%;\n overflow: auto;\n}\n\n.wrs_popupmessage_button_area {\n margin: 10px 0 0 0;\n}\n\n.wrs_panelContainer * {\n border: 0;\n}\n\n.wrs_button_cancel,\n.wrs_button_cancel:hover,\n.wrs_button_cancel:visited,\n.wrs_button_cancel:active,\n.wrs_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n background-image: none;\n height: 32px;\n}\n\n.wrs_button_accept,\n.wrs_button_accept:hover,\n.wrs_button_accept:visited,\n.wrs_button_accept:active,\n.wrs_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor button {\n box-shadow: none;\n}\n\n.wrs_editor .wrs_header button {\n border-bottom: none;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack.wrs_overlay_active {\n display: block;\n}\n\n/* Fix selection in drupal style */\n.wrs_toolbar tr:focus {\n background: none;\n}\n\n.wrs_toolbar tr:hover {\n background: none;\n}\n\n/* End of fix drupal */\n.wrs_modal_rtl .wrs_modal_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_modal_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n.wrs_modal_rtl .wrs_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n/* The Offline Modal (background) */\n.wrs_modal_offline {\n display: none;\n /* Hidden by default */\n position: fixed;\n /* Stay in place */\n z-index: 2;\n /* Sit on top */\n padding-top: 150px;\n /* Location of the box */\n left: 0;\n top: 0;\n width: 100%;\n /* Full width */\n height: 100%;\n /* Full height */\n overflow: auto;\n /* Enable scroll if needed */\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n margin: auto;\n}\n\n/* Modal Content */\n.wrs_modal_content_offline {\n margin: auto;\n padding: 16px;\n background: #fff7ed;\n border-radius: 6px;\n width: 517px;\n height: 100px;\n position: relative;\n}\n\n/* The Close Button */\n.wrs_modal_offline_close {\n color: #c2410c;\n font-size: 24px;\n font-weight: bold;\n left: 95.7%;\n right: 2.08%;\n top: 7.6%;\n bottom: 75%;\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n position: absolute;\n}\n\n/* The Warn Icon */\n.wrs_modal_offline_warn {\n position: absolute;\n left: 2.08%;\n right: 94%;\n top: 11.6%;\n bottom: 75%;\n font-size: 24px;\n color: #fb923c;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_offline_text_container {\n position: absolute;\n left: 6.8%;\n right: 6.08%;\n top: 10.4%;\n bottom: 2%;\n}\n\n.wrs_modal_offline_text {\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n color: #c2410c;\n}\n\n.wrs_modal_offline_text_warn {\n height: 25px;\n font-family: \"Inter\";\n font-style: normal;\n font-size: 14px;\n line-height: 20px;\n font-weight: bold;\n color: #c2410c;\n}\n\n.wrs_modal_offline_close:hover,\n.wrs_modal_offline_close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n"]} \ No newline at end of file diff --git a/packages/ckeditor5/dist/browser/index.js b/packages/ckeditor5/dist/browser/index.js index a26662594..942be4b8e 100644 --- a/packages/ckeditor5/dist/browser/index.js +++ b/packages/ckeditor5/dist/browser/index.js @@ -7170,25 +7170,25 @@ class Event { }; }; -var closeIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var closeIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var closeHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var closeHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var fullsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var fullsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var fullsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var fullsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var minIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var minIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var minHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var minHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var minsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var minsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var minsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var minsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var maxIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var maxIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; -var maxHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; +var maxHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; // eslint-disable-next-line max-classes-per-file const { unprotect, protect } = focusProtection(); @@ -9274,7 +9274,7 @@ if (!String.prototype.startsWith) { * @private */ Core._initialized = false; -var warnIcon = "\r\n\r\n\r\n"; +var warnIcon = "\n\n\n"; // eslint-disable-next-line no-unused-vars, import/named /** @@ -11465,11 +11465,11 @@ delete Array.prototype.__class__; // @codingStandardsIgnoreEnd } } -var mathIcon = "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n"; +var mathIcon = "\n\n\n\n\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n"; -var chemIcon = "\r\n\r\n\r\n\r\n\r\n\r\n"; +var chemIcon = "\n\n\n\n\n\n"; -var version = "8.14.0"; +var version = "8.13.4"; var packageInfo = { version: version}; diff --git a/packages/ckeditor5/dist/browser/index.js.map b/packages/ckeditor5/dist/browser/index.js.map index 01ac27819..36c36b0d2 100644 --- a/packages/ckeditor5/dist/browser/index.js.map +++ b/packages/ckeditor5/dist/browser/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../../../../node_modules/dompurify/dist/purify.es.mjs","../../../devkit/src/constants.js","../../../devkit/src/mathml.js","../../../devkit/src/configuration.js","../../../devkit/src/textcache.js","../../../devkit/src/listeners.js","../../../devkit/src/serviceprovider.js","../../../devkit/src/latex.js","../../../devkit/src/stringmanager.js","../../../devkit/src/util.js","../../../devkit/src/image.js","../../../devkit/src/accessibility.js","../../../devkit/src/parser.js","../../../devkit/src/editorlistener.js","../../../devkit/telemeter-wasm/telemeter_wasm.js","../../../devkit/src/telemeter.js","../../../devkit/src/contentmanager.js","../../../devkit/src/customeditors.js","../../../devkit/src/jsvariables.js","../../../devkit/src/event.js","../../../devkit/src/popupmessage.js","../../../devkit/src/focusprotection.js","../../../devkit/src/modal.js","../../../devkit/src/polyfills.js","../../../devkit/src/core.src.js","../../../devkit/src/integrationmodel.js","../../../devkit/src/md5.js","../../src/integration.js","../../src/commands.js","../../src/plugin.js"],"sourcesContent":["/*! @license DOMPurify 3.3.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.0/LICENSE */\n\nconst {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor\n} = Object;\nlet {\n freeze,\n seal,\n create\n} = Object; // eslint-disable-line import/no-mutable-exports\nlet {\n apply,\n construct\n} = typeof Reflect !== 'undefined' && Reflect;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!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}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\r\n * This class represents all the constants needed in a MathType integration among different classes.\r\n * If a constant should be used across different classes should be defined using attribute\r\n * accessors.\r\n */\r\nexport default class Constants {\r\n /**\r\n * Safe XML entities.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharactersEntities() {\r\n return {\r\n tagOpener: \"«\",\r\n tagCloser: \"»\",\r\n doubleQuote: \"¨\",\r\n realDoubleQuote: \""\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard invalid safe characters.\r\n * @type {Object}\r\n */\r\n static get safeBadBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยป<ยซ/moยป\",\r\n gtElement: \"ยซmoยป>ยซ/moยป\",\r\n ampElement: \"ยซmoยป&ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard valid safe characters.\r\n * @type{Object}\r\n */\r\n static get safeGoodBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\r\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\r\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Standard XML special characters.\r\n * @type {Object}\r\n */\r\n static get xmlCharacters() {\r\n return {\r\n id: \"xmlCharacters\",\r\n tagOpener: \"<\", // Hex: \\x3C.\r\n tagCloser: \">\", // Hex: \\x3E.\r\n doubleQuote: '\"', // Hex: \\x22.\r\n ampersand: \"&\", // Hex: \\x26.\r\n quote: \"'\", // Hex: \\x27.\r\n };\r\n }\r\n\r\n /**\r\n * Safe XML special characters. This characters are used instead the standard\r\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\r\n * special character have a UTF-8 representation.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharacters() {\r\n return {\r\n id: \"safeXmlCharacters\",\r\n tagOpener: \"ยซ\", // Hex: \\xAB.\r\n tagCloser: \"ยป\", // Hex: \\xBB.\r\n doubleQuote: \"ยจ\", // Hex: \\xA8.\r\n ampersand: \"ยง\", // Hex: \\xA7.\r\n quote: \"`\", // Hex: \\x60.\r\n realDoubleQuote: \"ยจ\",\r\n };\r\n }\r\n}\r\n","import Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a class to manage MathML objects.\r\n */\r\nexport default class MathML {\r\n /**\r\n * Checks if the mathml at position i is inside an HTML attribute or not.\r\n * @param {string} content - a string containing MathML code.\r\n * @param {number} i - search index.\r\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\r\n */\r\n static isMathmlInAttribute(content, i) {\r\n // Regex =\r\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\r\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\r\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\r\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\r\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\r\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\r\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\r\n const expression = new RegExp(regex);\r\n\r\n const actualContent = content.substring(0, i);\r\n const reversed = actualContent.split(\"\").reverse().join(\"\");\r\n const exists = expression.test(reversed);\r\n\r\n return exists;\r\n }\r\n\r\n /**\r\n * Decodes an encoded MathML with standard XML tags.\r\n * We use these entities because IE doesn't support html entities\r\n * on its attributes sometimes. Yes, sometimes.\r\n * @param {string} input - string to be decoded.\r\n * @return {string} decoded string.\r\n */\r\n static safeXmlDecode(input) {\r\n let { tagOpener } = Constants.safeXmlCharactersEntities;\r\n let { tagCloser } = Constants.safeXmlCharactersEntities;\r\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\r\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\r\n // Decoding entities.\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n // Added to fix problem due to import from 1.9.x.\r\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\r\n\r\n // Blackboard.\r\n const { ltElement } = Constants.safeBadBlackboardCharacters;\r\n const { gtElement } = Constants.safeBadBlackboardCharacters;\r\n const { ampElement } = Constants.safeBadBlackboardCharacters;\r\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\r\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\r\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\r\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\r\n }\r\n\r\n ({ tagOpener } = Constants.safeXmlCharacters);\r\n ({ tagCloser } = Constants.safeXmlCharacters);\r\n ({ doubleQuote } = Constants.safeXmlCharacters);\r\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\r\n const { ampersand } = Constants.safeXmlCharacters;\r\n const { quote } = Constants.safeXmlCharacters;\r\n\r\n // Decoding characters.\r\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.xmlCharacters.quote);\r\n\r\n // We are replacing $ by & when its part of an entity for retro-compatibility.\r\n // Now, the standard is replace ยง by &.\r\n let returnValue = \"\";\r\n let currentEntity = null;\r\n\r\n for (let i = 0; i < input.length; i += 1) {\r\n const character = input.charAt(i);\r\n if (currentEntity == null) {\r\n if (character === \"$\") {\r\n currentEntity = \"\";\r\n } else {\r\n returnValue += character;\r\n }\r\n } else if (character === \";\") {\r\n returnValue += `&${currentEntity}`;\r\n currentEntity = null;\r\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\r\n // Character is part of an entity.\r\n currentEntity += character;\r\n } else {\r\n returnValue += `$${currentEntity}`; // Is not an entity.\r\n currentEntity = null;\r\n i -= 1; // Parse again the current character.\r\n }\r\n }\r\n\r\n return returnValue;\r\n }\r\n\r\n /**\r\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\r\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\r\n * @param {string} input - input string to be encoded\r\n * @returns {string} encoded string.\r\n */\r\n static safeXmlEncode(input) {\r\n const { tagOpener } = Constants.xmlCharacters;\r\n const { tagCloser } = Constants.xmlCharacters;\r\n const { doubleQuote } = Constants.xmlCharacters;\r\n const { ampersand } = Constants.xmlCharacters;\r\n const { quote } = Constants.xmlCharacters;\r\n\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\r\n\r\n return input;\r\n }\r\n\r\n /**\r\n * Converts special symbols (> 128) to entities and replaces all textual\r\n * entities by its number entities.\r\n * @param {string} mathml - MathML string containing - or not - special symbols\r\n * @returns {string} MathML with all textual entities replaced.\r\n */\r\n static mathMLEntities(mathml) {\r\n let toReturn = \"\";\r\n\r\n for (let i = 0; i < mathml.length; i += 1) {\r\n const character = mathml.charAt(i);\r\n\r\n // Parsing > 128 characters.\r\n if (mathml.codePointAt(i) > 128) {\r\n toReturn += `&#${mathml.codePointAt(i)};`;\r\n // For UTF-32 characters we need to move the index one position.\r\n if (mathml.codePointAt(i) > 0xffff) {\r\n i += 1;\r\n }\r\n } else if (character === \"&\") {\r\n const end = mathml.indexOf(\";\", i + 1);\r\n if (end >= 0) {\r\n const container = document.createElement(\"span\");\r\n container.innerHTML = mathml.substring(i, end + 1);\r\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\r\n i = end;\r\n } else {\r\n toReturn += character;\r\n }\r\n } else {\r\n toReturn += character;\r\n }\r\n }\r\n\r\n return toReturn;\r\n }\r\n\r\n /**\r\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\r\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} MathML string with his class containing the editor toolbar string.\r\n */\r\n static addCustomEditorClassAttribute(mathml, customEditor) {\r\n let toReturn = \"\";\r\n\r\n const start = mathml.indexOf(\"\");\r\n if (mathml.indexOf(\"class\") === -1) {\r\n // Adding custom editor type.\r\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\r\n toReturn += mathml.substr(end + 1, mathml.length);\r\n return toReturn;\r\n }\r\n }\r\n return mathml;\r\n }\r\n\r\n /**\r\n * Remove a custom editor name from the MathML class attribute.\r\n * @param {string} mathml - a MathML string.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} The input MathML without customEditor name in his class.\r\n */\r\n static removeCustomEditorClassAttribute(mathml, customEditor) {\r\n // Discard MathML without the specified class.\r\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\r\n return mathml;\r\n }\r\n\r\n // Trivial case: class attribute value equal to editor name. Then\r\n // class attribute is removed.\r\n // First try to remove it with a space before if there is one\r\n // Otherwise without the space\r\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n\r\n // Non Trivial case: class attribute contains editor name.\r\n return mathml.replace(`wrs_${customEditor}`, \"\");\r\n }\r\n\r\n /**\r\n * Adds annotation tag in MathML element.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} content - value to put inside annotation tag.\r\n * @param {String} annotationEncoding - annotation encoding.\r\n * @returns {String} - 'mathml' with an annotation that contains\r\n * 'content' and encoding 'encoding'.\r\n */\r\n static addAnnotation(mathml, content, annotationEncoding) {\r\n // If contains annotation, also contains semantics tag.\r\n const containsAnnotation = mathml.indexOf(\"\");\r\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\r\n } else if (MathML.isEmpty(mathml)) {\r\n const endIndexInline = mathml.indexOf(\"/>\");\r\n const endIndexNonInline = mathml.indexOf(\">\");\r\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\r\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\r\n } else {\r\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\r\n const endMathmlContent = mathml.lastIndexOf(\"\");\r\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\r\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\r\n }\r\n\r\n return mathmlWithAnnotation;\r\n }\r\n\r\n /**\r\n * Removes specific annotation tag in MathML element.\r\n * In case of remove the unique annotation, also is removed semantics tag.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} annotationEncoding - annotation encoding to remove.\r\n * @returns {String} - 'mathml' without the annotation encoding specified.\r\n */\r\n static removeAnnotation(mathml, annotationEncoding) {\r\n let mathmlWithoutAnnotation = mathml;\r\n const openAnnotationTag = ``;\r\n const closeAnnotationTag = \"\";\r\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\r\n if (startAnnotationIndex !== -1) {\r\n let differentAnnotationFound = false;\r\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\r\n\r\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Removes semantics tag to element that contains mathml.\r\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\r\n * @param {string} element - Inner HTML text string.\r\n * @returns {string} - 'mathml' without semantics tag.\r\n */\r\n static removeSafeXMLSemantics(element) {\r\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\r\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\r\n\r\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Transforms all xml mathml occurrences that contain semantics to the same\r\n * xml mathml occurrences without semantics.\r\n * @param {string} text - string that can contain xml mathml occurrences.\r\n * @param {Constants} [characters] - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * xmlCharacters by default.\r\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\r\n */\r\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\r\n const mathTagStart = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const mathTagEndline = `/${characters.tagCloser}`;\r\n const { tagCloser } = characters;\r\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\r\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\r\n\r\n let output = \"\";\r\n let start = text.indexOf(mathTagStart);\r\n let end = 0;\r\n while (start !== -1) {\r\n output += text.substring(end, start);\r\n\r\n // MathML can be written as '' or ''.\r\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\r\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\r\n const firstTagCloser = text.indexOf(tagCloser, start);\r\n if (mathTagEndIndex !== -1) {\r\n end = mathTagEndIndex;\r\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\r\n end = mathTagEndlineIndex;\r\n }\r\n\r\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\r\n if (semanticsIndex !== -1) {\r\n const mmlTagStart = text.substring(start, semanticsIndex);\r\n const annotationIndex = text.indexOf(annotationTagStart, start);\r\n if (annotationIndex !== -1) {\r\n const startIndex = semanticsIndex + semanticsTagStart.length;\r\n const mmlContent = text.substring(startIndex, annotationIndex);\r\n output += mmlTagStart + mmlContent + mathTagEnd;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n end += mathTagEnd.length;\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n }\r\n\r\n output += text.substring(end, text.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Returns true if a MathML contains a certain class.\r\n * @param {string} mathML - input MathML.\r\n * @param {string} className - className.\r\n * @returns {boolean} true if the input MathML contains the input class.\r\n * false otherwise.\r\n * @static\r\n */\r\n static containClass(mathML, className) {\r\n const classIndex = mathML.indexOf(\"class\");\r\n if (classIndex === -1) {\r\n return false;\r\n }\r\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\r\n const classTag = mathML.substring(classIndex, classTagEndIndex);\r\n if (classTag.indexOf(className) !== -1) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns true if mathml is empty. Otherwise, false.\r\n * @param {string} mathml - valid MathML with standard XML tags.\r\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\r\n */\r\n static isEmpty(mathml) {\r\n // MathML can have the shape or ''.\r\n const closeTag = \">\";\r\n const closeTagInline = \"/>\";\r\n const firstCloseTagIndex = mathml.indexOf(closeTag);\r\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\r\n let empty = false;\r\n // MathML is always empty in the second shape.\r\n if (firstCloseTagInlineIndex !== -1) {\r\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\r\n empty = true;\r\n }\r\n }\r\n\r\n // MathML is always empty in the first shape when there aren't elements\r\n // between math tags.\r\n if (!empty) {\r\n const mathTagEndRegex = new RegExp(\"\");\r\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\r\n if (mathTagEndArray) {\r\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\r\n }\r\n }\r\n\r\n return empty;\r\n }\r\n\r\n /**\r\n * Encodes html entities inside properties.\r\n * @param {String} mathml - valid MathML with standard XML tags.\r\n * @returns {String} - 'mathml' with property entities encoded.\r\n */\r\n static encodeProperties(mathml) {\r\n // Search all the properties.\r\n const regex = /\\w+=\".*?\"/g;\r\n // Encode html entities.\r\n const replacer = (match) => {\r\n // It has the shape:\r\n // .\r\n const quoteIndex = match.indexOf('\"');\r\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\r\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\r\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\r\n return matchEncoded;\r\n };\r\n\r\n const mathmlEncoded = mathml.replace(regex, replacer);\r\n return mathmlEncoded;\r\n }\r\n}\r\n","/**\r\n * This class represents the configuration class.\r\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\r\n */\r\nexport default class Configuration {\r\n /**\r\n * Adds a properties object to {@link Configuration.properties}.\r\n * @param {Object} properties - properties to append to current properties.\r\n */\r\n static addConfiguration(properties) {\r\n Object.assign(Configuration.properties, properties);\r\n }\r\n\r\n /**\r\n * Static property.\r\n * The configuration properties object.\r\n * @private\r\n * @type {Object}\r\n */\r\n static get properties() {\r\n return Configuration._properties;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set configuration properties.\r\n * @param {Object} value - The property value.\r\n * @ignore\r\n */\r\n static set properties(value) {\r\n Configuration._properties = value;\r\n }\r\n\r\n /**\r\n * Returns the value of a property key.\r\n * @param {String} key - Property key\r\n * @returns {String} Property value\r\n */\r\n static get(key) {\r\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\r\n // Backwards compatibility.\r\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\r\n return Configuration.properties[`_wrs_conf_${key}`];\r\n }\r\n return false;\r\n }\r\n return Configuration.properties[key];\r\n }\r\n\r\n /**\r\n * Adds a new property to Configuration class.\r\n * @param {String} key - Property key.\r\n * @param {Object} value - Property value.\r\n */\r\n static set(key, value) {\r\n Configuration.properties[key] = value;\r\n }\r\n\r\n /**\r\n * Updates a property object value with new values.\r\n * @param {String} key - The property key to be updated.\r\n * @param {Object} propertyValue - Object containing the new values.\r\n */\r\n static update(key, propertyValue) {\r\n if (!Configuration.get(key)) {\r\n Configuration.set(key, propertyValue);\r\n } else {\r\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\r\n Configuration.set(key, updateProperty);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Static properties object. Stores all configuration properties.\r\n * Needed to attribute accessors.\r\n * @private\r\n * @type {Object}\r\n */\r\nConfiguration._properties = {};\r\n","export default class TextCache {\r\n /**\r\n * @classdesc\r\n * This class represent a client-side text cache class. Contains pairs of\r\n * strings (key/value) which can be retrieved in any moment. Usually used\r\n * to store AJAX responses for text services like mathml2latex\r\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Cache array property storing the cache entries.\r\n * @type {Array.}\r\n */\r\n this.cache = [];\r\n }\r\n\r\n /**\r\n * This method populates a key/value pair into the {@link this.cache} property.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @param {String} value - Cache value, usually the service response.\r\n */\r\n populate(key, value) {\r\n this.cache[key] = value;\r\n }\r\n\r\n /**\r\n * Returns the cache value associated to certain cache key.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @return {String} value - Cache value, if exists. False otherwise.\r\n */\r\n get(key) {\r\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\r\n return this.cache[key];\r\n }\r\n return false;\r\n }\r\n}\r\n","/**\r\n * This object represents a custom listener.\r\n * @typedef {Object} Listener\r\n * @property {String} Listener.eventName - The listener name.\r\n * @property {Function} Listener.callback - The listener callback function.\r\n */\r\n\r\nexport default class Listeners {\r\n /**\r\n * @classdesc\r\n * This class represents a custom listeners manager.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Array containing all custom listeners.\r\n * @type {Object[]}\r\n */\r\n this.listeners = [];\r\n }\r\n\r\n /**\r\n * Add a listener to Listener class.\r\n * @param {Object} listener - A listener object.\r\n */\r\n add(listener) {\r\n this.listeners.push(listener);\r\n }\r\n\r\n /**\r\n * Fires MathType event listeners\r\n * @param {String} eventName - event name\r\n * @param {Event} event - event object.\r\n * @return {boolean} false if event has been prevented. true otherwise.\r\n */\r\n fire(eventName, event) {\r\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\r\n if (this.listeners[i].eventName === eventName) {\r\n // Calling listener.\r\n this.listeners[i].callback(event);\r\n }\r\n }\r\n return event.defaultPrevented;\r\n }\r\n\r\n /**\r\n * Creates a new listener object.\r\n * @param {string} eventName - Event name.\r\n * @param {Object} callback - Callback function.\r\n * @returns {object} the listener object.\r\n */\r\n static newListener(eventName, callback) {\r\n const listener = {};\r\n listener.eventName = eventName;\r\n listener.callback = callback;\r\n return listener;\r\n }\r\n}\r\n","import Util from \"./util\";\r\nimport Listeners from \"./listeners\";\r\nimport Configuration from \"./configuration\";\r\n\r\n/**\r\n * @typedef {Object} ServiceProviderProperties\r\n * @property {String} URI - Service URI.\r\n * @property {String} server - Service server language.\r\n */\r\n\r\n/**\r\n * @classdesc\r\n * Class representing a serviceProvider. A serviceProvider is a class containing\r\n * an arbitrary number of services with the correspondent path.\r\n */\r\nexport default class ServiceProvider {\r\n /**\r\n * Returns Service Provider listeners.\r\n * @type {Listeners}\r\n */\r\n static get listeners() {\r\n return ServiceProvider._listeners;\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\r\n * @param {Listener} listener - Instance of {@link Listener}.\r\n */\r\n static addListener(listener) {\r\n ServiceProvider.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Fires events in Service Provider.\r\n * @param {String} eventName - Event name.\r\n * @param {Event} event - Event object.\r\n */\r\n static fireEvent(eventName, event) {\r\n ServiceProvider.listeners.fire(eventName, event);\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @type {ServiceProviderProperties}\r\n *\r\n */\r\n static get parameters() {\r\n return ServiceProvider._parameters;\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @private\r\n * @type {ServiceProviderProperties}\r\n */\r\n static set parameters(parameters) {\r\n ServiceProvider._parameters = parameters;\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Return service provider paths.\r\n * @private\r\n * @type {String}\r\n */\r\n static get servicePaths() {\r\n return ServiceProvider._servicePaths;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service paths.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set servicePaths(value) {\r\n ServiceProvider._servicePaths = value;\r\n }\r\n\r\n /**\r\n * Adds a new service to the ServiceProvider.\r\n * @param {String} service - Service name.\r\n * @param {String} path - Service path.\r\n * @static\r\n */\r\n static setServicePath(service, path) {\r\n ServiceProvider.servicePaths[service] = path;\r\n }\r\n\r\n /**\r\n * Returns the service path for a certain service.\r\n * @param {String} serviceName - Service name.\r\n * @returns {String} The service path.\r\n * @static\r\n */\r\n static getServicePath(serviceName) {\r\n return ServiceProvider.servicePaths[serviceName];\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Service provider integration path.\r\n * @type {String}\r\n */\r\n static get integrationPath() {\r\n return ServiceProvider._integrationPath;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service provider integration path.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set integrationPath(value) {\r\n ServiceProvider._integrationPath = value;\r\n }\r\n\r\n /**\r\n * Returns the server URL in the form protocol://serverName:serverPort.\r\n * @return {String} The client side server path.\r\n */\r\n static getServerURL() {\r\n const url = window.location.href;\r\n const arr = url.split(\"/\");\r\n const result = `${arr[0]}//${arr[2]}`;\r\n return result;\r\n }\r\n\r\n /**\r\n * Inits {@link this} class. Uses {@link this.integrationPath} as\r\n * base path to generate all backend services paths.\r\n * @param {Object} parameters - Function parameters.\r\n * @param {String} parameters.integrationPath - Service path.\r\n */\r\n static init(parameters) {\r\n ServiceProvider.parameters = parameters;\r\n // Services path (tech dependant).\r\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\r\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\r\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\r\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\r\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\r\n\r\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\r\n // for example: /app/service. For them we calculate the absolute URL path, i.e\r\n // protocol://domain:port/app/service\r\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\r\n const serverPath = ServiceProvider.getServerURL();\r\n configurationURI = serverPath + configurationURI;\r\n showImageURI = serverPath + showImageURI;\r\n createImageURI = serverPath + createImageURI;\r\n getMathMLURI = serverPath + getMathMLURI;\r\n serviceURI = serverPath + serviceURI;\r\n }\r\n\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\r\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\r\n ServiceProvider.setServicePath(\"service\", serviceURI);\r\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n\r\n ServiceProvider.listeners.fire(\"onInit\", {});\r\n }\r\n\r\n /**\r\n * Gets the content from an URL.\r\n * @param {String} url - Target URL.\r\n * @param {Object} [postVariables] - Object containing post variables.\r\n * null if a GET query should be done.\r\n * @returns {String} Content of the target URL.\r\n * @private\r\n * @static\r\n */\r\n static getUrl(url, postVariables) {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n const httpRequest = Util.createHttpRequest();\r\n\r\n if (httpRequest) {\r\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\r\n httpRequest.open(\"GET\", url, false);\r\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\r\n httpRequest.open(\"POST\", url, false);\r\n } else {\r\n httpRequest.open(\"POST\", currentPath + url, false);\r\n }\r\n\r\n let header = Configuration.get(\"customHeaders\");\r\n if (header) {\r\n if (typeof header === \"string\") {\r\n header = Util.convertStringToObject(header);\r\n }\r\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\r\n }\r\n\r\n if (typeof postVariables !== \"undefined\" && postVariables) {\r\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\r\n httpRequest.send(Util.httpBuildQuery(postVariables));\r\n } else {\r\n httpRequest.send(null);\r\n }\r\n\r\n return httpRequest.responseText;\r\n }\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Returns the response text of a certain service.\r\n * @param {String} service - Service name.\r\n * @param {String} postVariables - Post variables.\r\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\r\n * @returns {String} Service response text.\r\n */\r\n static getService(service, postVariables, get) {\r\n let response;\r\n if (get === true) {\r\n const getVariables = postVariables ? `?${postVariables}` : \"\";\r\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\r\n response = ServiceProvider.getUrl(serviceUrl);\r\n } else {\r\n const serviceUrl = ServiceProvider.getServicePath(service);\r\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\r\n }\r\n return response;\r\n }\r\n\r\n /**\r\n * Returns the server language of a certain service. The possible values\r\n * are: php, aspx, java and ruby.\r\n * This method has backward compatibility purposes.\r\n * @param {String} service - The configuration service.\r\n * @returns {String} - The server technology associated with the configuration service.\r\n */\r\n static getServerLanguageFromService(service) {\r\n if (service.indexOf(\".php\") !== -1) {\r\n return \"php\";\r\n }\r\n if (service.indexOf(\".aspx\") !== -1) {\r\n return \"aspx\";\r\n }\r\n if (service.indexOf(\"wirispluginengine\") !== -1) {\r\n return \"ruby\";\r\n }\r\n return \"java\";\r\n }\r\n\r\n /**\r\n * Returns the URI associated with a certain service.\r\n * @param {String} service - The service name.\r\n * @return {String} The service path.\r\n */\r\n static createServiceURI(service) {\r\n const extension = ServiceProvider.serverExtension();\r\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\r\n }\r\n\r\n static serverExtension() {\r\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\r\n return \".php\";\r\n }\r\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\r\n return \".aspx\";\r\n }\r\n return \"\";\r\n }\r\n}\r\n\r\n/**\r\n * @property {String} service - The service name.\r\n * @property {String} path - The service path.\r\n * @static\r\n */\r\nServiceProvider._servicePaths = {};\r\n\r\n/**\r\n * The integration path. Contains the path of the configuration service.\r\n * Used to define the path for all services.\r\n * @type {String}\r\n * @private\r\n */\r\nServiceProvider._integrationPath = \"\";\r\n\r\n/**\r\n * ServiceProvider static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nServiceProvider._listeners = new Listeners();\r\n\r\n/**\r\n * Service provider parameters.\r\n * @type {ServiceProviderParameters}\r\n */\r\nServiceProvider._parameters = {};\r\n","import TextCache from \"./textcache\";\r\nimport MathML from \"./mathml\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a LaTeX parser. Manages the services which allows to convert\r\n * LaTeX into MathML and MathML into LaTeX.\r\n */\r\nexport default class Latex {\r\n /**\r\n * Static property.\r\n * Return latex cache.\r\n * @private\r\n * @type {Cache}\r\n */\r\n static get cache() {\r\n return Latex._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set latex cache.\r\n * @param {Cache} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Latex._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\r\n * we call a text service with the param mathml2latex.\r\n * @param {String} mathml - MathML String.\r\n * @return {String} LaTeX string generated by the MathML argument.\r\n */\r\n static getLatexFromMathML(mathml) {\r\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\r\n /**\r\n * @type {TextCache}\r\n */\r\n const { cache } = Latex;\r\n\r\n const data = {\r\n service: \"mathml2latex\",\r\n mml: mathmlWithoutSemantics,\r\n };\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n // TODO: Error handling.\r\n let latex = \"\";\r\n\r\n if (jsonResponse.status === \"ok\") {\r\n latex = jsonResponse.result.text;\r\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\r\n // Inserting LaTeX semantics.\r\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\r\n cache.populate(latex, mathmlWithSemantics);\r\n }\r\n\r\n return latex;\r\n }\r\n\r\n /**\r\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\r\n * we call a text service with the param latex2mathml.\r\n * @param {String} latex - String containing a LaTeX formula.\r\n * @param {Boolean} includeLatexOnSemantics\r\n * - If true LaTeX would me included into MathML semantics.\r\n * @return {String} MathML string generated by the LaTeX argument.\r\n */\r\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\r\n /**\r\n * @type {TextCache}\r\n */\r\n const latexCache = Latex.cache;\r\n\r\n if (Latex.cache.get(latex)) {\r\n return Latex.cache.get(latex);\r\n }\r\n const data = {\r\n service: \"latex2mathml\",\r\n latex,\r\n };\r\n\r\n if (includeLatexOnSemantics) {\r\n data.saveLatex = \"\";\r\n }\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n let output;\r\n if (jsonResponse.status === \"ok\") {\r\n let mathml = jsonResponse.result.text;\r\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\r\n\r\n // Populate LatexCache.\r\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\r\n const content = Util.htmlEntities(latex);\r\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\r\n output = mathml;\r\n } else {\r\n output = mathml;\r\n }\r\n if (!latexCache.get(latex)) {\r\n latexCache.populate(latex, mathml);\r\n }\r\n } else {\r\n output = `$$${latex}$$`;\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML code to LaTeX.\r\n * The MathML code should containing to be converted.\r\n * @param {String} content - A string containing MathML valid code.\r\n * @param {Object} characters - An object containing special characters.\r\n * @return {String} A string containing all MathML annotated occurrences\r\n * replaced by the corresponding LaTeX code.\r\n */\r\n static parseMathmlToLatex(content, characters) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\r\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n let mathml;\r\n let startAnnotation;\r\n let closeAnnotation;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n mathml = content.substring(start, end);\r\n\r\n startAnnotation = mathml.indexOf(openTarget);\r\n if (startAnnotation !== -1) {\r\n startAnnotation += openTarget.length;\r\n closeAnnotation = mathml.indexOf(closeTarget);\r\n let latex = mathml.substring(startAnnotation, closeAnnotation);\r\n if (characters === Constants.safeXmlCharacters) {\r\n latex = MathML.safeXmlDecode(latex);\r\n }\r\n output += `$$${latex}$$`;\r\n // Populate latex into cache.\r\n\r\n Latex.cache.populate(latex, mathml);\r\n } else {\r\n output += mathml;\r\n }\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Extracts the latex of a determined position in a text.\r\n * @param {Node} textNode - textNode to extract the LaTeX\r\n * @param {Number} caretPosition - Starting position to find LaTeX.\r\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\r\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\r\n * \"$$\" by default.\r\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\r\n * @static\r\n */\r\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\r\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\r\n // Tags used for LaTeX formulas.\r\n const defaultLatexTags = {\r\n open: \"$$\",\r\n close: \"$$\",\r\n };\r\n // latexTags is an optional parameter. If is not set, use default latexTags.\r\n if (typeof latexTags === \"undefined\" || latexTags == null) {\r\n latexTags = defaultLatexTags;\r\n }\r\n // Looking for the first textNode.\r\n let startNode = textNode;\r\n\r\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n startNode = startNode.previousSibling;\r\n }\r\n\r\n /**\r\n * Returns the next latex position and node from a specific node and position.\r\n * @param {Node} currentNode - Node where searching latex.\r\n * @param {Number} currentPosition - Current position inside the currentNode.\r\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\r\n * \"$$\" by default.\r\n * @param {Boolean} tag - Tag containing the current search.\r\n * @returns {Object} Object containing the current node and the position.\r\n */\r\n function getNextLatexPosition(currentNode, currentPosition, tag) {\r\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\r\n\r\n while (position === -1) {\r\n currentNode = currentNode.nextSibling;\r\n\r\n if (!currentNode) {\r\n // TEXT_NODE.\r\n return null; // Not found.\r\n }\r\n\r\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\r\n }\r\n\r\n return {\r\n node: currentNode,\r\n position,\r\n };\r\n }\r\n\r\n /**\r\n * Determines if a node is previous, or not, to a second one.\r\n * @param {Node} node - Start node.\r\n * @param {Number} position - Start node position.\r\n * @param {Node} endNode - End node.\r\n * @param {Number} endPosition - End node position.\r\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\r\n */\r\n function isPrevious(node, position, endNode, endPosition) {\r\n if (node === endNode) {\r\n return position <= endPosition;\r\n }\r\n while (node && node !== endNode) {\r\n node = node.nextSibling;\r\n }\r\n\r\n return node === endNode;\r\n }\r\n\r\n let start;\r\n let end = {\r\n node: startNode,\r\n position: 0,\r\n };\r\n // Is supposed that open and close tags has the same length.\r\n const tagLength = latexTags.open.length;\r\n do {\r\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\r\n\r\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\r\n return null;\r\n }\r\n\r\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\r\n\r\n if (end == null) {\r\n return null;\r\n }\r\n\r\n end.position += tagLength;\r\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\r\n\r\n // Isolating latex.\r\n let latex;\r\n\r\n if (start.node === end.node) {\r\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\r\n } else {\r\n const index = start.position + tagLength;\r\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\r\n let currentNode = start.node;\r\n\r\n do {\r\n currentNode = currentNode.nextSibling;\r\n if (currentNode === end.node) {\r\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\r\n } else {\r\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\r\n }\r\n } while (currentNode !== end.node);\r\n }\r\n\r\n return {\r\n latex,\r\n startNode: start.node,\r\n startPosition: start.position,\r\n endNode: end.node,\r\n endPosition: end.position,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\r\n * @type {Cache}\r\n * @static\r\n */\r\nLatex._cache = new TextCache();\r\n","import translations from \"../lang/strings.json\";\r\n/**\r\n * This class represents a string manager. It's used to load localized strings.\r\n */\r\nexport default class StringManager {\r\n constructor() {\r\n throw new Error(\"Static class StringManager can not be instantiated.\");\r\n }\r\n\r\n /**\r\n * Returns the associated value of certain string key. If the associated value\r\n * doesn't exits returns the original key.\r\n * @param {string} key - string key\r\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\r\n * @returns {string} correspondent value. If doesn't exists original key.\r\n */\r\n static get(key, lang) {\r\n // Default language definition\r\n let { language } = this;\r\n\r\n // If parameter language, use it\r\n if (lang) {\r\n language = lang;\r\n }\r\n\r\n // Cut down on strings. e.g. en_US -> en\r\n if (language && language.length > 2) {\r\n language = language.slice(0, 2);\r\n }\r\n\r\n // Check if we support the language\r\n if (!this.strings.hasOwnProperty(language)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown language ${language} set in StringManager.`);\r\n language = \"en\";\r\n }\r\n\r\n // Check if the key is supported in the given language\r\n if (!this.strings[language].hasOwnProperty(key)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\r\n return key;\r\n }\r\n\r\n return this.strings[language][key];\r\n }\r\n}\r\n\r\n/**\r\n * Dictionary of dictionaries:\r\n * Key: language code\r\n * Value: Key: id of the string\r\n * Value: translation of the string\r\n */\r\nStringManager.strings = translations;\r\n\r\n/**\r\n * Language of the translations; English by default\r\n */\r\nStringManager.language = \"en\";\r\n","/* eslint-disable no-bitwise */\r\nimport DOMPurify from \"dompurify\";\r\nimport MathML from \"./mathml\";\r\nimport Configuration from \"./configuration\";\r\nimport Latex from \"./latex\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * This class represents an utility class.\r\n */\r\nexport default class Util {\r\n /**\r\n * Fires an event in a target.\r\n * @param {EventTarget} eventTarget - target where event should be fired.\r\n * @param {string} eventName event to fire.\r\n * @static\r\n */\r\n static fireEvent(eventTarget, eventName) {\r\n if (document.createEvent) {\r\n const eventObject = document.createEvent(\"HTMLEvents\");\r\n eventObject.initEvent(eventName, true, true);\r\n return !eventTarget.dispatchEvent(eventObject);\r\n }\r\n\r\n const eventObject = document.createEventObject();\r\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\r\n }\r\n\r\n /**\r\n * Cross-browser addEventListener/attachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - callback function.\r\n * @static\r\n */\r\n static addEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.addEventListener) {\r\n eventTarget.addEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.attachEvent) {\r\n // Backwards compatibility.\r\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * Cross-browser removeEventListener/detachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - function to remove from the event target.\r\n * @static\r\n */\r\n static removeEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.removeEventListener) {\r\n eventTarget.removeEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.detachEvent) {\r\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * A map from event target to event handlers so we can remove the event\r\n * listeners in removeElementEvents\r\n *\r\n * @type {Map}\r\n * @static\r\n */\r\n static elementEventsMap = new Map();\r\n\r\n /**\r\n * Adds the a callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\r\n * @param {Function} mousedownHandler - function to run when on mousedown event.\r\n * @param {Function} mouseupHandler - function to run when on mouseup event.\r\n * @static\r\n */\r\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\r\n // Make sure not to leak event listeners if we've already added events to\r\n // this element\r\n Util.removeElementEvents(eventTarget);\r\n\r\n let entry = {};\r\n Util.elementEventsMap.set(eventTarget, entry);\r\n\r\n if (doubleClickHandler) {\r\n entry.callbackDblclick = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n doubleClickHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n }\r\n\r\n if (mousedownHandler) {\r\n entry.callbackMousedown = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mousedownHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n }\r\n\r\n if (mouseupHandler) {\r\n entry.callbackMouseup = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mouseupHandler(element, realEvent);\r\n };\r\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\r\n // while the mouse is outside the editor text field.\r\n // This is a workaround: we trigger the event independently of where the mouse\r\n // is when we release its button.\r\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n }\r\n\r\n /**\r\n * Remove all callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @static\r\n */\r\n static removeElementEvents(eventTarget) {\r\n let entry = Util.elementEventsMap.get(eventTarget);\r\n if (!entry) {\r\n return;\r\n }\r\n\r\n Util.elementEventsMap.delete(eventTarget);\r\n\r\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n\r\n /**\r\n * Adds a class name to a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static addClass(element, className) {\r\n if (!Util.containsClass(element, className)) {\r\n element.className += ` ${className}`;\r\n }\r\n }\r\n\r\n /**\r\n * Checks if a HTMLElement contains a certain class.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the className.\r\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\r\n * @static\r\n */\r\n static containsClass(element, className) {\r\n if (element == null || !(\"className\" in element)) {\r\n return false;\r\n }\r\n\r\n const currentClasses = element.className.split(\" \");\r\n\r\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\r\n if (currentClasses[i] === className) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Remove a certain class for a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static removeClass(element, className) {\r\n let newClassName = \"\";\r\n const classes = element.className.split(\" \");\r\n\r\n for (let i = 0; i < classes.length; i += 1) {\r\n if (classes[i] !== className) {\r\n newClassName += `${classes[i]} `;\r\n }\r\n }\r\n element.className = newClassName.trim();\r\n }\r\n\r\n /**\r\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\r\n * used to parse old applets.\r\n * @param {string} text - string containing safeXml characters\r\n * @returns {string} a string with safeXml characters parsed.\r\n * @static\r\n */\r\n static convertOldXmlinitialtextAttribute(text) {\r\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\r\n // This could be removed in future.\r\n const val = \"value=\";\r\n\r\n const xitpos = text.indexOf(\"xmlinitialtext\");\r\n const valpos = text.indexOf(val, xitpos);\r\n const quote = text.charAt(valpos + val.length);\r\n const startquote = valpos + val.length + 1;\r\n const endquote = text.indexOf(quote, startquote);\r\n\r\n const value = text.substring(startquote, endquote);\r\n\r\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\r\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\r\n newvalue = newvalue.split(\"&\").join(\"ยง\");\r\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\r\n\r\n text = text.split(value).join(newvalue);\r\n return text;\r\n }\r\n\r\n /**\r\n * Convert a string representation of key-value pairs to an object.\r\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\r\n * @returns {Object} - Object containing the key-value pairs\r\n */\r\n static convertStringToObject(keyValueString) {\r\n if (!keyValueString || typeof keyValueString !== \"string\") {\r\n return {};\r\n }\r\n\r\n return keyValueString\r\n .split(\",\")\r\n .map((pair) => pair.trim().split(\"=\"))\r\n .reduce((resultObject, [key, value]) => {\r\n if (key && value) {\r\n resultObject[key] = value;\r\n }\r\n return resultObject;\r\n }, {});\r\n }\r\n\r\n /**\r\n * Cross-browser solution for creating new elements.\r\n * @param {string} tagName - tag name of the wished element.\r\n * @param {Object} attributes - an object where each key is a wished\r\n * attribute name and each value is its value.\r\n * @param {Object} [creator] - if supplied, this function will use\r\n * the \"createElement\" method from this param. Otherwise\r\n * document will be used as creator.\r\n * @returns {Element} The DOM element with the specified attributes assigned.\r\n * @static\r\n */\r\n static createElement(tagName, attributes, creator) {\r\n if (attributes === undefined) {\r\n attributes = {};\r\n }\r\n\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n let element;\r\n\r\n /*\r\n * Internet Explorer fix:\r\n * If you create a new object dynamically, you can't set a non-standard attribute.\r\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\r\n * Other browsers will throw an exception and will run the standard code.\r\n */\r\n try {\r\n let html = `<${tagName}`;\r\n\r\n Object.keys(attributes).forEach((attributeName) => {\r\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\r\n });\r\n html += \">\";\r\n element = creator.createElement(html);\r\n } catch (e) {\r\n element = creator.createElement(tagName);\r\n Object.keys(attributes).forEach((attributeName) => {\r\n element.setAttribute(attributeName, attributes[attributeName]);\r\n });\r\n }\r\n return element;\r\n }\r\n\r\n /**\r\n * Creates new HTML from it's HTML code as string.\r\n * @param {string} objectCode - html code\r\n * @returns {Element} the HTML element.\r\n * @static\r\n */\r\n static createObject(objectCode, creator) {\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\r\n objectCode = objectCode\r\n .split(\"\").join(\"\").split(\"\").join(\"

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%0AU2B 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ⅈ2XYf?cWNqHhI-JGRIh*J50XF7? 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≻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

\");\r\n\r\n objectCode = objectCode\r\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\r\n\r\n const container = Util.createElement(\"div\", {}, creator);\r\n container.innerHTML = objectCode;\r\n\r\n function recursiveParamsFix(object) {\r\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const param = Util.createElement(\"param\", attributesParsed, creator);\r\n\r\n // IE fix.\r\n if (param.NAME) {\r\n param.name = param.NAME;\r\n param.value = param.VALUE;\r\n }\r\n\r\n param.removeAttribute(\"wirisObject\");\r\n object.parentNode.replaceChild(param, object);\r\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\r\n applet.removeAttribute(\"wirisObject\");\r\n\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n\r\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\r\n applet.appendChild(object.childNodes[i]);\r\n i -= 1; // When we insert the object child into the applet, object loses one child.\r\n }\r\n }\r\n\r\n object.parentNode.replaceChild(applet, object);\r\n } else {\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n }\r\n }\r\n }\r\n\r\n recursiveParamsFix(container);\r\n return container.firstChild;\r\n }\r\n\r\n /**\r\n * Converts an Element to its HTML code.\r\n * @param {Element} element - entry element.\r\n * @return {string} the HTML code of the input element.\r\n * @static\r\n */\r\n static createObjectCode(element) {\r\n // In case that the image was not created, the object can be null or undefined.\r\n if (typeof element === \"undefined\" || element === null) {\r\n return null;\r\n }\r\n\r\n if (element.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let output = `<${element.tagName}`;\r\n\r\n for (let i = 0; i < element.attributes.length; i += 1) {\r\n if (element.attributes[i].specified) {\r\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\r\n }\r\n }\r\n\r\n if (element.childNodes.length > 0) {\r\n output += \">\";\r\n\r\n for (let i = 0; i < element.childNodes.length; i += 1) {\r\n output += Util.createObject(element.childNodes[i]);\r\n }\r\n\r\n output += ``;\r\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\r\n output += `>`;\r\n } else {\r\n output += \"/>\";\r\n }\r\n\r\n return output;\r\n }\r\n\r\n if (element.nodeType === 3) {\r\n // TEXT_NODE.\r\n return Util.htmlEntities(element.nodeValue);\r\n }\r\n\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Concatenates two URL paths.\r\n * @param {string} path1 - first URL path\r\n * @param {string} path2 - second URL path\r\n * @returns {string} new URL.\r\n */\r\n static concatenateUrl(path1, path2) {\r\n let separator = \"\";\r\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\r\n separator = \"/\";\r\n }\r\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all HTML special characters by their correspondent entities.\r\n * @param {string} input - text to be parsed.\r\n * @returns {string} the input text with all their special characters replaced by their entities.\r\n * @static\r\n */\r\n static htmlEntities(input) {\r\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\r\n }\r\n\r\n /**\r\n * Sanitize HTML to prevent XSS injections.\r\n * @param {string} html - html to be sanitize.\r\n * @returns {string} html sanitized.\r\n * @static\r\n */\r\n static htmlSanitize(html) {\r\n const annotationRegex = /\\/;\r\n // Get all the annotation content including the tags.\r\n const annotation = html.match(annotationRegex);\r\n // Sanitize html code without removing our supported MathML tags and attributes.\r\n html = DOMPurify.sanitize(html, {\r\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\r\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\r\n });\r\n // Readd old annotation content.\r\n return html.replace(annotationRegex, annotation);\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all the HTML entities by their characters.\r\n * @param {string} input - text to be parsed\r\n * @returns {string} the input text with all their entities replaced by characters.\r\n * @static\r\n */\r\n static htmlEntitiesDecode(input) {\r\n // Textarea element decodes when inner html is set.\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.innerHTML = input;\r\n return textarea.value;\r\n }\r\n\r\n /**\r\n * Returns a cross-browser http request.\r\n * @return {object} httpRequest request object.\r\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\r\n */\r\n static createHttpRequest() {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n if (currentPath.substr(0, 7) === \"file://\") {\r\n throw StringManager.get(\"exception_cross_site\");\r\n }\r\n\r\n if (typeof XMLHttpRequest !== \"undefined\") {\r\n return new XMLHttpRequest();\r\n }\r\n\r\n try {\r\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\r\n } catch (e) {\r\n try {\r\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\r\n } catch (oc) {\r\n return null;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Converts a hash to a HTTP query.\r\n * @param {Object[]} properties - a key/value object.\r\n * @returns {string} a HTTP query containing all the key value pairs with\r\n * all the special characters replaced by their entities.\r\n * @static\r\n */\r\n static httpBuildQuery(properties) {\r\n let result = \"\";\r\n\r\n Object.keys(properties).forEach((i) => {\r\n if (properties[i] != null) {\r\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\r\n }\r\n });\r\n\r\n // Deleting last '&' empty character.\r\n if (result.substring(result.length - 1) === \"&\") {\r\n result = result.substring(0, result.length - 1);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Convert a hash to string sorting keys to get a deterministic output\r\n * @param {Object[]} hash - a key/value object.\r\n * @returns {string} a string with the form key1=value1...keyn=valuen\r\n * @static\r\n */\r\n static propertiesToString(hash) {\r\n // 1. Sort keys. We sort the keys because we want a deterministic output.\r\n const keys = [];\r\n Object.keys(hash).forEach((key) => {\r\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\r\n keys.push(key);\r\n }\r\n });\r\n\r\n const n = keys.length;\r\n for (let i = 0; i < n; i += 1) {\r\n for (let j = i + 1; j < n; j += 1) {\r\n const s1 = keys[i];\r\n const s2 = keys[j];\r\n if (Util.compareStrings(s1, s2) > 0) {\r\n // Swap.\r\n keys[i] = s2;\r\n keys[j] = s1;\r\n }\r\n }\r\n }\r\n\r\n // 2. Generate output.\r\n let output = \"\";\r\n for (let i = 0; i < n; i += 1) {\r\n const key = keys[i];\r\n output += key;\r\n output += \"=\";\r\n let value = hash[key];\r\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\r\n value = value.replace(\"\\n\", \"\\\\n\");\r\n value = value.replace(\"\\r\", \"\\\\r\");\r\n value = value.replace(\"\\t\", \"\\\\t\");\r\n\r\n output += value;\r\n output += \"\\n\";\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Compare two strings using charCodeAt method\r\n * @param {string} a - first string to compare.\r\n * @param {string} b - second string to compare.\r\n * @returns {number} the difference between a and b\r\n * @static\r\n */\r\n static compareStrings(a, b) {\r\n let i;\r\n const an = a.length;\r\n const bn = b.length;\r\n const n = an > bn ? bn : an;\r\n for (i = 0; i < n; i += 1) {\r\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\r\n if (c !== 0) {\r\n return c;\r\n }\r\n }\r\n return a.length - b.length;\r\n }\r\n\r\n /**\r\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\r\n * @param {string} string - input string\r\n * @param {number} idx - an integer greater than or equal to 0\r\n * and less than the length of the string\r\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\r\n * @static\r\n */\r\n static fixedCharCodeAt(string, idx) {\r\n idx = idx || 0;\r\n const code = string.charCodeAt(idx);\r\n let hi;\r\n let low;\r\n\r\n /* High surrogate (could change last hex to 0xDB7F to treat high\r\n private surrogates as single characters) */\r\n\r\n if (code >= 0xd800 && code <= 0xdbff) {\r\n hi = code;\r\n low = string.charCodeAt(idx + 1);\r\n if (Number.isNaN(low)) {\r\n throw StringManager.get(\"exception_high_surrogate\");\r\n }\r\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\r\n }\r\n\r\n if (code >= 0xdc00 && code <= 0xdfff) {\r\n // Low surrogate.\r\n /* We return false to allow loops to skip this iteration since should have\r\n already handled high surrogate above in the previous iteration. */\r\n return false;\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Returns an URL with it's query params converted into array.\r\n * @param {string} url - input URL.\r\n * @returns {Object[]} an array containing all URL query params.\r\n * @static\r\n */\r\n static urlToAssArray(url) {\r\n let i;\r\n i = url.indexOf(\"?\");\r\n if (i > 0) {\r\n const query = url.substring(i + 1);\r\n const ss = query.split(\"&\");\r\n const h = {};\r\n for (i = 0; i < ss.length; i += 1) {\r\n const s = ss[i];\r\n const kv = s.split(\"=\");\r\n if (kv.length > 1) {\r\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\r\n }\r\n }\r\n return h;\r\n }\r\n return {};\r\n }\r\n\r\n /**\r\n * Returns an encoded URL by replacing each instance of certain characters by\r\n * one, two, three or four escape sequences using encodeURIComponent method.\r\n * !'()* . will not be encoded.\r\n *\r\n * @param {string} clearString - URL string to be encoded\r\n * @returns {string} URL with it's special characters replaced.\r\n * @static\r\n */\r\n static urlEncode(clearString) {\r\n let output = \"\";\r\n // Method encodeURIComponent doesn't encode !'()*~ .\r\n output = encodeURIComponent(clearString);\r\n return output;\r\n }\r\n\r\n // TODO: To parser?\r\n /**\r\n * Converts the HTML of a image into the output code that WIRIS must return.\r\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\r\n * or the Wiriscas attribute of a WIRIS applet.\r\n * @param {string} imgCode - the html code from a formula or a CAS image.\r\n * @param {boolean} convertToXml - true if the image should be converted to XML.\r\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\r\n * @returns {string} the XML or safeXML of a WIRIS image.\r\n * @static\r\n */\r\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\r\n const imgObject = Util.createObject(imgCode);\r\n if (imgObject) {\r\n if (\r\n imgObject.className === Configuration.get(\"imageClassName\") ||\r\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\r\n ) {\r\n if (!convertToXml) {\r\n return imgCode;\r\n }\r\n\r\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n // To handle annotations, first we need the MathML in XML.\r\n let mathML = MathML.safeXmlDecode(dataMathML);\r\n\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\r\n }\r\n\r\n if (mathML == null) {\r\n mathML = imgObject.getAttribute(\"alt\");\r\n }\r\n\r\n if (convertToSafeXml) {\r\n const safeMathML = MathML.safeXmlEncode(mathML);\r\n return safeMathML;\r\n }\r\n\r\n return mathML;\r\n }\r\n }\r\n return imgCode;\r\n }\r\n\r\n /**\r\n * Gets the node length in characters.\r\n * @param {Node} node - HTML node.\r\n * @returns {number} node length.\r\n * @static\r\n */\r\n static getNodeLength(node) {\r\n const staticNodeLengths = {\r\n IMG: 1,\r\n BR: 1,\r\n };\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return node.nodeValue.length;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\r\n\r\n if (length === undefined) {\r\n length = 0;\r\n }\r\n\r\n for (let i = 0; i < node.childNodes.length; i += 1) {\r\n length += Util.getNodeLength(node.childNodes[i]);\r\n }\r\n return length;\r\n }\r\n return 0;\r\n }\r\n\r\n /**\r\n * Gets a selected node or text from an editable HTMLElement.\r\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\r\n * @param {HTMLElement} target - the editable HTMLElement.\r\n * @param {boolean} isIframe - specifies if the target is an iframe or not\r\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\r\n * the current selection and uses window.getSelection()\r\n * @returns {object} an object with the 'node' key set if the item is an\r\n * element or the keys 'node' and 'caretPosition' if the element is text.\r\n * @static\r\n */\r\n static getSelectedItem(target, isIframe, forceGetSelection) {\r\n let windowTarget;\r\n\r\n if (isIframe) {\r\n windowTarget = target.contentWindow;\r\n windowTarget.focus();\r\n } else {\r\n windowTarget = window;\r\n target.focus();\r\n }\r\n\r\n if (document.selection && !forceGetSelection) {\r\n const range = windowTarget.document.selection.createRange();\r\n\r\n if (range.parentElement) {\r\n if (range.htmlText.length > 0) {\r\n if (range.text.length === 0) {\r\n return Util.getSelectedItem(target, isIframe, true);\r\n }\r\n\r\n return null;\r\n }\r\n\r\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\r\n let temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\r\n // IE9 fix: parentElement() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML('');\r\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\r\n }\r\n\r\n let node;\r\n let caretPosition;\r\n\r\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = temporalObject.nextSibling;\r\n caretPosition = 0;\r\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\r\n node = temporalObject.previousSibling;\r\n caretPosition = node.nodeValue.length;\r\n } else {\r\n node = windowTarget.document.createTextNode(\"\");\r\n temporalObject.parentNode.insertBefore(node, temporalObject);\r\n caretPosition = 0;\r\n }\r\n\r\n temporalObject.parentNode.removeChild(temporalObject);\r\n\r\n return {\r\n node,\r\n caretPosition,\r\n };\r\n }\r\n\r\n if (range.length > 1) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: range.item(0),\r\n };\r\n }\r\n\r\n if (windowTarget.getSelection) {\r\n let range;\r\n const selection = windowTarget.getSelection();\r\n\r\n try {\r\n range = selection.getRangeAt(0);\r\n } catch (e) {\r\n range = windowTarget.document.createRange();\r\n }\r\n\r\n const node = range.startContainer;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return {\r\n node,\r\n caretPosition: range.startOffset,\r\n };\r\n }\r\n\r\n if (node !== range.endContainer) {\r\n return null;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n const position = range.startOffset;\r\n\r\n if (node.childNodes[position]) {\r\n // In case that a formula is detected but not selected,\r\n // we create an empty span where we could insert the new formula.\r\n if (range.startOffset === range.endOffset) {\r\n if (\r\n position !== 0 &&\r\n node.childNodes[position - 1].localName === \"span\" &&\r\n node.childNodes[position].classList?.contains(\"Wirisformula\")\r\n ) {\r\n node.childNodes[position - 1].remove();\r\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\r\n }\r\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\r\n if (\r\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\r\n position === 0\r\n ) {\r\n const emptySpan = document.createElement(\"span\");\r\n node.insertBefore(emptySpan, node.childNodes[position]);\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Returns null if there isn't any item or if it is malformed.\r\n * Otherwise returns an object containing the node with the MathML image\r\n * and the cursor position inside the textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea element.\r\n * @returns {Object} An object containing the node, the index of the\r\n * beginning of the selected text, caret position and the start and end position of the\r\n * text node.\r\n * @static\r\n */\r\n static getSelectedItemOnTextarea(textarea) {\r\n const textNode = document.createTextNode(textarea.value);\r\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\r\n if (textNodeValues === null) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: textNode,\r\n caretPosition: textarea.selectionStart,\r\n startPosition: textNodeValues.startPosition,\r\n endPosition: textNodeValues.endPosition,\r\n };\r\n }\r\n\r\n /**\r\n * Looks for elements that match the given name in a HTML code string.\r\n * Important: this function is very concrete for WIRIS code.\r\n * It takes as preconditions lots of behaviors that are not the general case.\r\n * @param {string} code - HTML code.\r\n * @param {string} name - element name.\r\n * @param {boolean} autoClosed - true if the elements are autoClosed.\r\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\r\n * @static\r\n */\r\n static getElementsByNameFromString(code, name, autoClosed) {\r\n const elements = [];\r\n code = code.toLowerCase();\r\n name = name.toLowerCase();\r\n let start = code.indexOf(`<${name} `);\r\n\r\n while (start !== -1) {\r\n // Look for nodes.\r\n let endString;\r\n\r\n if (autoClosed) {\r\n endString = \">\";\r\n } else {\r\n endString = ``;\r\n }\r\n\r\n let end = code.indexOf(endString, start);\r\n\r\n if (end !== -1) {\r\n end += endString.length;\r\n elements.push({\r\n start,\r\n end,\r\n });\r\n } else {\r\n end = start + 1;\r\n }\r\n\r\n start = code.indexOf(`<${name} `, end);\r\n }\r\n\r\n return elements;\r\n }\r\n\r\n /**\r\n * Returns the numeric value of a base64 character.\r\n * @param {string} character - base64 character.\r\n * @returns {number} base64 character numeric value.\r\n * @static\r\n */\r\n static decode64(character) {\r\n const PLUS = \"+\".charCodeAt(0);\r\n const SLASH = \"/\".charCodeAt(0);\r\n const NUMBER = \"0\".charCodeAt(0);\r\n const LOWER = \"a\".charCodeAt(0);\r\n const UPPER = \"A\".charCodeAt(0);\r\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\r\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\r\n const code = character.charCodeAt(0);\r\n\r\n if (code === PLUS || code === PLUS_URL_SAFE) {\r\n return 62; // Char '+'.\r\n }\r\n if (code === SLASH || code === SLASH_URL_SAFE) {\r\n return 63; // Char '/'.\r\n }\r\n if (code < NUMBER) {\r\n return -1; // No match.\r\n }\r\n if (code < NUMBER + 10) {\r\n return code - NUMBER + 26 + 26;\r\n }\r\n if (code < UPPER + 26) {\r\n return code - UPPER;\r\n }\r\n if (code < LOWER + 26) {\r\n return code - LOWER + 26;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Converts a base64 string to a array of bytes.\r\n * @param {string} b64String - base64 string.\r\n * @param {number} length - dimension of byte array (by default whole string).\r\n * @return {Object[]} the resultant byte array.\r\n * @static\r\n */\r\n static b64ToByteArray(b64String, length) {\r\n let tmp;\r\n\r\n if (b64String.length % 4 > 0) {\r\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\r\n }\r\n\r\n const arr = [];\r\n\r\n let l;\r\n let placeHolders;\r\n if (!length) {\r\n // All b64String string.\r\n if (b64String.charAt(b64String.length - 2) === \"=\") {\r\n placeHolders = 2;\r\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\r\n placeHolders = 1;\r\n } else {\r\n placeHolders = 0;\r\n }\r\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\r\n } else {\r\n l = length;\r\n }\r\n\r\n let i;\r\n for (i = 0; i < l; i += 4) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 18) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\r\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\r\n Util.decode64(b64String.charAt(i + 3));\r\n\r\n arr.push((tmp >> 16) & 0xff);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n if (placeHolders) {\r\n if (placeHolders === 2) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\r\n arr.push(tmp & 0xff);\r\n } else if (placeHolders === 1) {\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 10) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\r\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n }\r\n return arr;\r\n }\r\n\r\n /**\r\n * Returns the first 32-bit signed integer from a byte array.\r\n * @param {Object[]} bytes - array of bytes.\r\n * @returns {number} the 32-bit signed integer.\r\n * @static\r\n */\r\n static readInt32(bytes) {\r\n if (bytes.length < 4) {\r\n return false;\r\n }\r\n const int32 = bytes.splice(0, 4);\r\n // @codingStandardsIgnoreStartยก\r\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read the first byte from a byte array.\r\n * @param {Object} bytes - input byte array.\r\n * @returns {number} first byte of the byte array.\r\n * @static\r\n */\r\n static readByte(bytes) {\r\n // @codingStandardsIgnoreStart\r\n return bytes.shift() << 0;\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\r\n * @param {Object[]} bytes - byte array.\r\n * @param {number} pos - start position.\r\n * @param {number} len - number of bytes to read.\r\n * @returns {Object[]} the byte array.\r\n * @static\r\n */\r\n static readBytes(bytes, pos, len) {\r\n return bytes.splice(pos, len);\r\n }\r\n\r\n /**\r\n * Inserts or modifies formulas or CAS on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea target.\r\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\r\n * call this function as (textarea, Parser.createImageSrc(mathml));\r\n * @static\r\n */\r\n static updateTextArea(textarea, text) {\r\n if (textarea && text) {\r\n textarea.focus();\r\n\r\n if (textarea.selectionStart != null) {\r\n const { selectionEnd } = textarea;\r\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\r\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\r\n textarea.value = selectionStart + text + selectionEndSub;\r\n textarea.selectionEnd = selectionEnd + text.length;\r\n } else {\r\n const selection = document.selection.createRange();\r\n selection.text = text;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Modifies existing formula on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - text area target.\r\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\r\n * to the image,you can call this function as\r\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\r\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\r\n * @param {number} end - ending index from textarea where it needs to be replaced by text\r\n * @static\r\n */\r\n static updateExistingTextOnTextarea(textarea, text, start, end) {\r\n textarea.focus();\r\n const textareaStart = textarea.value.substring(0, start);\r\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\r\n textarea.selectionEnd = start + text.length;\r\n }\r\n\r\n /**\r\n * Add a parameter with it's correspondent value to an URL (GET).\r\n * @param {string} path - URL path\r\n * @param {string} parameter - parameter\r\n * @param {string} value - value\r\n * @static\r\n */\r\n static addArgument(path, parameter, value) {\r\n let sep;\r\n if (path.indexOf(\"?\") > 0) {\r\n sep = \"&\";\r\n } else {\r\n sep = \"?\";\r\n }\r\n return `${path + sep + parameter}=${value}`;\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType Image class. Contains all the logic related\r\n * to MathType images manipulation.\r\n * All MathType images are generated using the appropriate MathType\r\n * integration service: showimage or createimage.\r\n *\r\n * There are two available image formats:\r\n * - svg (default)\r\n * - png\r\n *\r\n * There are two formats for the image src attribute:\r\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\r\n * - A link to the showimage service.\r\n */\r\nexport default class Image {\r\n /**\r\n * Removes data attributes from an image.\r\n * @param {HTMLImageElement} img - Image where remove data attributes.\r\n */\r\n static removeImgDataAttributes(img) {\r\n const attributesToRemove = [];\r\n const { attributes } = img;\r\n\r\n Object.keys(attributes).forEach((key) => {\r\n const attribute = attributes[key];\r\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\r\n // Is preferred keep an array and remove after the search\r\n // because when attribute is removed the array of attributes\r\n // is modified.\r\n attributesToRemove.push(attribute.name);\r\n }\r\n });\r\n\r\n attributesToRemove.forEach((attribute) => {\r\n img.removeAttribute(attribute);\r\n });\r\n }\r\n\r\n /**\r\n * @static\r\n * Clones all MathType image attributes from a HTMLImageElement to another.\r\n * @param {HTMLImageElement} originImg - The original image.\r\n * @param {HTMLImageElement} destImg - The destination image.\r\n */\r\n static clone(originImg, destImg) {\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (!originImg.hasAttribute(customEditorAttributeName)) {\r\n destImg.removeAttribute(customEditorAttributeName);\r\n }\r\n\r\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\r\n const imgAttributes = [\r\n mathmlAttributeName,\r\n customEditorAttributeName,\r\n \"alt\",\r\n \"height\",\r\n \"width\",\r\n \"style\",\r\n \"src\",\r\n \"role\",\r\n ];\r\n\r\n imgAttributes.forEach((iterator) => {\r\n const originAttribute = originImg.getAttribute(iterator);\r\n if (originAttribute) {\r\n destImg.setAttribute(iterator, originAttribute);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Determines whether an img src contains an SVG.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src contains an SVG, false otherwise\r\n */\r\n static isSvg(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;\");\r\n }\r\n\r\n /**\r\n * Determines whether an img src is encoded in base64 or not.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src is encoded in base64, false otherwise\r\n */\r\n static isBase64(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\r\n }\r\n\r\n /**\r\n * Calculates the metrics of a MathType image given the the service response and the image format.\r\n * @param {HTMLImageElement} img - The HTMLImageElement.\r\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\r\n * @param {Boolean} jsonResponse - True the response of the image service is a\r\n * JSON object. False otherwise.\r\n */\r\n static setImgSize(img, uri, jsonResponse) {\r\n let ar;\r\n let base64String;\r\n let bytes;\r\n let svgString;\r\n if (jsonResponse) {\r\n // Cleaning data:image/png;base64.\r\n if (Image.isSvg(img)) {\r\n // SVG format.\r\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\r\n if (!Image.isBase64(img)) {\r\n ar = Image.getMetricsFromSvgString(uri);\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n svgString = \"\";\r\n bytes = Util.b64ToByteArray(base64String, base64String.length);\r\n for (let i = 0; i < bytes.length; i += 1) {\r\n svgString += String.fromCharCode(bytes[i]);\r\n }\r\n ar = Image.getMetricsFromSvgString(svgString);\r\n }\r\n // PNG format: we store all metrics information in the first 88 bytes.\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n bytes = Util.b64ToByteArray(base64String, 88);\r\n ar = Image.getMetricsFromBytes(bytes);\r\n }\r\n // Backwards compatibility: we store the metrics into createimage response.\r\n } else {\r\n ar = Util.urlToAssArray(uri);\r\n }\r\n let width = ar.cw;\r\n if (!width) {\r\n return;\r\n }\r\n let height = ar.ch;\r\n let baseline = ar.cb;\r\n const { dpi } = ar;\r\n if (dpi) {\r\n width = (width * 96) / dpi;\r\n height = (height * 96) / dpi;\r\n baseline = (baseline * 96) / dpi;\r\n }\r\n img.width = width;\r\n img.height = height;\r\n img.style.verticalAlign = `-${height - baseline}px`;\r\n }\r\n\r\n /**\r\n * Calculates the metrics of an image which has been resized. Is used to restore the original\r\n * metrics of a resized image.\r\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\r\n */\r\n static fixAfterResize(img) {\r\n img.removeAttribute(\"style\");\r\n img.removeAttribute(\"width\");\r\n img.removeAttribute(\"height\");\r\n // In order to avoid resize with max-width css property.\r\n img.style.maxWidth = \"none\";\r\n\r\n const processImg = (img) => {\r\n if (img.src.indexOf(\"data:image\") !== -1) {\r\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\r\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\r\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\r\n // (which would actually make more sense for readibility and efficiency).\r\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\r\n // 'data:image/svg+xml;base64,'.length === 26\r\n const base64String = img.getAttribute(\"src\").substring(26);\r\n const svgString = window.atob(base64String);\r\n const encodedSvgString = encodeURIComponent(svgString);\r\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n // Return src to base64!\r\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\r\n } else {\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n }\r\n } else {\r\n // 'data:image/png;base64,' === 22.\r\n const base64 = img.src.substring(22, img.src.length);\r\n Image.setImgSize(img, base64, true);\r\n }\r\n } else {\r\n Image.setImgSize(img, img.src);\r\n }\r\n };\r\n\r\n // If the image doesn't contain a blob, just process it normally\r\n if (img.src.indexOf(\"blob:\") === -1) {\r\n processImg(img);\r\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = function () {\r\n img.setAttribute(\"src\", reader.result);\r\n processImg(img);\r\n };\r\n fetch(img.src)\r\n .then((r) => r.blob())\r\n .then((blob) => {\r\n reader.readAsDataURL(blob);\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\r\n * by the MathType image service. This image contains as an extra custom attribute:\r\n * the baseline (wrs:baseline).\r\n * @param {String} svgString - The SVG image.\r\n * @return {Array} - The image metrics.\r\n */\r\n static getMetricsFromSvgString(svgString) {\r\n let first = svgString.indexOf('height=\"');\r\n let last = svgString.indexOf('\"', first + 8, svgString.length);\r\n const height = svgString.substring(first + 8, last);\r\n\r\n first = svgString.indexOf('width=\"');\r\n last = svgString.indexOf('\"', first + 7, svgString.length);\r\n const width = svgString.substring(first + 7, last);\r\n\r\n first = svgString.indexOf('wrs:baseline=\"');\r\n last = svgString.indexOf('\"', first + 14, svgString.length);\r\n const baseline = svgString.substring(first + 14, last);\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n if (typeof baseline !== \"undefined\") {\r\n arr.cb = baseline;\r\n }\r\n return arr;\r\n }\r\n return [];\r\n }\r\n\r\n /**\r\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\r\n * @param {Array.} bytes - png byte array.\r\n * @return {Array} The png metrics.\r\n */\r\n static getMetricsFromBytes(bytes) {\r\n Util.readBytes(bytes, 0, 8);\r\n let width;\r\n let height;\r\n let typ;\r\n let baseline;\r\n let dpi;\r\n while (bytes.length >= 4) {\r\n typ = Util.readInt32(bytes);\r\n if (typ === 0x49484452) {\r\n width = Util.readInt32(bytes);\r\n height = Util.readInt32(bytes);\r\n // Read 5 bytes.\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n } else if (typ === 0x62615345) {\r\n // Baseline: 'baSE'.\r\n baseline = Util.readInt32(bytes);\r\n } else if (typ === 0x70485973) {\r\n // Dpis: 'pHYs'.\r\n dpi = Util.readInt32(bytes);\r\n dpi = Math.round(dpi / 39.37);\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n }\r\n Util.readInt32(bytes);\r\n }\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n arr.dpi = dpi;\r\n if (baseline) {\r\n arr.cb = baseline;\r\n }\r\n\r\n return arr;\r\n }\r\n return [];\r\n }\r\n}\r\n","import TextCache from \"./textcache\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport MathML from \"./mathml\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\r\n * the associated client-side cache.\r\n */\r\nexport default class Accessibility {\r\n /**\r\n * Static property.\r\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\r\n * @type {TextCache}\r\n */\r\n static get cache() {\r\n return Accessibility._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set accessibility cache.\r\n * @param {TextCahe} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Accessibility._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML strings to its accessible text representation.\r\n * @param {String} mathML - MathML to be converted to accessible text.\r\n * @param {String} [language] - Language of the accessible text. 'en' by default.\r\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\r\n * @return {String} Accessibility text.\r\n */\r\n static mathMLToAccessible(mathML, language, data) {\r\n if (typeof language === \"undefined\") {\r\n language = \"en\";\r\n }\r\n // Check MathML class. If the class is chemistry,\r\n // we add chemistry to data to force accessibility service\r\n // to load chemistry grammar.\r\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\r\n data.mode = \"chemistry\";\r\n }\r\n // Ignore accesibility styles\r\n data.ignoreStyles = true;\r\n let accessibleText = \"\";\r\n\r\n if (Accessibility.cache.get(mathML)) {\r\n accessibleText = Accessibility.cache.get(mathML);\r\n } else {\r\n data.service = \"mathml2accessible\";\r\n data.lang = language;\r\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n if (accessibleJsonResponse.status !== \"error\") {\r\n accessibleText = accessibleJsonResponse.result.text;\r\n Accessibility.cache.populate(mathML, accessibleText);\r\n } else {\r\n accessibleText = StringManager.get(\"error_convert_accessibility\");\r\n }\r\n }\r\n\r\n return accessibleText;\r\n }\r\n}\r\n\r\n/**\r\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\r\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\r\n * @private\r\n * @type {TextCache}\r\n */\r\nAccessibility._cache = new TextCache();\r\n","import Util from \"./util\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport Image from \"./image\";\r\nimport Accessibility from \"./accessibility\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Configuration from \"./configuration\";\r\nimport Constants from \"./constants\";\r\n// eslint-disable-next-line no-unused-vars\r\nimport md5 from \"./md5\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represent a MahML parser. Converts MathML into formulas depending on the\r\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\r\n * in the backend.\r\n */\r\nexport default class Parser {\r\n /**\r\n * Converts a MathML string to an img element.\r\n * @param {Document} creator - Document object to call createElement method.\r\n * @param {string} mathml - MathML code\r\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\r\n * @param {language} language - custom language for accessibility.\r\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\r\n * @static\r\n */\r\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\r\n const imgObject = creator.createElement(\"img\");\r\n imgObject.align = \"middle\";\r\n imgObject.style.maxWidth = \"none\";\r\n let data = wirisProperties || {};\r\n\r\n // Take into account the backend config\r\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\r\n data = { ...wirisEditorProperties, ...data };\r\n\r\n data.mml = mathml;\r\n data.lang = language;\r\n // Request metrics of the generated image.\r\n data.metrics = \"true\";\r\n data.centerbaseline = \"false\";\r\n\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n // Render js params: _wrs_int_wirisProperties contains some js render params.\r\n // Since MathML can support render params, js params should be send only to editor.\r\n\r\n imgObject.className = Configuration.get(\"imageClassName\");\r\n\r\n if (mathml.indexOf('class=\"') !== -1) {\r\n // We check here if the MathML has been created from a customEditor (such chemistry)\r\n // to add custom editor name attribute to img object (if necessary).\r\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\r\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\r\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\r\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\r\n }\r\n\r\n // Performance enabled.\r\n if (\r\n Configuration.get(\"wirisPluginPerformance\") &&\r\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\r\n ) {\r\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\r\n if (result.status === \"warning\") {\r\n // POST call.\r\n // if the mathml is malformed, this function will throw an exception.\r\n try {\r\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n ({ result } = result);\r\n if (result.format === \"png\") {\r\n imgObject.src = `data:image/png;base64,${result.content}`;\r\n } else {\r\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\r\n }\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n Image.setImgSize(imgObject, result.content, true);\r\n\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n if (typeof result.alt === \"undefined\") {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n } else {\r\n imgObject.alt = result.alt;\r\n }\r\n }\r\n } else {\r\n const result = Parser.createImageSrc(mathml, data);\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n imgObject.src = result;\r\n Image.setImgSize(\r\n imgObject,\r\n result,\r\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\r\n );\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n }\r\n }\r\n\r\n if (typeof Parser.observer !== \"undefined\") {\r\n Parser.observer.observe(imgObject);\r\n }\r\n\r\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\r\n imgObject.setAttribute(\"role\", \"math\");\r\n return imgObject;\r\n }\r\n\r\n /**\r\n * Returns the source to showimage service by calling createimage service. The\r\n * output of the createimage service is a URL path pointing to showimage service.\r\n * This method is called when performance is disabled.\r\n * @param {string} mathml - MathML code.\r\n * @param {Object[]} data - data object containing service parameters.\r\n * @returns {string} the showimage path.\r\n */\r\n static createImageSrc(mathml, data) {\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n let result = ServiceProvider.getService(\"createimage\", data);\r\n\r\n if (result.indexOf(\"@BASE@\") !== -1) {\r\n // Replacing '@BASE@' with the base URL of createimage.\r\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\r\n baseParts.pop();\r\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\r\n * this data would be converted as following:\r\n *
\r\n   * MathML code: Image containing the corresponding MathML formulas.\r\n   * MathML code with LaTeX annotation : LaTeX string.\r\n   * 
\r\n * @param {string} code - HTML code containing MathML data.\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\r\n */\r\n static initParse(code, language) {\r\n /* Note: The code inside this function has been inverted.\r\n If you invert again the code then you cannot use correctly LaTeX\r\n in Moodle.\r\n */\r\n code = Parser.initParseSaveMode(code, language);\r\n return Parser.initParseEditMode(code);\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the save mode. Transforms all MathML\r\n * occurrences for it's correspondent image or LaTeX.\r\n * @param {string} code - HTML code to be parsed\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static initParseSaveMode(code, language) {\r\n if (Configuration.get(\"saveMode\")) {\r\n // Converting XML to tags.\r\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\r\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\r\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\r\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\r\n code = Parser.codeImgTransform(code, \"base642showimage\");\r\n }\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the edit mode.\r\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\r\n * be converted into a LaTeX string instead of an image.\r\n * @param {string} code - HTML code containing MathML.\r\n * @returns {string} parsed HTML code.\r\n */\r\n static initParseEditMode(code) {\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\r\n const token = 'encoding=\"LaTeX\">';\r\n // While replacing images with latex, the indexes of the found images changes\r\n // respecting the original code, so this carry is needed.\r\n let carry = 0;\r\n\r\n for (let i = 0; i < imgList.length; i += 1) {\r\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\r\n\r\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\r\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\r\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n\r\n if (mathmlStart === -1) {\r\n mathmlStartToken = ' alt=\"';\r\n mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n }\r\n\r\n if (mathmlStart !== -1) {\r\n mathmlStart += mathmlStartToken.length;\r\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\r\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\r\n let latexStartPosition = mathml.indexOf(token);\r\n\r\n if (latexStartPosition !== -1) {\r\n latexStartPosition += token.length;\r\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\r\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\r\n\r\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\r\n const start = code.substring(0, imgList[i].start + carry);\r\n const end = code.substring(imgList[i].end + carry);\r\n code = start + replaceText + end;\r\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code. The end HTML code is HTML code with embedded images\r\n * or LaTeX formulas created with MathType.
\r\n * By default this method converts the formula images and LaTeX strings in MathML.
\r\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\r\n * @param {string} code - HTML to be parsed\r\n * @returns {string} the HTML code parsed.\r\n */\r\n static endParse(code) {\r\n // Transform LaTeX ocurrences to MathML elements.\r\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\r\n // Transform img elements to MathML elements.\r\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\r\n return codeEndParseSaveMode;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the edit mode.\r\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\r\n * @param {string} code - HTML code to be parsed.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseEditMode(code) {\r\n // Converting LaTeX to images.\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n let startPosition = code.indexOf(\"$$\");\r\n while (startPosition !== -1) {\r\n output += code.substring(endPosition, startPosition);\r\n endPosition = code.indexOf(\"$$\", startPosition + 2);\r\n\r\n if (endPosition !== -1) {\r\n // Before, it was a condition here to execute the next codelines\r\n // 'latex.indexOf('<') == -1'.\r\n // We don't know why it was used, but seems to have a conflict with\r\n // latex formulas that contains '<'.\r\n const latex = code.substring(startPosition + 2, endPosition);\r\n const decodedLatex = Util.htmlEntitiesDecode(latex);\r\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n // Remove hand traces.\r\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\r\n }\r\n output += mathml;\r\n endPosition += 2;\r\n } else {\r\n output += \"$$\";\r\n endPosition = startPosition + 2;\r\n }\r\n\r\n startPosition = code.indexOf(\"$$\", endPosition);\r\n }\r\n\r\n output += code.substring(endPosition, code.length);\r\n code = output;\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the save mode. Converts all\r\n * images into the element determined by the save mode:\r\n * - xml: Parses images formulas into MathML.\r\n * - safeXml: Parses images formulas into safeMAthML\r\n * - base64: Parses images into base64 images.\r\n * - image: Parse images into images (no parsing)\r\n * @param {string} code - HTML code to be parsed\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseSaveMode(code) {\r\n const savemode = Configuration.get(\"saveMode\");\r\n const base64savemode = Configuration.get(\"base64savemode\");\r\n\r\n if (savemode) {\r\n if (savemode === \"safeXml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"xml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\r\n code = Parser.codeImgTransform(code, \"img264\");\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Auxiliar function that builds the data object to send to the showimage endpoint\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object with the data to send to showimage.\r\n */\r\n static createShowImageSrcData(data, language) {\r\n const dataMd5 = {};\r\n const renderParams = [\r\n \"mml\",\r\n \"color\",\r\n \"centerbaseline\",\r\n \"zoom\",\r\n \"dpi\",\r\n \"fontSize\",\r\n \"fontFamily\",\r\n \"defaultStretchy\",\r\n \"backgroundColor\",\r\n \"format\",\r\n ];\r\n renderParams.forEach((param) => {\r\n if (typeof data[param] !== \"undefined\") {\r\n dataMd5[param] = data[param];\r\n }\r\n });\r\n // Data variables to get.\r\n const dataObject = {};\r\n Object.keys(data).forEach((key) => {\r\n // We don't need mathml in this request we try to get cached.\r\n // Only need the formula md5 calculated before.\r\n if (key !== \"mml\") {\r\n dataObject[key] = data[key];\r\n }\r\n });\r\n\r\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\r\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\r\n dataObject.version = Configuration.get(\"version\");\r\n\r\n return dataObject;\r\n }\r\n\r\n /**\r\n * Returns the result to call showimage service with the formula md5 as parameter.\r\n * The result could be:\r\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\r\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object containing showimage response.\r\n */\r\n static createShowImageSrc(data, language) {\r\n const dataObject = this.createShowImageSrcData(data, language);\r\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\r\n return result;\r\n }\r\n\r\n /**\r\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\r\n * or showimage img tags (i.e with showimage.php on src)\r\n * @param {string} code - HTML code\r\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\r\n * @returns {string} html - code transformed.\r\n */\r\n static codeImgTransform(code, mode) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n const pattern = /\") {\r\n endPosition = i + 1;\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n if (endPosition < startPosition) {\r\n // The img tag is stripped.\r\n output += code.substring(startPosition, code.length);\r\n return output;\r\n }\r\n let imgCode = code.substring(startPosition, endPosition);\r\n const imgObject = Util.createObject(imgCode);\r\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n let convertToXml;\r\n let convertToSafeXml;\r\n\r\n if (mode === \"base642showimage\") {\r\n if (xmlCode == null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\r\n output += Util.createObjectCode(imgCode);\r\n } else if (mode === \"img2mathml\") {\r\n if (Configuration.get(\"saveMode\")) {\r\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\r\n convertToXml = true;\r\n convertToSafeXml = true;\r\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\r\n convertToXml = true;\r\n convertToSafeXml = false;\r\n }\r\n }\r\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\r\n } else if (mode === \"img264\") {\r\n if (xmlCode === null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n\r\n const properties = {};\r\n properties.base64 = \"true\";\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\r\n // Metrics.\r\n Image.setImgSize(imgCode, imgCode.src, true);\r\n output += Util.createObjectCode(imgCode);\r\n }\r\n }\r\n output += code.substring(endPosition, code.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML to the corresponding image.\r\n * @param {string} content - string with valid MathML code.\r\n * The MathML code doesn't contain semantics.\r\n * @param {Constants} characters - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * @param {string} language - a valid language code\r\n * in order to generate formula accessibility.\r\n * @returns {string} The input string with all the MathML\r\n * occurrences replaced by the corresponding image.\r\n */\r\n static parseMathmlToImg(content, characters, language) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n // Avoid WIRIS images to be parsed.\r\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else if (imageMathmlAtrribute !== -1) {\r\n // First close tag of img attribute\r\n // If a mathmlAttribute exists should be inside a img tag.\r\n end += content.indexOf(\"/>\", start);\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\r\n let mathml = content.substring(start, end);\r\n mathml =\r\n characters.id === Constants.safeXmlCharacters.id\r\n ? MathML.safeXmlDecode(mathml)\r\n : MathML.mathMLEntities(mathml);\r\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\r\n } else {\r\n output += content.substring(start, end);\r\n }\r\n\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n}\r\n\r\n// Mutation observers to avoid wiris image formulas class be removed.\r\nif (typeof MutationObserver !== \"undefined\") {\r\n const mutationObserver = new MutationObserver((mutations) => {\r\n mutations.forEach((mutation) => {\r\n if (\r\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\r\n mutation.attributeName === \"class\" &&\r\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\r\n ) {\r\n mutation.target.className = Configuration.get(\"imageClassName\");\r\n }\r\n });\r\n });\r\n\r\n Parser.observer = Object.create(mutationObserver);\r\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\r\n // We use own default config.\r\n Parser.observer.observe = function (target) {\r\n Object.getPrototypeOf(this).observe(target, this.Config);\r\n };\r\n}\r\n","/* eslint-disable class-methods-use-this */\r\n/* eslint-disable no-unused-vars */\r\n/* eslint-disable no-extra-semi */\r\n\r\n// The rules above are disabled because we are implementing\r\n// an external interface.\r\n\r\nexport default class EditorListener {\r\n /**\r\n * @classdesc\r\n * Determines if the content of the\r\n * MathType Editor has changes.\r\n * @implements {EditorListeners}\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the content of the editor has changed.\r\n * @type {Boolean}\r\n */\r\n this.isContentChanged = false;\r\n\r\n /**\r\n * Indicates if the listener should be waiting for changes in the editor.\r\n * @type {Boolean}\r\n */\r\n this.waitingForChanges = false;\r\n }\r\n\r\n /**\r\n * Sets {@link EditorListener.isContentChanged} property.\r\n * @param {Boolean} value - The new vlue.\r\n */\r\n setIsContentChanged(value) {\r\n this.isContentChanged = value;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed, false otherwise.\r\n * @return {Boolean}\r\n */\r\n getIsContentChanged() {\r\n return this.isContentChanged;\r\n }\r\n\r\n /**\r\n * Determines if the EditorListener should wait for any changes.\r\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\r\n */\r\n setWaitingForChanges(value) {\r\n this.waitingForChanges = value;\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite.\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n caretPositionChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n clipboardChanged(_editor) {}\r\n\r\n /**\r\n * Determines if the content of an editor has been changed.\r\n * @param {JsEditor} editor - editor object.\r\n */\r\n contentChanged(_editor) {\r\n if (this.waitingForChanges === true && this.isContentChanged === false) {\r\n this.isContentChanged = true;\r\n }\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} editor - The editor instance.\r\n */\r\n styleChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} - The editor instance.\r\n */\r\n transformationReceived(_editor) {}\r\n}\r\n","let wasm;\r\n\r\nconst cachedTextDecoder =\r\n typeof TextDecoder !== \"undefined\"\r\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\r\n : {\r\n decode: () => {\r\n throw Error(\"TextDecoder not available\");\r\n },\r\n };\r\n\r\nif (typeof TextDecoder !== \"undefined\") {\r\n cachedTextDecoder.decode();\r\n}\r\n\r\nlet cachedUint8Memory0 = null;\r\n\r\nfunction getUint8Memory0() {\r\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\r\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\r\n }\r\n return cachedUint8Memory0;\r\n}\r\n\r\nfunction getStringFromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\r\n}\r\n\r\nconst heap = new Array(128).fill(undefined);\r\n\r\nheap.push(undefined, null, true, false);\r\n\r\nlet heap_next = heap.length;\r\n\r\nfunction addHeapObject(obj) {\r\n if (heap_next === heap.length) heap.push(heap.length + 1);\r\n const idx = heap_next;\r\n heap_next = heap[idx];\r\n\r\n heap[idx] = obj;\r\n return idx;\r\n}\r\n\r\nfunction getObject(idx) {\r\n return heap[idx];\r\n}\r\n\r\nfunction dropObject(idx) {\r\n if (idx < 132) return;\r\n heap[idx] = heap_next;\r\n heap_next = idx;\r\n}\r\n\r\nfunction takeObject(idx) {\r\n const ret = getObject(idx);\r\n dropObject(idx);\r\n return ret;\r\n}\r\n\r\nlet WASM_VECTOR_LEN = 0;\r\n\r\nconst cachedTextEncoder =\r\n typeof TextEncoder !== \"undefined\"\r\n ? new TextEncoder(\"utf-8\")\r\n : {\r\n encode: () => {\r\n throw Error(\"TextEncoder not available\");\r\n },\r\n };\r\n\r\nconst encodeString =\r\n typeof cachedTextEncoder.encodeInto === \"function\"\r\n ? function (arg, view) {\r\n return cachedTextEncoder.encodeInto(arg, view);\r\n }\r\n : function (arg, view) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n view.set(buf);\r\n return {\r\n read: arg.length,\r\n written: buf.length,\r\n };\r\n };\r\n\r\nfunction passStringToWasm0(arg, malloc, realloc) {\r\n if (realloc === undefined) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n const ptr = malloc(buf.length, 1) >>> 0;\r\n getUint8Memory0()\r\n .subarray(ptr, ptr + buf.length)\r\n .set(buf);\r\n WASM_VECTOR_LEN = buf.length;\r\n return ptr;\r\n }\r\n\r\n let len = arg.length;\r\n let ptr = malloc(len, 1) >>> 0;\r\n\r\n const mem = getUint8Memory0();\r\n\r\n let offset = 0;\r\n\r\n for (; offset < len; offset++) {\r\n const code = arg.charCodeAt(offset);\r\n if (code > 0x7f) break;\r\n mem[ptr + offset] = code;\r\n }\r\n\r\n if (offset !== len) {\r\n if (offset !== 0) {\r\n arg = arg.slice(offset);\r\n }\r\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\r\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\r\n const ret = encodeString(arg, view);\r\n\r\n offset += ret.written;\r\n }\r\n\r\n WASM_VECTOR_LEN = offset;\r\n return ptr;\r\n}\r\n\r\nfunction isLikeNone(x) {\r\n return x === undefined || x === null;\r\n}\r\n\r\nlet cachedInt32Memory0 = null;\r\n\r\nfunction getInt32Memory0() {\r\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\r\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\r\n }\r\n return cachedInt32Memory0;\r\n}\r\n\r\nlet cachedFloat64Memory0 = null;\r\n\r\nfunction getFloat64Memory0() {\r\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\r\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\r\n }\r\n return cachedFloat64Memory0;\r\n}\r\n\r\nlet cachedBigInt64Memory0 = null;\r\n\r\nfunction getBigInt64Memory0() {\r\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\r\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\r\n }\r\n return cachedBigInt64Memory0;\r\n}\r\n\r\nfunction debugString(val) {\r\n // primitive types\r\n const type = typeof val;\r\n if (type == \"number\" || type == \"boolean\" || val == null) {\r\n return `${val}`;\r\n }\r\n if (type == \"string\") {\r\n return `\"${val}\"`;\r\n }\r\n if (type == \"symbol\") {\r\n const description = val.description;\r\n if (description == null) {\r\n return \"Symbol\";\r\n } else {\r\n return `Symbol(${description})`;\r\n }\r\n }\r\n if (type == \"function\") {\r\n const name = val.name;\r\n if (typeof name == \"string\" && name.length > 0) {\r\n return `Function(${name})`;\r\n } else {\r\n return \"Function\";\r\n }\r\n }\r\n // objects\r\n if (Array.isArray(val)) {\r\n const length = val.length;\r\n let debug = \"[\";\r\n if (length > 0) {\r\n debug += debugString(val[0]);\r\n }\r\n for (let i = 1; i < length; i++) {\r\n debug += \", \" + debugString(val[i]);\r\n }\r\n debug += \"]\";\r\n return debug;\r\n }\r\n // Test for built-in\r\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\r\n let className;\r\n if (builtInMatches.length > 1) {\r\n className = builtInMatches[1];\r\n } else {\r\n // Failed to match the standard '[object ClassName]'\r\n return toString.call(val);\r\n }\r\n if (className == \"Object\") {\r\n // we're a user defined class or Object\r\n // JSON.stringify avoids problems with cycles, and is generally much\r\n // easier than looping through ownProperties of `val`.\r\n try {\r\n return \"Object(\" + JSON.stringify(val) + \")\";\r\n } catch (_) {\r\n return \"Object\";\r\n }\r\n }\r\n // errors\r\n if (val instanceof Error) {\r\n return `${val.name}: ${val.message}\\n${val.stack}`;\r\n }\r\n // TODO we could test for more things here, like `Set`s and `Map`s.\r\n return className;\r\n}\r\n\r\nfunction makeClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n try {\r\n return f(state.a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\r\n state.a = 0;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction makeMutClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n const a = state.a;\r\n state.a = 0;\r\n try {\r\n return f(a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\r\n } else {\r\n state.a = a;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_49(arg0, arg1) {\r\n wasm.__wbindgen_export_4(arg0, arg1);\r\n}\r\n\r\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction handleError(f, args) {\r\n try {\r\n return f.apply(this, args);\r\n } catch (e) {\r\n wasm.__wbindgen_export_6(addHeapObject(e));\r\n }\r\n}\r\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\r\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\r\n}\r\n\r\n/**\r\n */\r\nexport function main_js() {\r\n wasm.main_js();\r\n}\r\n\r\nfunction getArrayU8FromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\r\n}\r\n/**\r\n */\r\nexport const Level = Object.freeze({\r\n Err: 0,\r\n 0: \"Err\",\r\n Warn: 1,\r\n 1: \"Warn\",\r\n Info: 2,\r\n 2: \"Info\",\r\n Debug: 3,\r\n 3: \"Debug\",\r\n});\r\n/**\r\n */\r\nexport class Telemeter {\r\n __destroy_into_raw() {\r\n const ptr = this.__wbg_ptr;\r\n this.__wbg_ptr = 0;\r\n\r\n return ptr;\r\n }\r\n\r\n free() {\r\n const ptr = this.__destroy_into_raw();\r\n wasm.__wbg_telemeter_free(ptr);\r\n }\r\n /**\r\n * @param {any} solution\r\n * @param {any} hosts\r\n * @param {any} config\r\n */\r\n constructor(solution, hosts, config) {\r\n try {\r\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\r\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\r\n var r0 = getInt32Memory0()[retptr / 4 + 0];\r\n var r1 = getInt32Memory0()[retptr / 4 + 1];\r\n var r2 = getInt32Memory0()[retptr / 4 + 2];\r\n if (r2) {\r\n throw takeObject(r1);\r\n }\r\n this.__wbg_ptr = r0 >>> 0;\r\n return this;\r\n } finally {\r\n wasm.__wbindgen_add_to_stack_pointer(16);\r\n }\r\n }\r\n /**\r\n * @param {string} sender_id\r\n * @returns {Promise}\r\n */\r\n identify(sender_id) {\r\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {string} event_type\r\n * @param {any} event_payload\r\n * @returns {Promise}\r\n */\r\n track(event_type, event_payload) {\r\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {any} level\r\n * @param {string} message\r\n * @param {any} payload\r\n * @returns {Promise}\r\n */\r\n log(level, message, payload) {\r\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @returns {Promise}\r\n */\r\n finish() {\r\n const ptr = this.__destroy_into_raw();\r\n const ret = wasm.telemeter_finish(ptr);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {boolean | undefined} [new_debug_status]\r\n */\r\n debug(new_debug_status) {\r\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\r\n }\r\n}\r\n\r\nasync function __wbg_load(module, imports) {\r\n if (typeof Response === \"function\" && module instanceof Response) {\r\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\r\n try {\r\n return await WebAssembly.instantiateStreaming(module, imports);\r\n } catch (e) {\r\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\r\n console.warn(\r\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\r\n e,\r\n );\r\n } else {\r\n throw e;\r\n }\r\n }\r\n }\r\n\r\n const bytes = await module.arrayBuffer();\r\n return await WebAssembly.instantiate(bytes, imports);\r\n } else {\r\n const instance = await WebAssembly.instantiate(module, imports);\r\n\r\n if (instance instanceof WebAssembly.Instance) {\r\n return { instance, module };\r\n } else {\r\n return instance;\r\n }\r\n }\r\n}\r\n\r\nfunction __wbg_get_imports() {\r\n const imports = {};\r\n imports.wbg = {};\r\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\r\n const ret = getStringFromWasm0(arg0, arg1);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\r\n const ret = new Object();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\r\n const ret = getObject(arg0).status;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\r\n const ret = getObject(arg0).headers;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\r\n const ret = new Date();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\r\n const ret = getObject(arg0).getTime();\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\r\n takeObject(arg0);\r\n };\r\n imports.wbg.__wbindgen_is_object = function (arg0) {\r\n const val = getObject(arg0);\r\n const ret = typeof val === \"object\" && val !== null;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\r\n const ret = getObject(arg0).crypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\r\n const ret = getObject(arg0).process;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\r\n const ret = getObject(arg0).versions;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\r\n const ret = getObject(arg0).node;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_string = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"string\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\r\n const ret = getObject(arg0).msCrypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\r\n return handleError(function () {\r\n const ret = module.require;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\r\n const ret = new Uint8Array(arg0 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[arg1 >>> 0];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).next();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\r\n const ret = getObject(arg0).done;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\r\n const ret = getObject(arg0).value;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\r\n const ret = Symbol.iterator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\r\n const ret = getObject(arg0).next;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_function = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"function\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = getObject(arg0).call(getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\r\n const ret = getObject(arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\r\n return handleError(function () {\r\n const ret = self.self;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\r\n return handleError(function () {\r\n const ret = window.window;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\r\n return handleError(function () {\r\n const ret = globalThis.globalThis;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\r\n return handleError(function () {\r\n const ret = global.global;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\r\n const ret = getObject(arg0) === undefined;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\r\n const ret = new Function(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\r\n const ret = Array.isArray(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\r\n const ret = Number.isSafeInteger(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\r\n try {\r\n var state0 = { a: arg0, b: arg1 };\r\n var cb0 = (arg0, arg1) => {\r\n const a = state0.a;\r\n state0.a = 0;\r\n try {\r\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\r\n } finally {\r\n state0.a = a;\r\n }\r\n };\r\n const ret = new Promise(cb0);\r\n return addHeapObject(ret);\r\n } finally {\r\n state0.a = state0.b = 0;\r\n }\r\n };\r\n imports.wbg.__wbindgen_memory = function () {\r\n const ret = wasm.memory;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\r\n const ret = getObject(arg0).buffer;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\r\n const ret = new Uint8Array(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\r\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\r\n };\r\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"string\" ? obj : undefined;\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\r\n return handleError(function (arg0) {\r\n const ret = JSON.stringify(getObject(arg0));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\r\n const ret = getObject(arg0).fetch(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\r\n const ret = fetch(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\r\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\r\n return handleError(function () {\r\n const ret = new AbortController();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\r\n const ret = getObject(arg0).signal;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\r\n getObject(arg0).abort();\r\n };\r\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\r\n const ret = new Error(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) == getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\r\n const v = getObject(arg0);\r\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"number\" ? obj : undefined;\r\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Uint8Array;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof ArrayBuffer;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\r\n const ret = Object.entries(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\r\n const ret = String(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\r\n console.warn(getObject(arg0), getObject(arg1));\r\n };\r\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\r\n const ret = getObject(arg0).readyState;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\r\n console.warn(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\r\n return handleError(function (arg0) {\r\n getObject(arg0).close();\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_new_b9b318679315404f = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\r\n getObject(arg0).binaryType = takeObject(arg1);\r\n };\r\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\r\n console.log(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\r\n console.error(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\r\n console.info(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\r\n const ret = getObject(arg0).document;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\r\n const ret = getObject(arg0).visibilityState;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[getObject(arg1)];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"bigint\";\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\r\n const ret = arg0;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\r\n const ret = getObject(arg0) in getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\r\n const ret = BigInt.asUintN(64, arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) === getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).localStorage;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\r\n const ret = getObject(arg0).navigator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).mediaDevices;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).enumerateDevices();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\r\n const obj = takeObject(arg0).original;\r\n if (obj.cnt-- == 1) {\r\n obj.a = 0;\r\n return true;\r\n }\r\n const ret = false;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\r\n const ret = getObject(arg1).deviceId;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Response;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).randomFillSync(takeObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).getRandomValues(getObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\r\n const v = getObject(arg1);\r\n const ret = typeof v === \"bigint\" ? v : undefined;\r\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\r\n const ret = debugString(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\r\n throw new Error(getStringFromWasm0(arg0, arg1));\r\n };\r\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\r\n const ret = getObject(arg0).then(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\r\n queueMicrotask(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\r\n const ret = getObject(arg0).queueMicrotask;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\r\n const ret = Promise.resolve(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\r\n const ret = getObject(arg1).url;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_send_2860805104507701 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Window;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\r\n return handleError(function () {\r\n const ret = new Headers();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\r\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\r\n return addHeapObject(ret);\r\n };\r\n\r\n return imports;\r\n}\r\n\r\nfunction __wbg_init_memory(imports, maybe_memory) {}\r\n\r\nfunction __wbg_finalize_init(instance, module) {\r\n wasm = instance.exports;\r\n __wbg_init.__wbindgen_wasm_module = module;\r\n cachedBigInt64Memory0 = null;\r\n cachedFloat64Memory0 = null;\r\n cachedInt32Memory0 = null;\r\n cachedUint8Memory0 = null;\r\n\r\n wasm.__wbindgen_start();\r\n return wasm;\r\n}\r\n\r\nfunction initSync(module) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n const imports = __wbg_get_imports();\r\n\r\n __wbg_init_memory(imports);\r\n\r\n if (!(module instanceof WebAssembly.Module)) {\r\n module = new WebAssembly.Module(module);\r\n }\r\n\r\n const instance = new WebAssembly.Instance(module, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nasync function __wbg_init(input) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n if (typeof input === \"undefined\") {\r\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\r\n }\r\n const imports = __wbg_get_imports();\r\n\r\n if (\r\n typeof input === \"string\" ||\r\n (typeof Request === \"function\" && input instanceof Request) ||\r\n (typeof URL === \"function\" && input instanceof URL)\r\n ) {\r\n input = fetch(input);\r\n }\r\n\r\n __wbg_init_memory(imports);\r\n\r\n const { instance, module } = await __wbg_load(await input, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nexport { initSync };\r\nexport default __wbg_init;\r\n","/* eslint-disable-next-line */\r\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\r\n\r\n/**\r\n * @classdesc\r\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\r\n * to Telemetry in a more comfortable and homogeneous way.\r\n */\r\nexport default class Telemeter {\r\n /**\r\n * Inits Telemeter class.\r\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\r\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\r\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\r\n * @param {Object} telemeterAttributes.config - Configuration parameters.\r\n */\r\n static init(telemeterAttributes) {\r\n if (!this.telemeter && !this.waitingForInit) {\r\n this.waitingForInit = true;\r\n init(telemeterAttributes.url)\r\n .then(() => {\r\n this.telemeter = new TelemeterWASM(\r\n telemeterAttributes.solution,\r\n telemeterAttributes.hosts,\r\n telemeterAttributes.config,\r\n );\r\n })\r\n .catch((error) => {\r\n console.log(error);\r\n })\r\n .finally(() => (this.waitingForInit = false));\r\n }\r\n }\r\n\r\n /**\r\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\r\n */\r\n static async finish() {\r\n if (!this.telemeter) return;\r\n\r\n try {\r\n const local_telemeter = this.telemeter;\r\n this.telemeter = undefined;\r\n await local_telemeter.finish();\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Core from \"./core.src\";\r\nimport EditorListener from \"./editorlistener\";\r\nimport Listeners from \"./listeners\";\r\nimport MathML from \"./mathml\";\r\nimport Util from \"./util\";\r\nimport Telemeter from \"./telemeter\";\r\n\r\nexport default class ContentManager {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog, managing the following:\r\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\r\n * - The actions to be done once the modal object has been submitted\r\n * (submitAction} method).\r\n * - The update of the content when the {@link ModalDialog} class is also updated,\r\n * for example when ModalDialog is re-opened.\r\n * - The communication between the {@link ModalDialog} class and itself, if the content\r\n * has been changed (hasChanges} method).\r\n * @constructs\r\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\r\n * create a new instance.\r\n */\r\n constructor(contentManagerAttributes) {\r\n /**\r\n * An object containing MathType editor parameters. See\r\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\r\n * @type {Object}\r\n */\r\n this.editorAttributes = {};\r\n if (\"editorAttributes\" in contentManagerAttributes) {\r\n this.editorAttributes = contentManagerAttributes.editorAttributes;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\r\n }\r\n\r\n /**\r\n * CustomEditors instance. Contains the custom editors.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = null;\r\n if (\"customEditors\" in contentManagerAttributes) {\r\n this.customEditors = contentManagerAttributes.customEditors;\r\n }\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @type {Object}\r\n * @property {String} editor - Editor name. Usually the HTML editor.\r\n * @property {String} mode - Save mode. Xml by default.\r\n * @property {String} version - Plugin version.\r\n */\r\n this.environment = {};\r\n if (\"environment\" in contentManagerAttributes) {\r\n this.environment = contentManagerAttributes.environment;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: environment property missed\");\r\n }\r\n\r\n /**\r\n * ContentManager language.\r\n * @type {String}\r\n */\r\n this.language = \"\";\r\n if (\"language\" in contentManagerAttributes) {\r\n this.language = contentManagerAttributes.language;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: language property missed\");\r\n }\r\n\r\n /**\r\n * {@link EditorListener} instance. Manages the changes inside the editor.\r\n * @type {EditorListener}\r\n */\r\n this.editorListener = new EditorListener();\r\n\r\n /**\r\n * MathType editor instance.\r\n * @type {JsEditor}\r\n */\r\n this.editor = null;\r\n\r\n /**\r\n * Navigator user agent.\r\n * @type {String}\r\n */\r\n this.ua = navigator.userAgent.toLowerCase();\r\n\r\n /**\r\n * Mobile device properties object\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {};\r\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\r\n this.deviceProperties.isIOS = ContentManager.isIOS();\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.toolbar = null;\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.dbclick = null;\r\n\r\n /**\r\n * Instance of the {@link ModalDialog} class associated with the current\r\n * {@link ContentManager} instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialogInstance = null;\r\n\r\n /**\r\n * ContentManager listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * MathML associated to the ContentManager instance.\r\n * @type {String}\r\n */\r\n this.mathML = null;\r\n\r\n /**\r\n * Indicates if the edited element is a new one or not.\r\n * @type {Boolean}\r\n */\r\n this.isNewElement = true;\r\n\r\n /**\r\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n }\r\n\r\n /**\r\n * Adds a new listener to the current {@link ContentManager} instance.\r\n * @param {Object} listener - The listener to be added.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\r\n * instance.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\r\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\r\n */\r\n setModalDialogInstance(modalDialogInstance) {\r\n this.modalDialogInstance = modalDialogInstance;\r\n }\r\n\r\n /**\r\n * Inserts the content into the current {@link ModalDialog} instance updating\r\n * the title and inserting the JavaScript editor.\r\n */\r\n insert() {\r\n // Before insert the editor we update the modal object title to avoid weird render display.\r\n this.updateTitle(this.modalDialogInstance);\r\n this.insertEditor(this.modalDialogInstance);\r\n }\r\n\r\n /**\r\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\r\n * editor's JavaScript is loaded.\r\n */\r\n insertEditor() {\r\n if (ContentManager.isEditorLoaded()) {\r\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\r\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\r\n this.editor.focus();\r\n\r\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\r\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\r\n this.editor.action(\"rtl\");\r\n }\r\n // Setting div in rtl in case of it's activated.\r\n if (this.editor.getEditorModel().isRTL()) {\r\n this.editor.element.style.direction = \"rtl\";\r\n }\r\n\r\n // Editor listener: this object manages the changes logic of editor.\r\n this.editor.getEditorModel().addEditorListener(this.editorListener);\r\n\r\n // iOS events.\r\n if (this.modalDialogInstance.deviceProperties.isIOS) {\r\n setTimeout(function () {\r\n // Make sure the modalDialogInstance is available when the timeout is over\r\n // to avoid throw errors and stop execution.\r\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\r\n }, 400);\r\n\r\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\r\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\r\n }\r\n // Fire onLoad event. Necessary to set the MathML into the editor\r\n // after is loaded.\r\n this.listeners.fire(\"onLoad\", {});\r\n } else {\r\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Initializes the current class by loading MathType script.\r\n */\r\n init() {\r\n if (!ContentManager.isEditorLoaded()) {\r\n this.addEditorAsExternalDependency();\r\n }\r\n }\r\n\r\n /**\r\n * Adds script element to the DOM to include editor externally.\r\n */\r\n addEditorAsExternalDependency() {\r\n const script = document.createElement(\"script\");\r\n script.type = \"text/javascript\";\r\n let editorUrl = Configuration.get(\"editorUrl\");\r\n\r\n // We create an object url for parse url string and work more efficiently.\r\n const anchorElement = document.createElement(\"a\");\r\n\r\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\r\n ContentManager.setProtocolToAnchorElement(anchorElement);\r\n\r\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\r\n\r\n // Load editor URL. We add stats as GET params.\r\n const stats = this.getEditorStats();\r\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\r\n\r\n document.getElementsByTagName(\"head\")[0].appendChild(script);\r\n }\r\n\r\n /**\r\n * Sets the specified url to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\r\n * @param {String} url - URL to set.\r\n */\r\n static setHrefToAnchorElement(anchorElement, url) {\r\n anchorElement.href = url;\r\n }\r\n\r\n /**\r\n * Sets the current protocol to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\r\n */\r\n static setProtocolToAnchorElement(anchorElement) {\r\n // Change to https if necessary.\r\n if (window.location.href.indexOf(\"https://\") === 0) {\r\n // It check if browser is https and configuration is http.\r\n // If this is so, we will replace protocol.\r\n if (anchorElement.protocol === \"http:\") {\r\n anchorElement.protocol = \"https:\";\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the url of the anchor element adding the current port\r\n * if it is needed.\r\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\r\n * @returns {String}\r\n */\r\n static getURLFromAnchorElement(anchorElement) {\r\n // Check protocol and remove port if it's standard.\r\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\r\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\r\n }\r\n\r\n /**\r\n * Returns object with editor stats.\r\n *\r\n * @typedef {Object} EditorStatsObject\r\n * @property {string} editor - Editor name.\r\n * @property {string} mode - Current configuration for formula save mode.\r\n * @property {string} version - Current plugins version.\r\n * @returns {EditorStatsObject}\r\n */\r\n getEditorStats() {\r\n // Editor stats. Use environment property to set it.\r\n const stats = {};\r\n if (\"editor\" in this.environment) {\r\n stats.editor = this.environment.editor;\r\n } else {\r\n stats.editor = \"unknown\";\r\n }\r\n\r\n if (\"mode\" in this.environment) {\r\n stats.mode = this.environment.mode;\r\n } else {\r\n stats.mode = Configuration.get(\"saveMode\");\r\n }\r\n\r\n if (\"version\" in this.environment) {\r\n stats.version = this.environment.version;\r\n } else {\r\n stats.version = Configuration.get(\"version\");\r\n }\r\n\r\n return stats;\r\n }\r\n\r\n /**\r\n * Returns true if device is iOS. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isIOS() {\r\n return (\r\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\r\n // iPad on iOS 13 detection\r\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\r\n );\r\n }\r\n\r\n /**\r\n * Returns true if device is Mobile. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isMobile() {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\r\n }\r\n\r\n /**\r\n * Returns true if editor is loaded. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isEditorLoaded() {\r\n // To know if editor JavaScript is loaded we need to wait until\r\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\r\n return (\r\n window.com &&\r\n window.com.wiris &&\r\n window.com.wiris.jsEditor &&\r\n window.com.wiris.jsEditor.JsEditor &&\r\n window.com.wiris.jsEditor.JsEditor.newInstance\r\n );\r\n }\r\n\r\n /**\r\n * Sets the {@link ContentManager.editor} initial content.\r\n */\r\n setInitialContent() {\r\n if (!this.isNewElement) {\r\n this.setMathML(this.mathML);\r\n }\r\n }\r\n\r\n /**\r\n * Sets a MathML into {@link ContentManager.editor} instance.\r\n * @param {String} mathml - MathML string.\r\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\r\n * False by default.\r\n */\r\n setMathML(mathml, focusDisabled) {\r\n // By default focus is enabled.\r\n if (typeof focusDisabled === \"undefined\") {\r\n focusDisabled = false;\r\n }\r\n // Using setMathML method is not a change produced by the user but for the API\r\n // so we set to false the contentChange property of editorListener.\r\n this.editor.setMathMLWithCallback(mathml, () => {\r\n this.editorListener.setWaitingForChanges(true);\r\n });\r\n\r\n // We need to wait a little until the callback finish.\r\n setTimeout(() => {\r\n this.editorListener.setIsContentChanged(false);\r\n }, 500);\r\n\r\n // In some scenarios - like closing modal object - editor mustn't be focused.\r\n if (!focusDisabled) {\r\n this.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\r\n * {@link ModalDialog.focus}.\r\n */\r\n onFocus() {\r\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\r\n this.editor.focus();\r\n\r\n // On WordPress integration, the focus gets lost right after setting it.\r\n // To fix this, we enforce another focus some milliseconds after this behaviour.\r\n setTimeout(() => {\r\n this.editor.focus();\r\n }, 100);\r\n }\r\n }\r\n\r\n /**\r\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\r\n * Triggered by {@link ModalDialog.submitAction}.\r\n */\r\n submitAction() {\r\n if (!this.editor.isFormulaEmpty()) {\r\n let mathML = this.editor.getMathMLWithSemantics();\r\n // Add class for custom editors.\r\n if (this.customEditors.getActiveEditor() !== null) {\r\n const { toolbar } = this.customEditors.getActiveEditor();\r\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\r\n } else {\r\n // We need - if exists - the editor name from MathML\r\n // class attribute.\r\n Object.keys(this.customEditors.editors).forEach((key) => {\r\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\r\n });\r\n }\r\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\r\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\r\n } else {\r\n this.integrationModel.updateFormula(null);\r\n }\r\n\r\n this.customEditors.disable();\r\n this.integrationModel.notifyWindowClosed();\r\n\r\n // Set disabled focus to prevent lost focus.\r\n this.setEmptyMathML();\r\n this.customEditors.disable();\r\n }\r\n\r\n /**\r\n * Sets an empty MathML as {@link ContentManager.editor} content.\r\n * This will open the MT/CT editor with the hand mode.\r\n * It adds dir rtl in case of it's activated.\r\n */\r\n setEmptyMathML() {\r\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\r\n const isRTL = this.editor.getEditorModel().isRTL();\r\n\r\n if (isMobile || this.integrationModel.forcedHandMode) {\r\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\r\n const mathML = `[]`;\r\n this.setMathML(mathML, true);\r\n } else {\r\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\r\n const mathML = ``;\r\n this.setMathML(mathML, true);\r\n }\r\n }\r\n\r\n /**\r\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\r\n * - Updates the {@link ContentManager.editor} content\r\n * (with an empty MathML or an existing formula),\r\n * - Updates the {@link ContentManager.editor} toolbar.\r\n * - Recovers the the focus.\r\n */\r\n onOpen() {\r\n if (this.isNewElement) {\r\n this.setEmptyMathML();\r\n } else {\r\n this.setMathML(this.mathML);\r\n }\r\n const toolbar = this.updateToolbar();\r\n this.onFocus();\r\n\r\n if (this.deviceProperties.isIOS) {\r\n const zoom = document.documentElement.clientWidth / window.innerWidth;\r\n\r\n if (zoom !== 1) {\r\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\r\n this.setKeyboardMode();\r\n }\r\n }\r\n\r\n const trigger = this.dbclick ? \"formula\" : \"button\";\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\r\n toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalOpen\", {});\r\n\r\n if (this.integrationModel.forcedHandMode) {\r\n this.hideHandModeButton();\r\n\r\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\r\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\r\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Change Editor in keyboard mode when is loaded\r\n */\r\n setKeyboardMode() {\r\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\r\n if (wrsEditor) {\r\n wrsEditor.classList.remove(\"wrs_handOpen\");\r\n wrsEditor.classList.remove(\"wrs_disablePalette\");\r\n } else {\r\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Hides the hand <-> keyboard mode switch.\r\n *\r\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\r\n * any change on those classes will make this code stop working properly.\r\n *\r\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\r\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\r\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\r\n */\r\n hideHandModeButton(forced = true) {\r\n if (this.handSwitchHidden) {\r\n return; // hand <-> keyboard button already hidden.\r\n }\r\n\r\n // \"Open hand mode\" button takes a little bit to be available.\r\n // This selector gets the hand <-> keyboard mode switch\r\n const handModeButtonSelector =\r\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\r\n\r\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\r\n // We use an observer to ensure that the button it hidden as soon as it appears.\r\n if (forced) {\r\n const mutationInstance = new MutationObserver((mutations) => {\r\n const handModeButton = document.querySelector(handModeButtonSelector);\r\n if (handModeButton) {\r\n handModeButton.hidden = true;\r\n this.handSwitchHidden = true;\r\n mutationInstance.disconnect();\r\n }\r\n });\r\n mutationInstance.observe(document.body, {\r\n attributes: true,\r\n childList: true,\r\n characterData: true,\r\n subtree: true,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\r\n *\r\n * @param {String} mathml The original KeyBoard MathML\r\n * @param {Object} editor The editor object.\r\n */\r\n async openHandOnKeyboardMathML(mathml, editor) {\r\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\r\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\r\n await new Promise((resolve) => {\r\n editor.setMathMLWithCallback(mathml, resolve);\r\n });\r\n\r\n // We wait until the hand editor object exists.\r\n await this.waitForHand(editor);\r\n\r\n // Logic to get the hand traces and open the formula in hand mode.\r\n // This logic comes from the editor.\r\n const handEditor = editor.hand;\r\n editor.handTemporalMathML = editor.getMathML();\r\n const handCoordinates = editor.editorModel.getHandStrokes();\r\n handEditor.setStrokes(handCoordinates);\r\n handEditor.fitStrokes(true);\r\n editor.openHand();\r\n }\r\n\r\n /**\r\n * Waits until the hand editor object exists.\r\n * @param {Obect} editor The editor object.\r\n */\r\n async waitForHand(editor) {\r\n while (!editor.hand) {\r\n await new Promise((resolve) => setTimeout(resolve, 100));\r\n }\r\n }\r\n\r\n /**\r\n * Sets the correct toolbar depending if exist other custom toolbars\r\n * at the same time (e.g: Chemistry).\r\n */\r\n updateToolbar() {\r\n this.updateTitle(this.modalDialogInstance);\r\n const customEditor = this.customEditors.getActiveEditor();\r\n\r\n let toolbar;\r\n if (customEditor) {\r\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\r\n\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n }\r\n } else {\r\n toolbar = this.getToolbar();\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n this.customEditors.disable();\r\n }\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\r\n * sets the custom editor title. Otherwise sets the default title.\r\n */\r\n updateTitle() {\r\n const customEditor = this.customEditors.getActiveEditor();\r\n if (customEditor) {\r\n this.modalDialogInstance.setTitle(customEditor.title);\r\n } else {\r\n this.modalDialogInstance.setTitle(\"MathType\");\r\n }\r\n }\r\n\r\n /**\r\n * Returns the editor toolbar, depending on the configuration local or server side.\r\n * @returns {String} - Toolbar identifier.\r\n */\r\n getToolbar() {\r\n let toolbar = \"general\";\r\n if (\"toolbar\" in this.editorAttributes) {\r\n ({ toolbar } = this.editorAttributes);\r\n }\r\n // TODO: Change global integration variable for integration custom toolbar.\r\n if (toolbar === \"general\") {\r\n // eslint-disable-next-line camelcase\r\n toolbar =\r\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\r\n ? \"general\"\r\n : _wrs_int_wirisProperties.toolbar;\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Sets the current {@link ContentManager.editor} instance toolbar.\r\n * @param {String} toolbar - The toolbar name.\r\n */\r\n setToolbar(toolbar) {\r\n this.toolbar = toolbar;\r\n this.editor.setParams({ toolbar: this.toolbar });\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setCustomHeaders(headers) {\r\n let headersObj = {};\r\n\r\n // We control that we only get String or Object as the input.\r\n if (typeof headers === \"object\") {\r\n headersObj = headers;\r\n } else if (typeof headers === \"string\") {\r\n headersObj = Util.convertStringToObject(headers);\r\n }\r\n\r\n this.editor.setParams({ customHeaders: headersObj });\r\n return headersObj;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed. The logic of the changes\r\n * is delegated to {@link EditorListener} class.\r\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\r\n */\r\n hasChanges() {\r\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n // Code to detect Esc event.\r\n // There should be only one element with class name 'wrs_pressed' at the same time.\r\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\r\n if (list.length === 0) {\r\n this.modalDialogInstance.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.modalDialogInstance.submitButton) {\r\n // Focus is on OK button.\r\n this.editor.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\r\n // Focus is on minimize button (_).\r\n this.modalDialogInstance.closeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\r\n // Focus on cancel button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n this.modalDialogInstance.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\r\n // Focus is on X button.\r\n this.modalDialogInstance.minimizeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\r\n // Focus on help button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n const element = document.querySelector('[title=\"Manual\"]');\r\n element.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n } else {\r\n // There should be only one element with class name 'wrs_formulaDisplay'.\r\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\r\n // Focus is on formuladisplay.\r\n this.modalDialogInstance.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * A custom editor is MathType editor with a different\r\n * @typedef {Object} CustomEditor\r\n * @property {String} CustomEditor.name - Custom editor name.\r\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\r\n * @property {String} CustomEditor.icon - Custom editor icon.\r\n * @property {String} CustomEditor.confVariable - Configuration property to manage\r\n * the availability of the custom editor.\r\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\r\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\r\n */\r\n\r\nexport default class CustomEditors {\r\n /**\r\n * @classdesc\r\n * This class represents the MathType custom editors manager.\r\n * A custom editor is MathType editor with a custom toolbar.\r\n * This class associates a {@link CustomEditor} to:\r\n * - It's own formulas\r\n * - A custom toolbar\r\n * - An icon to open it from a HTML editor.\r\n * - A tooltip for the icon.\r\n * - A global variable to enable or disable it globally.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * The custom editors.\r\n * @type {Array.}\r\n */\r\n\r\n this.editors = [];\r\n /**\r\n * The active editor name.\r\n * @type {String}\r\n */\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Adds a {@link CustomEditor} to editors array.\r\n * @param {String} editorName - The editor name.\r\n * @param {CustomEditor} editorParams - The custom editor parameters.\r\n */\r\n addEditor(editorName, editorParams) {\r\n const customEditor = {};\r\n customEditor.name = editorParams.name;\r\n customEditor.toolbar = editorParams.toolbar;\r\n customEditor.icon = editorParams.icon;\r\n customEditor.confVariable = editorParams.confVariable;\r\n customEditor.title = editorParams.title;\r\n customEditor.tooltip = editorParams.tooltip;\r\n this.editors[editorName] = customEditor;\r\n }\r\n\r\n /**\r\n * Enables a {@link CustomEditor}.\r\n * @param {String} customEditorName - The custom editor name.\r\n */\r\n enable(customEditorName) {\r\n this.activeEditor = customEditorName;\r\n }\r\n\r\n /**\r\n * Disables a {@link CustomEditor}.\r\n */\r\n disable() {\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Returns the active editor.\r\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\r\n */\r\n getActiveEditor() {\r\n if (this.activeEditor !== \"default\") {\r\n return this.editors[this.activeEditor];\r\n }\r\n return null;\r\n }\r\n}\r\n","/**\r\n * Represents the configuration properties generated from the frontend (JavaScript variables).\r\n * @type {Object}\r\n * @property {string} imageClassName - Default MathType formula image class.\r\n * @property {string} imageClassName - Default MathType CAS image class.\r\n * @ignore\r\n */\r\nconst jsProperties = {\r\n imageCustomEditorName: \"data-custom-editor\",\r\n imageClassName: \"Wirisformula\",\r\n CASClassName: \"Wiriscas\",\r\n};\r\nexport default jsProperties;\r\n","export default class Event {\r\n /**\r\n * @classdesc\r\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\r\n *\r\n * ```js\r\n * let customEvent = new Event();\r\n * customEvent.properties = {};\r\n *\r\n * let listeners = new Listeners();\r\n * listeners.newListener(eventName, callback);\r\n *\r\n * listeners.fire(eventName, customEvent) *\r\n * ```\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the event should be cancelled.\r\n * @type {Boolean}\r\n */\r\n\r\n this.cancelled = false;\r\n /**\r\n * Indicates if the event should be prevented.\r\n * @type {Boolean}\r\n */\r\n this.defaultPrevented = false;\r\n }\r\n\r\n /**\r\n * Cancels the event.\r\n */\r\n cancel() {\r\n this.cancelled = true;\r\n }\r\n\r\n /**\r\n * Prevents the default action.\r\n */\r\n preventDefault() {\r\n this.defaultPrevented = true;\r\n }\r\n}\r\n","import IntegrationModel from \"./integrationmodel\";\r\n\r\n/**\r\n\r\n */\r\nexport default class PopUpMessage {\r\n /**\r\n * @classdesc\r\n * This class represents a dialog message overlaying a DOM element in order to\r\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\r\n * o canceled. In this last case a callback function should be called.\r\n * @constructs\r\n * @param {Object} popupMessageAttributes - Object containing popup properties.\r\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\r\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\r\n * methods for close and cancel actions.\r\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\r\n */\r\n constructor(popupMessageAttributes) {\r\n /**\r\n * Element to be overlaid when the popup appears.\r\n */\r\n this.overlayElement = popupMessageAttributes.overlayElement;\r\n\r\n this.callbacks = popupMessageAttributes.callbacks;\r\n\r\n /**\r\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\r\n */\r\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\r\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\r\n\r\n /**\r\n * HTMLElement to display the popup message, close button and cancel button.\r\n */\r\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n this.message.id = \"wrs_popupmessage\";\r\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\r\n this.message.setAttribute(\"role\", \"dialog\");\r\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\r\n const paragraph = document.createElement(\"p\");\r\n const text = document.createTextNode(popupMessageAttributes.strings.message);\r\n paragraph.appendChild(text);\r\n paragraph.id = \"description_txt\";\r\n this.message.appendChild(paragraph);\r\n\r\n /**\r\n * HTML element overlaying the overlayElement.\r\n */\r\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\r\n // We create a overlay that close popup message on click in there\r\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n /**\r\n * HTML element containing cancel and close buttons.\r\n */\r\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\r\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\r\n this.buttonArea.id = \"wrs_popup_button_area\";\r\n\r\n // Close button arguments.\r\n const buttonSubmitArguments = {\r\n class: \"wrs_button_accept\",\r\n innerHTML: popupMessageAttributes.strings.submitString,\r\n id: \"wrs_popup_accept_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-close-button\",\r\n };\r\n\r\n /**\r\n * Close button arguments.\r\n */\r\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\r\n this.buttonArea.appendChild(this.closeButton);\r\n\r\n // Cancel button arguments.\r\n const buttonCancelArguments = {\r\n class: \"wrs_button_cancel\",\r\n innerHTML: popupMessageAttributes.strings.cancelString,\r\n id: \"wrs_popup_cancel_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\r\n };\r\n\r\n /**\r\n * Cancel button.\r\n */\r\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\r\n this.buttonArea.appendChild(this.cancelButton);\r\n }\r\n\r\n /**\r\n * This method create a button with arguments and return button dom object\r\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\r\n * @param {String} parameters.id - Button id.\r\n * @param {String} parameters.class - Button class name.\r\n * @param {String} parameters.innerHTML - Button innerHTML text.\r\n * @param {Object} callback- Callback method to call on click event.\r\n * @returns {HTMLElement} HTML button.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createButton(parameters, callback) {\r\n let element = {};\r\n element = document.createElement(\"button\");\r\n element.setAttribute(\"id\", parameters.id);\r\n element.setAttribute(\"class\", parameters.class);\r\n element.innerHTML = parameters.innerHTML;\r\n element.addEventListener(\"click\", callback);\r\n if (parameters[\"data-testid\"]) {\r\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\r\n }\r\n\r\n return element;\r\n }\r\n\r\n /**\r\n * Shows the popupmessage containing a message, and two buttons\r\n * to cancel the action or close the modal dialog.\r\n */\r\n show() {\r\n if (this.overlayWrapper.style.display !== \"block\") {\r\n // Clear focus with blur for prevent press any key.\r\n document.activeElement.blur();\r\n this.overlayWrapper.style.display = \"block\";\r\n this.closeButton.focus();\r\n } else {\r\n this.overlayWrapper.style.display = \"none\";\r\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\r\n }\r\n }\r\n\r\n /**\r\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\r\n * A callback method is called (if defined). For example a method to focus the overlaid element.\r\n */\r\n cancelAction() {\r\n this.overlayWrapper.style.display = \"none\";\r\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\r\n this.callbacks.cancelCallback();\r\n // Set temporal image to null to prevent loading\r\n // an existent formula when starting one from scratch. Make focus come back too.\r\n // IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n }\r\n\r\n /**\r\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\r\n * For example to close the overlaid element.\r\n */\r\n closeAction() {\r\n this.cancelAction();\r\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\r\n this.callbacks.closeCallback();\r\n }\r\n IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Code to detect Esc event.\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n this.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.closeButton) {\r\n this.cancelButton.focus();\r\n } else {\r\n this.closeButton.focus();\r\n }\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * This module provides protection against external focus management scripts\r\n * that might interfere with the MathType editor modal.\r\n */\r\n\r\n/**\r\n * focusProtection function creates and returns methods to prevent external scripts from\r\n * interfering with the focus of the MathType modal dialog.\r\n *\r\n * @returns {Object} An object with protect and unprotect methods.\r\n */\r\nconst focusProtection = () => {\r\n /**\r\n * Initialize focus protection on the given modal element.\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to protect\r\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\r\n * @param {HTMLElement} editorElement - The editor element inside the modal\r\n */\r\n const protect = (modalElement, overlayElement, editorElement) => {\r\n if (!modalElement || !overlayElement || !editorElement) {\r\n console.warn(\"FocusProtection: Missing required elements\");\r\n return;\r\n }\r\n\r\n // Apply the focus protection\r\n overrideFocusBehavior(modalElement, editorElement);\r\n };\r\n\r\n /**\r\n * Apply focus protection by overriding focus-related methods\r\n *\r\n * @param {HTMLElement} modalElement - The modal element\r\n * @param {HTMLElement} editorElement - The editor element to keep focused\r\n * @private\r\n */\r\n const overrideFocusBehavior = (modalElement, editorElement) => {\r\n // Store original focus methods to be able to restore them\r\n const originalElementFocus = HTMLElement.prototype.focus;\r\n const originalElementBlur = HTMLElement.prototype.blur;\r\n\r\n // Override the focus method for all elements\r\n HTMLElement.prototype.focus = function (...args) {\r\n // If the modal is open and this is not part of the modal, prevent focus\r\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\r\n // If some external script is trying to focus another element, prevent it\r\n // and restore focus to the editor\r\n if (editorElement) {\r\n // Use the original focus method to avoid infinite recursion\r\n originalElementFocus.apply(editorElement, args);\r\n }\r\n return;\r\n }\r\n\r\n // Otherwise, allow the focus to happen\r\n originalElementFocus.apply(this, args);\r\n };\r\n\r\n // Store the methods to remove them when the modal is closed\r\n modalElement.originalElementFocus = originalElementFocus;\r\n modalElement.originalElementBlur = originalElementBlur;\r\n };\r\n\r\n /**\r\n * Remove focus protection from the modal\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to unprotect\r\n */\r\n const unprotect = (modalElement) => {\r\n if (!modalElement) {\r\n return;\r\n }\r\n\r\n // Restore original focus methods\r\n if (modalElement.originalElementFocus) {\r\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\r\n delete modalElement.originalElementFocus;\r\n }\r\n\r\n if (modalElement.originalElementBlur) {\r\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\r\n delete modalElement.originalElementBlur;\r\n }\r\n };\r\n\r\n return {\r\n protect,\r\n unprotect,\r\n };\r\n};\r\n\r\nexport default focusProtection;\r\n","// eslint-disable-next-line max-classes-per-file\r\nimport PopUpMessage from \"./popupmessage\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport Listeners from \"./listeners\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Telemeter from \"./telemeter\";\r\nimport IntegrationModel from \"./integrationmodel\";\r\nimport Core from \"./core.src\";\r\nimport focusProtection from \"./focusprotection\";\r\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\r\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\r\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\r\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\r\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\r\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\r\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\r\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\r\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\r\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\r\nconst { unprotect, protect } = focusProtection();\r\n\r\n/**\r\n * @typedef {Object} DeviceProperties\r\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\r\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\r\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\r\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\r\n * False otherwise.\r\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\r\n * False otherwise.\r\n */\r\n\r\nexport default class ModalDialog {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog. The modal dialog admits\r\n * a {@link ContentManager} instance to manage the content of the dialog.\r\n * @constructs\r\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\r\n */\r\n constructor(modalDialogAttributes) {\r\n this.attributes = modalDialogAttributes;\r\n\r\n // Metrics.\r\n const ua = navigator.userAgent.toLowerCase();\r\n const isAndroid = ua.indexOf(\"android\") > -1;\r\n const isIOS = ContentManager.isIOS();\r\n this.iosSoftkeyboardOpened = false;\r\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\r\n this.iosDivHeight = `auto`;\r\n\r\n const deviceWidth = window.outerWidth;\r\n const deviceHeight = window.outerHeight;\r\n\r\n const landscape = deviceWidth > deviceHeight;\r\n const portrait = deviceWidth < deviceHeight;\r\n\r\n // TODO: Detect isMobile without using editor metrics.\r\n const isLandscape = landscape && this.attributes.height > deviceHeight;\r\n const isPortrait = portrait && this.attributes.width > deviceWidth;\r\n const isMobile = ContentManager.isMobile();\r\n\r\n // Obtain number of current instance.\r\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\r\n\r\n // Device object properties.\r\n\r\n /**\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {\r\n orientation: landscape ? \"landscape\" : \"portrait\",\r\n isAndroid,\r\n isIOS,\r\n isMobile,\r\n isDesktop: !isMobile && !isIOS && !isAndroid,\r\n };\r\n\r\n this.properties = {\r\n created: false,\r\n state: \"\",\r\n previousState: \"\",\r\n position: { bottom: 0, right: 10 },\r\n size: { height: 338, width: 580 },\r\n };\r\n\r\n /**\r\n * Object to keep website's style before change it on lock scroll for mobile devices.\r\n * @type {Object}\r\n * @property {String} bodyStylePosition - Previous body style position.\r\n * @property {String} bodyStyleOverflow - Previous body style overflow.\r\n * @property {String} htmlStyleOverflow - Previous body style overflow.\r\n * @property {String} windowScrollX - Previous window's scroll Y.\r\n * @property {String} windowScrollY - Previous window's scroll X.\r\n */\r\n this.websiteBeforeLockParameters = null;\r\n\r\n let attributes = {};\r\n attributes.class = \"wrs_modal_overlay\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.overlay = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title_bar\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.titleBar = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.title = Util.createElement(\"div\", attributes);\r\n this.title.innerHTML = \"offline\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_close_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"close\");\r\n attributes.style = {};\r\n this.closeDiv = Util.createElement(\"a\", attributes);\r\n this.closeDiv.setAttribute(\"role\", \"button\");\r\n this.closeDiv.setAttribute(\"tabindex\", 3);\r\n // Apply styles and events after the creation as createElement doesn't process them correctly\r\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\r\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\r\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\r\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\r\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\r\n // To identifiy the element in automated testing\r\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_stack_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"exit_fullscreen\");\r\n this.stackDiv = Util.createElement(\"a\", attributes);\r\n this.stackDiv.setAttribute(\"role\", \"button\");\r\n this.stackDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\r\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\r\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\r\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\r\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\r\n // To identifiy the element in automated testing\r\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_maximize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"fullscreen\");\r\n this.maximizeDiv = Util.createElement(\"a\", attributes);\r\n this.maximizeDiv.setAttribute(\"role\", \"button\");\r\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\r\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\r\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\r\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\r\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\r\n // To identifiy the element in automated testing\r\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_minimize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"minimize\");\r\n this.minimizeDiv = Util.createElement(\"a\", attributes);\r\n this.minimizeDiv.setAttribute(\"role\", \"button\");\r\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\r\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\r\n // To identify the element in automated testing\r\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_dialogContainer\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.role = \"dialog\";\r\n this.container = Util.createElement(\"div\", attributes);\r\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_wrapper\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.wrapper = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_content_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.contentContainer = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_controls\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.controls = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_buttons_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.buttonContainer = Util.createElement(\"div\", attributes);\r\n\r\n // Buttons: all button must be created using createSubmitButton method.\r\n this.submitButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_accept\"),\r\n class: \"wrs_modal_button_accept\",\r\n innerHTML: StringManager.get(\"accept\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-insert-button\",\r\n },\r\n this.submitAction.bind(this),\r\n );\r\n\r\n this.cancelButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_cancel\"),\r\n class: \"wrs_modal_button_cancel\",\r\n innerHTML: StringManager.get(\"cancel\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cancel-button\",\r\n },\r\n this.cancelAction.bind(this),\r\n );\r\n\r\n this.contentManager = null;\r\n\r\n // Overlay popup.\r\n const popupStrings = {\r\n cancelString: StringManager.get(\"cancel\"),\r\n submitString: StringManager.get(\"close\"),\r\n message: StringManager.get(\"close_modal_warning\"),\r\n };\r\n\r\n const callbacks = {\r\n closeCallback: () => {\r\n this.close(\"mtc_close\");\r\n },\r\n cancelCallback: () => {\r\n this.focus();\r\n },\r\n };\r\n\r\n const popupupProperties = {\r\n overlayElement: this.container,\r\n callbacks,\r\n strings: popupStrings,\r\n };\r\n\r\n this.popup = new PopUpMessage(popupupProperties);\r\n\r\n /**\r\n * Indicates if directionality of the modal dialog is RTL. false by default.\r\n * @type {Boolean}\r\n */\r\n this.rtl = false;\r\n if (\"rtl\" in this.attributes) {\r\n this.rtl = this.attributes.rtl;\r\n }\r\n\r\n // Event handlers need modal instance context.\r\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\r\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\r\n }\r\n\r\n /**\r\n * This method sets an ContentManager instance to ModalDialog. ContentManager\r\n * manages the logic of ModalDialog content: submit, update, close and changes.\r\n * @param {ContentManager} contentManager - ContentManager instance.\r\n */\r\n setContentManager(contentManager) {\r\n this.contentManager = contentManager;\r\n }\r\n\r\n /**\r\n * Returns the modal contentElement object.\r\n * @returns {ContentManager} the instance of the ContentManager class.\r\n */\r\n getContentManager() {\r\n return this.contentManager;\r\n }\r\n\r\n /**\r\n * This method is called when the modal object has been submitted. Calls\r\n * contentElement submitAction method - if exists - and closes the modal\r\n * object. No logic about the content should be placed here,\r\n * contentElement.submitAction is the responsible of the content logic.\r\n */\r\n async submitAction() {\r\n if (typeof this.contentManager.submitAction !== \"undefined\") {\r\n this.contentManager.submitAction();\r\n }\r\n\r\n await this.close(\"mtc_insert\");\r\n }\r\n\r\n /**\r\n * Performs the cancel action.\r\n * If there are no changes in the content, it closes the modal.\r\n * Otherwise, it shows a pop-up message to confirm the cancel action.\r\n * @returns {Promise} - A promise that resolves when the modal is closed.\r\n */\r\n async cancelAction() {\r\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\r\n IntegrationModel.setActionsOnCancelButtons();\r\n await this.close(\"mtc_close\");\r\n } else {\r\n this.showPopUpMessage();\r\n }\r\n }\r\n\r\n /**\r\n * Returns a button element.\r\n * @param {Object} properties - Input button properties.\r\n * @param {String} properties.class - Input button class.\r\n * @param {String} properties.innerHTML - Input button innerHTML.\r\n * @param {Object} callback - Callback function associated to click event.\r\n * @returns {HTMLButtonElement} The button element.\r\n *\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createSubmitButton(properties, callback) {\r\n class SubmitButton {\r\n constructor() {\r\n this.element = document.createElement(\"button\");\r\n this.element.id = properties.id;\r\n this.element.className = properties.class;\r\n this.element.innerHTML = properties.innerHTML;\r\n this.element.dataset.testid = properties[\"data-testid\"];\r\n Util.addEvent(this.element, \"click\", callback);\r\n }\r\n\r\n getElement() {\r\n return this.element;\r\n }\r\n }\r\n return new SubmitButton(properties, callback).getElement();\r\n }\r\n\r\n /**\r\n * Creates the modal window object inserting a contentElement object.\r\n */\r\n create() {\r\n /* Modal Window Structure\r\n _____________________________________________________________________________________\r\n |wrs_modal_dialog_Container |\r\n | _________________________________________________________________________________ |\r\n | |title_bar minimize_button stack_button close_button | |\r\n | |_______________________________________________________________________________| |\r\n | |wrapper | |\r\n | | _____________________________________________________________________________ | |\r\n | | |content | | |\r\n | | | | | |\r\n | | | | | |\r\n | | |___________________________________________________________________________| | |\r\n | | _____________________________________________________________________________ | |\r\n | | |controls | | |\r\n | | | ___________________________________ | | |\r\n | | | |buttonContainer | | | |\r\n | | | | _______________________________ | | | |\r\n | | | | |button_accept | button_cancel| | | | |\r\n | | | |_|_____________ |______________|_| | | |\r\n | | |___________________________________________________________________________| | |\r\n | |_______________________________________________________________________________| |\r\n |___________________________________________________________________________________| */\r\n\r\n this.titleBar.appendChild(this.closeDiv);\r\n this.titleBar.appendChild(this.stackDiv);\r\n this.titleBar.appendChild(this.maximizeDiv);\r\n this.titleBar.appendChild(this.minimizeDiv);\r\n this.titleBar.appendChild(this.title);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n this.container.appendChild(this.titleBar);\r\n }\r\n\r\n this.wrapper.appendChild(this.contentContainer);\r\n this.wrapper.appendChild(this.controls);\r\n\r\n this.controls.appendChild(this.buttonContainer);\r\n\r\n this.buttonContainer.appendChild(this.submitButton);\r\n this.buttonContainer.appendChild(this.cancelButton);\r\n\r\n this.container.appendChild(this.wrapper);\r\n\r\n // Check if browser has scrollBar before modal has modified.\r\n this.recalculateScrollBar();\r\n\r\n document.body.appendChild(this.container);\r\n document.body.appendChild(this.overlay);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n // Desktop.\r\n this.createModalWindowDesktop();\r\n this.createResizeButtons();\r\n\r\n this.addListeners();\r\n // Maximize window only when the configuration is set and the device is not iOS or Android.\r\n if (Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n } else if (this.deviceProperties.isAndroid) {\r\n this.createModalWindowAndroid();\r\n } else if (this.deviceProperties.isIOS) {\r\n this.createModalWindowIos();\r\n }\r\n\r\n if (this.contentManager != null) {\r\n this.contentManager.insert(this);\r\n }\r\n\r\n this.properties.open = true;\r\n this.properties.created = true;\r\n\r\n // Checks language directionality.\r\n if (this.isRTL()) {\r\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\r\n this.container.className += \" wrs_modal_rtl\";\r\n }\r\n }\r\n\r\n /**\r\n * Creates a button in the modal object to resize it.\r\n */\r\n createResizeButtons() {\r\n // This is a definition of Resize Button Bottom-Right.\r\n this.resizerBR = document.createElement(\"div\");\r\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\r\n this.resizerBR.innerHTML = \"โ—ข\";\r\n // To identifiy the element in automated testing\r\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\r\n // This is a definition of Resize Button Top-Left.\r\n this.resizerTL = document.createElement(\"div\");\r\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\r\n // To identifiy the element in automated testing\r\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\r\n // Append resize buttons to modal.\r\n this.container.appendChild(this.resizerBR);\r\n this.titleBar.appendChild(this.resizerTL);\r\n // Add events to resize on click and drag.\r\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\r\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\r\n }\r\n\r\n /**\r\n * Initialize variables for Bottom-Right resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateBR(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, false);\r\n }\r\n\r\n /**\r\n * Initialize variables for Top-Left resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateTL(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, true);\r\n }\r\n\r\n /**\r\n * Common method to initialize variables at resize.\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n initializeResizeProperties(mouseEvent, leftOption) {\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n this.resizeDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Save Initial state of modal to compare on drag and obtain the difference.\r\n this.initialWidth = parseInt(this.container.style.width, 10);\r\n this.initialHeight = parseInt(this.container.style.height, 10);\r\n if (!leftOption) {\r\n this.initialRight = parseInt(this.container.style.right, 10);\r\n this.initialBottom = parseInt(this.container.style.bottom, 10);\r\n } else {\r\n this.leftScale = true;\r\n }\r\n if (!this.initialRight) {\r\n this.initialRight = 0;\r\n }\r\n if (!this.initialBottom) {\r\n this.initialBottom = 0;\r\n }\r\n // Disable mouse events on editor when we start to drag modal.\r\n document.body.style[\"user-select\"] = \"none\";\r\n }\r\n\r\n /**\r\n * This method opens the modal window, restoring the previous state, position and metrics,\r\n * if exists. By default the modal object opens in stack mode.\r\n */\r\n open() {\r\n // Removing close class.\r\n this.removeClass(\"wrs_closed\");\r\n // Hiding keyboard for mobile devices.\r\n const { isIOS } = this.deviceProperties;\r\n const { isAndroid } = this.deviceProperties;\r\n const { isMobile } = this.deviceProperties;\r\n if (isIOS || isAndroid || isMobile) {\r\n // Restore scale to 1.\r\n this.restoreWebsiteScale();\r\n this.lockWebsiteScroll();\r\n // Due to editor wait we need to wait until editor focus.\r\n setTimeout(() => {\r\n this.hideKeyboard();\r\n }, 400);\r\n }\r\n\r\n // New modal window. He need to create the whole object.\r\n if (!this.properties.created) {\r\n this.create();\r\n } else {\r\n // Previous state closed. Open method can be called even the previous state is open,\r\n // for example updating the content of the modal object.\r\n if (!this.properties.open) {\r\n this.properties.open = true;\r\n\r\n // Restoring the previous open state: if the modal object has been closed\r\n // re-open it should preserve the state and the metrics.\r\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\r\n this.restoreState();\r\n }\r\n }\r\n\r\n // Maximize window only when the configuration is set and the device is not iOs or Android.\r\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n\r\n // In iOS we need to recalculate the size of the modal object because\r\n // iOS keyboard is a float div which can overlay the modal object.\r\n if (this.deviceProperties.isIOS) {\r\n this.iosSoftkeyboardOpened = false;\r\n }\r\n }\r\n\r\n if (!ContentManager.isEditorLoaded()) {\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.displayEditor();\r\n });\r\n this.contentManager.addListener(listener);\r\n } else {\r\n this.displayEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Prepares and displays the editor in the modal.\r\n *\r\n * This method is responsible for displaying the MathType editor inside the modal container.\r\n *\r\n * For Moodle environments, it applies focus protection to prevent external scripts\r\n * from hijacking focus away from the editor while it's open. This is particularly\r\n * important in Moodle which may have its own focus management scripts.\r\n * @returns {void}\r\n */\r\n displayEditor() {\r\n if (this.contentManager.integrationModel.isMoodle) {\r\n protect(this.container, this.overlay, this.contentContainer);\r\n }\r\n\r\n // Initialize and open the editor using the contentManager.\r\n this.contentManager.onOpen(this);\r\n }\r\n\r\n /**\r\n * Closes the modal.\r\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\r\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\r\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\r\n * @returns {Promise} A promise that resolves when the modal is closed.\r\n */\r\n async close(trigger) {\r\n // Remove focus protection before closing\r\n unprotect(this.container);\r\n\r\n this.removeClass(\"wrs_maximized\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.removeClass(\"wrs_stack\");\r\n this.addClass(\"wrs_closed\");\r\n this.saveModalProperties();\r\n this.unlockWebsiteScroll();\r\n this.properties.open = false;\r\n\r\n if (trigger) {\r\n try {\r\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\r\n toolbar: this.contentManager.toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\r\n }\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalClose\", {});\r\n }\r\n\r\n /**\r\n * Closes modal window and destroys the object.\r\n */\r\n destroy() {\r\n // Remove focus protection before destroying\r\n unprotect(this.container);\r\n\r\n // Close modal window.\r\n this.close();\r\n // Remove listeners and destroy the object.\r\n this.removeListeners();\r\n this.overlay.remove();\r\n this.container.remove();\r\n // Reset properties to allow open again.\r\n this.properties.created = false;\r\n }\r\n\r\n /**\r\n * Sets the website scale to one.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n restoreWebsiteScale() {\r\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\r\n // Let the equal symbols in order to search and make meta's final content.\r\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\r\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\r\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\r\n const contentAttr = viewportelement.getAttribute(\"content\");\r\n // If it exists, we need to maintain old values and put our values.\r\n if (contentAttr) {\r\n const attrArray = contentAttr.split(\",\");\r\n let finalContentMeta = \"\";\r\n const oldAttrs = [];\r\n for (let i = 0; i < attrArray.length; i += 1) {\r\n let isAttrToUpdate = false;\r\n let j = 0;\r\n while (!isAttrToUpdate && j < contentAttrs.length) {\r\n if (attrArray[i].indexOf(contentAttrs[j])) {\r\n isAttrToUpdate = true;\r\n }\r\n j += 1;\r\n }\r\n\r\n if (!isAttrToUpdate) {\r\n oldAttrs.push(attrArray[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < contentAttrs.length; i += 1) {\r\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\r\n finalContentMeta += i === 0 ? attr : `,${attr}`;\r\n }\r\n\r\n for (let i = 0; i < oldAttrs.length; i += 1) {\r\n finalContentMeta += `,${oldAttrs[i]}`;\r\n }\r\n viewportelement.setAttribute(\"content\", finalContentMeta);\r\n // It needs to set to empty because setAttribute refresh only when attribute is different.\r\n viewportelement.setAttribute(\"content\", \"\");\r\n viewportelement.setAttribute(\"content\", contentAttr);\r\n } else {\r\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\r\n viewportelement.removeAttribute(\"content\");\r\n }\r\n };\r\n\r\n if (!viewportmeta) {\r\n viewportmeta = document.createElement(\"meta\");\r\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n viewportmeta.remove();\r\n } else {\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n }\r\n }\r\n\r\n /**\r\n * Locks website scroll for mobile devices.\r\n */\r\n lockWebsiteScroll() {\r\n this.websiteBeforeLockParameters = {\r\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\r\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\r\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\r\n windowScrollX: window.scrollX,\r\n windowScrollY: window.scrollY,\r\n };\r\n }\r\n\r\n /**\r\n * Unlocks website scroll for mobile devices.\r\n */\r\n unlockWebsiteScroll() {\r\n if (this.websiteBeforeLockParameters) {\r\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\r\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\r\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\r\n const { windowScrollX } = this.websiteBeforeLockParameters;\r\n const { windowScrollY } = this.websiteBeforeLockParameters;\r\n window.scrollTo(windowScrollX, windowScrollY);\r\n this.websiteBeforeLockParameters = null;\r\n }\r\n }\r\n\r\n /**\r\n * Util function to known if browser is IE11.\r\n * @returns {Boolean} true if the browser is IE11. false otherwise.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n isIE11() {\r\n if (\r\n navigator.userAgent.search(\"Msie/\") >= 0 ||\r\n navigator.userAgent.search(\"Trident/\") >= 0 ||\r\n navigator.userAgent.search(\"Edge/\") >= 0\r\n ) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns if the current language type is RTL.\r\n * @return {Boolean} true if current language is RTL. false otherwise.\r\n */\r\n isRTL() {\r\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\r\n return true;\r\n }\r\n return this.rtl;\r\n }\r\n\r\n /**\r\n * Adds a class to all modal ModalDialog DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n addClass(className) {\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.titleBar, className);\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.container, className);\r\n Util.addClass(this.contentContainer, className);\r\n Util.addClass(this.stackDiv, className);\r\n Util.addClass(this.minimizeDiv, className);\r\n Util.addClass(this.maximizeDiv, className);\r\n Util.addClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Remove a class from all modal DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n removeClass(className) {\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.titleBar, className);\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.container, className);\r\n Util.removeClass(this.contentContainer, className);\r\n Util.removeClass(this.stackDiv, className);\r\n Util.removeClass(this.minimizeDiv, className);\r\n Util.removeClass(this.maximizeDiv, className);\r\n Util.removeClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Create modal dialog for desktop.\r\n */\r\n createModalWindowDesktop() {\r\n this.addClass(\"wrs_modal_desktop\");\r\n this.stack();\r\n }\r\n\r\n /**\r\n * Create modal dialog for non android devices.\r\n */\r\n createModalWindowAndroid() {\r\n this.addClass(\"wrs_modal_android\");\r\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Create modal dialog for iOS devices.\r\n */\r\n createModalWindowIos() {\r\n this.addClass(\"wrs_modal_ios\");\r\n // Refresh the size when the orientation is changed.\r\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Restore previous state, position and size of previous stacked modal dialog.\r\n */\r\n restoreState() {\r\n if (this.properties.state === \"maximized\") {\r\n // Reset states for prevent return to stack state.\r\n this.maximize();\r\n } else if (this.properties.state === \"minimized\") {\r\n // Reset states for prevent return to stack state.\r\n this.properties.state = this.properties.previousState;\r\n this.properties.previousState = \"\";\r\n this.minimize();\r\n } else {\r\n this.stack();\r\n }\r\n }\r\n\r\n /**\r\n * Stacks the modal object.\r\n */\r\n stack() {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"stack\";\r\n this.removeClass(\"wrs_maximized\");\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.addClass(\"wrs_stack\");\r\n\r\n // Change maximize/minimize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n this.restoreModalProperties();\r\n\r\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\r\n this.setResizeButtonsVisibility();\r\n }\r\n\r\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Minimizes the modal object.\r\n */\r\n minimize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n this.title.style.cursor = \"pointer\";\r\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\r\n this.stack();\r\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n // Setting css to prevent important tag into css style.\r\n this.container.style.height = \"30px\";\r\n this.container.style.width = \"250px\";\r\n this.container.style.bottom = \"0px\";\r\n this.container.style.right = \"10px\";\r\n\r\n this.removeListeners();\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"minimized\";\r\n this.setResizeButtonsVisibility();\r\n this.minimizeDiv.title = StringManager.get(\"maximize\");\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.removeClass(\"wrs_stack\");\r\n } else {\r\n this.removeClass(\"wrs_maximized\");\r\n }\r\n this.addClass(\"wrs_minimized\");\r\n\r\n // Change minimize icon to maximize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n }\r\n }\r\n\r\n /**\r\n * Maximizes the modal object.\r\n */\r\n maximize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n if (this.properties.state !== \"maximized\") {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"maximized\";\r\n }\r\n // Don't permit resize on maximize mode.\r\n this.setResizeButtonsVisibility();\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.container.style.left = null;\r\n this.container.style.top = null;\r\n this.removeClass(\"wrs_stack\");\r\n }\r\n\r\n this.addClass(\"wrs_maximized\");\r\n\r\n // Change maximize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n // Set size to 80% screen with a max size.\r\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\r\n if (this.container.clientHeight > 700) {\r\n this.container.style.height = \"700px\";\r\n }\r\n if (this.container.clientWidth > 1200) {\r\n this.container.style.width = \"1200px\";\r\n }\r\n\r\n // Setting modal position in center on screen.\r\n const { innerHeight } = window;\r\n const { innerWidth } = window;\r\n const { offsetHeight } = this.container;\r\n const { offsetWidth } = this.container;\r\n const bottom = innerHeight / 2 - offsetHeight / 2;\r\n const right = innerWidth / 2 - offsetWidth / 2;\r\n\r\n this.setPosition(bottom, right);\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n this.recalculateSize();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Expand again the modal object from a minimized state.\r\n */\r\n reExpand() {\r\n if (this.properties.state === \"minimized\") {\r\n if (this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n this.stack();\r\n }\r\n this.title.style.cursor = \"\";\r\n }\r\n }\r\n\r\n /**\r\n * Sets modal size.\r\n * @param {Number} height - Height of the ModalDialog\r\n * @param {Number} width - Width of the ModalDialog.\r\n */\r\n setSize(height, width) {\r\n this.container.style.height = `${height}px`;\r\n this.container.style.width = `${width}px`;\r\n this.recalculateSize();\r\n }\r\n\r\n /**\r\n * Sets modal position using bottom and right style attributes.\r\n * @param {number} bottom - bottom attribute.\r\n * @param {number} right - right attribute.\r\n */\r\n setPosition(bottom, right) {\r\n this.container.style.bottom = `${bottom}px`;\r\n this.container.style.right = `${right}px`;\r\n }\r\n\r\n /**\r\n * Saves position and size parameters of and open ModalDialog. This attributes\r\n * are needed to restore it on re-open.\r\n */\r\n saveModalProperties() {\r\n // Saving values of modal only when modal is in stack state.\r\n if (this.properties.state === \"stack\") {\r\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\r\n this.properties.position.right = parseInt(this.container.style.right, 10);\r\n this.properties.size.width = parseInt(this.container.style.width, 10);\r\n this.properties.size.height = parseInt(this.container.style.height, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Restore ModalDialog position and size parameters.\r\n */\r\n restoreModalProperties() {\r\n if (this.properties.state === \"stack\") {\r\n // Restoring Bottom and Right values from last modal.\r\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\r\n // Restoring Height and Left values from last modal.\r\n this.setSize(this.properties.size.height, this.properties.size.width);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the modal dialog initial size.\r\n */\r\n recalculateSize() {\r\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\r\n }\r\n\r\n /**\r\n * Enable or disable visibility of resize buttons in modal window depend on state.\r\n */\r\n setResizeButtonsVisibility() {\r\n if (this.properties.state === \"stack\") {\r\n this.resizerTL.style.visibility = \"visible\";\r\n this.resizerBR.style.visibility = \"visible\";\r\n } else {\r\n this.resizerTL.style.visibility = \"hidden\";\r\n this.resizerBR.style.visibility = \"hidden\";\r\n }\r\n }\r\n\r\n /**\r\n * Makes an object draggable adding mouse and touch events.\r\n */\r\n addListeners() {\r\n // Button events (maximize, minimize, stack and close).\r\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\r\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\r\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\r\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\r\n this.maximizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n }\r\n },\r\n true,\r\n );\r\n this.stackDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.minimizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.closeDiv.addEventListener(\"keypress\", (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n });\r\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\r\n\r\n // Overlay events (close).\r\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n // Mouse events.\r\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\r\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\r\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\r\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\r\n // Key events.\r\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\r\n }\r\n\r\n /**\r\n * Removes draggable events from an object.\r\n */\r\n removeListeners() {\r\n // Mouse events.\r\n Util.removeEvent(window, \"mousedown\", this.startDrag);\r\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\r\n Util.removeEvent(window, \"mousemove\", this.drag);\r\n Util.removeEvent(window, \"resize\", this.onWindowResize);\r\n // Key events.\r\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\r\n }\r\n\r\n /**\r\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n * @return {Object} With the X and Y coordinates.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n eventClient(mouseEvent) {\r\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\r\n const client = {\r\n X: mouseEvent.changedTouches[0].clientX,\r\n Y: mouseEvent.changedTouches[0].clientY,\r\n };\r\n return client;\r\n }\r\n const client = {\r\n X: mouseEvent.clientX,\r\n Y: mouseEvent.clientY,\r\n };\r\n return client;\r\n }\r\n\r\n /**\r\n * Start drag function: set the object dragDataObject with the draggable\r\n * object offsets coordinates.\r\n * when drag starts (on touchstart or mousedown events).\r\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\r\n */\r\n startDrag(mouseEvent) {\r\n if (this.properties.state === \"minimized\") {\r\n return;\r\n }\r\n if (mouseEvent.target === this.title) {\r\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\r\n // Save first click mouse point on screen.\r\n this.dragDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Reset last drag position when start drag.\r\n this.lastDrag = {\r\n x: \"0px\",\r\n y: \"0px\",\r\n };\r\n // Init right and bottom values for window modal if it isn't exist.\r\n if (this.container.style.right === \"\") {\r\n this.container.style.right = \"0px\";\r\n }\r\n if (this.container.style.bottom === \"\") {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n\r\n // Needed for IE11 for apply disabled mouse events on editor because\r\n // internet explorer needs a dynamic object to apply this property.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = 'relative';\r\n }\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n // Obtain screen limits for prevent overflow.\r\n this.limitWindow = this.getLimitWindow();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Updates dragDataObject with the draggable object coordinates when\r\n * the draggable object is being moved.\r\n * @param {MouseEvent} mouseEvent - The mouse event.\r\n */\r\n drag(mouseEvent) {\r\n if (this.dragDataObject) {\r\n mouseEvent.preventDefault();\r\n // Calculate max and min between actual mouse position and limit of screeen.\r\n // It restric the movement of modal into window.\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\r\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\r\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\r\n // Subtract limit with first position to obtain relative pixels increment\r\n // to the anchor point.\r\n const dragX = `${limitX - this.dragDataObject.x}px`;\r\n const dragY = `${limitY - this.dragDataObject.y}px`;\r\n // Save last valid position of modal before window overflow.\r\n this.lastDrag = {\r\n x: dragX,\r\n y: dragY,\r\n };\r\n // This move modal with hardware acceleration.\r\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\r\n }\r\n if (this.resizeDataObject) {\r\n const { innerWidth } = window;\r\n const { innerHeight } = window;\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\r\n if (limitX < 0) {\r\n limitX = 0;\r\n }\r\n\r\n if (limitY < 0) {\r\n limitY = 0;\r\n }\r\n\r\n let scaleMultiplier;\r\n if (this.leftScale) {\r\n scaleMultiplier = -1;\r\n } else {\r\n scaleMultiplier = 1;\r\n }\r\n\r\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\r\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\r\n if (!this.leftScale) {\r\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\r\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\r\n } else {\r\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\r\n this.container.style.width = \"580px\";\r\n }\r\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\r\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\r\n } else {\r\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\r\n this.container.style.height = \"338px\";\r\n }\r\n }\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boundaries of actual window to limit modal movement.\r\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\r\n */\r\n getLimitWindow() {\r\n // Obtain dimensions of window page.\r\n const maxWidth = window.innerWidth;\r\n const maxHeight = window.innerHeight;\r\n\r\n // Calculate relative position of mouse point into window.\r\n const { offsetHeight } = this.container;\r\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\r\n const contStyleRight = parseInt(this.container.style.right, 10);\r\n\r\n const { pageXOffset } = window;\r\n const dragY = this.dragDataObject.y;\r\n const dragX = this.dragDataObject.x;\r\n\r\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\r\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\r\n\r\n // Calculate limits with sizes of window, modal and mouse position.\r\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\r\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\r\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\r\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\r\n const minPointer = { x: minPointerX, y: minPointerY };\r\n const maxPointer = { x: maxPointerX, y: maxPointerY };\r\n return { minPointer, maxPointer };\r\n }\r\n\r\n /**\r\n * Returns the scrollbar width size of browser\r\n * @returns {Number} The scrollbar width.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getScrollBarWidth() {\r\n // Create a paragraph with full width of page.\r\n const inner = document.createElement(\"p\");\r\n inner.style.width = \"100%\";\r\n inner.style.height = \"200px\";\r\n\r\n // Create a hidden div to compare sizes.\r\n const outer = document.createElement(\"div\");\r\n outer.style.position = \"absolute\";\r\n outer.style.top = \"0px\";\r\n outer.style.left = \"0px\";\r\n outer.style.visibility = \"hidden\";\r\n outer.style.width = \"200px\";\r\n outer.style.height = \"150px\";\r\n outer.style.overflow = \"hidden\";\r\n outer.appendChild(inner);\r\n\r\n document.body.appendChild(outer);\r\n const widthOuter = inner.offsetWidth;\r\n\r\n // Change type overflow of paragraph for measure scrollbar.\r\n outer.style.overflow = \"scroll\";\r\n let widthInner = inner.offsetWidth;\r\n\r\n // If measure is the same, we compare with internal div.\r\n if (widthOuter === widthInner) {\r\n widthInner = outer.clientWidth;\r\n }\r\n document.body.removeChild(outer);\r\n\r\n return widthOuter - widthInner;\r\n }\r\n\r\n /**\r\n * Set the dragDataObject to null.\r\n */\r\n stopDrag() {\r\n // Due to we have multiple events that call this function, we need only to execute\r\n // the next modifiers one time,\r\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\r\n if (this.dragDataObject || this.resizeDataObject) {\r\n // If modal doesn't change, it's not necessary to set position with interpolation.\r\n this.container.style.transform = \"\";\r\n if (this.dragDataObject) {\r\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\r\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\r\n }\r\n // We make focus on editor after drag modal windows to prevent lose focus.\r\n this.focus();\r\n // Restore mouse events on iframe.\r\n // this.iframe.style['pointer-events'] = 'auto';\r\n document.body.style[\"user-select\"] = \"\";\r\n // Restore static state of iframe if we use Internet Explorer.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = null;\r\n }\r\n // Active text select event.\r\n Util.removeClass(document.body, \"wrs_noselect\");\r\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\r\n }\r\n this.dragDataObject = null;\r\n this.resizeDataObject = null;\r\n this.initialWidth = null;\r\n this.leftScale = null;\r\n }\r\n\r\n /**\r\n * Recalculates scale for modal when resize browser window.\r\n */\r\n onWindowResize() {\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n }\r\n\r\n /**\r\n * Triggers keyboard events:\r\n * - Tab key tab to go to submit button.\r\n * - Esc key to close the modal dialog.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Popupmessage is not oppened.\r\n if (this.popup.overlayWrapper.style.display !== \"block\") {\r\n // Code to detect Esc event\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n if (this.properties.open) {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.cancelButton) {\r\n this.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.submitButton) {\r\n this.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n } else {\r\n // Popupmessage oppened.\r\n this.popup.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating position for modal dialog when the browser is resized.\r\n */\r\n recalculatePosition() {\r\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\r\n if (parseInt(this.container.style.right, 10) < 0) {\r\n this.container.style.right = \"0px\";\r\n }\r\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\r\n if (parseInt(this.container.style.bottom, 10) < 0) {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating scale for modal when the browser is resized.\r\n */\r\n recalculateScale() {\r\n let sizeModified = false;\r\n\r\n if (parseInt(this.container.style.width, 10) > 580) {\r\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.width = \"580px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (parseInt(this.container.style.height, 10) > 338) {\r\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.height = \"338px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (sizeModified) {\r\n this.recalculateSize();\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating width of browser scroll bar.\r\n */\r\n recalculateScrollBar() {\r\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\r\n if (this.hasScrollBar) {\r\n this.scrollbarWidth = this.getScrollBarWidth();\r\n } else {\r\n this.scrollbarWidth = 0;\r\n }\r\n }\r\n\r\n /**\r\n * Hide soft keyboards on iOS devices.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n hideKeyboard() {\r\n // iOS keyboard can't be detected or hide directly from JavaScript.\r\n // So, this method simulates that user focus a text input and blur\r\n // the selection.\r\n const inputField = document.createElement(\"input\");\r\n this.container.appendChild(inputField);\r\n inputField.focus();\r\n inputField.blur();\r\n // Is removed to not see it.\r\n inputField.remove();\r\n }\r\n\r\n /**\r\n * Focus to contentManager object.\r\n */\r\n focus() {\r\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\r\n this.contentManager.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Returns true when the device is on portrait mode.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n portraitMode() {\r\n return window.innerHeight > window.innerWidth;\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is opened.\r\n */\r\n handleOpenedIosSoftkeyboard() {\r\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\r\n }\r\n }\r\n this.iosSoftkeyboardOpened = true;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is closed.\r\n */\r\n handleClosedIosSoftkeyboard() {\r\n this.iosSoftkeyboardOpened = false;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on iOS.\r\n */\r\n orientationChangeIosSoftkeyboard() {\r\n if (this.iosSoftkeyboardOpened) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\r\n }\r\n } else {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on Android.\r\n */\r\n orientationChangeAndroidSoftkeyboard() {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Set iframe container height.\r\n * @param {Number} height - New height.\r\n */\r\n setContainerHeight(height) {\r\n this.iosDivHeight = height;\r\n this.wrapper.style.height = height;\r\n }\r\n\r\n /**\r\n * Check content of editor before close action.\r\n */\r\n showPopUpMessage() {\r\n if (this.properties.state === \"minimized\") {\r\n this.stack();\r\n }\r\n this.popup.show();\r\n }\r\n\r\n /**\r\n * Sets the title of the modal dialog.\r\n * @param {String} title - Modal dialog title.\r\n */\r\n setTitle(title) {\r\n this.title.innerHTML = title;\r\n }\r\n\r\n /**\r\n * Returns the id of an element, adding the instance number to\r\n * the element class name:\r\n * className --> className[idNumber]\r\n * @param {String} className - The element class name.\r\n * @returns {String} A string appending the instance id to the className.\r\n */\r\n getElementId(className) {\r\n return `${className}[${this.instanceId}]`;\r\n }\r\n}\r\n","/* eslint-disable */\r\nvar polyfills;\r\nexport default polyfills;\r\n\r\n// Polyfills.\r\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\r\nif (!String.prototype.codePointAt) {\r\n (function () {\r\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\r\n var codePointAt = function (position) {\r\n if (this == null) {\r\n throw TypeError();\r\n }\r\n var string = String(this);\r\n var size = string.length;\r\n // `ToInteger`\r\n var index = position ? Number(position) : 0;\r\n if (index != index) {\r\n // better `isNaN`\r\n index = 0;\r\n }\r\n // Account for out-of-bounds indices:\r\n if (index < 0 || index >= size) {\r\n return undefined;\r\n }\r\n // Get the first code unit\r\n var first = string.charCodeAt(index);\r\n var second;\r\n if (\r\n // check if itโ€™s the start of a surrogate pair\r\n first >= 0xd800 &&\r\n first <= 0xdbff && // high surrogate\r\n size > index + 1 // there is a next code unit\r\n ) {\r\n second = string.charCodeAt(index + 1);\r\n if (second >= 0xdc00 && second <= 0xdfff) {\r\n // low surrogate\r\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\r\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\r\n }\r\n }\r\n return first;\r\n };\r\n if (Object.defineProperty) {\r\n Object.defineProperty(String.prototype, \"codePointAt\", {\r\n value: codePointAt,\r\n configurable: true,\r\n writable: true,\r\n });\r\n } else {\r\n String.prototype.codePointAt = codePointAt;\r\n }\r\n })();\r\n}\r\n\r\n// Object.assign polyfill.\r\nif (typeof Object.assign != \"function\") {\r\n // Must be writable: true, enumerable: false, configurable: true\r\n Object.defineProperty(Object, \"assign\", {\r\n value: function assign(target, varArgs) {\r\n // .length of function is 2\r\n \"use strict\";\r\n if (target == null) {\r\n // TypeError if undefined or null\r\n throw new TypeError(\"Cannot convert undefined or null to object\");\r\n }\r\n\r\n var to = Object(target);\r\n\r\n for (var index = 1; index < arguments.length; index++) {\r\n var nextSource = arguments[index];\r\n\r\n if (nextSource != null) {\r\n // Skip over if undefined or null\r\n for (var nextKey in nextSource) {\r\n // Avoid bugs when hasOwnProperty is shadowed\r\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\r\n to[nextKey] = nextSource[nextKey];\r\n }\r\n }\r\n }\r\n }\r\n return to;\r\n },\r\n writable: true,\r\n configurable: true,\r\n });\r\n}\r\n\r\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\r\nif (!Array.prototype.includes) {\r\n Object.defineProperty(Array.prototype, \"includes\", {\r\n value: function (searchElement, fromIndex) {\r\n if (this == null) {\r\n throw new TypeError('\"this\" s null or is not defined');\r\n }\r\n\r\n // 1. Let O be ? ToObject(this value).\r\n var o = Object(this);\r\n\r\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\r\n var len = o.length >>> 0;\r\n\r\n // 3. if len is 0, return false.\r\n if (len === 0) {\r\n return false;\r\n }\r\n\r\n // 4. Let n be ? ToInteger(fromIndex).\r\n // (if fromIndex is undefinedo, this step generates the value 0.)\r\n var n = fromIndex | 0;\r\n\r\n // 5. if n โ‰ฅ 0, then\r\n // a. Let k be n.\r\n // 6. Else n < 0,\r\n // a. Let k be len + n.\r\n // b. if k < 0, let k be 0.\r\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\r\n\r\n function sameValueZero(x, y) {\r\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\r\n }\r\n\r\n // 7. Repeat while k < len\r\n while (k < len) {\r\n // a. let element k be the result of ? Get(O, ! ToString(k)).\r\n // b. if SameValueZero(searchElement, elementK) is true, return true.\r\n if (sameValueZero(o[k], searchElement)) {\r\n return true;\r\n }\r\n // c. Increase k by 1.\r\n k++;\r\n }\r\n\r\n // 8. Return false\r\n return false;\r\n },\r\n });\r\n}\r\n\r\nif (!String.prototype.includes) {\r\n String.prototype.includes = function (search, start) {\r\n \"use strict\";\r\n\r\n if (search instanceof RegExp) {\r\n throw TypeError(\"first argument must not be a RegExp\");\r\n }\r\n if (start === undefined) {\r\n start = 0;\r\n }\r\n return this.indexOf(search, start) !== -1;\r\n };\r\n}\r\n\r\nif (!String.prototype.startsWith) {\r\n Object.defineProperty(String.prototype, \"startsWith\", {\r\n value: function (search, rawPos) {\r\n var pos = rawPos > 0 ? rawPos | 0 : 0;\r\n return this.substring(pos, pos + search.length) === search;\r\n },\r\n });\r\n}\r\n","import Parser from \"./parser\";\r\nimport Util from \"./util\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport CustomEditors from \"./customeditors\";\r\nimport Configuration from \"./configuration\";\r\nimport jsProperties from \"./jsvariables\";\r\nimport Event from \"./event\";\r\nimport Listeners from \"./listeners\";\r\nimport Image from \"./image\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport ModalDialog from \"./modal\";\r\nimport Telemeter from \"./telemeter\";\r\nimport \"./polyfills\";\r\nimport \"../styles/styles.css\";\r\n\r\n/**\r\n * @typedef {Object} CoreProperties\r\n * @property {ServiceProviderProperties} serviceProviderProperties\r\n * - The ServiceProvider class properties. *\r\n */\r\nexport default class Core {\r\n /**\r\n * @classdesc\r\n * This class represents MathType integration Core, managing the following:\r\n * - Integration initialization.\r\n * - Event managing.\r\n * - Insertion of formulas into the edit area.\r\n * ```js\r\n * let core = new Core();\r\n * core.addListener(listener);\r\n * core.language = 'en';\r\n *\r\n * // Initializing Core class.\r\n * core.init(configurationService);\r\n * ```\r\n * @constructs\r\n * Core constructor.\r\n * @param {CoreProperties}\r\n */\r\n constructor(coreProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. 'en' by default.\r\n * @type {String}\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Edit mode, 'images' by default. Admits the following values:\r\n * - images\r\n * - latex\r\n * @type {String}\r\n */\r\n this.editMode = \"images\";\r\n\r\n /**\r\n * Modal dialog instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialog = null;\r\n\r\n /**\r\n * The instance of {@link CustomEditors}. By default\r\n * the only custom editor is the Chemistry editor.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = new CustomEditors();\r\n\r\n /**\r\n * Chemistry editor.\r\n * @type {CustomEditor}\r\n */\r\n const chemEditorParams = {\r\n name: \"Chemistry\",\r\n toolbar: \"chemistry\",\r\n icon: \"chem.png\",\r\n confVariable: \"chemEnabled\",\r\n title: \"ChemType\",\r\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\r\n };\r\n\r\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @typedef IntegrationEnvironment\r\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\r\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\r\n * @property {String} IntegrationEnvironment.version - Integration version.\r\n *\r\n */\r\n\r\n /**\r\n * The environment properties object.\r\n * @type {IntegrationEnvironment}\r\n */\r\n this.environment = {};\r\n\r\n /**\r\n * @typedef EditionProperties\r\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\r\n * False otherwise.\r\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\r\n * Null if the formula is new.\r\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\r\n * @property {Range} editionProperties.range - The range that contains the image element.\r\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\r\n */\r\n\r\n /**\r\n * The properties of the current edition process.\r\n * @type {EditionProperties}\r\n */\r\n this.editionProperties = {};\r\n\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.temporalImage = null;\r\n this.editionProperties.latexRange = null;\r\n this.editionProperties.range = null;\r\n this.editionProperties.editionStartTime = null;\r\n\r\n /**\r\n * The {@link IntegrationModel} instance.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n\r\n /**\r\n * The {@link ContentManager} instance.\r\n * @type {ContentManager}\r\n */\r\n this.contentManager = null;\r\n\r\n /**\r\n * The current browser.\r\n * @type {String}\r\n */\r\n this.browser = (() => {\r\n const ua = navigator.userAgent;\r\n let browser = \"none\";\r\n if (ua.search(\"Edge/\") >= 0) {\r\n browser = \"EDGE\";\r\n } else if (ua.search(\"Chrome/\") >= 0) {\r\n browser = \"CHROME\";\r\n } else if (ua.search(\"Trident/\") >= 0) {\r\n browser = \"IE\";\r\n } else if (ua.search(\"Firefox/\") >= 0) {\r\n browser = \"FIREFOX\";\r\n } else if (ua.search(\"Safari/\") >= 0) {\r\n browser = \"SAFARI\";\r\n }\r\n return browser;\r\n })();\r\n\r\n /**\r\n * Plugin listeners.\r\n * @type {Array.}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * Service provider properties.\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = {};\r\n if (\"serviceProviderProperties\" in coreProperties) {\r\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\r\n } else {\r\n throw new Error(\"serviceProviderProperties property missing.\");\r\n }\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Core listeners.\r\n * @private\r\n * @type {Listeners}\r\n */\r\n static get globalListeners() {\r\n return Core._globalListeners;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set core listeners.\r\n * @param {Listeners} value - The property value.\r\n * @ignore\r\n */\r\n static set globalListeners(value) {\r\n Core._globalListeners = value;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * True when Core.init was called. Otherwise, false.\r\n * @private\r\n * @type {Boolean}\r\n */\r\n static get initialized() {\r\n return Core._initialized;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\r\n * @ignore\r\n */\r\n static set initialized(value) {\r\n Core._initialized = value;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.integrationModel} property.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.environment} property.\r\n * @param {IntegrationEnvironment} integrationEnvironment -\r\n * The {@link IntegrationEnvironment} object.\r\n */\r\n setEnvironment(integrationEnvironment) {\r\n if (\"editor\" in integrationEnvironment) {\r\n this.environment.editor = integrationEnvironment.editor;\r\n }\r\n if (\"mode\" in integrationEnvironment) {\r\n this.environment.mode = integrationEnvironment.mode;\r\n }\r\n if (\"version\" in integrationEnvironment) {\r\n this.environment.version = integrationEnvironment.version;\r\n }\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setHeaders(headers) {\r\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\r\n Configuration.set(\"customHeaders\", headerObject);\r\n }\r\n\r\n /**\r\n * Returns the current {@link ModalDialog} instance.\r\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\r\n */\r\n getModalDialog() {\r\n return this.modalDialog;\r\n }\r\n\r\n /**\r\n * Inits the {@link Core} class, doing the following:\r\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\r\n * - Updates {@link Configuration} class with the previous configuration properties.\r\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\r\n * - Loads language strings.\r\n * - Fires onLoad event.\r\n * @param {Object} serviceParameters - Service parameters.\r\n */\r\n init() {\r\n if (!Core.initialized) {\r\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\r\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\r\n const jsonConfiguration = JSON.parse(jsConfiguration);\r\n Configuration.addConfiguration(jsonConfiguration);\r\n // Adding JavaScript (not backend) configuration variables.\r\n Configuration.addConfiguration(jsProperties);\r\n // Fire 'onLoad' event:\r\n // All integration must listen this event in order to know if the plugin\r\n // has been properly loaded.\r\n StringManager.language = this.language;\r\n this.listeners.fire(\"onLoad\", {});\r\n });\r\n\r\n ServiceProvider.addListener(serviceProviderListener);\r\n ServiceProvider.init(this.serviceProviderProperties);\r\n\r\n Core.initialized = true;\r\n } else {\r\n // Case when there are more than two editor instances.\r\n // After the first editor all the other editors don't need to load any file or service.\r\n this.listeners.fire(\"onLoad\", {});\r\n }\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\r\n * @param {Listener} listener - The listener object.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Adds the global {@link Listener} instance to {@link Core} class.\r\n * @param {Listener} listener - The event listener to be added.\r\n * @static\r\n */\r\n static addGlobalListener(listener) {\r\n Core.globalListeners.add(listener);\r\n }\r\n\r\n beforeUpdateFormula(mathml, wirisProperties) {\r\n /**\r\n * This event is fired before updating the formula.\r\n * @type {Object}\r\n * @property {String} mathml - MathML to be transformed.\r\n * @property {String} editMode - Edit mode.\r\n * @property {Object} wirisProperties - Extra attributes for the formula.\r\n * @property {String} language - Formula language.\r\n */\r\n const beforeUpdateEvent = new Event();\r\n\r\n beforeUpdateEvent.mathml = mathml;\r\n\r\n // Cloning wirisProperties object\r\n // We don't want wirisProperties object modified.\r\n beforeUpdateEvent.wirisProperties = {};\r\n\r\n if (wirisProperties != null) {\r\n Object.keys(wirisProperties).forEach((attr) => {\r\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\r\n });\r\n }\r\n\r\n // Read only.\r\n beforeUpdateEvent.language = this.language;\r\n beforeUpdateEvent.editMode = this.editMode;\r\n\r\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {\r\n mathml: beforeUpdateEvent.mathml,\r\n wirisProperties: beforeUpdateEvent.wirisProperties,\r\n };\r\n }\r\n\r\n /**\r\n * Converts a MathML into it's correspondent image and inserts the image is\r\n * inserted in a HTMLElement target by creating\r\n * a new image or updating an existing one.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\r\n * @param {Window} windowTarget - The window element where the editable content is.\r\n * @param {String} mathml - The MathML.\r\n * @param {Array.} wirisProperties - The extra attributes for the formula.\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n /**\r\n * It is the object with the information of the node or latex to insert.\r\n * @typedef ReturnObject\r\n * @property {Node} [node] - The DOM node to insert.\r\n * @property {String} [latex] - The latex to insert.\r\n */\r\n const returnObject = {};\r\n\r\n if (!mathml) {\r\n this.insertElementOnSelection(null, focusElement, windowTarget);\r\n } else if (this.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n // this.integrationModel.getNonLatexNode is an integration wrapper\r\n // to have special behaviours for nonLatex.\r\n // Not all the integrations have special behaviours for nonLatex.\r\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.latex = returnObject.latex;\r\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\r\n } else {\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n }\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n } else {\r\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\r\n\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n }\r\n\r\n return returnObject;\r\n }\r\n\r\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\r\n /**\r\n * This event is fired after update the formula.\r\n * @type {Event}\r\n * @param {String} editMode - edit mode.\r\n * @param {Object} windowTarget - target window.\r\n * @param {Object} focusElement - target element to be focused after update.\r\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\r\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\r\n */\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.node = node;\r\n afterUpdateEvent.latex = latex;\r\n\r\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {};\r\n }\r\n\r\n /**\r\n * Sets the caret after a given Node and set the focus to the owner document.\r\n * @param {Node} node - The Node element.\r\n */\r\n placeCaretAfterNode(node) {\r\n if (node === null) return;\r\n\r\n this.integrationModel.getSelection();\r\n const nodeDocument = node.ownerDocument;\r\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\r\n const range = nodeDocument.createRange();\r\n range.setStartAfter(node);\r\n range.collapse(true);\r\n const selection = nodeDocument.getSelection();\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n nodeDocument.body.focus();\r\n }\r\n }\r\n\r\n /**\r\n * Replaces a Selection object with an HTMLElement.\r\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\r\n * @param {Window} windowTarget - The window target.\r\n */\r\n insertElementOnSelection(element, focusElement, windowTarget) {\r\n let mathmlOrigin = null;\r\n if (this.editionProperties.isNewElement) {\r\n if (element) {\r\n if (focusElement.type === \"textarea\") {\r\n Util.updateTextArea(focusElement, element.textContent);\r\n } else if (document.selection && document.getSelection === 0) {\r\n let range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n\r\n if (!(\"parentElement\" in range)) {\r\n windowTarget.document.execCommand(\"delete\", false);\r\n range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n }\r\n\r\n if (\"parentElement\" in range) {\r\n const temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\r\n temporalObject.parentNode.replaceChild(element, temporalObject);\r\n } else {\r\n // IE9 fix: parentNode() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML(Util.createObjectCode(element));\r\n }\r\n }\r\n } else {\r\n let range = null;\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n if (this.editionProperties.range) {\r\n ({ range } = this.editionProperties);\r\n this.editionProperties.range = null;\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n range = editorSelection.getRangeAt(0);\r\n }\r\n\r\n // Delete if something was surrounded.\r\n range.deleteContents();\r\n\r\n let node = range.startContainer;\r\n const position = range.startOffset;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = node.splitText(position);\r\n node.parentNode.insertBefore(element, node);\r\n } else if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n node.insertBefore(element, node.childNodes[position]);\r\n }\r\n\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n focusElement.focus();\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (this.editionProperties.range) {\r\n const { range } = this.editionProperties;\r\n this.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n }\r\n }\r\n } else if (this.editionProperties.latexRange) {\r\n if (document.selection && document.getSelection === 0) {\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.latexRange.select();\r\n this.insertElementOnSelection(element, focusElement, windowTarget);\r\n } else {\r\n this.editionProperties.latexRange.deleteContents();\r\n this.editionProperties.latexRange.insertNode(element);\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n let item;\r\n // Wrapper for some integrations that can have special behaviours to show latex.\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n item = this.integrationModel.getSelectedItem(focusElement, false);\r\n } else {\r\n item = Util.getSelectedItemOnTextarea(focusElement);\r\n }\r\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\r\n } else {\r\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\r\n if (element && element.nodeName.toLowerCase() === \"img\") {\r\n // Editor empty, formula has been erased on edit.\r\n // There are editors (e.g: CKEditor) that put attributes in images.\r\n // We don't allow that behaviour in our images.\r\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\r\n // Clone is needed to maintain event references to temporalImage.\r\n Image.clone(element, this.editionProperties.temporalImage);\r\n } else {\r\n this.editionProperties.temporalImage.remove();\r\n }\r\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const mathml = element?.dataset?.mathml;\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove the desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n }\r\n\r\n /**\r\n * Opens a modal dialog containing MathType editor..\r\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\r\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\r\n */\r\n openModalDialog(target, isIframe) {\r\n // Count the time since the editor is open\r\n this.editionProperties.editionStartTime = Date.now();\r\n\r\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\r\n this.editMode = \"images\";\r\n\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n try {\r\n if (isIframe) {\r\n // Is needed focus the target first.\r\n target.contentWindow.focus();\r\n const selection = target.contentWindow.getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n } else {\r\n // Is needed focus the target first.\r\n target.focus();\r\n const selection = getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n }\r\n } catch (e) {\r\n this.editionProperties.range = null;\r\n }\r\n\r\n if (isIframe === undefined) {\r\n isIframe = true;\r\n }\r\n\r\n this.editionProperties.latexRange = null;\r\n\r\n if (target) {\r\n let selectedItem;\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\r\n } else {\r\n selectedItem = Util.getSelectedItem(target, isIframe);\r\n }\r\n\r\n // Check LaTeX if and only if the node is a text node (nodeType==3).\r\n if (selectedItem) {\r\n // Case when image was selected and button pressed.\r\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\r\n this.editionProperties.temporalImage = selectedItem.node;\r\n this.editionProperties.isNewElement = false;\r\n } else if (selectedItem.node.nodeType === 3) {\r\n // If it's a text node means that editor is working with LaTeX.\r\n if (this.integrationModel.getMathmlFromTextNode) {\r\n // If integration has this function it isn't set range due to we don't\r\n // know if it will be put into a textarea as a text or image.\r\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (mathml) {\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n }\r\n } else {\r\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (latexResult) {\r\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n const windowTarget = isIframe ? target.contentWindow : window;\r\n\r\n if (target.tagName.toLowerCase() !== \"textarea\") {\r\n if (document.selection) {\r\n let leftOffset = 0;\r\n let previousNode = latexResult.startNode.previousSibling;\r\n\r\n while (previousNode) {\r\n leftOffset += Util.getNodeLength(previousNode);\r\n previousNode = previousNode.previousSibling;\r\n }\r\n\r\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\r\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\r\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\r\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\r\n } else {\r\n this.editionProperties.latexRange = windowTarget.document.createRange();\r\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\r\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n } else if (target.tagName.toLowerCase() === \"textarea\") {\r\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\r\n this.editMode = \"latex\";\r\n }\r\n }\r\n\r\n // Setting an object with the editor parameters.\r\n // Editor parameters can be customized in several ways:\r\n // 1 - editorAttributes: Contains the default editor attributes,\r\n // usually the metrics in a comma separated string. Always exists.\r\n // 2 - editorParameters: Object containing custom editor parameters.\r\n // These parameters are defined in the backend. So they affects all integration instances.\r\n\r\n // The backend send the default editor attributes in a coma separated\r\n // with the following structure: key1=value1,key2=value2...\r\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\r\n const defaultEditorAttributes = {};\r\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\r\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\r\n const key = tempAttribute[0];\r\n const value = tempAttribute[1];\r\n defaultEditorAttributes[key] = value;\r\n }\r\n // Custom editor parameters.\r\n const editorAttributes = {\r\n language: this.language, // Default language value\r\n };\r\n // Editor parameters in backend, usually configuration.ini.\r\n const serverEditorParameters = Configuration.get(\"editorParameters\");\r\n // Editor parameters through JavaScript configuration.\r\n const clientEditorParameters = this.integrationModel.editorParameters;\r\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\r\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\r\n\r\n // Now, update backwards: if user has set a custom language, pass that back to core properties\r\n this.language = editorAttributes.language;\r\n StringManager.language = this.language;\r\n\r\n editorAttributes.rtl = this.integrationModel.rtl;\r\n\r\n const customHeaders = Configuration.get(\"customHeaders\");\r\n // This is not being used in the code, we are keeping it just in case it's needed.\r\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\r\n editorAttributes.customHeaders =\r\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\r\n\r\n const contentManagerAttributes = {};\r\n contentManagerAttributes.editorAttributes = editorAttributes;\r\n contentManagerAttributes.language = this.language;\r\n contentManagerAttributes.customEditors = this.customEditors;\r\n contentManagerAttributes.environment = this.environment;\r\n\r\n if (this.modalDialog == null) {\r\n this.modalDialog = new ModalDialog(editorAttributes);\r\n this.contentManager = new ContentManager(contentManagerAttributes);\r\n // When an instance of ContentManager is created we need to wait until\r\n // the ContentManager is ready by listening 'onLoad' event.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n });\r\n this.contentManager.addListener(listener);\r\n this.contentManager.init();\r\n this.modalDialog.setContentManager(this.contentManager);\r\n this.contentManager.setModalDialogInstance(this.modalDialog);\r\n } else {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n }\r\n this.contentManager.setIntegrationModel(this.integrationModel);\r\n this.modalDialog.open();\r\n }\r\n\r\n /**\r\n * Returns the {@link CustomEditors} instance.\r\n * @return {CustomEditors} The current {@link CustomEditors} instance.\r\n */\r\n getCustomEditors() {\r\n return this.customEditors;\r\n }\r\n}\r\n\r\n/**\r\n * Core static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nCore._globalListeners = new Listeners();\r\n\r\n/**\r\n * Resources state. Says if they were loaded or not.\r\n * @type {Boolean}\r\n * @private\r\n */\r\nCore._initialized = false;\r\n","// eslint-disable-next-line no-unused-vars, import/named\r\nimport Core from \"./core.src\";\r\nimport Image from \"./image\";\r\nimport Listeners from \"./listeners\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Telemeter from \"./telemeter\";\r\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\r\n\r\n/**\r\n * @typedef {Object} IntegrationModelProperties\r\n * @property {string} configurationService - Configuration service path.\r\n * This parameter is needed to determine all services paths.\r\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\r\n * @property {string} integrationModelProperties.scriptName - Integration script name.\r\n * Usually the name of the integration script.\r\n * @property {Object} integrationModelProperties.environment - integration environment properties.\r\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\r\n * callback method arguments.\r\n * @property {string} [integrationModelProperties.version] - integration version number.\r\n * @property {Object} [integrationModelProperties.editorObject] - object containing\r\n * the integration editor instance.\r\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\r\n * false otherwise.\r\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\r\n * - The service parameters.\r\n * @property {Object} [integrationModelProperties.integrationParameters]\r\n * - Overwritten integration parameters.\r\n */\r\n\r\nexport default class IntegrationModel {\r\n /**\r\n * @classdesc\r\n * This class represents an integration model, allowing the integration script to\r\n * communicate with Core class. Each integration must extend this class.\r\n * @constructs\r\n * @param {IntegrationModelProperties} integrationModelProperties\r\n */\r\n constructor(integrationModelProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. English by default.\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Service parameters\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\r\n\r\n /**\r\n * Configuration service path. The integration service is needed by Core class to\r\n * load all the backend configuration into the frontend and also to create the paths\r\n * of all services (all services lives in the same route). Mandatory property.\r\n */\r\n this.configurationService = \"\";\r\n if (\"configurationService\" in integrationModelProperties) {\r\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\r\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\r\n integrationModelProperties.configurationService,\r\n ]);\r\n }\r\n\r\n /**\r\n * Plugin version. Needed to stats and caching.\r\n * @type {string}\r\n */\r\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\r\n\r\n /**\r\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\r\n * Mandatory property.\r\n */\r\n this.target = null;\r\n if (\"target\" in integrationModelProperties) {\r\n this.target = integrationModelProperties.target;\r\n } else {\r\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\r\n }\r\n\r\n /**\r\n * Integration script name. Needed to know the plugin path.\r\n */\r\n if (\"scriptName\" in integrationModelProperties) {\r\n this.scriptName = integrationModelProperties.scriptName;\r\n }\r\n\r\n /**\r\n * Object containing the arguments needed by the callback function.\r\n */\r\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\r\n\r\n /**\r\n * Contains information about the integration environment:\r\n * like the name of the editor, the version, etc.\r\n */\r\n this.environment = integrationModelProperties.environment ?? {};\r\n\r\n /**\r\n * Indicates if the DOM target is - or not - and iframe.\r\n */\r\n this.isIframe = false;\r\n if (this.target != null) {\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Instance of the integration editor object. Usually the entry point to access the API\r\n * of a HTML editor.\r\n */\r\n this.editorObject = integrationModelProperties.editorObject ?? null;\r\n\r\n /**\r\n * Specifies if the direction of the text is RTL. false by default.\r\n */\r\n this.rtl = integrationModelProperties.rtl ?? false;\r\n\r\n /**\r\n * Specifies if the integration model exposes the locale strings. false by default.\r\n */\r\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\r\n\r\n /**\r\n * Specify if editor will open in hand mode only\r\n */\r\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\r\n\r\n /**\r\n * Indicates if an image is selected. Needed to resize the image to the original size in case\r\n * the image is resized.\r\n * @type {boolean}\r\n */\r\n this.temporalImageResizing = false;\r\n\r\n /**\r\n * The Core class instance associated to the integration model.\r\n * @type {Core}\r\n */\r\n this.core = null;\r\n\r\n /**\r\n * Integration model listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n // Parameters overwrite.\r\n if (\"integrationParameters\" in integrationModelProperties) {\r\n IntegrationModel.integrationParameters.forEach((parameter) => {\r\n if (parameter in integrationModelProperties.integrationParameters) {\r\n // Don't add empty parameters.\r\n const value = integrationModelProperties.integrationParameters[parameter];\r\n if (Object.keys(value).length !== 0) {\r\n this[parameter] = value;\r\n }\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Init function. Usually called from the integration side once the core.js file is loaded.\r\n */\r\n init() {\r\n // Check if language is an object and select the property necessary\r\n this.language = this.getLanguage();\r\n\r\n // We need to wait until Core class is loaded ('onLoad' event) before\r\n // call the callback method.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.callbackFunction(this.callbackMethodArguments);\r\n });\r\n\r\n // Backwards compatibility.\r\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\r\n const uri = this.serviceProviderProperties.URI;\r\n const server = ServiceProvider.getServerLanguageFromService(uri);\r\n this.serviceProviderProperties.server = server;\r\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\r\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\r\n this.serviceProviderProperties.URI = subsTring;\r\n }\r\n\r\n let serviceParametersURI = this.serviceProviderProperties.URI;\r\n serviceParametersURI =\r\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\r\n ? serviceParametersURI\r\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\r\n\r\n this.serviceProviderProperties.URI = serviceParametersURI;\r\n\r\n const coreProperties = {};\r\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\r\n\r\n this.setCore(new Core(coreProperties));\r\n this.core.addListener(listener);\r\n this.core.language = this.language;\r\n\r\n // Initializing Core class.\r\n this.core.init();\r\n // TODO: Move to Core constructor.\r\n this.core.setEnvironment(this.environment);\r\n\r\n // No internet connection modal.\r\n let attributes = {};\r\n attributes.class = attributes.id = \"wrs_modal_offline\";\r\n this.offlineModal = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_content_offline\";\r\n this.offlineModalContent = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_close\";\r\n this.offlineModalClose = Util.createElement(\"span\", attributes);\r\n this.offlineModalClose.innerHTML = \"×\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_warn\";\r\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\r\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\r\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_container\";\r\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_warn\";\r\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text\";\r\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\r\n\r\n // Append offline modal elements\r\n this.offlineModalContent.appendChild(this.offlineModalClose);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\r\n this.offlineModalContent.appendChild(this.offlineModalMessage);\r\n this.offlineModalContent.appendChild(this.offlineModalWarn);\r\n this.offlineModal.appendChild(this.offlineModalContent);\r\n document.body.appendChild(this.offlineModal);\r\n\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n this.offlineModalClose.addEventListener(\"click\", () => {\r\n modal.style.display = \"none\";\r\n });\r\n\r\n // Store editor name for telemetry purposes.\r\n let editorName = this.environment.editor;\r\n editorName = editorName.slice(0, -1); // Remove version number from editors\r\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\r\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\r\n let solutionTelemeter = editorName;\r\n\r\n // If moodle, add information to hosts and solution.\r\n const isMoodle = !!(typeof M === \"object\" && M !== null);\r\n let lms;\r\n\r\n if (isMoodle) {\r\n solutionTelemeter = \"Moodle\";\r\n lms = {\r\n nam: \"moodle\",\r\n fam: \"lms\",\r\n ver: this.environment.moodleVersion,\r\n category: this.environment.moodleCourseCategory,\r\n course: this.environment.moodleCourseName,\r\n };\r\n if (!editorName.includes(\"TinyMCE\")) {\r\n editorName = \"Atto\";\r\n }\r\n }\r\n\r\n // Get the OS and its version.\r\n const OSData = this.getOS();\r\n\r\n // Get the broswer and its version.\r\n const broswerData = this.getBrowser();\r\n\r\n // Create list of hosts to send to telemetry.\r\n let hosts = [\r\n {\r\n nam: broswerData.detectedBrowser,\r\n fam: \"browser\",\r\n ver: broswerData.versionBrowser,\r\n },\r\n {\r\n nam: editorName.toLowerCase(),\r\n fam: \"html-editor\",\r\n ver: this.environment.editorVersion,\r\n },\r\n {\r\n nam: OSData.detectedOS,\r\n fam: \"os\",\r\n ver: OSData.versionOS,\r\n },\r\n {\r\n nam: window.location.hostname,\r\n fam: \"domain\",\r\n },\r\n lms,\r\n ];\r\n\r\n // Filter hosts to remove empty objects and empty keys.\r\n hosts = hosts.filter((element) => {\r\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\r\n return element !== undefined;\r\n });\r\n\r\n // Initialize telemeter\r\n Telemeter.init({\r\n solution: {\r\n name: `MathType for ${solutionTelemeter}`,\r\n version: this.version,\r\n },\r\n hosts,\r\n config: {\r\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\r\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\r\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\r\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\r\n },\r\n url: undefined,\r\n });\r\n }\r\n\r\n /**\r\n * Returns the Browser name and its version.\r\n * @return {array} - detectedBrowser = Operating System name.\r\n * versionBrowser = Operating System version.\r\n */\r\n getBrowser() {\r\n // default value for OS just in case nothing is detected\r\n let detectedBrowser = \"unknown\";\r\n let versionBrowser = \"unknown\";\r\n\r\n const userAgent = window.navigator.userAgent;\r\n\r\n if (/Brave/.test(userAgent)) {\r\n detectedBrowser = \"brave\";\r\n if (userAgent.indexOf(\"Brave/\")) {\r\n const start = userAgent.indexOf(\"Brave\") + 6;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\r\n detectedBrowser = \"edge_chromium\";\r\n const start = userAgent.indexOf(\"Edg/\") + 4;\r\n versionBrowser = userAgent\r\n .substring(start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Edg/.test(userAgent)) {\r\n detectedBrowser = \"edge\";\r\n let start = userAgent.indexOf(\"Edg\") + 3;\r\n start += userAgent.substring(start).indexOf(\"/\");\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\r\n detectedBrowser = \"firefox\";\r\n let start = userAgent.indexOf(\"Firefox\");\r\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/OPR/.test(userAgent)) {\r\n detectedBrowser = \"opera\";\r\n const start = userAgent.indexOf(\"OPR/\") + 4;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\r\n detectedBrowser = \"chrome\";\r\n let start = userAgent.indexOf(\"Chrome\");\r\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/Safari/.test(userAgent)) {\r\n detectedBrowser = \"safari\";\r\n let start = userAgent.indexOf(\"Version/\");\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n }\r\n\r\n return { detectedBrowser, versionBrowser };\r\n }\r\n\r\n /**\r\n * Returns the operating system and its version.\r\n * @return {array} - detectedOS = Operating System name.\r\n * versionOS = Operating System version.\r\n */\r\n getOS() {\r\n // default value for OS just in case nothing is detected\r\n let detectedOS = \"unknown\";\r\n let versionOS = \"unknown\";\r\n\r\n // Retrieve properties to easily detect the OS\r\n const userAgent = window.navigator.userAgent;\r\n const platform = window.navigator.platform;\r\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\r\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\r\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\r\n\r\n // Find OS and their respective versions\r\n if (macosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"macos\";\r\n if (userAgent.indexOf(\"OS X\") !== -1) {\r\n const start = userAgent.indexOf(\"OS X\") + 5;\r\n const end = userAgent.substring(start).indexOf(\" \");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (iosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"ios\";\r\n if (userAgent.indexOf(\"OS \") !== -1) {\r\n const start = userAgent.indexOf(\"OS \") + 3;\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"windows\";\r\n const start = userAgent.indexOf(\"Windows\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Android/.test(userAgent)) {\r\n detectedOS = \"android\";\r\n const start = userAgent.indexOf(\"Android\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/CrOS/.test(userAgent)) {\r\n detectedOS = \"chromeos\";\r\n let start = userAgent.indexOf(\"CrOS \") + 5;\r\n start += userAgent.substring(start).indexOf(\" \");\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\r\n detectedOS = \"linux\";\r\n }\r\n\r\n return { detectedOS, versionOS };\r\n }\r\n\r\n /**\r\n * Returns the absolute path of the integration script.\r\n * @return {string} - Absolute path for the integration script.\r\n */\r\n getPath() {\r\n if (typeof this.scriptName === \"undefined\") {\r\n throw new Error(\"scriptName property needed for getPath.\");\r\n }\r\n const col = document.getElementsByTagName(\"script\");\r\n let path = \"\";\r\n for (let i = 0; i < col.length; i += 1) {\r\n const j = col[i].src.lastIndexOf(this.scriptName);\r\n if (j >= 0) {\r\n path = col[i].src.substr(0, j - 1);\r\n }\r\n }\r\n return path;\r\n }\r\n\r\n /**\r\n * Returns integration model plugin version\r\n * @param {string} - Plugin version\r\n */\r\n getVersion() {\r\n return this.version;\r\n }\r\n\r\n /**\r\n * Sets the language property.\r\n * @param {string} language - language code.\r\n */\r\n setLanguage(language) {\r\n this.language = language;\r\n }\r\n\r\n /**\r\n * Sets a Core instance.\r\n * @param {Core} core - instance of Core class.\r\n */\r\n setCore(core) {\r\n this.core = core;\r\n core.setIntegrationModel(this);\r\n }\r\n\r\n /**\r\n * Returns the Core instance.\r\n * @returns {Core} instance of Core class.\r\n */\r\n getCore() {\r\n return this.core;\r\n }\r\n\r\n /**\r\n * Sets the object target and updates the iframe property.\r\n * @param {HTMLElement} target - target object.\r\n */\r\n setTarget(target) {\r\n this.target = target;\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Sets the editor object.\r\n * @param {Object} editorObject - The editor object.\r\n */\r\n setEditorObject(editorObject) {\r\n this.editorObject = editorObject;\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openNewFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.dbclick = false;\r\n this.core.editionProperties.isNewElement = true;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openExistingFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.isNewElement = false;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Wrapper to Core.updateFormula method.\r\n * Transform a MathML into a image formula.\r\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\r\n * or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n */\r\n updateFormula(mathml) {\r\n if (this.editorParameters) {\r\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\r\n mathml,\r\n \"application/vnd.wiris.mtweb-params+json\",\r\n JSON.stringify(this.editorParameters),\r\n );\r\n }\r\n let focusElement;\r\n let windowTarget;\r\n const wirisProperties = null;\r\n\r\n if (this.isIframe) {\r\n focusElement = this.target.contentWindow;\r\n windowTarget = this.target.contentWindow;\r\n } else {\r\n focusElement = this.target;\r\n windowTarget = window;\r\n }\r\n\r\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\r\n }\r\n\r\n /**\r\n * Wrapper to Core.insertFormula method.\r\n * Inserts the formula in the specified target, creating\r\n * a new image (new formula) or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\r\n\r\n // Delete temporal image when inserted\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return obj;\r\n }\r\n\r\n /**\r\n * Returns the target selection.\r\n * @returns {Selection} target selection.\r\n */\r\n getSelection() {\r\n if (this.isIframe) {\r\n this.target.contentWindow.focus();\r\n return this.target.contentWindow.getSelection();\r\n }\r\n this.target.focus();\r\n return window.getSelection();\r\n }\r\n\r\n /**\r\n * Add events to formulas in the DOM target. The events added are the following:\r\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\r\n * to edit them.\r\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\r\n * in case the the formula is resized.\r\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\r\n * in case the formula is resized.\r\n */\r\n addEvents() {\r\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\r\n Util.addElementEvents(\r\n eventTarget,\r\n (element, event) => {\r\n this.doubleClickHandler(element, event);\r\n // Avoid creating the double click listener more than once for each element.\r\n // This also allows CKEditor4 to add their own double click listener.\r\n event.preventDefault();\r\n },\r\n (element, event) => {\r\n this.mousedownHandler(element, event);\r\n },\r\n (element, event) => {\r\n this.mouseupHandler(element, event);\r\n },\r\n );\r\n }\r\n\r\n /**\r\n * Remove events to formulas in the DOM target.\r\n */\r\n removeEvents() {\r\n const eventTarget =\r\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\r\n\r\n if (!eventTarget) {\r\n return;\r\n }\r\n\r\n Util.removeElementEvents(eventTarget);\r\n }\r\n\r\n /**\r\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\r\n */\r\n destroy() {\r\n this.removeEvents();\r\n // Destroy modal dialog if exists.\r\n if (this.core.modalDialog) {\r\n this.core.modalDialog.destroy();\r\n }\r\n\r\n // Remove offline modal dialog if exists.\r\n if (this.offlineModal) {\r\n this.offlineModal.remove();\r\n }\r\n\r\n this.editorObject = null;\r\n }\r\n\r\n /**\r\n * Handles a Double-click on the target element. Opens an editor\r\n * to re-edit the double-clicked formula.\r\n * @param {HTMLElement} element - DOM object target.\r\n */\r\n doubleClickHandler(element) {\r\n this.core.editionProperties.dbclick = true;\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (element.hasAttribute(customEditorAttributeName)) {\r\n const customEditor = element.getAttribute(customEditorAttributeName);\r\n this.core.getCustomEditors().enable(customEditor);\r\n }\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.core.editionProperties.temporalImage = element;\r\n this.core.editionProperties.isNewElement = true;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse up event on the target element. Restores the image size to avoid\r\n * resizing formulas.\r\n */\r\n mouseupHandler() {\r\n if (this.temporalImageResizing) {\r\n setTimeout(() => {\r\n Image.fixAfterResize(this.temporalImageResizing);\r\n }, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse down event on the target element. Saves the formula size to avoid\r\n * resizing formulas.\r\n * @param {HTMLElement} element - target element.\r\n */\r\n mousedownHandler(element) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.temporalImageResizing = element;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the integration language. By default the browser agent. This method\r\n * should be overwritten to obtain the integration language, for example using the\r\n * plugin API of an HTML editor.\r\n * @returns {string} integration language.\r\n */\r\n getLanguage() {\r\n return this.getBrowserLanguage();\r\n }\r\n\r\n /**\r\n * Returns the browser language.\r\n * @returns {string} the browser language.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getBrowserLanguage() {\r\n let language = \"en\";\r\n if (navigator.userLanguage) {\r\n language = navigator.userLanguage.substring(0, 2);\r\n } else if (navigator.language) {\r\n language = navigator.language.substring(0, 2);\r\n } else {\r\n language = \"en\";\r\n }\r\n return language;\r\n }\r\n\r\n /**\r\n * This function is called once the {@link Core} is loaded. IntegrationModel class\r\n * will fire this method when {@link Core} 'onLoad' event is fired.\r\n * This method should content all the logic to init\r\n * the integration.\r\n */\r\n callbackFunction() {\r\n // It's needed to wait until the integration target is ready. The event is fired\r\n // from the integration side.\r\n const listener = Listeners.newListener(\"onTargetReady\", () => {\r\n this.addEvents(this.target);\r\n });\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n notifyWindowClosed() {\r\n // Nothing.\r\n }\r\n\r\n /**\r\n * Wrapper.\r\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\r\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\r\n * @param {string} textNode - text node to extract the MathML.\r\n * @param {int} caretPosition - caret position inside the text node.\r\n * @returns {string} MathML inside the text node.\r\n */\r\n\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getMathmlFromTextNode(textNode, caretPosition) {}\r\n\r\n /**\r\n * Wrapper\r\n * It fills wrs event object of nonLatex with the desired data.\r\n * @param {Object} event - event object.\r\n * @param {Object} window dom window object.\r\n * @param {string} mathml valid mathml.\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n fillNonLatexNode(event, window, mathml) {}\r\n\r\n /**\r\n Wrapper.\r\n * Returns selected item from the target.\r\n * @param {HTMLElement} target - target element\r\n * @param {boolean} iframe\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getSelectedItem(target, isIframe) {}\r\n\r\n // Set temporal image to null and make focus come back.\r\n static setActionsOnCancelButtons() {\r\n // Make focus come back on the previous place it was when click cancel button\r\n const currentInstance = WirisPlugin.currentInstance;\r\n const editorSelection = currentInstance.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (currentInstance.core.editionProperties.range) {\r\n const { range } = currentInstance.core.editionProperties;\r\n currentInstance.core.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n if (range.startOffset !== range.endOffset) {\r\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\r\n }\r\n }\r\n\r\n // eslint-disable-next-line no-undef\r\n if (WirisPlugin.currentInstance) {\r\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\r\n }\r\n }\r\n}\r\n\r\n// To know if the integration that extends this class implements\r\n// wrapper methods, they are set as undefined.\r\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\r\nIntegrationModel.prototype.fillNonLatexNode = undefined;\r\nIntegrationModel.prototype.getSelectedItem = undefined;\r\n\r\n/**\r\n * An object containing a list with the overwritable class constructor properties.\r\n * @type {Object}\r\n */\r\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\r\n","/* eslint-disable */\r\nvar md5;\r\nexport default md5;\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n delete Array.prototype.__class__;\r\n})();\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n})();\r\ndelete Array.prototype.__class__;\r\n// @codingStandardsIgnoreEnd\r\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\r\n\r\n/**\r\n * This class represents the MathType integration for CKEditor5.\r\n * @extends {IntegrationModel}\r\n */\r\nexport default class CKEditor5Integration extends IntegrationModel {\r\n constructor(ckeditorIntegrationModelProperties) {\r\n const editor = ckeditorIntegrationModelProperties.editorObject;\r\n\r\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\r\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\r\n }\r\n /**\r\n * CKEditor5 Integration.\r\n *\r\n * @param {integrationModelProperties} integrationModelAttributes\r\n */\r\n super(ckeditorIntegrationModelProperties);\r\n\r\n /**\r\n * Folder name used for the integration inside CKEditor plugins folder.\r\n */\r\n this.integrationFolderName = \"ckeditor_wiris\";\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @returns {string} - The CKEditor instance language.\r\n * @override\r\n */\r\n getLanguage() {\r\n // Returns the CKEDitor instance language taking into account that the language can be an object.\r\n // Try to get editorParameters.language, fail silently otherwise\r\n try {\r\n return this.editorParameters.language;\r\n } catch (e) {}\r\n const languageObject = this.editorObject.config.get(\"language\");\r\n if (languageObject != null) {\r\n if (typeof languageObject === \"object\") {\r\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\r\n return languageObject.ui;\r\n }\r\n return this.editorObject.locale.uiLanguage;\r\n }\r\n return languageObject;\r\n }\r\n return super.getLanguage();\r\n }\r\n\r\n /**\r\n * Adds callbacks to the following CKEditor listeners:\r\n * - 'focus' - updates the current instance.\r\n * - 'contentDom' - adds 'doubleclick' callback.\r\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\r\n * - 'setData' - parses the data converting MathML into images.\r\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\r\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\r\n * - 'mode' - recalculates the active element.\r\n */\r\n addEditorListeners() {\r\n const editor = this.editorObject;\r\n\r\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\r\n this.checkElement();\r\n }\r\n }\r\n\r\n /**\r\n * Checks the current container and assign events in case that it doesn't have them.\r\n * CKEditor replaces several times the element element during its execution,\r\n * so we must assign the events again to editor element.\r\n */\r\n checkElement() {\r\n const editor = this.editorObject;\r\n const newElement = editor.sourceElement;\r\n\r\n // If the element wasn't treated, add the events.\r\n if (!newElement.wirisActive) {\r\n this.setTarget(newElement);\r\n this.addEvents();\r\n // Set the element as treated\r\n newElement.wirisActive = true;\r\n }\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @param {HTMLElement} element - HTMLElement target.\r\n * @param {MouseEvent} event - event which trigger the handler.\r\n */\r\n doubleClickHandler(element, event) {\r\n this.core.editionProperties.dbclick = true;\r\n if (this.editorObject.isReadOnly === false) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\r\n // doubleclick event ends here.\r\n if (typeof event.stopPropagation !== \"undefined\") {\r\n // old I.E compatibility.\r\n event.stopPropagation();\r\n } else {\r\n event.returnValue = false;\r\n }\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\r\n if (customEditorAttr) {\r\n this.core.getCustomEditors().enable(customEditorAttr);\r\n }\r\n this.core.editionProperties.temporalImage = element;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n static getCorePath() {\r\n return null; // TODO\r\n }\r\n\r\n /** @inheritdoc */\r\n callbackFunction() {\r\n super.callbackFunction();\r\n this.addEditorListeners();\r\n }\r\n\r\n openNewFormulaEditor() {\r\n // Store the editor selection as it will be lost upon opening the modal\r\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\r\n\r\n // Focus on the selected editor when multiple editor instances are present\r\n WirisPlugin.currentInstance = this;\r\n\r\n return super.openNewFormulaEditor();\r\n }\r\n\r\n /**\r\n * Replaces old formula with new MathML or inserts it in caret position if new\r\n * @param {String} mathml MathML to update old one or insert\r\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\r\n */\r\n insertMathml(mathml) {\r\n // This returns the value returned by the callback function (writer => {...})\r\n return this.editorObject.model.change((writer) => {\r\n const core = this.getCore();\r\n const selection = this.editorObject.model.document.selection;\r\n\r\n const modelElementNew = writer.createElement(\"mathml\", {\r\n formula: mathml,\r\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\r\n });\r\n\r\n // Obtain the DOM object corresponding to the formula\r\n if (core.editionProperties.isNewElement) {\r\n // Don't bother inserting anything at all if the MathML is empty.\r\n if (!mathml) return;\r\n\r\n const viewSelection =\r\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\r\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\r\n\r\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\r\n\r\n // Remove selection\r\n if (!viewSelection.isCollapsed) {\r\n for (const range of viewSelection.getRanges()) {\r\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\r\n }\r\n }\r\n\r\n // Set carret after the formula\r\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\r\n writer.setSelection(position);\r\n } else {\r\n const img = core.editionProperties.temporalImage;\r\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\r\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\r\n\r\n // Insert the new and remove the old one\r\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\r\n\r\n // If the given MathML is empty, don't insert a new formula.\r\n if (mathml) {\r\n this.editorObject.model.insertObject(modelElementNew, position);\r\n }\r\n writer.remove(modelElementOld);\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return modelElementNew;\r\n });\r\n }\r\n\r\n /**\r\n * Finds the text node corresponding to given DOM text element.\r\n * @param {element} viewElement Element to find corresponding text node of.\r\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\r\n */\r\n findText(viewElement) {\r\n // eslint-disable-line consistent-return\r\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\r\n let pivot = viewElement;\r\n let element;\r\n while (!element) {\r\n element = this.editorObject.editing.mapper.toModelElement(\r\n this.editorObject.editing.view.domConverter.domToView(pivot),\r\n );\r\n pivot = pivot.parentElement;\r\n }\r\n\r\n // Navigate through all the subtree under `pivot` in order to find the correct text node\r\n const range = this.editorObject.model.createRangeIn(element);\r\n const descendants = Array.from(range.getItems());\r\n for (const node of descendants) {\r\n let viewElementData = viewElement.data;\r\n if (viewElement.nodeType === 3) {\r\n // Remove invisible white spaces\r\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\r\n }\r\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\r\n return node.textNode;\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n // eslint-disable-line no-unused-vars\r\n const returnObject = {};\r\n\r\n let mathmlOrigin;\r\n if (!mathml) {\r\n this.insertMathml(\"\");\r\n } else if (this.core.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n\r\n this.editorObject.model.change((writer) => {\r\n const { latexRange } = this.core.editionProperties;\r\n\r\n const startNode = this.findText(latexRange.startContainer);\r\n const endNode = this.findText(latexRange.endContainer);\r\n\r\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\r\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\r\n\r\n let range = writer.createRange(startPosition, endPosition);\r\n\r\n // When Latex is next to image/formula.\r\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\r\n // Get the position of the latex to be replaced.\r\n const latexEdited = `$$${Latex.getLatexFromMathML(\r\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\r\n )}$$`;\r\n let data = latexRange.startContainer.data;\r\n\r\n // Remove invisible characters.\r\n data = data.replaceAll(String.fromCharCode(8288), \"\");\r\n\r\n // Get to the start of the latex we are editing.\r\n const offset = data.indexOf(latexEdited);\r\n const dataOffset = data.substring(offset);\r\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\r\n const substring = dataOffset.substr(0, second$);\r\n data = data.replace(substring, \"\");\r\n\r\n if (!data) {\r\n startPosition = writer.createPositionBefore(startNode);\r\n range = startNode;\r\n } else {\r\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\r\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\r\n range = writer.createRange(startPosition, endPosition);\r\n }\r\n }\r\n\r\n writer.remove(range);\r\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\r\n });\r\n } else {\r\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\r\n try {\r\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\r\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\r\n windowTarget.document,\r\n );\r\n } catch (e) {\r\n const x = e.toString();\r\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\r\n this.core.modalDialog.cancelAction();\r\n }\r\n }\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.core.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n\r\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\r\n We probably should add it here as well, but we should look further into how */\r\n // this.editorObject.fire('change');\r\n\r\n // Remove temporal image of inserted formula\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return returnObject;\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n notifyWindowClosed() {\r\n this.editorObject.editing.view.focus();\r\n }\r\n}\r\n","/* eslint-disable max-classes-per-file */\r\nimport { Command } from \"ckeditor5/src/core.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\n/**\r\n * Command for opening the MathType editor\r\n */\r\nexport class MathTypeCommand extends Command {\r\n execute(options = {}) {\r\n // Check we get a valid integration\r\n // eslint-disable-next-line no-prototype-builtins\r\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\r\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\r\n }\r\n\r\n // Save the integration instance as a property of the command.\r\n this.integration = options.integration;\r\n\r\n // Set custom editor or disable it\r\n this.setEditor();\r\n\r\n // Open the editor\r\n this.openEditor();\r\n }\r\n\r\n /**\r\n * Sets the appropriate custom editor, if any, or disables them.\r\n */\r\n setEditor() {\r\n // It's possible that a custom editor was last used.\r\n // We need to disable it to avoid wrong behaviors.\r\n this.integration.core.getCustomEditors().disable();\r\n }\r\n\r\n /**\r\n * Checks whether we are editing an existing formula or a new one and opens the editor.\r\n */\r\n openEditor() {\r\n this.integration.core.editionProperties.dbclick = false;\r\n const image = this._getSelectedImage();\r\n if (\r\n typeof image !== \"undefined\" &&\r\n image !== null &&\r\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\r\n ) {\r\n this.integration.core.editionProperties.temporalImage = image;\r\n this.integration.openExistingFormulaEditor();\r\n } else {\r\n this.integration.openNewFormulaEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Gets the currently selected formula image\r\n * @returns {Element} selected image, if any, undefined otherwise\r\n */\r\n _getSelectedImage() {\r\n const { selection } = this.editor.editing.view.document;\r\n\r\n // If we can not extract the formula, fall back to default behavior.\r\n if (selection.isCollapsed || selection.rangeCount !== 1) {\r\n return;\r\n }\r\n\r\n // Look for the wrapping the formula and then for the inside\r\n\r\n const range = selection.getFirstRange();\r\n\r\n let image;\r\n\r\n for (const span of range) {\r\n if (span.item.name !== \"span\") {\r\n return;\r\n }\r\n image = span.item.getChild(0);\r\n break;\r\n }\r\n\r\n if (!image) {\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return this.editor.editing.view.domConverter.mapViewToDom(image);\r\n }\r\n}\r\n\r\n/**\r\n * Command for opening the ChemType editor\r\n */\r\nexport class ChemTypeCommand extends MathTypeCommand {\r\n setEditor() {\r\n this.integration.core.getCustomEditors().enable(\"chemistry\");\r\n }\r\n}\r\n","// CKEditor imports\r\nimport { Plugin } from \"ckeditor5/src/core.js\";\r\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\r\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\r\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\r\n\r\n// MathType API imports\r\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\r\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\r\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\r\n\r\n// Local imports\r\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\r\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\r\n\r\nimport packageInfo from \"../package.json\";\r\n\r\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\r\n\r\nexport default class MathType extends Plugin {\r\n static get requires() {\r\n return [Widget];\r\n }\r\n\r\n static get pluginName() {\r\n return \"MathType\";\r\n }\r\n\r\n init() {\r\n // Create the MathType API Integration object\r\n const integration = this._addIntegration();\r\n currentInstance = integration;\r\n\r\n // Add the MathType and ChemType commands to the editor\r\n this._addCommands();\r\n\r\n // Add the buttons for MathType and ChemType\r\n this._addViews(integration);\r\n\r\n // Registers the element in the schema\r\n this._addSchema();\r\n\r\n // Add the downcast and upcast converters\r\n this._addConverters(integration);\r\n\r\n // Expose the WirisPlugin variable to the window\r\n this._exposeWiris();\r\n }\r\n\r\n /**\r\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\r\n */\r\n destroy() {\r\n // eslint-disable-line class-methods-use-this\r\n currentInstance?.destroy();\r\n }\r\n\r\n /**\r\n * Create the MathType API Integration object\r\n * @returns {CKEditor5Integration} the integration object\r\n */\r\n _addIntegration() {\r\n const { editor } = this;\r\n\r\n /**\r\n * Integration model constructor attributes.\r\n * @type {integrationModelProperties}\r\n */\r\n const integrationProperties = {};\r\n integrationProperties.environment = {};\r\n integrationProperties.environment.editor = \"CKEditor5\";\r\n integrationProperties.environment.editorVersion = \"5.x\";\r\n integrationProperties.version = packageInfo.version;\r\n integrationProperties.editorObject = editor;\r\n integrationProperties.serviceProviderProperties = {};\r\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\r\n integrationProperties.serviceProviderProperties.server = \"java\";\r\n integrationProperties.target = editor.sourceElement;\r\n integrationProperties.scriptName = \"bundle.js\";\r\n integrationProperties.managesLanguage = true;\r\n // etc\r\n\r\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\r\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\r\n let integration;\r\n if (integrationProperties.target) {\r\n // Instance of the integration associated to this editor instance\r\n integration = new CKEditor5Integration(integrationProperties);\r\n integration.init();\r\n integration.listeners.fire(\"onTargetReady\", {});\r\n\r\n integration.checkElement();\r\n\r\n this.listenTo(\r\n editor.editing.view.document,\r\n \"click\",\r\n (evt, data) => {\r\n // Is Double-click\r\n if (data.domEvent.detail === 2) {\r\n integration.doubleClickHandler(data.domTarget, data.domEvent);\r\n evt.stop();\r\n }\r\n },\r\n { priority: \"highest\" },\r\n );\r\n }\r\n\r\n return integration;\r\n }\r\n\r\n /**\r\n * Add the MathType and ChemType commands to the editor\r\n */\r\n _addCommands() {\r\n const { editor } = this;\r\n\r\n // Add command to open the formula editor\r\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\r\n\r\n // Add command to open the chemistry formula editor\r\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\r\n }\r\n\r\n /**\r\n * Add the buttons for MathType and ChemType\r\n * @param {CKEditor5Integration} integration the integration object\r\n */\r\n _addViews(integration) {\r\n const { editor } = this;\r\n\r\n // Check if MathType editor is enabled\r\n if (Configuration.get(\"editorEnabled\")) {\r\n // Add button for the formula editor\r\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\r\n view.set({\r\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\r\n icon: mathIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"MathType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Check if ChemType editor is enabled\r\n if (Configuration.get(\"chemEnabled\")) {\r\n // Add button for the chemistry formula editor\r\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\r\n\r\n view.set({\r\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\r\n icon: chemIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"ChemType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Observer for the Double-click event\r\n editor.editing.view.addObserver(ClickObserver);\r\n }\r\n\r\n /**\r\n * Registers the element in the schema\r\n */\r\n _addSchema() {\r\n const { schema } = this.editor.model;\r\n\r\n schema.register(\"mathml\", {\r\n inheritAllFrom: \"$inlineObject\",\r\n allowAttributes: [\"formula\", \"htmlContent\"],\r\n });\r\n }\r\n\r\n /**\r\n * Add the downcast and upcast converters\r\n */\r\n _addConverters(integration) {\r\n const { editor } = this;\r\n\r\n // Editing view -> Model\r\n editor.conversion.for(\"upcast\").elementToElement({\r\n view: {\r\n name: \"span\",\r\n classes: \"ck-math-widget\",\r\n },\r\n model: (viewElement, { writer: modelWriter }) => {\r\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\r\n return modelWriter.createElement(\"mathml\", {\r\n formula,\r\n });\r\n },\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // When element was already consumed then skip it.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // If we encounter any with a LaTeX annotation inside,\r\n // convert it into a \"$$...$$\" string.\r\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\r\n\r\n // Get the formula of the (which is all its children).\r\n const processor = new XmlDataProcessor(editor.editing.view.document);\r\n\r\n // Only god knows why the following line makes viewItem lose all of its children,\r\n // so we obtain isLatex before doing this because we need viewItem's children for that.\r\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\r\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\r\n\r\n // and obtain the attributes of too!\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n\r\n // We process the document fragment\r\n let formula = processor.toData(viewDocumentFragment) || \"\";\r\n\r\n // And obtain the complete formula\r\n formula = Util.htmlSanitize(`${formula}`);\r\n\r\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\r\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\r\n\r\n /* Model node that contains what's going to actually be inserted. This can be either:\r\n - A element with a formula attribute set to the given formula, or\r\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\r\n const modelNode = isLatex\r\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\r\n : writer.createElement(\"mathml\", { formula });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\r\n \"element:img\",\r\n (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // Only upcast when is wiris formula\r\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\r\n return;\r\n }\r\n\r\n // The following code ensures that the element's name, attributes, and classes are consumed.\r\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\r\n\r\n // Check if we can consume the element name.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // Consume the name, attributes, and classes so nothing else processes it.\r\n consumable.consume(viewItem, { name: true });\r\n for (const attrName of viewItem.getAttributes()) {\r\n consumable.consume(viewItem, { attributes: [attrName] });\r\n }\r\n\r\n for (const className of viewItem.getClassNames()) {\r\n consumable.consume(viewItem, { classes: [className] });\r\n }\r\n\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n const htmlContent = Util.htmlSanitize(``);\r\n\r\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n },\r\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\r\n { priority: \"high\" },\r\n );\r\n\r\n /**\r\n * Whether the given view element has a LaTeX annotation element.\r\n * @param {*} math\r\n * @returns {bool}\r\n */\r\n function mathIsLatex(math) {\r\n const semantics = math.getChild(0);\r\n if (!semantics || semantics.name !== \"semantics\") return false;\r\n for (const child of semantics.getChildren()) {\r\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n function createViewWidget(modelItem, { writer: viewWriter }) {\r\n const widgetElement = viewWriter.createContainerElement(\"span\", {\r\n class: \"ck-math-widget\",\r\n });\r\n\r\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\r\n\r\n if (mathUIElement) {\r\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\r\n }\r\n\r\n return toWidget(widgetElement, viewWriter);\r\n }\r\n\r\n function createViewImage(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n const formula = modelItem.getAttribute(\"formula\");\r\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\r\n\r\n if (!formula && !htmlContent) {\r\n return null;\r\n }\r\n\r\n let imgElement = null;\r\n\r\n if (htmlContent) {\r\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\r\n } else if (formula) {\r\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\r\n\r\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\r\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\r\n\r\n // Add HTML element () to model\r\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\r\n }\r\n\r\n /* Although we use the HtmlDataProcessor to obtain the attributes,\r\n * we must create a new EmptyElement which is independent of the\r\n * DataProcessor being used by this editor instance\r\n */\r\n if (imgElement) {\r\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\r\n renderUnsafeAttributes: [\"src\"],\r\n });\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // Model -> Editing view\r\n editor.conversion.for(\"editingDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createViewWidget,\r\n });\r\n\r\n // Model -> Data view\r\n editor.conversion.for(\"dataDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createDataString, // eslint-disable-line no-use-before-define\r\n });\r\n\r\n /**\r\n * Makes a copy of the given view node.\r\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\r\n * @returns {module:engine/view/node~Node} Copy of the node.\r\n */\r\n function clone(viewWriter, sourceNode) {\r\n if (sourceNode.is(\"text\")) {\r\n return viewWriter.createText(sourceNode.data);\r\n }\r\n if (sourceNode.is(\"element\")) {\r\n if (sourceNode.is(\"emptyElement\")) {\r\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\r\n }\r\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\r\n for (const child of sourceNode.getChildren()) {\r\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\r\n }\r\n return element;\r\n }\r\n\r\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\r\n }\r\n\r\n function createDataString(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n // Load img element\r\n const mathString =\r\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\r\n\r\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\r\n\r\n return clone(viewWriter, sourceMathElement);\r\n }\r\n\r\n // This stops the view selection getting into the s and messing up caret movement\r\n editor.editing.mapper.on(\r\n \"viewToModelPosition\",\r\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\r\n );\r\n\r\n // Keep a reference to the original get and set function.\r\n const { get, set } = editor.data;\r\n\r\n /**\r\n * Hack to transform $$latex$$ into in editor.getData()'s output.\r\n */\r\n editor.data.on(\r\n \"get\",\r\n (e) => {\r\n const output = e.return;\r\n const parsedResult = Parser.endParse(output);\r\n\r\n // Cleans all the semantics tag for safexml\r\n // including the handwritten data points\r\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\r\n },\r\n { priority: \"low\" },\r\n );\r\n\r\n /**\r\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\r\n */\r\n editor.data.on(\r\n \"set\",\r\n (e, args) => {\r\n // Retrieve the data to be set on the CKEditor.\r\n let modifiedData = args[0];\r\n // Regex to find all mathml formulas.\r\n const regexp = /(]*>)|()/gm;\r\n const formulas = [];\r\n let formula;\r\n\r\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\r\n while ((formula = regexp.exec(modifiedData)) !== null) {\r\n formulas.push(formula[0]);\r\n }\r\n\r\n // Loop to find LaTeX and formula images and replace the MathML for the both.\r\n formulas.forEach((formula) => {\r\n if (formula.includes('encoding=\"LaTeX\"')) {\r\n // LaTeX found.\r\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\r\n modifiedData = modifiedData.replace(formula, latex);\r\n } else if (formula.includes(\" 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return func.apply(thisArg, args);\n };\n}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\n * This class represents all the constants needed in a MathType integration among different classes.\n * If a constant should be used across different classes should be defined using attribute\n * accessors.\n */\nexport default class Constants {\n /**\n * Safe XML entities.\n * @type {Object}\n */\n static get safeXmlCharactersEntities() {\n return {\n tagOpener: \"«\",\n tagCloser: \"»\",\n doubleQuote: \"¨\",\n realDoubleQuote: \""\",\n };\n }\n\n /**\n * Blackboard invalid safe characters.\n * @type {Object}\n */\n static get safeBadBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยป<ยซ/moยป\",\n gtElement: \"ยซmoยป>ยซ/moยป\",\n ampElement: \"ยซmoยป&ยซ/moยป\",\n };\n }\n\n /**\n * Blackboard valid safe characters.\n * @type{Object}\n */\n static get safeGoodBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\n };\n }\n\n /**\n * Standard XML special characters.\n * @type {Object}\n */\n static get xmlCharacters() {\n return {\n id: \"xmlCharacters\",\n tagOpener: \"<\", // Hex: \\x3C.\n tagCloser: \">\", // Hex: \\x3E.\n doubleQuote: '\"', // Hex: \\x22.\n ampersand: \"&\", // Hex: \\x26.\n quote: \"'\", // Hex: \\x27.\n };\n }\n\n /**\n * Safe XML special characters. This characters are used instead the standard\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\n * special character have a UTF-8 representation.\n * @type {Object}\n */\n static get safeXmlCharacters() {\n return {\n id: \"safeXmlCharacters\",\n tagOpener: \"ยซ\", // Hex: \\xAB.\n tagCloser: \"ยป\", // Hex: \\xBB.\n doubleQuote: \"ยจ\", // Hex: \\xA8.\n ampersand: \"ยง\", // Hex: \\xA7.\n quote: \"`\", // Hex: \\x60.\n realDoubleQuote: \"ยจ\",\n };\n }\n}\n","import Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a class to manage MathML objects.\n */\nexport default class MathML {\n /**\n * Checks if the mathml at position i is inside an HTML attribute or not.\n * @param {string} content - a string containing MathML code.\n * @param {number} i - search index.\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\n */\n static isMathmlInAttribute(content, i) {\n // Regex =\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\n const expression = new RegExp(regex);\n\n const actualContent = content.substring(0, i);\n const reversed = actualContent.split(\"\").reverse().join(\"\");\n const exists = expression.test(reversed);\n\n return exists;\n }\n\n /**\n * Decodes an encoded MathML with standard XML tags.\n * We use these entities because IE doesn't support html entities\n * on its attributes sometimes. Yes, sometimes.\n * @param {string} input - string to be decoded.\n * @return {string} decoded string.\n */\n static safeXmlDecode(input) {\n let { tagOpener } = Constants.safeXmlCharactersEntities;\n let { tagCloser } = Constants.safeXmlCharactersEntities;\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\n // Decoding entities.\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n // Added to fix problem due to import from 1.9.x.\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\n\n // Blackboard.\n const { ltElement } = Constants.safeBadBlackboardCharacters;\n const { gtElement } = Constants.safeBadBlackboardCharacters;\n const { ampElement } = Constants.safeBadBlackboardCharacters;\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\n }\n\n ({ tagOpener } = Constants.safeXmlCharacters);\n ({ tagCloser } = Constants.safeXmlCharacters);\n ({ doubleQuote } = Constants.safeXmlCharacters);\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\n const { ampersand } = Constants.safeXmlCharacters;\n const { quote } = Constants.safeXmlCharacters;\n\n // Decoding characters.\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\n input = input.split(quote).join(Constants.xmlCharacters.quote);\n\n // We are replacing $ by & when its part of an entity for retro-compatibility.\n // Now, the standard is replace ยง by &.\n let returnValue = \"\";\n let currentEntity = null;\n\n for (let i = 0; i < input.length; i += 1) {\n const character = input.charAt(i);\n if (currentEntity == null) {\n if (character === \"$\") {\n currentEntity = \"\";\n } else {\n returnValue += character;\n }\n } else if (character === \";\") {\n returnValue += `&${currentEntity}`;\n currentEntity = null;\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\n // Character is part of an entity.\n currentEntity += character;\n } else {\n returnValue += `$${currentEntity}`; // Is not an entity.\n currentEntity = null;\n i -= 1; // Parse again the current character.\n }\n }\n\n return returnValue;\n }\n\n /**\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\n * @param {string} input - input string to be encoded\n * @returns {string} encoded string.\n */\n static safeXmlEncode(input) {\n const { tagOpener } = Constants.xmlCharacters;\n const { tagCloser } = Constants.xmlCharacters;\n const { doubleQuote } = Constants.xmlCharacters;\n const { ampersand } = Constants.xmlCharacters;\n const { quote } = Constants.xmlCharacters;\n\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\n\n return input;\n }\n\n /**\n * Converts special symbols (> 128) to entities and replaces all textual\n * entities by its number entities.\n * @param {string} mathml - MathML string containing - or not - special symbols\n * @returns {string} MathML with all textual entities replaced.\n */\n static mathMLEntities(mathml) {\n let toReturn = \"\";\n\n for (let i = 0; i < mathml.length; i += 1) {\n const character = mathml.charAt(i);\n\n // Parsing > 128 characters.\n if (mathml.codePointAt(i) > 128) {\n toReturn += `&#${mathml.codePointAt(i)};`;\n // For UTF-32 characters we need to move the index one position.\n if (mathml.codePointAt(i) > 0xffff) {\n i += 1;\n }\n } else if (character === \"&\") {\n const end = mathml.indexOf(\";\", i + 1);\n if (end >= 0) {\n const container = document.createElement(\"span\");\n container.innerHTML = mathml.substring(i, end + 1);\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\n i = end;\n } else {\n toReturn += character;\n }\n } else {\n toReturn += character;\n }\n }\n\n return toReturn;\n }\n\n /**\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\n * @param {string} customEditor - custom editor name.\n * @returns {string} MathML string with his class containing the editor toolbar string.\n */\n static addCustomEditorClassAttribute(mathml, customEditor) {\n let toReturn = \"\";\n\n const start = mathml.indexOf(\"\");\n if (mathml.indexOf(\"class\") === -1) {\n // Adding custom editor type.\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\n toReturn += mathml.substr(end + 1, mathml.length);\n return toReturn;\n }\n }\n return mathml;\n }\n\n /**\n * Remove a custom editor name from the MathML class attribute.\n * @param {string} mathml - a MathML string.\n * @param {string} customEditor - custom editor name.\n * @returns {string} The input MathML without customEditor name in his class.\n */\n static removeCustomEditorClassAttribute(mathml, customEditor) {\n // Discard MathML without the specified class.\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\n return mathml;\n }\n\n // Trivial case: class attribute value equal to editor name. Then\n // class attribute is removed.\n // First try to remove it with a space before if there is one\n // Otherwise without the space\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\n }\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\n }\n\n // Non Trivial case: class attribute contains editor name.\n return mathml.replace(`wrs_${customEditor}`, \"\");\n }\n\n /**\n * Adds annotation tag in MathML element.\n * @param {String} mathml - valid MathML.\n * @param {String} content - value to put inside annotation tag.\n * @param {String} annotationEncoding - annotation encoding.\n * @returns {String} - 'mathml' with an annotation that contains\n * 'content' and encoding 'encoding'.\n */\n static addAnnotation(mathml, content, annotationEncoding) {\n // If contains annotation, also contains semantics tag.\n const containsAnnotation = mathml.indexOf(\"\");\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\n } else if (MathML.isEmpty(mathml)) {\n const endIndexInline = mathml.indexOf(\"/>\");\n const endIndexNonInline = mathml.indexOf(\">\");\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\n } else {\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\n const endMathmlContent = mathml.lastIndexOf(\"\");\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\n }\n\n return mathmlWithAnnotation;\n }\n\n /**\n * Removes specific annotation tag in MathML element.\n * In case of remove the unique annotation, also is removed semantics tag.\n * @param {String} mathml - valid MathML.\n * @param {String} annotationEncoding - annotation encoding to remove.\n * @returns {String} - 'mathml' without the annotation encoding specified.\n */\n static removeAnnotation(mathml, annotationEncoding) {\n let mathmlWithoutAnnotation = mathml;\n const openAnnotationTag = ``;\n const closeAnnotationTag = \"\";\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\n if (startAnnotationIndex !== -1) {\n let differentAnnotationFound = false;\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\n\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\n }\n\n /**\n * Removes semantics tag to element that contains mathml.\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\n * @param {string} element - Inner HTML text string.\n * @returns {string} - 'mathml' without semantics tag.\n */\n static removeSafeXMLSemantics(element) {\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\n\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\n }\n\n /**\n * Transforms all xml mathml occurrences that contain semantics to the same\n * xml mathml occurrences without semantics.\n * @param {string} text - string that can contain xml mathml occurrences.\n * @param {Constants} [characters] - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * xmlCharacters by default.\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\n */\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\n const mathTagStart = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const mathTagEndline = `/${characters.tagCloser}`;\n const { tagCloser } = characters;\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\n\n let output = \"\";\n let start = text.indexOf(mathTagStart);\n let end = 0;\n while (start !== -1) {\n output += text.substring(end, start);\n\n // MathML can be written as '' or ''.\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\n const firstTagCloser = text.indexOf(tagCloser, start);\n if (mathTagEndIndex !== -1) {\n end = mathTagEndIndex;\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\n end = mathTagEndlineIndex;\n }\n\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\n if (semanticsIndex !== -1) {\n const mmlTagStart = text.substring(start, semanticsIndex);\n const annotationIndex = text.indexOf(annotationTagStart, start);\n if (annotationIndex !== -1) {\n const startIndex = semanticsIndex + semanticsTagStart.length;\n const mmlContent = text.substring(startIndex, annotationIndex);\n output += mmlTagStart + mmlContent + mathTagEnd;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n end += mathTagEnd.length;\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n }\n\n output += text.substring(end, text.length);\n return output;\n }\n\n /**\n * Returns true if a MathML contains a certain class.\n * @param {string} mathML - input MathML.\n * @param {string} className - className.\n * @returns {boolean} true if the input MathML contains the input class.\n * false otherwise.\n * @static\n */\n static containClass(mathML, className) {\n const classIndex = mathML.indexOf(\"class\");\n if (classIndex === -1) {\n return false;\n }\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\n const classTag = mathML.substring(classIndex, classTagEndIndex);\n if (classTag.indexOf(className) !== -1) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns true if mathml is empty. Otherwise, false.\n * @param {string} mathml - valid MathML with standard XML tags.\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\n */\n static isEmpty(mathml) {\n // MathML can have the shape or ''.\n const closeTag = \">\";\n const closeTagInline = \"/>\";\n const firstCloseTagIndex = mathml.indexOf(closeTag);\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\n let empty = false;\n // MathML is always empty in the second shape.\n if (firstCloseTagInlineIndex !== -1) {\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\n empty = true;\n }\n }\n\n // MathML is always empty in the first shape when there aren't elements\n // between math tags.\n if (!empty) {\n const mathTagEndRegex = new RegExp(\"\");\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\n if (mathTagEndArray) {\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\n }\n }\n\n return empty;\n }\n\n /**\n * Encodes html entities inside properties.\n * @param {String} mathml - valid MathML with standard XML tags.\n * @returns {String} - 'mathml' with property entities encoded.\n */\n static encodeProperties(mathml) {\n // Search all the properties.\n const regex = /\\w+=\".*?\"/g;\n // Encode html entities.\n const replacer = (match) => {\n // It has the shape:\n // .\n const quoteIndex = match.indexOf('\"');\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\n return matchEncoded;\n };\n\n const mathmlEncoded = mathml.replace(regex, replacer);\n return mathmlEncoded;\n }\n}\n","/**\n * This class represents the configuration class.\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\n */\nexport default class Configuration {\n /**\n * Adds a properties object to {@link Configuration.properties}.\n * @param {Object} properties - properties to append to current properties.\n */\n static addConfiguration(properties) {\n Object.assign(Configuration.properties, properties);\n }\n\n /**\n * Static property.\n * The configuration properties object.\n * @private\n * @type {Object}\n */\n static get properties() {\n return Configuration._properties;\n }\n\n /**\n * Static property setter.\n * Set configuration properties.\n * @param {Object} value - The property value.\n * @ignore\n */\n static set properties(value) {\n Configuration._properties = value;\n }\n\n /**\n * Returns the value of a property key.\n * @param {String} key - Property key\n * @returns {String} Property value\n */\n static get(key) {\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\n // Backwards compatibility.\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\n return Configuration.properties[`_wrs_conf_${key}`];\n }\n return false;\n }\n return Configuration.properties[key];\n }\n\n /**\n * Adds a new property to Configuration class.\n * @param {String} key - Property key.\n * @param {Object} value - Property value.\n */\n static set(key, value) {\n Configuration.properties[key] = value;\n }\n\n /**\n * Updates a property object value with new values.\n * @param {String} key - The property key to be updated.\n * @param {Object} propertyValue - Object containing the new values.\n */\n static update(key, propertyValue) {\n if (!Configuration.get(key)) {\n Configuration.set(key, propertyValue);\n } else {\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\n Configuration.set(key, updateProperty);\n }\n }\n}\n\n/**\n * Static properties object. Stores all configuration properties.\n * Needed to attribute accessors.\n * @private\n * @type {Object}\n */\nConfiguration._properties = {};\n","export default class TextCache {\n /**\n * @classdesc\n * This class represent a client-side text cache class. Contains pairs of\n * strings (key/value) which can be retrieved in any moment. Usually used\n * to store AJAX responses for text services like mathml2latex\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\n * @constructs\n */\n constructor() {\n /**\n * Cache array property storing the cache entries.\n * @type {Array.}\n */\n this.cache = [];\n }\n\n /**\n * This method populates a key/value pair into the {@link this.cache} property.\n * @param {String} key - Cache key, usually the service string parameter.\n * @param {String} value - Cache value, usually the service response.\n */\n populate(key, value) {\n this.cache[key] = value;\n }\n\n /**\n * Returns the cache value associated to certain cache key.\n * @param {String} key - Cache key, usually the service string parameter.\n * @return {String} value - Cache value, if exists. False otherwise.\n */\n get(key) {\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\n return this.cache[key];\n }\n return false;\n }\n}\n","/**\n * This object represents a custom listener.\n * @typedef {Object} Listener\n * @property {String} Listener.eventName - The listener name.\n * @property {Function} Listener.callback - The listener callback function.\n */\n\nexport default class Listeners {\n /**\n * @classdesc\n * This class represents a custom listeners manager.\n * @constructs\n */\n constructor() {\n /**\n * Array containing all custom listeners.\n * @type {Object[]}\n */\n this.listeners = [];\n }\n\n /**\n * Add a listener to Listener class.\n * @param {Object} listener - A listener object.\n */\n add(listener) {\n this.listeners.push(listener);\n }\n\n /**\n * Fires MathType event listeners\n * @param {String} eventName - event name\n * @param {Event} event - event object.\n * @return {boolean} false if event has been prevented. true otherwise.\n */\n fire(eventName, event) {\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\n if (this.listeners[i].eventName === eventName) {\n // Calling listener.\n this.listeners[i].callback(event);\n }\n }\n return event.defaultPrevented;\n }\n\n /**\n * Creates a new listener object.\n * @param {string} eventName - Event name.\n * @param {Object} callback - Callback function.\n * @returns {object} the listener object.\n */\n static newListener(eventName, callback) {\n const listener = {};\n listener.eventName = eventName;\n listener.callback = callback;\n return listener;\n }\n}\n","import Util from \"./util\";\nimport Listeners from \"./listeners\";\nimport Configuration from \"./configuration\";\n\n/**\n * @typedef {Object} ServiceProviderProperties\n * @property {String} URI - Service URI.\n * @property {String} server - Service server language.\n */\n\n/**\n * @classdesc\n * Class representing a serviceProvider. A serviceProvider is a class containing\n * an arbitrary number of services with the correspondent path.\n */\nexport default class ServiceProvider {\n /**\n * Returns Service Provider listeners.\n * @type {Listeners}\n */\n static get listeners() {\n return ServiceProvider._listeners;\n }\n\n /**\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\n * @param {Listener} listener - Instance of {@link Listener}.\n */\n static addListener(listener) {\n ServiceProvider.listeners.add(listener);\n }\n\n /**\n * Fires events in Service Provider.\n * @param {String} eventName - Event name.\n * @param {Event} event - Event object.\n */\n static fireEvent(eventName, event) {\n ServiceProvider.listeners.fire(eventName, event);\n }\n\n /**\n * Service parameters.\n * @type {ServiceProviderProperties}\n *\n */\n static get parameters() {\n return ServiceProvider._parameters;\n }\n\n /**\n * Service parameters.\n * @private\n * @type {ServiceProviderProperties}\n */\n static set parameters(parameters) {\n ServiceProvider._parameters = parameters;\n }\n\n /**\n * Static property.\n * Return service provider paths.\n * @private\n * @type {String}\n */\n static get servicePaths() {\n return ServiceProvider._servicePaths;\n }\n\n /**\n * Static property setter.\n * Set service paths.\n * @param {String} value - The property value.\n * @ignore\n */\n static set servicePaths(value) {\n ServiceProvider._servicePaths = value;\n }\n\n /**\n * Adds a new service to the ServiceProvider.\n * @param {String} service - Service name.\n * @param {String} path - Service path.\n * @static\n */\n static setServicePath(service, path) {\n ServiceProvider.servicePaths[service] = path;\n }\n\n /**\n * Returns the service path for a certain service.\n * @param {String} serviceName - Service name.\n * @returns {String} The service path.\n * @static\n */\n static getServicePath(serviceName) {\n return ServiceProvider.servicePaths[serviceName];\n }\n\n /**\n * Static property.\n * Service provider integration path.\n * @type {String}\n */\n static get integrationPath() {\n return ServiceProvider._integrationPath;\n }\n\n /**\n * Static property setter.\n * Set service provider integration path.\n * @param {String} value - The property value.\n * @ignore\n */\n static set integrationPath(value) {\n ServiceProvider._integrationPath = value;\n }\n\n /**\n * Returns the server URL in the form protocol://serverName:serverPort.\n * @return {String} The client side server path.\n */\n static getServerURL() {\n const url = window.location.href;\n const arr = url.split(\"/\");\n const result = `${arr[0]}//${arr[2]}`;\n return result;\n }\n\n /**\n * Inits {@link this} class. Uses {@link this.integrationPath} as\n * base path to generate all backend services paths.\n * @param {Object} parameters - Function parameters.\n * @param {String} parameters.integrationPath - Service path.\n */\n static init(parameters) {\n ServiceProvider.parameters = parameters;\n // Services path (tech dependant).\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\n\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\n // for example: /app/service. For them we calculate the absolute URL path, i.e\n // protocol://domain:port/app/service\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\n const serverPath = ServiceProvider.getServerURL();\n configurationURI = serverPath + configurationURI;\n showImageURI = serverPath + showImageURI;\n createImageURI = serverPath + createImageURI;\n getMathMLURI = serverPath + getMathMLURI;\n serviceURI = serverPath + serviceURI;\n }\n\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\n ServiceProvider.setServicePath(\"service\", serviceURI);\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n\n ServiceProvider.listeners.fire(\"onInit\", {});\n }\n\n /**\n * Gets the content from an URL.\n * @param {String} url - Target URL.\n * @param {Object} [postVariables] - Object containing post variables.\n * null if a GET query should be done.\n * @returns {String} Content of the target URL.\n * @private\n * @static\n */\n static getUrl(url, postVariables) {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n const httpRequest = Util.createHttpRequest();\n\n if (httpRequest) {\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\n httpRequest.open(\"GET\", url, false);\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\n httpRequest.open(\"POST\", url, false);\n } else {\n httpRequest.open(\"POST\", currentPath + url, false);\n }\n\n let header = Configuration.get(\"customHeaders\");\n if (header) {\n if (typeof header === \"string\") {\n header = Util.convertStringToObject(header);\n }\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\n }\n\n if (typeof postVariables !== \"undefined\" && postVariables) {\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n httpRequest.send(Util.httpBuildQuery(postVariables));\n } else {\n httpRequest.send(null);\n }\n\n return httpRequest.responseText;\n }\n return \"\";\n }\n\n /**\n * Returns the response text of a certain service.\n * @param {String} service - Service name.\n * @param {String} postVariables - Post variables.\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\n * @returns {String} Service response text.\n */\n static getService(service, postVariables, get) {\n let response;\n if (get === true) {\n const getVariables = postVariables ? `?${postVariables}` : \"\";\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\n response = ServiceProvider.getUrl(serviceUrl);\n } else {\n const serviceUrl = ServiceProvider.getServicePath(service);\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\n }\n return response;\n }\n\n /**\n * Returns the server language of a certain service. The possible values\n * are: php, aspx, java and ruby.\n * This method has backward compatibility purposes.\n * @param {String} service - The configuration service.\n * @returns {String} - The server technology associated with the configuration service.\n */\n static getServerLanguageFromService(service) {\n if (service.indexOf(\".php\") !== -1) {\n return \"php\";\n }\n if (service.indexOf(\".aspx\") !== -1) {\n return \"aspx\";\n }\n if (service.indexOf(\"wirispluginengine\") !== -1) {\n return \"ruby\";\n }\n return \"java\";\n }\n\n /**\n * Returns the URI associated with a certain service.\n * @param {String} service - The service name.\n * @return {String} The service path.\n */\n static createServiceURI(service) {\n const extension = ServiceProvider.serverExtension();\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\n }\n\n static serverExtension() {\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\n return \".php\";\n }\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\n return \".aspx\";\n }\n return \"\";\n }\n}\n\n/**\n * @property {String} service - The service name.\n * @property {String} path - The service path.\n * @static\n */\nServiceProvider._servicePaths = {};\n\n/**\n * The integration path. Contains the path of the configuration service.\n * Used to define the path for all services.\n * @type {String}\n * @private\n */\nServiceProvider._integrationPath = \"\";\n\n/**\n * ServiceProvider static listeners.\n * @type {Listeners}\n * @private\n */\nServiceProvider._listeners = new Listeners();\n\n/**\n * Service provider parameters.\n * @type {ServiceProviderParameters}\n */\nServiceProvider._parameters = {};\n","import TextCache from \"./textcache\";\nimport MathML from \"./mathml\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a LaTeX parser. Manages the services which allows to convert\n * LaTeX into MathML and MathML into LaTeX.\n */\nexport default class Latex {\n /**\n * Static property.\n * Return latex cache.\n * @private\n * @type {Cache}\n */\n static get cache() {\n return Latex._cache;\n }\n\n /**\n * Static property setter.\n * Set latex cache.\n * @param {Cache} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Latex._cache = value;\n }\n\n /**\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\n * we call a text service with the param mathml2latex.\n * @param {String} mathml - MathML String.\n * @return {String} LaTeX string generated by the MathML argument.\n */\n static getLatexFromMathML(mathml) {\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\n /**\n * @type {TextCache}\n */\n const { cache } = Latex;\n\n const data = {\n service: \"mathml2latex\",\n mml: mathmlWithoutSemantics,\n };\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n // TODO: Error handling.\n let latex = \"\";\n\n if (jsonResponse.status === \"ok\") {\n latex = jsonResponse.result.text;\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\n // Inserting LaTeX semantics.\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\n cache.populate(latex, mathmlWithSemantics);\n }\n\n return latex;\n }\n\n /**\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\n * we call a text service with the param latex2mathml.\n * @param {String} latex - String containing a LaTeX formula.\n * @param {Boolean} includeLatexOnSemantics\n * - If true LaTeX would me included into MathML semantics.\n * @return {String} MathML string generated by the LaTeX argument.\n */\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\n /**\n * @type {TextCache}\n */\n const latexCache = Latex.cache;\n\n if (Latex.cache.get(latex)) {\n return Latex.cache.get(latex);\n }\n const data = {\n service: \"latex2mathml\",\n latex,\n };\n\n if (includeLatexOnSemantics) {\n data.saveLatex = \"\";\n }\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n let output;\n if (jsonResponse.status === \"ok\") {\n let mathml = jsonResponse.result.text;\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\n\n // Populate LatexCache.\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\n const content = Util.htmlEntities(latex);\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\n output = mathml;\n } else {\n output = mathml;\n }\n if (!latexCache.get(latex)) {\n latexCache.populate(latex, mathml);\n }\n } else {\n output = `$$${latex}$$`;\n }\n return output;\n }\n\n /**\n * Converts all occurrences of MathML code to LaTeX.\n * The MathML code should containing to be converted.\n * @param {String} content - A string containing MathML valid code.\n * @param {Object} characters - An object containing special characters.\n * @return {String} A string containing all MathML annotated occurrences\n * replaced by the corresponding LaTeX code.\n */\n static parseMathmlToLatex(content, characters) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n let mathml;\n let startAnnotation;\n let closeAnnotation;\n\n while (start !== -1) {\n output += content.substring(end, start);\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else {\n end += mathTagEnd.length;\n }\n\n mathml = content.substring(start, end);\n\n startAnnotation = mathml.indexOf(openTarget);\n if (startAnnotation !== -1) {\n startAnnotation += openTarget.length;\n closeAnnotation = mathml.indexOf(closeTarget);\n let latex = mathml.substring(startAnnotation, closeAnnotation);\n if (characters === Constants.safeXmlCharacters) {\n latex = MathML.safeXmlDecode(latex);\n }\n output += `$$${latex}$$`;\n // Populate latex into cache.\n\n Latex.cache.populate(latex, mathml);\n } else {\n output += mathml;\n }\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n\n /**\n * Extracts the latex of a determined position in a text.\n * @param {Node} textNode - textNode to extract the LaTeX\n * @param {Number} caretPosition - Starting position to find LaTeX.\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\n * \"$$\" by default.\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\n * @static\n */\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\n // Tags used for LaTeX formulas.\n const defaultLatexTags = {\n open: \"$$\",\n close: \"$$\",\n };\n // latexTags is an optional parameter. If is not set, use default latexTags.\n if (typeof latexTags === \"undefined\" || latexTags == null) {\n latexTags = defaultLatexTags;\n }\n // Looking for the first textNode.\n let startNode = textNode;\n\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\n // TEXT_NODE.\n startNode = startNode.previousSibling;\n }\n\n /**\n * Returns the next latex position and node from a specific node and position.\n * @param {Node} currentNode - Node where searching latex.\n * @param {Number} currentPosition - Current position inside the currentNode.\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\n * \"$$\" by default.\n * @param {Boolean} tag - Tag containing the current search.\n * @returns {Object} Object containing the current node and the position.\n */\n function getNextLatexPosition(currentNode, currentPosition, tag) {\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\n\n while (position === -1) {\n currentNode = currentNode.nextSibling;\n\n if (!currentNode) {\n // TEXT_NODE.\n return null; // Not found.\n }\n\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\n }\n\n return {\n node: currentNode,\n position,\n };\n }\n\n /**\n * Determines if a node is previous, or not, to a second one.\n * @param {Node} node - Start node.\n * @param {Number} position - Start node position.\n * @param {Node} endNode - End node.\n * @param {Number} endPosition - End node position.\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\n */\n function isPrevious(node, position, endNode, endPosition) {\n if (node === endNode) {\n return position <= endPosition;\n }\n while (node && node !== endNode) {\n node = node.nextSibling;\n }\n\n return node === endNode;\n }\n\n let start;\n let end = {\n node: startNode,\n position: 0,\n };\n // Is supposed that open and close tags has the same length.\n const tagLength = latexTags.open.length;\n do {\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\n\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\n return null;\n }\n\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\n\n if (end == null) {\n return null;\n }\n\n end.position += tagLength;\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\n\n // Isolating latex.\n let latex;\n\n if (start.node === end.node) {\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\n } else {\n const index = start.position + tagLength;\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\n let currentNode = start.node;\n\n do {\n currentNode = currentNode.nextSibling;\n if (currentNode === end.node) {\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\n } else {\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\n }\n } while (currentNode !== end.node);\n }\n\n return {\n latex,\n startNode: start.node,\n startPosition: start.position,\n endNode: end.node,\n endPosition: end.position,\n };\n }\n}\n\n/**\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\n * @type {Cache}\n * @static\n */\nLatex._cache = new TextCache();\n","import translations from \"../lang/strings.json\";\n/**\n * This class represents a string manager. It's used to load localized strings.\n */\nexport default class StringManager {\n constructor() {\n throw new Error(\"Static class StringManager can not be instantiated.\");\n }\n\n /**\n * Returns the associated value of certain string key. If the associated value\n * doesn't exits returns the original key.\n * @param {string} key - string key\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\n * @returns {string} correspondent value. If doesn't exists original key.\n */\n static get(key, lang) {\n // Default language definition\n let { language } = this;\n\n // If parameter language, use it\n if (lang) {\n language = lang;\n }\n\n // Cut down on strings. e.g. en_US -> en\n if (language && language.length > 2) {\n language = language.slice(0, 2);\n }\n\n // Check if we support the language\n if (!this.strings.hasOwnProperty(language)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown language ${language} set in StringManager.`);\n language = \"en\";\n }\n\n // Check if the key is supported in the given language\n if (!this.strings[language].hasOwnProperty(key)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\n return key;\n }\n\n return this.strings[language][key];\n }\n}\n\n/**\n * Dictionary of dictionaries:\n * Key: language code\n * Value: Key: id of the string\n * Value: translation of the string\n */\nStringManager.strings = translations;\n\n/**\n * Language of the translations; English by default\n */\nStringManager.language = \"en\";\n","/* eslint-disable no-bitwise */\nimport DOMPurify from \"dompurify\";\nimport MathML from \"./mathml\";\nimport Configuration from \"./configuration\";\nimport Latex from \"./latex\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * This class represents an utility class.\n */\nexport default class Util {\n /**\n * Fires an event in a target.\n * @param {EventTarget} eventTarget - target where event should be fired.\n * @param {string} eventName event to fire.\n * @static\n */\n static fireEvent(eventTarget, eventName) {\n if (document.createEvent) {\n const eventObject = document.createEvent(\"HTMLEvents\");\n eventObject.initEvent(eventName, true, true);\n return !eventTarget.dispatchEvent(eventObject);\n }\n\n const eventObject = document.createEventObject();\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\n }\n\n /**\n * Cross-browser addEventListener/attachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - callback function.\n * @static\n */\n static addEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.addEventListener) {\n eventTarget.addEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.attachEvent) {\n // Backwards compatibility.\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * Cross-browser removeEventListener/detachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - function to remove from the event target.\n * @static\n */\n static removeEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.removeEventListener) {\n eventTarget.removeEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.detachEvent) {\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * A map from event target to event handlers so we can remove the event\n * listeners in removeElementEvents\n *\n * @type {Map}\n * @static\n */\n static elementEventsMap = new Map();\n\n /**\n * Adds the a callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\n * @param {Function} mousedownHandler - function to run when on mousedown event.\n * @param {Function} mouseupHandler - function to run when on mouseup event.\n * @static\n */\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\n // Make sure not to leak event listeners if we've already added events to\n // this element\n Util.removeElementEvents(eventTarget);\n\n let entry = {};\n Util.elementEventsMap.set(eventTarget, entry);\n\n if (doubleClickHandler) {\n entry.callbackDblclick = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n doubleClickHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n }\n\n if (mousedownHandler) {\n entry.callbackMousedown = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mousedownHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n }\n\n if (mouseupHandler) {\n entry.callbackMouseup = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mouseupHandler(element, realEvent);\n };\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\n // while the mouse is outside the editor text field.\n // This is a workaround: we trigger the event independently of where the mouse\n // is when we release its button.\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n }\n\n /**\n * Remove all callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @static\n */\n static removeElementEvents(eventTarget) {\n let entry = Util.elementEventsMap.get(eventTarget);\n if (!entry) {\n return;\n }\n\n Util.elementEventsMap.delete(eventTarget);\n\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n\n /**\n * Adds a class name to a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static addClass(element, className) {\n if (!Util.containsClass(element, className)) {\n element.className += ` ${className}`;\n }\n }\n\n /**\n * Checks if a HTMLElement contains a certain class.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the className.\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\n * @static\n */\n static containsClass(element, className) {\n if (element == null || !(\"className\" in element)) {\n return false;\n }\n\n const currentClasses = element.className.split(\" \");\n\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\n if (currentClasses[i] === className) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Remove a certain class for a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static removeClass(element, className) {\n let newClassName = \"\";\n const classes = element.className.split(\" \");\n\n for (let i = 0; i < classes.length; i += 1) {\n if (classes[i] !== className) {\n newClassName += `${classes[i]} `;\n }\n }\n element.className = newClassName.trim();\n }\n\n /**\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\n * used to parse old applets.\n * @param {string} text - string containing safeXml characters\n * @returns {string} a string with safeXml characters parsed.\n * @static\n */\n static convertOldXmlinitialtextAttribute(text) {\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\n // This could be removed in future.\n const val = \"value=\";\n\n const xitpos = text.indexOf(\"xmlinitialtext\");\n const valpos = text.indexOf(val, xitpos);\n const quote = text.charAt(valpos + val.length);\n const startquote = valpos + val.length + 1;\n const endquote = text.indexOf(quote, startquote);\n\n const value = text.substring(startquote, endquote);\n\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\n newvalue = newvalue.split(\"&\").join(\"ยง\");\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\n\n text = text.split(value).join(newvalue);\n return text;\n }\n\n /**\n * Convert a string representation of key-value pairs to an object.\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\n * @returns {Object} - Object containing the key-value pairs\n */\n static convertStringToObject(keyValueString) {\n if (!keyValueString || typeof keyValueString !== \"string\") {\n return {};\n }\n\n return keyValueString\n .split(\",\")\n .map((pair) => pair.trim().split(\"=\"))\n .reduce((resultObject, [key, value]) => {\n if (key && value) {\n resultObject[key] = value;\n }\n return resultObject;\n }, {});\n }\n\n /**\n * Cross-browser solution for creating new elements.\n * @param {string} tagName - tag name of the wished element.\n * @param {Object} attributes - an object where each key is a wished\n * attribute name and each value is its value.\n * @param {Object} [creator] - if supplied, this function will use\n * the \"createElement\" method from this param. Otherwise\n * document will be used as creator.\n * @returns {Element} The DOM element with the specified attributes assigned.\n * @static\n */\n static createElement(tagName, attributes, creator) {\n if (attributes === undefined) {\n attributes = {};\n }\n\n if (creator === undefined) {\n creator = document;\n }\n\n let element;\n\n /*\n * Internet Explorer fix:\n * If you create a new object dynamically, you can't set a non-standard attribute.\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\n * Other browsers will throw an exception and will run the standard code.\n */\n try {\n let html = `<${tagName}`;\n\n Object.keys(attributes).forEach((attributeName) => {\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\n });\n html += \">\";\n element = creator.createElement(html);\n } catch (e) {\n element = creator.createElement(tagName);\n Object.keys(attributes).forEach((attributeName) => {\n element.setAttribute(attributeName, attributes[attributeName]);\n });\n }\n return element;\n }\n\n /**\n * Creates new HTML from it's HTML code as string.\n * @param {string} objectCode - html code\n * @returns {Element} the HTML element.\n * @static\n */\n static createObject(objectCode, creator) {\n if (creator === undefined) {\n creator = document;\n }\n\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\n objectCode = objectCode\n .split(\"\").join(\"\").split(\"\").join(\"\");\n\n objectCode = objectCode\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\n\n const container = Util.createElement(\"div\", {}, creator);\n container.innerHTML = objectCode;\n\n function recursiveParamsFix(object) {\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const param = Util.createElement(\"param\", attributesParsed, creator);\n\n // IE fix.\n if (param.NAME) {\n param.name = param.NAME;\n param.value = param.VALUE;\n }\n\n param.removeAttribute(\"wirisObject\");\n object.parentNode.replaceChild(param, object);\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\n applet.removeAttribute(\"wirisObject\");\n\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\n applet.appendChild(object.childNodes[i]);\n i -= 1; // When we insert the object child into the applet, object loses one child.\n }\n }\n\n object.parentNode.replaceChild(applet, object);\n } else {\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n }\n }\n }\n\n recursiveParamsFix(container);\n return container.firstChild;\n }\n\n /**\n * Converts an Element to its HTML code.\n * @param {Element} element - entry element.\n * @return {string} the HTML code of the input element.\n * @static\n */\n static createObjectCode(element) {\n // In case that the image was not created, the object can be null or undefined.\n if (typeof element === \"undefined\" || element === null) {\n return null;\n }\n\n if (element.nodeType === 1) {\n // ELEMENT_NODE.\n let output = `<${element.tagName}`;\n\n for (let i = 0; i < element.attributes.length; i += 1) {\n if (element.attributes[i].specified) {\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\n }\n }\n\n if (element.childNodes.length > 0) {\n output += \">\";\n\n for (let i = 0; i < element.childNodes.length; i += 1) {\n output += Util.createObject(element.childNodes[i]);\n }\n\n output += ``;\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\n output += `>`;\n } else {\n output += \"/>\";\n }\n\n return output;\n }\n\n if (element.nodeType === 3) {\n // TEXT_NODE.\n return Util.htmlEntities(element.nodeValue);\n }\n\n return \"\";\n }\n\n /**\n * Concatenates two URL paths.\n * @param {string} path1 - first URL path\n * @param {string} path2 - second URL path\n * @returns {string} new URL.\n */\n static concatenateUrl(path1, path2) {\n let separator = \"\";\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\n separator = \"/\";\n }\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\n }\n\n /**\n * Parses a text and replaces all HTML special characters by their correspondent entities.\n * @param {string} input - text to be parsed.\n * @returns {string} the input text with all their special characters replaced by their entities.\n * @static\n */\n static htmlEntities(input) {\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\n }\n\n /**\n * Sanitize HTML to prevent XSS injections.\n * @param {string} html - html to be sanitize.\n * @returns {string} html sanitized.\n * @static\n */\n static htmlSanitize(html) {\n const annotationRegex = /\\/;\n // Get all the annotation content including the tags.\n const annotation = html.match(annotationRegex);\n // Sanitize html code without removing our supported MathML tags and attributes.\n html = DOMPurify.sanitize(html, {\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\n });\n // Readd old annotation content.\n return html.replace(annotationRegex, annotation);\n }\n\n /**\n * Parses a text and replaces all the HTML entities by their characters.\n * @param {string} input - text to be parsed\n * @returns {string} the input text with all their entities replaced by characters.\n * @static\n */\n static htmlEntitiesDecode(input) {\n // Textarea element decodes when inner html is set.\n const textarea = document.createElement(\"textarea\");\n textarea.innerHTML = input;\n return textarea.value;\n }\n\n /**\n * Returns a cross-browser http request.\n * @return {object} httpRequest request object.\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\n */\n static createHttpRequest() {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n if (currentPath.substr(0, 7) === \"file://\") {\n throw StringManager.get(\"exception_cross_site\");\n }\n\n if (typeof XMLHttpRequest !== \"undefined\") {\n return new XMLHttpRequest();\n }\n\n try {\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\n } catch (e) {\n try {\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\n } catch (oc) {\n return null;\n }\n }\n }\n\n /**\n * Converts a hash to a HTTP query.\n * @param {Object[]} properties - a key/value object.\n * @returns {string} a HTTP query containing all the key value pairs with\n * all the special characters replaced by their entities.\n * @static\n */\n static httpBuildQuery(properties) {\n let result = \"\";\n\n Object.keys(properties).forEach((i) => {\n if (properties[i] != null) {\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\n }\n });\n\n // Deleting last '&' empty character.\n if (result.substring(result.length - 1) === \"&\") {\n result = result.substring(0, result.length - 1);\n }\n\n return result;\n }\n\n /**\n * Convert a hash to string sorting keys to get a deterministic output\n * @param {Object[]} hash - a key/value object.\n * @returns {string} a string with the form key1=value1...keyn=valuen\n * @static\n */\n static propertiesToString(hash) {\n // 1. Sort keys. We sort the keys because we want a deterministic output.\n const keys = [];\n Object.keys(hash).forEach((key) => {\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\n keys.push(key);\n }\n });\n\n const n = keys.length;\n for (let i = 0; i < n; i += 1) {\n for (let j = i + 1; j < n; j += 1) {\n const s1 = keys[i];\n const s2 = keys[j];\n if (Util.compareStrings(s1, s2) > 0) {\n // Swap.\n keys[i] = s2;\n keys[j] = s1;\n }\n }\n }\n\n // 2. Generate output.\n let output = \"\";\n for (let i = 0; i < n; i += 1) {\n const key = keys[i];\n output += key;\n output += \"=\";\n let value = hash[key];\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\n value = value.replace(\"\\n\", \"\\\\n\");\n value = value.replace(\"\\r\", \"\\\\r\");\n value = value.replace(\"\\t\", \"\\\\t\");\n\n output += value;\n output += \"\\n\";\n }\n return output;\n }\n\n /**\n * Compare two strings using charCodeAt method\n * @param {string} a - first string to compare.\n * @param {string} b - second string to compare.\n * @returns {number} the difference between a and b\n * @static\n */\n static compareStrings(a, b) {\n let i;\n const an = a.length;\n const bn = b.length;\n const n = an > bn ? bn : an;\n for (i = 0; i < n; i += 1) {\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\n if (c !== 0) {\n return c;\n }\n }\n return a.length - b.length;\n }\n\n /**\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\n * @param {string} string - input string\n * @param {number} idx - an integer greater than or equal to 0\n * and less than the length of the string\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\n * @static\n */\n static fixedCharCodeAt(string, idx) {\n idx = idx || 0;\n const code = string.charCodeAt(idx);\n let hi;\n let low;\n\n /* High surrogate (could change last hex to 0xDB7F to treat high\n private surrogates as single characters) */\n\n if (code >= 0xd800 && code <= 0xdbff) {\n hi = code;\n low = string.charCodeAt(idx + 1);\n if (Number.isNaN(low)) {\n throw StringManager.get(\"exception_high_surrogate\");\n }\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\n }\n\n if (code >= 0xdc00 && code <= 0xdfff) {\n // Low surrogate.\n /* We return false to allow loops to skip this iteration since should have\n already handled high surrogate above in the previous iteration. */\n return false;\n }\n return code;\n }\n\n /**\n * Returns an URL with it's query params converted into array.\n * @param {string} url - input URL.\n * @returns {Object[]} an array containing all URL query params.\n * @static\n */\n static urlToAssArray(url) {\n let i;\n i = url.indexOf(\"?\");\n if (i > 0) {\n const query = url.substring(i + 1);\n const ss = query.split(\"&\");\n const h = {};\n for (i = 0; i < ss.length; i += 1) {\n const s = ss[i];\n const kv = s.split(\"=\");\n if (kv.length > 1) {\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\n }\n }\n return h;\n }\n return {};\n }\n\n /**\n * Returns an encoded URL by replacing each instance of certain characters by\n * one, two, three or four escape sequences using encodeURIComponent method.\n * !'()* . will not be encoded.\n *\n * @param {string} clearString - URL string to be encoded\n * @returns {string} URL with it's special characters replaced.\n * @static\n */\n static urlEncode(clearString) {\n let output = \"\";\n // Method encodeURIComponent doesn't encode !'()*~ .\n output = encodeURIComponent(clearString);\n return output;\n }\n\n // TODO: To parser?\n /**\n * Converts the HTML of a image into the output code that WIRIS must return.\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\n * or the Wiriscas attribute of a WIRIS applet.\n * @param {string} imgCode - the html code from a formula or a CAS image.\n * @param {boolean} convertToXml - true if the image should be converted to XML.\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\n * @returns {string} the XML or safeXML of a WIRIS image.\n * @static\n */\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\n const imgObject = Util.createObject(imgCode);\n if (imgObject) {\n if (\n imgObject.className === Configuration.get(\"imageClassName\") ||\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\n ) {\n if (!convertToXml) {\n return imgCode;\n }\n\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n // To handle annotations, first we need the MathML in XML.\n let mathML = MathML.safeXmlDecode(dataMathML);\n\n if (!Configuration.get(\"saveHandTraces\")) {\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\n }\n\n if (mathML == null) {\n mathML = imgObject.getAttribute(\"alt\");\n }\n\n if (convertToSafeXml) {\n const safeMathML = MathML.safeXmlEncode(mathML);\n return safeMathML;\n }\n\n return mathML;\n }\n }\n return imgCode;\n }\n\n /**\n * Gets the node length in characters.\n * @param {Node} node - HTML node.\n * @returns {number} node length.\n * @static\n */\n static getNodeLength(node) {\n const staticNodeLengths = {\n IMG: 1,\n BR: 1,\n };\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return node.nodeValue.length;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\n\n if (length === undefined) {\n length = 0;\n }\n\n for (let i = 0; i < node.childNodes.length; i += 1) {\n length += Util.getNodeLength(node.childNodes[i]);\n }\n return length;\n }\n return 0;\n }\n\n /**\n * Gets a selected node or text from an editable HTMLElement.\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\n * @param {HTMLElement} target - the editable HTMLElement.\n * @param {boolean} isIframe - specifies if the target is an iframe or not\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\n * the current selection and uses window.getSelection()\n * @returns {object} an object with the 'node' key set if the item is an\n * element or the keys 'node' and 'caretPosition' if the element is text.\n * @static\n */\n static getSelectedItem(target, isIframe, forceGetSelection) {\n let windowTarget;\n\n if (isIframe) {\n windowTarget = target.contentWindow;\n windowTarget.focus();\n } else {\n windowTarget = window;\n target.focus();\n }\n\n if (document.selection && !forceGetSelection) {\n const range = windowTarget.document.selection.createRange();\n\n if (range.parentElement) {\n if (range.htmlText.length > 0) {\n if (range.text.length === 0) {\n return Util.getSelectedItem(target, isIframe, true);\n }\n\n return null;\n }\n\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\n let temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\n // IE9 fix: parentElement() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML('');\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\n }\n\n let node;\n let caretPosition;\n\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\n // TEXT_NODE.\n node = temporalObject.nextSibling;\n caretPosition = 0;\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\n node = temporalObject.previousSibling;\n caretPosition = node.nodeValue.length;\n } else {\n node = windowTarget.document.createTextNode(\"\");\n temporalObject.parentNode.insertBefore(node, temporalObject);\n caretPosition = 0;\n }\n\n temporalObject.parentNode.removeChild(temporalObject);\n\n return {\n node,\n caretPosition,\n };\n }\n\n if (range.length > 1) {\n return null;\n }\n\n return {\n node: range.item(0),\n };\n }\n\n if (windowTarget.getSelection) {\n let range;\n const selection = windowTarget.getSelection();\n\n try {\n range = selection.getRangeAt(0);\n } catch (e) {\n range = windowTarget.document.createRange();\n }\n\n const node = range.startContainer;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return {\n node,\n caretPosition: range.startOffset,\n };\n }\n\n if (node !== range.endContainer) {\n return null;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n const position = range.startOffset;\n\n if (node.childNodes[position]) {\n // In case that a formula is detected but not selected,\n // we create an empty span where we could insert the new formula.\n if (range.startOffset === range.endOffset) {\n if (\n position !== 0 &&\n node.childNodes[position - 1].localName === \"span\" &&\n node.childNodes[position].classList?.contains(\"Wirisformula\")\n ) {\n node.childNodes[position - 1].remove();\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\n }\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\n if (\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\n position === 0\n ) {\n const emptySpan = document.createElement(\"span\");\n node.insertBefore(emptySpan, node.childNodes[position]);\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n\n return null;\n }\n\n /**\n * Returns null if there isn't any item or if it is malformed.\n * Otherwise returns an object containing the node with the MathML image\n * and the cursor position inside the textarea.\n * @param {HTMLTextAreaElement} textarea - textarea element.\n * @returns {Object} An object containing the node, the index of the\n * beginning of the selected text, caret position and the start and end position of the\n * text node.\n * @static\n */\n static getSelectedItemOnTextarea(textarea) {\n const textNode = document.createTextNode(textarea.value);\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\n if (textNodeValues === null) {\n return null;\n }\n\n return {\n node: textNode,\n caretPosition: textarea.selectionStart,\n startPosition: textNodeValues.startPosition,\n endPosition: textNodeValues.endPosition,\n };\n }\n\n /**\n * Looks for elements that match the given name in a HTML code string.\n * Important: this function is very concrete for WIRIS code.\n * It takes as preconditions lots of behaviors that are not the general case.\n * @param {string} code - HTML code.\n * @param {string} name - element name.\n * @param {boolean} autoClosed - true if the elements are autoClosed.\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\n * @static\n */\n static getElementsByNameFromString(code, name, autoClosed) {\n const elements = [];\n code = code.toLowerCase();\n name = name.toLowerCase();\n let start = code.indexOf(`<${name} `);\n\n while (start !== -1) {\n // Look for nodes.\n let endString;\n\n if (autoClosed) {\n endString = \">\";\n } else {\n endString = ``;\n }\n\n let end = code.indexOf(endString, start);\n\n if (end !== -1) {\n end += endString.length;\n elements.push({\n start,\n end,\n });\n } else {\n end = start + 1;\n }\n\n start = code.indexOf(`<${name} `, end);\n }\n\n return elements;\n }\n\n /**\n * Returns the numeric value of a base64 character.\n * @param {string} character - base64 character.\n * @returns {number} base64 character numeric value.\n * @static\n */\n static decode64(character) {\n const PLUS = \"+\".charCodeAt(0);\n const SLASH = \"/\".charCodeAt(0);\n const NUMBER = \"0\".charCodeAt(0);\n const LOWER = \"a\".charCodeAt(0);\n const UPPER = \"A\".charCodeAt(0);\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\n const code = character.charCodeAt(0);\n\n if (code === PLUS || code === PLUS_URL_SAFE) {\n return 62; // Char '+'.\n }\n if (code === SLASH || code === SLASH_URL_SAFE) {\n return 63; // Char '/'.\n }\n if (code < NUMBER) {\n return -1; // No match.\n }\n if (code < NUMBER + 10) {\n return code - NUMBER + 26 + 26;\n }\n if (code < UPPER + 26) {\n return code - UPPER;\n }\n if (code < LOWER + 26) {\n return code - LOWER + 26;\n }\n\n return null;\n }\n\n /**\n * Converts a base64 string to a array of bytes.\n * @param {string} b64String - base64 string.\n * @param {number} length - dimension of byte array (by default whole string).\n * @return {Object[]} the resultant byte array.\n * @static\n */\n static b64ToByteArray(b64String, length) {\n let tmp;\n\n if (b64String.length % 4 > 0) {\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\n }\n\n const arr = [];\n\n let l;\n let placeHolders;\n if (!length) {\n // All b64String string.\n if (b64String.charAt(b64String.length - 2) === \"=\") {\n placeHolders = 2;\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\n placeHolders = 1;\n } else {\n placeHolders = 0;\n }\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\n } else {\n l = length;\n }\n\n let i;\n for (i = 0; i < l; i += 4) {\n // Ignoring code checker standards (bitewise operators).\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 18) |\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\n Util.decode64(b64String.charAt(i + 3));\n\n arr.push((tmp >> 16) & 0xff);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n\n if (placeHolders) {\n if (placeHolders === 2) {\n // Ignoring code checker standards (bitewise operators).\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\n arr.push(tmp & 0xff);\n } else if (placeHolders === 1) {\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 10) |\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n }\n return arr;\n }\n\n /**\n * Returns the first 32-bit signed integer from a byte array.\n * @param {Object[]} bytes - array of bytes.\n * @returns {number} the 32-bit signed integer.\n * @static\n */\n static readInt32(bytes) {\n if (bytes.length < 4) {\n return false;\n }\n const int32 = bytes.splice(0, 4);\n // @codingStandardsIgnoreStartยก\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read the first byte from a byte array.\n * @param {Object} bytes - input byte array.\n * @returns {number} first byte of the byte array.\n * @static\n */\n static readByte(bytes) {\n // @codingStandardsIgnoreStart\n return bytes.shift() << 0;\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\n * @param {Object[]} bytes - byte array.\n * @param {number} pos - start position.\n * @param {number} len - number of bytes to read.\n * @returns {Object[]} the byte array.\n * @static\n */\n static readBytes(bytes, pos, len) {\n return bytes.splice(pos, len);\n }\n\n /**\n * Inserts or modifies formulas or CAS on a textarea.\n * @param {HTMLTextAreaElement} textarea - textarea target.\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\n * call this function as (textarea, Parser.createImageSrc(mathml));\n * @static\n */\n static updateTextArea(textarea, text) {\n if (textarea && text) {\n textarea.focus();\n\n if (textarea.selectionStart != null) {\n const { selectionEnd } = textarea;\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\n textarea.value = selectionStart + text + selectionEndSub;\n textarea.selectionEnd = selectionEnd + text.length;\n } else {\n const selection = document.selection.createRange();\n selection.text = text;\n }\n }\n }\n\n /**\n * Modifies existing formula on a textarea.\n * @param {HTMLTextAreaElement} textarea - text area target.\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\n * to the image,you can call this function as\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\n * @param {number} end - ending index from textarea where it needs to be replaced by text\n * @static\n */\n static updateExistingTextOnTextarea(textarea, text, start, end) {\n textarea.focus();\n const textareaStart = textarea.value.substring(0, start);\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\n textarea.selectionEnd = start + text.length;\n }\n\n /**\n * Add a parameter with it's correspondent value to an URL (GET).\n * @param {string} path - URL path\n * @param {string} parameter - parameter\n * @param {string} value - value\n * @static\n */\n static addArgument(path, parameter, value) {\n let sep;\n if (path.indexOf(\"?\") > 0) {\n sep = \"&\";\n } else {\n sep = \"?\";\n }\n return `${path + sep + parameter}=${value}`;\n }\n}\n","import Configuration from \"./configuration\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents MathType Image class. Contains all the logic related\n * to MathType images manipulation.\n * All MathType images are generated using the appropriate MathType\n * integration service: showimage or createimage.\n *\n * There are two available image formats:\n * - svg (default)\n * - png\n *\n * There are two formats for the image src attribute:\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\n * - A link to the showimage service.\n */\nexport default class Image {\n /**\n * Removes data attributes from an image.\n * @param {HTMLImageElement} img - Image where remove data attributes.\n */\n static removeImgDataAttributes(img) {\n const attributesToRemove = [];\n const { attributes } = img;\n\n Object.keys(attributes).forEach((key) => {\n const attribute = attributes[key];\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\n // Is preferred keep an array and remove after the search\n // because when attribute is removed the array of attributes\n // is modified.\n attributesToRemove.push(attribute.name);\n }\n });\n\n attributesToRemove.forEach((attribute) => {\n img.removeAttribute(attribute);\n });\n }\n\n /**\n * @static\n * Clones all MathType image attributes from a HTMLImageElement to another.\n * @param {HTMLImageElement} originImg - The original image.\n * @param {HTMLImageElement} destImg - The destination image.\n */\n static clone(originImg, destImg) {\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (!originImg.hasAttribute(customEditorAttributeName)) {\n destImg.removeAttribute(customEditorAttributeName);\n }\n\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\n const imgAttributes = [\n mathmlAttributeName,\n customEditorAttributeName,\n \"alt\",\n \"height\",\n \"width\",\n \"style\",\n \"src\",\n \"role\",\n ];\n\n imgAttributes.forEach((iterator) => {\n const originAttribute = originImg.getAttribute(iterator);\n if (originAttribute) {\n destImg.setAttribute(iterator, originAttribute);\n }\n });\n }\n\n /**\n * Determines whether an img src contains an SVG.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src contains an SVG, false otherwise\n */\n static isSvg(img) {\n return img.src.startsWith(\"data:image/svg+xml;\");\n }\n\n /**\n * Determines whether an img src is encoded in base64 or not.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src is encoded in base64, false otherwise\n */\n static isBase64(img) {\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\n }\n\n /**\n * Calculates the metrics of a MathType image given the the service response and the image format.\n * @param {HTMLImageElement} img - The HTMLImageElement.\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\n * @param {Boolean} jsonResponse - True the response of the image service is a\n * JSON object. False otherwise.\n */\n static setImgSize(img, uri, jsonResponse) {\n let ar;\n let base64String;\n let bytes;\n let svgString;\n if (jsonResponse) {\n // Cleaning data:image/png;base64.\n if (Image.isSvg(img)) {\n // SVG format.\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\n if (!Image.isBase64(img)) {\n ar = Image.getMetricsFromSvgString(uri);\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n svgString = \"\";\n bytes = Util.b64ToByteArray(base64String, base64String.length);\n for (let i = 0; i < bytes.length; i += 1) {\n svgString += String.fromCharCode(bytes[i]);\n }\n ar = Image.getMetricsFromSvgString(svgString);\n }\n // PNG format: we store all metrics information in the first 88 bytes.\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n bytes = Util.b64ToByteArray(base64String, 88);\n ar = Image.getMetricsFromBytes(bytes);\n }\n // Backwards compatibility: we store the metrics into createimage response.\n } else {\n ar = Util.urlToAssArray(uri);\n }\n let width = ar.cw;\n if (!width) {\n return;\n }\n let height = ar.ch;\n let baseline = ar.cb;\n const { dpi } = ar;\n if (dpi) {\n width = (width * 96) / dpi;\n height = (height * 96) / dpi;\n baseline = (baseline * 96) / dpi;\n }\n img.width = width;\n img.height = height;\n img.style.verticalAlign = `-${height - baseline}px`;\n }\n\n /**\n * Calculates the metrics of an image which has been resized. Is used to restore the original\n * metrics of a resized image.\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\n */\n static fixAfterResize(img) {\n img.removeAttribute(\"style\");\n img.removeAttribute(\"width\");\n img.removeAttribute(\"height\");\n // In order to avoid resize with max-width css property.\n img.style.maxWidth = \"none\";\n\n const processImg = (img) => {\n if (img.src.indexOf(\"data:image\") !== -1) {\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\n // (which would actually make more sense for readibility and efficiency).\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\n // 'data:image/svg+xml;base64,'.length === 26\n const base64String = img.getAttribute(\"src\").substring(26);\n const svgString = window.atob(base64String);\n const encodedSvgString = encodeURIComponent(svgString);\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n // Return src to base64!\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\n } else {\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n }\n } else {\n // 'data:image/png;base64,' === 22.\n const base64 = img.src.substring(22, img.src.length);\n Image.setImgSize(img, base64, true);\n }\n } else {\n Image.setImgSize(img, img.src);\n }\n };\n\n // If the image doesn't contain a blob, just process it normally\n if (img.src.indexOf(\"blob:\") === -1) {\n processImg(img);\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\n } else {\n const reader = new FileReader();\n reader.onload = function () {\n img.setAttribute(\"src\", reader.result);\n processImg(img);\n };\n fetch(img.src)\n .then((r) => r.blob())\n .then((blob) => {\n reader.readAsDataURL(blob);\n });\n }\n }\n\n /**\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\n * by the MathType image service. This image contains as an extra custom attribute:\n * the baseline (wrs:baseline).\n * @param {String} svgString - The SVG image.\n * @return {Array} - The image metrics.\n */\n static getMetricsFromSvgString(svgString) {\n let first = svgString.indexOf('height=\"');\n let last = svgString.indexOf('\"', first + 8, svgString.length);\n const height = svgString.substring(first + 8, last);\n\n first = svgString.indexOf('width=\"');\n last = svgString.indexOf('\"', first + 7, svgString.length);\n const width = svgString.substring(first + 7, last);\n\n first = svgString.indexOf('wrs:baseline=\"');\n last = svgString.indexOf('\"', first + 14, svgString.length);\n const baseline = svgString.substring(first + 14, last);\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n if (typeof baseline !== \"undefined\") {\n arr.cb = baseline;\n }\n return arr;\n }\n return [];\n }\n\n /**\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\n * @param {Array.} bytes - png byte array.\n * @return {Array} The png metrics.\n */\n static getMetricsFromBytes(bytes) {\n Util.readBytes(bytes, 0, 8);\n let width;\n let height;\n let typ;\n let baseline;\n let dpi;\n while (bytes.length >= 4) {\n typ = Util.readInt32(bytes);\n if (typ === 0x49484452) {\n width = Util.readInt32(bytes);\n height = Util.readInt32(bytes);\n // Read 5 bytes.\n Util.readInt32(bytes);\n Util.readByte(bytes);\n } else if (typ === 0x62615345) {\n // Baseline: 'baSE'.\n baseline = Util.readInt32(bytes);\n } else if (typ === 0x70485973) {\n // Dpis: 'pHYs'.\n dpi = Util.readInt32(bytes);\n dpi = Math.round(dpi / 39.37);\n Util.readInt32(bytes);\n Util.readByte(bytes);\n }\n Util.readInt32(bytes);\n }\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n arr.dpi = dpi;\n if (baseline) {\n arr.cb = baseline;\n }\n\n return arr;\n }\n return [];\n }\n}\n","import TextCache from \"./textcache\";\nimport ServiceProvider from \"./serviceprovider\";\nimport MathML from \"./mathml\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * @classdesc\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\n * the associated client-side cache.\n */\nexport default class Accessibility {\n /**\n * Static property.\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\n * @type {TextCache}\n */\n static get cache() {\n return Accessibility._cache;\n }\n\n /**\n * Static property setter.\n * Set accessibility cache.\n * @param {TextCahe} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Accessibility._cache = value;\n }\n\n /**\n * Converts MathML strings to its accessible text representation.\n * @param {String} mathML - MathML to be converted to accessible text.\n * @param {String} [language] - Language of the accessible text. 'en' by default.\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\n * @return {String} Accessibility text.\n */\n static mathMLToAccessible(mathML, language, data) {\n if (typeof language === \"undefined\") {\n language = \"en\";\n }\n // Check MathML class. If the class is chemistry,\n // we add chemistry to data to force accessibility service\n // to load chemistry grammar.\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\n data.mode = \"chemistry\";\n }\n // Ignore accesibility styles\n data.ignoreStyles = true;\n let accessibleText = \"\";\n\n if (Accessibility.cache.get(mathML)) {\n accessibleText = Accessibility.cache.get(mathML);\n } else {\n data.service = \"mathml2accessible\";\n data.lang = language;\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n if (accessibleJsonResponse.status !== \"error\") {\n accessibleText = accessibleJsonResponse.result.text;\n Accessibility.cache.populate(mathML, accessibleText);\n } else {\n accessibleText = StringManager.get(\"error_convert_accessibility\");\n }\n }\n\n return accessibleText;\n }\n}\n\n/**\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\n * @private\n * @type {TextCache}\n */\nAccessibility._cache = new TextCache();\n","import Util from \"./util\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport Image from \"./image\";\nimport Accessibility from \"./accessibility\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Configuration from \"./configuration\";\nimport Constants from \"./constants\";\n// eslint-disable-next-line no-unused-vars\nimport md5 from \"./md5\";\n\n/**\n * @classdesc\n * This class represent a MahML parser. Converts MathML into formulas depending on the\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\n * in the backend.\n */\nexport default class Parser {\n /**\n * Converts a MathML string to an img element.\n * @param {Document} creator - Document object to call createElement method.\n * @param {string} mathml - MathML code\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\n * @param {language} language - custom language for accessibility.\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\n * @static\n */\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\n const imgObject = creator.createElement(\"img\");\n imgObject.align = \"middle\";\n imgObject.style.maxWidth = \"none\";\n let data = wirisProperties || {};\n\n // Take into account the backend config\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\n data = { ...wirisEditorProperties, ...data };\n\n data.mml = mathml;\n data.lang = language;\n // Request metrics of the generated image.\n data.metrics = \"true\";\n data.centerbaseline = \"false\";\n\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n // Render js params: _wrs_int_wirisProperties contains some js render params.\n // Since MathML can support render params, js params should be send only to editor.\n\n imgObject.className = Configuration.get(\"imageClassName\");\n\n if (mathml.indexOf('class=\"') !== -1) {\n // We check here if the MathML has been created from a customEditor (such chemistry)\n // to add custom editor name attribute to img object (if necessary).\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\n }\n\n // Performance enabled.\n if (\n Configuration.get(\"wirisPluginPerformance\") &&\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\n ) {\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\n if (result.status === \"warning\") {\n // POST call.\n // if the mathml is malformed, this function will throw an exception.\n try {\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\n } catch (e) {\n return null;\n }\n }\n ({ result } = result);\n if (result.format === \"png\") {\n imgObject.src = `data:image/png;base64,${result.content}`;\n } else {\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\n }\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n Image.setImgSize(imgObject, result.content, true);\n\n if (Configuration.get(\"enableAccessibility\")) {\n if (typeof result.alt === \"undefined\") {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n } else {\n imgObject.alt = result.alt;\n }\n }\n } else {\n const result = Parser.createImageSrc(mathml, data);\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n imgObject.src = result;\n Image.setImgSize(\n imgObject,\n result,\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\n );\n if (Configuration.get(\"enableAccessibility\")) {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n }\n }\n\n if (typeof Parser.observer !== \"undefined\") {\n Parser.observer.observe(imgObject);\n }\n\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\n imgObject.setAttribute(\"role\", \"math\");\n return imgObject;\n }\n\n /**\n * Returns the source to showimage service by calling createimage service. The\n * output of the createimage service is a URL path pointing to showimage service.\n * This method is called when performance is disabled.\n * @param {string} mathml - MathML code.\n * @param {Object[]} data - data object containing service parameters.\n * @returns {string} the showimage path.\n */\n static createImageSrc(mathml, data) {\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n let result = ServiceProvider.getService(\"createimage\", data);\n\n if (result.indexOf(\"@BASE@\") !== -1) {\n // Replacing '@BASE@' with the base URL of createimage.\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\n baseParts.pop();\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\n }\n\n return result;\n }\n\n /**\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\n * this data would be converted as following:\n *
\n   * MathML code: Image containing the corresponding MathML formulas.\n   * MathML code with LaTeX annotation : LaTeX string.\n   * 
\n * @param {string} code - HTML code containing MathML data.\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\n */\n static initParse(code, language) {\n /* Note: The code inside this function has been inverted.\n If you invert again the code then you cannot use correctly LaTeX\n in Moodle.\n */\n code = Parser.initParseSaveMode(code, language);\n return Parser.initParseEditMode(code);\n }\n\n /**\n * Parses initial HTML code depending on the save mode. Transforms all MathML\n * occurrences for it's correspondent image or LaTeX.\n * @param {string} code - HTML code to be parsed\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code parsed.\n */\n static initParseSaveMode(code, language) {\n if (Configuration.get(\"saveMode\")) {\n // Converting XML to tags.\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\n code = Parser.codeImgTransform(code, \"base642showimage\");\n }\n }\n return code;\n }\n\n /**\n * Parses initial HTML code depending on the edit mode.\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\n * be converted into a LaTeX string instead of an image.\n * @param {string} code - HTML code containing MathML.\n * @returns {string} parsed HTML code.\n */\n static initParseEditMode(code) {\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\n const token = 'encoding=\"LaTeX\">';\n // While replacing images with latex, the indexes of the found images changes\n // respecting the original code, so this carry is needed.\n let carry = 0;\n\n for (let i = 0; i < imgList.length; i += 1) {\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\n\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\n\n if (mathmlStart === -1) {\n mathmlStartToken = ' alt=\"';\n mathmlStart = imgCode.indexOf(mathmlStartToken);\n }\n\n if (mathmlStart !== -1) {\n mathmlStart += mathmlStartToken.length;\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\n let latexStartPosition = mathml.indexOf(token);\n\n if (latexStartPosition !== -1) {\n latexStartPosition += token.length;\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\n\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\n const start = code.substring(0, imgList[i].start + carry);\n const end = code.substring(imgList[i].end + carry);\n code = start + replaceText + end;\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\n }\n }\n }\n }\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code. The end HTML code is HTML code with embedded images\n * or LaTeX formulas created with MathType.
\n * By default this method converts the formula images and LaTeX strings in MathML.
\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\n * @param {string} code - HTML to be parsed\n * @returns {string} the HTML code parsed.\n */\n static endParse(code) {\n // Transform LaTeX ocurrences to MathML elements.\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\n // Transform img elements to MathML elements.\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\n return codeEndParseSaveMode;\n }\n\n /**\n * Parses end HTML code depending on the edit mode.\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\n * @param {string} code - HTML code to be parsed.\n * @returns {string} HTML code parsed.\n */\n static endParseEditMode(code) {\n // Converting LaTeX to images.\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n let output = \"\";\n let endPosition = 0;\n let startPosition = code.indexOf(\"$$\");\n while (startPosition !== -1) {\n output += code.substring(endPosition, startPosition);\n endPosition = code.indexOf(\"$$\", startPosition + 2);\n\n if (endPosition !== -1) {\n // Before, it was a condition here to execute the next codelines\n // 'latex.indexOf('<') == -1'.\n // We don't know why it was used, but seems to have a conflict with\n // latex formulas that contains '<'.\n const latex = code.substring(startPosition + 2, endPosition);\n const decodedLatex = Util.htmlEntitiesDecode(latex);\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\n if (!Configuration.get(\"saveHandTraces\")) {\n // Remove hand traces.\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\n }\n output += mathml;\n endPosition += 2;\n } else {\n output += \"$$\";\n endPosition = startPosition + 2;\n }\n\n startPosition = code.indexOf(\"$$\", endPosition);\n }\n\n output += code.substring(endPosition, code.length);\n code = output;\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code depending on the save mode. Converts all\n * images into the element determined by the save mode:\n * - xml: Parses images formulas into MathML.\n * - safeXml: Parses images formulas into safeMAthML\n * - base64: Parses images into base64 images.\n * - image: Parse images into images (no parsing)\n * @param {string} code - HTML code to be parsed\n * @returns {string} HTML code parsed.\n */\n static endParseSaveMode(code) {\n const savemode = Configuration.get(\"saveMode\");\n const base64savemode = Configuration.get(\"base64savemode\");\n\n if (savemode) {\n if (savemode === \"safeXml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"xml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\n code = Parser.codeImgTransform(code, \"img264\");\n }\n }\n\n return code;\n }\n\n /**\n * Auxiliar function that builds the data object to send to the showimage endpoint\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object with the data to send to showimage.\n */\n static createShowImageSrcData(data, language) {\n const dataMd5 = {};\n const renderParams = [\n \"mml\",\n \"color\",\n \"centerbaseline\",\n \"zoom\",\n \"dpi\",\n \"fontSize\",\n \"fontFamily\",\n \"defaultStretchy\",\n \"backgroundColor\",\n \"format\",\n ];\n renderParams.forEach((param) => {\n if (typeof data[param] !== \"undefined\") {\n dataMd5[param] = data[param];\n }\n });\n // Data variables to get.\n const dataObject = {};\n Object.keys(data).forEach((key) => {\n // We don't need mathml in this request we try to get cached.\n // Only need the formula md5 calculated before.\n if (key !== \"mml\") {\n dataObject[key] = data[key];\n }\n });\n\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\n dataObject.version = Configuration.get(\"version\");\n\n return dataObject;\n }\n\n /**\n * Returns the result to call showimage service with the formula md5 as parameter.\n * The result could be:\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object containing showimage response.\n */\n static createShowImageSrc(data, language) {\n const dataObject = this.createShowImageSrcData(data, language);\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\n return result;\n }\n\n /**\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\n * or showimage img tags (i.e with showimage.php on src)\n * @param {string} code - HTML code\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\n * @returns {string} html - code transformed.\n */\n static codeImgTransform(code, mode) {\n let output = \"\";\n let endPosition = 0;\n const pattern = /\") {\n endPosition = i + 1;\n }\n\n i += 1;\n }\n\n if (endPosition < startPosition) {\n // The img tag is stripped.\n output += code.substring(startPosition, code.length);\n return output;\n }\n let imgCode = code.substring(startPosition, endPosition);\n const imgObject = Util.createObject(imgCode);\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n let convertToXml;\n let convertToSafeXml;\n\n if (mode === \"base642showimage\") {\n if (xmlCode == null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\n output += Util.createObjectCode(imgCode);\n } else if (mode === \"img2mathml\") {\n if (Configuration.get(\"saveMode\")) {\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\n convertToXml = true;\n convertToSafeXml = true;\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\n convertToXml = true;\n convertToSafeXml = false;\n }\n }\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\n } else if (mode === \"img264\") {\n if (xmlCode === null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n\n const properties = {};\n properties.base64 = \"true\";\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\n // Metrics.\n Image.setImgSize(imgCode, imgCode.src, true);\n output += Util.createObjectCode(imgCode);\n }\n }\n output += code.substring(endPosition, code.length);\n return output;\n }\n\n /**\n * Converts all occurrences of MathML to the corresponding image.\n * @param {string} content - string with valid MathML code.\n * The MathML code doesn't contain semantics.\n * @param {Constants} characters - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * @param {string} language - a valid language code\n * in order to generate formula accessibility.\n * @returns {string} The input string with all the MathML\n * occurrences replaced by the corresponding image.\n */\n static parseMathmlToImg(content, characters, language) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n\n while (start !== -1) {\n output += content.substring(end, start);\n // Avoid WIRIS images to be parsed.\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else if (imageMathmlAtrribute !== -1) {\n // First close tag of img attribute\n // If a mathmlAttribute exists should be inside a img tag.\n end += content.indexOf(\"/>\", start);\n } else {\n end += mathTagEnd.length;\n }\n\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\n let mathml = content.substring(start, end);\n mathml =\n characters.id === Constants.safeXmlCharacters.id\n ? MathML.safeXmlDecode(mathml)\n : MathML.mathMLEntities(mathml);\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\n } else {\n output += content.substring(start, end);\n }\n\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n}\n\n// Mutation observers to avoid wiris image formulas class be removed.\nif (typeof MutationObserver !== \"undefined\") {\n const mutationObserver = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\n mutation.attributeName === \"class\" &&\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\n ) {\n mutation.target.className = Configuration.get(\"imageClassName\");\n }\n });\n });\n\n Parser.observer = Object.create(mutationObserver);\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\n // We use own default config.\n Parser.observer.observe = function (target) {\n Object.getPrototypeOf(this).observe(target, this.Config);\n };\n}\n","/* eslint-disable class-methods-use-this */\n/* eslint-disable no-unused-vars */\n/* eslint-disable no-extra-semi */\n\n// The rules above are disabled because we are implementing\n// an external interface.\n\nexport default class EditorListener {\n /**\n * @classdesc\n * Determines if the content of the\n * MathType Editor has changes.\n * @implements {EditorListeners}\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the content of the editor has changed.\n * @type {Boolean}\n */\n this.isContentChanged = false;\n\n /**\n * Indicates if the listener should be waiting for changes in the editor.\n * @type {Boolean}\n */\n this.waitingForChanges = false;\n }\n\n /**\n * Sets {@link EditorListener.isContentChanged} property.\n * @param {Boolean} value - The new vlue.\n */\n setIsContentChanged(value) {\n this.isContentChanged = value;\n }\n\n /**\n * Returns true if the content of the editor has been changed, false otherwise.\n * @return {Boolean}\n */\n getIsContentChanged() {\n return this.isContentChanged;\n }\n\n /**\n * Determines if the EditorListener should wait for any changes.\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\n */\n setWaitingForChanges(value) {\n this.waitingForChanges = value;\n }\n\n /**\n * EditorListener method to overwrite.\n * @type {JsEditor}\n * @ignore\n */\n caretPositionChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @type {JsEditor}\n * @ignore\n */\n clipboardChanged(_editor) {}\n\n /**\n * Determines if the content of an editor has been changed.\n * @param {JsEditor} editor - editor object.\n */\n contentChanged(_editor) {\n if (this.waitingForChanges === true && this.isContentChanged === false) {\n this.isContentChanged = true;\n }\n }\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} editor - The editor instance.\n */\n styleChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} - The editor instance.\n */\n transformationReceived(_editor) {}\n}\n","let wasm;\n\nconst cachedTextDecoder =\n typeof TextDecoder !== \"undefined\"\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\n : {\n decode: () => {\n throw Error(\"TextDecoder not available\");\n },\n };\n\nif (typeof TextDecoder !== \"undefined\") {\n cachedTextDecoder.decode();\n}\n\nlet cachedUint8Memory0 = null;\n\nfunction getUint8Memory0() {\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachedUint8Memory0;\n}\n\nfunction getStringFromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\n}\n\nconst heap = new Array(128).fill(undefined);\n\nheap.push(undefined, null, true, false);\n\nlet heap_next = heap.length;\n\nfunction addHeapObject(obj) {\n if (heap_next === heap.length) heap.push(heap.length + 1);\n const idx = heap_next;\n heap_next = heap[idx];\n\n heap[idx] = obj;\n return idx;\n}\n\nfunction getObject(idx) {\n return heap[idx];\n}\n\nfunction dropObject(idx) {\n if (idx < 132) return;\n heap[idx] = heap_next;\n heap_next = idx;\n}\n\nfunction takeObject(idx) {\n const ret = getObject(idx);\n dropObject(idx);\n return ret;\n}\n\nlet WASM_VECTOR_LEN = 0;\n\nconst cachedTextEncoder =\n typeof TextEncoder !== \"undefined\"\n ? new TextEncoder(\"utf-8\")\n : {\n encode: () => {\n throw Error(\"TextEncoder not available\");\n },\n };\n\nconst encodeString =\n typeof cachedTextEncoder.encodeInto === \"function\"\n ? function (arg, view) {\n return cachedTextEncoder.encodeInto(arg, view);\n }\n : function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length,\n };\n };\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length, 1) >>> 0;\n getUint8Memory0()\n .subarray(ptr, ptr + buf.length)\n .set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len, 1) >>> 0;\n\n const mem = getUint8Memory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7f) break;\n mem[ptr + offset] = code;\n }\n\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\n const ret = encodeString(arg, view);\n\n offset += ret.written;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction isLikeNone(x) {\n return x === undefined || x === null;\n}\n\nlet cachedInt32Memory0 = null;\n\nfunction getInt32Memory0() {\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\n }\n return cachedInt32Memory0;\n}\n\nlet cachedFloat64Memory0 = null;\n\nfunction getFloat64Memory0() {\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\n }\n return cachedFloat64Memory0;\n}\n\nlet cachedBigInt64Memory0 = null;\n\nfunction getBigInt64Memory0() {\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\n }\n return cachedBigInt64Memory0;\n}\n\nfunction debugString(val) {\n // primitive types\n const type = typeof val;\n if (type == \"number\" || type == \"boolean\" || val == null) {\n return `${val}`;\n }\n if (type == \"string\") {\n return `\"${val}\"`;\n }\n if (type == \"symbol\") {\n const description = val.description;\n if (description == null) {\n return \"Symbol\";\n } else {\n return `Symbol(${description})`;\n }\n }\n if (type == \"function\") {\n const name = val.name;\n if (typeof name == \"string\" && name.length > 0) {\n return `Function(${name})`;\n } else {\n return \"Function\";\n }\n }\n // objects\n if (Array.isArray(val)) {\n const length = val.length;\n let debug = \"[\";\n if (length > 0) {\n debug += debugString(val[0]);\n }\n for (let i = 1; i < length; i++) {\n debug += \", \" + debugString(val[i]);\n }\n debug += \"]\";\n return debug;\n }\n // Test for built-in\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\n let className;\n if (builtInMatches.length > 1) {\n className = builtInMatches[1];\n } else {\n // Failed to match the standard '[object ClassName]'\n return toString.call(val);\n }\n if (className == \"Object\") {\n // we're a user defined class or Object\n // JSON.stringify avoids problems with cycles, and is generally much\n // easier than looping through ownProperties of `val`.\n try {\n return \"Object(\" + JSON.stringify(val) + \")\";\n } catch (_) {\n return \"Object\";\n }\n }\n // errors\n if (val instanceof Error) {\n return `${val.name}: ${val.message}\\n${val.stack}`;\n }\n // TODO we could test for more things here, like `Set`s and `Map`s.\n return className;\n}\n\nfunction makeClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n try {\n return f(state.a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\n state.a = 0;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction makeMutClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n const a = state.a;\n state.a = 0;\n try {\n return f(a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\n } else {\n state.a = a;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_49(arg0, arg1) {\n wasm.__wbindgen_export_4(arg0, arg1);\n}\n\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction handleError(f, args) {\n try {\n return f.apply(this, args);\n } catch (e) {\n wasm.__wbindgen_export_6(addHeapObject(e));\n }\n}\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\n}\n\n/**\n */\nexport function main_js() {\n wasm.main_js();\n}\n\nfunction getArrayU8FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\n}\n/**\n */\nexport const Level = Object.freeze({\n Err: 0,\n 0: \"Err\",\n Warn: 1,\n 1: \"Warn\",\n Info: 2,\n 2: \"Info\",\n Debug: 3,\n 3: \"Debug\",\n});\n/**\n */\nexport class Telemeter {\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n\n return ptr;\n }\n\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_telemeter_free(ptr);\n }\n /**\n * @param {any} solution\n * @param {any} hosts\n * @param {any} config\n */\n constructor(solution, hosts, config) {\n try {\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\n var r0 = getInt32Memory0()[retptr / 4 + 0];\n var r1 = getInt32Memory0()[retptr / 4 + 1];\n var r2 = getInt32Memory0()[retptr / 4 + 2];\n if (r2) {\n throw takeObject(r1);\n }\n this.__wbg_ptr = r0 >>> 0;\n return this;\n } finally {\n wasm.__wbindgen_add_to_stack_pointer(16);\n }\n }\n /**\n * @param {string} sender_id\n * @returns {Promise}\n */\n identify(sender_id) {\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\n return takeObject(ret);\n }\n /**\n * @param {string} event_type\n * @param {any} event_payload\n * @returns {Promise}\n */\n track(event_type, event_payload) {\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\n return takeObject(ret);\n }\n /**\n * @param {any} level\n * @param {string} message\n * @param {any} payload\n * @returns {Promise}\n */\n log(level, message, payload) {\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\n return takeObject(ret);\n }\n /**\n * @returns {Promise}\n */\n finish() {\n const ptr = this.__destroy_into_raw();\n const ret = wasm.telemeter_finish(ptr);\n return takeObject(ret);\n }\n /**\n * @param {boolean | undefined} [new_debug_status]\n */\n debug(new_debug_status) {\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\n }\n}\n\nasync function __wbg_load(module, imports) {\n if (typeof Response === \"function\" && module instanceof Response) {\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\n try {\n return await WebAssembly.instantiateStreaming(module, imports);\n } catch (e) {\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\n console.warn(\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\n e,\n );\n } else {\n throw e;\n }\n }\n }\n\n const bytes = await module.arrayBuffer();\n return await WebAssembly.instantiate(bytes, imports);\n } else {\n const instance = await WebAssembly.instantiate(module, imports);\n\n if (instance instanceof WebAssembly.Instance) {\n return { instance, module };\n } else {\n return instance;\n }\n }\n}\n\nfunction __wbg_get_imports() {\n const imports = {};\n imports.wbg = {};\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\n const ret = getStringFromWasm0(arg0, arg1);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\n const ret = new Object();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\n const ret = getObject(arg0).status;\n return ret;\n };\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\n const ret = getObject(arg0).headers;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\n const ret = new Date();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\n const ret = getObject(arg0).getTime();\n return ret;\n };\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\n takeObject(arg0);\n };\n imports.wbg.__wbindgen_is_object = function (arg0) {\n const val = getObject(arg0);\n const ret = typeof val === \"object\" && val !== null;\n return ret;\n };\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\n const ret = getObject(arg0).crypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\n const ret = getObject(arg0).process;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\n const ret = getObject(arg0).versions;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\n const ret = getObject(arg0).node;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_string = function (arg0) {\n const ret = typeof getObject(arg0) === \"string\";\n return ret;\n };\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\n const ret = getObject(arg0).msCrypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\n return handleError(function () {\n const ret = module.require;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\n const ret = new Uint8Array(arg0 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\n const ret = getObject(arg0)[arg1 >>> 0];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).next();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\n const ret = getObject(arg0).done;\n return ret;\n };\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\n const ret = getObject(arg0).value;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\n const ret = Symbol.iterator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\n const ret = getObject(arg0).next;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_function = function (arg0) {\n const ret = typeof getObject(arg0) === \"function\";\n return ret;\n };\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\n return handleError(function (arg0, arg1) {\n const ret = getObject(arg0).call(getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\n const ret = getObject(arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\n return handleError(function () {\n const ret = self.self;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\n return handleError(function () {\n const ret = window.window;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\n return handleError(function () {\n const ret = globalThis.globalThis;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\n return handleError(function () {\n const ret = global.global;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\n const ret = getObject(arg0) === undefined;\n return ret;\n };\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\n const ret = new Function(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\n const ret = Array.isArray(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\n const ret = Number.isSafeInteger(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\n try {\n var state0 = { a: arg0, b: arg1 };\n var cb0 = (arg0, arg1) => {\n const a = state0.a;\n state0.a = 0;\n try {\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\n } finally {\n state0.a = a;\n }\n };\n const ret = new Promise(cb0);\n return addHeapObject(ret);\n } finally {\n state0.a = state0.b = 0;\n }\n };\n imports.wbg.__wbindgen_memory = function () {\n const ret = wasm.memory;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\n const ret = getObject(arg0).buffer;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\n const ret = new Uint8Array(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\n };\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"string\" ? obj : undefined;\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\n return handleError(function (arg0) {\n const ret = JSON.stringify(getObject(arg0));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\n const ret = getObject(arg0).fetch(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\n const ret = fetch(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\n return handleError(function () {\n const ret = new AbortController();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\n const ret = getObject(arg0).signal;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\n getObject(arg0).abort();\n };\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\n const ret = new Error(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\n const ret = getObject(arg0) == getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\n const v = getObject(arg0);\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\n return ret;\n };\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"number\" ? obj : undefined;\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Uint8Array;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof ArrayBuffer;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\n const ret = Object.entries(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\n const ret = String(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\n console.warn(getObject(arg0), getObject(arg1));\n };\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\n const ret = getObject(arg0).readyState;\n return ret;\n };\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\n console.warn(getObject(arg0));\n };\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\n return handleError(function (arg0) {\n getObject(arg0).close();\n }, arguments);\n };\n imports.wbg.__wbg_new_b9b318679315404f = function () {\n return handleError(function (arg0, arg1) {\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\n getObject(arg0).binaryType = takeObject(arg1);\n };\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\n console.log(getObject(arg0));\n };\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\n console.error(getObject(arg0));\n };\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\n console.info(getObject(arg0));\n };\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\n const ret = getObject(arg0).document;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n };\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\n const ret = getObject(arg0).visibilityState;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\n const ret = getObject(arg0)[getObject(arg1)];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\n const ret = typeof getObject(arg0) === \"bigint\";\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\n const ret = arg0;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\n const ret = getObject(arg0) in getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\n const ret = BigInt.asUintN(64, arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\n const ret = getObject(arg0) === getObject(arg1);\n return ret;\n };\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).localStorage;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n }, arguments);\n };\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\n const ret = getObject(arg0).navigator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).mediaDevices;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).enumerateDevices();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\n const obj = takeObject(arg0).original;\n if (obj.cnt-- == 1) {\n obj.a = 0;\n return true;\n }\n const ret = false;\n return ret;\n };\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\n const ret = getObject(arg1).deviceId;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Response;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).randomFillSync(takeObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).getRandomValues(getObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\n const v = getObject(arg1);\n const ret = typeof v === \"bigint\" ? v : undefined;\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\n const ret = debugString(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\n throw new Error(getStringFromWasm0(arg0, arg1));\n };\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\n const ret = getObject(arg0).then(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\n queueMicrotask(getObject(arg0));\n };\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\n const ret = getObject(arg0).queueMicrotask;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\n const ret = Promise.resolve(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\n const ret = getObject(arg1).url;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_send_2860805104507701 = function () {\n return handleError(function (arg0, arg1, arg2) {\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\n }, arguments);\n };\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Window;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\n return handleError(function () {\n const ret = new Headers();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\n return addHeapObject(ret);\n };\n\n return imports;\n}\n\nfunction __wbg_init_memory(imports, maybe_memory) {}\n\nfunction __wbg_finalize_init(instance, module) {\n wasm = instance.exports;\n __wbg_init.__wbindgen_wasm_module = module;\n cachedBigInt64Memory0 = null;\n cachedFloat64Memory0 = null;\n cachedInt32Memory0 = null;\n cachedUint8Memory0 = null;\n\n wasm.__wbindgen_start();\n return wasm;\n}\n\nfunction initSync(module) {\n if (wasm !== undefined) return wasm;\n\n const imports = __wbg_get_imports();\n\n __wbg_init_memory(imports);\n\n if (!(module instanceof WebAssembly.Module)) {\n module = new WebAssembly.Module(module);\n }\n\n const instance = new WebAssembly.Instance(module, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nasync function __wbg_init(input) {\n if (wasm !== undefined) return wasm;\n\n if (typeof input === \"undefined\") {\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\n }\n const imports = __wbg_get_imports();\n\n if (\n typeof input === \"string\" ||\n (typeof Request === \"function\" && input instanceof Request) ||\n (typeof URL === \"function\" && input instanceof URL)\n ) {\n input = fetch(input);\n }\n\n __wbg_init_memory(imports);\n\n const { instance, module } = await __wbg_load(await input, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nexport { initSync };\nexport default __wbg_init;\n","/* eslint-disable-next-line */\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\n\n/**\n * @classdesc\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\n * to Telemetry in a more comfortable and homogeneous way.\n */\nexport default class Telemeter {\n /**\n * Inits Telemeter class.\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\n * @param {Object} telemeterAttributes.config - Configuration parameters.\n */\n static init(telemeterAttributes) {\n if (!this.telemeter && !this.waitingForInit) {\n this.waitingForInit = true;\n init(telemeterAttributes.url)\n .then(() => {\n this.telemeter = new TelemeterWASM(\n telemeterAttributes.solution,\n telemeterAttributes.hosts,\n telemeterAttributes.config,\n );\n })\n .catch((error) => {\n console.log(error);\n })\n .finally(() => (this.waitingForInit = false));\n }\n }\n\n /**\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\n */\n static async finish() {\n if (!this.telemeter) return;\n\n try {\n const local_telemeter = this.telemeter;\n this.telemeter = undefined;\n await local_telemeter.finish();\n } catch (e) {\n console.error(e);\n }\n }\n}\n","import Configuration from \"./configuration\";\nimport Core from \"./core.src\";\nimport EditorListener from \"./editorlistener\";\nimport Listeners from \"./listeners\";\nimport MathML from \"./mathml\";\nimport Util from \"./util\";\nimport Telemeter from \"./telemeter\";\n\nexport default class ContentManager {\n /**\n * @classdesc\n * This class represents a modal dialog, managing the following:\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\n * - The actions to be done once the modal object has been submitted\n * (submitAction} method).\n * - The update of the content when the {@link ModalDialog} class is also updated,\n * for example when ModalDialog is re-opened.\n * - The communication between the {@link ModalDialog} class and itself, if the content\n * has been changed (hasChanges} method).\n * @constructs\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\n * create a new instance.\n */\n constructor(contentManagerAttributes) {\n /**\n * An object containing MathType editor parameters. See\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\n * @type {Object}\n */\n this.editorAttributes = {};\n if (\"editorAttributes\" in contentManagerAttributes) {\n this.editorAttributes = contentManagerAttributes.editorAttributes;\n } else {\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\n }\n\n /**\n * CustomEditors instance. Contains the custom editors.\n * @type {CustomEditors}\n */\n this.customEditors = null;\n if (\"customEditors\" in contentManagerAttributes) {\n this.customEditors = contentManagerAttributes.customEditors;\n }\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @type {Object}\n * @property {String} editor - Editor name. Usually the HTML editor.\n * @property {String} mode - Save mode. Xml by default.\n * @property {String} version - Plugin version.\n */\n this.environment = {};\n if (\"environment\" in contentManagerAttributes) {\n this.environment = contentManagerAttributes.environment;\n } else {\n throw new Error(\"ContentManager constructor error: environment property missed\");\n }\n\n /**\n * ContentManager language.\n * @type {String}\n */\n this.language = \"\";\n if (\"language\" in contentManagerAttributes) {\n this.language = contentManagerAttributes.language;\n } else {\n throw new Error(\"ContentManager constructor error: language property missed\");\n }\n\n /**\n * {@link EditorListener} instance. Manages the changes inside the editor.\n * @type {EditorListener}\n */\n this.editorListener = new EditorListener();\n\n /**\n * MathType editor instance.\n * @type {JsEditor}\n */\n this.editor = null;\n\n /**\n * Navigator user agent.\n * @type {String}\n */\n this.ua = navigator.userAgent.toLowerCase();\n\n /**\n * Mobile device properties object\n * @type {DeviceProperties}\n */\n this.deviceProperties = {};\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\n this.deviceProperties.isIOS = ContentManager.isIOS();\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.toolbar = null;\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.dbclick = null;\n\n /**\n * Instance of the {@link ModalDialog} class associated with the current\n * {@link ContentManager} instance.\n * @type {ModalDialog}\n */\n this.modalDialogInstance = null;\n\n /**\n * ContentManager listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n /**\n * MathML associated to the ContentManager instance.\n * @type {String}\n */\n this.mathML = null;\n\n /**\n * Indicates if the edited element is a new one or not.\n * @type {Boolean}\n */\n this.isNewElement = true;\n\n /**\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n }\n\n /**\n * Adds a new listener to the current {@link ContentManager} instance.\n * @param {Object} listener - The listener to be added.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\n * instance.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\n */\n setModalDialogInstance(modalDialogInstance) {\n this.modalDialogInstance = modalDialogInstance;\n }\n\n /**\n * Inserts the content into the current {@link ModalDialog} instance updating\n * the title and inserting the JavaScript editor.\n */\n insert() {\n // Before insert the editor we update the modal object title to avoid weird render display.\n this.updateTitle(this.modalDialogInstance);\n this.insertEditor(this.modalDialogInstance);\n }\n\n /**\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\n * editor's JavaScript is loaded.\n */\n insertEditor() {\n if (ContentManager.isEditorLoaded()) {\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\n this.editor.focus();\n\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\n this.editor.action(\"rtl\");\n }\n // Setting div in rtl in case of it's activated.\n if (this.editor.getEditorModel().isRTL()) {\n this.editor.element.style.direction = \"rtl\";\n }\n\n // Editor listener: this object manages the changes logic of editor.\n this.editor.getEditorModel().addEditorListener(this.editorListener);\n\n // iOS events.\n if (this.modalDialogInstance.deviceProperties.isIOS) {\n setTimeout(function () {\n // Make sure the modalDialogInstance is available when the timeout is over\n // to avoid throw errors and stop execution.\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\n }, 400);\n\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\n }\n // Fire onLoad event. Necessary to set the MathML into the editor\n // after is loaded.\n this.listeners.fire(\"onLoad\", {});\n } else {\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\n }\n }\n\n /**\n * Initializes the current class by loading MathType script.\n */\n init() {\n if (!ContentManager.isEditorLoaded()) {\n this.addEditorAsExternalDependency();\n }\n }\n\n /**\n * Adds script element to the DOM to include editor externally.\n */\n addEditorAsExternalDependency() {\n const script = document.createElement(\"script\");\n script.type = \"text/javascript\";\n let editorUrl = Configuration.get(\"editorUrl\");\n\n // We create an object url for parse url string and work more efficiently.\n const anchorElement = document.createElement(\"a\");\n\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\n ContentManager.setProtocolToAnchorElement(anchorElement);\n\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\n\n // Load editor URL. We add stats as GET params.\n const stats = this.getEditorStats();\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\n\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n }\n\n /**\n * Sets the specified url to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\n * @param {String} url - URL to set.\n */\n static setHrefToAnchorElement(anchorElement, url) {\n anchorElement.href = url;\n }\n\n /**\n * Sets the current protocol to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\n */\n static setProtocolToAnchorElement(anchorElement) {\n // Change to https if necessary.\n if (window.location.href.indexOf(\"https://\") === 0) {\n // It check if browser is https and configuration is http.\n // If this is so, we will replace protocol.\n if (anchorElement.protocol === \"http:\") {\n anchorElement.protocol = \"https:\";\n }\n }\n }\n\n /**\n * Returns the url of the anchor element adding the current port\n * if it is needed.\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\n * @returns {String}\n */\n static getURLFromAnchorElement(anchorElement) {\n // Check protocol and remove port if it's standard.\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\n }\n\n /**\n * Returns object with editor stats.\n *\n * @typedef {Object} EditorStatsObject\n * @property {string} editor - Editor name.\n * @property {string} mode - Current configuration for formula save mode.\n * @property {string} version - Current plugins version.\n * @returns {EditorStatsObject}\n */\n getEditorStats() {\n // Editor stats. Use environment property to set it.\n const stats = {};\n if (\"editor\" in this.environment) {\n stats.editor = this.environment.editor;\n } else {\n stats.editor = \"unknown\";\n }\n\n if (\"mode\" in this.environment) {\n stats.mode = this.environment.mode;\n } else {\n stats.mode = Configuration.get(\"saveMode\");\n }\n\n if (\"version\" in this.environment) {\n stats.version = this.environment.version;\n } else {\n stats.version = Configuration.get(\"version\");\n }\n\n return stats;\n }\n\n /**\n * Returns true if device is iOS. Otherwise, false.\n * @returns {Boolean}\n */\n static isIOS() {\n return (\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\n // iPad on iOS 13 detection\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\n );\n }\n\n /**\n * Returns true if device is Mobile. Otherwise, false.\n * @returns {Boolean}\n */\n static isMobile() {\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n }\n\n /**\n * Returns true if editor is loaded. Otherwise, false.\n * @returns {Boolean}\n */\n static isEditorLoaded() {\n // To know if editor JavaScript is loaded we need to wait until\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\n return (\n window.com &&\n window.com.wiris &&\n window.com.wiris.jsEditor &&\n window.com.wiris.jsEditor.JsEditor &&\n window.com.wiris.jsEditor.JsEditor.newInstance\n );\n }\n\n /**\n * Sets the {@link ContentManager.editor} initial content.\n */\n setInitialContent() {\n if (!this.isNewElement) {\n this.setMathML(this.mathML);\n }\n }\n\n /**\n * Sets a MathML into {@link ContentManager.editor} instance.\n * @param {String} mathml - MathML string.\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\n * False by default.\n */\n setMathML(mathml, focusDisabled) {\n // By default focus is enabled.\n if (typeof focusDisabled === \"undefined\") {\n focusDisabled = false;\n }\n // Using setMathML method is not a change produced by the user but for the API\n // so we set to false the contentChange property of editorListener.\n this.editor.setMathMLWithCallback(mathml, () => {\n this.editorListener.setWaitingForChanges(true);\n });\n\n // We need to wait a little until the callback finish.\n setTimeout(() => {\n this.editorListener.setIsContentChanged(false);\n }, 500);\n\n // In some scenarios - like closing modal object - editor mustn't be focused.\n if (!focusDisabled) {\n this.onFocus();\n }\n }\n\n /**\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\n * {@link ModalDialog.focus}.\n */\n onFocus() {\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\n this.editor.focus();\n\n // On WordPress integration, the focus gets lost right after setting it.\n // To fix this, we enforce another focus some milliseconds after this behaviour.\n setTimeout(() => {\n this.editor.focus();\n }, 100);\n }\n }\n\n /**\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\n * Triggered by {@link ModalDialog.submitAction}.\n */\n submitAction() {\n if (!this.editor.isFormulaEmpty()) {\n let mathML = this.editor.getMathMLWithSemantics();\n // Add class for custom editors.\n if (this.customEditors.getActiveEditor() !== null) {\n const { toolbar } = this.customEditors.getActiveEditor();\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\n } else {\n // We need - if exists - the editor name from MathML\n // class attribute.\n Object.keys(this.customEditors.editors).forEach((key) => {\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\n });\n }\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\n } else {\n this.integrationModel.updateFormula(null);\n }\n\n this.customEditors.disable();\n this.integrationModel.notifyWindowClosed();\n\n // Set disabled focus to prevent lost focus.\n this.setEmptyMathML();\n this.customEditors.disable();\n }\n\n /**\n * Sets an empty MathML as {@link ContentManager.editor} content.\n * This will open the MT/CT editor with the hand mode.\n * It adds dir rtl in case of it's activated.\n */\n setEmptyMathML() {\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\n const isRTL = this.editor.getEditorModel().isRTL();\n\n if (isMobile || this.integrationModel.forcedHandMode) {\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\n const mathML = `[]`;\n this.setMathML(mathML, true);\n } else {\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\n const mathML = ``;\n this.setMathML(mathML, true);\n }\n }\n\n /**\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\n * - Updates the {@link ContentManager.editor} content\n * (with an empty MathML or an existing formula),\n * - Updates the {@link ContentManager.editor} toolbar.\n * - Recovers the the focus.\n */\n onOpen() {\n if (this.isNewElement) {\n this.setEmptyMathML();\n } else {\n this.setMathML(this.mathML);\n }\n const toolbar = this.updateToolbar();\n this.onFocus();\n\n if (this.deviceProperties.isIOS) {\n const zoom = document.documentElement.clientWidth / window.innerWidth;\n\n if (zoom !== 1) {\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\n this.setKeyboardMode();\n }\n }\n\n const trigger = this.dbclick ? \"formula\" : \"button\";\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\n toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\n }\n\n Core.globalListeners.fire(\"onModalOpen\", {});\n\n if (this.integrationModel.forcedHandMode) {\n this.hideHandModeButton();\n\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\n }\n }\n }\n\n /**\n * Change Editor in keyboard mode when is loaded\n */\n setKeyboardMode() {\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\n if (wrsEditor) {\n wrsEditor.classList.remove(\"wrs_handOpen\");\n wrsEditor.classList.remove(\"wrs_disablePalette\");\n } else {\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\n }\n }\n\n /**\n * Hides the hand <-> keyboard mode switch.\n *\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\n * any change on those classes will make this code stop working properly.\n *\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\n */\n hideHandModeButton(forced = true) {\n if (this.handSwitchHidden) {\n return; // hand <-> keyboard button already hidden.\n }\n\n // \"Open hand mode\" button takes a little bit to be available.\n // This selector gets the hand <-> keyboard mode switch\n const handModeButtonSelector =\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\n\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\n // We use an observer to ensure that the button it hidden as soon as it appears.\n if (forced) {\n const mutationInstance = new MutationObserver((mutations) => {\n const handModeButton = document.querySelector(handModeButtonSelector);\n if (handModeButton) {\n handModeButton.hidden = true;\n this.handSwitchHidden = true;\n mutationInstance.disconnect();\n }\n });\n mutationInstance.observe(document.body, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n }\n\n /**\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\n *\n * @param {String} mathml The original KeyBoard MathML\n * @param {Object} editor The editor object.\n */\n async openHandOnKeyboardMathML(mathml, editor) {\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\n await new Promise((resolve) => {\n editor.setMathMLWithCallback(mathml, resolve);\n });\n\n // We wait until the hand editor object exists.\n await this.waitForHand(editor);\n\n // Logic to get the hand traces and open the formula in hand mode.\n // This logic comes from the editor.\n const handEditor = editor.hand;\n editor.handTemporalMathML = editor.getMathML();\n const handCoordinates = editor.editorModel.getHandStrokes();\n handEditor.setStrokes(handCoordinates);\n handEditor.fitStrokes(true);\n editor.openHand();\n }\n\n /**\n * Waits until the hand editor object exists.\n * @param {Obect} editor The editor object.\n */\n async waitForHand(editor) {\n while (!editor.hand) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n\n /**\n * Sets the correct toolbar depending if exist other custom toolbars\n * at the same time (e.g: Chemistry).\n */\n updateToolbar() {\n this.updateTitle(this.modalDialogInstance);\n const customEditor = this.customEditors.getActiveEditor();\n\n let toolbar;\n if (customEditor) {\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\n\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n }\n } else {\n toolbar = this.getToolbar();\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n this.customEditors.disable();\n }\n }\n\n return toolbar;\n }\n\n /**\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\n * sets the custom editor title. Otherwise sets the default title.\n */\n updateTitle() {\n const customEditor = this.customEditors.getActiveEditor();\n if (customEditor) {\n this.modalDialogInstance.setTitle(customEditor.title);\n } else {\n this.modalDialogInstance.setTitle(\"MathType\");\n }\n }\n\n /**\n * Returns the editor toolbar, depending on the configuration local or server side.\n * @returns {String} - Toolbar identifier.\n */\n getToolbar() {\n let toolbar = \"general\";\n if (\"toolbar\" in this.editorAttributes) {\n ({ toolbar } = this.editorAttributes);\n }\n // TODO: Change global integration variable for integration custom toolbar.\n if (toolbar === \"general\") {\n // eslint-disable-next-line camelcase\n toolbar =\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\n ? \"general\"\n : _wrs_int_wirisProperties.toolbar;\n }\n\n return toolbar;\n }\n\n /**\n * Sets the current {@link ContentManager.editor} instance toolbar.\n * @param {String} toolbar - The toolbar name.\n */\n setToolbar(toolbar) {\n this.toolbar = toolbar;\n this.editor.setParams({ toolbar: this.toolbar });\n }\n\n /**\n * Sets the custom headers added on editor requests.\n * @returns {Object} headers - key value headers.\n */\n setCustomHeaders(headers) {\n let headersObj = {};\n\n // We control that we only get String or Object as the input.\n if (typeof headers === \"object\") {\n headersObj = headers;\n } else if (typeof headers === \"string\") {\n headersObj = Util.convertStringToObject(headers);\n }\n\n this.editor.setParams({ customHeaders: headersObj });\n return headersObj;\n }\n\n /**\n * Returns true if the content of the editor has been changed. The logic of the changes\n * is delegated to {@link EditorListener} class.\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\n */\n hasChanges() {\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n // Code to detect Esc event.\n // There should be only one element with class name 'wrs_pressed' at the same time.\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\n if (list.length === 0) {\n this.modalDialogInstance.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.modalDialogInstance.submitButton) {\n // Focus is on OK button.\n this.editor.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\n // Focus is on minimize button (_).\n this.modalDialogInstance.closeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\n // Focus on cancel button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n this.modalDialogInstance.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\n // Focus is on X button.\n this.modalDialogInstance.minimizeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\n // Focus on help button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n const element = document.querySelector('[title=\"Manual\"]');\n element.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n } else {\n // There should be only one element with class name 'wrs_formulaDisplay'.\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\n // Focus is on formuladisplay.\n this.modalDialogInstance.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n }\n }\n}\n","/**\n * A custom editor is MathType editor with a different\n * @typedef {Object} CustomEditor\n * @property {String} CustomEditor.name - Custom editor name.\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\n * @property {String} CustomEditor.icon - Custom editor icon.\n * @property {String} CustomEditor.confVariable - Configuration property to manage\n * the availability of the custom editor.\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\n */\n\nexport default class CustomEditors {\n /**\n * @classdesc\n * This class represents the MathType custom editors manager.\n * A custom editor is MathType editor with a custom toolbar.\n * This class associates a {@link CustomEditor} to:\n * - It's own formulas\n * - A custom toolbar\n * - An icon to open it from a HTML editor.\n * - A tooltip for the icon.\n * - A global variable to enable or disable it globally.\n * @constructs\n */\n constructor() {\n /**\n * The custom editors.\n * @type {Array.}\n */\n\n this.editors = [];\n /**\n * The active editor name.\n * @type {String}\n */\n this.activeEditor = \"default\";\n }\n\n /**\n * Adds a {@link CustomEditor} to editors array.\n * @param {String} editorName - The editor name.\n * @param {CustomEditor} editorParams - The custom editor parameters.\n */\n addEditor(editorName, editorParams) {\n const customEditor = {};\n customEditor.name = editorParams.name;\n customEditor.toolbar = editorParams.toolbar;\n customEditor.icon = editorParams.icon;\n customEditor.confVariable = editorParams.confVariable;\n customEditor.title = editorParams.title;\n customEditor.tooltip = editorParams.tooltip;\n this.editors[editorName] = customEditor;\n }\n\n /**\n * Enables a {@link CustomEditor}.\n * @param {String} customEditorName - The custom editor name.\n */\n enable(customEditorName) {\n this.activeEditor = customEditorName;\n }\n\n /**\n * Disables a {@link CustomEditor}.\n */\n disable() {\n this.activeEditor = \"default\";\n }\n\n /**\n * Returns the active editor.\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\n */\n getActiveEditor() {\n if (this.activeEditor !== \"default\") {\n return this.editors[this.activeEditor];\n }\n return null;\n }\n}\n","/**\n * Represents the configuration properties generated from the frontend (JavaScript variables).\n * @type {Object}\n * @property {string} imageClassName - Default MathType formula image class.\n * @property {string} imageClassName - Default MathType CAS image class.\n * @ignore\n */\nconst jsProperties = {\n imageCustomEditorName: \"data-custom-editor\",\n imageClassName: \"Wirisformula\",\n CASClassName: \"Wiriscas\",\n};\nexport default jsProperties;\n","export default class Event {\n /**\n * @classdesc\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\n *\n * ```js\n * let customEvent = new Event();\n * customEvent.properties = {};\n *\n * let listeners = new Listeners();\n * listeners.newListener(eventName, callback);\n *\n * listeners.fire(eventName, customEvent) *\n * ```\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the event should be cancelled.\n * @type {Boolean}\n */\n\n this.cancelled = false;\n /**\n * Indicates if the event should be prevented.\n * @type {Boolean}\n */\n this.defaultPrevented = false;\n }\n\n /**\n * Cancels the event.\n */\n cancel() {\n this.cancelled = true;\n }\n\n /**\n * Prevents the default action.\n */\n preventDefault() {\n this.defaultPrevented = true;\n }\n}\n","import IntegrationModel from \"./integrationmodel\";\n\n/**\n\n */\nexport default class PopUpMessage {\n /**\n * @classdesc\n * This class represents a dialog message overlaying a DOM element in order to\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\n * o canceled. In this last case a callback function should be called.\n * @constructs\n * @param {Object} popupMessageAttributes - Object containing popup properties.\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\n * methods for close and cancel actions.\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\n */\n constructor(popupMessageAttributes) {\n /**\n * Element to be overlaid when the popup appears.\n */\n this.overlayElement = popupMessageAttributes.overlayElement;\n\n this.callbacks = popupMessageAttributes.callbacks;\n\n /**\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\n */\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\n\n /**\n * HTMLElement to display the popup message, close button and cancel button.\n */\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n this.message.id = \"wrs_popupmessage\";\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\n this.message.setAttribute(\"role\", \"dialog\");\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\n const paragraph = document.createElement(\"p\");\n const text = document.createTextNode(popupMessageAttributes.strings.message);\n paragraph.appendChild(text);\n paragraph.id = \"description_txt\";\n this.message.appendChild(paragraph);\n\n /**\n * HTML element overlaying the overlayElement.\n */\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\n // We create a overlay that close popup message on click in there\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n /**\n * HTML element containing cancel and close buttons.\n */\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\n this.buttonArea.id = \"wrs_popup_button_area\";\n\n // Close button arguments.\n const buttonSubmitArguments = {\n class: \"wrs_button_accept\",\n innerHTML: popupMessageAttributes.strings.submitString,\n id: \"wrs_popup_accept_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-close-button\",\n };\n\n /**\n * Close button arguments.\n */\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\n this.buttonArea.appendChild(this.closeButton);\n\n // Cancel button arguments.\n const buttonCancelArguments = {\n class: \"wrs_button_cancel\",\n innerHTML: popupMessageAttributes.strings.cancelString,\n id: \"wrs_popup_cancel_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\n };\n\n /**\n * Cancel button.\n */\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\n this.buttonArea.appendChild(this.cancelButton);\n }\n\n /**\n * This method create a button with arguments and return button dom object\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\n * @param {String} parameters.id - Button id.\n * @param {String} parameters.class - Button class name.\n * @param {String} parameters.innerHTML - Button innerHTML text.\n * @param {Object} callback- Callback method to call on click event.\n * @returns {HTMLElement} HTML button.\n */\n // eslint-disable-next-line class-methods-use-this\n createButton(parameters, callback) {\n let element = {};\n element = document.createElement(\"button\");\n element.setAttribute(\"id\", parameters.id);\n element.setAttribute(\"class\", parameters.class);\n element.innerHTML = parameters.innerHTML;\n element.addEventListener(\"click\", callback);\n if (parameters[\"data-testid\"]) {\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\n }\n\n return element;\n }\n\n /**\n * Shows the popupmessage containing a message, and two buttons\n * to cancel the action or close the modal dialog.\n */\n show() {\n if (this.overlayWrapper.style.display !== \"block\") {\n // Clear focus with blur for prevent press any key.\n document.activeElement.blur();\n this.overlayWrapper.style.display = \"block\";\n this.closeButton.focus();\n } else {\n this.overlayWrapper.style.display = \"none\";\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\n }\n }\n\n /**\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\n * A callback method is called (if defined). For example a method to focus the overlaid element.\n */\n cancelAction() {\n this.overlayWrapper.style.display = \"none\";\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\n this.callbacks.cancelCallback();\n // Set temporal image to null to prevent loading\n // an existent formula when starting one from scratch. Make focus come back too.\n // IntegrationModel.setActionsOnCancelButtons();\n }\n }\n\n /**\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\n * For example to close the overlaid element.\n */\n closeAction() {\n this.cancelAction();\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\n this.callbacks.closeCallback();\n }\n IntegrationModel.setActionsOnCancelButtons();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Code to detect Esc event.\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n this.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.closeButton) {\n this.cancelButton.focus();\n } else {\n this.closeButton.focus();\n }\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n}\n","/**\n * This module provides protection against external focus management scripts\n * that might interfere with the MathType editor modal.\n */\n\n/**\n * focusProtection function creates and returns methods to prevent external scripts from\n * interfering with the focus of the MathType modal dialog.\n *\n * @returns {Object} An object with protect and unprotect methods.\n */\nconst focusProtection = () => {\n /**\n * Initialize focus protection on the given modal element.\n *\n * @param {HTMLElement} modalElement - The modal element to protect\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\n * @param {HTMLElement} editorElement - The editor element inside the modal\n */\n const protect = (modalElement, overlayElement, editorElement) => {\n if (!modalElement || !overlayElement || !editorElement) {\n console.warn(\"FocusProtection: Missing required elements\");\n return;\n }\n\n // Apply the focus protection\n overrideFocusBehavior(modalElement, editorElement);\n };\n\n /**\n * Apply focus protection by overriding focus-related methods\n *\n * @param {HTMLElement} modalElement - The modal element\n * @param {HTMLElement} editorElement - The editor element to keep focused\n * @private\n */\n const overrideFocusBehavior = (modalElement, editorElement) => {\n // Store original focus methods to be able to restore them\n const originalElementFocus = HTMLElement.prototype.focus;\n const originalElementBlur = HTMLElement.prototype.blur;\n\n // Override the focus method for all elements\n HTMLElement.prototype.focus = function (...args) {\n // If the modal is open and this is not part of the modal, prevent focus\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\n // If some external script is trying to focus another element, prevent it\n // and restore focus to the editor\n if (editorElement) {\n // Use the original focus method to avoid infinite recursion\n originalElementFocus.apply(editorElement, args);\n }\n return;\n }\n\n // Otherwise, allow the focus to happen\n originalElementFocus.apply(this, args);\n };\n\n // Store the methods to remove them when the modal is closed\n modalElement.originalElementFocus = originalElementFocus;\n modalElement.originalElementBlur = originalElementBlur;\n };\n\n /**\n * Remove focus protection from the modal\n *\n * @param {HTMLElement} modalElement - The modal element to unprotect\n */\n const unprotect = (modalElement) => {\n if (!modalElement) {\n return;\n }\n\n // Restore original focus methods\n if (modalElement.originalElementFocus) {\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\n delete modalElement.originalElementFocus;\n }\n\n if (modalElement.originalElementBlur) {\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\n delete modalElement.originalElementBlur;\n }\n };\n\n return {\n protect,\n unprotect,\n };\n};\n\nexport default focusProtection;\n","// eslint-disable-next-line max-classes-per-file\nimport PopUpMessage from \"./popupmessage\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport Listeners from \"./listeners\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Telemeter from \"./telemeter\";\nimport IntegrationModel from \"./integrationmodel\";\nimport Core from \"./core.src\";\nimport focusProtection from \"./focusprotection\";\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\nconst { unprotect, protect } = focusProtection();\n\n/**\n * @typedef {Object} DeviceProperties\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\n * False otherwise.\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\n * False otherwise.\n */\n\nexport default class ModalDialog {\n /**\n * @classdesc\n * This class represents a modal dialog. The modal dialog admits\n * a {@link ContentManager} instance to manage the content of the dialog.\n * @constructs\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\n */\n constructor(modalDialogAttributes) {\n this.attributes = modalDialogAttributes;\n\n // Metrics.\n const ua = navigator.userAgent.toLowerCase();\n const isAndroid = ua.indexOf(\"android\") > -1;\n const isIOS = ContentManager.isIOS();\n this.iosSoftkeyboardOpened = false;\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\n this.iosDivHeight = `auto`;\n\n const deviceWidth = window.outerWidth;\n const deviceHeight = window.outerHeight;\n\n const landscape = deviceWidth > deviceHeight;\n const portrait = deviceWidth < deviceHeight;\n\n // TODO: Detect isMobile without using editor metrics.\n const isLandscape = landscape && this.attributes.height > deviceHeight;\n const isPortrait = portrait && this.attributes.width > deviceWidth;\n const isMobile = ContentManager.isMobile();\n\n // Obtain number of current instance.\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\n\n // Device object properties.\n\n /**\n * @type {DeviceProperties}\n */\n this.deviceProperties = {\n orientation: landscape ? \"landscape\" : \"portrait\",\n isAndroid,\n isIOS,\n isMobile,\n isDesktop: !isMobile && !isIOS && !isAndroid,\n };\n\n this.properties = {\n created: false,\n state: \"\",\n previousState: \"\",\n position: { bottom: 0, right: 10 },\n size: { height: 338, width: 580 },\n };\n\n /**\n * Object to keep website's style before change it on lock scroll for mobile devices.\n * @type {Object}\n * @property {String} bodyStylePosition - Previous body style position.\n * @property {String} bodyStyleOverflow - Previous body style overflow.\n * @property {String} htmlStyleOverflow - Previous body style overflow.\n * @property {String} windowScrollX - Previous window's scroll Y.\n * @property {String} windowScrollY - Previous window's scroll X.\n */\n this.websiteBeforeLockParameters = null;\n\n let attributes = {};\n attributes.class = \"wrs_modal_overlay\";\n attributes.id = this.getElementId(attributes.class);\n this.overlay = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title_bar\";\n attributes.id = this.getElementId(attributes.class);\n this.titleBar = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title\";\n attributes.id = this.getElementId(attributes.class);\n this.title = Util.createElement(\"div\", attributes);\n this.title.innerHTML = \"offline\";\n\n attributes = {};\n attributes.class = \"wrs_modal_close_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"close\");\n attributes.style = {};\n this.closeDiv = Util.createElement(\"a\", attributes);\n this.closeDiv.setAttribute(\"role\", \"button\");\n this.closeDiv.setAttribute(\"tabindex\", 3);\n // Apply styles and events after the creation as createElement doesn't process them correctly\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\n // To identifiy the element in automated testing\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_stack_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"exit_fullscreen\");\n this.stackDiv = Util.createElement(\"a\", attributes);\n this.stackDiv.setAttribute(\"role\", \"button\");\n this.stackDiv.setAttribute(\"tabindex\", 2);\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\n // To identifiy the element in automated testing\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_maximize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"fullscreen\");\n this.maximizeDiv = Util.createElement(\"a\", attributes);\n this.maximizeDiv.setAttribute(\"role\", \"button\");\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\n // To identifiy the element in automated testing\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_minimize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"minimize\");\n this.minimizeDiv = Util.createElement(\"a\", attributes);\n this.minimizeDiv.setAttribute(\"role\", \"button\");\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\n // To identify the element in automated testing\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_dialogContainer\";\n attributes.id = this.getElementId(attributes.class);\n attributes.role = \"dialog\";\n this.container = Util.createElement(\"div\", attributes);\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\n\n attributes = {};\n attributes.class = \"wrs_modal_wrapper\";\n attributes.id = this.getElementId(attributes.class);\n this.wrapper = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_content_container\";\n attributes.id = this.getElementId(attributes.class);\n this.contentContainer = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_controls\";\n attributes.id = this.getElementId(attributes.class);\n this.controls = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_buttons_container\";\n attributes.id = this.getElementId(attributes.class);\n this.buttonContainer = Util.createElement(\"div\", attributes);\n\n // Buttons: all button must be created using createSubmitButton method.\n this.submitButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_accept\"),\n class: \"wrs_modal_button_accept\",\n innerHTML: StringManager.get(\"accept\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-insert-button\",\n },\n this.submitAction.bind(this),\n );\n\n this.cancelButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_cancel\"),\n class: \"wrs_modal_button_cancel\",\n innerHTML: StringManager.get(\"cancel\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cancel-button\",\n },\n this.cancelAction.bind(this),\n );\n\n this.contentManager = null;\n\n // Overlay popup.\n const popupStrings = {\n cancelString: StringManager.get(\"cancel\"),\n submitString: StringManager.get(\"close\"),\n message: StringManager.get(\"close_modal_warning\"),\n };\n\n const callbacks = {\n closeCallback: () => {\n this.close(\"mtc_close\");\n },\n cancelCallback: () => {\n this.focus();\n },\n };\n\n const popupupProperties = {\n overlayElement: this.container,\n callbacks,\n strings: popupStrings,\n };\n\n this.popup = new PopUpMessage(popupupProperties);\n\n /**\n * Indicates if directionality of the modal dialog is RTL. false by default.\n * @type {Boolean}\n */\n this.rtl = false;\n if (\"rtl\" in this.attributes) {\n this.rtl = this.attributes.rtl;\n }\n\n // Event handlers need modal instance context.\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\n }\n\n /**\n * This method sets an ContentManager instance to ModalDialog. ContentManager\n * manages the logic of ModalDialog content: submit, update, close and changes.\n * @param {ContentManager} contentManager - ContentManager instance.\n */\n setContentManager(contentManager) {\n this.contentManager = contentManager;\n }\n\n /**\n * Returns the modal contentElement object.\n * @returns {ContentManager} the instance of the ContentManager class.\n */\n getContentManager() {\n return this.contentManager;\n }\n\n /**\n * This method is called when the modal object has been submitted. Calls\n * contentElement submitAction method - if exists - and closes the modal\n * object. No logic about the content should be placed here,\n * contentElement.submitAction is the responsible of the content logic.\n */\n async submitAction() {\n if (typeof this.contentManager.submitAction !== \"undefined\") {\n this.contentManager.submitAction();\n }\n\n await this.close(\"mtc_insert\");\n }\n\n /**\n * Performs the cancel action.\n * If there are no changes in the content, it closes the modal.\n * Otherwise, it shows a pop-up message to confirm the cancel action.\n * @returns {Promise} - A promise that resolves when the modal is closed.\n */\n async cancelAction() {\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\n IntegrationModel.setActionsOnCancelButtons();\n await this.close(\"mtc_close\");\n } else {\n this.showPopUpMessage();\n }\n }\n\n /**\n * Returns a button element.\n * @param {Object} properties - Input button properties.\n * @param {String} properties.class - Input button class.\n * @param {String} properties.innerHTML - Input button innerHTML.\n * @param {Object} callback - Callback function associated to click event.\n * @returns {HTMLButtonElement} The button element.\n *\n */\n // eslint-disable-next-line class-methods-use-this\n createSubmitButton(properties, callback) {\n class SubmitButton {\n constructor() {\n this.element = document.createElement(\"button\");\n this.element.id = properties.id;\n this.element.className = properties.class;\n this.element.innerHTML = properties.innerHTML;\n this.element.dataset.testid = properties[\"data-testid\"];\n Util.addEvent(this.element, \"click\", callback);\n }\n\n getElement() {\n return this.element;\n }\n }\n return new SubmitButton(properties, callback).getElement();\n }\n\n /**\n * Creates the modal window object inserting a contentElement object.\n */\n create() {\n /* Modal Window Structure\n _____________________________________________________________________________________\n |wrs_modal_dialog_Container |\n | _________________________________________________________________________________ |\n | |title_bar minimize_button stack_button close_button | |\n | |_______________________________________________________________________________| |\n | |wrapper | |\n | | _____________________________________________________________________________ | |\n | | |content | | |\n | | | | | |\n | | | | | |\n | | |___________________________________________________________________________| | |\n | | _____________________________________________________________________________ | |\n | | |controls | | |\n | | | ___________________________________ | | |\n | | | |buttonContainer | | | |\n | | | | _______________________________ | | | |\n | | | | |button_accept | button_cancel| | | | |\n | | | |_|_____________ |______________|_| | | |\n | | |___________________________________________________________________________| | |\n | |_______________________________________________________________________________| |\n |___________________________________________________________________________________| */\n\n this.titleBar.appendChild(this.closeDiv);\n this.titleBar.appendChild(this.stackDiv);\n this.titleBar.appendChild(this.maximizeDiv);\n this.titleBar.appendChild(this.minimizeDiv);\n this.titleBar.appendChild(this.title);\n\n if (this.deviceProperties.isDesktop) {\n this.container.appendChild(this.titleBar);\n }\n\n this.wrapper.appendChild(this.contentContainer);\n this.wrapper.appendChild(this.controls);\n\n this.controls.appendChild(this.buttonContainer);\n\n this.buttonContainer.appendChild(this.submitButton);\n this.buttonContainer.appendChild(this.cancelButton);\n\n this.container.appendChild(this.wrapper);\n\n // Check if browser has scrollBar before modal has modified.\n this.recalculateScrollBar();\n\n document.body.appendChild(this.container);\n document.body.appendChild(this.overlay);\n\n if (this.deviceProperties.isDesktop) {\n // Desktop.\n this.createModalWindowDesktop();\n this.createResizeButtons();\n\n this.addListeners();\n // Maximize window only when the configuration is set and the device is not iOS or Android.\n if (Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n } else if (this.deviceProperties.isAndroid) {\n this.createModalWindowAndroid();\n } else if (this.deviceProperties.isIOS) {\n this.createModalWindowIos();\n }\n\n if (this.contentManager != null) {\n this.contentManager.insert(this);\n }\n\n this.properties.open = true;\n this.properties.created = true;\n\n // Checks language directionality.\n if (this.isRTL()) {\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\n this.container.className += \" wrs_modal_rtl\";\n }\n }\n\n /**\n * Creates a button in the modal object to resize it.\n */\n createResizeButtons() {\n // This is a definition of Resize Button Bottom-Right.\n this.resizerBR = document.createElement(\"div\");\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\n this.resizerBR.innerHTML = \"โ—ข\";\n // To identifiy the element in automated testing\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\n // This is a definition of Resize Button Top-Left.\n this.resizerTL = document.createElement(\"div\");\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\n // To identifiy the element in automated testing\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\n // Append resize buttons to modal.\n this.container.appendChild(this.resizerBR);\n this.titleBar.appendChild(this.resizerTL);\n // Add events to resize on click and drag.\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\n }\n\n /**\n * Initialize variables for Bottom-Right resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateBR(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, false);\n }\n\n /**\n * Initialize variables for Top-Left resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateTL(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, true);\n }\n\n /**\n * Common method to initialize variables at resize.\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n initializeResizeProperties(mouseEvent, leftOption) {\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n this.resizeDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Save Initial state of modal to compare on drag and obtain the difference.\n this.initialWidth = parseInt(this.container.style.width, 10);\n this.initialHeight = parseInt(this.container.style.height, 10);\n if (!leftOption) {\n this.initialRight = parseInt(this.container.style.right, 10);\n this.initialBottom = parseInt(this.container.style.bottom, 10);\n } else {\n this.leftScale = true;\n }\n if (!this.initialRight) {\n this.initialRight = 0;\n }\n if (!this.initialBottom) {\n this.initialBottom = 0;\n }\n // Disable mouse events on editor when we start to drag modal.\n document.body.style[\"user-select\"] = \"none\";\n }\n\n /**\n * This method opens the modal window, restoring the previous state, position and metrics,\n * if exists. By default the modal object opens in stack mode.\n */\n open() {\n // Removing close class.\n this.removeClass(\"wrs_closed\");\n // Hiding keyboard for mobile devices.\n const { isIOS } = this.deviceProperties;\n const { isAndroid } = this.deviceProperties;\n const { isMobile } = this.deviceProperties;\n if (isIOS || isAndroid || isMobile) {\n // Restore scale to 1.\n this.restoreWebsiteScale();\n this.lockWebsiteScroll();\n // Due to editor wait we need to wait until editor focus.\n setTimeout(() => {\n this.hideKeyboard();\n }, 400);\n }\n\n // New modal window. He need to create the whole object.\n if (!this.properties.created) {\n this.create();\n } else {\n // Previous state closed. Open method can be called even the previous state is open,\n // for example updating the content of the modal object.\n if (!this.properties.open) {\n this.properties.open = true;\n\n // Restoring the previous open state: if the modal object has been closed\n // re-open it should preserve the state and the metrics.\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\n this.restoreState();\n }\n }\n\n // Maximize window only when the configuration is set and the device is not iOs or Android.\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n\n // In iOS we need to recalculate the size of the modal object because\n // iOS keyboard is a float div which can overlay the modal object.\n if (this.deviceProperties.isIOS) {\n this.iosSoftkeyboardOpened = false;\n }\n }\n\n if (!ContentManager.isEditorLoaded()) {\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.displayEditor();\n });\n this.contentManager.addListener(listener);\n } else {\n this.displayEditor();\n }\n }\n\n /**\n * Prepares and displays the editor in the modal.\n *\n * This method is responsible for displaying the MathType editor inside the modal container.\n *\n * For Moodle environments, it applies focus protection to prevent external scripts\n * from hijacking focus away from the editor while it's open. This is particularly\n * important in Moodle which may have its own focus management scripts.\n * @returns {void}\n */\n displayEditor() {\n if (this.contentManager.integrationModel.isMoodle) {\n protect(this.container, this.overlay, this.contentContainer);\n }\n\n // Initialize and open the editor using the contentManager.\n this.contentManager.onOpen(this);\n }\n\n /**\n * Closes the modal.\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\n * @returns {Promise} A promise that resolves when the modal is closed.\n */\n async close(trigger) {\n // Remove focus protection before closing\n unprotect(this.container);\n\n this.removeClass(\"wrs_maximized\");\n this.removeClass(\"wrs_minimized\");\n this.removeClass(\"wrs_stack\");\n this.addClass(\"wrs_closed\");\n this.saveModalProperties();\n this.unlockWebsiteScroll();\n this.properties.open = false;\n\n if (trigger) {\n try {\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\n toolbar: this.contentManager.toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\n }\n }\n\n Core.globalListeners.fire(\"onModalClose\", {});\n }\n\n /**\n * Closes modal window and destroys the object.\n */\n destroy() {\n // Remove focus protection before destroying\n unprotect(this.container);\n\n // Close modal window.\n this.close();\n // Remove listeners and destroy the object.\n this.removeListeners();\n this.overlay.remove();\n this.container.remove();\n // Reset properties to allow open again.\n this.properties.created = false;\n }\n\n /**\n * Sets the website scale to one.\n */\n // eslint-disable-next-line class-methods-use-this\n restoreWebsiteScale() {\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\n // Let the equal symbols in order to search and make meta's final content.\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\n const contentAttr = viewportelement.getAttribute(\"content\");\n // If it exists, we need to maintain old values and put our values.\n if (contentAttr) {\n const attrArray = contentAttr.split(\",\");\n let finalContentMeta = \"\";\n const oldAttrs = [];\n for (let i = 0; i < attrArray.length; i += 1) {\n let isAttrToUpdate = false;\n let j = 0;\n while (!isAttrToUpdate && j < contentAttrs.length) {\n if (attrArray[i].indexOf(contentAttrs[j])) {\n isAttrToUpdate = true;\n }\n j += 1;\n }\n\n if (!isAttrToUpdate) {\n oldAttrs.push(attrArray[i]);\n }\n }\n\n for (let i = 0; i < contentAttrs.length; i += 1) {\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\n finalContentMeta += i === 0 ? attr : `,${attr}`;\n }\n\n for (let i = 0; i < oldAttrs.length; i += 1) {\n finalContentMeta += `,${oldAttrs[i]}`;\n }\n viewportelement.setAttribute(\"content\", finalContentMeta);\n // It needs to set to empty because setAttribute refresh only when attribute is different.\n viewportelement.setAttribute(\"content\", \"\");\n viewportelement.setAttribute(\"content\", contentAttr);\n } else {\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\n viewportelement.removeAttribute(\"content\");\n }\n };\n\n if (!viewportmeta) {\n viewportmeta = document.createElement(\"meta\");\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n viewportmeta.remove();\n } else {\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n }\n }\n\n /**\n * Locks website scroll for mobile devices.\n */\n lockWebsiteScroll() {\n this.websiteBeforeLockParameters = {\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\n windowScrollX: window.scrollX,\n windowScrollY: window.scrollY,\n };\n }\n\n /**\n * Unlocks website scroll for mobile devices.\n */\n unlockWebsiteScroll() {\n if (this.websiteBeforeLockParameters) {\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\n const { windowScrollX } = this.websiteBeforeLockParameters;\n const { windowScrollY } = this.websiteBeforeLockParameters;\n window.scrollTo(windowScrollX, windowScrollY);\n this.websiteBeforeLockParameters = null;\n }\n }\n\n /**\n * Util function to known if browser is IE11.\n * @returns {Boolean} true if the browser is IE11. false otherwise.\n */\n // eslint-disable-next-line class-methods-use-this\n isIE11() {\n if (\n navigator.userAgent.search(\"Msie/\") >= 0 ||\n navigator.userAgent.search(\"Trident/\") >= 0 ||\n navigator.userAgent.search(\"Edge/\") >= 0\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns if the current language type is RTL.\n * @return {Boolean} true if current language is RTL. false otherwise.\n */\n isRTL() {\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\n return true;\n }\n return this.rtl;\n }\n\n /**\n * Adds a class to all modal ModalDialog DOM elements.\n * @param {String} className - Class name.\n */\n addClass(className) {\n Util.addClass(this.overlay, className);\n Util.addClass(this.titleBar, className);\n Util.addClass(this.overlay, className);\n Util.addClass(this.container, className);\n Util.addClass(this.contentContainer, className);\n Util.addClass(this.stackDiv, className);\n Util.addClass(this.minimizeDiv, className);\n Util.addClass(this.maximizeDiv, className);\n Util.addClass(this.wrapper, className);\n }\n\n /**\n * Remove a class from all modal DOM elements.\n * @param {String} className - Class name.\n */\n removeClass(className) {\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.titleBar, className);\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.container, className);\n Util.removeClass(this.contentContainer, className);\n Util.removeClass(this.stackDiv, className);\n Util.removeClass(this.minimizeDiv, className);\n Util.removeClass(this.maximizeDiv, className);\n Util.removeClass(this.wrapper, className);\n }\n\n /**\n * Create modal dialog for desktop.\n */\n createModalWindowDesktop() {\n this.addClass(\"wrs_modal_desktop\");\n this.stack();\n }\n\n /**\n * Create modal dialog for non android devices.\n */\n createModalWindowAndroid() {\n this.addClass(\"wrs_modal_android\");\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\n }\n\n /**\n * Create modal dialog for iOS devices.\n */\n createModalWindowIos() {\n this.addClass(\"wrs_modal_ios\");\n // Refresh the size when the orientation is changed.\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\n }\n\n /**\n * Restore previous state, position and size of previous stacked modal dialog.\n */\n restoreState() {\n if (this.properties.state === \"maximized\") {\n // Reset states for prevent return to stack state.\n this.maximize();\n } else if (this.properties.state === \"minimized\") {\n // Reset states for prevent return to stack state.\n this.properties.state = this.properties.previousState;\n this.properties.previousState = \"\";\n this.minimize();\n } else {\n this.stack();\n }\n }\n\n /**\n * Stacks the modal object.\n */\n stack() {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"stack\";\n this.removeClass(\"wrs_maximized\");\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n this.addClass(\"wrs_stack\");\n\n // Change maximize/minimize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n this.restoreModalProperties();\n\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\n this.setResizeButtonsVisibility();\n }\n\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n this.focus();\n }\n\n /**\n * Minimizes the modal object.\n */\n minimize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n this.title.style.cursor = \"pointer\";\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\n this.stack();\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n // Setting css to prevent important tag into css style.\n this.container.style.height = \"30px\";\n this.container.style.width = \"250px\";\n this.container.style.bottom = \"0px\";\n this.container.style.right = \"10px\";\n\n this.removeListeners();\n this.properties.previousState = this.properties.state;\n this.properties.state = \"minimized\";\n this.setResizeButtonsVisibility();\n this.minimizeDiv.title = StringManager.get(\"maximize\");\n\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.removeClass(\"wrs_stack\");\n } else {\n this.removeClass(\"wrs_maximized\");\n }\n this.addClass(\"wrs_minimized\");\n\n // Change minimize icon to maximize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n }\n }\n\n /**\n * Maximizes the modal object.\n */\n maximize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n if (this.properties.state !== \"maximized\") {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"maximized\";\n }\n // Don't permit resize on maximize mode.\n this.setResizeButtonsVisibility();\n\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.container.style.left = null;\n this.container.style.top = null;\n this.removeClass(\"wrs_stack\");\n }\n\n this.addClass(\"wrs_maximized\");\n\n // Change maximize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n // Set size to 80% screen with a max size.\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\n if (this.container.clientHeight > 700) {\n this.container.style.height = \"700px\";\n }\n if (this.container.clientWidth > 1200) {\n this.container.style.width = \"1200px\";\n }\n\n // Setting modal position in center on screen.\n const { innerHeight } = window;\n const { innerWidth } = window;\n const { offsetHeight } = this.container;\n const { offsetWidth } = this.container;\n const bottom = innerHeight / 2 - offsetHeight / 2;\n const right = innerWidth / 2 - offsetWidth / 2;\n\n this.setPosition(bottom, right);\n this.recalculateScale();\n this.recalculatePosition();\n this.recalculateSize();\n this.focus();\n }\n\n /**\n * Expand again the modal object from a minimized state.\n */\n reExpand() {\n if (this.properties.state === \"minimized\") {\n if (this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n this.stack();\n }\n this.title.style.cursor = \"\";\n }\n }\n\n /**\n * Sets modal size.\n * @param {Number} height - Height of the ModalDialog\n * @param {Number} width - Width of the ModalDialog.\n */\n setSize(height, width) {\n this.container.style.height = `${height}px`;\n this.container.style.width = `${width}px`;\n this.recalculateSize();\n }\n\n /**\n * Sets modal position using bottom and right style attributes.\n * @param {number} bottom - bottom attribute.\n * @param {number} right - right attribute.\n */\n setPosition(bottom, right) {\n this.container.style.bottom = `${bottom}px`;\n this.container.style.right = `${right}px`;\n }\n\n /**\n * Saves position and size parameters of and open ModalDialog. This attributes\n * are needed to restore it on re-open.\n */\n saveModalProperties() {\n // Saving values of modal only when modal is in stack state.\n if (this.properties.state === \"stack\") {\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\n this.properties.position.right = parseInt(this.container.style.right, 10);\n this.properties.size.width = parseInt(this.container.style.width, 10);\n this.properties.size.height = parseInt(this.container.style.height, 10);\n }\n }\n\n /**\n * Restore ModalDialog position and size parameters.\n */\n restoreModalProperties() {\n if (this.properties.state === \"stack\") {\n // Restoring Bottom and Right values from last modal.\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\n // Restoring Height and Left values from last modal.\n this.setSize(this.properties.size.height, this.properties.size.width);\n }\n }\n\n /**\n * Sets the modal dialog initial size.\n */\n recalculateSize() {\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\n }\n\n /**\n * Enable or disable visibility of resize buttons in modal window depend on state.\n */\n setResizeButtonsVisibility() {\n if (this.properties.state === \"stack\") {\n this.resizerTL.style.visibility = \"visible\";\n this.resizerBR.style.visibility = \"visible\";\n } else {\n this.resizerTL.style.visibility = \"hidden\";\n this.resizerBR.style.visibility = \"hidden\";\n }\n }\n\n /**\n * Makes an object draggable adding mouse and touch events.\n */\n addListeners() {\n // Button events (maximize, minimize, stack and close).\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\n this.maximizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n }\n },\n true,\n );\n this.stackDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.minimizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.closeDiv.addEventListener(\"keypress\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n });\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\n\n // Overlay events (close).\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n // Mouse events.\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\n // Key events.\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\n }\n\n /**\n * Removes draggable events from an object.\n */\n removeListeners() {\n // Mouse events.\n Util.removeEvent(window, \"mousedown\", this.startDrag);\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\n Util.removeEvent(window, \"mousemove\", this.drag);\n Util.removeEvent(window, \"resize\", this.onWindowResize);\n // Key events.\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\n }\n\n /**\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\n * @param {MouseEvent} mouseEvent - Mouse event.\n * @return {Object} With the X and Y coordinates.\n */\n // eslint-disable-next-line class-methods-use-this\n eventClient(mouseEvent) {\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\n const client = {\n X: mouseEvent.changedTouches[0].clientX,\n Y: mouseEvent.changedTouches[0].clientY,\n };\n return client;\n }\n const client = {\n X: mouseEvent.clientX,\n Y: mouseEvent.clientY,\n };\n return client;\n }\n\n /**\n * Start drag function: set the object dragDataObject with the draggable\n * object offsets coordinates.\n * when drag starts (on touchstart or mousedown events).\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\n */\n startDrag(mouseEvent) {\n if (this.properties.state === \"minimized\") {\n return;\n }\n if (mouseEvent.target === this.title) {\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\n // Save first click mouse point on screen.\n this.dragDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Reset last drag position when start drag.\n this.lastDrag = {\n x: \"0px\",\n y: \"0px\",\n };\n // Init right and bottom values for window modal if it isn't exist.\n if (this.container.style.right === \"\") {\n this.container.style.right = \"0px\";\n }\n if (this.container.style.bottom === \"\") {\n this.container.style.bottom = \"0px\";\n }\n\n // Needed for IE11 for apply disabled mouse events on editor because\n // internet explorer needs a dynamic object to apply this property.\n if (this.isIE11()) {\n // this.iframe.style['position'] = 'relative';\n }\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n // Obtain screen limits for prevent overflow.\n this.limitWindow = this.getLimitWindow();\n }\n }\n }\n\n /**\n * Updates dragDataObject with the draggable object coordinates when\n * the draggable object is being moved.\n * @param {MouseEvent} mouseEvent - The mouse event.\n */\n drag(mouseEvent) {\n if (this.dragDataObject) {\n mouseEvent.preventDefault();\n // Calculate max and min between actual mouse position and limit of screeen.\n // It restric the movement of modal into window.\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\n // Subtract limit with first position to obtain relative pixels increment\n // to the anchor point.\n const dragX = `${limitX - this.dragDataObject.x}px`;\n const dragY = `${limitY - this.dragDataObject.y}px`;\n // Save last valid position of modal before window overflow.\n this.lastDrag = {\n x: dragX,\n y: dragY,\n };\n // This move modal with hardware acceleration.\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\n }\n if (this.resizeDataObject) {\n const { innerWidth } = window;\n const { innerHeight } = window;\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\n if (limitX < 0) {\n limitX = 0;\n }\n\n if (limitY < 0) {\n limitY = 0;\n }\n\n let scaleMultiplier;\n if (this.leftScale) {\n scaleMultiplier = -1;\n } else {\n scaleMultiplier = 1;\n }\n\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\n if (!this.leftScale) {\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\n } else {\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\n this.container.style.width = \"580px\";\n }\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\n } else {\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\n this.container.style.height = \"338px\";\n }\n }\n this.recalculateScale();\n this.recalculatePosition();\n }\n }\n\n /**\n * Returns the boundaries of actual window to limit modal movement.\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\n */\n getLimitWindow() {\n // Obtain dimensions of window page.\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight;\n\n // Calculate relative position of mouse point into window.\n const { offsetHeight } = this.container;\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\n const contStyleRight = parseInt(this.container.style.right, 10);\n\n const { pageXOffset } = window;\n const dragY = this.dragDataObject.y;\n const dragX = this.dragDataObject.x;\n\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\n\n // Calculate limits with sizes of window, modal and mouse position.\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\n const minPointer = { x: minPointerX, y: minPointerY };\n const maxPointer = { x: maxPointerX, y: maxPointerY };\n return { minPointer, maxPointer };\n }\n\n /**\n * Returns the scrollbar width size of browser\n * @returns {Number} The scrollbar width.\n */\n // eslint-disable-next-line class-methods-use-this\n getScrollBarWidth() {\n // Create a paragraph with full width of page.\n const inner = document.createElement(\"p\");\n inner.style.width = \"100%\";\n inner.style.height = \"200px\";\n\n // Create a hidden div to compare sizes.\n const outer = document.createElement(\"div\");\n outer.style.position = \"absolute\";\n outer.style.top = \"0px\";\n outer.style.left = \"0px\";\n outer.style.visibility = \"hidden\";\n outer.style.width = \"200px\";\n outer.style.height = \"150px\";\n outer.style.overflow = \"hidden\";\n outer.appendChild(inner);\n\n document.body.appendChild(outer);\n const widthOuter = inner.offsetWidth;\n\n // Change type overflow of paragraph for measure scrollbar.\n outer.style.overflow = \"scroll\";\n let widthInner = inner.offsetWidth;\n\n // If measure is the same, we compare with internal div.\n if (widthOuter === widthInner) {\n widthInner = outer.clientWidth;\n }\n document.body.removeChild(outer);\n\n return widthOuter - widthInner;\n }\n\n /**\n * Set the dragDataObject to null.\n */\n stopDrag() {\n // Due to we have multiple events that call this function, we need only to execute\n // the next modifiers one time,\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\n if (this.dragDataObject || this.resizeDataObject) {\n // If modal doesn't change, it's not necessary to set position with interpolation.\n this.container.style.transform = \"\";\n if (this.dragDataObject) {\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\n }\n // We make focus on editor after drag modal windows to prevent lose focus.\n this.focus();\n // Restore mouse events on iframe.\n // this.iframe.style['pointer-events'] = 'auto';\n document.body.style[\"user-select\"] = \"\";\n // Restore static state of iframe if we use Internet Explorer.\n if (this.isIE11()) {\n // this.iframe.style['position'] = null;\n }\n // Active text select event.\n Util.removeClass(document.body, \"wrs_noselect\");\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\n }\n this.dragDataObject = null;\n this.resizeDataObject = null;\n this.initialWidth = null;\n this.leftScale = null;\n }\n\n /**\n * Recalculates scale for modal when resize browser window.\n */\n onWindowResize() {\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n }\n\n /**\n * Triggers keyboard events:\n * - Tab key tab to go to submit button.\n * - Esc key to close the modal dialog.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Popupmessage is not oppened.\n if (this.popup.overlayWrapper.style.display !== \"block\") {\n // Code to detect Esc event\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n if (this.properties.open) {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.cancelButton) {\n this.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.submitButton) {\n this.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n }\n } else {\n // Popupmessage oppened.\n this.popup.onKeyDown(keyboardEvent);\n }\n }\n }\n\n /**\n * Recalculating position for modal dialog when the browser is resized.\n */\n recalculatePosition() {\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\n if (parseInt(this.container.style.right, 10) < 0) {\n this.container.style.right = \"0px\";\n }\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\n if (parseInt(this.container.style.bottom, 10) < 0) {\n this.container.style.bottom = \"0px\";\n }\n }\n\n /**\n * Recalculating scale for modal when the browser is resized.\n */\n recalculateScale() {\n let sizeModified = false;\n\n if (parseInt(this.container.style.width, 10) > 580) {\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\n sizeModified = true;\n } else {\n this.container.style.width = \"580px\";\n sizeModified = true;\n }\n\n if (parseInt(this.container.style.height, 10) > 338) {\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\n sizeModified = true;\n } else {\n this.container.style.height = \"338px\";\n sizeModified = true;\n }\n\n if (sizeModified) {\n this.recalculateSize();\n }\n }\n\n /**\n * Recalculating width of browser scroll bar.\n */\n recalculateScrollBar() {\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\n if (this.hasScrollBar) {\n this.scrollbarWidth = this.getScrollBarWidth();\n } else {\n this.scrollbarWidth = 0;\n }\n }\n\n /**\n * Hide soft keyboards on iOS devices.\n */\n // eslint-disable-next-line class-methods-use-this\n hideKeyboard() {\n // iOS keyboard can't be detected or hide directly from JavaScript.\n // So, this method simulates that user focus a text input and blur\n // the selection.\n const inputField = document.createElement(\"input\");\n this.container.appendChild(inputField);\n inputField.focus();\n inputField.blur();\n // Is removed to not see it.\n inputField.remove();\n }\n\n /**\n * Focus to contentManager object.\n */\n focus() {\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\n this.contentManager.onFocus();\n }\n }\n\n /**\n * Returns true when the device is on portrait mode.\n */\n // eslint-disable-next-line class-methods-use-this\n portraitMode() {\n return window.innerHeight > window.innerWidth;\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is opened.\n */\n handleOpenedIosSoftkeyboard() {\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\n if (this.portraitMode()) {\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\n }\n }\n this.iosSoftkeyboardOpened = true;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is closed.\n */\n handleClosedIosSoftkeyboard() {\n this.iosSoftkeyboardOpened = false;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Change container sizes when orientation is changed on iOS.\n */\n orientationChangeIosSoftkeyboard() {\n if (this.iosSoftkeyboardOpened) {\n if (this.portraitMode()) {\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\n }\n } else {\n this.wrapper.style.flexGrow = \"1\";\n }\n }\n\n /**\n * Change container sizes when orientation is changed on Android.\n */\n orientationChangeAndroidSoftkeyboard() {\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Set iframe container height.\n * @param {Number} height - New height.\n */\n setContainerHeight(height) {\n this.iosDivHeight = height;\n this.wrapper.style.height = height;\n }\n\n /**\n * Check content of editor before close action.\n */\n showPopUpMessage() {\n if (this.properties.state === \"minimized\") {\n this.stack();\n }\n this.popup.show();\n }\n\n /**\n * Sets the title of the modal dialog.\n * @param {String} title - Modal dialog title.\n */\n setTitle(title) {\n this.title.innerHTML = title;\n }\n\n /**\n * Returns the id of an element, adding the instance number to\n * the element class name:\n * className --> className[idNumber]\n * @param {String} className - The element class name.\n * @returns {String} A string appending the instance id to the className.\n */\n getElementId(className) {\n return `${className}[${this.instanceId}]`;\n }\n}\n","/* eslint-disable */\nvar polyfills;\nexport default polyfills;\n\n// Polyfills.\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\nif (!String.prototype.codePointAt) {\n (function () {\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\n var codePointAt = function (position) {\n if (this == null) {\n throw TypeError();\n }\n var string = String(this);\n var size = string.length;\n // `ToInteger`\n var index = position ? Number(position) : 0;\n if (index != index) {\n // better `isNaN`\n index = 0;\n }\n // Account for out-of-bounds indices:\n if (index < 0 || index >= size) {\n return undefined;\n }\n // Get the first code unit\n var first = string.charCodeAt(index);\n var second;\n if (\n // check if itโ€™s the start of a surrogate pair\n first >= 0xd800 &&\n first <= 0xdbff && // high surrogate\n size > index + 1 // there is a next code unit\n ) {\n second = string.charCodeAt(index + 1);\n if (second >= 0xdc00 && second <= 0xdfff) {\n // low surrogate\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\n }\n }\n return first;\n };\n if (Object.defineProperty) {\n Object.defineProperty(String.prototype, \"codePointAt\", {\n value: codePointAt,\n configurable: true,\n writable: true,\n });\n } else {\n String.prototype.codePointAt = codePointAt;\n }\n })();\n}\n\n// Object.assign polyfill.\nif (typeof Object.assign != \"function\") {\n // Must be writable: true, enumerable: false, configurable: true\n Object.defineProperty(Object, \"assign\", {\n value: function assign(target, varArgs) {\n // .length of function is 2\n \"use strict\";\n if (target == null) {\n // TypeError if undefined or null\n throw new TypeError(\"Cannot convert undefined or null to object\");\n }\n\n var to = Object(target);\n\n for (var index = 1; index < arguments.length; index++) {\n var nextSource = arguments[index];\n\n if (nextSource != null) {\n // Skip over if undefined or null\n for (var nextKey in nextSource) {\n // Avoid bugs when hasOwnProperty is shadowed\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\n to[nextKey] = nextSource[nextKey];\n }\n }\n }\n }\n return to;\n },\n writable: true,\n configurable: true,\n });\n}\n\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\nif (!Array.prototype.includes) {\n Object.defineProperty(Array.prototype, \"includes\", {\n value: function (searchElement, fromIndex) {\n if (this == null) {\n throw new TypeError('\"this\" s null or is not defined');\n }\n\n // 1. Let O be ? ToObject(this value).\n var o = Object(this);\n\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\n var len = o.length >>> 0;\n\n // 3. if len is 0, return false.\n if (len === 0) {\n return false;\n }\n\n // 4. Let n be ? ToInteger(fromIndex).\n // (if fromIndex is undefinedo, this step generates the value 0.)\n var n = fromIndex | 0;\n\n // 5. if n โ‰ฅ 0, then\n // a. Let k be n.\n // 6. Else n < 0,\n // a. Let k be len + n.\n // b. if k < 0, let k be 0.\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\n\n function sameValueZero(x, y) {\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\n }\n\n // 7. Repeat while k < len\n while (k < len) {\n // a. let element k be the result of ? Get(O, ! ToString(k)).\n // b. if SameValueZero(searchElement, elementK) is true, return true.\n if (sameValueZero(o[k], searchElement)) {\n return true;\n }\n // c. Increase k by 1.\n k++;\n }\n\n // 8. Return false\n return false;\n },\n });\n}\n\nif (!String.prototype.includes) {\n String.prototype.includes = function (search, start) {\n \"use strict\";\n\n if (search instanceof RegExp) {\n throw TypeError(\"first argument must not be a RegExp\");\n }\n if (start === undefined) {\n start = 0;\n }\n return this.indexOf(search, start) !== -1;\n };\n}\n\nif (!String.prototype.startsWith) {\n Object.defineProperty(String.prototype, \"startsWith\", {\n value: function (search, rawPos) {\n var pos = rawPos > 0 ? rawPos | 0 : 0;\n return this.substring(pos, pos + search.length) === search;\n },\n });\n}\n","import Parser from \"./parser\";\nimport Util from \"./util\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport CustomEditors from \"./customeditors\";\nimport Configuration from \"./configuration\";\nimport jsProperties from \"./jsvariables\";\nimport Event from \"./event\";\nimport Listeners from \"./listeners\";\nimport Image from \"./image\";\nimport ServiceProvider from \"./serviceprovider\";\nimport ModalDialog from \"./modal\";\nimport Telemeter from \"./telemeter\";\nimport \"./polyfills\";\nimport \"../styles/styles.css\";\n\n/**\n * @typedef {Object} CoreProperties\n * @property {ServiceProviderProperties} serviceProviderProperties\n * - The ServiceProvider class properties. *\n */\nexport default class Core {\n /**\n * @classdesc\n * This class represents MathType integration Core, managing the following:\n * - Integration initialization.\n * - Event managing.\n * - Insertion of formulas into the edit area.\n * ```js\n * let core = new Core();\n * core.addListener(listener);\n * core.language = 'en';\n *\n * // Initializing Core class.\n * core.init(configurationService);\n * ```\n * @constructs\n * Core constructor.\n * @param {CoreProperties}\n */\n constructor(coreProperties) {\n /**\n * Language. Needed for accessibility and locales. 'en' by default.\n * @type {String}\n */\n this.language = \"en\";\n\n /**\n * Edit mode, 'images' by default. Admits the following values:\n * - images\n * - latex\n * @type {String}\n */\n this.editMode = \"images\";\n\n /**\n * Modal dialog instance.\n * @type {ModalDialog}\n */\n this.modalDialog = null;\n\n /**\n * The instance of {@link CustomEditors}. By default\n * the only custom editor is the Chemistry editor.\n * @type {CustomEditors}\n */\n this.customEditors = new CustomEditors();\n\n /**\n * Chemistry editor.\n * @type {CustomEditor}\n */\n const chemEditorParams = {\n name: \"Chemistry\",\n toolbar: \"chemistry\",\n icon: \"chem.png\",\n confVariable: \"chemEnabled\",\n title: \"ChemType\",\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\n };\n\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @typedef IntegrationEnvironment\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\n * @property {String} IntegrationEnvironment.version - Integration version.\n *\n */\n\n /**\n * The environment properties object.\n * @type {IntegrationEnvironment}\n */\n this.environment = {};\n\n /**\n * @typedef EditionProperties\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\n * False otherwise.\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\n * Null if the formula is new.\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\n * @property {Range} editionProperties.range - The range that contains the image element.\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\n */\n\n /**\n * The properties of the current edition process.\n * @type {EditionProperties}\n */\n this.editionProperties = {};\n\n this.editionProperties.isNewElement = true;\n this.editionProperties.temporalImage = null;\n this.editionProperties.latexRange = null;\n this.editionProperties.range = null;\n this.editionProperties.editionStartTime = null;\n\n /**\n * The {@link IntegrationModel} instance.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n\n /**\n * The {@link ContentManager} instance.\n * @type {ContentManager}\n */\n this.contentManager = null;\n\n /**\n * The current browser.\n * @type {String}\n */\n this.browser = (() => {\n const ua = navigator.userAgent;\n let browser = \"none\";\n if (ua.search(\"Edge/\") >= 0) {\n browser = \"EDGE\";\n } else if (ua.search(\"Chrome/\") >= 0) {\n browser = \"CHROME\";\n } else if (ua.search(\"Trident/\") >= 0) {\n browser = \"IE\";\n } else if (ua.search(\"Firefox/\") >= 0) {\n browser = \"FIREFOX\";\n } else if (ua.search(\"Safari/\") >= 0) {\n browser = \"SAFARI\";\n }\n return browser;\n })();\n\n /**\n * Plugin listeners.\n * @type {Array.}\n */\n this.listeners = new Listeners();\n\n /**\n * Service provider properties.\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = {};\n if (\"serviceProviderProperties\" in coreProperties) {\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\n } else {\n throw new Error(\"serviceProviderProperties property missing.\");\n }\n }\n\n /**\n * Static property.\n * Core listeners.\n * @private\n * @type {Listeners}\n */\n static get globalListeners() {\n return Core._globalListeners;\n }\n\n /**\n * Static property setter.\n * Set core listeners.\n * @param {Listeners} value - The property value.\n * @ignore\n */\n static set globalListeners(value) {\n Core._globalListeners = value;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * True when Core.init was called. Otherwise, false.\n * @private\n * @type {Boolean}\n */\n static get initialized() {\n return Core._initialized;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\n * @ignore\n */\n static set initialized(value) {\n Core._initialized = value;\n }\n\n /**\n * Sets the {@link Core.integrationModel} property.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link Core.environment} property.\n * @param {IntegrationEnvironment} integrationEnvironment -\n * The {@link IntegrationEnvironment} object.\n */\n setEnvironment(integrationEnvironment) {\n if (\"editor\" in integrationEnvironment) {\n this.environment.editor = integrationEnvironment.editor;\n }\n if (\"mode\" in integrationEnvironment) {\n this.environment.mode = integrationEnvironment.mode;\n }\n if (\"version\" in integrationEnvironment) {\n this.environment.version = integrationEnvironment.version;\n }\n }\n\n /**\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\n * @returns {Object} headers - key value headers.\n */\n setHeaders(headers) {\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\n Configuration.set(\"customHeaders\", headerObject);\n }\n\n /**\n * Returns the current {@link ModalDialog} instance.\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\n */\n getModalDialog() {\n return this.modalDialog;\n }\n\n /**\n * Inits the {@link Core} class, doing the following:\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\n * - Updates {@link Configuration} class with the previous configuration properties.\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\n * - Loads language strings.\n * - Fires onLoad event.\n * @param {Object} serviceParameters - Service parameters.\n */\n init() {\n if (!Core.initialized) {\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\n const jsonConfiguration = JSON.parse(jsConfiguration);\n Configuration.addConfiguration(jsonConfiguration);\n // Adding JavaScript (not backend) configuration variables.\n Configuration.addConfiguration(jsProperties);\n // Fire 'onLoad' event:\n // All integration must listen this event in order to know if the plugin\n // has been properly loaded.\n StringManager.language = this.language;\n this.listeners.fire(\"onLoad\", {});\n });\n\n ServiceProvider.addListener(serviceProviderListener);\n ServiceProvider.init(this.serviceProviderProperties);\n\n Core.initialized = true;\n } else {\n // Case when there are more than two editor instances.\n // After the first editor all the other editors don't need to load any file or service.\n this.listeners.fire(\"onLoad\", {});\n }\n }\n\n /**\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\n * @param {Listener} listener - The listener object.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Adds the global {@link Listener} instance to {@link Core} class.\n * @param {Listener} listener - The event listener to be added.\n * @static\n */\n static addGlobalListener(listener) {\n Core.globalListeners.add(listener);\n }\n\n beforeUpdateFormula(mathml, wirisProperties) {\n /**\n * This event is fired before updating the formula.\n * @type {Object}\n * @property {String} mathml - MathML to be transformed.\n * @property {String} editMode - Edit mode.\n * @property {Object} wirisProperties - Extra attributes for the formula.\n * @property {String} language - Formula language.\n */\n const beforeUpdateEvent = new Event();\n\n beforeUpdateEvent.mathml = mathml;\n\n // Cloning wirisProperties object\n // We don't want wirisProperties object modified.\n beforeUpdateEvent.wirisProperties = {};\n\n if (wirisProperties != null) {\n Object.keys(wirisProperties).forEach((attr) => {\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\n });\n }\n\n // Read only.\n beforeUpdateEvent.language = this.language;\n beforeUpdateEvent.editMode = this.editMode;\n\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n return {\n mathml: beforeUpdateEvent.mathml,\n wirisProperties: beforeUpdateEvent.wirisProperties,\n };\n }\n\n /**\n * Converts a MathML into it's correspondent image and inserts the image is\n * inserted in a HTMLElement target by creating\n * a new image or updating an existing one.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\n * @param {Window} windowTarget - The window element where the editable content is.\n * @param {String} mathml - The MathML.\n * @param {Array.} wirisProperties - The extra attributes for the formula.\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n /**\n * It is the object with the information of the node or latex to insert.\n * @typedef ReturnObject\n * @property {Node} [node] - The DOM node to insert.\n * @property {String} [latex] - The latex to insert.\n */\n const returnObject = {};\n\n if (!mathml) {\n this.insertElementOnSelection(null, focusElement, windowTarget);\n } else if (this.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n // this.integrationModel.getNonLatexNode is an integration wrapper\n // to have special behaviours for nonLatex.\n // Not all the integrations have special behaviours for nonLatex.\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.latex = returnObject.latex;\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\n } else {\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n }\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n } else {\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\n\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n }\n\n return returnObject;\n }\n\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\n /**\n * This event is fired after update the formula.\n * @type {Event}\n * @param {String} editMode - edit mode.\n * @param {Object} windowTarget - target window.\n * @param {Object} focusElement - target element to be focused after update.\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\n */\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.node = node;\n afterUpdateEvent.latex = latex;\n\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n return {};\n }\n\n /**\n * Sets the caret after a given Node and set the focus to the owner document.\n * @param {Node} node - The Node element.\n */\n placeCaretAfterNode(node) {\n if (node === null) return;\n\n this.integrationModel.getSelection();\n const nodeDocument = node.ownerDocument;\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\n const range = nodeDocument.createRange();\n range.setStartAfter(node);\n range.collapse(true);\n const selection = nodeDocument.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n nodeDocument.body.focus();\n }\n }\n\n /**\n * Replaces a Selection object with an HTMLElement.\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\n * @param {Window} windowTarget - The window target.\n */\n insertElementOnSelection(element, focusElement, windowTarget) {\n let mathmlOrigin = null;\n if (this.editionProperties.isNewElement) {\n if (element) {\n if (focusElement.type === \"textarea\") {\n Util.updateTextArea(focusElement, element.textContent);\n } else if (document.selection && document.getSelection === 0) {\n let range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n\n if (!(\"parentElement\" in range)) {\n windowTarget.document.execCommand(\"delete\", false);\n range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n }\n\n if (\"parentElement\" in range) {\n const temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\n temporalObject.parentNode.replaceChild(element, temporalObject);\n } else {\n // IE9 fix: parentNode() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML(Util.createObjectCode(element));\n }\n }\n } else {\n let range = null;\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n if (this.editionProperties.range) {\n ({ range } = this.editionProperties);\n this.editionProperties.range = null;\n } else {\n const editorSelection = this.integrationModel.getSelection();\n range = editorSelection.getRangeAt(0);\n }\n\n // Delete if something was surrounded.\n range.deleteContents();\n\n let node = range.startContainer;\n const position = range.startOffset;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n node = node.splitText(position);\n node.parentNode.insertBefore(element, node);\n } else if (node.nodeType === 1) {\n // ELEMENT_NODE.\n node.insertBefore(element, node.childNodes[position]);\n }\n\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n focusElement.focus();\n } else {\n const editorSelection = this.integrationModel.getSelection();\n editorSelection.removeAllRanges();\n\n if (this.editionProperties.range) {\n const { range } = this.editionProperties;\n this.editionProperties.range = null;\n editorSelection.addRange(range);\n }\n }\n } else if (this.editionProperties.latexRange) {\n if (document.selection && document.getSelection === 0) {\n this.editionProperties.isNewElement = true;\n this.editionProperties.latexRange.select();\n this.insertElementOnSelection(element, focusElement, windowTarget);\n } else {\n this.editionProperties.latexRange.deleteContents();\n this.editionProperties.latexRange.insertNode(element);\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n let item;\n // Wrapper for some integrations that can have special behaviours to show latex.\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n item = this.integrationModel.getSelectedItem(focusElement, false);\n } else {\n item = Util.getSelectedItemOnTextarea(focusElement);\n }\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\n } else {\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\n if (element && element.nodeName.toLowerCase() === \"img\") {\n // Editor empty, formula has been erased on edit.\n // There are editors (e.g: CKEditor) that put attributes in images.\n // We don't allow that behaviour in our images.\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\n // Clone is needed to maintain event references to temporalImage.\n Image.clone(element, this.editionProperties.temporalImage);\n } else {\n this.editionProperties.temporalImage.remove();\n }\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const mathml = element?.dataset?.mathml;\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove the desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n }\n\n /**\n * Opens a modal dialog containing MathType editor..\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\n */\n openModalDialog(target, isIframe) {\n // Count the time since the editor is open\n this.editionProperties.editionStartTime = Date.now();\n\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\n this.editMode = \"images\";\n\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n try {\n if (isIframe) {\n // Is needed focus the target first.\n target.contentWindow.focus();\n const selection = target.contentWindow.getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n } else {\n // Is needed focus the target first.\n target.focus();\n const selection = getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n }\n } catch (e) {\n this.editionProperties.range = null;\n }\n\n if (isIframe === undefined) {\n isIframe = true;\n }\n\n this.editionProperties.latexRange = null;\n\n if (target) {\n let selectedItem;\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\n } else {\n selectedItem = Util.getSelectedItem(target, isIframe);\n }\n\n // Check LaTeX if and only if the node is a text node (nodeType==3).\n if (selectedItem) {\n // Case when image was selected and button pressed.\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\n this.editionProperties.temporalImage = selectedItem.node;\n this.editionProperties.isNewElement = false;\n } else if (selectedItem.node.nodeType === 3) {\n // If it's a text node means that editor is working with LaTeX.\n if (this.integrationModel.getMathmlFromTextNode) {\n // If integration has this function it isn't set range due to we don't\n // know if it will be put into a textarea as a text or image.\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (mathml) {\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n }\n } else {\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (latexResult) {\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n const windowTarget = isIframe ? target.contentWindow : window;\n\n if (target.tagName.toLowerCase() !== \"textarea\") {\n if (document.selection) {\n let leftOffset = 0;\n let previousNode = latexResult.startNode.previousSibling;\n\n while (previousNode) {\n leftOffset += Util.getNodeLength(previousNode);\n previousNode = previousNode.previousSibling;\n }\n\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\n } else {\n this.editionProperties.latexRange = windowTarget.document.createRange();\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\n }\n }\n }\n }\n }\n } else if (target.tagName.toLowerCase() === \"textarea\") {\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\n this.editMode = \"latex\";\n }\n }\n\n // Setting an object with the editor parameters.\n // Editor parameters can be customized in several ways:\n // 1 - editorAttributes: Contains the default editor attributes,\n // usually the metrics in a comma separated string. Always exists.\n // 2 - editorParameters: Object containing custom editor parameters.\n // These parameters are defined in the backend. So they affects all integration instances.\n\n // The backend send the default editor attributes in a coma separated\n // with the following structure: key1=value1,key2=value2...\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\n const defaultEditorAttributes = {};\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\n const key = tempAttribute[0];\n const value = tempAttribute[1];\n defaultEditorAttributes[key] = value;\n }\n // Custom editor parameters.\n const editorAttributes = {\n language: this.language, // Default language value\n };\n // Editor parameters in backend, usually configuration.ini.\n const serverEditorParameters = Configuration.get(\"editorParameters\");\n // Editor parameters through JavaScript configuration.\n const clientEditorParameters = this.integrationModel.editorParameters;\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\n\n // Now, update backwards: if user has set a custom language, pass that back to core properties\n this.language = editorAttributes.language;\n StringManager.language = this.language;\n\n editorAttributes.rtl = this.integrationModel.rtl;\n\n const customHeaders = Configuration.get(\"customHeaders\");\n // This is not being used in the code, we are keeping it just in case it's needed.\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\n editorAttributes.customHeaders =\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\n\n const contentManagerAttributes = {};\n contentManagerAttributes.editorAttributes = editorAttributes;\n contentManagerAttributes.language = this.language;\n contentManagerAttributes.customEditors = this.customEditors;\n contentManagerAttributes.environment = this.environment;\n\n if (this.modalDialog == null) {\n this.modalDialog = new ModalDialog(editorAttributes);\n this.contentManager = new ContentManager(contentManagerAttributes);\n // When an instance of ContentManager is created we need to wait until\n // the ContentManager is ready by listening 'onLoad' event.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n });\n this.contentManager.addListener(listener);\n this.contentManager.init();\n this.modalDialog.setContentManager(this.contentManager);\n this.contentManager.setModalDialogInstance(this.modalDialog);\n } else {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n }\n this.contentManager.setIntegrationModel(this.integrationModel);\n this.modalDialog.open();\n }\n\n /**\n * Returns the {@link CustomEditors} instance.\n * @return {CustomEditors} The current {@link CustomEditors} instance.\n */\n getCustomEditors() {\n return this.customEditors;\n }\n}\n\n/**\n * Core static listeners.\n * @type {Listeners}\n * @private\n */\nCore._globalListeners = new Listeners();\n\n/**\n * Resources state. Says if they were loaded or not.\n * @type {Boolean}\n * @private\n */\nCore._initialized = false;\n","// eslint-disable-next-line no-unused-vars, import/named\nimport Core from \"./core.src\";\nimport Image from \"./image\";\nimport Listeners from \"./listeners\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Telemeter from \"./telemeter\";\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\n\n/**\n * @typedef {Object} IntegrationModelProperties\n * @property {string} configurationService - Configuration service path.\n * This parameter is needed to determine all services paths.\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\n * @property {string} integrationModelProperties.scriptName - Integration script name.\n * Usually the name of the integration script.\n * @property {Object} integrationModelProperties.environment - integration environment properties.\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\n * callback method arguments.\n * @property {string} [integrationModelProperties.version] - integration version number.\n * @property {Object} [integrationModelProperties.editorObject] - object containing\n * the integration editor instance.\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\n * false otherwise.\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\n * - The service parameters.\n * @property {Object} [integrationModelProperties.integrationParameters]\n * - Overwritten integration parameters.\n */\n\nexport default class IntegrationModel {\n /**\n * @classdesc\n * This class represents an integration model, allowing the integration script to\n * communicate with Core class. Each integration must extend this class.\n * @constructs\n * @param {IntegrationModelProperties} integrationModelProperties\n */\n constructor(integrationModelProperties) {\n /**\n * Language. Needed for accessibility and locales. English by default.\n */\n this.language = \"en\";\n\n /**\n * Service parameters\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\n\n /**\n * Configuration service path. The integration service is needed by Core class to\n * load all the backend configuration into the frontend and also to create the paths\n * of all services (all services lives in the same route). Mandatory property.\n */\n this.configurationService = \"\";\n if (\"configurationService\" in integrationModelProperties) {\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\n integrationModelProperties.configurationService,\n ]);\n }\n\n /**\n * Plugin version. Needed to stats and caching.\n * @type {string}\n */\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\n\n /**\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\n * Mandatory property.\n */\n this.target = null;\n if (\"target\" in integrationModelProperties) {\n this.target = integrationModelProperties.target;\n } else {\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\n }\n\n /**\n * Integration script name. Needed to know the plugin path.\n */\n if (\"scriptName\" in integrationModelProperties) {\n this.scriptName = integrationModelProperties.scriptName;\n }\n\n /**\n * Object containing the arguments needed by the callback function.\n */\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\n\n /**\n * Contains information about the integration environment:\n * like the name of the editor, the version, etc.\n */\n this.environment = integrationModelProperties.environment ?? {};\n\n /**\n * Indicates if the DOM target is - or not - and iframe.\n */\n this.isIframe = false;\n if (this.target != null) {\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Instance of the integration editor object. Usually the entry point to access the API\n * of a HTML editor.\n */\n this.editorObject = integrationModelProperties.editorObject ?? null;\n\n /**\n * Specifies if the direction of the text is RTL. false by default.\n */\n this.rtl = integrationModelProperties.rtl ?? false;\n\n /**\n * Specifies if the integration model exposes the locale strings. false by default.\n */\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\n\n /**\n * Specify if editor will open in hand mode only\n */\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\n\n /**\n * Indicates if an image is selected. Needed to resize the image to the original size in case\n * the image is resized.\n * @type {boolean}\n */\n this.temporalImageResizing = false;\n\n /**\n * The Core class instance associated to the integration model.\n * @type {Core}\n */\n this.core = null;\n\n /**\n * Integration model listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n // Parameters overwrite.\n if (\"integrationParameters\" in integrationModelProperties) {\n IntegrationModel.integrationParameters.forEach((parameter) => {\n if (parameter in integrationModelProperties.integrationParameters) {\n // Don't add empty parameters.\n const value = integrationModelProperties.integrationParameters[parameter];\n if (Object.keys(value).length !== 0) {\n this[parameter] = value;\n }\n }\n });\n }\n }\n\n /**\n * Init function. Usually called from the integration side once the core.js file is loaded.\n */\n init() {\n // Check if language is an object and select the property necessary\n this.language = this.getLanguage();\n\n // We need to wait until Core class is loaded ('onLoad' event) before\n // call the callback method.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.callbackFunction(this.callbackMethodArguments);\n });\n\n // Backwards compatibility.\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\n const uri = this.serviceProviderProperties.URI;\n const server = ServiceProvider.getServerLanguageFromService(uri);\n this.serviceProviderProperties.server = server;\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\n this.serviceProviderProperties.URI = subsTring;\n }\n\n let serviceParametersURI = this.serviceProviderProperties.URI;\n serviceParametersURI =\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\n ? serviceParametersURI\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\n\n this.serviceProviderProperties.URI = serviceParametersURI;\n\n const coreProperties = {};\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\n\n this.setCore(new Core(coreProperties));\n this.core.addListener(listener);\n this.core.language = this.language;\n\n // Initializing Core class.\n this.core.init();\n // TODO: Move to Core constructor.\n this.core.setEnvironment(this.environment);\n\n // No internet connection modal.\n let attributes = {};\n attributes.class = attributes.id = \"wrs_modal_offline\";\n this.offlineModal = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_content_offline\";\n this.offlineModalContent = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_close\";\n this.offlineModalClose = Util.createElement(\"span\", attributes);\n this.offlineModalClose.innerHTML = \"×\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_warn\";\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_container\";\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_warn\";\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text\";\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\n\n // Append offline modal elements\n this.offlineModalContent.appendChild(this.offlineModalClose);\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\n this.offlineModalContent.appendChild(this.offlineModalMessage);\n this.offlineModalContent.appendChild(this.offlineModalWarn);\n this.offlineModal.appendChild(this.offlineModalContent);\n document.body.appendChild(this.offlineModal);\n\n const modal = document.getElementById(\"wrs_modal_offline\");\n this.offlineModalClose.addEventListener(\"click\", () => {\n modal.style.display = \"none\";\n });\n\n // Store editor name for telemetry purposes.\n let editorName = this.environment.editor;\n editorName = editorName.slice(0, -1); // Remove version number from editors\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\n let solutionTelemeter = editorName;\n\n // If moodle, add information to hosts and solution.\n const isMoodle = !!(typeof M === \"object\" && M !== null);\n let lms;\n\n if (isMoodle) {\n solutionTelemeter = \"Moodle\";\n lms = {\n nam: \"moodle\",\n fam: \"lms\",\n ver: this.environment.moodleVersion,\n category: this.environment.moodleCourseCategory,\n course: this.environment.moodleCourseName,\n };\n if (!editorName.includes(\"TinyMCE\")) {\n editorName = \"Atto\";\n }\n }\n\n // Get the OS and its version.\n const OSData = this.getOS();\n\n // Get the broswer and its version.\n const broswerData = this.getBrowser();\n\n // Create list of hosts to send to telemetry.\n let hosts = [\n {\n nam: broswerData.detectedBrowser,\n fam: \"browser\",\n ver: broswerData.versionBrowser,\n },\n {\n nam: editorName.toLowerCase(),\n fam: \"html-editor\",\n ver: this.environment.editorVersion,\n },\n {\n nam: OSData.detectedOS,\n fam: \"os\",\n ver: OSData.versionOS,\n },\n {\n nam: window.location.hostname,\n fam: \"domain\",\n },\n lms,\n ];\n\n // Filter hosts to remove empty objects and empty keys.\n hosts = hosts.filter((element) => {\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\n return element !== undefined;\n });\n\n // Initialize telemeter\n Telemeter.init({\n solution: {\n name: `MathType for ${solutionTelemeter}`,\n version: this.version,\n },\n hosts,\n config: {\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\n },\n url: undefined,\n });\n }\n\n /**\n * Returns the Browser name and its version.\n * @return {array} - detectedBrowser = Operating System name.\n * versionBrowser = Operating System version.\n */\n getBrowser() {\n // default value for OS just in case nothing is detected\n let detectedBrowser = \"unknown\";\n let versionBrowser = \"unknown\";\n\n const userAgent = window.navigator.userAgent;\n\n if (/Brave/.test(userAgent)) {\n detectedBrowser = \"brave\";\n if (userAgent.indexOf(\"Brave/\")) {\n const start = userAgent.indexOf(\"Brave\") + 6;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\n detectedBrowser = \"edge_chromium\";\n const start = userAgent.indexOf(\"Edg/\") + 4;\n versionBrowser = userAgent\n .substring(start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Edg/.test(userAgent)) {\n detectedBrowser = \"edge\";\n let start = userAgent.indexOf(\"Edg\") + 3;\n start += userAgent.substring(start).indexOf(\"/\");\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\n detectedBrowser = \"firefox\";\n let start = userAgent.indexOf(\"Firefox\");\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/OPR/.test(userAgent)) {\n detectedBrowser = \"opera\";\n const start = userAgent.indexOf(\"OPR/\") + 4;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\n detectedBrowser = \"chrome\";\n let start = userAgent.indexOf(\"Chrome\");\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/Safari/.test(userAgent)) {\n detectedBrowser = \"safari\";\n let start = userAgent.indexOf(\"Version/\");\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n }\n\n return { detectedBrowser, versionBrowser };\n }\n\n /**\n * Returns the operating system and its version.\n * @return {array} - detectedOS = Operating System name.\n * versionOS = Operating System version.\n */\n getOS() {\n // default value for OS just in case nothing is detected\n let detectedOS = \"unknown\";\n let versionOS = \"unknown\";\n\n // Retrieve properties to easily detect the OS\n const userAgent = window.navigator.userAgent;\n const platform = window.navigator.platform;\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\n\n // Find OS and their respective versions\n if (macosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"macos\";\n if (userAgent.indexOf(\"OS X\") !== -1) {\n const start = userAgent.indexOf(\"OS X\") + 5;\n const end = userAgent.substring(start).indexOf(\" \");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (iosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"ios\";\n if (userAgent.indexOf(\"OS \") !== -1) {\n const start = userAgent.indexOf(\"OS \") + 3;\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"windows\";\n const start = userAgent.indexOf(\"Windows\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Android/.test(userAgent)) {\n detectedOS = \"android\";\n const start = userAgent.indexOf(\"Android\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/CrOS/.test(userAgent)) {\n detectedOS = \"chromeos\";\n let start = userAgent.indexOf(\"CrOS \") + 5;\n start += userAgent.substring(start).indexOf(\" \");\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\n detectedOS = \"linux\";\n }\n\n return { detectedOS, versionOS };\n }\n\n /**\n * Returns the absolute path of the integration script.\n * @return {string} - Absolute path for the integration script.\n */\n getPath() {\n if (typeof this.scriptName === \"undefined\") {\n throw new Error(\"scriptName property needed for getPath.\");\n }\n const col = document.getElementsByTagName(\"script\");\n let path = \"\";\n for (let i = 0; i < col.length; i += 1) {\n const j = col[i].src.lastIndexOf(this.scriptName);\n if (j >= 0) {\n path = col[i].src.substr(0, j - 1);\n }\n }\n return path;\n }\n\n /**\n * Returns integration model plugin version\n * @param {string} - Plugin version\n */\n getVersion() {\n return this.version;\n }\n\n /**\n * Sets the language property.\n * @param {string} language - language code.\n */\n setLanguage(language) {\n this.language = language;\n }\n\n /**\n * Sets a Core instance.\n * @param {Core} core - instance of Core class.\n */\n setCore(core) {\n this.core = core;\n core.setIntegrationModel(this);\n }\n\n /**\n * Returns the Core instance.\n * @returns {Core} instance of Core class.\n */\n getCore() {\n return this.core;\n }\n\n /**\n * Sets the object target and updates the iframe property.\n * @param {HTMLElement} target - target object.\n */\n setTarget(target) {\n this.target = target;\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Sets the editor object.\n * @param {Object} editorObject - The editor object.\n */\n setEditorObject(editorObject) {\n this.editorObject = editorObject;\n }\n\n /**\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openNewFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.dbclick = false;\n this.core.editionProperties.isNewElement = true;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openExistingFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.isNewElement = false;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Wrapper to Core.updateFormula method.\n * Transform a MathML into a image formula.\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\n * or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n */\n updateFormula(mathml) {\n if (this.editorParameters) {\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\n mathml,\n \"application/vnd.wiris.mtweb-params+json\",\n JSON.stringify(this.editorParameters),\n );\n }\n let focusElement;\n let windowTarget;\n const wirisProperties = null;\n\n if (this.isIframe) {\n focusElement = this.target.contentWindow;\n windowTarget = this.target.contentWindow;\n } else {\n focusElement = this.target;\n windowTarget = window;\n }\n\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\n }\n\n /**\n * Wrapper to Core.insertFormula method.\n * Inserts the formula in the specified target, creating\n * a new image (new formula) or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\n\n // Delete temporal image when inserted\n this.core.editionProperties.temporalImage = null;\n\n return obj;\n }\n\n /**\n * Returns the target selection.\n * @returns {Selection} target selection.\n */\n getSelection() {\n if (this.isIframe) {\n this.target.contentWindow.focus();\n return this.target.contentWindow.getSelection();\n }\n this.target.focus();\n return window.getSelection();\n }\n\n /**\n * Add events to formulas in the DOM target. The events added are the following:\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\n * to edit them.\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\n * in case the the formula is resized.\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\n * in case the formula is resized.\n */\n addEvents() {\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\n Util.addElementEvents(\n eventTarget,\n (element, event) => {\n this.doubleClickHandler(element, event);\n // Avoid creating the double click listener more than once for each element.\n // This also allows CKEditor4 to add their own double click listener.\n event.preventDefault();\n },\n (element, event) => {\n this.mousedownHandler(element, event);\n },\n (element, event) => {\n this.mouseupHandler(element, event);\n },\n );\n }\n\n /**\n * Remove events to formulas in the DOM target.\n */\n removeEvents() {\n const eventTarget =\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\n\n if (!eventTarget) {\n return;\n }\n\n Util.removeElementEvents(eventTarget);\n }\n\n /**\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\n */\n destroy() {\n this.removeEvents();\n // Destroy modal dialog if exists.\n if (this.core.modalDialog) {\n this.core.modalDialog.destroy();\n }\n\n // Remove offline modal dialog if exists.\n if (this.offlineModal) {\n this.offlineModal.remove();\n }\n\n this.editorObject = null;\n }\n\n /**\n * Handles a Double-click on the target element. Opens an editor\n * to re-edit the double-clicked formula.\n * @param {HTMLElement} element - DOM object target.\n */\n doubleClickHandler(element) {\n this.core.editionProperties.dbclick = true;\n if (element.nodeName.toLowerCase() === \"img\") {\n this.core.getCustomEditors().disable();\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (element.hasAttribute(customEditorAttributeName)) {\n const customEditor = element.getAttribute(customEditorAttributeName);\n this.core.getCustomEditors().enable(customEditor);\n }\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.core.editionProperties.temporalImage = element;\n this.core.editionProperties.isNewElement = true;\n this.openExistingFormulaEditor();\n }\n }\n }\n\n /**\n * Handles a mouse up event on the target element. Restores the image size to avoid\n * resizing formulas.\n */\n mouseupHandler() {\n if (this.temporalImageResizing) {\n setTimeout(() => {\n Image.fixAfterResize(this.temporalImageResizing);\n }, 10);\n }\n }\n\n /**\n * Handles a mouse down event on the target element. Saves the formula size to avoid\n * resizing formulas.\n * @param {HTMLElement} element - target element.\n */\n mousedownHandler(element) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.temporalImageResizing = element;\n }\n }\n }\n\n /**\n * Returns the integration language. By default the browser agent. This method\n * should be overwritten to obtain the integration language, for example using the\n * plugin API of an HTML editor.\n * @returns {string} integration language.\n */\n getLanguage() {\n return this.getBrowserLanguage();\n }\n\n /**\n * Returns the browser language.\n * @returns {string} the browser language.\n */\n // eslint-disable-next-line class-methods-use-this\n getBrowserLanguage() {\n let language = \"en\";\n if (navigator.userLanguage) {\n language = navigator.userLanguage.substring(0, 2);\n } else if (navigator.language) {\n language = navigator.language.substring(0, 2);\n } else {\n language = \"en\";\n }\n return language;\n }\n\n /**\n * This function is called once the {@link Core} is loaded. IntegrationModel class\n * will fire this method when {@link Core} 'onLoad' event is fired.\n * This method should content all the logic to init\n * the integration.\n */\n callbackFunction() {\n // It's needed to wait until the integration target is ready. The event is fired\n // from the integration side.\n const listener = Listeners.newListener(\"onTargetReady\", () => {\n this.addEvents(this.target);\n });\n this.listeners.add(listener);\n }\n\n /**\n * Function called when the content submits an action.\n */\n // eslint-disable-next-line class-methods-use-this\n notifyWindowClosed() {\n // Nothing.\n }\n\n /**\n * Wrapper.\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\n * @param {string} textNode - text node to extract the MathML.\n * @param {int} caretPosition - caret position inside the text node.\n * @returns {string} MathML inside the text node.\n */\n\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getMathmlFromTextNode(textNode, caretPosition) {}\n\n /**\n * Wrapper\n * It fills wrs event object of nonLatex with the desired data.\n * @param {Object} event - event object.\n * @param {Object} window dom window object.\n * @param {string} mathml valid mathml.\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n fillNonLatexNode(event, window, mathml) {}\n\n /**\n Wrapper.\n * Returns selected item from the target.\n * @param {HTMLElement} target - target element\n * @param {boolean} iframe\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getSelectedItem(target, isIframe) {}\n\n // Set temporal image to null and make focus come back.\n static setActionsOnCancelButtons() {\n // Make focus come back on the previous place it was when click cancel button\n const currentInstance = WirisPlugin.currentInstance;\n const editorSelection = currentInstance.getSelection();\n editorSelection.removeAllRanges();\n\n if (currentInstance.core.editionProperties.range) {\n const { range } = currentInstance.core.editionProperties;\n currentInstance.core.editionProperties.range = null;\n editorSelection.addRange(range);\n if (range.startOffset !== range.endOffset) {\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\n }\n }\n\n // eslint-disable-next-line no-undef\n if (WirisPlugin.currentInstance) {\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\n }\n }\n}\n\n// To know if the integration that extends this class implements\n// wrapper methods, they are set as undefined.\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\nIntegrationModel.prototype.fillNonLatexNode = undefined;\nIntegrationModel.prototype.getSelectedItem = undefined;\n\n/**\n * An object containing a list with the overwritable class constructor properties.\n * @type {Object}\n */\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\n","/* eslint-disable */\nvar md5;\nexport default md5;\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n delete Array.prototype.__class__;\n})();\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n})();\ndelete Array.prototype.__class__;\n// @codingStandardsIgnoreEnd\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\n\n/**\n * This class represents the MathType integration for CKEditor5.\n * @extends {IntegrationModel}\n */\nexport default class CKEditor5Integration extends IntegrationModel {\n constructor(ckeditorIntegrationModelProperties) {\n const editor = ckeditorIntegrationModelProperties.editorObject;\n\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\n }\n /**\n * CKEditor5 Integration.\n *\n * @param {integrationModelProperties} integrationModelAttributes\n */\n super(ckeditorIntegrationModelProperties);\n\n /**\n * Folder name used for the integration inside CKEditor plugins folder.\n */\n this.integrationFolderName = \"ckeditor_wiris\";\n }\n\n /**\n * @inheritdoc\n * @returns {string} - The CKEditor instance language.\n * @override\n */\n getLanguage() {\n // Returns the CKEDitor instance language taking into account that the language can be an object.\n // Try to get editorParameters.language, fail silently otherwise\n try {\n return this.editorParameters.language;\n } catch (e) {}\n const languageObject = this.editorObject.config.get(\"language\");\n if (languageObject != null) {\n if (typeof languageObject === \"object\") {\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\n return languageObject.ui;\n }\n return this.editorObject.locale.uiLanguage;\n }\n return languageObject;\n }\n return super.getLanguage();\n }\n\n /**\n * Adds callbacks to the following CKEditor listeners:\n * - 'focus' - updates the current instance.\n * - 'contentDom' - adds 'doubleclick' callback.\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\n * - 'setData' - parses the data converting MathML into images.\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\n * - 'mode' - recalculates the active element.\n */\n addEditorListeners() {\n const editor = this.editorObject;\n\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\n this.checkElement();\n }\n }\n\n /**\n * Checks the current container and assign events in case that it doesn't have them.\n * CKEditor replaces several times the element element during its execution,\n * so we must assign the events again to editor element.\n */\n checkElement() {\n const editor = this.editorObject;\n const newElement = editor.sourceElement;\n\n // If the element wasn't treated, add the events.\n if (!newElement.wirisActive) {\n this.setTarget(newElement);\n this.addEvents();\n // Set the element as treated\n newElement.wirisActive = true;\n }\n }\n\n /**\n * @inheritdoc\n * @param {HTMLElement} element - HTMLElement target.\n * @param {MouseEvent} event - event which trigger the handler.\n */\n doubleClickHandler(element, event) {\n this.core.editionProperties.dbclick = true;\n if (this.editorObject.isReadOnly === false) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\n // doubleclick event ends here.\n if (typeof event.stopPropagation !== \"undefined\") {\n // old I.E compatibility.\n event.stopPropagation();\n } else {\n event.returnValue = false;\n }\n this.core.getCustomEditors().disable();\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\n if (customEditorAttr) {\n this.core.getCustomEditors().enable(customEditorAttr);\n }\n this.core.editionProperties.temporalImage = element;\n this.openExistingFormulaEditor();\n }\n }\n }\n }\n\n /** @inheritdoc */\n static getCorePath() {\n return null; // TODO\n }\n\n /** @inheritdoc */\n callbackFunction() {\n super.callbackFunction();\n this.addEditorListeners();\n }\n\n openNewFormulaEditor() {\n // Store the editor selection as it will be lost upon opening the modal\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\n\n // Focus on the selected editor when multiple editor instances are present\n WirisPlugin.currentInstance = this;\n\n return super.openNewFormulaEditor();\n }\n\n /**\n * Replaces old formula with new MathML or inserts it in caret position if new\n * @param {String} mathml MathML to update old one or insert\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\n */\n insertMathml(mathml) {\n // This returns the value returned by the callback function (writer => {...})\n return this.editorObject.model.change((writer) => {\n const core = this.getCore();\n const selection = this.editorObject.model.document.selection;\n\n const modelElementNew = writer.createElement(\"mathml\", {\n formula: mathml,\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\n });\n\n // Obtain the DOM object corresponding to the formula\n if (core.editionProperties.isNewElement) {\n // Don't bother inserting anything at all if the MathML is empty.\n if (!mathml) return;\n\n const viewSelection =\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\n\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\n\n // Remove selection\n if (!viewSelection.isCollapsed) {\n for (const range of viewSelection.getRanges()) {\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\n }\n }\n\n // Set carret after the formula\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\n writer.setSelection(position);\n } else {\n const img = core.editionProperties.temporalImage;\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\n\n // Insert the new and remove the old one\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\n\n // If the given MathML is empty, don't insert a new formula.\n if (mathml) {\n this.editorObject.model.insertObject(modelElementNew, position);\n }\n writer.remove(modelElementOld);\n }\n\n // eslint-disable-next-line consistent-return\n return modelElementNew;\n });\n }\n\n /**\n * Finds the text node corresponding to given DOM text element.\n * @param {element} viewElement Element to find corresponding text node of.\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\n */\n findText(viewElement) {\n // eslint-disable-line consistent-return\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\n let pivot = viewElement;\n let element;\n while (!element) {\n element = this.editorObject.editing.mapper.toModelElement(\n this.editorObject.editing.view.domConverter.domToView(pivot),\n );\n pivot = pivot.parentElement;\n }\n\n // Navigate through all the subtree under `pivot` in order to find the correct text node\n const range = this.editorObject.model.createRangeIn(element);\n const descendants = Array.from(range.getItems());\n for (const node of descendants) {\n let viewElementData = viewElement.data;\n if (viewElement.nodeType === 3) {\n // Remove invisible white spaces\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\n }\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\n return node.textNode;\n }\n }\n }\n\n /** @inheritdoc */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n // eslint-disable-line no-unused-vars\n const returnObject = {};\n\n let mathmlOrigin;\n if (!mathml) {\n this.insertMathml(\"\");\n } else if (this.core.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n\n this.editorObject.model.change((writer) => {\n const { latexRange } = this.core.editionProperties;\n\n const startNode = this.findText(latexRange.startContainer);\n const endNode = this.findText(latexRange.endContainer);\n\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\n\n let range = writer.createRange(startPosition, endPosition);\n\n // When Latex is next to image/formula.\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\n // Get the position of the latex to be replaced.\n const latexEdited = `$$${Latex.getLatexFromMathML(\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\n )}$$`;\n let data = latexRange.startContainer.data;\n\n // Remove invisible characters.\n data = data.replaceAll(String.fromCharCode(8288), \"\");\n\n // Get to the start of the latex we are editing.\n const offset = data.indexOf(latexEdited);\n const dataOffset = data.substring(offset);\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\n const substring = dataOffset.substr(0, second$);\n data = data.replace(substring, \"\");\n\n if (!data) {\n startPosition = writer.createPositionBefore(startNode);\n range = startNode;\n } else {\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\n range = writer.createRange(startPosition, endPosition);\n }\n }\n\n writer.remove(range);\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\n });\n } else {\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\n try {\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\n windowTarget.document,\n );\n } catch (e) {\n const x = e.toString();\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\n this.core.modalDialog.cancelAction();\n }\n }\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.core.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\n We probably should add it here as well, but we should look further into how */\n // this.editorObject.fire('change');\n\n // Remove temporal image of inserted formula\n this.core.editionProperties.temporalImage = null;\n\n return returnObject;\n }\n\n /**\n * Function called when the content submits an action.\n */\n notifyWindowClosed() {\n this.editorObject.editing.view.focus();\n }\n}\n","/* eslint-disable max-classes-per-file */\nimport { Command } from \"ckeditor5/src/core.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\n/**\n * Command for opening the MathType editor\n */\nexport class MathTypeCommand extends Command {\n execute(options = {}) {\n // Check we get a valid integration\n // eslint-disable-next-line no-prototype-builtins\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\n }\n\n // Save the integration instance as a property of the command.\n this.integration = options.integration;\n\n // Set custom editor or disable it\n this.setEditor();\n\n // Open the editor\n this.openEditor();\n }\n\n /**\n * Sets the appropriate custom editor, if any, or disables them.\n */\n setEditor() {\n // It's possible that a custom editor was last used.\n // We need to disable it to avoid wrong behaviors.\n this.integration.core.getCustomEditors().disable();\n }\n\n /**\n * Checks whether we are editing an existing formula or a new one and opens the editor.\n */\n openEditor() {\n this.integration.core.editionProperties.dbclick = false;\n const image = this._getSelectedImage();\n if (\n typeof image !== \"undefined\" &&\n image !== null &&\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\n ) {\n this.integration.core.editionProperties.temporalImage = image;\n this.integration.openExistingFormulaEditor();\n } else {\n this.integration.openNewFormulaEditor();\n }\n }\n\n /**\n * Gets the currently selected formula image\n * @returns {Element} selected image, if any, undefined otherwise\n */\n _getSelectedImage() {\n const { selection } = this.editor.editing.view.document;\n\n // If we can not extract the formula, fall back to default behavior.\n if (selection.isCollapsed || selection.rangeCount !== 1) {\n return;\n }\n\n // Look for the wrapping the formula and then for the inside\n\n const range = selection.getFirstRange();\n\n let image;\n\n for (const span of range) {\n if (span.item.name !== \"span\") {\n return;\n }\n image = span.item.getChild(0);\n break;\n }\n\n if (!image) {\n return;\n }\n\n // eslint-disable-next-line consistent-return\n return this.editor.editing.view.domConverter.mapViewToDom(image);\n }\n}\n\n/**\n * Command for opening the ChemType editor\n */\nexport class ChemTypeCommand extends MathTypeCommand {\n setEditor() {\n this.integration.core.getCustomEditors().enable(\"chemistry\");\n }\n}\n","// CKEditor imports\nimport { Plugin } from \"ckeditor5/src/core.js\";\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\n\n// MathType API imports\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\n\n// Local imports\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\n\nimport packageInfo from \"../package.json\";\n\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\n\nexport default class MathType extends Plugin {\n static get requires() {\n return [Widget];\n }\n\n static get pluginName() {\n return \"MathType\";\n }\n\n init() {\n // Create the MathType API Integration object\n const integration = this._addIntegration();\n currentInstance = integration;\n\n // Add the MathType and ChemType commands to the editor\n this._addCommands();\n\n // Add the buttons for MathType and ChemType\n this._addViews(integration);\n\n // Registers the element in the schema\n this._addSchema();\n\n // Add the downcast and upcast converters\n this._addConverters(integration);\n\n // Expose the WirisPlugin variable to the window\n this._exposeWiris();\n }\n\n /**\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\n */\n destroy() {\n // eslint-disable-line class-methods-use-this\n currentInstance?.destroy();\n }\n\n /**\n * Create the MathType API Integration object\n * @returns {CKEditor5Integration} the integration object\n */\n _addIntegration() {\n const { editor } = this;\n\n /**\n * Integration model constructor attributes.\n * @type {integrationModelProperties}\n */\n const integrationProperties = {};\n integrationProperties.environment = {};\n integrationProperties.environment.editor = \"CKEditor5\";\n integrationProperties.environment.editorVersion = \"5.x\";\n integrationProperties.version = packageInfo.version;\n integrationProperties.editorObject = editor;\n integrationProperties.serviceProviderProperties = {};\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\n integrationProperties.serviceProviderProperties.server = \"java\";\n integrationProperties.target = editor.sourceElement;\n integrationProperties.scriptName = \"bundle.js\";\n integrationProperties.managesLanguage = true;\n // etc\n\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\n let integration;\n if (integrationProperties.target) {\n // Instance of the integration associated to this editor instance\n integration = new CKEditor5Integration(integrationProperties);\n integration.init();\n integration.listeners.fire(\"onTargetReady\", {});\n\n integration.checkElement();\n\n this.listenTo(\n editor.editing.view.document,\n \"click\",\n (evt, data) => {\n // Is Double-click\n if (data.domEvent.detail === 2) {\n integration.doubleClickHandler(data.domTarget, data.domEvent);\n evt.stop();\n }\n },\n { priority: \"highest\" },\n );\n }\n\n return integration;\n }\n\n /**\n * Add the MathType and ChemType commands to the editor\n */\n _addCommands() {\n const { editor } = this;\n\n // Add command to open the formula editor\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\n\n // Add command to open the chemistry formula editor\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\n }\n\n /**\n * Add the buttons for MathType and ChemType\n * @param {CKEditor5Integration} integration the integration object\n */\n _addViews(integration) {\n const { editor } = this;\n\n // Check if MathType editor is enabled\n if (Configuration.get(\"editorEnabled\")) {\n // Add button for the formula editor\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\n view.set({\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\n icon: mathIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"MathType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Check if ChemType editor is enabled\n if (Configuration.get(\"chemEnabled\")) {\n // Add button for the chemistry formula editor\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\n\n view.set({\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\n icon: chemIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"ChemType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Observer for the Double-click event\n editor.editing.view.addObserver(ClickObserver);\n }\n\n /**\n * Registers the element in the schema\n */\n _addSchema() {\n const { schema } = this.editor.model;\n\n schema.register(\"mathml\", {\n inheritAllFrom: \"$inlineObject\",\n allowAttributes: [\"formula\", \"htmlContent\"],\n });\n }\n\n /**\n * Add the downcast and upcast converters\n */\n _addConverters(integration) {\n const { editor } = this;\n\n // Editing view -> Model\n editor.conversion.for(\"upcast\").elementToElement({\n view: {\n name: \"span\",\n classes: \"ck-math-widget\",\n },\n model: (viewElement, { writer: modelWriter }) => {\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\n return modelWriter.createElement(\"mathml\", {\n formula,\n });\n },\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // When element was already consumed then skip it.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // If we encounter any with a LaTeX annotation inside,\n // convert it into a \"$$...$$\" string.\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\n\n // Get the formula of the (which is all its children).\n const processor = new XmlDataProcessor(editor.editing.view.document);\n\n // Only god knows why the following line makes viewItem lose all of its children,\n // so we obtain isLatex before doing this because we need viewItem's children for that.\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\n\n // and obtain the attributes of too!\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n\n // We process the document fragment\n let formula = processor.toData(viewDocumentFragment) || \"\";\n\n // And obtain the complete formula\n formula = Util.htmlSanitize(`${formula}`);\n\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\n\n /* Model node that contains what's going to actually be inserted. This can be either:\n - A element with a formula attribute set to the given formula, or\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\n const modelNode = isLatex\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\n : writer.createElement(\"mathml\", { formula });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\n \"element:img\",\n (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // Only upcast when is wiris formula\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\n return;\n }\n\n // The following code ensures that the element's name, attributes, and classes are consumed.\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\n\n // Check if we can consume the element name.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // Consume the name, attributes, and classes so nothing else processes it.\n consumable.consume(viewItem, { name: true });\n for (const attrName of viewItem.getAttributes()) {\n consumable.consume(viewItem, { attributes: [attrName] });\n }\n\n for (const className of viewItem.getClassNames()) {\n consumable.consume(viewItem, { classes: [className] });\n }\n\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n const htmlContent = Util.htmlSanitize(``);\n\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n },\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\n { priority: \"high\" },\n );\n\n /**\n * Whether the given view element has a LaTeX annotation element.\n * @param {*} math\n * @returns {bool}\n */\n function mathIsLatex(math) {\n const semantics = math.getChild(0);\n if (!semantics || semantics.name !== \"semantics\") return false;\n for (const child of semantics.getChildren()) {\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\n return true;\n }\n }\n return false;\n }\n\n function createViewWidget(modelItem, { writer: viewWriter }) {\n const widgetElement = viewWriter.createContainerElement(\"span\", {\n class: \"ck-math-widget\",\n });\n\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\n\n if (mathUIElement) {\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\n }\n\n return toWidget(widgetElement, viewWriter);\n }\n\n function createViewImage(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n const formula = modelItem.getAttribute(\"formula\");\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\n\n if (!formula && !htmlContent) {\n return null;\n }\n\n let imgElement = null;\n\n if (htmlContent) {\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\n } else if (formula) {\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\n\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\n\n // Add HTML element () to model\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\n }\n\n /* Although we use the HtmlDataProcessor to obtain the attributes,\n * we must create a new EmptyElement which is independent of the\n * DataProcessor being used by this editor instance\n */\n if (imgElement) {\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\n renderUnsafeAttributes: [\"src\"],\n });\n }\n\n return null;\n }\n\n // Model -> Editing view\n editor.conversion.for(\"editingDowncast\").elementToElement({\n model: \"mathml\",\n view: createViewWidget,\n });\n\n // Model -> Data view\n editor.conversion.for(\"dataDowncast\").elementToElement({\n model: \"mathml\",\n view: createDataString, // eslint-disable-line no-use-before-define\n });\n\n /**\n * Makes a copy of the given view node.\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\n * @returns {module:engine/view/node~Node} Copy of the node.\n */\n function clone(viewWriter, sourceNode) {\n if (sourceNode.is(\"text\")) {\n return viewWriter.createText(sourceNode.data);\n }\n if (sourceNode.is(\"element\")) {\n if (sourceNode.is(\"emptyElement\")) {\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\n }\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\n for (const child of sourceNode.getChildren()) {\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\n }\n return element;\n }\n\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\n }\n\n function createDataString(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n // Load img element\n const mathString =\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\n\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\n\n return clone(viewWriter, sourceMathElement);\n }\n\n // This stops the view selection getting into the s and messing up caret movement\n editor.editing.mapper.on(\n \"viewToModelPosition\",\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\n );\n\n // Keep a reference to the original get and set function.\n const { get, set } = editor.data;\n\n /**\n * Hack to transform $$latex$$ into in editor.getData()'s output.\n */\n editor.data.on(\n \"get\",\n (e) => {\n const output = e.return;\n const parsedResult = Parser.endParse(output);\n\n // Cleans all the semantics tag for safexml\n // including the handwritten data points\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\n },\n { priority: \"low\" },\n );\n\n /**\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\n */\n editor.data.on(\n \"set\",\n (e, args) => {\n // Retrieve the data to be set on the CKEditor.\n let modifiedData = args[0];\n // Regex to find all mathml formulas.\n const regexp = /(]*>)|()/gm;\n const formulas = [];\n let formula;\n\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\n while ((formula = regexp.exec(modifiedData)) !== null) {\n formulas.push(formula[0]);\n }\n\n // Loop to find LaTeX and formula images and replace the MathML for the both.\n formulas.forEach((formula) => {\n if (formula.includes('encoding=\"LaTeX\"')) {\n // LaTeX found.\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\n modifiedData = modifiedData.replace(formula, latex);\n } else if (formula.includes(\"\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var closeIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var closeHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var closeHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var fullsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var fullsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var fullsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var fullsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var minIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var minIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var minHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var minHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var minsIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var minsIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var minsHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var minsHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var maxIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var maxIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; - var maxHoverIcon = "\r\n\r\n \r\n \r\n \r\n image/svg+xml\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n"; + var maxHoverIcon = "\n\n \n \n \n image/svg+xml\n \n \n \n \n \n \n \n\n"; // eslint-disable-next-line max-classes-per-file const { unprotect, protect } = focusProtection(); @@ -9276,7 +9276,7 @@ * @private */ Core._initialized = false; - var warnIcon = "\r\n\r\n\r\n"; + var warnIcon = "\n\n\n"; // eslint-disable-next-line no-unused-vars, import/named /** @@ -11467,11 +11467,11 @@ } } - var mathIcon = "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n"; + var mathIcon = "\n\n\n\n\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n"; - var chemIcon = "\r\n\r\n\r\n\r\n\r\n\r\n"; + var chemIcon = "\n\n\n\n\n\n"; - var version = "8.14.0"; + var version = "8.13.4"; var packageInfo = { version: version}; diff --git a/packages/ckeditor5/dist/browser/index.umd.js.map b/packages/ckeditor5/dist/browser/index.umd.js.map index db6210c14..39d6a768b 100644 --- a/packages/ckeditor5/dist/browser/index.umd.js.map +++ b/packages/ckeditor5/dist/browser/index.umd.js.map @@ -1 +1 @@ -{"version":3,"file":"index.umd.js","sources":["../../../../node_modules/dompurify/dist/purify.es.mjs","../../../devkit/src/constants.js","../../../devkit/src/mathml.js","../../../devkit/src/configuration.js","../../../devkit/src/textcache.js","../../../devkit/src/listeners.js","../../../devkit/src/serviceprovider.js","../../../devkit/src/latex.js","../../../devkit/src/stringmanager.js","../../../devkit/src/util.js","../../../devkit/src/image.js","../../../devkit/src/accessibility.js","../../../devkit/src/parser.js","../../../devkit/src/editorlistener.js","../../../devkit/telemeter-wasm/telemeter_wasm.js","../../../devkit/src/telemeter.js","../../../devkit/src/contentmanager.js","../../../devkit/src/customeditors.js","../../../devkit/src/jsvariables.js","../../../devkit/src/event.js","../../../devkit/src/popupmessage.js","../../../devkit/src/focusprotection.js","../../../devkit/src/modal.js","../../../devkit/src/polyfills.js","../../../devkit/src/core.src.js","../../../devkit/src/integrationmodel.js","../../../devkit/src/md5.js","../../src/integration.js","../../src/commands.js","../../src/plugin.js"],"sourcesContent":["/*! @license DOMPurify 3.3.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.0/LICENSE */\n\nconst {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor\n} = Object;\nlet {\n freeze,\n seal,\n create\n} = Object; // eslint-disable-line import/no-mutable-exports\nlet {\n apply,\n construct\n} = typeof Reflect !== 'undefined' && Reflect;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!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}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\r\n * This class represents all the constants needed in a MathType integration among different classes.\r\n * If a constant should be used across different classes should be defined using attribute\r\n * accessors.\r\n */\r\nexport default class Constants {\r\n /**\r\n * Safe XML entities.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharactersEntities() {\r\n return {\r\n tagOpener: \"«\",\r\n tagCloser: \"»\",\r\n doubleQuote: \"¨\",\r\n realDoubleQuote: \""\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard invalid safe characters.\r\n * @type {Object}\r\n */\r\n static get safeBadBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยป<ยซ/moยป\",\r\n gtElement: \"ยซmoยป>ยซ/moยป\",\r\n ampElement: \"ยซmoยป&ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Blackboard valid safe characters.\r\n * @type{Object}\r\n */\r\n static get safeGoodBlackboardCharacters() {\r\n return {\r\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\r\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\r\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\r\n };\r\n }\r\n\r\n /**\r\n * Standard XML special characters.\r\n * @type {Object}\r\n */\r\n static get xmlCharacters() {\r\n return {\r\n id: \"xmlCharacters\",\r\n tagOpener: \"<\", // Hex: \\x3C.\r\n tagCloser: \">\", // Hex: \\x3E.\r\n doubleQuote: '\"', // Hex: \\x22.\r\n ampersand: \"&\", // Hex: \\x26.\r\n quote: \"'\", // Hex: \\x27.\r\n };\r\n }\r\n\r\n /**\r\n * Safe XML special characters. This characters are used instead the standard\r\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\r\n * special character have a UTF-8 representation.\r\n * @type {Object}\r\n */\r\n static get safeXmlCharacters() {\r\n return {\r\n id: \"safeXmlCharacters\",\r\n tagOpener: \"ยซ\", // Hex: \\xAB.\r\n tagCloser: \"ยป\", // Hex: \\xBB.\r\n doubleQuote: \"ยจ\", // Hex: \\xA8.\r\n ampersand: \"ยง\", // Hex: \\xA7.\r\n quote: \"`\", // Hex: \\x60.\r\n realDoubleQuote: \"ยจ\",\r\n };\r\n }\r\n}\r\n","import Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a class to manage MathML objects.\r\n */\r\nexport default class MathML {\r\n /**\r\n * Checks if the mathml at position i is inside an HTML attribute or not.\r\n * @param {string} content - a string containing MathML code.\r\n * @param {number} i - search index.\r\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\r\n */\r\n static isMathmlInAttribute(content, i) {\r\n // Regex =\r\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\r\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\r\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\r\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\r\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\r\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\r\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\r\n const expression = new RegExp(regex);\r\n\r\n const actualContent = content.substring(0, i);\r\n const reversed = actualContent.split(\"\").reverse().join(\"\");\r\n const exists = expression.test(reversed);\r\n\r\n return exists;\r\n }\r\n\r\n /**\r\n * Decodes an encoded MathML with standard XML tags.\r\n * We use these entities because IE doesn't support html entities\r\n * on its attributes sometimes. Yes, sometimes.\r\n * @param {string} input - string to be decoded.\r\n * @return {string} decoded string.\r\n */\r\n static safeXmlDecode(input) {\r\n let { tagOpener } = Constants.safeXmlCharactersEntities;\r\n let { tagCloser } = Constants.safeXmlCharactersEntities;\r\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\r\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\r\n // Decoding entities.\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n // Added to fix problem due to import from 1.9.x.\r\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\r\n\r\n // Blackboard.\r\n const { ltElement } = Constants.safeBadBlackboardCharacters;\r\n const { gtElement } = Constants.safeBadBlackboardCharacters;\r\n const { ampElement } = Constants.safeBadBlackboardCharacters;\r\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\r\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\r\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\r\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\r\n }\r\n\r\n ({ tagOpener } = Constants.safeXmlCharacters);\r\n ({ tagCloser } = Constants.safeXmlCharacters);\r\n ({ doubleQuote } = Constants.safeXmlCharacters);\r\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\r\n const { ampersand } = Constants.safeXmlCharacters;\r\n const { quote } = Constants.safeXmlCharacters;\r\n\r\n // Decoding characters.\r\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.xmlCharacters.quote);\r\n\r\n // We are replacing $ by & when its part of an entity for retro-compatibility.\r\n // Now, the standard is replace ยง by &.\r\n let returnValue = \"\";\r\n let currentEntity = null;\r\n\r\n for (let i = 0; i < input.length; i += 1) {\r\n const character = input.charAt(i);\r\n if (currentEntity == null) {\r\n if (character === \"$\") {\r\n currentEntity = \"\";\r\n } else {\r\n returnValue += character;\r\n }\r\n } else if (character === \";\") {\r\n returnValue += `&${currentEntity}`;\r\n currentEntity = null;\r\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\r\n // Character is part of an entity.\r\n currentEntity += character;\r\n } else {\r\n returnValue += `$${currentEntity}`; // Is not an entity.\r\n currentEntity = null;\r\n i -= 1; // Parse again the current character.\r\n }\r\n }\r\n\r\n return returnValue;\r\n }\r\n\r\n /**\r\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\r\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\r\n * @param {string} input - input string to be encoded\r\n * @returns {string} encoded string.\r\n */\r\n static safeXmlEncode(input) {\r\n const { tagOpener } = Constants.xmlCharacters;\r\n const { tagCloser } = Constants.xmlCharacters;\r\n const { doubleQuote } = Constants.xmlCharacters;\r\n const { ampersand } = Constants.xmlCharacters;\r\n const { quote } = Constants.xmlCharacters;\r\n\r\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\r\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\r\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\r\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\r\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\r\n\r\n return input;\r\n }\r\n\r\n /**\r\n * Converts special symbols (> 128) to entities and replaces all textual\r\n * entities by its number entities.\r\n * @param {string} mathml - MathML string containing - or not - special symbols\r\n * @returns {string} MathML with all textual entities replaced.\r\n */\r\n static mathMLEntities(mathml) {\r\n let toReturn = \"\";\r\n\r\n for (let i = 0; i < mathml.length; i += 1) {\r\n const character = mathml.charAt(i);\r\n\r\n // Parsing > 128 characters.\r\n if (mathml.codePointAt(i) > 128) {\r\n toReturn += `&#${mathml.codePointAt(i)};`;\r\n // For UTF-32 characters we need to move the index one position.\r\n if (mathml.codePointAt(i) > 0xffff) {\r\n i += 1;\r\n }\r\n } else if (character === \"&\") {\r\n const end = mathml.indexOf(\";\", i + 1);\r\n if (end >= 0) {\r\n const container = document.createElement(\"span\");\r\n container.innerHTML = mathml.substring(i, end + 1);\r\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\r\n i = end;\r\n } else {\r\n toReturn += character;\r\n }\r\n } else {\r\n toReturn += character;\r\n }\r\n }\r\n\r\n return toReturn;\r\n }\r\n\r\n /**\r\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\r\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} MathML string with his class containing the editor toolbar string.\r\n */\r\n static addCustomEditorClassAttribute(mathml, customEditor) {\r\n let toReturn = \"\";\r\n\r\n const start = mathml.indexOf(\"\");\r\n if (mathml.indexOf(\"class\") === -1) {\r\n // Adding custom editor type.\r\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\r\n toReturn += mathml.substr(end + 1, mathml.length);\r\n return toReturn;\r\n }\r\n }\r\n return mathml;\r\n }\r\n\r\n /**\r\n * Remove a custom editor name from the MathML class attribute.\r\n * @param {string} mathml - a MathML string.\r\n * @param {string} customEditor - custom editor name.\r\n * @returns {string} The input MathML without customEditor name in his class.\r\n */\r\n static removeCustomEditorClassAttribute(mathml, customEditor) {\r\n // Discard MathML without the specified class.\r\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\r\n return mathml;\r\n }\r\n\r\n // Trivial case: class attribute value equal to editor name. Then\r\n // class attribute is removed.\r\n // First try to remove it with a space before if there is one\r\n // Otherwise without the space\r\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\r\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\r\n }\r\n\r\n // Non Trivial case: class attribute contains editor name.\r\n return mathml.replace(`wrs_${customEditor}`, \"\");\r\n }\r\n\r\n /**\r\n * Adds annotation tag in MathML element.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} content - value to put inside annotation tag.\r\n * @param {String} annotationEncoding - annotation encoding.\r\n * @returns {String} - 'mathml' with an annotation that contains\r\n * 'content' and encoding 'encoding'.\r\n */\r\n static addAnnotation(mathml, content, annotationEncoding) {\r\n // If contains annotation, also contains semantics tag.\r\n const containsAnnotation = mathml.indexOf(\"\");\r\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\r\n } else if (MathML.isEmpty(mathml)) {\r\n const endIndexInline = mathml.indexOf(\"/>\");\r\n const endIndexNonInline = mathml.indexOf(\">\");\r\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\r\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\r\n } else {\r\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\r\n const endMathmlContent = mathml.lastIndexOf(\"\");\r\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\r\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\r\n }\r\n\r\n return mathmlWithAnnotation;\r\n }\r\n\r\n /**\r\n * Removes specific annotation tag in MathML element.\r\n * In case of remove the unique annotation, also is removed semantics tag.\r\n * @param {String} mathml - valid MathML.\r\n * @param {String} annotationEncoding - annotation encoding to remove.\r\n * @returns {String} - 'mathml' without the annotation encoding specified.\r\n */\r\n static removeAnnotation(mathml, annotationEncoding) {\r\n let mathmlWithoutAnnotation = mathml;\r\n const openAnnotationTag = ``;\r\n const closeAnnotationTag = \"\";\r\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\r\n if (startAnnotationIndex !== -1) {\r\n let differentAnnotationFound = false;\r\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\r\n\r\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Removes semantics tag to element that contains mathml.\r\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\r\n * @param {string} element - Inner HTML text string.\r\n * @returns {string} - 'mathml' without semantics tag.\r\n */\r\n static removeSafeXMLSemantics(element) {\r\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\r\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\r\n\r\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\r\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\r\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\r\n\r\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\r\n }\r\n\r\n /**\r\n * Transforms all xml mathml occurrences that contain semantics to the same\r\n * xml mathml occurrences without semantics.\r\n * @param {string} text - string that can contain xml mathml occurrences.\r\n * @param {Constants} [characters] - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * xmlCharacters by default.\r\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\r\n */\r\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\r\n const mathTagStart = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const mathTagEndline = `/${characters.tagCloser}`;\r\n const { tagCloser } = characters;\r\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\r\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\r\n\r\n let output = \"\";\r\n let start = text.indexOf(mathTagStart);\r\n let end = 0;\r\n while (start !== -1) {\r\n output += text.substring(end, start);\r\n\r\n // MathML can be written as '' or ''.\r\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\r\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\r\n const firstTagCloser = text.indexOf(tagCloser, start);\r\n if (mathTagEndIndex !== -1) {\r\n end = mathTagEndIndex;\r\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\r\n end = mathTagEndlineIndex;\r\n }\r\n\r\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\r\n if (semanticsIndex !== -1) {\r\n const mmlTagStart = text.substring(start, semanticsIndex);\r\n const annotationIndex = text.indexOf(annotationTagStart, start);\r\n if (annotationIndex !== -1) {\r\n const startIndex = semanticsIndex + semanticsTagStart.length;\r\n const mmlContent = text.substring(startIndex, annotationIndex);\r\n output += mmlTagStart + mmlContent + mathTagEnd;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n end += mathTagEnd.length;\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n } else {\r\n end = start;\r\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\r\n }\r\n }\r\n\r\n output += text.substring(end, text.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Returns true if a MathML contains a certain class.\r\n * @param {string} mathML - input MathML.\r\n * @param {string} className - className.\r\n * @returns {boolean} true if the input MathML contains the input class.\r\n * false otherwise.\r\n * @static\r\n */\r\n static containClass(mathML, className) {\r\n const classIndex = mathML.indexOf(\"class\");\r\n if (classIndex === -1) {\r\n return false;\r\n }\r\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\r\n const classTag = mathML.substring(classIndex, classTagEndIndex);\r\n if (classTag.indexOf(className) !== -1) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns true if mathml is empty. Otherwise, false.\r\n * @param {string} mathml - valid MathML with standard XML tags.\r\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\r\n */\r\n static isEmpty(mathml) {\r\n // MathML can have the shape or ''.\r\n const closeTag = \">\";\r\n const closeTagInline = \"/>\";\r\n const firstCloseTagIndex = mathml.indexOf(closeTag);\r\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\r\n let empty = false;\r\n // MathML is always empty in the second shape.\r\n if (firstCloseTagInlineIndex !== -1) {\r\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\r\n empty = true;\r\n }\r\n }\r\n\r\n // MathML is always empty in the first shape when there aren't elements\r\n // between math tags.\r\n if (!empty) {\r\n const mathTagEndRegex = new RegExp(\"\");\r\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\r\n if (mathTagEndArray) {\r\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\r\n }\r\n }\r\n\r\n return empty;\r\n }\r\n\r\n /**\r\n * Encodes html entities inside properties.\r\n * @param {String} mathml - valid MathML with standard XML tags.\r\n * @returns {String} - 'mathml' with property entities encoded.\r\n */\r\n static encodeProperties(mathml) {\r\n // Search all the properties.\r\n const regex = /\\w+=\".*?\"/g;\r\n // Encode html entities.\r\n const replacer = (match) => {\r\n // It has the shape:\r\n // .\r\n const quoteIndex = match.indexOf('\"');\r\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\r\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\r\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\r\n return matchEncoded;\r\n };\r\n\r\n const mathmlEncoded = mathml.replace(regex, replacer);\r\n return mathmlEncoded;\r\n }\r\n}\r\n","/**\r\n * This class represents the configuration class.\r\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\r\n */\r\nexport default class Configuration {\r\n /**\r\n * Adds a properties object to {@link Configuration.properties}.\r\n * @param {Object} properties - properties to append to current properties.\r\n */\r\n static addConfiguration(properties) {\r\n Object.assign(Configuration.properties, properties);\r\n }\r\n\r\n /**\r\n * Static property.\r\n * The configuration properties object.\r\n * @private\r\n * @type {Object}\r\n */\r\n static get properties() {\r\n return Configuration._properties;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set configuration properties.\r\n * @param {Object} value - The property value.\r\n * @ignore\r\n */\r\n static set properties(value) {\r\n Configuration._properties = value;\r\n }\r\n\r\n /**\r\n * Returns the value of a property key.\r\n * @param {String} key - Property key\r\n * @returns {String} Property value\r\n */\r\n static get(key) {\r\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\r\n // Backwards compatibility.\r\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\r\n return Configuration.properties[`_wrs_conf_${key}`];\r\n }\r\n return false;\r\n }\r\n return Configuration.properties[key];\r\n }\r\n\r\n /**\r\n * Adds a new property to Configuration class.\r\n * @param {String} key - Property key.\r\n * @param {Object} value - Property value.\r\n */\r\n static set(key, value) {\r\n Configuration.properties[key] = value;\r\n }\r\n\r\n /**\r\n * Updates a property object value with new values.\r\n * @param {String} key - The property key to be updated.\r\n * @param {Object} propertyValue - Object containing the new values.\r\n */\r\n static update(key, propertyValue) {\r\n if (!Configuration.get(key)) {\r\n Configuration.set(key, propertyValue);\r\n } else {\r\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\r\n Configuration.set(key, updateProperty);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Static properties object. Stores all configuration properties.\r\n * Needed to attribute accessors.\r\n * @private\r\n * @type {Object}\r\n */\r\nConfiguration._properties = {};\r\n","export default class TextCache {\r\n /**\r\n * @classdesc\r\n * This class represent a client-side text cache class. Contains pairs of\r\n * strings (key/value) which can be retrieved in any moment. Usually used\r\n * to store AJAX responses for text services like mathml2latex\r\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Cache array property storing the cache entries.\r\n * @type {Array.}\r\n */\r\n this.cache = [];\r\n }\r\n\r\n /**\r\n * This method populates a key/value pair into the {@link this.cache} property.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @param {String} value - Cache value, usually the service response.\r\n */\r\n populate(key, value) {\r\n this.cache[key] = value;\r\n }\r\n\r\n /**\r\n * Returns the cache value associated to certain cache key.\r\n * @param {String} key - Cache key, usually the service string parameter.\r\n * @return {String} value - Cache value, if exists. False otherwise.\r\n */\r\n get(key) {\r\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\r\n return this.cache[key];\r\n }\r\n return false;\r\n }\r\n}\r\n","/**\r\n * This object represents a custom listener.\r\n * @typedef {Object} Listener\r\n * @property {String} Listener.eventName - The listener name.\r\n * @property {Function} Listener.callback - The listener callback function.\r\n */\r\n\r\nexport default class Listeners {\r\n /**\r\n * @classdesc\r\n * This class represents a custom listeners manager.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Array containing all custom listeners.\r\n * @type {Object[]}\r\n */\r\n this.listeners = [];\r\n }\r\n\r\n /**\r\n * Add a listener to Listener class.\r\n * @param {Object} listener - A listener object.\r\n */\r\n add(listener) {\r\n this.listeners.push(listener);\r\n }\r\n\r\n /**\r\n * Fires MathType event listeners\r\n * @param {String} eventName - event name\r\n * @param {Event} event - event object.\r\n * @return {boolean} false if event has been prevented. true otherwise.\r\n */\r\n fire(eventName, event) {\r\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\r\n if (this.listeners[i].eventName === eventName) {\r\n // Calling listener.\r\n this.listeners[i].callback(event);\r\n }\r\n }\r\n return event.defaultPrevented;\r\n }\r\n\r\n /**\r\n * Creates a new listener object.\r\n * @param {string} eventName - Event name.\r\n * @param {Object} callback - Callback function.\r\n * @returns {object} the listener object.\r\n */\r\n static newListener(eventName, callback) {\r\n const listener = {};\r\n listener.eventName = eventName;\r\n listener.callback = callback;\r\n return listener;\r\n }\r\n}\r\n","import Util from \"./util\";\r\nimport Listeners from \"./listeners\";\r\nimport Configuration from \"./configuration\";\r\n\r\n/**\r\n * @typedef {Object} ServiceProviderProperties\r\n * @property {String} URI - Service URI.\r\n * @property {String} server - Service server language.\r\n */\r\n\r\n/**\r\n * @classdesc\r\n * Class representing a serviceProvider. A serviceProvider is a class containing\r\n * an arbitrary number of services with the correspondent path.\r\n */\r\nexport default class ServiceProvider {\r\n /**\r\n * Returns Service Provider listeners.\r\n * @type {Listeners}\r\n */\r\n static get listeners() {\r\n return ServiceProvider._listeners;\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\r\n * @param {Listener} listener - Instance of {@link Listener}.\r\n */\r\n static addListener(listener) {\r\n ServiceProvider.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Fires events in Service Provider.\r\n * @param {String} eventName - Event name.\r\n * @param {Event} event - Event object.\r\n */\r\n static fireEvent(eventName, event) {\r\n ServiceProvider.listeners.fire(eventName, event);\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @type {ServiceProviderProperties}\r\n *\r\n */\r\n static get parameters() {\r\n return ServiceProvider._parameters;\r\n }\r\n\r\n /**\r\n * Service parameters.\r\n * @private\r\n * @type {ServiceProviderProperties}\r\n */\r\n static set parameters(parameters) {\r\n ServiceProvider._parameters = parameters;\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Return service provider paths.\r\n * @private\r\n * @type {String}\r\n */\r\n static get servicePaths() {\r\n return ServiceProvider._servicePaths;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service paths.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set servicePaths(value) {\r\n ServiceProvider._servicePaths = value;\r\n }\r\n\r\n /**\r\n * Adds a new service to the ServiceProvider.\r\n * @param {String} service - Service name.\r\n * @param {String} path - Service path.\r\n * @static\r\n */\r\n static setServicePath(service, path) {\r\n ServiceProvider.servicePaths[service] = path;\r\n }\r\n\r\n /**\r\n * Returns the service path for a certain service.\r\n * @param {String} serviceName - Service name.\r\n * @returns {String} The service path.\r\n * @static\r\n */\r\n static getServicePath(serviceName) {\r\n return ServiceProvider.servicePaths[serviceName];\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Service provider integration path.\r\n * @type {String}\r\n */\r\n static get integrationPath() {\r\n return ServiceProvider._integrationPath;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set service provider integration path.\r\n * @param {String} value - The property value.\r\n * @ignore\r\n */\r\n static set integrationPath(value) {\r\n ServiceProvider._integrationPath = value;\r\n }\r\n\r\n /**\r\n * Returns the server URL in the form protocol://serverName:serverPort.\r\n * @return {String} The client side server path.\r\n */\r\n static getServerURL() {\r\n const url = window.location.href;\r\n const arr = url.split(\"/\");\r\n const result = `${arr[0]}//${arr[2]}`;\r\n return result;\r\n }\r\n\r\n /**\r\n * Inits {@link this} class. Uses {@link this.integrationPath} as\r\n * base path to generate all backend services paths.\r\n * @param {Object} parameters - Function parameters.\r\n * @param {String} parameters.integrationPath - Service path.\r\n */\r\n static init(parameters) {\r\n ServiceProvider.parameters = parameters;\r\n // Services path (tech dependant).\r\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\r\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\r\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\r\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\r\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\r\n\r\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\r\n // for example: /app/service. For them we calculate the absolute URL path, i.e\r\n // protocol://domain:port/app/service\r\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\r\n const serverPath = ServiceProvider.getServerURL();\r\n configurationURI = serverPath + configurationURI;\r\n showImageURI = serverPath + showImageURI;\r\n createImageURI = serverPath + createImageURI;\r\n getMathMLURI = serverPath + getMathMLURI;\r\n serviceURI = serverPath + serviceURI;\r\n }\r\n\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\r\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\r\n ServiceProvider.setServicePath(\"service\", serviceURI);\r\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\r\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\r\n\r\n ServiceProvider.listeners.fire(\"onInit\", {});\r\n }\r\n\r\n /**\r\n * Gets the content from an URL.\r\n * @param {String} url - Target URL.\r\n * @param {Object} [postVariables] - Object containing post variables.\r\n * null if a GET query should be done.\r\n * @returns {String} Content of the target URL.\r\n * @private\r\n * @static\r\n */\r\n static getUrl(url, postVariables) {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n const httpRequest = Util.createHttpRequest();\r\n\r\n if (httpRequest) {\r\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\r\n httpRequest.open(\"GET\", url, false);\r\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\r\n httpRequest.open(\"POST\", url, false);\r\n } else {\r\n httpRequest.open(\"POST\", currentPath + url, false);\r\n }\r\n\r\n let header = Configuration.get(\"customHeaders\");\r\n if (header) {\r\n if (typeof header === \"string\") {\r\n header = Util.convertStringToObject(header);\r\n }\r\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\r\n }\r\n\r\n if (typeof postVariables !== \"undefined\" && postVariables) {\r\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\r\n httpRequest.send(Util.httpBuildQuery(postVariables));\r\n } else {\r\n httpRequest.send(null);\r\n }\r\n\r\n return httpRequest.responseText;\r\n }\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Returns the response text of a certain service.\r\n * @param {String} service - Service name.\r\n * @param {String} postVariables - Post variables.\r\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\r\n * @returns {String} Service response text.\r\n */\r\n static getService(service, postVariables, get) {\r\n let response;\r\n if (get === true) {\r\n const getVariables = postVariables ? `?${postVariables}` : \"\";\r\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\r\n response = ServiceProvider.getUrl(serviceUrl);\r\n } else {\r\n const serviceUrl = ServiceProvider.getServicePath(service);\r\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\r\n }\r\n return response;\r\n }\r\n\r\n /**\r\n * Returns the server language of a certain service. The possible values\r\n * are: php, aspx, java and ruby.\r\n * This method has backward compatibility purposes.\r\n * @param {String} service - The configuration service.\r\n * @returns {String} - The server technology associated with the configuration service.\r\n */\r\n static getServerLanguageFromService(service) {\r\n if (service.indexOf(\".php\") !== -1) {\r\n return \"php\";\r\n }\r\n if (service.indexOf(\".aspx\") !== -1) {\r\n return \"aspx\";\r\n }\r\n if (service.indexOf(\"wirispluginengine\") !== -1) {\r\n return \"ruby\";\r\n }\r\n return \"java\";\r\n }\r\n\r\n /**\r\n * Returns the URI associated with a certain service.\r\n * @param {String} service - The service name.\r\n * @return {String} The service path.\r\n */\r\n static createServiceURI(service) {\r\n const extension = ServiceProvider.serverExtension();\r\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\r\n }\r\n\r\n static serverExtension() {\r\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\r\n return \".php\";\r\n }\r\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\r\n return \".aspx\";\r\n }\r\n return \"\";\r\n }\r\n}\r\n\r\n/**\r\n * @property {String} service - The service name.\r\n * @property {String} path - The service path.\r\n * @static\r\n */\r\nServiceProvider._servicePaths = {};\r\n\r\n/**\r\n * The integration path. Contains the path of the configuration service.\r\n * Used to define the path for all services.\r\n * @type {String}\r\n * @private\r\n */\r\nServiceProvider._integrationPath = \"\";\r\n\r\n/**\r\n * ServiceProvider static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nServiceProvider._listeners = new Listeners();\r\n\r\n/**\r\n * Service provider parameters.\r\n * @type {ServiceProviderParameters}\r\n */\r\nServiceProvider._parameters = {};\r\n","import TextCache from \"./textcache\";\r\nimport MathML from \"./mathml\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Constants from \"./constants\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents a LaTeX parser. Manages the services which allows to convert\r\n * LaTeX into MathML and MathML into LaTeX.\r\n */\r\nexport default class Latex {\r\n /**\r\n * Static property.\r\n * Return latex cache.\r\n * @private\r\n * @type {Cache}\r\n */\r\n static get cache() {\r\n return Latex._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set latex cache.\r\n * @param {Cache} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Latex._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\r\n * we call a text service with the param mathml2latex.\r\n * @param {String} mathml - MathML String.\r\n * @return {String} LaTeX string generated by the MathML argument.\r\n */\r\n static getLatexFromMathML(mathml) {\r\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\r\n /**\r\n * @type {TextCache}\r\n */\r\n const { cache } = Latex;\r\n\r\n const data = {\r\n service: \"mathml2latex\",\r\n mml: mathmlWithoutSemantics,\r\n };\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n // TODO: Error handling.\r\n let latex = \"\";\r\n\r\n if (jsonResponse.status === \"ok\") {\r\n latex = jsonResponse.result.text;\r\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\r\n // Inserting LaTeX semantics.\r\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\r\n cache.populate(latex, mathmlWithSemantics);\r\n }\r\n\r\n return latex;\r\n }\r\n\r\n /**\r\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\r\n * we call a text service with the param latex2mathml.\r\n * @param {String} latex - String containing a LaTeX formula.\r\n * @param {Boolean} includeLatexOnSemantics\r\n * - If true LaTeX would me included into MathML semantics.\r\n * @return {String} MathML string generated by the LaTeX argument.\r\n */\r\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\r\n /**\r\n * @type {TextCache}\r\n */\r\n const latexCache = Latex.cache;\r\n\r\n if (Latex.cache.get(latex)) {\r\n return Latex.cache.get(latex);\r\n }\r\n const data = {\r\n service: \"latex2mathml\",\r\n latex,\r\n };\r\n\r\n if (includeLatexOnSemantics) {\r\n data.saveLatex = \"\";\r\n }\r\n\r\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n\r\n let output;\r\n if (jsonResponse.status === \"ok\") {\r\n let mathml = jsonResponse.result.text;\r\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\r\n\r\n // Populate LatexCache.\r\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\r\n const content = Util.htmlEntities(latex);\r\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\r\n output = mathml;\r\n } else {\r\n output = mathml;\r\n }\r\n if (!latexCache.get(latex)) {\r\n latexCache.populate(latex, mathml);\r\n }\r\n } else {\r\n output = `$$${latex}$$`;\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML code to LaTeX.\r\n * The MathML code should containing to be converted.\r\n * @param {String} content - A string containing MathML valid code.\r\n * @param {Object} characters - An object containing special characters.\r\n * @return {String} A string containing all MathML annotated occurrences\r\n * replaced by the corresponding LaTeX code.\r\n */\r\n static parseMathmlToLatex(content, characters) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\r\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n let mathml;\r\n let startAnnotation;\r\n let closeAnnotation;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n mathml = content.substring(start, end);\r\n\r\n startAnnotation = mathml.indexOf(openTarget);\r\n if (startAnnotation !== -1) {\r\n startAnnotation += openTarget.length;\r\n closeAnnotation = mathml.indexOf(closeTarget);\r\n let latex = mathml.substring(startAnnotation, closeAnnotation);\r\n if (characters === Constants.safeXmlCharacters) {\r\n latex = MathML.safeXmlDecode(latex);\r\n }\r\n output += `$$${latex}$$`;\r\n // Populate latex into cache.\r\n\r\n Latex.cache.populate(latex, mathml);\r\n } else {\r\n output += mathml;\r\n }\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Extracts the latex of a determined position in a text.\r\n * @param {Node} textNode - textNode to extract the LaTeX\r\n * @param {Number} caretPosition - Starting position to find LaTeX.\r\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\r\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\r\n * \"$$\" by default.\r\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\r\n * @static\r\n */\r\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\r\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\r\n // Tags used for LaTeX formulas.\r\n const defaultLatexTags = {\r\n open: \"$$\",\r\n close: \"$$\",\r\n };\r\n // latexTags is an optional parameter. If is not set, use default latexTags.\r\n if (typeof latexTags === \"undefined\" || latexTags == null) {\r\n latexTags = defaultLatexTags;\r\n }\r\n // Looking for the first textNode.\r\n let startNode = textNode;\r\n\r\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n startNode = startNode.previousSibling;\r\n }\r\n\r\n /**\r\n * Returns the next latex position and node from a specific node and position.\r\n * @param {Node} currentNode - Node where searching latex.\r\n * @param {Number} currentPosition - Current position inside the currentNode.\r\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\r\n * \"$$\" by default.\r\n * @param {Boolean} tag - Tag containing the current search.\r\n * @returns {Object} Object containing the current node and the position.\r\n */\r\n function getNextLatexPosition(currentNode, currentPosition, tag) {\r\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\r\n\r\n while (position === -1) {\r\n currentNode = currentNode.nextSibling;\r\n\r\n if (!currentNode) {\r\n // TEXT_NODE.\r\n return null; // Not found.\r\n }\r\n\r\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\r\n }\r\n\r\n return {\r\n node: currentNode,\r\n position,\r\n };\r\n }\r\n\r\n /**\r\n * Determines if a node is previous, or not, to a second one.\r\n * @param {Node} node - Start node.\r\n * @param {Number} position - Start node position.\r\n * @param {Node} endNode - End node.\r\n * @param {Number} endPosition - End node position.\r\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\r\n */\r\n function isPrevious(node, position, endNode, endPosition) {\r\n if (node === endNode) {\r\n return position <= endPosition;\r\n }\r\n while (node && node !== endNode) {\r\n node = node.nextSibling;\r\n }\r\n\r\n return node === endNode;\r\n }\r\n\r\n let start;\r\n let end = {\r\n node: startNode,\r\n position: 0,\r\n };\r\n // Is supposed that open and close tags has the same length.\r\n const tagLength = latexTags.open.length;\r\n do {\r\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\r\n\r\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\r\n return null;\r\n }\r\n\r\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\r\n\r\n if (end == null) {\r\n return null;\r\n }\r\n\r\n end.position += tagLength;\r\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\r\n\r\n // Isolating latex.\r\n let latex;\r\n\r\n if (start.node === end.node) {\r\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\r\n } else {\r\n const index = start.position + tagLength;\r\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\r\n let currentNode = start.node;\r\n\r\n do {\r\n currentNode = currentNode.nextSibling;\r\n if (currentNode === end.node) {\r\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\r\n } else {\r\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\r\n }\r\n } while (currentNode !== end.node);\r\n }\r\n\r\n return {\r\n latex,\r\n startNode: start.node,\r\n startPosition: start.position,\r\n endNode: end.node,\r\n endPosition: end.position,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\r\n * @type {Cache}\r\n * @static\r\n */\r\nLatex._cache = new TextCache();\r\n","import translations from \"../lang/strings.json\";\r\n/**\r\n * This class represents a string manager. It's used to load localized strings.\r\n */\r\nexport default class StringManager {\r\n constructor() {\r\n throw new Error(\"Static class StringManager can not be instantiated.\");\r\n }\r\n\r\n /**\r\n * Returns the associated value of certain string key. If the associated value\r\n * doesn't exits returns the original key.\r\n * @param {string} key - string key\r\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\r\n * @returns {string} correspondent value. If doesn't exists original key.\r\n */\r\n static get(key, lang) {\r\n // Default language definition\r\n let { language } = this;\r\n\r\n // If parameter language, use it\r\n if (lang) {\r\n language = lang;\r\n }\r\n\r\n // Cut down on strings. e.g. en_US -> en\r\n if (language && language.length > 2) {\r\n language = language.slice(0, 2);\r\n }\r\n\r\n // Check if we support the language\r\n if (!this.strings.hasOwnProperty(language)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown language ${language} set in StringManager.`);\r\n language = \"en\";\r\n }\r\n\r\n // Check if the key is supported in the given language\r\n if (!this.strings[language].hasOwnProperty(key)) {\r\n // eslint-disable-line no-prototype-builtins\r\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\r\n return key;\r\n }\r\n\r\n return this.strings[language][key];\r\n }\r\n}\r\n\r\n/**\r\n * Dictionary of dictionaries:\r\n * Key: language code\r\n * Value: Key: id of the string\r\n * Value: translation of the string\r\n */\r\nStringManager.strings = translations;\r\n\r\n/**\r\n * Language of the translations; English by default\r\n */\r\nStringManager.language = \"en\";\r\n","/* eslint-disable no-bitwise */\r\nimport DOMPurify from \"dompurify\";\r\nimport MathML from \"./mathml\";\r\nimport Configuration from \"./configuration\";\r\nimport Latex from \"./latex\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * This class represents an utility class.\r\n */\r\nexport default class Util {\r\n /**\r\n * Fires an event in a target.\r\n * @param {EventTarget} eventTarget - target where event should be fired.\r\n * @param {string} eventName event to fire.\r\n * @static\r\n */\r\n static fireEvent(eventTarget, eventName) {\r\n if (document.createEvent) {\r\n const eventObject = document.createEvent(\"HTMLEvents\");\r\n eventObject.initEvent(eventName, true, true);\r\n return !eventTarget.dispatchEvent(eventObject);\r\n }\r\n\r\n const eventObject = document.createEventObject();\r\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\r\n }\r\n\r\n /**\r\n * Cross-browser addEventListener/attachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - callback function.\r\n * @static\r\n */\r\n static addEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.addEventListener) {\r\n eventTarget.addEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.attachEvent) {\r\n // Backwards compatibility.\r\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * Cross-browser removeEventListener/detachEvent function.\r\n * @param {EventTarget} eventTarget - target to add the event.\r\n * @param {string} eventName - specifies the type of event.\r\n * @param {Function} callBackFunction - function to remove from the event target.\r\n * @static\r\n */\r\n static removeEvent(eventTarget, eventName, callBackFunction) {\r\n if (eventTarget.removeEventListener) {\r\n eventTarget.removeEventListener(eventName, callBackFunction, true);\r\n } else if (eventTarget.detachEvent) {\r\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\r\n }\r\n }\r\n\r\n /**\r\n * A map from event target to event handlers so we can remove the event\r\n * listeners in removeElementEvents\r\n *\r\n * @type {Map}\r\n * @static\r\n */\r\n static elementEventsMap = new Map();\r\n\r\n /**\r\n * Adds the a callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\r\n * @param {Function} mousedownHandler - function to run when on mousedown event.\r\n * @param {Function} mouseupHandler - function to run when on mouseup event.\r\n * @static\r\n */\r\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\r\n // Make sure not to leak event listeners if we've already added events to\r\n // this element\r\n Util.removeElementEvents(eventTarget);\r\n\r\n let entry = {};\r\n Util.elementEventsMap.set(eventTarget, entry);\r\n\r\n if (doubleClickHandler) {\r\n entry.callbackDblclick = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n doubleClickHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n }\r\n\r\n if (mousedownHandler) {\r\n entry.callbackMousedown = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mousedownHandler(element, realEvent);\r\n };\r\n\r\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n }\r\n\r\n if (mouseupHandler) {\r\n entry.callbackMouseup = (event) => {\r\n const realEvent = event || window.event;\r\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\r\n mouseupHandler(element, realEvent);\r\n };\r\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\r\n // while the mouse is outside the editor text field.\r\n // This is a workaround: we trigger the event independently of where the mouse\r\n // is when we release its button.\r\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n }\r\n\r\n /**\r\n * Remove all callback function, for a certain event target, to the following event types:\r\n * - dblclick\r\n * - mousedown\r\n * - mouseup\r\n * @param {EventTarget} eventTarget - event target.\r\n * @static\r\n */\r\n static removeElementEvents(eventTarget) {\r\n let entry = Util.elementEventsMap.get(eventTarget);\r\n if (!entry) {\r\n return;\r\n }\r\n\r\n Util.elementEventsMap.delete(eventTarget);\r\n\r\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\r\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\r\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\r\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\r\n }\r\n\r\n /**\r\n * Adds a class name to a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static addClass(element, className) {\r\n if (!Util.containsClass(element, className)) {\r\n element.className += ` ${className}`;\r\n }\r\n }\r\n\r\n /**\r\n * Checks if a HTMLElement contains a certain class.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the className.\r\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\r\n * @static\r\n */\r\n static containsClass(element, className) {\r\n if (element == null || !(\"className\" in element)) {\r\n return false;\r\n }\r\n\r\n const currentClasses = element.className.split(\" \");\r\n\r\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\r\n if (currentClasses[i] === className) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Remove a certain class for a HTMLElement.\r\n * @param {HTMLElement} element - the HTML element.\r\n * @param {string} className - the class name.\r\n * @static\r\n */\r\n static removeClass(element, className) {\r\n let newClassName = \"\";\r\n const classes = element.className.split(\" \");\r\n\r\n for (let i = 0; i < classes.length; i += 1) {\r\n if (classes[i] !== className) {\r\n newClassName += `${classes[i]} `;\r\n }\r\n }\r\n element.className = newClassName.trim();\r\n }\r\n\r\n /**\r\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\r\n * used to parse old applets.\r\n * @param {string} text - string containing safeXml characters\r\n * @returns {string} a string with safeXml characters parsed.\r\n * @static\r\n */\r\n static convertOldXmlinitialtextAttribute(text) {\r\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\r\n // This could be removed in future.\r\n const val = \"value=\";\r\n\r\n const xitpos = text.indexOf(\"xmlinitialtext\");\r\n const valpos = text.indexOf(val, xitpos);\r\n const quote = text.charAt(valpos + val.length);\r\n const startquote = valpos + val.length + 1;\r\n const endquote = text.indexOf(quote, startquote);\r\n\r\n const value = text.substring(startquote, endquote);\r\n\r\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\r\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\r\n newvalue = newvalue.split(\"&\").join(\"ยง\");\r\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\r\n\r\n text = text.split(value).join(newvalue);\r\n return text;\r\n }\r\n\r\n /**\r\n * Convert a string representation of key-value pairs to an object.\r\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\r\n * @returns {Object} - Object containing the key-value pairs\r\n */\r\n static convertStringToObject(keyValueString) {\r\n if (!keyValueString || typeof keyValueString !== \"string\") {\r\n return {};\r\n }\r\n\r\n return keyValueString\r\n .split(\",\")\r\n .map((pair) => pair.trim().split(\"=\"))\r\n .reduce((resultObject, [key, value]) => {\r\n if (key && value) {\r\n resultObject[key] = value;\r\n }\r\n return resultObject;\r\n }, {});\r\n }\r\n\r\n /**\r\n * Cross-browser solution for creating new elements.\r\n * @param {string} tagName - tag name of the wished element.\r\n * @param {Object} attributes - an object where each key is a wished\r\n * attribute name and each value is its value.\r\n * @param {Object} [creator] - if supplied, this function will use\r\n * the \"createElement\" method from this param. Otherwise\r\n * document will be used as creator.\r\n * @returns {Element} The DOM element with the specified attributes assigned.\r\n * @static\r\n */\r\n static createElement(tagName, attributes, creator) {\r\n if (attributes === undefined) {\r\n attributes = {};\r\n }\r\n\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n let element;\r\n\r\n /*\r\n * Internet Explorer fix:\r\n * If you create a new object dynamically, you can't set a non-standard attribute.\r\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\r\n * Other browsers will throw an exception and will run the standard code.\r\n */\r\n try {\r\n let html = `<${tagName}`;\r\n\r\n Object.keys(attributes).forEach((attributeName) => {\r\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\r\n });\r\n html += \">\";\r\n element = creator.createElement(html);\r\n } catch (e) {\r\n element = creator.createElement(tagName);\r\n Object.keys(attributes).forEach((attributeName) => {\r\n element.setAttribute(attributeName, attributes[attributeName]);\r\n });\r\n }\r\n return element;\r\n }\r\n\r\n /**\r\n * Creates new HTML from it's HTML code as string.\r\n * @param {string} objectCode - html code\r\n * @returns {Element} the HTML element.\r\n * @static\r\n */\r\n static createObject(objectCode, creator) {\r\n if (creator === undefined) {\r\n creator = document;\r\n }\r\n\r\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\r\n objectCode = objectCode\r\n .split(\"\").join(\"\").split(\"\").join(\"\");\r\n\r\n objectCode = objectCode\r\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\r\n\r\n const container = Util.createElement(\"div\", {}, creator);\r\n container.innerHTML = objectCode;\r\n\r\n function recursiveParamsFix(object) {\r\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const param = Util.createElement(\"param\", attributesParsed, creator);\r\n\r\n // IE fix.\r\n if (param.NAME) {\r\n param.name = param.NAME;\r\n param.value = param.VALUE;\r\n }\r\n\r\n param.removeAttribute(\"wirisObject\");\r\n object.parentNode.replaceChild(param, object);\r\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\r\n const attributesParsed = {};\r\n\r\n for (let i = 0; i < object.attributes.length; i += 1) {\r\n if (object.attributes[i].nodeValue !== null) {\r\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\r\n }\r\n }\r\n\r\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\r\n applet.removeAttribute(\"wirisObject\");\r\n\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n\r\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\r\n applet.appendChild(object.childNodes[i]);\r\n i -= 1; // When we insert the object child into the applet, object loses one child.\r\n }\r\n }\r\n\r\n object.parentNode.replaceChild(applet, object);\r\n } else {\r\n for (let i = 0; i < object.childNodes.length; i += 1) {\r\n recursiveParamsFix(object.childNodes[i]);\r\n }\r\n }\r\n }\r\n\r\n recursiveParamsFix(container);\r\n return container.firstChild;\r\n }\r\n\r\n /**\r\n * Converts an Element to its HTML code.\r\n * @param {Element} element - entry element.\r\n * @return {string} the HTML code of the input element.\r\n * @static\r\n */\r\n static createObjectCode(element) {\r\n // In case that the image was not created, the object can be null or undefined.\r\n if (typeof element === \"undefined\" || element === null) {\r\n return null;\r\n }\r\n\r\n if (element.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let output = `<${element.tagName}`;\r\n\r\n for (let i = 0; i < element.attributes.length; i += 1) {\r\n if (element.attributes[i].specified) {\r\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\r\n }\r\n }\r\n\r\n if (element.childNodes.length > 0) {\r\n output += \">\";\r\n\r\n for (let i = 0; i < element.childNodes.length; i += 1) {\r\n output += Util.createObject(element.childNodes[i]);\r\n }\r\n\r\n output += ``;\r\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\r\n output += `>`;\r\n } else {\r\n output += \"/>\";\r\n }\r\n\r\n return output;\r\n }\r\n\r\n if (element.nodeType === 3) {\r\n // TEXT_NODE.\r\n return Util.htmlEntities(element.nodeValue);\r\n }\r\n\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Concatenates two URL paths.\r\n * @param {string} path1 - first URL path\r\n * @param {string} path2 - second URL path\r\n * @returns {string} new URL.\r\n */\r\n static concatenateUrl(path1, path2) {\r\n let separator = \"\";\r\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\r\n separator = \"/\";\r\n }\r\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all HTML special characters by their correspondent entities.\r\n * @param {string} input - text to be parsed.\r\n * @returns {string} the input text with all their special characters replaced by their entities.\r\n * @static\r\n */\r\n static htmlEntities(input) {\r\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\r\n }\r\n\r\n /**\r\n * Sanitize HTML to prevent XSS injections.\r\n * @param {string} html - html to be sanitize.\r\n * @returns {string} html sanitized.\r\n * @static\r\n */\r\n static htmlSanitize(html) {\r\n const annotationRegex = /\\/;\r\n // Get all the annotation content including the tags.\r\n const annotation = html.match(annotationRegex);\r\n // Sanitize html code without removing our supported MathML tags and attributes.\r\n html = DOMPurify.sanitize(html, {\r\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\r\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\r\n });\r\n // Readd old annotation content.\r\n return html.replace(annotationRegex, annotation);\r\n }\r\n\r\n /**\r\n * Parses a text and replaces all the HTML entities by their characters.\r\n * @param {string} input - text to be parsed\r\n * @returns {string} the input text with all their entities replaced by characters.\r\n * @static\r\n */\r\n static htmlEntitiesDecode(input) {\r\n // Textarea element decodes when inner html is set.\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.innerHTML = input;\r\n return textarea.value;\r\n }\r\n\r\n /**\r\n * Returns a cross-browser http request.\r\n * @return {object} httpRequest request object.\r\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\r\n */\r\n static createHttpRequest() {\r\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\r\n if (currentPath.substr(0, 7) === \"file://\") {\r\n throw StringManager.get(\"exception_cross_site\");\r\n }\r\n\r\n if (typeof XMLHttpRequest !== \"undefined\") {\r\n return new XMLHttpRequest();\r\n }\r\n\r\n try {\r\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\r\n } catch (e) {\r\n try {\r\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\r\n } catch (oc) {\r\n return null;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Converts a hash to a HTTP query.\r\n * @param {Object[]} properties - a key/value object.\r\n * @returns {string} a HTTP query containing all the key value pairs with\r\n * all the special characters replaced by their entities.\r\n * @static\r\n */\r\n static httpBuildQuery(properties) {\r\n let result = \"\";\r\n\r\n Object.keys(properties).forEach((i) => {\r\n if (properties[i] != null) {\r\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\r\n }\r\n });\r\n\r\n // Deleting last '&' empty character.\r\n if (result.substring(result.length - 1) === \"&\") {\r\n result = result.substring(0, result.length - 1);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Convert a hash to string sorting keys to get a deterministic output\r\n * @param {Object[]} hash - a key/value object.\r\n * @returns {string} a string with the form key1=value1...keyn=valuen\r\n * @static\r\n */\r\n static propertiesToString(hash) {\r\n // 1. Sort keys. We sort the keys because we want a deterministic output.\r\n const keys = [];\r\n Object.keys(hash).forEach((key) => {\r\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\r\n keys.push(key);\r\n }\r\n });\r\n\r\n const n = keys.length;\r\n for (let i = 0; i < n; i += 1) {\r\n for (let j = i + 1; j < n; j += 1) {\r\n const s1 = keys[i];\r\n const s2 = keys[j];\r\n if (Util.compareStrings(s1, s2) > 0) {\r\n // Swap.\r\n keys[i] = s2;\r\n keys[j] = s1;\r\n }\r\n }\r\n }\r\n\r\n // 2. Generate output.\r\n let output = \"\";\r\n for (let i = 0; i < n; i += 1) {\r\n const key = keys[i];\r\n output += key;\r\n output += \"=\";\r\n let value = hash[key];\r\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\r\n value = value.replace(\"\\n\", \"\\\\n\");\r\n value = value.replace(\"\\r\", \"\\\\r\");\r\n value = value.replace(\"\\t\", \"\\\\t\");\r\n\r\n output += value;\r\n output += \"\\n\";\r\n }\r\n return output;\r\n }\r\n\r\n /**\r\n * Compare two strings using charCodeAt method\r\n * @param {string} a - first string to compare.\r\n * @param {string} b - second string to compare.\r\n * @returns {number} the difference between a and b\r\n * @static\r\n */\r\n static compareStrings(a, b) {\r\n let i;\r\n const an = a.length;\r\n const bn = b.length;\r\n const n = an > bn ? bn : an;\r\n for (i = 0; i < n; i += 1) {\r\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\r\n if (c !== 0) {\r\n return c;\r\n }\r\n }\r\n return a.length - b.length;\r\n }\r\n\r\n /**\r\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\r\n * @param {string} string - input string\r\n * @param {number} idx - an integer greater than or equal to 0\r\n * and less than the length of the string\r\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\r\n * @static\r\n */\r\n static fixedCharCodeAt(string, idx) {\r\n idx = idx || 0;\r\n const code = string.charCodeAt(idx);\r\n let hi;\r\n let low;\r\n\r\n /* High surrogate (could change last hex to 0xDB7F to treat high\r\n private surrogates as single characters) */\r\n\r\n if (code >= 0xd800 && code <= 0xdbff) {\r\n hi = code;\r\n low = string.charCodeAt(idx + 1);\r\n if (Number.isNaN(low)) {\r\n throw StringManager.get(\"exception_high_surrogate\");\r\n }\r\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\r\n }\r\n\r\n if (code >= 0xdc00 && code <= 0xdfff) {\r\n // Low surrogate.\r\n /* We return false to allow loops to skip this iteration since should have\r\n already handled high surrogate above in the previous iteration. */\r\n return false;\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Returns an URL with it's query params converted into array.\r\n * @param {string} url - input URL.\r\n * @returns {Object[]} an array containing all URL query params.\r\n * @static\r\n */\r\n static urlToAssArray(url) {\r\n let i;\r\n i = url.indexOf(\"?\");\r\n if (i > 0) {\r\n const query = url.substring(i + 1);\r\n const ss = query.split(\"&\");\r\n const h = {};\r\n for (i = 0; i < ss.length; i += 1) {\r\n const s = ss[i];\r\n const kv = s.split(\"=\");\r\n if (kv.length > 1) {\r\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\r\n }\r\n }\r\n return h;\r\n }\r\n return {};\r\n }\r\n\r\n /**\r\n * Returns an encoded URL by replacing each instance of certain characters by\r\n * one, two, three or four escape sequences using encodeURIComponent method.\r\n * !'()* . will not be encoded.\r\n *\r\n * @param {string} clearString - URL string to be encoded\r\n * @returns {string} URL with it's special characters replaced.\r\n * @static\r\n */\r\n static urlEncode(clearString) {\r\n let output = \"\";\r\n // Method encodeURIComponent doesn't encode !'()*~ .\r\n output = encodeURIComponent(clearString);\r\n return output;\r\n }\r\n\r\n // TODO: To parser?\r\n /**\r\n * Converts the HTML of a image into the output code that WIRIS must return.\r\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\r\n * or the Wiriscas attribute of a WIRIS applet.\r\n * @param {string} imgCode - the html code from a formula or a CAS image.\r\n * @param {boolean} convertToXml - true if the image should be converted to XML.\r\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\r\n * @returns {string} the XML or safeXML of a WIRIS image.\r\n * @static\r\n */\r\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\r\n const imgObject = Util.createObject(imgCode);\r\n if (imgObject) {\r\n if (\r\n imgObject.className === Configuration.get(\"imageClassName\") ||\r\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\r\n ) {\r\n if (!convertToXml) {\r\n return imgCode;\r\n }\r\n\r\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n // To handle annotations, first we need the MathML in XML.\r\n let mathML = MathML.safeXmlDecode(dataMathML);\r\n\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\r\n }\r\n\r\n if (mathML == null) {\r\n mathML = imgObject.getAttribute(\"alt\");\r\n }\r\n\r\n if (convertToSafeXml) {\r\n const safeMathML = MathML.safeXmlEncode(mathML);\r\n return safeMathML;\r\n }\r\n\r\n return mathML;\r\n }\r\n }\r\n return imgCode;\r\n }\r\n\r\n /**\r\n * Gets the node length in characters.\r\n * @param {Node} node - HTML node.\r\n * @returns {number} node length.\r\n * @static\r\n */\r\n static getNodeLength(node) {\r\n const staticNodeLengths = {\r\n IMG: 1,\r\n BR: 1,\r\n };\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return node.nodeValue.length;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\r\n\r\n if (length === undefined) {\r\n length = 0;\r\n }\r\n\r\n for (let i = 0; i < node.childNodes.length; i += 1) {\r\n length += Util.getNodeLength(node.childNodes[i]);\r\n }\r\n return length;\r\n }\r\n return 0;\r\n }\r\n\r\n /**\r\n * Gets a selected node or text from an editable HTMLElement.\r\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\r\n * @param {HTMLElement} target - the editable HTMLElement.\r\n * @param {boolean} isIframe - specifies if the target is an iframe or not\r\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\r\n * the current selection and uses window.getSelection()\r\n * @returns {object} an object with the 'node' key set if the item is an\r\n * element or the keys 'node' and 'caretPosition' if the element is text.\r\n * @static\r\n */\r\n static getSelectedItem(target, isIframe, forceGetSelection) {\r\n let windowTarget;\r\n\r\n if (isIframe) {\r\n windowTarget = target.contentWindow;\r\n windowTarget.focus();\r\n } else {\r\n windowTarget = window;\r\n target.focus();\r\n }\r\n\r\n if (document.selection && !forceGetSelection) {\r\n const range = windowTarget.document.selection.createRange();\r\n\r\n if (range.parentElement) {\r\n if (range.htmlText.length > 0) {\r\n if (range.text.length === 0) {\r\n return Util.getSelectedItem(target, isIframe, true);\r\n }\r\n\r\n return null;\r\n }\r\n\r\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\r\n let temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\r\n // IE9 fix: parentElement() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML('');\r\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\r\n }\r\n\r\n let node;\r\n let caretPosition;\r\n\r\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = temporalObject.nextSibling;\r\n caretPosition = 0;\r\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\r\n node = temporalObject.previousSibling;\r\n caretPosition = node.nodeValue.length;\r\n } else {\r\n node = windowTarget.document.createTextNode(\"\");\r\n temporalObject.parentNode.insertBefore(node, temporalObject);\r\n caretPosition = 0;\r\n }\r\n\r\n temporalObject.parentNode.removeChild(temporalObject);\r\n\r\n return {\r\n node,\r\n caretPosition,\r\n };\r\n }\r\n\r\n if (range.length > 1) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: range.item(0),\r\n };\r\n }\r\n\r\n if (windowTarget.getSelection) {\r\n let range;\r\n const selection = windowTarget.getSelection();\r\n\r\n try {\r\n range = selection.getRangeAt(0);\r\n } catch (e) {\r\n range = windowTarget.document.createRange();\r\n }\r\n\r\n const node = range.startContainer;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n return {\r\n node,\r\n caretPosition: range.startOffset,\r\n };\r\n }\r\n\r\n if (node !== range.endContainer) {\r\n return null;\r\n }\r\n\r\n if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n const position = range.startOffset;\r\n\r\n if (node.childNodes[position]) {\r\n // In case that a formula is detected but not selected,\r\n // we create an empty span where we could insert the new formula.\r\n if (range.startOffset === range.endOffset) {\r\n if (\r\n position !== 0 &&\r\n node.childNodes[position - 1].localName === \"span\" &&\r\n node.childNodes[position].classList?.contains(\"Wirisformula\")\r\n ) {\r\n node.childNodes[position - 1].remove();\r\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\r\n }\r\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\r\n if (\r\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\r\n position === 0\r\n ) {\r\n const emptySpan = document.createElement(\"span\");\r\n node.insertBefore(emptySpan, node.childNodes[position]);\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n return {\r\n node: node.childNodes[position],\r\n };\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Returns null if there isn't any item or if it is malformed.\r\n * Otherwise returns an object containing the node with the MathML image\r\n * and the cursor position inside the textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea element.\r\n * @returns {Object} An object containing the node, the index of the\r\n * beginning of the selected text, caret position and the start and end position of the\r\n * text node.\r\n * @static\r\n */\r\n static getSelectedItemOnTextarea(textarea) {\r\n const textNode = document.createTextNode(textarea.value);\r\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\r\n if (textNodeValues === null) {\r\n return null;\r\n }\r\n\r\n return {\r\n node: textNode,\r\n caretPosition: textarea.selectionStart,\r\n startPosition: textNodeValues.startPosition,\r\n endPosition: textNodeValues.endPosition,\r\n };\r\n }\r\n\r\n /**\r\n * Looks for elements that match the given name in a HTML code string.\r\n * Important: this function is very concrete for WIRIS code.\r\n * It takes as preconditions lots of behaviors that are not the general case.\r\n * @param {string} code - HTML code.\r\n * @param {string} name - element name.\r\n * @param {boolean} autoClosed - true if the elements are autoClosed.\r\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\r\n * @static\r\n */\r\n static getElementsByNameFromString(code, name, autoClosed) {\r\n const elements = [];\r\n code = code.toLowerCase();\r\n name = name.toLowerCase();\r\n let start = code.indexOf(`<${name} `);\r\n\r\n while (start !== -1) {\r\n // Look for nodes.\r\n let endString;\r\n\r\n if (autoClosed) {\r\n endString = \">\";\r\n } else {\r\n endString = ``;\r\n }\r\n\r\n let end = code.indexOf(endString, start);\r\n\r\n if (end !== -1) {\r\n end += endString.length;\r\n elements.push({\r\n start,\r\n end,\r\n });\r\n } else {\r\n end = start + 1;\r\n }\r\n\r\n start = code.indexOf(`<${name} `, end);\r\n }\r\n\r\n return elements;\r\n }\r\n\r\n /**\r\n * Returns the numeric value of a base64 character.\r\n * @param {string} character - base64 character.\r\n * @returns {number} base64 character numeric value.\r\n * @static\r\n */\r\n static decode64(character) {\r\n const PLUS = \"+\".charCodeAt(0);\r\n const SLASH = \"/\".charCodeAt(0);\r\n const NUMBER = \"0\".charCodeAt(0);\r\n const LOWER = \"a\".charCodeAt(0);\r\n const UPPER = \"A\".charCodeAt(0);\r\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\r\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\r\n const code = character.charCodeAt(0);\r\n\r\n if (code === PLUS || code === PLUS_URL_SAFE) {\r\n return 62; // Char '+'.\r\n }\r\n if (code === SLASH || code === SLASH_URL_SAFE) {\r\n return 63; // Char '/'.\r\n }\r\n if (code < NUMBER) {\r\n return -1; // No match.\r\n }\r\n if (code < NUMBER + 10) {\r\n return code - NUMBER + 26 + 26;\r\n }\r\n if (code < UPPER + 26) {\r\n return code - UPPER;\r\n }\r\n if (code < LOWER + 26) {\r\n return code - LOWER + 26;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Converts a base64 string to a array of bytes.\r\n * @param {string} b64String - base64 string.\r\n * @param {number} length - dimension of byte array (by default whole string).\r\n * @return {Object[]} the resultant byte array.\r\n * @static\r\n */\r\n static b64ToByteArray(b64String, length) {\r\n let tmp;\r\n\r\n if (b64String.length % 4 > 0) {\r\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\r\n }\r\n\r\n const arr = [];\r\n\r\n let l;\r\n let placeHolders;\r\n if (!length) {\r\n // All b64String string.\r\n if (b64String.charAt(b64String.length - 2) === \"=\") {\r\n placeHolders = 2;\r\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\r\n placeHolders = 1;\r\n } else {\r\n placeHolders = 0;\r\n }\r\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\r\n } else {\r\n l = length;\r\n }\r\n\r\n let i;\r\n for (i = 0; i < l; i += 4) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 18) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\r\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\r\n Util.decode64(b64String.charAt(i + 3));\r\n\r\n arr.push((tmp >> 16) & 0xff);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n if (placeHolders) {\r\n if (placeHolders === 2) {\r\n // Ignoring code checker standards (bitewise operators).\r\n // @codingStandardsIgnoreStart\r\n // eslint-disable-next-line max-len\r\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\r\n arr.push(tmp & 0xff);\r\n } else if (placeHolders === 1) {\r\n // eslint-disable-next-line max-len\r\n tmp =\r\n (Util.decode64(b64String.charAt(i)) << 10) |\r\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\r\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\r\n arr.push((tmp >> 8) & 0xff);\r\n arr.push(tmp & 0xff);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n }\r\n return arr;\r\n }\r\n\r\n /**\r\n * Returns the first 32-bit signed integer from a byte array.\r\n * @param {Object[]} bytes - array of bytes.\r\n * @returns {number} the 32-bit signed integer.\r\n * @static\r\n */\r\n static readInt32(bytes) {\r\n if (bytes.length < 4) {\r\n return false;\r\n }\r\n const int32 = bytes.splice(0, 4);\r\n // @codingStandardsIgnoreStartยก\r\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read the first byte from a byte array.\r\n * @param {Object} bytes - input byte array.\r\n * @returns {number} first byte of the byte array.\r\n * @static\r\n */\r\n static readByte(bytes) {\r\n // @codingStandardsIgnoreStart\r\n return bytes.shift() << 0;\r\n // @codingStandardsIgnoreEnd\r\n }\r\n\r\n /**\r\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\r\n * @param {Object[]} bytes - byte array.\r\n * @param {number} pos - start position.\r\n * @param {number} len - number of bytes to read.\r\n * @returns {Object[]} the byte array.\r\n * @static\r\n */\r\n static readBytes(bytes, pos, len) {\r\n return bytes.splice(pos, len);\r\n }\r\n\r\n /**\r\n * Inserts or modifies formulas or CAS on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - textarea target.\r\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\r\n * call this function as (textarea, Parser.createImageSrc(mathml));\r\n * @static\r\n */\r\n static updateTextArea(textarea, text) {\r\n if (textarea && text) {\r\n textarea.focus();\r\n\r\n if (textarea.selectionStart != null) {\r\n const { selectionEnd } = textarea;\r\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\r\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\r\n textarea.value = selectionStart + text + selectionEndSub;\r\n textarea.selectionEnd = selectionEnd + text.length;\r\n } else {\r\n const selection = document.selection.createRange();\r\n selection.text = text;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Modifies existing formula on a textarea.\r\n * @param {HTMLTextAreaElement} textarea - text area target.\r\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\r\n * to the image,you can call this function as\r\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\r\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\r\n * @param {number} end - ending index from textarea where it needs to be replaced by text\r\n * @static\r\n */\r\n static updateExistingTextOnTextarea(textarea, text, start, end) {\r\n textarea.focus();\r\n const textareaStart = textarea.value.substring(0, start);\r\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\r\n textarea.selectionEnd = start + text.length;\r\n }\r\n\r\n /**\r\n * Add a parameter with it's correspondent value to an URL (GET).\r\n * @param {string} path - URL path\r\n * @param {string} parameter - parameter\r\n * @param {string} value - value\r\n * @static\r\n */\r\n static addArgument(path, parameter, value) {\r\n let sep;\r\n if (path.indexOf(\"?\") > 0) {\r\n sep = \"&\";\r\n } else {\r\n sep = \"?\";\r\n }\r\n return `${path + sep + parameter}=${value}`;\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Util from \"./util\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType Image class. Contains all the logic related\r\n * to MathType images manipulation.\r\n * All MathType images are generated using the appropriate MathType\r\n * integration service: showimage or createimage.\r\n *\r\n * There are two available image formats:\r\n * - svg (default)\r\n * - png\r\n *\r\n * There are two formats for the image src attribute:\r\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\r\n * - A link to the showimage service.\r\n */\r\nexport default class Image {\r\n /**\r\n * Removes data attributes from an image.\r\n * @param {HTMLImageElement} img - Image where remove data attributes.\r\n */\r\n static removeImgDataAttributes(img) {\r\n const attributesToRemove = [];\r\n const { attributes } = img;\r\n\r\n Object.keys(attributes).forEach((key) => {\r\n const attribute = attributes[key];\r\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\r\n // Is preferred keep an array and remove after the search\r\n // because when attribute is removed the array of attributes\r\n // is modified.\r\n attributesToRemove.push(attribute.name);\r\n }\r\n });\r\n\r\n attributesToRemove.forEach((attribute) => {\r\n img.removeAttribute(attribute);\r\n });\r\n }\r\n\r\n /**\r\n * @static\r\n * Clones all MathType image attributes from a HTMLImageElement to another.\r\n * @param {HTMLImageElement} originImg - The original image.\r\n * @param {HTMLImageElement} destImg - The destination image.\r\n */\r\n static clone(originImg, destImg) {\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (!originImg.hasAttribute(customEditorAttributeName)) {\r\n destImg.removeAttribute(customEditorAttributeName);\r\n }\r\n\r\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\r\n const imgAttributes = [\r\n mathmlAttributeName,\r\n customEditorAttributeName,\r\n \"alt\",\r\n \"height\",\r\n \"width\",\r\n \"style\",\r\n \"src\",\r\n \"role\",\r\n ];\r\n\r\n imgAttributes.forEach((iterator) => {\r\n const originAttribute = originImg.getAttribute(iterator);\r\n if (originAttribute) {\r\n destImg.setAttribute(iterator, originAttribute);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Determines whether an img src contains an SVG.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src contains an SVG, false otherwise\r\n */\r\n static isSvg(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;\");\r\n }\r\n\r\n /**\r\n * Determines whether an img src is encoded in base64 or not.\r\n * @param {HTMLImageElement} img the img element to inspect\r\n * @returns true if the img src is encoded in base64, false otherwise\r\n */\r\n static isBase64(img) {\r\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\r\n }\r\n\r\n /**\r\n * Calculates the metrics of a MathType image given the the service response and the image format.\r\n * @param {HTMLImageElement} img - The HTMLImageElement.\r\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\r\n * @param {Boolean} jsonResponse - True the response of the image service is a\r\n * JSON object. False otherwise.\r\n */\r\n static setImgSize(img, uri, jsonResponse) {\r\n let ar;\r\n let base64String;\r\n let bytes;\r\n let svgString;\r\n if (jsonResponse) {\r\n // Cleaning data:image/png;base64.\r\n if (Image.isSvg(img)) {\r\n // SVG format.\r\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\r\n if (!Image.isBase64(img)) {\r\n ar = Image.getMetricsFromSvgString(uri);\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n svgString = \"\";\r\n bytes = Util.b64ToByteArray(base64String, base64String.length);\r\n for (let i = 0; i < bytes.length; i += 1) {\r\n svgString += String.fromCharCode(bytes[i]);\r\n }\r\n ar = Image.getMetricsFromSvgString(svgString);\r\n }\r\n // PNG format: we store all metrics information in the first 88 bytes.\r\n } else {\r\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\r\n bytes = Util.b64ToByteArray(base64String, 88);\r\n ar = Image.getMetricsFromBytes(bytes);\r\n }\r\n // Backwards compatibility: we store the metrics into createimage response.\r\n } else {\r\n ar = Util.urlToAssArray(uri);\r\n }\r\n let width = ar.cw;\r\n if (!width) {\r\n return;\r\n }\r\n let height = ar.ch;\r\n let baseline = ar.cb;\r\n const { dpi } = ar;\r\n if (dpi) {\r\n width = (width * 96) / dpi;\r\n height = (height * 96) / dpi;\r\n baseline = (baseline * 96) / dpi;\r\n }\r\n img.width = width;\r\n img.height = height;\r\n img.style.verticalAlign = `-${height - baseline}px`;\r\n }\r\n\r\n /**\r\n * Calculates the metrics of an image which has been resized. Is used to restore the original\r\n * metrics of a resized image.\r\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\r\n */\r\n static fixAfterResize(img) {\r\n img.removeAttribute(\"style\");\r\n img.removeAttribute(\"width\");\r\n img.removeAttribute(\"height\");\r\n // In order to avoid resize with max-width css property.\r\n img.style.maxWidth = \"none\";\r\n\r\n const processImg = (img) => {\r\n if (img.src.indexOf(\"data:image\") !== -1) {\r\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\r\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\r\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\r\n // (which would actually make more sense for readibility and efficiency).\r\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\r\n // 'data:image/svg+xml;base64,'.length === 26\r\n const base64String = img.getAttribute(\"src\").substring(26);\r\n const svgString = window.atob(base64String);\r\n const encodedSvgString = encodeURIComponent(svgString);\r\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n // Return src to base64!\r\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\r\n } else {\r\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\r\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\r\n Image.setImgSize(img, svg, true);\r\n }\r\n } else {\r\n // 'data:image/png;base64,' === 22.\r\n const base64 = img.src.substring(22, img.src.length);\r\n Image.setImgSize(img, base64, true);\r\n }\r\n } else {\r\n Image.setImgSize(img, img.src);\r\n }\r\n };\r\n\r\n // If the image doesn't contain a blob, just process it normally\r\n if (img.src.indexOf(\"blob:\") === -1) {\r\n processImg(img);\r\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = function () {\r\n img.setAttribute(\"src\", reader.result);\r\n processImg(img);\r\n };\r\n fetch(img.src)\r\n .then((r) => r.blob())\r\n .then((blob) => {\r\n reader.readAsDataURL(blob);\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\r\n * by the MathType image service. This image contains as an extra custom attribute:\r\n * the baseline (wrs:baseline).\r\n * @param {String} svgString - The SVG image.\r\n * @return {Array} - The image metrics.\r\n */\r\n static getMetricsFromSvgString(svgString) {\r\n let first = svgString.indexOf('height=\"');\r\n let last = svgString.indexOf('\"', first + 8, svgString.length);\r\n const height = svgString.substring(first + 8, last);\r\n\r\n first = svgString.indexOf('width=\"');\r\n last = svgString.indexOf('\"', first + 7, svgString.length);\r\n const width = svgString.substring(first + 7, last);\r\n\r\n first = svgString.indexOf('wrs:baseline=\"');\r\n last = svgString.indexOf('\"', first + 14, svgString.length);\r\n const baseline = svgString.substring(first + 14, last);\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n if (typeof baseline !== \"undefined\") {\r\n arr.cb = baseline;\r\n }\r\n return arr;\r\n }\r\n return [];\r\n }\r\n\r\n /**\r\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\r\n * @param {Array.} bytes - png byte array.\r\n * @return {Array} The png metrics.\r\n */\r\n static getMetricsFromBytes(bytes) {\r\n Util.readBytes(bytes, 0, 8);\r\n let width;\r\n let height;\r\n let typ;\r\n let baseline;\r\n let dpi;\r\n while (bytes.length >= 4) {\r\n typ = Util.readInt32(bytes);\r\n if (typ === 0x49484452) {\r\n width = Util.readInt32(bytes);\r\n height = Util.readInt32(bytes);\r\n // Read 5 bytes.\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n } else if (typ === 0x62615345) {\r\n // Baseline: 'baSE'.\r\n baseline = Util.readInt32(bytes);\r\n } else if (typ === 0x70485973) {\r\n // Dpis: 'pHYs'.\r\n dpi = Util.readInt32(bytes);\r\n dpi = Math.round(dpi / 39.37);\r\n Util.readInt32(bytes);\r\n Util.readByte(bytes);\r\n }\r\n Util.readInt32(bytes);\r\n }\r\n\r\n if (typeof width !== \"undefined\") {\r\n const arr = [];\r\n arr.cw = width;\r\n arr.ch = height;\r\n arr.dpi = dpi;\r\n if (baseline) {\r\n arr.cb = baseline;\r\n }\r\n\r\n return arr;\r\n }\r\n return [];\r\n }\r\n}\r\n","import TextCache from \"./textcache\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport MathML from \"./mathml\";\r\nimport StringManager from \"./stringmanager\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\r\n * the associated client-side cache.\r\n */\r\nexport default class Accessibility {\r\n /**\r\n * Static property.\r\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\r\n * @type {TextCache}\r\n */\r\n static get cache() {\r\n return Accessibility._cache;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set accessibility cache.\r\n * @param {TextCahe} value - The property value.\r\n * @ignore\r\n */\r\n static set cache(value) {\r\n Accessibility._cache = value;\r\n }\r\n\r\n /**\r\n * Converts MathML strings to its accessible text representation.\r\n * @param {String} mathML - MathML to be converted to accessible text.\r\n * @param {String} [language] - Language of the accessible text. 'en' by default.\r\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\r\n * @return {String} Accessibility text.\r\n */\r\n static mathMLToAccessible(mathML, language, data) {\r\n if (typeof language === \"undefined\") {\r\n language = \"en\";\r\n }\r\n // Check MathML class. If the class is chemistry,\r\n // we add chemistry to data to force accessibility service\r\n // to load chemistry grammar.\r\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\r\n data.mode = \"chemistry\";\r\n }\r\n // Ignore accesibility styles\r\n data.ignoreStyles = true;\r\n let accessibleText = \"\";\r\n\r\n if (Accessibility.cache.get(mathML)) {\r\n accessibleText = Accessibility.cache.get(mathML);\r\n } else {\r\n data.service = \"mathml2accessible\";\r\n data.lang = language;\r\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\r\n if (accessibleJsonResponse.status !== \"error\") {\r\n accessibleText = accessibleJsonResponse.result.text;\r\n Accessibility.cache.populate(mathML, accessibleText);\r\n } else {\r\n accessibleText = StringManager.get(\"error_convert_accessibility\");\r\n }\r\n }\r\n\r\n return accessibleText;\r\n }\r\n}\r\n\r\n/**\r\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\r\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\r\n * @private\r\n * @type {TextCache}\r\n */\r\nAccessibility._cache = new TextCache();\r\n","import Util from \"./util\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport Image from \"./image\";\r\nimport Accessibility from \"./accessibility\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Configuration from \"./configuration\";\r\nimport Constants from \"./constants\";\r\n// eslint-disable-next-line no-unused-vars\r\nimport md5 from \"./md5\";\r\n\r\n/**\r\n * @classdesc\r\n * This class represent a MahML parser. Converts MathML into formulas depending on the\r\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\r\n * in the backend.\r\n */\r\nexport default class Parser {\r\n /**\r\n * Converts a MathML string to an img element.\r\n * @param {Document} creator - Document object to call createElement method.\r\n * @param {string} mathml - MathML code\r\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\r\n * @param {language} language - custom language for accessibility.\r\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\r\n * @static\r\n */\r\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\r\n const imgObject = creator.createElement(\"img\");\r\n imgObject.align = \"middle\";\r\n imgObject.style.maxWidth = \"none\";\r\n let data = wirisProperties || {};\r\n\r\n // Take into account the backend config\r\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\r\n data = { ...wirisEditorProperties, ...data };\r\n\r\n data.mml = mathml;\r\n data.lang = language;\r\n // Request metrics of the generated image.\r\n data.metrics = \"true\";\r\n data.centerbaseline = \"false\";\r\n\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n // Render js params: _wrs_int_wirisProperties contains some js render params.\r\n // Since MathML can support render params, js params should be send only to editor.\r\n\r\n imgObject.className = Configuration.get(\"imageClassName\");\r\n\r\n if (mathml.indexOf('class=\"') !== -1) {\r\n // We check here if the MathML has been created from a customEditor (such chemistry)\r\n // to add custom editor name attribute to img object (if necessary).\r\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\r\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\r\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\r\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\r\n }\r\n\r\n // Performance enabled.\r\n if (\r\n Configuration.get(\"wirisPluginPerformance\") &&\r\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\r\n ) {\r\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\r\n if (result.status === \"warning\") {\r\n // POST call.\r\n // if the mathml is malformed, this function will throw an exception.\r\n try {\r\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n ({ result } = result);\r\n if (result.format === \"png\") {\r\n imgObject.src = `data:image/png;base64,${result.content}`;\r\n } else {\r\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\r\n }\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n Image.setImgSize(imgObject, result.content, true);\r\n\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n if (typeof result.alt === \"undefined\") {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n } else {\r\n imgObject.alt = result.alt;\r\n }\r\n }\r\n } else {\r\n const result = Parser.createImageSrc(mathml, data);\r\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\r\n imgObject.src = result;\r\n Image.setImgSize(\r\n imgObject,\r\n result,\r\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\r\n );\r\n if (Configuration.get(\"enableAccessibility\")) {\r\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\r\n }\r\n }\r\n\r\n if (typeof Parser.observer !== \"undefined\") {\r\n Parser.observer.observe(imgObject);\r\n }\r\n\r\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\r\n imgObject.setAttribute(\"role\", \"math\");\r\n return imgObject;\r\n }\r\n\r\n /**\r\n * Returns the source to showimage service by calling createimage service. The\r\n * output of the createimage service is a URL path pointing to showimage service.\r\n * This method is called when performance is disabled.\r\n * @param {string} mathml - MathML code.\r\n * @param {Object[]} data - data object containing service parameters.\r\n * @returns {string} the showimage path.\r\n */\r\n static createImageSrc(mathml, data) {\r\n // Full base64 method (edit & save).\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\r\n data.base64 = true;\r\n }\r\n\r\n let result = ServiceProvider.getService(\"createimage\", data);\r\n\r\n if (result.indexOf(\"@BASE@\") !== -1) {\r\n // Replacing '@BASE@' with the base URL of createimage.\r\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\r\n baseParts.pop();\r\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\r\n * this data would be converted as following:\r\n *
\r\n   * MathML code: Image containing the corresponding MathML formulas.\r\n   * MathML code with LaTeX annotation : LaTeX string.\r\n   * 
\r\n * @param {string} code - HTML code containing MathML data.\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\r\n */\r\n static initParse(code, language) {\r\n /* Note: The code inside this function has been inverted.\r\n If you invert again the code then you cannot use correctly LaTeX\r\n in Moodle.\r\n */\r\n code = Parser.initParseSaveMode(code, language);\r\n return Parser.initParseEditMode(code);\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the save mode. Transforms all MathML\r\n * occurrences for it's correspondent image or LaTeX.\r\n * @param {string} code - HTML code to be parsed\r\n * @param {string} language - language to create image alt text.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static initParseSaveMode(code, language) {\r\n if (Configuration.get(\"saveMode\")) {\r\n // Converting XML to tags.\r\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\r\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\r\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\r\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\r\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\r\n code = Parser.codeImgTransform(code, \"base642showimage\");\r\n }\r\n }\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses initial HTML code depending on the edit mode.\r\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\r\n * be converted into a LaTeX string instead of an image.\r\n * @param {string} code - HTML code containing MathML.\r\n * @returns {string} parsed HTML code.\r\n */\r\n static initParseEditMode(code) {\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\r\n const token = 'encoding=\"LaTeX\">';\r\n // While replacing images with latex, the indexes of the found images changes\r\n // respecting the original code, so this carry is needed.\r\n let carry = 0;\r\n\r\n for (let i = 0; i < imgList.length; i += 1) {\r\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\r\n\r\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\r\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\r\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n\r\n if (mathmlStart === -1) {\r\n mathmlStartToken = ' alt=\"';\r\n mathmlStart = imgCode.indexOf(mathmlStartToken);\r\n }\r\n\r\n if (mathmlStart !== -1) {\r\n mathmlStart += mathmlStartToken.length;\r\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\r\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\r\n let latexStartPosition = mathml.indexOf(token);\r\n\r\n if (latexStartPosition !== -1) {\r\n latexStartPosition += token.length;\r\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\r\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\r\n\r\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\r\n const start = code.substring(0, imgList[i].start + carry);\r\n const end = code.substring(imgList[i].end + carry);\r\n code = start + replaceText + end;\r\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code. The end HTML code is HTML code with embedded images\r\n * or LaTeX formulas created with MathType.
\r\n * By default this method converts the formula images and LaTeX strings in MathML.
\r\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\r\n * @param {string} code - HTML to be parsed\r\n * @returns {string} the HTML code parsed.\r\n */\r\n static endParse(code) {\r\n // Transform LaTeX ocurrences to MathML elements.\r\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\r\n // Transform img elements to MathML elements.\r\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\r\n return codeEndParseSaveMode;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the edit mode.\r\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\r\n * @param {string} code - HTML code to be parsed.\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseEditMode(code) {\r\n // Converting LaTeX to images.\r\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n let startPosition = code.indexOf(\"$$\");\r\n while (startPosition !== -1) {\r\n output += code.substring(endPosition, startPosition);\r\n endPosition = code.indexOf(\"$$\", startPosition + 2);\r\n\r\n if (endPosition !== -1) {\r\n // Before, it was a condition here to execute the next codelines\r\n // 'latex.indexOf('<') == -1'.\r\n // We don't know why it was used, but seems to have a conflict with\r\n // latex formulas that contains '<'.\r\n const latex = code.substring(startPosition + 2, endPosition);\r\n const decodedLatex = Util.htmlEntitiesDecode(latex);\r\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\r\n if (!Configuration.get(\"saveHandTraces\")) {\r\n // Remove hand traces.\r\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\r\n }\r\n output += mathml;\r\n endPosition += 2;\r\n } else {\r\n output += \"$$\";\r\n endPosition = startPosition + 2;\r\n }\r\n\r\n startPosition = code.indexOf(\"$$\", endPosition);\r\n }\r\n\r\n output += code.substring(endPosition, code.length);\r\n code = output;\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Parses end HTML code depending on the save mode. Converts all\r\n * images into the element determined by the save mode:\r\n * - xml: Parses images formulas into MathML.\r\n * - safeXml: Parses images formulas into safeMAthML\r\n * - base64: Parses images into base64 images.\r\n * - image: Parse images into images (no parsing)\r\n * @param {string} code - HTML code to be parsed\r\n * @returns {string} HTML code parsed.\r\n */\r\n static endParseSaveMode(code) {\r\n const savemode = Configuration.get(\"saveMode\");\r\n const base64savemode = Configuration.get(\"base64savemode\");\r\n\r\n if (savemode) {\r\n if (savemode === \"safeXml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"xml\") {\r\n code = Parser.codeImgTransform(code, \"img2mathml\");\r\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\r\n code = Parser.codeImgTransform(code, \"img264\");\r\n }\r\n }\r\n\r\n return code;\r\n }\r\n\r\n /**\r\n * Auxiliar function that builds the data object to send to the showimage endpoint\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object with the data to send to showimage.\r\n */\r\n static createShowImageSrcData(data, language) {\r\n const dataMd5 = {};\r\n const renderParams = [\r\n \"mml\",\r\n \"color\",\r\n \"centerbaseline\",\r\n \"zoom\",\r\n \"dpi\",\r\n \"fontSize\",\r\n \"fontFamily\",\r\n \"defaultStretchy\",\r\n \"backgroundColor\",\r\n \"format\",\r\n ];\r\n renderParams.forEach((param) => {\r\n if (typeof data[param] !== \"undefined\") {\r\n dataMd5[param] = data[param];\r\n }\r\n });\r\n // Data variables to get.\r\n const dataObject = {};\r\n Object.keys(data).forEach((key) => {\r\n // We don't need mathml in this request we try to get cached.\r\n // Only need the formula md5 calculated before.\r\n if (key !== \"mml\") {\r\n dataObject[key] = data[key];\r\n }\r\n });\r\n\r\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\r\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\r\n dataObject.version = Configuration.get(\"version\");\r\n\r\n return dataObject;\r\n }\r\n\r\n /**\r\n * Returns the result to call showimage service with the formula md5 as parameter.\r\n * The result could be:\r\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\r\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\r\n * @param {Object[]} data - object containing showimage service parameters.\r\n * @param {string} language - string containing the language of the formula.\r\n * @returns {Object} JSON object containing showimage response.\r\n */\r\n static createShowImageSrc(data, language) {\r\n const dataObject = this.createShowImageSrcData(data, language);\r\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\r\n return result;\r\n }\r\n\r\n /**\r\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\r\n * or showimage img tags (i.e with showimage.php on src)\r\n * @param {string} code - HTML code\r\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\r\n * @returns {string} html - code transformed.\r\n */\r\n static codeImgTransform(code, mode) {\r\n let output = \"\";\r\n let endPosition = 0;\r\n const pattern = /\") {\r\n endPosition = i + 1;\r\n }\r\n\r\n i += 1;\r\n }\r\n\r\n if (endPosition < startPosition) {\r\n // The img tag is stripped.\r\n output += code.substring(startPosition, code.length);\r\n return output;\r\n }\r\n let imgCode = code.substring(startPosition, endPosition);\r\n const imgObject = Util.createObject(imgCode);\r\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\r\n let convertToXml;\r\n let convertToSafeXml;\r\n\r\n if (mode === \"base642showimage\") {\r\n if (xmlCode == null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\r\n output += Util.createObjectCode(imgCode);\r\n } else if (mode === \"img2mathml\") {\r\n if (Configuration.get(\"saveMode\")) {\r\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\r\n convertToXml = true;\r\n convertToSafeXml = true;\r\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\r\n convertToXml = true;\r\n convertToSafeXml = false;\r\n }\r\n }\r\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\r\n } else if (mode === \"img264\") {\r\n if (xmlCode === null) {\r\n xmlCode = imgObject.getAttribute(\"alt\");\r\n }\r\n xmlCode = MathML.safeXmlDecode(xmlCode);\r\n\r\n const properties = {};\r\n properties.base64 = \"true\";\r\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\r\n // Metrics.\r\n Image.setImgSize(imgCode, imgCode.src, true);\r\n output += Util.createObjectCode(imgCode);\r\n }\r\n }\r\n output += code.substring(endPosition, code.length);\r\n return output;\r\n }\r\n\r\n /**\r\n * Converts all occurrences of MathML to the corresponding image.\r\n * @param {string} content - string with valid MathML code.\r\n * The MathML code doesn't contain semantics.\r\n * @param {Constants} characters - Constant object containing xmlCharacters\r\n * or safeXmlCharacters relation.\r\n * @param {string} language - a valid language code\r\n * in order to generate formula accessibility.\r\n * @returns {string} The input string with all the MathML\r\n * occurrences replaced by the corresponding image.\r\n */\r\n static parseMathmlToImg(content, characters, language) {\r\n let output = \"\";\r\n const mathTagBegin = `${characters.tagOpener}math`;\r\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\r\n let start = content.indexOf(mathTagBegin);\r\n let end = 0;\r\n\r\n while (start !== -1) {\r\n output += content.substring(end, start);\r\n // Avoid WIRIS images to be parsed.\r\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\r\n end = content.indexOf(mathTagEnd, start);\r\n\r\n if (end === -1) {\r\n end = content.length - 1;\r\n } else if (imageMathmlAtrribute !== -1) {\r\n // First close tag of img attribute\r\n // If a mathmlAttribute exists should be inside a img tag.\r\n end += content.indexOf(\"/>\", start);\r\n } else {\r\n end += mathTagEnd.length;\r\n }\r\n\r\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\r\n let mathml = content.substring(start, end);\r\n mathml =\r\n characters.id === Constants.safeXmlCharacters.id\r\n ? MathML.safeXmlDecode(mathml)\r\n : MathML.mathMLEntities(mathml);\r\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\r\n } else {\r\n output += content.substring(start, end);\r\n }\r\n\r\n start = content.indexOf(mathTagBegin, end);\r\n }\r\n\r\n output += content.substring(end, content.length);\r\n return output;\r\n }\r\n}\r\n\r\n// Mutation observers to avoid wiris image formulas class be removed.\r\nif (typeof MutationObserver !== \"undefined\") {\r\n const mutationObserver = new MutationObserver((mutations) => {\r\n mutations.forEach((mutation) => {\r\n if (\r\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\r\n mutation.attributeName === \"class\" &&\r\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\r\n ) {\r\n mutation.target.className = Configuration.get(\"imageClassName\");\r\n }\r\n });\r\n });\r\n\r\n Parser.observer = Object.create(mutationObserver);\r\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\r\n // We use own default config.\r\n Parser.observer.observe = function (target) {\r\n Object.getPrototypeOf(this).observe(target, this.Config);\r\n };\r\n}\r\n","/* eslint-disable class-methods-use-this */\r\n/* eslint-disable no-unused-vars */\r\n/* eslint-disable no-extra-semi */\r\n\r\n// The rules above are disabled because we are implementing\r\n// an external interface.\r\n\r\nexport default class EditorListener {\r\n /**\r\n * @classdesc\r\n * Determines if the content of the\r\n * MathType Editor has changes.\r\n * @implements {EditorListeners}\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the content of the editor has changed.\r\n * @type {Boolean}\r\n */\r\n this.isContentChanged = false;\r\n\r\n /**\r\n * Indicates if the listener should be waiting for changes in the editor.\r\n * @type {Boolean}\r\n */\r\n this.waitingForChanges = false;\r\n }\r\n\r\n /**\r\n * Sets {@link EditorListener.isContentChanged} property.\r\n * @param {Boolean} value - The new vlue.\r\n */\r\n setIsContentChanged(value) {\r\n this.isContentChanged = value;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed, false otherwise.\r\n * @return {Boolean}\r\n */\r\n getIsContentChanged() {\r\n return this.isContentChanged;\r\n }\r\n\r\n /**\r\n * Determines if the EditorListener should wait for any changes.\r\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\r\n */\r\n setWaitingForChanges(value) {\r\n this.waitingForChanges = value;\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite.\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n caretPositionChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @type {JsEditor}\r\n * @ignore\r\n */\r\n clipboardChanged(_editor) {}\r\n\r\n /**\r\n * Determines if the content of an editor has been changed.\r\n * @param {JsEditor} editor - editor object.\r\n */\r\n contentChanged(_editor) {\r\n if (this.waitingForChanges === true && this.isContentChanged === false) {\r\n this.isContentChanged = true;\r\n }\r\n }\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} editor - The editor instance.\r\n */\r\n styleChanged(_editor) {}\r\n\r\n /**\r\n * EditorListener method to overwrite\r\n * @param {JsEditor} - The editor instance.\r\n */\r\n transformationReceived(_editor) {}\r\n}\r\n","let wasm;\r\n\r\nconst cachedTextDecoder =\r\n typeof TextDecoder !== \"undefined\"\r\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\r\n : {\r\n decode: () => {\r\n throw Error(\"TextDecoder not available\");\r\n },\r\n };\r\n\r\nif (typeof TextDecoder !== \"undefined\") {\r\n cachedTextDecoder.decode();\r\n}\r\n\r\nlet cachedUint8Memory0 = null;\r\n\r\nfunction getUint8Memory0() {\r\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\r\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\r\n }\r\n return cachedUint8Memory0;\r\n}\r\n\r\nfunction getStringFromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\r\n}\r\n\r\nconst heap = new Array(128).fill(undefined);\r\n\r\nheap.push(undefined, null, true, false);\r\n\r\nlet heap_next = heap.length;\r\n\r\nfunction addHeapObject(obj) {\r\n if (heap_next === heap.length) heap.push(heap.length + 1);\r\n const idx = heap_next;\r\n heap_next = heap[idx];\r\n\r\n heap[idx] = obj;\r\n return idx;\r\n}\r\n\r\nfunction getObject(idx) {\r\n return heap[idx];\r\n}\r\n\r\nfunction dropObject(idx) {\r\n if (idx < 132) return;\r\n heap[idx] = heap_next;\r\n heap_next = idx;\r\n}\r\n\r\nfunction takeObject(idx) {\r\n const ret = getObject(idx);\r\n dropObject(idx);\r\n return ret;\r\n}\r\n\r\nlet WASM_VECTOR_LEN = 0;\r\n\r\nconst cachedTextEncoder =\r\n typeof TextEncoder !== \"undefined\"\r\n ? new TextEncoder(\"utf-8\")\r\n : {\r\n encode: () => {\r\n throw Error(\"TextEncoder not available\");\r\n },\r\n };\r\n\r\nconst encodeString =\r\n typeof cachedTextEncoder.encodeInto === \"function\"\r\n ? function (arg, view) {\r\n return cachedTextEncoder.encodeInto(arg, view);\r\n }\r\n : function (arg, view) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n view.set(buf);\r\n return {\r\n read: arg.length,\r\n written: buf.length,\r\n };\r\n };\r\n\r\nfunction passStringToWasm0(arg, malloc, realloc) {\r\n if (realloc === undefined) {\r\n const buf = cachedTextEncoder.encode(arg);\r\n const ptr = malloc(buf.length, 1) >>> 0;\r\n getUint8Memory0()\r\n .subarray(ptr, ptr + buf.length)\r\n .set(buf);\r\n WASM_VECTOR_LEN = buf.length;\r\n return ptr;\r\n }\r\n\r\n let len = arg.length;\r\n let ptr = malloc(len, 1) >>> 0;\r\n\r\n const mem = getUint8Memory0();\r\n\r\n let offset = 0;\r\n\r\n for (; offset < len; offset++) {\r\n const code = arg.charCodeAt(offset);\r\n if (code > 0x7f) break;\r\n mem[ptr + offset] = code;\r\n }\r\n\r\n if (offset !== len) {\r\n if (offset !== 0) {\r\n arg = arg.slice(offset);\r\n }\r\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\r\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\r\n const ret = encodeString(arg, view);\r\n\r\n offset += ret.written;\r\n }\r\n\r\n WASM_VECTOR_LEN = offset;\r\n return ptr;\r\n}\r\n\r\nfunction isLikeNone(x) {\r\n return x === undefined || x === null;\r\n}\r\n\r\nlet cachedInt32Memory0 = null;\r\n\r\nfunction getInt32Memory0() {\r\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\r\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\r\n }\r\n return cachedInt32Memory0;\r\n}\r\n\r\nlet cachedFloat64Memory0 = null;\r\n\r\nfunction getFloat64Memory0() {\r\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\r\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\r\n }\r\n return cachedFloat64Memory0;\r\n}\r\n\r\nlet cachedBigInt64Memory0 = null;\r\n\r\nfunction getBigInt64Memory0() {\r\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\r\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\r\n }\r\n return cachedBigInt64Memory0;\r\n}\r\n\r\nfunction debugString(val) {\r\n // primitive types\r\n const type = typeof val;\r\n if (type == \"number\" || type == \"boolean\" || val == null) {\r\n return `${val}`;\r\n }\r\n if (type == \"string\") {\r\n return `\"${val}\"`;\r\n }\r\n if (type == \"symbol\") {\r\n const description = val.description;\r\n if (description == null) {\r\n return \"Symbol\";\r\n } else {\r\n return `Symbol(${description})`;\r\n }\r\n }\r\n if (type == \"function\") {\r\n const name = val.name;\r\n if (typeof name == \"string\" && name.length > 0) {\r\n return `Function(${name})`;\r\n } else {\r\n return \"Function\";\r\n }\r\n }\r\n // objects\r\n if (Array.isArray(val)) {\r\n const length = val.length;\r\n let debug = \"[\";\r\n if (length > 0) {\r\n debug += debugString(val[0]);\r\n }\r\n for (let i = 1; i < length; i++) {\r\n debug += \", \" + debugString(val[i]);\r\n }\r\n debug += \"]\";\r\n return debug;\r\n }\r\n // Test for built-in\r\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\r\n let className;\r\n if (builtInMatches.length > 1) {\r\n className = builtInMatches[1];\r\n } else {\r\n // Failed to match the standard '[object ClassName]'\r\n return toString.call(val);\r\n }\r\n if (className == \"Object\") {\r\n // we're a user defined class or Object\r\n // JSON.stringify avoids problems with cycles, and is generally much\r\n // easier than looping through ownProperties of `val`.\r\n try {\r\n return \"Object(\" + JSON.stringify(val) + \")\";\r\n } catch (_) {\r\n return \"Object\";\r\n }\r\n }\r\n // errors\r\n if (val instanceof Error) {\r\n return `${val.name}: ${val.message}\\n${val.stack}`;\r\n }\r\n // TODO we could test for more things here, like `Set`s and `Map`s.\r\n return className;\r\n}\r\n\r\nfunction makeClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n try {\r\n return f(state.a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\r\n state.a = 0;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction makeMutClosure(arg0, arg1, dtor, f) {\r\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\r\n const real = (...args) => {\r\n // First up with a closure we increment the internal reference\r\n // count. This ensures that the Rust closure environment won't\r\n // be deallocated while we're invoking it.\r\n state.cnt++;\r\n const a = state.a;\r\n state.a = 0;\r\n try {\r\n return f(a, state.b, ...args);\r\n } finally {\r\n if (--state.cnt === 0) {\r\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\r\n } else {\r\n state.a = a;\r\n }\r\n }\r\n };\r\n real.original = state;\r\n\r\n return real;\r\n}\r\nfunction __wbg_adapter_49(arg0, arg1) {\r\n wasm.__wbindgen_export_4(arg0, arg1);\r\n}\r\n\r\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\r\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\r\n}\r\n\r\nfunction handleError(f, args) {\r\n try {\r\n return f.apply(this, args);\r\n } catch (e) {\r\n wasm.__wbindgen_export_6(addHeapObject(e));\r\n }\r\n}\r\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\r\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\r\n}\r\n\r\n/**\r\n */\r\nexport function main_js() {\r\n wasm.main_js();\r\n}\r\n\r\nfunction getArrayU8FromWasm0(ptr, len) {\r\n ptr = ptr >>> 0;\r\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\r\n}\r\n/**\r\n */\r\nexport const Level = Object.freeze({\r\n Err: 0,\r\n 0: \"Err\",\r\n Warn: 1,\r\n 1: \"Warn\",\r\n Info: 2,\r\n 2: \"Info\",\r\n Debug: 3,\r\n 3: \"Debug\",\r\n});\r\n/**\r\n */\r\nexport class Telemeter {\r\n __destroy_into_raw() {\r\n const ptr = this.__wbg_ptr;\r\n this.__wbg_ptr = 0;\r\n\r\n return ptr;\r\n }\r\n\r\n free() {\r\n const ptr = this.__destroy_into_raw();\r\n wasm.__wbg_telemeter_free(ptr);\r\n }\r\n /**\r\n * @param {any} solution\r\n * @param {any} hosts\r\n * @param {any} config\r\n */\r\n constructor(solution, hosts, config) {\r\n try {\r\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\r\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\r\n var r0 = getInt32Memory0()[retptr / 4 + 0];\r\n var r1 = getInt32Memory0()[retptr / 4 + 1];\r\n var r2 = getInt32Memory0()[retptr / 4 + 2];\r\n if (r2) {\r\n throw takeObject(r1);\r\n }\r\n this.__wbg_ptr = r0 >>> 0;\r\n return this;\r\n } finally {\r\n wasm.__wbindgen_add_to_stack_pointer(16);\r\n }\r\n }\r\n /**\r\n * @param {string} sender_id\r\n * @returns {Promise}\r\n */\r\n identify(sender_id) {\r\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {string} event_type\r\n * @param {any} event_payload\r\n * @returns {Promise}\r\n */\r\n track(event_type, event_payload) {\r\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {any} level\r\n * @param {string} message\r\n * @param {any} payload\r\n * @returns {Promise}\r\n */\r\n log(level, message, payload) {\r\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len0 = WASM_VECTOR_LEN;\r\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @returns {Promise}\r\n */\r\n finish() {\r\n const ptr = this.__destroy_into_raw();\r\n const ret = wasm.telemeter_finish(ptr);\r\n return takeObject(ret);\r\n }\r\n /**\r\n * @param {boolean | undefined} [new_debug_status]\r\n */\r\n debug(new_debug_status) {\r\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\r\n }\r\n}\r\n\r\nasync function __wbg_load(module, imports) {\r\n if (typeof Response === \"function\" && module instanceof Response) {\r\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\r\n try {\r\n return await WebAssembly.instantiateStreaming(module, imports);\r\n } catch (e) {\r\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\r\n console.warn(\r\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\r\n e,\r\n );\r\n } else {\r\n throw e;\r\n }\r\n }\r\n }\r\n\r\n const bytes = await module.arrayBuffer();\r\n return await WebAssembly.instantiate(bytes, imports);\r\n } else {\r\n const instance = await WebAssembly.instantiate(module, imports);\r\n\r\n if (instance instanceof WebAssembly.Instance) {\r\n return { instance, module };\r\n } else {\r\n return instance;\r\n }\r\n }\r\n}\r\n\r\nfunction __wbg_get_imports() {\r\n const imports = {};\r\n imports.wbg = {};\r\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\r\n const ret = getStringFromWasm0(arg0, arg1);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\r\n const ret = new Object();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\r\n const ret = getObject(arg0).status;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\r\n const ret = getObject(arg0).headers;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\r\n const ret = new Date();\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\r\n const ret = getObject(arg0).getTime();\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\r\n takeObject(arg0);\r\n };\r\n imports.wbg.__wbindgen_is_object = function (arg0) {\r\n const val = getObject(arg0);\r\n const ret = typeof val === \"object\" && val !== null;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\r\n const ret = getObject(arg0).crypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\r\n const ret = getObject(arg0).process;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\r\n const ret = getObject(arg0).versions;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\r\n const ret = getObject(arg0).node;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_string = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"string\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\r\n const ret = getObject(arg0).msCrypto;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\r\n return handleError(function () {\r\n const ret = module.require;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\r\n const ret = new Uint8Array(arg0 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[arg1 >>> 0];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).next();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\r\n const ret = getObject(arg0).done;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\r\n const ret = getObject(arg0).value;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\r\n const ret = Symbol.iterator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\r\n const ret = getObject(arg0).next;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_is_function = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"function\";\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = getObject(arg0).call(getObject(arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\r\n const ret = getObject(arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\r\n return handleError(function () {\r\n const ret = self.self;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\r\n return handleError(function () {\r\n const ret = window.window;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\r\n return handleError(function () {\r\n const ret = globalThis.globalThis;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\r\n return handleError(function () {\r\n const ret = global.global;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\r\n const ret = getObject(arg0) === undefined;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\r\n const ret = new Function(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\r\n const ret = Array.isArray(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\r\n const ret = Number.isSafeInteger(getObject(arg0));\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\r\n try {\r\n var state0 = { a: arg0, b: arg1 };\r\n var cb0 = (arg0, arg1) => {\r\n const a = state0.a;\r\n state0.a = 0;\r\n try {\r\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\r\n } finally {\r\n state0.a = a;\r\n }\r\n };\r\n const ret = new Promise(cb0);\r\n return addHeapObject(ret);\r\n } finally {\r\n state0.a = state0.b = 0;\r\n }\r\n };\r\n imports.wbg.__wbindgen_memory = function () {\r\n const ret = wasm.memory;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\r\n const ret = getObject(arg0).buffer;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\r\n const ret = new Uint8Array(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\r\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\r\n };\r\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"string\" ? obj : undefined;\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\r\n return handleError(function (arg0) {\r\n const ret = JSON.stringify(getObject(arg0));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\r\n return ret;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\r\n const ret = getObject(arg0).fetch(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\r\n const ret = fetch(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\r\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\r\n return handleError(function () {\r\n const ret = new AbortController();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\r\n const ret = getObject(arg0).signal;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\r\n getObject(arg0).abort();\r\n };\r\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\r\n const ret = new Error(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) == getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\r\n const v = getObject(arg0);\r\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\r\n const obj = getObject(arg1);\r\n const ret = typeof obj === \"number\" ? obj : undefined;\r\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Uint8Array;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof ArrayBuffer;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\r\n const ret = Object.entries(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\r\n const ret = String(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\r\n console.warn(getObject(arg0), getObject(arg1));\r\n };\r\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\r\n const ret = getObject(arg0).readyState;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\r\n console.warn(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\r\n return handleError(function (arg0) {\r\n getObject(arg0).close();\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_new_b9b318679315404f = function () {\r\n return handleError(function (arg0, arg1) {\r\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\r\n getObject(arg0).binaryType = takeObject(arg1);\r\n };\r\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\r\n console.log(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\r\n console.error(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\r\n console.info(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\r\n const ret = getObject(arg0).document;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\r\n const ret = getObject(arg0).visibilityState;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\r\n const ret = getObject(arg0)[getObject(arg1)];\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\r\n const ret = getObject(arg0).length;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\r\n const ret = typeof getObject(arg0) === \"bigint\";\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\r\n const ret = arg0;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\r\n const ret = getObject(arg0) in getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\r\n const ret = BigInt.asUintN(64, arg0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\r\n const ret = getObject(arg0) === getObject(arg1);\r\n return ret;\r\n };\r\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).localStorage;\r\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3) {\r\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\r\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n var len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\r\n const ret = getObject(arg0).navigator;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).mediaDevices;\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\r\n return handleError(function (arg0) {\r\n const ret = getObject(arg0).enumerateDevices();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\r\n const obj = takeObject(arg0).original;\r\n if (obj.cnt-- == 1) {\r\n obj.a = 0;\r\n return true;\r\n }\r\n const ret = false;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\r\n const ret = getObject(arg1).deviceId;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Response;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).randomFillSync(takeObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\r\n return handleError(function (arg0, arg1) {\r\n getObject(arg0).getRandomValues(getObject(arg1));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\r\n const v = getObject(arg1);\r\n const ret = typeof v === \"bigint\" ? v : undefined;\r\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\r\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\r\n };\r\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\r\n const ret = debugString(getObject(arg1));\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\r\n throw new Error(getStringFromWasm0(arg0, arg1));\r\n };\r\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\r\n const ret = getObject(arg0).then(getObject(arg1));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\r\n queueMicrotask(getObject(arg0));\r\n };\r\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\r\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\r\n const ret = getObject(arg0).queueMicrotask;\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\r\n const ret = Promise.resolve(getObject(arg0));\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\r\n const ret = getObject(arg1).url;\r\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\r\n const len1 = WASM_VECTOR_LEN;\r\n getInt32Memory0()[arg0 / 4 + 1] = len1;\r\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\r\n };\r\n imports.wbg.__wbg_send_2860805104507701 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\r\n let result;\r\n try {\r\n result = getObject(arg0) instanceof Window;\r\n } catch (_) {\r\n result = false;\r\n }\r\n const ret = result;\r\n return ret;\r\n };\r\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\r\n return handleError(function () {\r\n const ret = new Headers();\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\r\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\r\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\r\n }, arguments);\r\n };\r\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\r\n return handleError(function (arg0, arg1, arg2) {\r\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\r\n return addHeapObject(ret);\r\n }, arguments);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\r\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\r\n return addHeapObject(ret);\r\n };\r\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\r\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\r\n return addHeapObject(ret);\r\n };\r\n\r\n return imports;\r\n}\r\n\r\nfunction __wbg_init_memory(imports, maybe_memory) {}\r\n\r\nfunction __wbg_finalize_init(instance, module) {\r\n wasm = instance.exports;\r\n __wbg_init.__wbindgen_wasm_module = module;\r\n cachedBigInt64Memory0 = null;\r\n cachedFloat64Memory0 = null;\r\n cachedInt32Memory0 = null;\r\n cachedUint8Memory0 = null;\r\n\r\n wasm.__wbindgen_start();\r\n return wasm;\r\n}\r\n\r\nfunction initSync(module) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n const imports = __wbg_get_imports();\r\n\r\n __wbg_init_memory(imports);\r\n\r\n if (!(module instanceof WebAssembly.Module)) {\r\n module = new WebAssembly.Module(module);\r\n }\r\n\r\n const instance = new WebAssembly.Instance(module, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nasync function __wbg_init(input) {\r\n if (wasm !== undefined) return wasm;\r\n\r\n if (typeof input === \"undefined\") {\r\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\r\n }\r\n const imports = __wbg_get_imports();\r\n\r\n if (\r\n typeof input === \"string\" ||\r\n (typeof Request === \"function\" && input instanceof Request) ||\r\n (typeof URL === \"function\" && input instanceof URL)\r\n ) {\r\n input = fetch(input);\r\n }\r\n\r\n __wbg_init_memory(imports);\r\n\r\n const { instance, module } = await __wbg_load(await input, imports);\r\n\r\n return __wbg_finalize_init(instance, module);\r\n}\r\n\r\nexport { initSync };\r\nexport default __wbg_init;\r\n","/* eslint-disable-next-line */\r\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\r\n\r\n/**\r\n * @classdesc\r\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\r\n * to Telemetry in a more comfortable and homogeneous way.\r\n */\r\nexport default class Telemeter {\r\n /**\r\n * Inits Telemeter class.\r\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\r\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\r\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\r\n * @param {Object} telemeterAttributes.config - Configuration parameters.\r\n */\r\n static init(telemeterAttributes) {\r\n if (!this.telemeter && !this.waitingForInit) {\r\n this.waitingForInit = true;\r\n init(telemeterAttributes.url)\r\n .then(() => {\r\n this.telemeter = new TelemeterWASM(\r\n telemeterAttributes.solution,\r\n telemeterAttributes.hosts,\r\n telemeterAttributes.config,\r\n );\r\n })\r\n .catch((error) => {\r\n console.log(error);\r\n })\r\n .finally(() => (this.waitingForInit = false));\r\n }\r\n }\r\n\r\n /**\r\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\r\n */\r\n static async finish() {\r\n if (!this.telemeter) return;\r\n\r\n try {\r\n const local_telemeter = this.telemeter;\r\n this.telemeter = undefined;\r\n await local_telemeter.finish();\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n }\r\n}\r\n","import Configuration from \"./configuration\";\r\nimport Core from \"./core.src\";\r\nimport EditorListener from \"./editorlistener\";\r\nimport Listeners from \"./listeners\";\r\nimport MathML from \"./mathml\";\r\nimport Util from \"./util\";\r\nimport Telemeter from \"./telemeter\";\r\n\r\nexport default class ContentManager {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog, managing the following:\r\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\r\n * - The actions to be done once the modal object has been submitted\r\n * (submitAction} method).\r\n * - The update of the content when the {@link ModalDialog} class is also updated,\r\n * for example when ModalDialog is re-opened.\r\n * - The communication between the {@link ModalDialog} class and itself, if the content\r\n * has been changed (hasChanges} method).\r\n * @constructs\r\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\r\n * create a new instance.\r\n */\r\n constructor(contentManagerAttributes) {\r\n /**\r\n * An object containing MathType editor parameters. See\r\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\r\n * @type {Object}\r\n */\r\n this.editorAttributes = {};\r\n if (\"editorAttributes\" in contentManagerAttributes) {\r\n this.editorAttributes = contentManagerAttributes.editorAttributes;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\r\n }\r\n\r\n /**\r\n * CustomEditors instance. Contains the custom editors.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = null;\r\n if (\"customEditors\" in contentManagerAttributes) {\r\n this.customEditors = contentManagerAttributes.customEditors;\r\n }\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @type {Object}\r\n * @property {String} editor - Editor name. Usually the HTML editor.\r\n * @property {String} mode - Save mode. Xml by default.\r\n * @property {String} version - Plugin version.\r\n */\r\n this.environment = {};\r\n if (\"environment\" in contentManagerAttributes) {\r\n this.environment = contentManagerAttributes.environment;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: environment property missed\");\r\n }\r\n\r\n /**\r\n * ContentManager language.\r\n * @type {String}\r\n */\r\n this.language = \"\";\r\n if (\"language\" in contentManagerAttributes) {\r\n this.language = contentManagerAttributes.language;\r\n } else {\r\n throw new Error(\"ContentManager constructor error: language property missed\");\r\n }\r\n\r\n /**\r\n * {@link EditorListener} instance. Manages the changes inside the editor.\r\n * @type {EditorListener}\r\n */\r\n this.editorListener = new EditorListener();\r\n\r\n /**\r\n * MathType editor instance.\r\n * @type {JsEditor}\r\n */\r\n this.editor = null;\r\n\r\n /**\r\n * Navigator user agent.\r\n * @type {String}\r\n */\r\n this.ua = navigator.userAgent.toLowerCase();\r\n\r\n /**\r\n * Mobile device properties object\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {};\r\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\r\n this.deviceProperties.isIOS = ContentManager.isIOS();\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.toolbar = null;\r\n\r\n /**\r\n * Custom editor toolbar.\r\n * @type {String}\r\n */\r\n this.dbclick = null;\r\n\r\n /**\r\n * Instance of the {@link ModalDialog} class associated with the current\r\n * {@link ContentManager} instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialogInstance = null;\r\n\r\n /**\r\n * ContentManager listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * MathML associated to the ContentManager instance.\r\n * @type {String}\r\n */\r\n this.mathML = null;\r\n\r\n /**\r\n * Indicates if the edited element is a new one or not.\r\n * @type {Boolean}\r\n */\r\n this.isNewElement = true;\r\n\r\n /**\r\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n }\r\n\r\n /**\r\n * Adds a new listener to the current {@link ContentManager} instance.\r\n * @param {Object} listener - The listener to be added.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\r\n * instance.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\r\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\r\n */\r\n setModalDialogInstance(modalDialogInstance) {\r\n this.modalDialogInstance = modalDialogInstance;\r\n }\r\n\r\n /**\r\n * Inserts the content into the current {@link ModalDialog} instance updating\r\n * the title and inserting the JavaScript editor.\r\n */\r\n insert() {\r\n // Before insert the editor we update the modal object title to avoid weird render display.\r\n this.updateTitle(this.modalDialogInstance);\r\n this.insertEditor(this.modalDialogInstance);\r\n }\r\n\r\n /**\r\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\r\n * editor's JavaScript is loaded.\r\n */\r\n insertEditor() {\r\n if (ContentManager.isEditorLoaded()) {\r\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\r\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\r\n this.editor.focus();\r\n\r\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\r\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\r\n this.editor.action(\"rtl\");\r\n }\r\n // Setting div in rtl in case of it's activated.\r\n if (this.editor.getEditorModel().isRTL()) {\r\n this.editor.element.style.direction = \"rtl\";\r\n }\r\n\r\n // Editor listener: this object manages the changes logic of editor.\r\n this.editor.getEditorModel().addEditorListener(this.editorListener);\r\n\r\n // iOS events.\r\n if (this.modalDialogInstance.deviceProperties.isIOS) {\r\n setTimeout(function () {\r\n // Make sure the modalDialogInstance is available when the timeout is over\r\n // to avoid throw errors and stop execution.\r\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\r\n }, 400);\r\n\r\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\r\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\r\n }\r\n // Fire onLoad event. Necessary to set the MathML into the editor\r\n // after is loaded.\r\n this.listeners.fire(\"onLoad\", {});\r\n } else {\r\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Initializes the current class by loading MathType script.\r\n */\r\n init() {\r\n if (!ContentManager.isEditorLoaded()) {\r\n this.addEditorAsExternalDependency();\r\n }\r\n }\r\n\r\n /**\r\n * Adds script element to the DOM to include editor externally.\r\n */\r\n addEditorAsExternalDependency() {\r\n const script = document.createElement(\"script\");\r\n script.type = \"text/javascript\";\r\n let editorUrl = Configuration.get(\"editorUrl\");\r\n\r\n // We create an object url for parse url string and work more efficiently.\r\n const anchorElement = document.createElement(\"a\");\r\n\r\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\r\n ContentManager.setProtocolToAnchorElement(anchorElement);\r\n\r\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\r\n\r\n // Load editor URL. We add stats as GET params.\r\n const stats = this.getEditorStats();\r\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\r\n\r\n document.getElementsByTagName(\"head\")[0].appendChild(script);\r\n }\r\n\r\n /**\r\n * Sets the specified url to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\r\n * @param {String} url - URL to set.\r\n */\r\n static setHrefToAnchorElement(anchorElement, url) {\r\n anchorElement.href = url;\r\n }\r\n\r\n /**\r\n * Sets the current protocol to the anchor element.\r\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\r\n */\r\n static setProtocolToAnchorElement(anchorElement) {\r\n // Change to https if necessary.\r\n if (window.location.href.indexOf(\"https://\") === 0) {\r\n // It check if browser is https and configuration is http.\r\n // If this is so, we will replace protocol.\r\n if (anchorElement.protocol === \"http:\") {\r\n anchorElement.protocol = \"https:\";\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the url of the anchor element adding the current port\r\n * if it is needed.\r\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\r\n * @returns {String}\r\n */\r\n static getURLFromAnchorElement(anchorElement) {\r\n // Check protocol and remove port if it's standard.\r\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\r\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\r\n }\r\n\r\n /**\r\n * Returns object with editor stats.\r\n *\r\n * @typedef {Object} EditorStatsObject\r\n * @property {string} editor - Editor name.\r\n * @property {string} mode - Current configuration for formula save mode.\r\n * @property {string} version - Current plugins version.\r\n * @returns {EditorStatsObject}\r\n */\r\n getEditorStats() {\r\n // Editor stats. Use environment property to set it.\r\n const stats = {};\r\n if (\"editor\" in this.environment) {\r\n stats.editor = this.environment.editor;\r\n } else {\r\n stats.editor = \"unknown\";\r\n }\r\n\r\n if (\"mode\" in this.environment) {\r\n stats.mode = this.environment.mode;\r\n } else {\r\n stats.mode = Configuration.get(\"saveMode\");\r\n }\r\n\r\n if (\"version\" in this.environment) {\r\n stats.version = this.environment.version;\r\n } else {\r\n stats.version = Configuration.get(\"version\");\r\n }\r\n\r\n return stats;\r\n }\r\n\r\n /**\r\n * Returns true if device is iOS. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isIOS() {\r\n return (\r\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\r\n // iPad on iOS 13 detection\r\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\r\n );\r\n }\r\n\r\n /**\r\n * Returns true if device is Mobile. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isMobile() {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\r\n }\r\n\r\n /**\r\n * Returns true if editor is loaded. Otherwise, false.\r\n * @returns {Boolean}\r\n */\r\n static isEditorLoaded() {\r\n // To know if editor JavaScript is loaded we need to wait until\r\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\r\n return (\r\n window.com &&\r\n window.com.wiris &&\r\n window.com.wiris.jsEditor &&\r\n window.com.wiris.jsEditor.JsEditor &&\r\n window.com.wiris.jsEditor.JsEditor.newInstance\r\n );\r\n }\r\n\r\n /**\r\n * Sets the {@link ContentManager.editor} initial content.\r\n */\r\n setInitialContent() {\r\n if (!this.isNewElement) {\r\n this.setMathML(this.mathML);\r\n }\r\n }\r\n\r\n /**\r\n * Sets a MathML into {@link ContentManager.editor} instance.\r\n * @param {String} mathml - MathML string.\r\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\r\n * False by default.\r\n */\r\n setMathML(mathml, focusDisabled) {\r\n // By default focus is enabled.\r\n if (typeof focusDisabled === \"undefined\") {\r\n focusDisabled = false;\r\n }\r\n // Using setMathML method is not a change produced by the user but for the API\r\n // so we set to false the contentChange property of editorListener.\r\n this.editor.setMathMLWithCallback(mathml, () => {\r\n this.editorListener.setWaitingForChanges(true);\r\n });\r\n\r\n // We need to wait a little until the callback finish.\r\n setTimeout(() => {\r\n this.editorListener.setIsContentChanged(false);\r\n }, 500);\r\n\r\n // In some scenarios - like closing modal object - editor mustn't be focused.\r\n if (!focusDisabled) {\r\n this.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\r\n * {@link ModalDialog.focus}.\r\n */\r\n onFocus() {\r\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\r\n this.editor.focus();\r\n\r\n // On WordPress integration, the focus gets lost right after setting it.\r\n // To fix this, we enforce another focus some milliseconds after this behaviour.\r\n setTimeout(() => {\r\n this.editor.focus();\r\n }, 100);\r\n }\r\n }\r\n\r\n /**\r\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\r\n * Triggered by {@link ModalDialog.submitAction}.\r\n */\r\n submitAction() {\r\n if (!this.editor.isFormulaEmpty()) {\r\n let mathML = this.editor.getMathMLWithSemantics();\r\n // Add class for custom editors.\r\n if (this.customEditors.getActiveEditor() !== null) {\r\n const { toolbar } = this.customEditors.getActiveEditor();\r\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\r\n } else {\r\n // We need - if exists - the editor name from MathML\r\n // class attribute.\r\n Object.keys(this.customEditors.editors).forEach((key) => {\r\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\r\n });\r\n }\r\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\r\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\r\n } else {\r\n this.integrationModel.updateFormula(null);\r\n }\r\n\r\n this.customEditors.disable();\r\n this.integrationModel.notifyWindowClosed();\r\n\r\n // Set disabled focus to prevent lost focus.\r\n this.setEmptyMathML();\r\n this.customEditors.disable();\r\n }\r\n\r\n /**\r\n * Sets an empty MathML as {@link ContentManager.editor} content.\r\n * This will open the MT/CT editor with the hand mode.\r\n * It adds dir rtl in case of it's activated.\r\n */\r\n setEmptyMathML() {\r\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\r\n const isRTL = this.editor.getEditorModel().isRTL();\r\n\r\n if (isMobile || this.integrationModel.forcedHandMode) {\r\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\r\n const mathML = `[]`;\r\n this.setMathML(mathML, true);\r\n } else {\r\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\r\n const mathML = ``;\r\n this.setMathML(mathML, true);\r\n }\r\n }\r\n\r\n /**\r\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\r\n * - Updates the {@link ContentManager.editor} content\r\n * (with an empty MathML or an existing formula),\r\n * - Updates the {@link ContentManager.editor} toolbar.\r\n * - Recovers the the focus.\r\n */\r\n onOpen() {\r\n if (this.isNewElement) {\r\n this.setEmptyMathML();\r\n } else {\r\n this.setMathML(this.mathML);\r\n }\r\n const toolbar = this.updateToolbar();\r\n this.onFocus();\r\n\r\n if (this.deviceProperties.isIOS) {\r\n const zoom = document.documentElement.clientWidth / window.innerWidth;\r\n\r\n if (zoom !== 1) {\r\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\r\n this.setKeyboardMode();\r\n }\r\n }\r\n\r\n const trigger = this.dbclick ? \"formula\" : \"button\";\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\r\n toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalOpen\", {});\r\n\r\n if (this.integrationModel.forcedHandMode) {\r\n this.hideHandModeButton();\r\n\r\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\r\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\r\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Change Editor in keyboard mode when is loaded\r\n */\r\n setKeyboardMode() {\r\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\r\n if (wrsEditor) {\r\n wrsEditor.classList.remove(\"wrs_handOpen\");\r\n wrsEditor.classList.remove(\"wrs_disablePalette\");\r\n } else {\r\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\r\n }\r\n }\r\n\r\n /**\r\n * Hides the hand <-> keyboard mode switch.\r\n *\r\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\r\n * any change on those classes will make this code stop working properly.\r\n *\r\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\r\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\r\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\r\n */\r\n hideHandModeButton(forced = true) {\r\n if (this.handSwitchHidden) {\r\n return; // hand <-> keyboard button already hidden.\r\n }\r\n\r\n // \"Open hand mode\" button takes a little bit to be available.\r\n // This selector gets the hand <-> keyboard mode switch\r\n const handModeButtonSelector =\r\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\r\n\r\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\r\n // We use an observer to ensure that the button it hidden as soon as it appears.\r\n if (forced) {\r\n const mutationInstance = new MutationObserver((mutations) => {\r\n const handModeButton = document.querySelector(handModeButtonSelector);\r\n if (handModeButton) {\r\n handModeButton.hidden = true;\r\n this.handSwitchHidden = true;\r\n mutationInstance.disconnect();\r\n }\r\n });\r\n mutationInstance.observe(document.body, {\r\n attributes: true,\r\n childList: true,\r\n characterData: true,\r\n subtree: true,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\r\n *\r\n * @param {String} mathml The original KeyBoard MathML\r\n * @param {Object} editor The editor object.\r\n */\r\n async openHandOnKeyboardMathML(mathml, editor) {\r\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\r\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\r\n await new Promise((resolve) => {\r\n editor.setMathMLWithCallback(mathml, resolve);\r\n });\r\n\r\n // We wait until the hand editor object exists.\r\n await this.waitForHand(editor);\r\n\r\n // Logic to get the hand traces and open the formula in hand mode.\r\n // This logic comes from the editor.\r\n const handEditor = editor.hand;\r\n editor.handTemporalMathML = editor.getMathML();\r\n const handCoordinates = editor.editorModel.getHandStrokes();\r\n handEditor.setStrokes(handCoordinates);\r\n handEditor.fitStrokes(true);\r\n editor.openHand();\r\n }\r\n\r\n /**\r\n * Waits until the hand editor object exists.\r\n * @param {Obect} editor The editor object.\r\n */\r\n async waitForHand(editor) {\r\n while (!editor.hand) {\r\n await new Promise((resolve) => setTimeout(resolve, 100));\r\n }\r\n }\r\n\r\n /**\r\n * Sets the correct toolbar depending if exist other custom toolbars\r\n * at the same time (e.g: Chemistry).\r\n */\r\n updateToolbar() {\r\n this.updateTitle(this.modalDialogInstance);\r\n const customEditor = this.customEditors.getActiveEditor();\r\n\r\n let toolbar;\r\n if (customEditor) {\r\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\r\n\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n }\r\n } else {\r\n toolbar = this.getToolbar();\r\n if (this.toolbar == null || this.toolbar !== toolbar) {\r\n this.setToolbar(toolbar);\r\n this.customEditors.disable();\r\n }\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\r\n * sets the custom editor title. Otherwise sets the default title.\r\n */\r\n updateTitle() {\r\n const customEditor = this.customEditors.getActiveEditor();\r\n if (customEditor) {\r\n this.modalDialogInstance.setTitle(customEditor.title);\r\n } else {\r\n this.modalDialogInstance.setTitle(\"MathType\");\r\n }\r\n }\r\n\r\n /**\r\n * Returns the editor toolbar, depending on the configuration local or server side.\r\n * @returns {String} - Toolbar identifier.\r\n */\r\n getToolbar() {\r\n let toolbar = \"general\";\r\n if (\"toolbar\" in this.editorAttributes) {\r\n ({ toolbar } = this.editorAttributes);\r\n }\r\n // TODO: Change global integration variable for integration custom toolbar.\r\n if (toolbar === \"general\") {\r\n // eslint-disable-next-line camelcase\r\n toolbar =\r\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\r\n ? \"general\"\r\n : _wrs_int_wirisProperties.toolbar;\r\n }\r\n\r\n return toolbar;\r\n }\r\n\r\n /**\r\n * Sets the current {@link ContentManager.editor} instance toolbar.\r\n * @param {String} toolbar - The toolbar name.\r\n */\r\n setToolbar(toolbar) {\r\n this.toolbar = toolbar;\r\n this.editor.setParams({ toolbar: this.toolbar });\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setCustomHeaders(headers) {\r\n let headersObj = {};\r\n\r\n // We control that we only get String or Object as the input.\r\n if (typeof headers === \"object\") {\r\n headersObj = headers;\r\n } else if (typeof headers === \"string\") {\r\n headersObj = Util.convertStringToObject(headers);\r\n }\r\n\r\n this.editor.setParams({ customHeaders: headersObj });\r\n return headersObj;\r\n }\r\n\r\n /**\r\n * Returns true if the content of the editor has been changed. The logic of the changes\r\n * is delegated to {@link EditorListener} class.\r\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\r\n */\r\n hasChanges() {\r\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n // Code to detect Esc event.\r\n // There should be only one element with class name 'wrs_pressed' at the same time.\r\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\r\n if (list.length === 0) {\r\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\r\n if (list.length === 0) {\r\n this.modalDialogInstance.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.modalDialogInstance.submitButton) {\r\n // Focus is on OK button.\r\n this.editor.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\r\n // Focus is on minimize button (_).\r\n this.modalDialogInstance.closeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\r\n // Focus on cancel button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n this.modalDialogInstance.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\r\n // Focus is on X button.\r\n this.modalDialogInstance.minimizeDiv.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\r\n // Focus on help button.\r\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\r\n const element = document.querySelector('[title=\"Manual\"]');\r\n element.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n } else {\r\n // There should be only one element with class name 'wrs_formulaDisplay'.\r\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\r\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\r\n // Focus is on formuladisplay.\r\n this.modalDialogInstance.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * A custom editor is MathType editor with a different\r\n * @typedef {Object} CustomEditor\r\n * @property {String} CustomEditor.name - Custom editor name.\r\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\r\n * @property {String} CustomEditor.icon - Custom editor icon.\r\n * @property {String} CustomEditor.confVariable - Configuration property to manage\r\n * the availability of the custom editor.\r\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\r\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\r\n */\r\n\r\nexport default class CustomEditors {\r\n /**\r\n * @classdesc\r\n * This class represents the MathType custom editors manager.\r\n * A custom editor is MathType editor with a custom toolbar.\r\n * This class associates a {@link CustomEditor} to:\r\n * - It's own formulas\r\n * - A custom toolbar\r\n * - An icon to open it from a HTML editor.\r\n * - A tooltip for the icon.\r\n * - A global variable to enable or disable it globally.\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * The custom editors.\r\n * @type {Array.}\r\n */\r\n\r\n this.editors = [];\r\n /**\r\n * The active editor name.\r\n * @type {String}\r\n */\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Adds a {@link CustomEditor} to editors array.\r\n * @param {String} editorName - The editor name.\r\n * @param {CustomEditor} editorParams - The custom editor parameters.\r\n */\r\n addEditor(editorName, editorParams) {\r\n const customEditor = {};\r\n customEditor.name = editorParams.name;\r\n customEditor.toolbar = editorParams.toolbar;\r\n customEditor.icon = editorParams.icon;\r\n customEditor.confVariable = editorParams.confVariable;\r\n customEditor.title = editorParams.title;\r\n customEditor.tooltip = editorParams.tooltip;\r\n this.editors[editorName] = customEditor;\r\n }\r\n\r\n /**\r\n * Enables a {@link CustomEditor}.\r\n * @param {String} customEditorName - The custom editor name.\r\n */\r\n enable(customEditorName) {\r\n this.activeEditor = customEditorName;\r\n }\r\n\r\n /**\r\n * Disables a {@link CustomEditor}.\r\n */\r\n disable() {\r\n this.activeEditor = \"default\";\r\n }\r\n\r\n /**\r\n * Returns the active editor.\r\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\r\n */\r\n getActiveEditor() {\r\n if (this.activeEditor !== \"default\") {\r\n return this.editors[this.activeEditor];\r\n }\r\n return null;\r\n }\r\n}\r\n","/**\r\n * Represents the configuration properties generated from the frontend (JavaScript variables).\r\n * @type {Object}\r\n * @property {string} imageClassName - Default MathType formula image class.\r\n * @property {string} imageClassName - Default MathType CAS image class.\r\n * @ignore\r\n */\r\nconst jsProperties = {\r\n imageCustomEditorName: \"data-custom-editor\",\r\n imageClassName: \"Wirisformula\",\r\n CASClassName: \"Wiriscas\",\r\n};\r\nexport default jsProperties;\r\n","export default class Event {\r\n /**\r\n * @classdesc\r\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\r\n *\r\n * ```js\r\n * let customEvent = new Event();\r\n * customEvent.properties = {};\r\n *\r\n * let listeners = new Listeners();\r\n * listeners.newListener(eventName, callback);\r\n *\r\n * listeners.fire(eventName, customEvent) *\r\n * ```\r\n * @constructs\r\n */\r\n constructor() {\r\n /**\r\n * Indicates if the event should be cancelled.\r\n * @type {Boolean}\r\n */\r\n\r\n this.cancelled = false;\r\n /**\r\n * Indicates if the event should be prevented.\r\n * @type {Boolean}\r\n */\r\n this.defaultPrevented = false;\r\n }\r\n\r\n /**\r\n * Cancels the event.\r\n */\r\n cancel() {\r\n this.cancelled = true;\r\n }\r\n\r\n /**\r\n * Prevents the default action.\r\n */\r\n preventDefault() {\r\n this.defaultPrevented = true;\r\n }\r\n}\r\n","import IntegrationModel from \"./integrationmodel\";\r\n\r\n/**\r\n\r\n */\r\nexport default class PopUpMessage {\r\n /**\r\n * @classdesc\r\n * This class represents a dialog message overlaying a DOM element in order to\r\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\r\n * o canceled. In this last case a callback function should be called.\r\n * @constructs\r\n * @param {Object} popupMessageAttributes - Object containing popup properties.\r\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\r\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\r\n * methods for close and cancel actions.\r\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\r\n */\r\n constructor(popupMessageAttributes) {\r\n /**\r\n * Element to be overlaid when the popup appears.\r\n */\r\n this.overlayElement = popupMessageAttributes.overlayElement;\r\n\r\n this.callbacks = popupMessageAttributes.callbacks;\r\n\r\n /**\r\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\r\n */\r\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\r\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\r\n\r\n /**\r\n * HTMLElement to display the popup message, close button and cancel button.\r\n */\r\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n this.message.id = \"wrs_popupmessage\";\r\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\r\n this.message.setAttribute(\"role\", \"dialog\");\r\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\r\n const paragraph = document.createElement(\"p\");\r\n const text = document.createTextNode(popupMessageAttributes.strings.message);\r\n paragraph.appendChild(text);\r\n paragraph.id = \"description_txt\";\r\n this.message.appendChild(paragraph);\r\n\r\n /**\r\n * HTML element overlaying the overlayElement.\r\n */\r\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\r\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\r\n // We create a overlay that close popup message on click in there\r\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n /**\r\n * HTML element containing cancel and close buttons.\r\n */\r\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\r\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\r\n this.buttonArea.id = \"wrs_popup_button_area\";\r\n\r\n // Close button arguments.\r\n const buttonSubmitArguments = {\r\n class: \"wrs_button_accept\",\r\n innerHTML: popupMessageAttributes.strings.submitString,\r\n id: \"wrs_popup_accept_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-close-button\",\r\n };\r\n\r\n /**\r\n * Close button arguments.\r\n */\r\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\r\n this.buttonArea.appendChild(this.closeButton);\r\n\r\n // Cancel button arguments.\r\n const buttonCancelArguments = {\r\n class: \"wrs_button_cancel\",\r\n innerHTML: popupMessageAttributes.strings.cancelString,\r\n id: \"wrs_popup_cancel_button\",\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\r\n };\r\n\r\n /**\r\n * Cancel button.\r\n */\r\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\r\n this.buttonArea.appendChild(this.cancelButton);\r\n }\r\n\r\n /**\r\n * This method create a button with arguments and return button dom object\r\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\r\n * @param {String} parameters.id - Button id.\r\n * @param {String} parameters.class - Button class name.\r\n * @param {String} parameters.innerHTML - Button innerHTML text.\r\n * @param {Object} callback- Callback method to call on click event.\r\n * @returns {HTMLElement} HTML button.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createButton(parameters, callback) {\r\n let element = {};\r\n element = document.createElement(\"button\");\r\n element.setAttribute(\"id\", parameters.id);\r\n element.setAttribute(\"class\", parameters.class);\r\n element.innerHTML = parameters.innerHTML;\r\n element.addEventListener(\"click\", callback);\r\n if (parameters[\"data-testid\"]) {\r\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\r\n }\r\n\r\n return element;\r\n }\r\n\r\n /**\r\n * Shows the popupmessage containing a message, and two buttons\r\n * to cancel the action or close the modal dialog.\r\n */\r\n show() {\r\n if (this.overlayWrapper.style.display !== \"block\") {\r\n // Clear focus with blur for prevent press any key.\r\n document.activeElement.blur();\r\n this.overlayWrapper.style.display = \"block\";\r\n this.closeButton.focus();\r\n } else {\r\n this.overlayWrapper.style.display = \"none\";\r\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\r\n }\r\n }\r\n\r\n /**\r\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\r\n * A callback method is called (if defined). For example a method to focus the overlaid element.\r\n */\r\n cancelAction() {\r\n this.overlayWrapper.style.display = \"none\";\r\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\r\n this.callbacks.cancelCallback();\r\n // Set temporal image to null to prevent loading\r\n // an existent formula when starting one from scratch. Make focus come back too.\r\n // IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n }\r\n\r\n /**\r\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\r\n * For example to close the overlaid element.\r\n */\r\n closeAction() {\r\n this.cancelAction();\r\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\r\n this.callbacks.closeCallback();\r\n }\r\n IntegrationModel.setActionsOnCancelButtons();\r\n }\r\n\r\n /**\r\n * Handle keyboard events detected in modal when elements of this class intervene.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Code to detect Esc event.\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n this.cancelAction();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.closeButton) {\r\n this.cancelButton.focus();\r\n } else {\r\n this.closeButton.focus();\r\n }\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * This module provides protection against external focus management scripts\r\n * that might interfere with the MathType editor modal.\r\n */\r\n\r\n/**\r\n * focusProtection function creates and returns methods to prevent external scripts from\r\n * interfering with the focus of the MathType modal dialog.\r\n *\r\n * @returns {Object} An object with protect and unprotect methods.\r\n */\r\nconst focusProtection = () => {\r\n /**\r\n * Initialize focus protection on the given modal element.\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to protect\r\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\r\n * @param {HTMLElement} editorElement - The editor element inside the modal\r\n */\r\n const protect = (modalElement, overlayElement, editorElement) => {\r\n if (!modalElement || !overlayElement || !editorElement) {\r\n console.warn(\"FocusProtection: Missing required elements\");\r\n return;\r\n }\r\n\r\n // Apply the focus protection\r\n overrideFocusBehavior(modalElement, editorElement);\r\n };\r\n\r\n /**\r\n * Apply focus protection by overriding focus-related methods\r\n *\r\n * @param {HTMLElement} modalElement - The modal element\r\n * @param {HTMLElement} editorElement - The editor element to keep focused\r\n * @private\r\n */\r\n const overrideFocusBehavior = (modalElement, editorElement) => {\r\n // Store original focus methods to be able to restore them\r\n const originalElementFocus = HTMLElement.prototype.focus;\r\n const originalElementBlur = HTMLElement.prototype.blur;\r\n\r\n // Override the focus method for all elements\r\n HTMLElement.prototype.focus = function (...args) {\r\n // If the modal is open and this is not part of the modal, prevent focus\r\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\r\n // If some external script is trying to focus another element, prevent it\r\n // and restore focus to the editor\r\n if (editorElement) {\r\n // Use the original focus method to avoid infinite recursion\r\n originalElementFocus.apply(editorElement, args);\r\n }\r\n return;\r\n }\r\n\r\n // Otherwise, allow the focus to happen\r\n originalElementFocus.apply(this, args);\r\n };\r\n\r\n // Store the methods to remove them when the modal is closed\r\n modalElement.originalElementFocus = originalElementFocus;\r\n modalElement.originalElementBlur = originalElementBlur;\r\n };\r\n\r\n /**\r\n * Remove focus protection from the modal\r\n *\r\n * @param {HTMLElement} modalElement - The modal element to unprotect\r\n */\r\n const unprotect = (modalElement) => {\r\n if (!modalElement) {\r\n return;\r\n }\r\n\r\n // Restore original focus methods\r\n if (modalElement.originalElementFocus) {\r\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\r\n delete modalElement.originalElementFocus;\r\n }\r\n\r\n if (modalElement.originalElementBlur) {\r\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\r\n delete modalElement.originalElementBlur;\r\n }\r\n };\r\n\r\n return {\r\n protect,\r\n unprotect,\r\n };\r\n};\r\n\r\nexport default focusProtection;\r\n","// eslint-disable-next-line max-classes-per-file\r\nimport PopUpMessage from \"./popupmessage\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport Listeners from \"./listeners\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Telemeter from \"./telemeter\";\r\nimport IntegrationModel from \"./integrationmodel\";\r\nimport Core from \"./core.src\";\r\nimport focusProtection from \"./focusprotection\";\r\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\r\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\r\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\r\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\r\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\r\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\r\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\r\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\r\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\r\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\r\nconst { unprotect, protect } = focusProtection();\r\n\r\n/**\r\n * @typedef {Object} DeviceProperties\r\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\r\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\r\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\r\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\r\n * False otherwise.\r\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\r\n * False otherwise.\r\n */\r\n\r\nexport default class ModalDialog {\r\n /**\r\n * @classdesc\r\n * This class represents a modal dialog. The modal dialog admits\r\n * a {@link ContentManager} instance to manage the content of the dialog.\r\n * @constructs\r\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\r\n */\r\n constructor(modalDialogAttributes) {\r\n this.attributes = modalDialogAttributes;\r\n\r\n // Metrics.\r\n const ua = navigator.userAgent.toLowerCase();\r\n const isAndroid = ua.indexOf(\"android\") > -1;\r\n const isIOS = ContentManager.isIOS();\r\n this.iosSoftkeyboardOpened = false;\r\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\r\n this.iosDivHeight = `auto`;\r\n\r\n const deviceWidth = window.outerWidth;\r\n const deviceHeight = window.outerHeight;\r\n\r\n const landscape = deviceWidth > deviceHeight;\r\n const portrait = deviceWidth < deviceHeight;\r\n\r\n // TODO: Detect isMobile without using editor metrics.\r\n const isLandscape = landscape && this.attributes.height > deviceHeight;\r\n const isPortrait = portrait && this.attributes.width > deviceWidth;\r\n const isMobile = ContentManager.isMobile();\r\n\r\n // Obtain number of current instance.\r\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\r\n\r\n // Device object properties.\r\n\r\n /**\r\n * @type {DeviceProperties}\r\n */\r\n this.deviceProperties = {\r\n orientation: landscape ? \"landscape\" : \"portrait\",\r\n isAndroid,\r\n isIOS,\r\n isMobile,\r\n isDesktop: !isMobile && !isIOS && !isAndroid,\r\n };\r\n\r\n this.properties = {\r\n created: false,\r\n state: \"\",\r\n previousState: \"\",\r\n position: { bottom: 0, right: 10 },\r\n size: { height: 338, width: 580 },\r\n };\r\n\r\n /**\r\n * Object to keep website's style before change it on lock scroll for mobile devices.\r\n * @type {Object}\r\n * @property {String} bodyStylePosition - Previous body style position.\r\n * @property {String} bodyStyleOverflow - Previous body style overflow.\r\n * @property {String} htmlStyleOverflow - Previous body style overflow.\r\n * @property {String} windowScrollX - Previous window's scroll Y.\r\n * @property {String} windowScrollY - Previous window's scroll X.\r\n */\r\n this.websiteBeforeLockParameters = null;\r\n\r\n let attributes = {};\r\n attributes.class = \"wrs_modal_overlay\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.overlay = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title_bar\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.titleBar = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_title\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.title = Util.createElement(\"div\", attributes);\r\n this.title.innerHTML = \"offline\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_close_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"close\");\r\n attributes.style = {};\r\n this.closeDiv = Util.createElement(\"a\", attributes);\r\n this.closeDiv.setAttribute(\"role\", \"button\");\r\n this.closeDiv.setAttribute(\"tabindex\", 3);\r\n // Apply styles and events after the creation as createElement doesn't process them correctly\r\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\r\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\r\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\r\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\r\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\r\n // To identifiy the element in automated testing\r\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_stack_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"exit_fullscreen\");\r\n this.stackDiv = Util.createElement(\"a\", attributes);\r\n this.stackDiv.setAttribute(\"role\", \"button\");\r\n this.stackDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\r\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\r\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\r\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\r\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\r\n // To identifiy the element in automated testing\r\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_maximize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"fullscreen\");\r\n this.maximizeDiv = Util.createElement(\"a\", attributes);\r\n this.maximizeDiv.setAttribute(\"role\", \"button\");\r\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\r\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\r\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\r\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\r\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\r\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\r\n // To identifiy the element in automated testing\r\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_minimize_button\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.title = StringManager.get(\"minimize\");\r\n this.minimizeDiv = Util.createElement(\"a\", attributes);\r\n this.minimizeDiv.setAttribute(\"role\", \"button\");\r\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\r\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\r\n // To identify the element in automated testing\r\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_dialogContainer\";\r\n attributes.id = this.getElementId(attributes.class);\r\n attributes.role = \"dialog\";\r\n this.container = Util.createElement(\"div\", attributes);\r\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_wrapper\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.wrapper = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_content_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.contentContainer = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_controls\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.controls = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_buttons_container\";\r\n attributes.id = this.getElementId(attributes.class);\r\n this.buttonContainer = Util.createElement(\"div\", attributes);\r\n\r\n // Buttons: all button must be created using createSubmitButton method.\r\n this.submitButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_accept\"),\r\n class: \"wrs_modal_button_accept\",\r\n innerHTML: StringManager.get(\"accept\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-insert-button\",\r\n },\r\n this.submitAction.bind(this),\r\n );\r\n\r\n this.cancelButton = this.createSubmitButton(\r\n {\r\n id: this.getElementId(\"wrs_modal_button_cancel\"),\r\n class: \"wrs_modal_button_cancel\",\r\n innerHTML: StringManager.get(\"cancel\"),\r\n // To identifiy the element in automated testing\r\n \"data-testid\": \"mtcteditor-cancel-button\",\r\n },\r\n this.cancelAction.bind(this),\r\n );\r\n\r\n this.contentManager = null;\r\n\r\n // Overlay popup.\r\n const popupStrings = {\r\n cancelString: StringManager.get(\"cancel\"),\r\n submitString: StringManager.get(\"close\"),\r\n message: StringManager.get(\"close_modal_warning\"),\r\n };\r\n\r\n const callbacks = {\r\n closeCallback: () => {\r\n this.close(\"mtc_close\");\r\n },\r\n cancelCallback: () => {\r\n this.focus();\r\n },\r\n };\r\n\r\n const popupupProperties = {\r\n overlayElement: this.container,\r\n callbacks,\r\n strings: popupStrings,\r\n };\r\n\r\n this.popup = new PopUpMessage(popupupProperties);\r\n\r\n /**\r\n * Indicates if directionality of the modal dialog is RTL. false by default.\r\n * @type {Boolean}\r\n */\r\n this.rtl = false;\r\n if (\"rtl\" in this.attributes) {\r\n this.rtl = this.attributes.rtl;\r\n }\r\n\r\n // Event handlers need modal instance context.\r\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\r\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\r\n }\r\n\r\n /**\r\n * This method sets an ContentManager instance to ModalDialog. ContentManager\r\n * manages the logic of ModalDialog content: submit, update, close and changes.\r\n * @param {ContentManager} contentManager - ContentManager instance.\r\n */\r\n setContentManager(contentManager) {\r\n this.contentManager = contentManager;\r\n }\r\n\r\n /**\r\n * Returns the modal contentElement object.\r\n * @returns {ContentManager} the instance of the ContentManager class.\r\n */\r\n getContentManager() {\r\n return this.contentManager;\r\n }\r\n\r\n /**\r\n * This method is called when the modal object has been submitted. Calls\r\n * contentElement submitAction method - if exists - and closes the modal\r\n * object. No logic about the content should be placed here,\r\n * contentElement.submitAction is the responsible of the content logic.\r\n */\r\n async submitAction() {\r\n if (typeof this.contentManager.submitAction !== \"undefined\") {\r\n this.contentManager.submitAction();\r\n }\r\n\r\n await this.close(\"mtc_insert\");\r\n }\r\n\r\n /**\r\n * Performs the cancel action.\r\n * If there are no changes in the content, it closes the modal.\r\n * Otherwise, it shows a pop-up message to confirm the cancel action.\r\n * @returns {Promise} - A promise that resolves when the modal is closed.\r\n */\r\n async cancelAction() {\r\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\r\n IntegrationModel.setActionsOnCancelButtons();\r\n await this.close(\"mtc_close\");\r\n } else {\r\n this.showPopUpMessage();\r\n }\r\n }\r\n\r\n /**\r\n * Returns a button element.\r\n * @param {Object} properties - Input button properties.\r\n * @param {String} properties.class - Input button class.\r\n * @param {String} properties.innerHTML - Input button innerHTML.\r\n * @param {Object} callback - Callback function associated to click event.\r\n * @returns {HTMLButtonElement} The button element.\r\n *\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n createSubmitButton(properties, callback) {\r\n class SubmitButton {\r\n constructor() {\r\n this.element = document.createElement(\"button\");\r\n this.element.id = properties.id;\r\n this.element.className = properties.class;\r\n this.element.innerHTML = properties.innerHTML;\r\n this.element.dataset.testid = properties[\"data-testid\"];\r\n Util.addEvent(this.element, \"click\", callback);\r\n }\r\n\r\n getElement() {\r\n return this.element;\r\n }\r\n }\r\n return new SubmitButton(properties, callback).getElement();\r\n }\r\n\r\n /**\r\n * Creates the modal window object inserting a contentElement object.\r\n */\r\n create() {\r\n /* Modal Window Structure\r\n _____________________________________________________________________________________\r\n |wrs_modal_dialog_Container |\r\n | _________________________________________________________________________________ |\r\n | |title_bar minimize_button stack_button close_button | |\r\n | |_______________________________________________________________________________| |\r\n | |wrapper | |\r\n | | _____________________________________________________________________________ | |\r\n | | |content | | |\r\n | | | | | |\r\n | | | | | |\r\n | | |___________________________________________________________________________| | |\r\n | | _____________________________________________________________________________ | |\r\n | | |controls | | |\r\n | | | ___________________________________ | | |\r\n | | | |buttonContainer | | | |\r\n | | | | _______________________________ | | | |\r\n | | | | |button_accept | button_cancel| | | | |\r\n | | | |_|_____________ |______________|_| | | |\r\n | | |___________________________________________________________________________| | |\r\n | |_______________________________________________________________________________| |\r\n |___________________________________________________________________________________| */\r\n\r\n this.titleBar.appendChild(this.closeDiv);\r\n this.titleBar.appendChild(this.stackDiv);\r\n this.titleBar.appendChild(this.maximizeDiv);\r\n this.titleBar.appendChild(this.minimizeDiv);\r\n this.titleBar.appendChild(this.title);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n this.container.appendChild(this.titleBar);\r\n }\r\n\r\n this.wrapper.appendChild(this.contentContainer);\r\n this.wrapper.appendChild(this.controls);\r\n\r\n this.controls.appendChild(this.buttonContainer);\r\n\r\n this.buttonContainer.appendChild(this.submitButton);\r\n this.buttonContainer.appendChild(this.cancelButton);\r\n\r\n this.container.appendChild(this.wrapper);\r\n\r\n // Check if browser has scrollBar before modal has modified.\r\n this.recalculateScrollBar();\r\n\r\n document.body.appendChild(this.container);\r\n document.body.appendChild(this.overlay);\r\n\r\n if (this.deviceProperties.isDesktop) {\r\n // Desktop.\r\n this.createModalWindowDesktop();\r\n this.createResizeButtons();\r\n\r\n this.addListeners();\r\n // Maximize window only when the configuration is set and the device is not iOS or Android.\r\n if (Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n } else if (this.deviceProperties.isAndroid) {\r\n this.createModalWindowAndroid();\r\n } else if (this.deviceProperties.isIOS) {\r\n this.createModalWindowIos();\r\n }\r\n\r\n if (this.contentManager != null) {\r\n this.contentManager.insert(this);\r\n }\r\n\r\n this.properties.open = true;\r\n this.properties.created = true;\r\n\r\n // Checks language directionality.\r\n if (this.isRTL()) {\r\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\r\n this.container.className += \" wrs_modal_rtl\";\r\n }\r\n }\r\n\r\n /**\r\n * Creates a button in the modal object to resize it.\r\n */\r\n createResizeButtons() {\r\n // This is a definition of Resize Button Bottom-Right.\r\n this.resizerBR = document.createElement(\"div\");\r\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\r\n this.resizerBR.innerHTML = \"โ—ข\";\r\n // To identifiy the element in automated testing\r\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\r\n // This is a definition of Resize Button Top-Left.\r\n this.resizerTL = document.createElement(\"div\");\r\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\r\n // To identifiy the element in automated testing\r\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\r\n // Append resize buttons to modal.\r\n this.container.appendChild(this.resizerBR);\r\n this.titleBar.appendChild(this.resizerTL);\r\n // Add events to resize on click and drag.\r\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\r\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\r\n }\r\n\r\n /**\r\n * Initialize variables for Bottom-Right resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateBR(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, false);\r\n }\r\n\r\n /**\r\n * Initialize variables for Top-Left resize button\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n activateResizeStateTL(mouseEvent) {\r\n this.initializeResizeProperties(mouseEvent, true);\r\n }\r\n\r\n /**\r\n * Common method to initialize variables at resize.\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n */\r\n initializeResizeProperties(mouseEvent, leftOption) {\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n this.resizeDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Save Initial state of modal to compare on drag and obtain the difference.\r\n this.initialWidth = parseInt(this.container.style.width, 10);\r\n this.initialHeight = parseInt(this.container.style.height, 10);\r\n if (!leftOption) {\r\n this.initialRight = parseInt(this.container.style.right, 10);\r\n this.initialBottom = parseInt(this.container.style.bottom, 10);\r\n } else {\r\n this.leftScale = true;\r\n }\r\n if (!this.initialRight) {\r\n this.initialRight = 0;\r\n }\r\n if (!this.initialBottom) {\r\n this.initialBottom = 0;\r\n }\r\n // Disable mouse events on editor when we start to drag modal.\r\n document.body.style[\"user-select\"] = \"none\";\r\n }\r\n\r\n /**\r\n * This method opens the modal window, restoring the previous state, position and metrics,\r\n * if exists. By default the modal object opens in stack mode.\r\n */\r\n open() {\r\n // Removing close class.\r\n this.removeClass(\"wrs_closed\");\r\n // Hiding keyboard for mobile devices.\r\n const { isIOS } = this.deviceProperties;\r\n const { isAndroid } = this.deviceProperties;\r\n const { isMobile } = this.deviceProperties;\r\n if (isIOS || isAndroid || isMobile) {\r\n // Restore scale to 1.\r\n this.restoreWebsiteScale();\r\n this.lockWebsiteScroll();\r\n // Due to editor wait we need to wait until editor focus.\r\n setTimeout(() => {\r\n this.hideKeyboard();\r\n }, 400);\r\n }\r\n\r\n // New modal window. He need to create the whole object.\r\n if (!this.properties.created) {\r\n this.create();\r\n } else {\r\n // Previous state closed. Open method can be called even the previous state is open,\r\n // for example updating the content of the modal object.\r\n if (!this.properties.open) {\r\n this.properties.open = true;\r\n\r\n // Restoring the previous open state: if the modal object has been closed\r\n // re-open it should preserve the state and the metrics.\r\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\r\n this.restoreState();\r\n }\r\n }\r\n\r\n // Maximize window only when the configuration is set and the device is not iOs or Android.\r\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\r\n this.maximize();\r\n }\r\n\r\n // In iOS we need to recalculate the size of the modal object because\r\n // iOS keyboard is a float div which can overlay the modal object.\r\n if (this.deviceProperties.isIOS) {\r\n this.iosSoftkeyboardOpened = false;\r\n }\r\n }\r\n\r\n if (!ContentManager.isEditorLoaded()) {\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.displayEditor();\r\n });\r\n this.contentManager.addListener(listener);\r\n } else {\r\n this.displayEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Prepares and displays the editor in the modal.\r\n *\r\n * This method is responsible for displaying the MathType editor inside the modal container.\r\n *\r\n * For Moodle environments, it applies focus protection to prevent external scripts\r\n * from hijacking focus away from the editor while it's open. This is particularly\r\n * important in Moodle which may have its own focus management scripts.\r\n * @returns {void}\r\n */\r\n displayEditor() {\r\n if (this.contentManager.integrationModel.isMoodle) {\r\n protect(this.container, this.overlay, this.contentContainer);\r\n }\r\n\r\n // Initialize and open the editor using the contentManager.\r\n this.contentManager.onOpen(this);\r\n }\r\n\r\n /**\r\n * Closes the modal.\r\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\r\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\r\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\r\n * @returns {Promise} A promise that resolves when the modal is closed.\r\n */\r\n async close(trigger) {\r\n // Remove focus protection before closing\r\n unprotect(this.container);\r\n\r\n this.removeClass(\"wrs_maximized\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.removeClass(\"wrs_stack\");\r\n this.addClass(\"wrs_closed\");\r\n this.saveModalProperties();\r\n this.unlockWebsiteScroll();\r\n this.properties.open = false;\r\n\r\n if (trigger) {\r\n try {\r\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\r\n toolbar: this.contentManager.toolbar,\r\n trigger,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\r\n }\r\n }\r\n\r\n Core.globalListeners.fire(\"onModalClose\", {});\r\n }\r\n\r\n /**\r\n * Closes modal window and destroys the object.\r\n */\r\n destroy() {\r\n // Remove focus protection before destroying\r\n unprotect(this.container);\r\n\r\n // Close modal window.\r\n this.close();\r\n // Remove listeners and destroy the object.\r\n this.removeListeners();\r\n this.overlay.remove();\r\n this.container.remove();\r\n // Reset properties to allow open again.\r\n this.properties.created = false;\r\n }\r\n\r\n /**\r\n * Sets the website scale to one.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n restoreWebsiteScale() {\r\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\r\n // Let the equal symbols in order to search and make meta's final content.\r\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\r\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\r\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\r\n const contentAttr = viewportelement.getAttribute(\"content\");\r\n // If it exists, we need to maintain old values and put our values.\r\n if (contentAttr) {\r\n const attrArray = contentAttr.split(\",\");\r\n let finalContentMeta = \"\";\r\n const oldAttrs = [];\r\n for (let i = 0; i < attrArray.length; i += 1) {\r\n let isAttrToUpdate = false;\r\n let j = 0;\r\n while (!isAttrToUpdate && j < contentAttrs.length) {\r\n if (attrArray[i].indexOf(contentAttrs[j])) {\r\n isAttrToUpdate = true;\r\n }\r\n j += 1;\r\n }\r\n\r\n if (!isAttrToUpdate) {\r\n oldAttrs.push(attrArray[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < contentAttrs.length; i += 1) {\r\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\r\n finalContentMeta += i === 0 ? attr : `,${attr}`;\r\n }\r\n\r\n for (let i = 0; i < oldAttrs.length; i += 1) {\r\n finalContentMeta += `,${oldAttrs[i]}`;\r\n }\r\n viewportelement.setAttribute(\"content\", finalContentMeta);\r\n // It needs to set to empty because setAttribute refresh only when attribute is different.\r\n viewportelement.setAttribute(\"content\", \"\");\r\n viewportelement.setAttribute(\"content\", contentAttr);\r\n } else {\r\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\r\n viewportelement.removeAttribute(\"content\");\r\n }\r\n };\r\n\r\n if (!viewportmeta) {\r\n viewportmeta = document.createElement(\"meta\");\r\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n viewportmeta.remove();\r\n } else {\r\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\r\n }\r\n }\r\n\r\n /**\r\n * Locks website scroll for mobile devices.\r\n */\r\n lockWebsiteScroll() {\r\n this.websiteBeforeLockParameters = {\r\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\r\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\r\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\r\n windowScrollX: window.scrollX,\r\n windowScrollY: window.scrollY,\r\n };\r\n }\r\n\r\n /**\r\n * Unlocks website scroll for mobile devices.\r\n */\r\n unlockWebsiteScroll() {\r\n if (this.websiteBeforeLockParameters) {\r\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\r\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\r\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\r\n const { windowScrollX } = this.websiteBeforeLockParameters;\r\n const { windowScrollY } = this.websiteBeforeLockParameters;\r\n window.scrollTo(windowScrollX, windowScrollY);\r\n this.websiteBeforeLockParameters = null;\r\n }\r\n }\r\n\r\n /**\r\n * Util function to known if browser is IE11.\r\n * @returns {Boolean} true if the browser is IE11. false otherwise.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n isIE11() {\r\n if (\r\n navigator.userAgent.search(\"Msie/\") >= 0 ||\r\n navigator.userAgent.search(\"Trident/\") >= 0 ||\r\n navigator.userAgent.search(\"Edge/\") >= 0\r\n ) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns if the current language type is RTL.\r\n * @return {Boolean} true if current language is RTL. false otherwise.\r\n */\r\n isRTL() {\r\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\r\n return true;\r\n }\r\n return this.rtl;\r\n }\r\n\r\n /**\r\n * Adds a class to all modal ModalDialog DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n addClass(className) {\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.titleBar, className);\r\n Util.addClass(this.overlay, className);\r\n Util.addClass(this.container, className);\r\n Util.addClass(this.contentContainer, className);\r\n Util.addClass(this.stackDiv, className);\r\n Util.addClass(this.minimizeDiv, className);\r\n Util.addClass(this.maximizeDiv, className);\r\n Util.addClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Remove a class from all modal DOM elements.\r\n * @param {String} className - Class name.\r\n */\r\n removeClass(className) {\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.titleBar, className);\r\n Util.removeClass(this.overlay, className);\r\n Util.removeClass(this.container, className);\r\n Util.removeClass(this.contentContainer, className);\r\n Util.removeClass(this.stackDiv, className);\r\n Util.removeClass(this.minimizeDiv, className);\r\n Util.removeClass(this.maximizeDiv, className);\r\n Util.removeClass(this.wrapper, className);\r\n }\r\n\r\n /**\r\n * Create modal dialog for desktop.\r\n */\r\n createModalWindowDesktop() {\r\n this.addClass(\"wrs_modal_desktop\");\r\n this.stack();\r\n }\r\n\r\n /**\r\n * Create modal dialog for non android devices.\r\n */\r\n createModalWindowAndroid() {\r\n this.addClass(\"wrs_modal_android\");\r\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Create modal dialog for iOS devices.\r\n */\r\n createModalWindowIos() {\r\n this.addClass(\"wrs_modal_ios\");\r\n // Refresh the size when the orientation is changed.\r\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\r\n }\r\n\r\n /**\r\n * Restore previous state, position and size of previous stacked modal dialog.\r\n */\r\n restoreState() {\r\n if (this.properties.state === \"maximized\") {\r\n // Reset states for prevent return to stack state.\r\n this.maximize();\r\n } else if (this.properties.state === \"minimized\") {\r\n // Reset states for prevent return to stack state.\r\n this.properties.state = this.properties.previousState;\r\n this.properties.previousState = \"\";\r\n this.minimize();\r\n } else {\r\n this.stack();\r\n }\r\n }\r\n\r\n /**\r\n * Stacks the modal object.\r\n */\r\n stack() {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"stack\";\r\n this.removeClass(\"wrs_maximized\");\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n this.addClass(\"wrs_stack\");\r\n\r\n // Change maximize/minimize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n this.restoreModalProperties();\r\n\r\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\r\n this.setResizeButtonsVisibility();\r\n }\r\n\r\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Minimizes the modal object.\r\n */\r\n minimize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n this.title.style.cursor = \"pointer\";\r\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\r\n this.stack();\r\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n // Setting css to prevent important tag into css style.\r\n this.container.style.height = \"30px\";\r\n this.container.style.width = \"250px\";\r\n this.container.style.bottom = \"0px\";\r\n this.container.style.right = \"10px\";\r\n\r\n this.removeListeners();\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"minimized\";\r\n this.setResizeButtonsVisibility();\r\n this.minimizeDiv.title = StringManager.get(\"maximize\");\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.removeClass(\"wrs_stack\");\r\n } else {\r\n this.removeClass(\"wrs_maximized\");\r\n }\r\n this.addClass(\"wrs_minimized\");\r\n\r\n // Change minimize icon to maximize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n }\r\n }\r\n\r\n /**\r\n * Maximizes the modal object.\r\n */\r\n maximize() {\r\n // Saving width, height, top and bottom parameters to restore when opening.\r\n this.saveModalProperties();\r\n if (this.properties.state !== \"maximized\") {\r\n this.properties.previousState = this.properties.state;\r\n this.properties.state = \"maximized\";\r\n }\r\n // Don't permit resize on maximize mode.\r\n this.setResizeButtonsVisibility();\r\n\r\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\r\n this.minimizeDiv.title = StringManager.get(\"minimize\");\r\n this.removeClass(\"wrs_minimized\");\r\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\r\n this.container.style.left = null;\r\n this.container.style.top = null;\r\n this.removeClass(\"wrs_stack\");\r\n }\r\n\r\n this.addClass(\"wrs_maximized\");\r\n\r\n // Change maximize icon to minimize icon\r\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\r\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\r\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\r\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\r\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\r\n\r\n // Set size to 80% screen with a max size.\r\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\r\n if (this.container.clientHeight > 700) {\r\n this.container.style.height = \"700px\";\r\n }\r\n if (this.container.clientWidth > 1200) {\r\n this.container.style.width = \"1200px\";\r\n }\r\n\r\n // Setting modal position in center on screen.\r\n const { innerHeight } = window;\r\n const { innerWidth } = window;\r\n const { offsetHeight } = this.container;\r\n const { offsetWidth } = this.container;\r\n const bottom = innerHeight / 2 - offsetHeight / 2;\r\n const right = innerWidth / 2 - offsetWidth / 2;\r\n\r\n this.setPosition(bottom, right);\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n this.recalculateSize();\r\n this.focus();\r\n }\r\n\r\n /**\r\n * Expand again the modal object from a minimized state.\r\n */\r\n reExpand() {\r\n if (this.properties.state === \"minimized\") {\r\n if (this.properties.previousState === \"maximized\") {\r\n this.maximize();\r\n } else {\r\n this.stack();\r\n }\r\n this.title.style.cursor = \"\";\r\n }\r\n }\r\n\r\n /**\r\n * Sets modal size.\r\n * @param {Number} height - Height of the ModalDialog\r\n * @param {Number} width - Width of the ModalDialog.\r\n */\r\n setSize(height, width) {\r\n this.container.style.height = `${height}px`;\r\n this.container.style.width = `${width}px`;\r\n this.recalculateSize();\r\n }\r\n\r\n /**\r\n * Sets modal position using bottom and right style attributes.\r\n * @param {number} bottom - bottom attribute.\r\n * @param {number} right - right attribute.\r\n */\r\n setPosition(bottom, right) {\r\n this.container.style.bottom = `${bottom}px`;\r\n this.container.style.right = `${right}px`;\r\n }\r\n\r\n /**\r\n * Saves position and size parameters of and open ModalDialog. This attributes\r\n * are needed to restore it on re-open.\r\n */\r\n saveModalProperties() {\r\n // Saving values of modal only when modal is in stack state.\r\n if (this.properties.state === \"stack\") {\r\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\r\n this.properties.position.right = parseInt(this.container.style.right, 10);\r\n this.properties.size.width = parseInt(this.container.style.width, 10);\r\n this.properties.size.height = parseInt(this.container.style.height, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Restore ModalDialog position and size parameters.\r\n */\r\n restoreModalProperties() {\r\n if (this.properties.state === \"stack\") {\r\n // Restoring Bottom and Right values from last modal.\r\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\r\n // Restoring Height and Left values from last modal.\r\n this.setSize(this.properties.size.height, this.properties.size.width);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the modal dialog initial size.\r\n */\r\n recalculateSize() {\r\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\r\n }\r\n\r\n /**\r\n * Enable or disable visibility of resize buttons in modal window depend on state.\r\n */\r\n setResizeButtonsVisibility() {\r\n if (this.properties.state === \"stack\") {\r\n this.resizerTL.style.visibility = \"visible\";\r\n this.resizerBR.style.visibility = \"visible\";\r\n } else {\r\n this.resizerTL.style.visibility = \"hidden\";\r\n this.resizerBR.style.visibility = \"hidden\";\r\n }\r\n }\r\n\r\n /**\r\n * Makes an object draggable adding mouse and touch events.\r\n */\r\n addListeners() {\r\n // Button events (maximize, minimize, stack and close).\r\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\r\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\r\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\r\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\r\n this.maximizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n }\r\n },\r\n true,\r\n );\r\n this.stackDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.minimizeDiv.addEventListener(\r\n \"keypress\",\r\n (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n },\r\n true,\r\n );\r\n this.closeDiv.addEventListener(\"keypress\", (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\r\n // Handle enter and space.\r\n e.target.click();\r\n e.preventDefault();\r\n }\r\n });\r\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\r\n\r\n // Overlay events (close).\r\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\r\n\r\n // Mouse events.\r\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\r\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\r\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\r\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\r\n // Key events.\r\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\r\n }\r\n\r\n /**\r\n * Removes draggable events from an object.\r\n */\r\n removeListeners() {\r\n // Mouse events.\r\n Util.removeEvent(window, \"mousedown\", this.startDrag);\r\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\r\n Util.removeEvent(window, \"mousemove\", this.drag);\r\n Util.removeEvent(window, \"resize\", this.onWindowResize);\r\n // Key events.\r\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\r\n }\r\n\r\n /**\r\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\r\n * @param {MouseEvent} mouseEvent - Mouse event.\r\n * @return {Object} With the X and Y coordinates.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n eventClient(mouseEvent) {\r\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\r\n const client = {\r\n X: mouseEvent.changedTouches[0].clientX,\r\n Y: mouseEvent.changedTouches[0].clientY,\r\n };\r\n return client;\r\n }\r\n const client = {\r\n X: mouseEvent.clientX,\r\n Y: mouseEvent.clientY,\r\n };\r\n return client;\r\n }\r\n\r\n /**\r\n * Start drag function: set the object dragDataObject with the draggable\r\n * object offsets coordinates.\r\n * when drag starts (on touchstart or mousedown events).\r\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\r\n */\r\n startDrag(mouseEvent) {\r\n if (this.properties.state === \"minimized\") {\r\n return;\r\n }\r\n if (mouseEvent.target === this.title) {\r\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\r\n // Save first click mouse point on screen.\r\n this.dragDataObject = {\r\n x: this.eventClient(mouseEvent).X,\r\n y: this.eventClient(mouseEvent).Y,\r\n };\r\n // Reset last drag position when start drag.\r\n this.lastDrag = {\r\n x: \"0px\",\r\n y: \"0px\",\r\n };\r\n // Init right and bottom values for window modal if it isn't exist.\r\n if (this.container.style.right === \"\") {\r\n this.container.style.right = \"0px\";\r\n }\r\n if (this.container.style.bottom === \"\") {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n\r\n // Needed for IE11 for apply disabled mouse events on editor because\r\n // internet explorer needs a dynamic object to apply this property.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = 'relative';\r\n }\r\n // Apply class for disable involuntary select text when drag.\r\n Util.addClass(document.body, \"wrs_noselect\");\r\n Util.addClass(this.overlay, \"wrs_overlay_active\");\r\n // Obtain screen limits for prevent overflow.\r\n this.limitWindow = this.getLimitWindow();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Updates dragDataObject with the draggable object coordinates when\r\n * the draggable object is being moved.\r\n * @param {MouseEvent} mouseEvent - The mouse event.\r\n */\r\n drag(mouseEvent) {\r\n if (this.dragDataObject) {\r\n mouseEvent.preventDefault();\r\n // Calculate max and min between actual mouse position and limit of screeen.\r\n // It restric the movement of modal into window.\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\r\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\r\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\r\n // Subtract limit with first position to obtain relative pixels increment\r\n // to the anchor point.\r\n const dragX = `${limitX - this.dragDataObject.x}px`;\r\n const dragY = `${limitY - this.dragDataObject.y}px`;\r\n // Save last valid position of modal before window overflow.\r\n this.lastDrag = {\r\n x: dragX,\r\n y: dragY,\r\n };\r\n // This move modal with hardware acceleration.\r\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\r\n }\r\n if (this.resizeDataObject) {\r\n const { innerWidth } = window;\r\n const { innerHeight } = window;\r\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\r\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\r\n if (limitX < 0) {\r\n limitX = 0;\r\n }\r\n\r\n if (limitY < 0) {\r\n limitY = 0;\r\n }\r\n\r\n let scaleMultiplier;\r\n if (this.leftScale) {\r\n scaleMultiplier = -1;\r\n } else {\r\n scaleMultiplier = 1;\r\n }\r\n\r\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\r\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\r\n if (!this.leftScale) {\r\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\r\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\r\n } else {\r\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\r\n this.container.style.width = \"580px\";\r\n }\r\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\r\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\r\n } else {\r\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\r\n this.container.style.height = \"338px\";\r\n }\r\n }\r\n this.recalculateScale();\r\n this.recalculatePosition();\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boundaries of actual window to limit modal movement.\r\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\r\n */\r\n getLimitWindow() {\r\n // Obtain dimensions of window page.\r\n const maxWidth = window.innerWidth;\r\n const maxHeight = window.innerHeight;\r\n\r\n // Calculate relative position of mouse point into window.\r\n const { offsetHeight } = this.container;\r\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\r\n const contStyleRight = parseInt(this.container.style.right, 10);\r\n\r\n const { pageXOffset } = window;\r\n const dragY = this.dragDataObject.y;\r\n const dragX = this.dragDataObject.x;\r\n\r\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\r\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\r\n\r\n // Calculate limits with sizes of window, modal and mouse position.\r\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\r\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\r\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\r\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\r\n const minPointer = { x: minPointerX, y: minPointerY };\r\n const maxPointer = { x: maxPointerX, y: maxPointerY };\r\n return { minPointer, maxPointer };\r\n }\r\n\r\n /**\r\n * Returns the scrollbar width size of browser\r\n * @returns {Number} The scrollbar width.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getScrollBarWidth() {\r\n // Create a paragraph with full width of page.\r\n const inner = document.createElement(\"p\");\r\n inner.style.width = \"100%\";\r\n inner.style.height = \"200px\";\r\n\r\n // Create a hidden div to compare sizes.\r\n const outer = document.createElement(\"div\");\r\n outer.style.position = \"absolute\";\r\n outer.style.top = \"0px\";\r\n outer.style.left = \"0px\";\r\n outer.style.visibility = \"hidden\";\r\n outer.style.width = \"200px\";\r\n outer.style.height = \"150px\";\r\n outer.style.overflow = \"hidden\";\r\n outer.appendChild(inner);\r\n\r\n document.body.appendChild(outer);\r\n const widthOuter = inner.offsetWidth;\r\n\r\n // Change type overflow of paragraph for measure scrollbar.\r\n outer.style.overflow = \"scroll\";\r\n let widthInner = inner.offsetWidth;\r\n\r\n // If measure is the same, we compare with internal div.\r\n if (widthOuter === widthInner) {\r\n widthInner = outer.clientWidth;\r\n }\r\n document.body.removeChild(outer);\r\n\r\n return widthOuter - widthInner;\r\n }\r\n\r\n /**\r\n * Set the dragDataObject to null.\r\n */\r\n stopDrag() {\r\n // Due to we have multiple events that call this function, we need only to execute\r\n // the next modifiers one time,\r\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\r\n if (this.dragDataObject || this.resizeDataObject) {\r\n // If modal doesn't change, it's not necessary to set position with interpolation.\r\n this.container.style.transform = \"\";\r\n if (this.dragDataObject) {\r\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\r\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\r\n }\r\n // We make focus on editor after drag modal windows to prevent lose focus.\r\n this.focus();\r\n // Restore mouse events on iframe.\r\n // this.iframe.style['pointer-events'] = 'auto';\r\n document.body.style[\"user-select\"] = \"\";\r\n // Restore static state of iframe if we use Internet Explorer.\r\n if (this.isIE11()) {\r\n // this.iframe.style['position'] = null;\r\n }\r\n // Active text select event.\r\n Util.removeClass(document.body, \"wrs_noselect\");\r\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\r\n }\r\n this.dragDataObject = null;\r\n this.resizeDataObject = null;\r\n this.initialWidth = null;\r\n this.leftScale = null;\r\n }\r\n\r\n /**\r\n * Recalculates scale for modal when resize browser window.\r\n */\r\n onWindowResize() {\r\n this.recalculateScrollBar();\r\n this.recalculatePosition();\r\n this.recalculateScale();\r\n }\r\n\r\n /**\r\n * Triggers keyboard events:\r\n * - Tab key tab to go to submit button.\r\n * - Esc key to close the modal dialog.\r\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\r\n */\r\n onKeyDown(keyboardEvent) {\r\n if (keyboardEvent.key !== undefined) {\r\n // Popupmessage is not oppened.\r\n if (this.popup.overlayWrapper.style.display !== \"block\") {\r\n // Code to detect Esc event\r\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\r\n if (this.properties.open) {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\r\n // Code to detect shift Tab event.\r\n if (document.activeElement === this.cancelButton) {\r\n this.submitButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n } else if (keyboardEvent.key === \"Tab\") {\r\n // Code to detect Tab event.\r\n if (document.activeElement === this.submitButton) {\r\n this.cancelButton.focus();\r\n keyboardEvent.stopPropagation();\r\n keyboardEvent.preventDefault();\r\n } else {\r\n this.contentManager.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n } else {\r\n // Popupmessage oppened.\r\n this.popup.onKeyDown(keyboardEvent);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating position for modal dialog when the browser is resized.\r\n */\r\n recalculatePosition() {\r\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\r\n if (parseInt(this.container.style.right, 10) < 0) {\r\n this.container.style.right = \"0px\";\r\n }\r\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\r\n if (parseInt(this.container.style.bottom, 10) < 0) {\r\n this.container.style.bottom = \"0px\";\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating scale for modal when the browser is resized.\r\n */\r\n recalculateScale() {\r\n let sizeModified = false;\r\n\r\n if (parseInt(this.container.style.width, 10) > 580) {\r\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.width = \"580px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (parseInt(this.container.style.height, 10) > 338) {\r\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\r\n sizeModified = true;\r\n } else {\r\n this.container.style.height = \"338px\";\r\n sizeModified = true;\r\n }\r\n\r\n if (sizeModified) {\r\n this.recalculateSize();\r\n }\r\n }\r\n\r\n /**\r\n * Recalculating width of browser scroll bar.\r\n */\r\n recalculateScrollBar() {\r\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\r\n if (this.hasScrollBar) {\r\n this.scrollbarWidth = this.getScrollBarWidth();\r\n } else {\r\n this.scrollbarWidth = 0;\r\n }\r\n }\r\n\r\n /**\r\n * Hide soft keyboards on iOS devices.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n hideKeyboard() {\r\n // iOS keyboard can't be detected or hide directly from JavaScript.\r\n // So, this method simulates that user focus a text input and blur\r\n // the selection.\r\n const inputField = document.createElement(\"input\");\r\n this.container.appendChild(inputField);\r\n inputField.focus();\r\n inputField.blur();\r\n // Is removed to not see it.\r\n inputField.remove();\r\n }\r\n\r\n /**\r\n * Focus to contentManager object.\r\n */\r\n focus() {\r\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\r\n this.contentManager.onFocus();\r\n }\r\n }\r\n\r\n /**\r\n * Returns true when the device is on portrait mode.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n portraitMode() {\r\n return window.innerHeight > window.innerWidth;\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is opened.\r\n */\r\n handleOpenedIosSoftkeyboard() {\r\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\r\n }\r\n }\r\n this.iosSoftkeyboardOpened = true;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Event handler that change container size when IOS soft keyboard is closed.\r\n */\r\n handleClosedIosSoftkeyboard() {\r\n this.iosSoftkeyboardOpened = false;\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on iOS.\r\n */\r\n orientationChangeIosSoftkeyboard() {\r\n if (this.iosSoftkeyboardOpened) {\r\n if (this.portraitMode()) {\r\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\r\n } else {\r\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\r\n }\r\n } else {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n }\r\n\r\n /**\r\n * Change container sizes when orientation is changed on Android.\r\n */\r\n orientationChangeAndroidSoftkeyboard() {\r\n this.wrapper.style.flexGrow = \"1\";\r\n }\r\n\r\n /**\r\n * Set iframe container height.\r\n * @param {Number} height - New height.\r\n */\r\n setContainerHeight(height) {\r\n this.iosDivHeight = height;\r\n this.wrapper.style.height = height;\r\n }\r\n\r\n /**\r\n * Check content of editor before close action.\r\n */\r\n showPopUpMessage() {\r\n if (this.properties.state === \"minimized\") {\r\n this.stack();\r\n }\r\n this.popup.show();\r\n }\r\n\r\n /**\r\n * Sets the title of the modal dialog.\r\n * @param {String} title - Modal dialog title.\r\n */\r\n setTitle(title) {\r\n this.title.innerHTML = title;\r\n }\r\n\r\n /**\r\n * Returns the id of an element, adding the instance number to\r\n * the element class name:\r\n * className --> className[idNumber]\r\n * @param {String} className - The element class name.\r\n * @returns {String} A string appending the instance id to the className.\r\n */\r\n getElementId(className) {\r\n return `${className}[${this.instanceId}]`;\r\n }\r\n}\r\n","/* eslint-disable */\r\nvar polyfills;\r\nexport default polyfills;\r\n\r\n// Polyfills.\r\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\r\nif (!String.prototype.codePointAt) {\r\n (function () {\r\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\r\n var codePointAt = function (position) {\r\n if (this == null) {\r\n throw TypeError();\r\n }\r\n var string = String(this);\r\n var size = string.length;\r\n // `ToInteger`\r\n var index = position ? Number(position) : 0;\r\n if (index != index) {\r\n // better `isNaN`\r\n index = 0;\r\n }\r\n // Account for out-of-bounds indices:\r\n if (index < 0 || index >= size) {\r\n return undefined;\r\n }\r\n // Get the first code unit\r\n var first = string.charCodeAt(index);\r\n var second;\r\n if (\r\n // check if itโ€™s the start of a surrogate pair\r\n first >= 0xd800 &&\r\n first <= 0xdbff && // high surrogate\r\n size > index + 1 // there is a next code unit\r\n ) {\r\n second = string.charCodeAt(index + 1);\r\n if (second >= 0xdc00 && second <= 0xdfff) {\r\n // low surrogate\r\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\r\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\r\n }\r\n }\r\n return first;\r\n };\r\n if (Object.defineProperty) {\r\n Object.defineProperty(String.prototype, \"codePointAt\", {\r\n value: codePointAt,\r\n configurable: true,\r\n writable: true,\r\n });\r\n } else {\r\n String.prototype.codePointAt = codePointAt;\r\n }\r\n })();\r\n}\r\n\r\n// Object.assign polyfill.\r\nif (typeof Object.assign != \"function\") {\r\n // Must be writable: true, enumerable: false, configurable: true\r\n Object.defineProperty(Object, \"assign\", {\r\n value: function assign(target, varArgs) {\r\n // .length of function is 2\r\n \"use strict\";\r\n if (target == null) {\r\n // TypeError if undefined or null\r\n throw new TypeError(\"Cannot convert undefined or null to object\");\r\n }\r\n\r\n var to = Object(target);\r\n\r\n for (var index = 1; index < arguments.length; index++) {\r\n var nextSource = arguments[index];\r\n\r\n if (nextSource != null) {\r\n // Skip over if undefined or null\r\n for (var nextKey in nextSource) {\r\n // Avoid bugs when hasOwnProperty is shadowed\r\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\r\n to[nextKey] = nextSource[nextKey];\r\n }\r\n }\r\n }\r\n }\r\n return to;\r\n },\r\n writable: true,\r\n configurable: true,\r\n });\r\n}\r\n\r\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\r\nif (!Array.prototype.includes) {\r\n Object.defineProperty(Array.prototype, \"includes\", {\r\n value: function (searchElement, fromIndex) {\r\n if (this == null) {\r\n throw new TypeError('\"this\" s null or is not defined');\r\n }\r\n\r\n // 1. Let O be ? ToObject(this value).\r\n var o = Object(this);\r\n\r\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\r\n var len = o.length >>> 0;\r\n\r\n // 3. if len is 0, return false.\r\n if (len === 0) {\r\n return false;\r\n }\r\n\r\n // 4. Let n be ? ToInteger(fromIndex).\r\n // (if fromIndex is undefinedo, this step generates the value 0.)\r\n var n = fromIndex | 0;\r\n\r\n // 5. if n โ‰ฅ 0, then\r\n // a. Let k be n.\r\n // 6. Else n < 0,\r\n // a. Let k be len + n.\r\n // b. if k < 0, let k be 0.\r\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\r\n\r\n function sameValueZero(x, y) {\r\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\r\n }\r\n\r\n // 7. Repeat while k < len\r\n while (k < len) {\r\n // a. let element k be the result of ? Get(O, ! ToString(k)).\r\n // b. if SameValueZero(searchElement, elementK) is true, return true.\r\n if (sameValueZero(o[k], searchElement)) {\r\n return true;\r\n }\r\n // c. Increase k by 1.\r\n k++;\r\n }\r\n\r\n // 8. Return false\r\n return false;\r\n },\r\n });\r\n}\r\n\r\nif (!String.prototype.includes) {\r\n String.prototype.includes = function (search, start) {\r\n \"use strict\";\r\n\r\n if (search instanceof RegExp) {\r\n throw TypeError(\"first argument must not be a RegExp\");\r\n }\r\n if (start === undefined) {\r\n start = 0;\r\n }\r\n return this.indexOf(search, start) !== -1;\r\n };\r\n}\r\n\r\nif (!String.prototype.startsWith) {\r\n Object.defineProperty(String.prototype, \"startsWith\", {\r\n value: function (search, rawPos) {\r\n var pos = rawPos > 0 ? rawPos | 0 : 0;\r\n return this.substring(pos, pos + search.length) === search;\r\n },\r\n });\r\n}\r\n","import Parser from \"./parser\";\r\nimport Util from \"./util\";\r\nimport StringManager from \"./stringmanager\";\r\nimport ContentManager from \"./contentmanager\";\r\nimport Latex from \"./latex\";\r\nimport MathML from \"./mathml\";\r\nimport CustomEditors from \"./customeditors\";\r\nimport Configuration from \"./configuration\";\r\nimport jsProperties from \"./jsvariables\";\r\nimport Event from \"./event\";\r\nimport Listeners from \"./listeners\";\r\nimport Image from \"./image\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport ModalDialog from \"./modal\";\r\nimport Telemeter from \"./telemeter\";\r\nimport \"./polyfills\";\r\nimport \"../styles/styles.css\";\r\n\r\n/**\r\n * @typedef {Object} CoreProperties\r\n * @property {ServiceProviderProperties} serviceProviderProperties\r\n * - The ServiceProvider class properties. *\r\n */\r\nexport default class Core {\r\n /**\r\n * @classdesc\r\n * This class represents MathType integration Core, managing the following:\r\n * - Integration initialization.\r\n * - Event managing.\r\n * - Insertion of formulas into the edit area.\r\n * ```js\r\n * let core = new Core();\r\n * core.addListener(listener);\r\n * core.language = 'en';\r\n *\r\n * // Initializing Core class.\r\n * core.init(configurationService);\r\n * ```\r\n * @constructs\r\n * Core constructor.\r\n * @param {CoreProperties}\r\n */\r\n constructor(coreProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. 'en' by default.\r\n * @type {String}\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Edit mode, 'images' by default. Admits the following values:\r\n * - images\r\n * - latex\r\n * @type {String}\r\n */\r\n this.editMode = \"images\";\r\n\r\n /**\r\n * Modal dialog instance.\r\n * @type {ModalDialog}\r\n */\r\n this.modalDialog = null;\r\n\r\n /**\r\n * The instance of {@link CustomEditors}. By default\r\n * the only custom editor is the Chemistry editor.\r\n * @type {CustomEditors}\r\n */\r\n this.customEditors = new CustomEditors();\r\n\r\n /**\r\n * Chemistry editor.\r\n * @type {CustomEditor}\r\n */\r\n const chemEditorParams = {\r\n name: \"Chemistry\",\r\n toolbar: \"chemistry\",\r\n icon: \"chem.png\",\r\n confVariable: \"chemEnabled\",\r\n title: \"ChemType\",\r\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\r\n };\r\n\r\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\r\n\r\n /**\r\n * Environment properties. This object contains data about the integration platform.\r\n * @typedef IntegrationEnvironment\r\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\r\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\r\n * @property {String} IntegrationEnvironment.version - Integration version.\r\n *\r\n */\r\n\r\n /**\r\n * The environment properties object.\r\n * @type {IntegrationEnvironment}\r\n */\r\n this.environment = {};\r\n\r\n /**\r\n * @typedef EditionProperties\r\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\r\n * False otherwise.\r\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\r\n * Null if the formula is new.\r\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\r\n * @property {Range} editionProperties.range - The range that contains the image element.\r\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\r\n */\r\n\r\n /**\r\n * The properties of the current edition process.\r\n * @type {EditionProperties}\r\n */\r\n this.editionProperties = {};\r\n\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.temporalImage = null;\r\n this.editionProperties.latexRange = null;\r\n this.editionProperties.range = null;\r\n this.editionProperties.editionStartTime = null;\r\n\r\n /**\r\n * The {@link IntegrationModel} instance.\r\n * @type {IntegrationModel}\r\n */\r\n this.integrationModel = null;\r\n\r\n /**\r\n * The {@link ContentManager} instance.\r\n * @type {ContentManager}\r\n */\r\n this.contentManager = null;\r\n\r\n /**\r\n * The current browser.\r\n * @type {String}\r\n */\r\n this.browser = (() => {\r\n const ua = navigator.userAgent;\r\n let browser = \"none\";\r\n if (ua.search(\"Edge/\") >= 0) {\r\n browser = \"EDGE\";\r\n } else if (ua.search(\"Chrome/\") >= 0) {\r\n browser = \"CHROME\";\r\n } else if (ua.search(\"Trident/\") >= 0) {\r\n browser = \"IE\";\r\n } else if (ua.search(\"Firefox/\") >= 0) {\r\n browser = \"FIREFOX\";\r\n } else if (ua.search(\"Safari/\") >= 0) {\r\n browser = \"SAFARI\";\r\n }\r\n return browser;\r\n })();\r\n\r\n /**\r\n * Plugin listeners.\r\n * @type {Array.}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n /**\r\n * Service provider properties.\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = {};\r\n if (\"serviceProviderProperties\" in coreProperties) {\r\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\r\n } else {\r\n throw new Error(\"serviceProviderProperties property missing.\");\r\n }\r\n }\r\n\r\n /**\r\n * Static property.\r\n * Core listeners.\r\n * @private\r\n * @type {Listeners}\r\n */\r\n static get globalListeners() {\r\n return Core._globalListeners;\r\n }\r\n\r\n /**\r\n * Static property setter.\r\n * Set core listeners.\r\n * @param {Listeners} value - The property value.\r\n * @ignore\r\n */\r\n static set globalListeners(value) {\r\n Core._globalListeners = value;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * True when Core.init was called. Otherwise, false.\r\n * @private\r\n * @type {Boolean}\r\n */\r\n static get initialized() {\r\n return Core._initialized;\r\n }\r\n\r\n /**\r\n * Core state. Says if it was loaded previously.\r\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\r\n * @ignore\r\n */\r\n static set initialized(value) {\r\n Core._initialized = value;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.integrationModel} property.\r\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\r\n */\r\n setIntegrationModel(integrationModel) {\r\n this.integrationModel = integrationModel;\r\n }\r\n\r\n /**\r\n * Sets the {@link Core.environment} property.\r\n * @param {IntegrationEnvironment} integrationEnvironment -\r\n * The {@link IntegrationEnvironment} object.\r\n */\r\n setEnvironment(integrationEnvironment) {\r\n if (\"editor\" in integrationEnvironment) {\r\n this.environment.editor = integrationEnvironment.editor;\r\n }\r\n if (\"mode\" in integrationEnvironment) {\r\n this.environment.mode = integrationEnvironment.mode;\r\n }\r\n if (\"version\" in integrationEnvironment) {\r\n this.environment.version = integrationEnvironment.version;\r\n }\r\n }\r\n\r\n /**\r\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\r\n * @returns {Object} headers - key value headers.\r\n */\r\n setHeaders(headers) {\r\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\r\n Configuration.set(\"customHeaders\", headerObject);\r\n }\r\n\r\n /**\r\n * Returns the current {@link ModalDialog} instance.\r\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\r\n */\r\n getModalDialog() {\r\n return this.modalDialog;\r\n }\r\n\r\n /**\r\n * Inits the {@link Core} class, doing the following:\r\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\r\n * - Updates {@link Configuration} class with the previous configuration properties.\r\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\r\n * - Loads language strings.\r\n * - Fires onLoad event.\r\n * @param {Object} serviceParameters - Service parameters.\r\n */\r\n init() {\r\n if (!Core.initialized) {\r\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\r\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\r\n const jsonConfiguration = JSON.parse(jsConfiguration);\r\n Configuration.addConfiguration(jsonConfiguration);\r\n // Adding JavaScript (not backend) configuration variables.\r\n Configuration.addConfiguration(jsProperties);\r\n // Fire 'onLoad' event:\r\n // All integration must listen this event in order to know if the plugin\r\n // has been properly loaded.\r\n StringManager.language = this.language;\r\n this.listeners.fire(\"onLoad\", {});\r\n });\r\n\r\n ServiceProvider.addListener(serviceProviderListener);\r\n ServiceProvider.init(this.serviceProviderProperties);\r\n\r\n Core.initialized = true;\r\n } else {\r\n // Case when there are more than two editor instances.\r\n // After the first editor all the other editors don't need to load any file or service.\r\n this.listeners.fire(\"onLoad\", {});\r\n }\r\n }\r\n\r\n /**\r\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\r\n * @param {Listener} listener - The listener object.\r\n */\r\n addListener(listener) {\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Adds the global {@link Listener} instance to {@link Core} class.\r\n * @param {Listener} listener - The event listener to be added.\r\n * @static\r\n */\r\n static addGlobalListener(listener) {\r\n Core.globalListeners.add(listener);\r\n }\r\n\r\n beforeUpdateFormula(mathml, wirisProperties) {\r\n /**\r\n * This event is fired before updating the formula.\r\n * @type {Object}\r\n * @property {String} mathml - MathML to be transformed.\r\n * @property {String} editMode - Edit mode.\r\n * @property {Object} wirisProperties - Extra attributes for the formula.\r\n * @property {String} language - Formula language.\r\n */\r\n const beforeUpdateEvent = new Event();\r\n\r\n beforeUpdateEvent.mathml = mathml;\r\n\r\n // Cloning wirisProperties object\r\n // We don't want wirisProperties object modified.\r\n beforeUpdateEvent.wirisProperties = {};\r\n\r\n if (wirisProperties != null) {\r\n Object.keys(wirisProperties).forEach((attr) => {\r\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\r\n });\r\n }\r\n\r\n // Read only.\r\n beforeUpdateEvent.language = this.language;\r\n beforeUpdateEvent.editMode = this.editMode;\r\n\r\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {\r\n mathml: beforeUpdateEvent.mathml,\r\n wirisProperties: beforeUpdateEvent.wirisProperties,\r\n };\r\n }\r\n\r\n /**\r\n * Converts a MathML into it's correspondent image and inserts the image is\r\n * inserted in a HTMLElement target by creating\r\n * a new image or updating an existing one.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\r\n * @param {Window} windowTarget - The window element where the editable content is.\r\n * @param {String} mathml - The MathML.\r\n * @param {Array.} wirisProperties - The extra attributes for the formula.\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n /**\r\n * It is the object with the information of the node or latex to insert.\r\n * @typedef ReturnObject\r\n * @property {Node} [node] - The DOM node to insert.\r\n * @property {String} [latex] - The latex to insert.\r\n */\r\n const returnObject = {};\r\n\r\n if (!mathml) {\r\n this.insertElementOnSelection(null, focusElement, windowTarget);\r\n } else if (this.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n // this.integrationModel.getNonLatexNode is an integration wrapper\r\n // to have special behaviours for nonLatex.\r\n // Not all the integrations have special behaviours for nonLatex.\r\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.latex = returnObject.latex;\r\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\r\n } else {\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n }\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n } else {\r\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\r\n\r\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\r\n }\r\n\r\n return returnObject;\r\n }\r\n\r\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\r\n /**\r\n * This event is fired after update the formula.\r\n * @type {Event}\r\n * @param {String} editMode - edit mode.\r\n * @param {Object} windowTarget - target window.\r\n * @param {Object} focusElement - target element to be focused after update.\r\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\r\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\r\n */\r\n const afterUpdateEvent = new Event();\r\n afterUpdateEvent.editMode = this.editMode;\r\n afterUpdateEvent.windowTarget = windowTarget;\r\n afterUpdateEvent.focusElement = focusElement;\r\n afterUpdateEvent.node = node;\r\n afterUpdateEvent.latex = latex;\r\n\r\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\r\n return {};\r\n }\r\n\r\n return {};\r\n }\r\n\r\n /**\r\n * Sets the caret after a given Node and set the focus to the owner document.\r\n * @param {Node} node - The Node element.\r\n */\r\n placeCaretAfterNode(node) {\r\n if (node === null) return;\r\n\r\n this.integrationModel.getSelection();\r\n const nodeDocument = node.ownerDocument;\r\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\r\n const range = nodeDocument.createRange();\r\n range.setStartAfter(node);\r\n range.collapse(true);\r\n const selection = nodeDocument.getSelection();\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n nodeDocument.body.focus();\r\n }\r\n }\r\n\r\n /**\r\n * Replaces a Selection object with an HTMLElement.\r\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\r\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\r\n * @param {Window} windowTarget - The window target.\r\n */\r\n insertElementOnSelection(element, focusElement, windowTarget) {\r\n let mathmlOrigin = null;\r\n if (this.editionProperties.isNewElement) {\r\n if (element) {\r\n if (focusElement.type === \"textarea\") {\r\n Util.updateTextArea(focusElement, element.textContent);\r\n } else if (document.selection && document.getSelection === 0) {\r\n let range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n\r\n if (!(\"parentElement\" in range)) {\r\n windowTarget.document.execCommand(\"delete\", false);\r\n range = windowTarget.document.selection.createRange();\r\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\r\n }\r\n\r\n if (\"parentElement\" in range) {\r\n const temporalObject = range.parentElement();\r\n\r\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\r\n temporalObject.parentNode.replaceChild(element, temporalObject);\r\n } else {\r\n // IE9 fix: parentNode() does not return the IMG node,\r\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\r\n range.pasteHTML(Util.createObjectCode(element));\r\n }\r\n }\r\n } else {\r\n let range = null;\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n if (this.editionProperties.range) {\r\n ({ range } = this.editionProperties);\r\n this.editionProperties.range = null;\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n range = editorSelection.getRangeAt(0);\r\n }\r\n\r\n // Delete if something was surrounded.\r\n range.deleteContents();\r\n\r\n let node = range.startContainer;\r\n const position = range.startOffset;\r\n\r\n if (node.nodeType === 3) {\r\n // TEXT_NODE.\r\n node = node.splitText(position);\r\n node.parentNode.insertBefore(element, node);\r\n } else if (node.nodeType === 1) {\r\n // ELEMENT_NODE.\r\n node.insertBefore(element, node.childNodes[position]);\r\n }\r\n\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n focusElement.focus();\r\n } else {\r\n const editorSelection = this.integrationModel.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (this.editionProperties.range) {\r\n const { range } = this.editionProperties;\r\n this.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n }\r\n }\r\n } else if (this.editionProperties.latexRange) {\r\n if (document.selection && document.getSelection === 0) {\r\n this.editionProperties.isNewElement = true;\r\n this.editionProperties.latexRange.select();\r\n this.insertElementOnSelection(element, focusElement, windowTarget);\r\n } else {\r\n this.editionProperties.latexRange.deleteContents();\r\n this.editionProperties.latexRange.insertNode(element);\r\n this.placeCaretAfterNode(element);\r\n }\r\n } else if (focusElement.type === \"textarea\") {\r\n let item;\r\n // Wrapper for some integrations that can have special behaviours to show latex.\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n item = this.integrationModel.getSelectedItem(focusElement, false);\r\n } else {\r\n item = Util.getSelectedItemOnTextarea(focusElement);\r\n }\r\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\r\n } else {\r\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\r\n if (element && element.nodeName.toLowerCase() === \"img\") {\r\n // Editor empty, formula has been erased on edit.\r\n // There are editors (e.g: CKEditor) that put attributes in images.\r\n // We don't allow that behaviour in our images.\r\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\r\n // Clone is needed to maintain event references to temporalImage.\r\n Image.clone(element, this.editionProperties.temporalImage);\r\n } else {\r\n this.editionProperties.temporalImage.remove();\r\n }\r\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const mathml = element?.dataset?.mathml;\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove the desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n }\r\n\r\n /**\r\n * Opens a modal dialog containing MathType editor..\r\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\r\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\r\n */\r\n openModalDialog(target, isIframe) {\r\n // Count the time since the editor is open\r\n this.editionProperties.editionStartTime = Date.now();\r\n\r\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\r\n this.editMode = \"images\";\r\n\r\n // In IE is needed keep the range due to after focus the modal window\r\n // it can't be retrieved the last selection.\r\n try {\r\n if (isIframe) {\r\n // Is needed focus the target first.\r\n target.contentWindow.focus();\r\n const selection = target.contentWindow.getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n } else {\r\n // Is needed focus the target first.\r\n target.focus();\r\n const selection = getSelection();\r\n this.editionProperties.range = selection.getRangeAt(0);\r\n }\r\n } catch (e) {\r\n this.editionProperties.range = null;\r\n }\r\n\r\n if (isIframe === undefined) {\r\n isIframe = true;\r\n }\r\n\r\n this.editionProperties.latexRange = null;\r\n\r\n if (target) {\r\n let selectedItem;\r\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\r\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\r\n } else {\r\n selectedItem = Util.getSelectedItem(target, isIframe);\r\n }\r\n\r\n // Check LaTeX if and only if the node is a text node (nodeType==3).\r\n if (selectedItem) {\r\n // Case when image was selected and button pressed.\r\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\r\n this.editionProperties.temporalImage = selectedItem.node;\r\n this.editionProperties.isNewElement = false;\r\n } else if (selectedItem.node.nodeType === 3) {\r\n // If it's a text node means that editor is working with LaTeX.\r\n if (this.integrationModel.getMathmlFromTextNode) {\r\n // If integration has this function it isn't set range due to we don't\r\n // know if it will be put into a textarea as a text or image.\r\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (mathml) {\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n }\r\n } else {\r\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\r\n if (latexResult) {\r\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\r\n this.editMode = \"latex\";\r\n this.editionProperties.isNewElement = false;\r\n this.editionProperties.temporalImage = document.createElement(\"img\");\r\n this.editionProperties.temporalImage.setAttribute(\r\n Configuration.get(\"imageMathmlAttribute\"),\r\n MathML.safeXmlEncode(mathml),\r\n );\r\n const windowTarget = isIframe ? target.contentWindow : window;\r\n\r\n if (target.tagName.toLowerCase() !== \"textarea\") {\r\n if (document.selection) {\r\n let leftOffset = 0;\r\n let previousNode = latexResult.startNode.previousSibling;\r\n\r\n while (previousNode) {\r\n leftOffset += Util.getNodeLength(previousNode);\r\n previousNode = previousNode.previousSibling;\r\n }\r\n\r\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\r\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\r\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\r\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\r\n } else {\r\n this.editionProperties.latexRange = windowTarget.document.createRange();\r\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\r\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n } else if (target.tagName.toLowerCase() === \"textarea\") {\r\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\r\n this.editMode = \"latex\";\r\n }\r\n }\r\n\r\n // Setting an object with the editor parameters.\r\n // Editor parameters can be customized in several ways:\r\n // 1 - editorAttributes: Contains the default editor attributes,\r\n // usually the metrics in a comma separated string. Always exists.\r\n // 2 - editorParameters: Object containing custom editor parameters.\r\n // These parameters are defined in the backend. So they affects all integration instances.\r\n\r\n // The backend send the default editor attributes in a coma separated\r\n // with the following structure: key1=value1,key2=value2...\r\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\r\n const defaultEditorAttributes = {};\r\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\r\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\r\n const key = tempAttribute[0];\r\n const value = tempAttribute[1];\r\n defaultEditorAttributes[key] = value;\r\n }\r\n // Custom editor parameters.\r\n const editorAttributes = {\r\n language: this.language, // Default language value\r\n };\r\n // Editor parameters in backend, usually configuration.ini.\r\n const serverEditorParameters = Configuration.get(\"editorParameters\");\r\n // Editor parameters through JavaScript configuration.\r\n const clientEditorParameters = this.integrationModel.editorParameters;\r\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\r\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\r\n\r\n // Now, update backwards: if user has set a custom language, pass that back to core properties\r\n this.language = editorAttributes.language;\r\n StringManager.language = this.language;\r\n\r\n editorAttributes.rtl = this.integrationModel.rtl;\r\n\r\n const customHeaders = Configuration.get(\"customHeaders\");\r\n // This is not being used in the code, we are keeping it just in case it's needed.\r\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\r\n editorAttributes.customHeaders =\r\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\r\n\r\n const contentManagerAttributes = {};\r\n contentManagerAttributes.editorAttributes = editorAttributes;\r\n contentManagerAttributes.language = this.language;\r\n contentManagerAttributes.customEditors = this.customEditors;\r\n contentManagerAttributes.environment = this.environment;\r\n\r\n if (this.modalDialog == null) {\r\n this.modalDialog = new ModalDialog(editorAttributes);\r\n this.contentManager = new ContentManager(contentManagerAttributes);\r\n // When an instance of ContentManager is created we need to wait until\r\n // the ContentManager is ready by listening 'onLoad' event.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n });\r\n this.contentManager.addListener(listener);\r\n this.contentManager.init();\r\n this.modalDialog.setContentManager(this.contentManager);\r\n this.contentManager.setModalDialogInstance(this.modalDialog);\r\n } else {\r\n this.contentManager.dbclick = this.editionProperties.dbclick;\r\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\r\n if (this.editionProperties.temporalImage != null) {\r\n const mathML = MathML.safeXmlDecode(\r\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\r\n );\r\n this.contentManager.mathML = mathML;\r\n }\r\n }\r\n this.contentManager.setIntegrationModel(this.integrationModel);\r\n this.modalDialog.open();\r\n }\r\n\r\n /**\r\n * Returns the {@link CustomEditors} instance.\r\n * @return {CustomEditors} The current {@link CustomEditors} instance.\r\n */\r\n getCustomEditors() {\r\n return this.customEditors;\r\n }\r\n}\r\n\r\n/**\r\n * Core static listeners.\r\n * @type {Listeners}\r\n * @private\r\n */\r\nCore._globalListeners = new Listeners();\r\n\r\n/**\r\n * Resources state. Says if they were loaded or not.\r\n * @type {Boolean}\r\n * @private\r\n */\r\nCore._initialized = false;\r\n","// eslint-disable-next-line no-unused-vars, import/named\r\nimport Core from \"./core.src\";\r\nimport Image from \"./image\";\r\nimport Listeners from \"./listeners\";\r\nimport Util from \"./util\";\r\nimport Configuration from \"./configuration\";\r\nimport ServiceProvider from \"./serviceprovider\";\r\nimport Telemeter from \"./telemeter\";\r\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\r\n\r\n/**\r\n * @typedef {Object} IntegrationModelProperties\r\n * @property {string} configurationService - Configuration service path.\r\n * This parameter is needed to determine all services paths.\r\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\r\n * @property {string} integrationModelProperties.scriptName - Integration script name.\r\n * Usually the name of the integration script.\r\n * @property {Object} integrationModelProperties.environment - integration environment properties.\r\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\r\n * callback method arguments.\r\n * @property {string} [integrationModelProperties.version] - integration version number.\r\n * @property {Object} [integrationModelProperties.editorObject] - object containing\r\n * the integration editor instance.\r\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\r\n * false otherwise.\r\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\r\n * - The service parameters.\r\n * @property {Object} [integrationModelProperties.integrationParameters]\r\n * - Overwritten integration parameters.\r\n */\r\n\r\nexport default class IntegrationModel {\r\n /**\r\n * @classdesc\r\n * This class represents an integration model, allowing the integration script to\r\n * communicate with Core class. Each integration must extend this class.\r\n * @constructs\r\n * @param {IntegrationModelProperties} integrationModelProperties\r\n */\r\n constructor(integrationModelProperties) {\r\n /**\r\n * Language. Needed for accessibility and locales. English by default.\r\n */\r\n this.language = \"en\";\r\n\r\n /**\r\n * Service parameters\r\n * @type {ServiceProviderProperties}\r\n */\r\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\r\n\r\n /**\r\n * Configuration service path. The integration service is needed by Core class to\r\n * load all the backend configuration into the frontend and also to create the paths\r\n * of all services (all services lives in the same route). Mandatory property.\r\n */\r\n this.configurationService = \"\";\r\n if (\"configurationService\" in integrationModelProperties) {\r\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\r\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\r\n integrationModelProperties.configurationService,\r\n ]);\r\n }\r\n\r\n /**\r\n * Plugin version. Needed to stats and caching.\r\n * @type {string}\r\n */\r\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\r\n\r\n /**\r\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\r\n * Mandatory property.\r\n */\r\n this.target = null;\r\n if (\"target\" in integrationModelProperties) {\r\n this.target = integrationModelProperties.target;\r\n } else {\r\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\r\n }\r\n\r\n /**\r\n * Integration script name. Needed to know the plugin path.\r\n */\r\n if (\"scriptName\" in integrationModelProperties) {\r\n this.scriptName = integrationModelProperties.scriptName;\r\n }\r\n\r\n /**\r\n * Object containing the arguments needed by the callback function.\r\n */\r\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\r\n\r\n /**\r\n * Contains information about the integration environment:\r\n * like the name of the editor, the version, etc.\r\n */\r\n this.environment = integrationModelProperties.environment ?? {};\r\n\r\n /**\r\n * Indicates if the DOM target is - or not - and iframe.\r\n */\r\n this.isIframe = false;\r\n if (this.target != null) {\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Instance of the integration editor object. Usually the entry point to access the API\r\n * of a HTML editor.\r\n */\r\n this.editorObject = integrationModelProperties.editorObject ?? null;\r\n\r\n /**\r\n * Specifies if the direction of the text is RTL. false by default.\r\n */\r\n this.rtl = integrationModelProperties.rtl ?? false;\r\n\r\n /**\r\n * Specifies if the integration model exposes the locale strings. false by default.\r\n */\r\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\r\n\r\n /**\r\n * Specify if editor will open in hand mode only\r\n */\r\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\r\n\r\n /**\r\n * Indicates if an image is selected. Needed to resize the image to the original size in case\r\n * the image is resized.\r\n * @type {boolean}\r\n */\r\n this.temporalImageResizing = false;\r\n\r\n /**\r\n * The Core class instance associated to the integration model.\r\n * @type {Core}\r\n */\r\n this.core = null;\r\n\r\n /**\r\n * Integration model listeners.\r\n * @type {Listeners}\r\n */\r\n this.listeners = new Listeners();\r\n\r\n // Parameters overwrite.\r\n if (\"integrationParameters\" in integrationModelProperties) {\r\n IntegrationModel.integrationParameters.forEach((parameter) => {\r\n if (parameter in integrationModelProperties.integrationParameters) {\r\n // Don't add empty parameters.\r\n const value = integrationModelProperties.integrationParameters[parameter];\r\n if (Object.keys(value).length !== 0) {\r\n this[parameter] = value;\r\n }\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Init function. Usually called from the integration side once the core.js file is loaded.\r\n */\r\n init() {\r\n // Check if language is an object and select the property necessary\r\n this.language = this.getLanguage();\r\n\r\n // We need to wait until Core class is loaded ('onLoad' event) before\r\n // call the callback method.\r\n const listener = Listeners.newListener(\"onLoad\", () => {\r\n this.callbackFunction(this.callbackMethodArguments);\r\n });\r\n\r\n // Backwards compatibility.\r\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\r\n const uri = this.serviceProviderProperties.URI;\r\n const server = ServiceProvider.getServerLanguageFromService(uri);\r\n this.serviceProviderProperties.server = server;\r\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\r\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\r\n this.serviceProviderProperties.URI = subsTring;\r\n }\r\n\r\n let serviceParametersURI = this.serviceProviderProperties.URI;\r\n serviceParametersURI =\r\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\r\n ? serviceParametersURI\r\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\r\n\r\n this.serviceProviderProperties.URI = serviceParametersURI;\r\n\r\n const coreProperties = {};\r\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\r\n\r\n this.setCore(new Core(coreProperties));\r\n this.core.addListener(listener);\r\n this.core.language = this.language;\r\n\r\n // Initializing Core class.\r\n this.core.init();\r\n // TODO: Move to Core constructor.\r\n this.core.setEnvironment(this.environment);\r\n\r\n // No internet connection modal.\r\n let attributes = {};\r\n attributes.class = attributes.id = \"wrs_modal_offline\";\r\n this.offlineModal = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_content_offline\";\r\n this.offlineModalContent = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_close\";\r\n this.offlineModalClose = Util.createElement(\"span\", attributes);\r\n this.offlineModalClose.innerHTML = \"×\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_warn\";\r\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\r\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\r\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_container\";\r\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text_warn\";\r\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\r\n\r\n attributes = {};\r\n attributes.class = \"wrs_modal_offline_text\";\r\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\r\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\r\n\r\n // Append offline modal elements\r\n this.offlineModalContent.appendChild(this.offlineModalClose);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\r\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\r\n this.offlineModalContent.appendChild(this.offlineModalMessage);\r\n this.offlineModalContent.appendChild(this.offlineModalWarn);\r\n this.offlineModal.appendChild(this.offlineModalContent);\r\n document.body.appendChild(this.offlineModal);\r\n\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n this.offlineModalClose.addEventListener(\"click\", () => {\r\n modal.style.display = \"none\";\r\n });\r\n\r\n // Store editor name for telemetry purposes.\r\n let editorName = this.environment.editor;\r\n editorName = editorName.slice(0, -1); // Remove version number from editors\r\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\r\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\r\n let solutionTelemeter = editorName;\r\n\r\n // If moodle, add information to hosts and solution.\r\n const isMoodle = !!(typeof M === \"object\" && M !== null);\r\n let lms;\r\n\r\n if (isMoodle) {\r\n solutionTelemeter = \"Moodle\";\r\n lms = {\r\n nam: \"moodle\",\r\n fam: \"lms\",\r\n ver: this.environment.moodleVersion,\r\n category: this.environment.moodleCourseCategory,\r\n course: this.environment.moodleCourseName,\r\n };\r\n if (!editorName.includes(\"TinyMCE\")) {\r\n editorName = \"Atto\";\r\n }\r\n }\r\n\r\n // Get the OS and its version.\r\n const OSData = this.getOS();\r\n\r\n // Get the broswer and its version.\r\n const broswerData = this.getBrowser();\r\n\r\n // Create list of hosts to send to telemetry.\r\n let hosts = [\r\n {\r\n nam: broswerData.detectedBrowser,\r\n fam: \"browser\",\r\n ver: broswerData.versionBrowser,\r\n },\r\n {\r\n nam: editorName.toLowerCase(),\r\n fam: \"html-editor\",\r\n ver: this.environment.editorVersion,\r\n },\r\n {\r\n nam: OSData.detectedOS,\r\n fam: \"os\",\r\n ver: OSData.versionOS,\r\n },\r\n {\r\n nam: window.location.hostname,\r\n fam: \"domain\",\r\n },\r\n lms,\r\n ];\r\n\r\n // Filter hosts to remove empty objects and empty keys.\r\n hosts = hosts.filter((element) => {\r\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\r\n return element !== undefined;\r\n });\r\n\r\n // Initialize telemeter\r\n Telemeter.init({\r\n solution: {\r\n name: `MathType for ${solutionTelemeter}`,\r\n version: this.version,\r\n },\r\n hosts,\r\n config: {\r\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\r\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\r\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\r\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\r\n },\r\n url: undefined,\r\n });\r\n }\r\n\r\n /**\r\n * Returns the Browser name and its version.\r\n * @return {array} - detectedBrowser = Operating System name.\r\n * versionBrowser = Operating System version.\r\n */\r\n getBrowser() {\r\n // default value for OS just in case nothing is detected\r\n let detectedBrowser = \"unknown\";\r\n let versionBrowser = \"unknown\";\r\n\r\n const userAgent = window.navigator.userAgent;\r\n\r\n if (/Brave/.test(userAgent)) {\r\n detectedBrowser = \"brave\";\r\n if (userAgent.indexOf(\"Brave/\")) {\r\n const start = userAgent.indexOf(\"Brave\") + 6;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\r\n detectedBrowser = \"edge_chromium\";\r\n const start = userAgent.indexOf(\"Edg/\") + 4;\r\n versionBrowser = userAgent\r\n .substring(start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Edg/.test(userAgent)) {\r\n detectedBrowser = \"edge\";\r\n let start = userAgent.indexOf(\"Edg\") + 3;\r\n start += userAgent.substring(start).indexOf(\"/\");\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\r\n detectedBrowser = \"firefox\";\r\n let start = userAgent.indexOf(\"Firefox\");\r\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/OPR/.test(userAgent)) {\r\n detectedBrowser = \"opera\";\r\n const start = userAgent.indexOf(\"OPR/\") + 4;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\r\n detectedBrowser = \"chrome\";\r\n let start = userAgent.indexOf(\"Chrome\");\r\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n } else if (/Safari/.test(userAgent)) {\r\n detectedBrowser = \"safari\";\r\n let start = userAgent.indexOf(\"Version/\");\r\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\r\n let end = userAgent.substring(start).indexOf(\" \");\r\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\r\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\r\n }\r\n\r\n return { detectedBrowser, versionBrowser };\r\n }\r\n\r\n /**\r\n * Returns the operating system and its version.\r\n * @return {array} - detectedOS = Operating System name.\r\n * versionOS = Operating System version.\r\n */\r\n getOS() {\r\n // default value for OS just in case nothing is detected\r\n let detectedOS = \"unknown\";\r\n let versionOS = \"unknown\";\r\n\r\n // Retrieve properties to easily detect the OS\r\n const userAgent = window.navigator.userAgent;\r\n const platform = window.navigator.platform;\r\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\r\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\r\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\r\n\r\n // Find OS and their respective versions\r\n if (macosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"macos\";\r\n if (userAgent.indexOf(\"OS X\") !== -1) {\r\n const start = userAgent.indexOf(\"OS X\") + 5;\r\n const end = userAgent.substring(start).indexOf(\" \");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (iosPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"ios\";\r\n if (userAgent.indexOf(\"OS \") !== -1) {\r\n const start = userAgent.indexOf(\"OS \") + 3;\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n }\r\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\r\n detectedOS = \"windows\";\r\n const start = userAgent.indexOf(\"Windows\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/Android/.test(userAgent)) {\r\n detectedOS = \"android\";\r\n const start = userAgent.indexOf(\"Android\");\r\n let end = userAgent.substring(start).indexOf(\";\");\r\n if (end === -1) {\r\n end = userAgent.substring(start).indexOf(\")\");\r\n }\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (/CrOS/.test(userAgent)) {\r\n detectedOS = \"chromeos\";\r\n let start = userAgent.indexOf(\"CrOS \") + 5;\r\n start += userAgent.substring(start).indexOf(\" \");\r\n const end = userAgent.substring(start).indexOf(\")\");\r\n versionOS = userAgent\r\n .substring(start, end + start)\r\n .replace(\"_\", \".\")\r\n .replace(/[^\\d.-]/g, \"\");\r\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\r\n detectedOS = \"linux\";\r\n }\r\n\r\n return { detectedOS, versionOS };\r\n }\r\n\r\n /**\r\n * Returns the absolute path of the integration script.\r\n * @return {string} - Absolute path for the integration script.\r\n */\r\n getPath() {\r\n if (typeof this.scriptName === \"undefined\") {\r\n throw new Error(\"scriptName property needed for getPath.\");\r\n }\r\n const col = document.getElementsByTagName(\"script\");\r\n let path = \"\";\r\n for (let i = 0; i < col.length; i += 1) {\r\n const j = col[i].src.lastIndexOf(this.scriptName);\r\n if (j >= 0) {\r\n path = col[i].src.substr(0, j - 1);\r\n }\r\n }\r\n return path;\r\n }\r\n\r\n /**\r\n * Returns integration model plugin version\r\n * @param {string} - Plugin version\r\n */\r\n getVersion() {\r\n return this.version;\r\n }\r\n\r\n /**\r\n * Sets the language property.\r\n * @param {string} language - language code.\r\n */\r\n setLanguage(language) {\r\n this.language = language;\r\n }\r\n\r\n /**\r\n * Sets a Core instance.\r\n * @param {Core} core - instance of Core class.\r\n */\r\n setCore(core) {\r\n this.core = core;\r\n core.setIntegrationModel(this);\r\n }\r\n\r\n /**\r\n * Returns the Core instance.\r\n * @returns {Core} instance of Core class.\r\n */\r\n getCore() {\r\n return this.core;\r\n }\r\n\r\n /**\r\n * Sets the object target and updates the iframe property.\r\n * @param {HTMLElement} target - target object.\r\n */\r\n setTarget(target) {\r\n this.target = target;\r\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\r\n }\r\n\r\n /**\r\n * Sets the editor object.\r\n * @param {Object} editorObject - The editor object.\r\n */\r\n setEditorObject(editorObject) {\r\n this.editorObject = editorObject;\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openNewFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.dbclick = false;\r\n this.core.editionProperties.isNewElement = true;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\r\n * actions from integration part before the formula is edited.\r\n */\r\n openExistingFormulaEditor() {\r\n if (window.navigator.onLine) {\r\n this.core.editionProperties.isNewElement = false;\r\n this.core.openModalDialog(this.target, this.isIframe);\r\n } else {\r\n const modal = document.getElementById(\"wrs_modal_offline\");\r\n modal.style.display = \"block\";\r\n }\r\n }\r\n\r\n /**\r\n * Wrapper to Core.updateFormula method.\r\n * Transform a MathML into a image formula.\r\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\r\n * or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n */\r\n updateFormula(mathml) {\r\n if (this.editorParameters) {\r\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\r\n mathml,\r\n \"application/vnd.wiris.mtweb-params+json\",\r\n JSON.stringify(this.editorParameters),\r\n );\r\n }\r\n let focusElement;\r\n let windowTarget;\r\n const wirisProperties = null;\r\n\r\n if (this.isIframe) {\r\n focusElement = this.target.contentWindow;\r\n windowTarget = this.target.contentWindow;\r\n } else {\r\n focusElement = this.target;\r\n windowTarget = window;\r\n }\r\n\r\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\r\n\r\n if (!obj) {\r\n return \"\";\r\n }\r\n\r\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\r\n }\r\n\r\n /**\r\n * Wrapper to Core.insertFormula method.\r\n * Inserts the formula in the specified target, creating\r\n * a new image (new formula) or updating an existing one.\r\n * @param {string} mathml - MathML to generate the formula.\r\n * @param {string} editMode - Edit Mode (LaTeX or images).\r\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\r\n */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\r\n\r\n // Delete temporal image when inserted\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return obj;\r\n }\r\n\r\n /**\r\n * Returns the target selection.\r\n * @returns {Selection} target selection.\r\n */\r\n getSelection() {\r\n if (this.isIframe) {\r\n this.target.contentWindow.focus();\r\n return this.target.contentWindow.getSelection();\r\n }\r\n this.target.focus();\r\n return window.getSelection();\r\n }\r\n\r\n /**\r\n * Add events to formulas in the DOM target. The events added are the following:\r\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\r\n * to edit them.\r\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\r\n * in case the the formula is resized.\r\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\r\n * in case the formula is resized.\r\n */\r\n addEvents() {\r\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\r\n Util.addElementEvents(\r\n eventTarget,\r\n (element, event) => {\r\n this.doubleClickHandler(element, event);\r\n // Avoid creating the double click listener more than once for each element.\r\n // This also allows CKEditor4 to add their own double click listener.\r\n event.preventDefault();\r\n },\r\n (element, event) => {\r\n this.mousedownHandler(element, event);\r\n },\r\n (element, event) => {\r\n this.mouseupHandler(element, event);\r\n },\r\n );\r\n }\r\n\r\n /**\r\n * Remove events to formulas in the DOM target.\r\n */\r\n removeEvents() {\r\n const eventTarget =\r\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\r\n\r\n if (!eventTarget) {\r\n return;\r\n }\r\n\r\n Util.removeElementEvents(eventTarget);\r\n }\r\n\r\n /**\r\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\r\n */\r\n destroy() {\r\n this.removeEvents();\r\n // Destroy modal dialog if exists.\r\n if (this.core.modalDialog) {\r\n this.core.modalDialog.destroy();\r\n }\r\n\r\n // Remove offline modal dialog if exists.\r\n if (this.offlineModal) {\r\n this.offlineModal.remove();\r\n }\r\n\r\n this.editorObject = null;\r\n }\r\n\r\n /**\r\n * Handles a Double-click on the target element. Opens an editor\r\n * to re-edit the double-clicked formula.\r\n * @param {HTMLElement} element - DOM object target.\r\n */\r\n doubleClickHandler(element) {\r\n this.core.editionProperties.dbclick = true;\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\r\n if (element.hasAttribute(customEditorAttributeName)) {\r\n const customEditor = element.getAttribute(customEditorAttributeName);\r\n this.core.getCustomEditors().enable(customEditor);\r\n }\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.core.editionProperties.temporalImage = element;\r\n this.core.editionProperties.isNewElement = true;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse up event on the target element. Restores the image size to avoid\r\n * resizing formulas.\r\n */\r\n mouseupHandler() {\r\n if (this.temporalImageResizing) {\r\n setTimeout(() => {\r\n Image.fixAfterResize(this.temporalImageResizing);\r\n }, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Handles a mouse down event on the target element. Saves the formula size to avoid\r\n * resizing formulas.\r\n * @param {HTMLElement} element - target element.\r\n */\r\n mousedownHandler(element) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n this.temporalImageResizing = element;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the integration language. By default the browser agent. This method\r\n * should be overwritten to obtain the integration language, for example using the\r\n * plugin API of an HTML editor.\r\n * @returns {string} integration language.\r\n */\r\n getLanguage() {\r\n return this.getBrowserLanguage();\r\n }\r\n\r\n /**\r\n * Returns the browser language.\r\n * @returns {string} the browser language.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n getBrowserLanguage() {\r\n let language = \"en\";\r\n if (navigator.userLanguage) {\r\n language = navigator.userLanguage.substring(0, 2);\r\n } else if (navigator.language) {\r\n language = navigator.language.substring(0, 2);\r\n } else {\r\n language = \"en\";\r\n }\r\n return language;\r\n }\r\n\r\n /**\r\n * This function is called once the {@link Core} is loaded. IntegrationModel class\r\n * will fire this method when {@link Core} 'onLoad' event is fired.\r\n * This method should content all the logic to init\r\n * the integration.\r\n */\r\n callbackFunction() {\r\n // It's needed to wait until the integration target is ready. The event is fired\r\n // from the integration side.\r\n const listener = Listeners.newListener(\"onTargetReady\", () => {\r\n this.addEvents(this.target);\r\n });\r\n this.listeners.add(listener);\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n // eslint-disable-next-line class-methods-use-this\r\n notifyWindowClosed() {\r\n // Nothing.\r\n }\r\n\r\n /**\r\n * Wrapper.\r\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\r\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\r\n * @param {string} textNode - text node to extract the MathML.\r\n * @param {int} caretPosition - caret position inside the text node.\r\n * @returns {string} MathML inside the text node.\r\n */\r\n\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getMathmlFromTextNode(textNode, caretPosition) {}\r\n\r\n /**\r\n * Wrapper\r\n * It fills wrs event object of nonLatex with the desired data.\r\n * @param {Object} event - event object.\r\n * @param {Object} window dom window object.\r\n * @param {string} mathml valid mathml.\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n fillNonLatexNode(event, window, mathml) {}\r\n\r\n /**\r\n Wrapper.\r\n * Returns selected item from the target.\r\n * @param {HTMLElement} target - target element\r\n * @param {boolean} iframe\r\n */\r\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\r\n getSelectedItem(target, isIframe) {}\r\n\r\n // Set temporal image to null and make focus come back.\r\n static setActionsOnCancelButtons() {\r\n // Make focus come back on the previous place it was when click cancel button\r\n const currentInstance = WirisPlugin.currentInstance;\r\n const editorSelection = currentInstance.getSelection();\r\n editorSelection.removeAllRanges();\r\n\r\n if (currentInstance.core.editionProperties.range) {\r\n const { range } = currentInstance.core.editionProperties;\r\n currentInstance.core.editionProperties.range = null;\r\n editorSelection.addRange(range);\r\n if (range.startOffset !== range.endOffset) {\r\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\r\n }\r\n }\r\n\r\n // eslint-disable-next-line no-undef\r\n if (WirisPlugin.currentInstance) {\r\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\r\n }\r\n }\r\n}\r\n\r\n// To know if the integration that extends this class implements\r\n// wrapper methods, they are set as undefined.\r\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\r\nIntegrationModel.prototype.fillNonLatexNode = undefined;\r\nIntegrationModel.prototype.getSelectedItem = undefined;\r\n\r\n/**\r\n * An object containing a list with the overwritable class constructor properties.\r\n * @type {Object}\r\n */\r\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\r\n","/* eslint-disable */\r\nvar md5;\r\nexport default md5;\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n delete Array.prototype.__class__;\r\n})();\r\n\r\n(function () {\r\n var HxOverrides = function () {};\r\n HxOverrides.__name__ = true;\r\n HxOverrides.dateStr = function (date) {\r\n var m = date.getMonth() + 1;\r\n var d = date.getDate();\r\n var h = date.getHours();\r\n var mi = date.getMinutes();\r\n var s = date.getSeconds();\r\n return (\r\n date.getFullYear() +\r\n \"-\" +\r\n (m < 10 ? \"0\" + m : \"\" + m) +\r\n \"-\" +\r\n (d < 10 ? \"0\" + d : \"\" + d) +\r\n \" \" +\r\n (h < 10 ? \"0\" + h : \"\" + h) +\r\n \":\" +\r\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\r\n \":\" +\r\n (s < 10 ? \"0\" + s : \"\" + s)\r\n );\r\n };\r\n HxOverrides.strDate = function (s) {\r\n switch (s.length) {\r\n case 8:\r\n var k = s.split(\":\");\r\n var d = new Date();\r\n d.setTime(0);\r\n d.setUTCHours(k[0]);\r\n d.setUTCMinutes(k[1]);\r\n d.setUTCSeconds(k[2]);\r\n return d;\r\n case 10:\r\n var k = s.split(\"-\");\r\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\r\n case 19:\r\n var k = s.split(\" \");\r\n var y = k[0].split(\"-\");\r\n var t = k[1].split(\":\");\r\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\r\n default:\r\n throw \"Invalid date format : \" + s;\r\n }\r\n };\r\n HxOverrides.cca = function (s, index) {\r\n var x = s.charCodeAt(index);\r\n if (x != x) return undefined;\r\n return x;\r\n };\r\n HxOverrides.substr = function (s, pos, len) {\r\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\r\n if (len == null) len = s.length;\r\n if (pos < 0) {\r\n pos = s.length + pos;\r\n if (pos < 0) pos = 0;\r\n } else if (len < 0) len = s.length + len - pos;\r\n return s.substr(pos, len);\r\n };\r\n HxOverrides.remove = function (a, obj) {\r\n var i = 0;\r\n var l = a.length;\r\n while (i < l) {\r\n if (a[i] == obj) {\r\n a.splice(i, 1);\r\n return true;\r\n }\r\n i++;\r\n }\r\n return false;\r\n };\r\n HxOverrides.iter = function (a) {\r\n return {\r\n cur: 0,\r\n arr: a,\r\n hasNext: function () {\r\n return this.cur < this.arr.length;\r\n },\r\n next: function () {\r\n return this.arr[this.cur++];\r\n },\r\n };\r\n };\r\n var IntIter = function (min, max) {\r\n this.min = min;\r\n this.max = max;\r\n };\r\n IntIter.__name__ = true;\r\n IntIter.prototype = {\r\n next: function () {\r\n return this.min++;\r\n },\r\n hasNext: function () {\r\n return this.min < this.max;\r\n },\r\n __class__: IntIter,\r\n };\r\n var Std = function () {};\r\n Std.__name__ = true;\r\n Std[\"is\"] = function (v, t) {\r\n return js.Boot.__instanceof(v, t);\r\n };\r\n Std.string = function (s) {\r\n return js.Boot.__string_rec(s, \"\");\r\n };\r\n Std[\"int\"] = function (x) {\r\n return x | 0;\r\n };\r\n Std.parseInt = function (x) {\r\n var v = parseInt(x, 10);\r\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\r\n if (isNaN(v)) return null;\r\n return v;\r\n };\r\n Std.parseFloat = function (x) {\r\n return parseFloat(x);\r\n };\r\n Std.random = function (x) {\r\n return Math.floor(Math.random() * x);\r\n };\r\n var com = com || {};\r\n if (!com.wiris) com.wiris = {};\r\n if (!com.wiris.js) com.wiris.js = {};\r\n com.wiris.js.JsPluginTools = function () {\r\n this.tryReady();\r\n };\r\n com.wiris.js.JsPluginTools.__name__ = true;\r\n com.wiris.js.JsPluginTools.main = function () {\r\n var ev;\r\n ev = com.wiris.js.JsPluginTools.getInstance();\r\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\r\n };\r\n com.wiris.js.JsPluginTools.getInstance = function () {\r\n if (com.wiris.js.JsPluginTools.instance == null)\r\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\r\n return com.wiris.js.JsPluginTools.instance;\r\n };\r\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\r\n if (window.com == null) window.com = {};\r\n if (window.com.wiris == null) window.com.wiris = {};\r\n if (window.com.wiris.js == null) window.com.wiris.js = {};\r\n if (window.com.wiris.js.JsPluginTools == null)\r\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\r\n };\r\n com.wiris.js.JsPluginTools.prototype = {\r\n md5encode: function (content) {\r\n return haxe.Md5.encode(content);\r\n },\r\n doLoad: function () {\r\n this.ready = true;\r\n com.wiris.js.JsPluginTools.instance = this;\r\n com.wiris.js.JsPluginTools.bypassEncapsulation();\r\n },\r\n tryReady: function () {\r\n this.ready = false;\r\n if (js.Lib.document.readyState) {\r\n this.doLoad();\r\n this.ready = true;\r\n }\r\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\r\n },\r\n __class__: com.wiris.js.JsPluginTools,\r\n };\r\n var haxe = haxe || {};\r\n haxe.Log = function () {};\r\n haxe.Log.__name__ = true;\r\n haxe.Log.trace = function (v, infos) {\r\n js.Boot.__trace(v, infos);\r\n };\r\n haxe.Log.clear = function () {\r\n js.Boot.__clear_trace();\r\n };\r\n haxe.Md5 = function () {};\r\n haxe.Md5.__name__ = true;\r\n haxe.Md5.encode = function (s) {\r\n return new haxe.Md5().doEncode(s);\r\n };\r\n haxe.Md5.prototype = {\r\n doEncode: function (str) {\r\n var x = this.str2blks(str);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var step;\r\n var i = 0;\r\n while (i < x.length) {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n step = 0;\r\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\r\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\r\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\r\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\r\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\r\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\r\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\r\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\r\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\r\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\r\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\r\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\r\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\r\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\r\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\r\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\r\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\r\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\r\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\r\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\r\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\r\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\r\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\r\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\r\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\r\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\r\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\r\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\r\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\r\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\r\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\r\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\r\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\r\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\r\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\r\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\r\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\r\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\r\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\r\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\r\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\r\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\r\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\r\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\r\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\r\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\r\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\r\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\r\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\r\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\r\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\r\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\r\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\r\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\r\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\r\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\r\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\r\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\r\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\r\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\r\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\r\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\r\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\r\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\r\n a = this.addme(a, olda);\r\n b = this.addme(b, oldb);\r\n c = this.addme(c, oldc);\r\n d = this.addme(d, oldd);\r\n i += 16;\r\n }\r\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\r\n },\r\n ii: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\r\n },\r\n hh: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\r\n },\r\n gg: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\r\n },\r\n ff: function (a, b, c, d, x, s, t) {\r\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\r\n },\r\n cmn: function (q, a, b, x, s, t) {\r\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\r\n },\r\n rol: function (num, cnt) {\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n },\r\n str2blks: function (str) {\r\n var nblk = ((str.length + 8) >> 6) + 1;\r\n var blks = new Array();\r\n var _g1 = 0,\r\n _g = nblk * 16;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n blks[i] = 0;\r\n }\r\n var i = 0;\r\n while (i < str.length) {\r\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\r\n i++;\r\n }\r\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\r\n var l = str.length * 8;\r\n var k = nblk * 16 - 2;\r\n blks[k] = l & 255;\r\n blks[k] |= ((l >>> 8) & 255) << 8;\r\n blks[k] |= ((l >>> 16) & 255) << 16;\r\n blks[k] |= ((l >>> 24) & 255) << 24;\r\n return blks;\r\n },\r\n rhex: function (num) {\r\n var str = \"\";\r\n var hex_chr = \"0123456789abcdef\";\r\n var _g = 0;\r\n while (_g < 4) {\r\n var j = _g++;\r\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\r\n }\r\n return str;\r\n },\r\n addme: function (x, y) {\r\n var lsw = (x & 65535) + (y & 65535);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 65535);\r\n },\r\n bitAND: function (a, b) {\r\n var lsb = a & 1 & (b & 1);\r\n var msb31 = (a >>> 1) & (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitXOR: function (a, b) {\r\n var lsb = (a & 1) ^ (b & 1);\r\n var msb31 = (a >>> 1) ^ (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n bitOR: function (a, b) {\r\n var lsb = (a & 1) | (b & 1);\r\n var msb31 = (a >>> 1) | (b >>> 1);\r\n return (msb31 << 1) | lsb;\r\n },\r\n __class__: haxe.Md5,\r\n };\r\n haxe.Timer = function (time_ms) {\r\n var me = this;\r\n this.id = window.setInterval(function () {\r\n me.run();\r\n }, time_ms);\r\n };\r\n haxe.Timer.__name__ = true;\r\n haxe.Timer.delay = function (f, time_ms) {\r\n var t = new haxe.Timer(time_ms);\r\n t.run = function () {\r\n t.stop();\r\n f();\r\n };\r\n return t;\r\n };\r\n haxe.Timer.measure = function (f, pos) {\r\n var t0 = haxe.Timer.stamp();\r\n var r = f();\r\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\r\n return r;\r\n };\r\n haxe.Timer.stamp = function () {\r\n return new Date().getTime() / 1000;\r\n };\r\n haxe.Timer.prototype = {\r\n run: function () {},\r\n stop: function () {\r\n if (this.id == null) return;\r\n window.clearInterval(this.id);\r\n this.id = null;\r\n },\r\n __class__: haxe.Timer,\r\n };\r\n var js = js || {};\r\n js.Boot = function () {};\r\n js.Boot.__name__ = true;\r\n js.Boot.__unhtml = function (s) {\r\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\r\n };\r\n js.Boot.__trace = function (v, i) {\r\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\r\n msg += js.Boot.__string_rec(v, \"\");\r\n var d;\r\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\r\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\r\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\r\n };\r\n js.Boot.__clear_trace = function () {\r\n var d = document.getElementById(\"haxe:trace\");\r\n if (d != null) d.innerHTML = \"\";\r\n };\r\n js.Boot.isClass = function (o) {\r\n return o.__name__;\r\n };\r\n js.Boot.isEnum = function (e) {\r\n return e.__ename__;\r\n };\r\n js.Boot.getClass = function (o) {\r\n return o.__class__;\r\n };\r\n js.Boot.__string_rec = function (o, s) {\r\n if (o == null) return \"null\";\r\n if (s.length >= 5) return \"<...>\";\r\n var t = typeof o;\r\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\r\n switch (t) {\r\n case \"object\":\r\n if (o instanceof Array) {\r\n if (o.__enum__) {\r\n if (o.length == 2) return o[0];\r\n var str = o[0] + \"(\";\r\n s += \"\\t\";\r\n var _g1 = 2,\r\n _g = o.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\r\n else str += js.Boot.__string_rec(o[i], s);\r\n }\r\n return str + \")\";\r\n }\r\n var l = o.length;\r\n var i;\r\n var str = \"[\";\r\n s += \"\\t\";\r\n var _g = 0;\r\n while (_g < l) {\r\n var i1 = _g++;\r\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\r\n }\r\n str += \"]\";\r\n return str;\r\n }\r\n var tostr;\r\n try {\r\n tostr = o.toString;\r\n } catch (e) {\r\n return \"???\";\r\n }\r\n if (tostr != null && tostr != Object.toString) {\r\n var s2 = o.toString();\r\n if (s2 != \"[object Object]\") return s2;\r\n }\r\n var k = null;\r\n var str = \"{\\n\";\r\n s += \"\\t\";\r\n var hasp = o.hasOwnProperty != null;\r\n for (var k in o) {\r\n if (hasp && !o.hasOwnProperty(k)) {\r\n continue;\r\n }\r\n if (\r\n k == \"prototype\" ||\r\n k == \"__class__\" ||\r\n k == \"__super__\" ||\r\n k == \"__interfaces__\" ||\r\n k == \"__properties__\"\r\n ) {\r\n continue;\r\n }\r\n if (str.length != 2) str += \", \\n\";\r\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\r\n }\r\n s = s.substring(1);\r\n str += \"\\n\" + s + \"}\";\r\n return str;\r\n case \"function\":\r\n return \"\";\r\n case \"string\":\r\n return o;\r\n default:\r\n return String(o);\r\n }\r\n };\r\n js.Boot.__interfLoop = function (cc, cl) {\r\n if (cc == null) return false;\r\n if (cc == cl) return true;\r\n var intf = cc.__interfaces__;\r\n if (intf != null) {\r\n var _g1 = 0,\r\n _g = intf.length;\r\n while (_g1 < _g) {\r\n var i = _g1++;\r\n var i1 = intf[i];\r\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\r\n }\r\n }\r\n return js.Boot.__interfLoop(cc.__super__, cl);\r\n };\r\n js.Boot.__instanceof = function (o, cl) {\r\n try {\r\n if (o instanceof cl) {\r\n if (cl == Array) return o.__enum__ == null;\r\n return true;\r\n }\r\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\r\n } catch (e) {\r\n if (cl == null) return false;\r\n }\r\n switch (cl) {\r\n case Int:\r\n return Math.ceil(o % 2147483648.0) === o;\r\n case Float:\r\n return typeof o == \"number\";\r\n case Bool:\r\n return o === true || o === false;\r\n case String:\r\n return typeof o == \"string\";\r\n case Dynamic:\r\n return true;\r\n default:\r\n if (o == null) return false;\r\n if (cl == Class && o.__name__ != null) return true;\r\n else null;\r\n if (cl == Enum && o.__ename__ != null) return true;\r\n else null;\r\n return o.__enum__ == cl;\r\n }\r\n };\r\n js.Boot.__cast = function (o, t) {\r\n if (js.Boot.__instanceof(o, t)) return o;\r\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\r\n };\r\n js.Lib = function () {};\r\n js.Lib.__name__ = true;\r\n js.Lib.debug = function () {\r\n debugger;\r\n };\r\n js.Lib.alert = function (v) {\r\n alert(js.Boot.__string_rec(v, \"\"));\r\n };\r\n js.Lib.eval = function (code) {\r\n return eval(code);\r\n };\r\n js.Lib.setErrorHandler = function (f) {\r\n js.Lib.onerror = f;\r\n };\r\n var $_;\r\n function $bind(o, m) {\r\n var f = function () {\r\n return f.method.apply(f.scope, arguments);\r\n };\r\n f.scope = o;\r\n f.method = m;\r\n return f;\r\n }\r\n if (Array.prototype.indexOf)\r\n HxOverrides.remove = function (a, o) {\r\n var i = a.indexOf(o);\r\n if (i == -1) return false;\r\n a.splice(i, 1);\r\n return true;\r\n };\r\n else null;\r\n Math.__name__ = [\"Math\"];\r\n Math.NaN = Number.NaN;\r\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\r\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\r\n Math.isFinite = function (i) {\r\n return isFinite(i);\r\n };\r\n Math.isNaN = function (i) {\r\n return isNaN(i);\r\n };\r\n String.prototype.__class__ = String;\r\n String.__name__ = true;\r\n Array.prototype.__class__ = Array;\r\n Array.__name__ = true;\r\n Date.prototype.__class__ = Date;\r\n Date.__name__ = [\"Date\"];\r\n var Int = { __name__: [\"Int\"] };\r\n var Dynamic = { __name__: [\"Dynamic\"] };\r\n var Float = Number;\r\n Float.__name__ = [\"Float\"];\r\n var Bool = Boolean;\r\n Bool.__ename__ = [\"Bool\"];\r\n var Class = { __name__: [\"Class\"] };\r\n var Enum = {};\r\n var Void = { __ename__: [\"Void\"] };\r\n if (typeof document != \"undefined\") js.Lib.document = document;\r\n if (typeof window != \"undefined\") {\r\n js.Lib.window = window;\r\n js.Lib.window.onerror = function (msg, url, line) {\r\n var f = js.Lib.onerror;\r\n if (f == null) return false;\r\n return f(msg, [url + \":\" + line]);\r\n };\r\n }\r\n com.wiris.js.JsPluginTools.main();\r\n})();\r\ndelete Array.prototype.__class__;\r\n// @codingStandardsIgnoreEnd\r\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\r\n\r\n/**\r\n * This class represents the MathType integration for CKEditor5.\r\n * @extends {IntegrationModel}\r\n */\r\nexport default class CKEditor5Integration extends IntegrationModel {\r\n constructor(ckeditorIntegrationModelProperties) {\r\n const editor = ckeditorIntegrationModelProperties.editorObject;\r\n\r\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\r\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\r\n }\r\n /**\r\n * CKEditor5 Integration.\r\n *\r\n * @param {integrationModelProperties} integrationModelAttributes\r\n */\r\n super(ckeditorIntegrationModelProperties);\r\n\r\n /**\r\n * Folder name used for the integration inside CKEditor plugins folder.\r\n */\r\n this.integrationFolderName = \"ckeditor_wiris\";\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @returns {string} - The CKEditor instance language.\r\n * @override\r\n */\r\n getLanguage() {\r\n // Returns the CKEDitor instance language taking into account that the language can be an object.\r\n // Try to get editorParameters.language, fail silently otherwise\r\n try {\r\n return this.editorParameters.language;\r\n } catch (e) {}\r\n const languageObject = this.editorObject.config.get(\"language\");\r\n if (languageObject != null) {\r\n if (typeof languageObject === \"object\") {\r\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\r\n return languageObject.ui;\r\n }\r\n return this.editorObject.locale.uiLanguage;\r\n }\r\n return languageObject;\r\n }\r\n return super.getLanguage();\r\n }\r\n\r\n /**\r\n * Adds callbacks to the following CKEditor listeners:\r\n * - 'focus' - updates the current instance.\r\n * - 'contentDom' - adds 'doubleclick' callback.\r\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\r\n * - 'setData' - parses the data converting MathML into images.\r\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\r\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\r\n * - 'mode' - recalculates the active element.\r\n */\r\n addEditorListeners() {\r\n const editor = this.editorObject;\r\n\r\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\r\n this.checkElement();\r\n }\r\n }\r\n\r\n /**\r\n * Checks the current container and assign events in case that it doesn't have them.\r\n * CKEditor replaces several times the element element during its execution,\r\n * so we must assign the events again to editor element.\r\n */\r\n checkElement() {\r\n const editor = this.editorObject;\r\n const newElement = editor.sourceElement;\r\n\r\n // If the element wasn't treated, add the events.\r\n if (!newElement.wirisActive) {\r\n this.setTarget(newElement);\r\n this.addEvents();\r\n // Set the element as treated\r\n newElement.wirisActive = true;\r\n }\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @param {HTMLElement} element - HTMLElement target.\r\n * @param {MouseEvent} event - event which trigger the handler.\r\n */\r\n doubleClickHandler(element, event) {\r\n this.core.editionProperties.dbclick = true;\r\n if (this.editorObject.isReadOnly === false) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\r\n // doubleclick event ends here.\r\n if (typeof event.stopPropagation !== \"undefined\") {\r\n // old I.E compatibility.\r\n event.stopPropagation();\r\n } else {\r\n event.returnValue = false;\r\n }\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\r\n if (customEditorAttr) {\r\n this.core.getCustomEditors().enable(customEditorAttr);\r\n }\r\n this.core.editionProperties.temporalImage = element;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n static getCorePath() {\r\n return null; // TODO\r\n }\r\n\r\n /** @inheritdoc */\r\n callbackFunction() {\r\n super.callbackFunction();\r\n this.addEditorListeners();\r\n }\r\n\r\n openNewFormulaEditor() {\r\n // Store the editor selection as it will be lost upon opening the modal\r\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\r\n\r\n // Focus on the selected editor when multiple editor instances are present\r\n WirisPlugin.currentInstance = this;\r\n\r\n return super.openNewFormulaEditor();\r\n }\r\n\r\n /**\r\n * Replaces old formula with new MathML or inserts it in caret position if new\r\n * @param {String} mathml MathML to update old one or insert\r\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\r\n */\r\n insertMathml(mathml) {\r\n // This returns the value returned by the callback function (writer => {...})\r\n return this.editorObject.model.change((writer) => {\r\n const core = this.getCore();\r\n const selection = this.editorObject.model.document.selection;\r\n\r\n const modelElementNew = writer.createElement(\"mathml\", {\r\n formula: mathml,\r\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\r\n });\r\n\r\n // Obtain the DOM object corresponding to the formula\r\n if (core.editionProperties.isNewElement) {\r\n // Don't bother inserting anything at all if the MathML is empty.\r\n if (!mathml) return;\r\n\r\n const viewSelection =\r\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\r\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\r\n\r\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\r\n\r\n // Remove selection\r\n if (!viewSelection.isCollapsed) {\r\n for (const range of viewSelection.getRanges()) {\r\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\r\n }\r\n }\r\n\r\n // Set carret after the formula\r\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\r\n writer.setSelection(position);\r\n } else {\r\n const img = core.editionProperties.temporalImage;\r\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\r\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\r\n\r\n // Insert the new and remove the old one\r\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\r\n\r\n // If the given MathML is empty, don't insert a new formula.\r\n if (mathml) {\r\n this.editorObject.model.insertObject(modelElementNew, position);\r\n }\r\n writer.remove(modelElementOld);\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return modelElementNew;\r\n });\r\n }\r\n\r\n /**\r\n * Finds the text node corresponding to given DOM text element.\r\n * @param {element} viewElement Element to find corresponding text node of.\r\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\r\n */\r\n findText(viewElement) {\r\n // eslint-disable-line consistent-return\r\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\r\n let pivot = viewElement;\r\n let element;\r\n while (!element) {\r\n element = this.editorObject.editing.mapper.toModelElement(\r\n this.editorObject.editing.view.domConverter.domToView(pivot),\r\n );\r\n pivot = pivot.parentElement;\r\n }\r\n\r\n // Navigate through all the subtree under `pivot` in order to find the correct text node\r\n const range = this.editorObject.model.createRangeIn(element);\r\n const descendants = Array.from(range.getItems());\r\n for (const node of descendants) {\r\n let viewElementData = viewElement.data;\r\n if (viewElement.nodeType === 3) {\r\n // Remove invisible white spaces\r\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\r\n }\r\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\r\n return node.textNode;\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n // eslint-disable-line no-unused-vars\r\n const returnObject = {};\r\n\r\n let mathmlOrigin;\r\n if (!mathml) {\r\n this.insertMathml(\"\");\r\n } else if (this.core.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n\r\n this.editorObject.model.change((writer) => {\r\n const { latexRange } = this.core.editionProperties;\r\n\r\n const startNode = this.findText(latexRange.startContainer);\r\n const endNode = this.findText(latexRange.endContainer);\r\n\r\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\r\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\r\n\r\n let range = writer.createRange(startPosition, endPosition);\r\n\r\n // When Latex is next to image/formula.\r\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\r\n // Get the position of the latex to be replaced.\r\n const latexEdited = `$$${Latex.getLatexFromMathML(\r\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\r\n )}$$`;\r\n let data = latexRange.startContainer.data;\r\n\r\n // Remove invisible characters.\r\n data = data.replaceAll(String.fromCharCode(8288), \"\");\r\n\r\n // Get to the start of the latex we are editing.\r\n const offset = data.indexOf(latexEdited);\r\n const dataOffset = data.substring(offset);\r\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\r\n const substring = dataOffset.substr(0, second$);\r\n data = data.replace(substring, \"\");\r\n\r\n if (!data) {\r\n startPosition = writer.createPositionBefore(startNode);\r\n range = startNode;\r\n } else {\r\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\r\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\r\n range = writer.createRange(startPosition, endPosition);\r\n }\r\n }\r\n\r\n writer.remove(range);\r\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\r\n });\r\n } else {\r\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\r\n try {\r\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\r\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\r\n windowTarget.document,\r\n );\r\n } catch (e) {\r\n const x = e.toString();\r\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\r\n this.core.modalDialog.cancelAction();\r\n }\r\n }\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.core.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n\r\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\r\n We probably should add it here as well, but we should look further into how */\r\n // this.editorObject.fire('change');\r\n\r\n // Remove temporal image of inserted formula\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return returnObject;\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n notifyWindowClosed() {\r\n this.editorObject.editing.view.focus();\r\n }\r\n}\r\n","/* eslint-disable max-classes-per-file */\r\nimport { Command } from \"ckeditor5/src/core.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\n/**\r\n * Command for opening the MathType editor\r\n */\r\nexport class MathTypeCommand extends Command {\r\n execute(options = {}) {\r\n // Check we get a valid integration\r\n // eslint-disable-next-line no-prototype-builtins\r\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\r\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\r\n }\r\n\r\n // Save the integration instance as a property of the command.\r\n this.integration = options.integration;\r\n\r\n // Set custom editor or disable it\r\n this.setEditor();\r\n\r\n // Open the editor\r\n this.openEditor();\r\n }\r\n\r\n /**\r\n * Sets the appropriate custom editor, if any, or disables them.\r\n */\r\n setEditor() {\r\n // It's possible that a custom editor was last used.\r\n // We need to disable it to avoid wrong behaviors.\r\n this.integration.core.getCustomEditors().disable();\r\n }\r\n\r\n /**\r\n * Checks whether we are editing an existing formula or a new one and opens the editor.\r\n */\r\n openEditor() {\r\n this.integration.core.editionProperties.dbclick = false;\r\n const image = this._getSelectedImage();\r\n if (\r\n typeof image !== \"undefined\" &&\r\n image !== null &&\r\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\r\n ) {\r\n this.integration.core.editionProperties.temporalImage = image;\r\n this.integration.openExistingFormulaEditor();\r\n } else {\r\n this.integration.openNewFormulaEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Gets the currently selected formula image\r\n * @returns {Element} selected image, if any, undefined otherwise\r\n */\r\n _getSelectedImage() {\r\n const { selection } = this.editor.editing.view.document;\r\n\r\n // If we can not extract the formula, fall back to default behavior.\r\n if (selection.isCollapsed || selection.rangeCount !== 1) {\r\n return;\r\n }\r\n\r\n // Look for the wrapping the formula and then for the inside\r\n\r\n const range = selection.getFirstRange();\r\n\r\n let image;\r\n\r\n for (const span of range) {\r\n if (span.item.name !== \"span\") {\r\n return;\r\n }\r\n image = span.item.getChild(0);\r\n break;\r\n }\r\n\r\n if (!image) {\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return this.editor.editing.view.domConverter.mapViewToDom(image);\r\n }\r\n}\r\n\r\n/**\r\n * Command for opening the ChemType editor\r\n */\r\nexport class ChemTypeCommand extends MathTypeCommand {\r\n setEditor() {\r\n this.integration.core.getCustomEditors().enable(\"chemistry\");\r\n }\r\n}\r\n","// CKEditor imports\r\nimport { Plugin } from \"ckeditor5/src/core.js\";\r\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\r\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\r\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\r\n\r\n// MathType API imports\r\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\r\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\r\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\r\n\r\n// Local imports\r\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\r\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\r\n\r\nimport packageInfo from \"../package.json\";\r\n\r\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\r\n\r\nexport default class MathType extends Plugin {\r\n static get requires() {\r\n return [Widget];\r\n }\r\n\r\n static get pluginName() {\r\n return \"MathType\";\r\n }\r\n\r\n init() {\r\n // Create the MathType API Integration object\r\n const integration = this._addIntegration();\r\n currentInstance = integration;\r\n\r\n // Add the MathType and ChemType commands to the editor\r\n this._addCommands();\r\n\r\n // Add the buttons for MathType and ChemType\r\n this._addViews(integration);\r\n\r\n // Registers the element in the schema\r\n this._addSchema();\r\n\r\n // Add the downcast and upcast converters\r\n this._addConverters(integration);\r\n\r\n // Expose the WirisPlugin variable to the window\r\n this._exposeWiris();\r\n }\r\n\r\n /**\r\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\r\n */\r\n destroy() {\r\n // eslint-disable-line class-methods-use-this\r\n currentInstance?.destroy();\r\n }\r\n\r\n /**\r\n * Create the MathType API Integration object\r\n * @returns {CKEditor5Integration} the integration object\r\n */\r\n _addIntegration() {\r\n const { editor } = this;\r\n\r\n /**\r\n * Integration model constructor attributes.\r\n * @type {integrationModelProperties}\r\n */\r\n const integrationProperties = {};\r\n integrationProperties.environment = {};\r\n integrationProperties.environment.editor = \"CKEditor5\";\r\n integrationProperties.environment.editorVersion = \"5.x\";\r\n integrationProperties.version = packageInfo.version;\r\n integrationProperties.editorObject = editor;\r\n integrationProperties.serviceProviderProperties = {};\r\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\r\n integrationProperties.serviceProviderProperties.server = \"java\";\r\n integrationProperties.target = editor.sourceElement;\r\n integrationProperties.scriptName = \"bundle.js\";\r\n integrationProperties.managesLanguage = true;\r\n // etc\r\n\r\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\r\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\r\n let integration;\r\n if (integrationProperties.target) {\r\n // Instance of the integration associated to this editor instance\r\n integration = new CKEditor5Integration(integrationProperties);\r\n integration.init();\r\n integration.listeners.fire(\"onTargetReady\", {});\r\n\r\n integration.checkElement();\r\n\r\n this.listenTo(\r\n editor.editing.view.document,\r\n \"click\",\r\n (evt, data) => {\r\n // Is Double-click\r\n if (data.domEvent.detail === 2) {\r\n integration.doubleClickHandler(data.domTarget, data.domEvent);\r\n evt.stop();\r\n }\r\n },\r\n { priority: \"highest\" },\r\n );\r\n }\r\n\r\n return integration;\r\n }\r\n\r\n /**\r\n * Add the MathType and ChemType commands to the editor\r\n */\r\n _addCommands() {\r\n const { editor } = this;\r\n\r\n // Add command to open the formula editor\r\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\r\n\r\n // Add command to open the chemistry formula editor\r\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\r\n }\r\n\r\n /**\r\n * Add the buttons for MathType and ChemType\r\n * @param {CKEditor5Integration} integration the integration object\r\n */\r\n _addViews(integration) {\r\n const { editor } = this;\r\n\r\n // Check if MathType editor is enabled\r\n if (Configuration.get(\"editorEnabled\")) {\r\n // Add button for the formula editor\r\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\r\n view.set({\r\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\r\n icon: mathIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"MathType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Check if ChemType editor is enabled\r\n if (Configuration.get(\"chemEnabled\")) {\r\n // Add button for the chemistry formula editor\r\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\r\n\r\n view.set({\r\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\r\n icon: chemIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"ChemType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Observer for the Double-click event\r\n editor.editing.view.addObserver(ClickObserver);\r\n }\r\n\r\n /**\r\n * Registers the element in the schema\r\n */\r\n _addSchema() {\r\n const { schema } = this.editor.model;\r\n\r\n schema.register(\"mathml\", {\r\n inheritAllFrom: \"$inlineObject\",\r\n allowAttributes: [\"formula\", \"htmlContent\"],\r\n });\r\n }\r\n\r\n /**\r\n * Add the downcast and upcast converters\r\n */\r\n _addConverters(integration) {\r\n const { editor } = this;\r\n\r\n // Editing view -> Model\r\n editor.conversion.for(\"upcast\").elementToElement({\r\n view: {\r\n name: \"span\",\r\n classes: \"ck-math-widget\",\r\n },\r\n model: (viewElement, { writer: modelWriter }) => {\r\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\r\n return modelWriter.createElement(\"mathml\", {\r\n formula,\r\n });\r\n },\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // When element was already consumed then skip it.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // If we encounter any with a LaTeX annotation inside,\r\n // convert it into a \"$$...$$\" string.\r\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\r\n\r\n // Get the formula of the (which is all its children).\r\n const processor = new XmlDataProcessor(editor.editing.view.document);\r\n\r\n // Only god knows why the following line makes viewItem lose all of its children,\r\n // so we obtain isLatex before doing this because we need viewItem's children for that.\r\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\r\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\r\n\r\n // and obtain the attributes of too!\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n\r\n // We process the document fragment\r\n let formula = processor.toData(viewDocumentFragment) || \"\";\r\n\r\n // And obtain the complete formula\r\n formula = Util.htmlSanitize(`${formula}`);\r\n\r\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\r\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\r\n\r\n /* Model node that contains what's going to actually be inserted. This can be either:\r\n - A element with a formula attribute set to the given formula, or\r\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\r\n const modelNode = isLatex\r\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\r\n : writer.createElement(\"mathml\", { formula });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\r\n \"element:img\",\r\n (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // Only upcast when is wiris formula\r\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\r\n return;\r\n }\r\n\r\n // The following code ensures that the element's name, attributes, and classes are consumed.\r\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\r\n\r\n // Check if we can consume the element name.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // Consume the name, attributes, and classes so nothing else processes it.\r\n consumable.consume(viewItem, { name: true });\r\n for (const attrName of viewItem.getAttributes()) {\r\n consumable.consume(viewItem, { attributes: [attrName] });\r\n }\r\n\r\n for (const className of viewItem.getClassNames()) {\r\n consumable.consume(viewItem, { classes: [className] });\r\n }\r\n\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n const htmlContent = Util.htmlSanitize(``);\r\n\r\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n },\r\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\r\n { priority: \"high\" },\r\n );\r\n\r\n /**\r\n * Whether the given view element has a LaTeX annotation element.\r\n * @param {*} math\r\n * @returns {bool}\r\n */\r\n function mathIsLatex(math) {\r\n const semantics = math.getChild(0);\r\n if (!semantics || semantics.name !== \"semantics\") return false;\r\n for (const child of semantics.getChildren()) {\r\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n function createViewWidget(modelItem, { writer: viewWriter }) {\r\n const widgetElement = viewWriter.createContainerElement(\"span\", {\r\n class: \"ck-math-widget\",\r\n });\r\n\r\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\r\n\r\n if (mathUIElement) {\r\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\r\n }\r\n\r\n return toWidget(widgetElement, viewWriter);\r\n }\r\n\r\n function createViewImage(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n const formula = modelItem.getAttribute(\"formula\");\r\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\r\n\r\n if (!formula && !htmlContent) {\r\n return null;\r\n }\r\n\r\n let imgElement = null;\r\n\r\n if (htmlContent) {\r\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\r\n } else if (formula) {\r\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\r\n\r\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\r\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\r\n\r\n // Add HTML element () to model\r\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\r\n }\r\n\r\n /* Although we use the HtmlDataProcessor to obtain the attributes,\r\n * we must create a new EmptyElement which is independent of the\r\n * DataProcessor being used by this editor instance\r\n */\r\n if (imgElement) {\r\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\r\n renderUnsafeAttributes: [\"src\"],\r\n });\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // Model -> Editing view\r\n editor.conversion.for(\"editingDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createViewWidget,\r\n });\r\n\r\n // Model -> Data view\r\n editor.conversion.for(\"dataDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createDataString, // eslint-disable-line no-use-before-define\r\n });\r\n\r\n /**\r\n * Makes a copy of the given view node.\r\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\r\n * @returns {module:engine/view/node~Node} Copy of the node.\r\n */\r\n function clone(viewWriter, sourceNode) {\r\n if (sourceNode.is(\"text\")) {\r\n return viewWriter.createText(sourceNode.data);\r\n }\r\n if (sourceNode.is(\"element\")) {\r\n if (sourceNode.is(\"emptyElement\")) {\r\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\r\n }\r\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\r\n for (const child of sourceNode.getChildren()) {\r\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\r\n }\r\n return element;\r\n }\r\n\r\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\r\n }\r\n\r\n function createDataString(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n // Load img element\r\n const mathString =\r\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\r\n\r\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\r\n\r\n return clone(viewWriter, sourceMathElement);\r\n }\r\n\r\n // This stops the view selection getting into the s and messing up caret movement\r\n editor.editing.mapper.on(\r\n \"viewToModelPosition\",\r\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\r\n );\r\n\r\n // Keep a reference to the original get and set function.\r\n const { get, set } = editor.data;\r\n\r\n /**\r\n * Hack to transform $$latex$$ into in editor.getData()'s output.\r\n */\r\n editor.data.on(\r\n \"get\",\r\n (e) => {\r\n const output = e.return;\r\n const parsedResult = Parser.endParse(output);\r\n\r\n // Cleans all the semantics tag for safexml\r\n // including the handwritten data points\r\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\r\n },\r\n { priority: \"low\" },\r\n );\r\n\r\n /**\r\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\r\n */\r\n editor.data.on(\r\n \"set\",\r\n (e, args) => {\r\n // Retrieve the data to be set on the CKEditor.\r\n let modifiedData = args[0];\r\n // Regex to find all mathml formulas.\r\n const regexp = /(]*>)|()/gm;\r\n const formulas = [];\r\n let formula;\r\n\r\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\r\n while ((formula = regexp.exec(modifiedData)) !== null) {\r\n formulas.push(formula[0]);\r\n }\r\n\r\n // Loop to find LaTeX and formula images and replace the MathML for the both.\r\n formulas.forEach((formula) => {\r\n if (formula.includes('encoding=\"LaTeX\"')) {\r\n // LaTeX found.\r\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\r\n modifiedData = modifiedData.replace(formula, latex);\r\n } else if (formula.includes(\" 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return func.apply(thisArg, args);\n };\n}\nif (!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}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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 */\nfunction 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\nconst 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']);\nconst 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', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst 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.\nconst 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']);\nconst 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.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst 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']);\nconst 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', 'mask-type', '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']);\nconst 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']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst 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);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar 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\nconst 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};\nconst 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 */\nconst _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};\nconst _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};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.0';\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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\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 ... 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 (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\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 . 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 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 . 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 // 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 = '' + 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 = '' + dirty + '';\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 (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!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 (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; 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 = '\\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}\nvar purify = createDOMPurify();\n\nexport { purify as default };\n//# sourceMappingURL=purify.es.mjs.map\n","/**\n * This class represents all the constants needed in a MathType integration among different classes.\n * If a constant should be used across different classes should be defined using attribute\n * accessors.\n */\nexport default class Constants {\n /**\n * Safe XML entities.\n * @type {Object}\n */\n static get safeXmlCharactersEntities() {\n return {\n tagOpener: \"«\",\n tagCloser: \"»\",\n doubleQuote: \"¨\",\n realDoubleQuote: \""\",\n };\n }\n\n /**\n * Blackboard invalid safe characters.\n * @type {Object}\n */\n static get safeBadBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยป<ยซ/moยป\",\n gtElement: \"ยซmoยป>ยซ/moยป\",\n ampElement: \"ยซmoยป&ยซ/moยป\",\n };\n }\n\n /**\n * Blackboard valid safe characters.\n * @type{Object}\n */\n static get safeGoodBlackboardCharacters() {\n return {\n ltElement: \"ยซmoยปยงlt;ยซ/moยป\",\n gtElement: \"ยซmoยปยงgt;ยซ/moยป\",\n ampElement: \"ยซmoยปยงamp;ยซ/moยป\",\n };\n }\n\n /**\n * Standard XML special characters.\n * @type {Object}\n */\n static get xmlCharacters() {\n return {\n id: \"xmlCharacters\",\n tagOpener: \"<\", // Hex: \\x3C.\n tagCloser: \">\", // Hex: \\x3E.\n doubleQuote: '\"', // Hex: \\x22.\n ampersand: \"&\", // Hex: \\x26.\n quote: \"'\", // Hex: \\x27.\n };\n }\n\n /**\n * Safe XML special characters. This characters are used instead the standard\n * the standard to parse the MathML if safeXML save mode is enable. Each XML\n * special character have a UTF-8 representation.\n * @type {Object}\n */\n static get safeXmlCharacters() {\n return {\n id: \"safeXmlCharacters\",\n tagOpener: \"ยซ\", // Hex: \\xAB.\n tagCloser: \"ยป\", // Hex: \\xBB.\n doubleQuote: \"ยจ\", // Hex: \\xA8.\n ampersand: \"ยง\", // Hex: \\xA7.\n quote: \"`\", // Hex: \\x60.\n realDoubleQuote: \"ยจ\",\n };\n }\n}\n","import Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a class to manage MathML objects.\n */\nexport default class MathML {\n /**\n * Checks if the mathml at position i is inside an HTML attribute or not.\n * @param {string} content - a string containing MathML code.\n * @param {number} i - search index.\n * @return {boolean} true if is inside an HTML attribute. false otherwise.\n */\n static isMathmlInAttribute(content, i) {\n // Regex =\n // '^[\\'\"][\\\\s]*=[\\\\s]*[\\\\w-]+([\\\\s]*(\"[^\"]*\"|\\'[^\\']*\\')[\\\\s]*\n // =[\\\\s]*[\\\\w-]+[\\\\s]*)*[\\\\s]+gmi<';\n const mathAtt = \"['\\\"][\\\\s]*=[\\\\s]*[\\\\w-]+\"; // \"=att OR '=att\n const attContent = \"\\\"[^\\\"]*\\\"|'[^']*'\"; // \"blabla\" OR 'blabla'\n const att = `[\\\\s]*(${attContent})[\\\\s]*=[\\\\s]*[\\\\w-]+[\\\\s]*`; // \"blabla\"=att OR 'blabla'=att\n const atts = `('${att}')*`; // \"blabla\"=att1 \"blabla\"=att2\n const regex = `^${mathAtt}${atts}[\\\\s]+gmi<`; // \"=att \"blabla\"=att1 \"blabla\"=att2 gmi< .\n const expression = new RegExp(regex);\n\n const actualContent = content.substring(0, i);\n const reversed = actualContent.split(\"\").reverse().join(\"\");\n const exists = expression.test(reversed);\n\n return exists;\n }\n\n /**\n * Decodes an encoded MathML with standard XML tags.\n * We use these entities because IE doesn't support html entities\n * on its attributes sometimes. Yes, sometimes.\n * @param {string} input - string to be decoded.\n * @return {string} decoded string.\n */\n static safeXmlDecode(input) {\n let { tagOpener } = Constants.safeXmlCharactersEntities;\n let { tagCloser } = Constants.safeXmlCharactersEntities;\n let { doubleQuote } = Constants.safeXmlCharactersEntities;\n let { realDoubleQuote } = Constants.safeXmlCharactersEntities;\n // Decoding entities.\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n // Added to fix problem due to import from 1.9.x.\n input = input.split(realDoubleQuote).join(Constants.safeXmlCharacters.realDoubleQuote);\n\n // Blackboard.\n const { ltElement } = Constants.safeBadBlackboardCharacters;\n const { gtElement } = Constants.safeBadBlackboardCharacters;\n const { ampElement } = Constants.safeBadBlackboardCharacters;\n if (\"_wrs_blackboard\" in window && window._wrs_blackboard) {\n input = input.split(ltElement).join(Constants.safeGoodBlackboardCharacters.ltElement);\n input = input.split(gtElement).join(Constants.safeGoodBlackboardCharacters.gtElement);\n input = input.split(ampElement).join(Constants.safeGoodBlackboardCharacters.ampElement);\n }\n\n ({ tagOpener } = Constants.safeXmlCharacters);\n ({ tagCloser } = Constants.safeXmlCharacters);\n ({ doubleQuote } = Constants.safeXmlCharacters);\n ({ realDoubleQuote } = Constants.safeXmlCharacters);\n const { ampersand } = Constants.safeXmlCharacters;\n const { quote } = Constants.safeXmlCharacters;\n\n // Decoding characters.\n input = input.split(tagOpener).join(Constants.xmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.xmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.xmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.xmlCharacters.ampersand);\n input = input.split(quote).join(Constants.xmlCharacters.quote);\n\n // We are replacing $ by & when its part of an entity for retro-compatibility.\n // Now, the standard is replace ยง by &.\n let returnValue = \"\";\n let currentEntity = null;\n\n for (let i = 0; i < input.length; i += 1) {\n const character = input.charAt(i);\n if (currentEntity == null) {\n if (character === \"$\") {\n currentEntity = \"\";\n } else {\n returnValue += character;\n }\n } else if (character === \";\") {\n returnValue += `&${currentEntity}`;\n currentEntity = null;\n } else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {\n // Character is part of an entity.\n currentEntity += character;\n } else {\n returnValue += `$${currentEntity}`; // Is not an entity.\n currentEntity = null;\n i -= 1; // Parse again the current character.\n }\n }\n\n return returnValue;\n }\n\n /**\n * Encodes a MathML with standard XML tags to a MMathML encoded with safe XML tags.\n * We use these entities because IE doesn't support html entities on its attributes sometimes.\n * @param {string} input - input string to be encoded\n * @returns {string} encoded string.\n */\n static safeXmlEncode(input) {\n const { tagOpener } = Constants.xmlCharacters;\n const { tagCloser } = Constants.xmlCharacters;\n const { doubleQuote } = Constants.xmlCharacters;\n const { ampersand } = Constants.xmlCharacters;\n const { quote } = Constants.xmlCharacters;\n\n input = input.split(tagOpener).join(Constants.safeXmlCharacters.tagOpener);\n input = input.split(tagCloser).join(Constants.safeXmlCharacters.tagCloser);\n input = input.split(doubleQuote).join(Constants.safeXmlCharacters.doubleQuote);\n input = input.split(ampersand).join(Constants.safeXmlCharacters.ampersand);\n input = input.split(quote).join(Constants.safeXmlCharacters.quote);\n\n return input;\n }\n\n /**\n * Converts special symbols (> 128) to entities and replaces all textual\n * entities by its number entities.\n * @param {string} mathml - MathML string containing - or not - special symbols\n * @returns {string} MathML with all textual entities replaced.\n */\n static mathMLEntities(mathml) {\n let toReturn = \"\";\n\n for (let i = 0; i < mathml.length; i += 1) {\n const character = mathml.charAt(i);\n\n // Parsing > 128 characters.\n if (mathml.codePointAt(i) > 128) {\n toReturn += `&#${mathml.codePointAt(i)};`;\n // For UTF-32 characters we need to move the index one position.\n if (mathml.codePointAt(i) > 0xffff) {\n i += 1;\n }\n } else if (character === \"&\") {\n const end = mathml.indexOf(\";\", i + 1);\n if (end >= 0) {\n const container = document.createElement(\"span\");\n container.innerHTML = mathml.substring(i, end + 1);\n toReturn += `&#${Util.fixedCharCodeAt(container.textContent || container.innerText, 0)};`;\n i = end;\n } else {\n toReturn += character;\n }\n } else {\n toReturn += character;\n }\n }\n\n return toReturn;\n }\n\n /**\n * Add a custom editor name with the prefix wrs_ to a MathML class attribute.\n * @param {string} mathml - a MathML string created with a custom editor, like chemistry.\n * @param {string} customEditor - custom editor name.\n * @returns {string} MathML string with his class containing the editor toolbar string.\n */\n static addCustomEditorClassAttribute(mathml, customEditor) {\n let toReturn = \"\";\n\n const start = mathml.indexOf(\"\");\n if (mathml.indexOf(\"class\") === -1) {\n // Adding custom editor type.\n toReturn = `${mathml.substr(start, end)} class=\"wrs_${customEditor}\">`;\n toReturn += mathml.substr(end + 1, mathml.length);\n return toReturn;\n }\n }\n return mathml;\n }\n\n /**\n * Remove a custom editor name from the MathML class attribute.\n * @param {string} mathml - a MathML string.\n * @param {string} customEditor - custom editor name.\n * @returns {string} The input MathML without customEditor name in his class.\n */\n static removeCustomEditorClassAttribute(mathml, customEditor) {\n // Discard MathML without the specified class.\n if (mathml.indexOf(\"class\") === -1 || mathml.indexOf(`wrs_${customEditor}`) === -1) {\n return mathml;\n }\n\n // Trivial case: class attribute value equal to editor name. Then\n // class attribute is removed.\n // First try to remove it with a space before if there is one\n // Otherwise without the space\n if (mathml.indexOf(` class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(` class=\"wrs_${customEditor}\"`, \"\");\n }\n if (mathml.indexOf(`class=\"wrs_${customEditor}\"`) !== -1) {\n return mathml.replace(`class=\"wrs_${customEditor}\"`, \"\");\n }\n\n // Non Trivial case: class attribute contains editor name.\n return mathml.replace(`wrs_${customEditor}`, \"\");\n }\n\n /**\n * Adds annotation tag in MathML element.\n * @param {String} mathml - valid MathML.\n * @param {String} content - value to put inside annotation tag.\n * @param {String} annotationEncoding - annotation encoding.\n * @returns {String} - 'mathml' with an annotation that contains\n * 'content' and encoding 'encoding'.\n */\n static addAnnotation(mathml, content, annotationEncoding) {\n // If contains annotation, also contains semantics tag.\n const containsAnnotation = mathml.indexOf(\"\");\n mathmlWithAnnotation = `${mathml.substring(0, closeSemanticsIndex)}${content}${mathml.substring(closeSemanticsIndex)}`;\n } else if (MathML.isEmpty(mathml)) {\n const endIndexInline = mathml.indexOf(\"/>\");\n const endIndexNonInline = mathml.indexOf(\">\");\n const endIndex = endIndexNonInline === endIndexInline ? endIndexInline : endIndexNonInline;\n mathmlWithAnnotation = `${mathml.substring(0, endIndex)}>${content}`;\n } else {\n const beginMathMLContent = mathml.indexOf(\">\") + 1;\n const endMathmlContent = mathml.lastIndexOf(\"\");\n const mathmlContent = mathml.substring(beginMathMLContent, endMathmlContent);\n mathmlWithAnnotation = `${mathml.substring(0, beginMathMLContent)}${mathmlContent}${content}`; // eslint-disable-line max-len\n }\n\n return mathmlWithAnnotation;\n }\n\n /**\n * Removes specific annotation tag in MathML element.\n * In case of remove the unique annotation, also is removed semantics tag.\n * @param {String} mathml - valid MathML.\n * @param {String} annotationEncoding - annotation encoding to remove.\n * @returns {String} - 'mathml' without the annotation encoding specified.\n */\n static removeAnnotation(mathml, annotationEncoding) {\n let mathmlWithoutAnnotation = mathml;\n const openAnnotationTag = ``;\n const closeAnnotationTag = \"\";\n const startAnnotationIndex = mathml.indexOf(openAnnotationTag);\n if (startAnnotationIndex !== -1) {\n let differentAnnotationFound = false;\n let differentAnnotationIndex = mathml.indexOf(\"\\s*?()?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsEndingTagRegex = /(<\\/mrow>)?\\s*/gm;\n\n return mathml.replace(semanticsStartingTagRegex, \"\").replace(semanticsEndingTagRegex, \"\");\n }\n\n /**\n * Removes semantics tag to element that contains mathml.\n * When using Hand to create formulas, it adds the mrow tag due to the semantics one, this one is also removed.\n * @param {string} element - Inner HTML text string.\n * @returns {string} - 'mathml' without semantics tag.\n */\n static removeSafeXMLSemantics(element) {\n // If `mrow` is found right before the `semantics` starting tag, it's removed as well\n const semanticsSafeStartingTagRegex = /ยซsemanticsยป\\s*?(ยซmrowยป)?/gm;\n\n // If `mrow` is found right after the `annotation` ending tag, it's removed as well\n // alongside `semantics` closing tag and the whole `annotation` tag and its contents.\n const semanticsSafeEndingTagRegex = /(ยซ\\/mrowยป)?\\s*ยซannotation[\\W\\w]*?ยซ\\/semanticsยป/gm;\n\n return element.replace(semanticsSafeStartingTagRegex, \"\").replace(semanticsSafeEndingTagRegex, \"\");\n }\n\n /**\n * Transforms all xml mathml occurrences that contain semantics to the same\n * xml mathml occurrences without semantics.\n * @param {string} text - string that can contain xml mathml occurrences.\n * @param {Constants} [characters] - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * xmlCharacters by default.\n * @returns {string} - 'text' with all xml mathml occurrences without annotation tag.\n */\n static removeSemanticsOcurrences(text, characters = Constants.xmlCharacters) {\n const mathTagStart = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const mathTagEndline = `/${characters.tagCloser}`;\n const { tagCloser } = characters;\n const semanticsTagStart = `${characters.tagOpener}semantics${characters.tagCloser}`;\n const annotationTagStart = `${characters.tagOpener}annotation encoding=`;\n\n let output = \"\";\n let start = text.indexOf(mathTagStart);\n let end = 0;\n while (start !== -1) {\n output += text.substring(end, start);\n\n // MathML can be written as '' or ''.\n const mathTagEndIndex = text.indexOf(mathTagEnd, start);\n const mathTagEndlineIndex = text.indexOf(mathTagEndline, start);\n const firstTagCloser = text.indexOf(tagCloser, start);\n if (mathTagEndIndex !== -1) {\n end = mathTagEndIndex;\n } else if (mathTagEndlineIndex === firstTagCloser - 1) {\n end = mathTagEndlineIndex;\n }\n\n const semanticsIndex = text.indexOf(semanticsTagStart, start);\n if (semanticsIndex !== -1) {\n const mmlTagStart = text.substring(start, semanticsIndex);\n const annotationIndex = text.indexOf(annotationTagStart, start);\n if (annotationIndex !== -1) {\n const startIndex = semanticsIndex + semanticsTagStart.length;\n const mmlContent = text.substring(startIndex, annotationIndex);\n output += mmlTagStart + mmlContent + mathTagEnd;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n end += mathTagEnd.length;\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n } else {\n end = start;\n start = text.indexOf(mathTagStart, start + mathTagStart.length);\n }\n }\n\n output += text.substring(end, text.length);\n return output;\n }\n\n /**\n * Returns true if a MathML contains a certain class.\n * @param {string} mathML - input MathML.\n * @param {string} className - className.\n * @returns {boolean} true if the input MathML contains the input class.\n * false otherwise.\n * @static\n */\n static containClass(mathML, className) {\n const classIndex = mathML.indexOf(\"class\");\n if (classIndex === -1) {\n return false;\n }\n const classTagEndIndex = mathML.indexOf(\">\", classIndex);\n const classTag = mathML.substring(classIndex, classTagEndIndex);\n if (classTag.indexOf(className) !== -1) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns true if mathml is empty. Otherwise, false.\n * @param {string} mathml - valid MathML with standard XML tags.\n * @returns {boolean} - true if mathml is empty. Otherwise, false.\n */\n static isEmpty(mathml) {\n // MathML can have the shape or ''.\n const closeTag = \">\";\n const closeTagInline = \"/>\";\n const firstCloseTagIndex = mathml.indexOf(closeTag);\n const firstCloseTagInlineIndex = mathml.indexOf(closeTagInline);\n let empty = false;\n // MathML is always empty in the second shape.\n if (firstCloseTagInlineIndex !== -1) {\n if (firstCloseTagInlineIndex === firstCloseTagIndex - 1) {\n empty = true;\n }\n }\n\n // MathML is always empty in the first shape when there aren't elements\n // between math tags.\n if (!empty) {\n const mathTagEndRegex = new RegExp(\"\");\n const mathTagEndArray = mathTagEndRegex.exec(mathml);\n if (mathTagEndArray) {\n empty = firstCloseTagIndex + 1 === mathTagEndArray.index;\n }\n }\n\n return empty;\n }\n\n /**\n * Encodes html entities inside properties.\n * @param {String} mathml - valid MathML with standard XML tags.\n * @returns {String} - 'mathml' with property entities encoded.\n */\n static encodeProperties(mathml) {\n // Search all the properties.\n const regex = /\\w+=\".*?\"/g;\n // Encode html entities.\n const replacer = (match) => {\n // It has the shape:\n // .\n const quoteIndex = match.indexOf('\"');\n const propertyValue = match.substring(quoteIndex + 1, match.length - 1);\n const propertyValueEncoded = Util.htmlEntities(propertyValue);\n const matchEncoded = `${match.substring(0, quoteIndex + 1)}${propertyValueEncoded}\"`;\n return matchEncoded;\n };\n\n const mathmlEncoded = mathml.replace(regex, replacer);\n return mathmlEncoded;\n }\n}\n","/**\n * This class represents the configuration class.\n * Usually used to retrieve configuration properties generated in the backend into the frontend.\n */\nexport default class Configuration {\n /**\n * Adds a properties object to {@link Configuration.properties}.\n * @param {Object} properties - properties to append to current properties.\n */\n static addConfiguration(properties) {\n Object.assign(Configuration.properties, properties);\n }\n\n /**\n * Static property.\n * The configuration properties object.\n * @private\n * @type {Object}\n */\n static get properties() {\n return Configuration._properties;\n }\n\n /**\n * Static property setter.\n * Set configuration properties.\n * @param {Object} value - The property value.\n * @ignore\n */\n static set properties(value) {\n Configuration._properties = value;\n }\n\n /**\n * Returns the value of a property key.\n * @param {String} key - Property key\n * @returns {String} Property value\n */\n static get(key) {\n if (!Object.prototype.hasOwnProperty.call(Configuration.properties, key)) {\n // Backwards compatibility.\n if (Object.prototype.hasOwnProperty.call(Configuration.properties, \"_wrs_conf_\")) {\n return Configuration.properties[`_wrs_conf_${key}`];\n }\n return false;\n }\n return Configuration.properties[key];\n }\n\n /**\n * Adds a new property to Configuration class.\n * @param {String} key - Property key.\n * @param {Object} value - Property value.\n */\n static set(key, value) {\n Configuration.properties[key] = value;\n }\n\n /**\n * Updates a property object value with new values.\n * @param {String} key - The property key to be updated.\n * @param {Object} propertyValue - Object containing the new values.\n */\n static update(key, propertyValue) {\n if (!Configuration.get(key)) {\n Configuration.set(key, propertyValue);\n } else {\n const updateProperty = Object.assign(Configuration.get(key), propertyValue);\n Configuration.set(key, updateProperty);\n }\n }\n}\n\n/**\n * Static properties object. Stores all configuration properties.\n * Needed to attribute accessors.\n * @private\n * @type {Object}\n */\nConfiguration._properties = {};\n","export default class TextCache {\n /**\n * @classdesc\n * This class represent a client-side text cache class. Contains pairs of\n * strings (key/value) which can be retrieved in any moment. Usually used\n * to store AJAX responses for text services like mathml2latex\n * (c.f {@link Latex} class) or mathml2accessible (c.f {@link Accessibility} class).\n * @constructs\n */\n constructor() {\n /**\n * Cache array property storing the cache entries.\n * @type {Array.}\n */\n this.cache = [];\n }\n\n /**\n * This method populates a key/value pair into the {@link this.cache} property.\n * @param {String} key - Cache key, usually the service string parameter.\n * @param {String} value - Cache value, usually the service response.\n */\n populate(key, value) {\n this.cache[key] = value;\n }\n\n /**\n * Returns the cache value associated to certain cache key.\n * @param {String} key - Cache key, usually the service string parameter.\n * @return {String} value - Cache value, if exists. False otherwise.\n */\n get(key) {\n if (Object.prototype.hasOwnProperty.call(this.cache, key)) {\n return this.cache[key];\n }\n return false;\n }\n}\n","/**\n * This object represents a custom listener.\n * @typedef {Object} Listener\n * @property {String} Listener.eventName - The listener name.\n * @property {Function} Listener.callback - The listener callback function.\n */\n\nexport default class Listeners {\n /**\n * @classdesc\n * This class represents a custom listeners manager.\n * @constructs\n */\n constructor() {\n /**\n * Array containing all custom listeners.\n * @type {Object[]}\n */\n this.listeners = [];\n }\n\n /**\n * Add a listener to Listener class.\n * @param {Object} listener - A listener object.\n */\n add(listener) {\n this.listeners.push(listener);\n }\n\n /**\n * Fires MathType event listeners\n * @param {String} eventName - event name\n * @param {Event} event - event object.\n * @return {boolean} false if event has been prevented. true otherwise.\n */\n fire(eventName, event) {\n for (let i = 0; i < this.listeners.length && !event.cancelled; i += 1) {\n if (this.listeners[i].eventName === eventName) {\n // Calling listener.\n this.listeners[i].callback(event);\n }\n }\n return event.defaultPrevented;\n }\n\n /**\n * Creates a new listener object.\n * @param {string} eventName - Event name.\n * @param {Object} callback - Callback function.\n * @returns {object} the listener object.\n */\n static newListener(eventName, callback) {\n const listener = {};\n listener.eventName = eventName;\n listener.callback = callback;\n return listener;\n }\n}\n","import Util from \"./util\";\nimport Listeners from \"./listeners\";\nimport Configuration from \"./configuration\";\n\n/**\n * @typedef {Object} ServiceProviderProperties\n * @property {String} URI - Service URI.\n * @property {String} server - Service server language.\n */\n\n/**\n * @classdesc\n * Class representing a serviceProvider. A serviceProvider is a class containing\n * an arbitrary number of services with the correspondent path.\n */\nexport default class ServiceProvider {\n /**\n * Returns Service Provider listeners.\n * @type {Listeners}\n */\n static get listeners() {\n return ServiceProvider._listeners;\n }\n\n /**\n * Adds a {@link Listener} instance to {@link ServiceProvider} class.\n * @param {Listener} listener - Instance of {@link Listener}.\n */\n static addListener(listener) {\n ServiceProvider.listeners.add(listener);\n }\n\n /**\n * Fires events in Service Provider.\n * @param {String} eventName - Event name.\n * @param {Event} event - Event object.\n */\n static fireEvent(eventName, event) {\n ServiceProvider.listeners.fire(eventName, event);\n }\n\n /**\n * Service parameters.\n * @type {ServiceProviderProperties}\n *\n */\n static get parameters() {\n return ServiceProvider._parameters;\n }\n\n /**\n * Service parameters.\n * @private\n * @type {ServiceProviderProperties}\n */\n static set parameters(parameters) {\n ServiceProvider._parameters = parameters;\n }\n\n /**\n * Static property.\n * Return service provider paths.\n * @private\n * @type {String}\n */\n static get servicePaths() {\n return ServiceProvider._servicePaths;\n }\n\n /**\n * Static property setter.\n * Set service paths.\n * @param {String} value - The property value.\n * @ignore\n */\n static set servicePaths(value) {\n ServiceProvider._servicePaths = value;\n }\n\n /**\n * Adds a new service to the ServiceProvider.\n * @param {String} service - Service name.\n * @param {String} path - Service path.\n * @static\n */\n static setServicePath(service, path) {\n ServiceProvider.servicePaths[service] = path;\n }\n\n /**\n * Returns the service path for a certain service.\n * @param {String} serviceName - Service name.\n * @returns {String} The service path.\n * @static\n */\n static getServicePath(serviceName) {\n return ServiceProvider.servicePaths[serviceName];\n }\n\n /**\n * Static property.\n * Service provider integration path.\n * @type {String}\n */\n static get integrationPath() {\n return ServiceProvider._integrationPath;\n }\n\n /**\n * Static property setter.\n * Set service provider integration path.\n * @param {String} value - The property value.\n * @ignore\n */\n static set integrationPath(value) {\n ServiceProvider._integrationPath = value;\n }\n\n /**\n * Returns the server URL in the form protocol://serverName:serverPort.\n * @return {String} The client side server path.\n */\n static getServerURL() {\n const url = window.location.href;\n const arr = url.split(\"/\");\n const result = `${arr[0]}//${arr[2]}`;\n return result;\n }\n\n /**\n * Inits {@link this} class. Uses {@link this.integrationPath} as\n * base path to generate all backend services paths.\n * @param {Object} parameters - Function parameters.\n * @param {String} parameters.integrationPath - Service path.\n */\n static init(parameters) {\n ServiceProvider.parameters = parameters;\n // Services path (tech dependant).\n let configurationURI = ServiceProvider.createServiceURI(\"configurationjs\");\n let createImageURI = ServiceProvider.createServiceURI(\"createimage\");\n let showImageURI = ServiceProvider.createServiceURI(\"showimage\");\n let getMathMLURI = ServiceProvider.createServiceURI(\"getmathml\");\n let serviceURI = ServiceProvider.createServiceURI(\"service\");\n\n // Some backend integrations (like Java o Ruby) have an absolute backend path,\n // for example: /app/service. For them we calculate the absolute URL path, i.e\n // protocol://domain:port/app/service\n if (ServiceProvider.parameters.URI.indexOf(\"/\") === 0) {\n const serverPath = ServiceProvider.getServerURL();\n configurationURI = serverPath + configurationURI;\n showImageURI = serverPath + showImageURI;\n createImageURI = serverPath + createImageURI;\n getMathMLURI = serverPath + getMathMLURI;\n serviceURI = serverPath + serviceURI;\n }\n\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n ServiceProvider.setServicePath(\"showimage\", showImageURI);\n ServiceProvider.setServicePath(\"createimage\", createImageURI);\n ServiceProvider.setServicePath(\"service\", serviceURI);\n ServiceProvider.setServicePath(\"getmathml\", getMathMLURI);\n ServiceProvider.setServicePath(\"configurationjs\", configurationURI);\n\n ServiceProvider.listeners.fire(\"onInit\", {});\n }\n\n /**\n * Gets the content from an URL.\n * @param {String} url - Target URL.\n * @param {Object} [postVariables] - Object containing post variables.\n * null if a GET query should be done.\n * @returns {String} Content of the target URL.\n * @private\n * @static\n */\n static getUrl(url, postVariables) {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n const httpRequest = Util.createHttpRequest();\n\n if (httpRequest) {\n if (typeof postVariables === \"undefined\" || typeof postVariables === \"undefined\") {\n httpRequest.open(\"GET\", url, false);\n } else if (url.substr(0, 1) === \"/\" || url.substr(0, 7) === \"http://\" || url.substr(0, 8) === \"https://\") {\n httpRequest.open(\"POST\", url, false);\n } else {\n httpRequest.open(\"POST\", currentPath + url, false);\n }\n\n let header = Configuration.get(\"customHeaders\");\n if (header) {\n if (typeof header === \"string\") {\n header = Util.convertStringToObject(header);\n }\n Object.entries(header).forEach(([key, val]) => httpRequest.setRequestHeader(key, val));\n }\n\n if (typeof postVariables !== \"undefined\" && postVariables) {\n httpRequest.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n httpRequest.send(Util.httpBuildQuery(postVariables));\n } else {\n httpRequest.send(null);\n }\n\n return httpRequest.responseText;\n }\n return \"\";\n }\n\n /**\n * Returns the response text of a certain service.\n * @param {String} service - Service name.\n * @param {String} postVariables - Post variables.\n * @param {Boolean} get - True if the request is GET instead of POST. false otherwise.\n * @returns {String} Service response text.\n */\n static getService(service, postVariables, get) {\n let response;\n if (get === true) {\n const getVariables = postVariables ? `?${postVariables}` : \"\";\n const serviceUrl = `${ServiceProvider.getServicePath(service)}${getVariables}`;\n response = ServiceProvider.getUrl(serviceUrl);\n } else {\n const serviceUrl = ServiceProvider.getServicePath(service);\n response = ServiceProvider.getUrl(serviceUrl, postVariables);\n }\n return response;\n }\n\n /**\n * Returns the server language of a certain service. The possible values\n * are: php, aspx, java and ruby.\n * This method has backward compatibility purposes.\n * @param {String} service - The configuration service.\n * @returns {String} - The server technology associated with the configuration service.\n */\n static getServerLanguageFromService(service) {\n if (service.indexOf(\".php\") !== -1) {\n return \"php\";\n }\n if (service.indexOf(\".aspx\") !== -1) {\n return \"aspx\";\n }\n if (service.indexOf(\"wirispluginengine\") !== -1) {\n return \"ruby\";\n }\n return \"java\";\n }\n\n /**\n * Returns the URI associated with a certain service.\n * @param {String} service - The service name.\n * @return {String} The service path.\n */\n static createServiceURI(service) {\n const extension = ServiceProvider.serverExtension();\n return Util.concatenateUrl(ServiceProvider.parameters.URI, service) + extension;\n }\n\n static serverExtension() {\n if (ServiceProvider.parameters.server.indexOf(\"php\") !== -1) {\n return \".php\";\n }\n if (ServiceProvider.parameters.server.indexOf(\"aspx\") !== -1) {\n return \".aspx\";\n }\n return \"\";\n }\n}\n\n/**\n * @property {String} service - The service name.\n * @property {String} path - The service path.\n * @static\n */\nServiceProvider._servicePaths = {};\n\n/**\n * The integration path. Contains the path of the configuration service.\n * Used to define the path for all services.\n * @type {String}\n * @private\n */\nServiceProvider._integrationPath = \"\";\n\n/**\n * ServiceProvider static listeners.\n * @type {Listeners}\n * @private\n */\nServiceProvider._listeners = new Listeners();\n\n/**\n * Service provider parameters.\n * @type {ServiceProviderParameters}\n */\nServiceProvider._parameters = {};\n","import TextCache from \"./textcache\";\nimport MathML from \"./mathml\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Constants from \"./constants\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents a LaTeX parser. Manages the services which allows to convert\n * LaTeX into MathML and MathML into LaTeX.\n */\nexport default class Latex {\n /**\n * Static property.\n * Return latex cache.\n * @private\n * @type {Cache}\n */\n static get cache() {\n return Latex._cache;\n }\n\n /**\n * Static property setter.\n * Set latex cache.\n * @param {Cache} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Latex._cache = value;\n }\n\n /**\n * Converts MathML to LaTeX by calling mathml2latex service. For text services\n * we call a text service with the param mathml2latex.\n * @param {String} mathml - MathML String.\n * @return {String} LaTeX string generated by the MathML argument.\n */\n static getLatexFromMathML(mathml) {\n const mathmlWithoutSemantics = MathML.removeSemantics(mathml);\n /**\n * @type {TextCache}\n */\n const { cache } = Latex;\n\n const data = {\n service: \"mathml2latex\",\n mml: mathmlWithoutSemantics,\n };\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n // TODO: Error handling.\n let latex = \"\";\n\n if (jsonResponse.status === \"ok\") {\n latex = jsonResponse.result.text;\n const latexHtmlEntitiesEncoded = Util.htmlEntities(latex);\n // Inserting LaTeX semantics.\n const mathmlWithSemantics = MathML.addAnnotation(mathml, latexHtmlEntitiesEncoded, \"LaTeX\");\n cache.populate(latex, mathmlWithSemantics);\n }\n\n return latex;\n }\n\n /**\n * Converts LaTeX to MathML by calling latex2mathml service. For text services\n * we call a text service with the param latex2mathml.\n * @param {String} latex - String containing a LaTeX formula.\n * @param {Boolean} includeLatexOnSemantics\n * - If true LaTeX would me included into MathML semantics.\n * @return {String} MathML string generated by the LaTeX argument.\n */\n static getMathMLFromLatex(latex, includeLatexOnSemantics) {\n /**\n * @type {TextCache}\n */\n const latexCache = Latex.cache;\n\n if (Latex.cache.get(latex)) {\n return Latex.cache.get(latex);\n }\n const data = {\n service: \"latex2mathml\",\n latex,\n };\n\n if (includeLatexOnSemantics) {\n data.saveLatex = \"\";\n }\n\n const jsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n\n let output;\n if (jsonResponse.status === \"ok\") {\n let mathml = jsonResponse.result.text;\n mathml = mathml.split(\"\\r\").join(\"\").split(\"\\n\").join(\" \");\n\n // Populate LatexCache.\n if (mathml.indexOf(\"semantics\") === -1 && mathml.indexOf(\"annotation\") === -1) {\n const content = Util.htmlEntities(latex);\n mathml = MathML.addAnnotation(mathml, content, \"LaTeX\");\n output = mathml;\n } else {\n output = mathml;\n }\n if (!latexCache.get(latex)) {\n latexCache.populate(latex, mathml);\n }\n } else {\n output = `$$${latex}$$`;\n }\n return output;\n }\n\n /**\n * Converts all occurrences of MathML code to LaTeX.\n * The MathML code should containing to be converted.\n * @param {String} content - A string containing MathML valid code.\n * @param {Object} characters - An object containing special characters.\n * @return {String} A string containing all MathML annotated occurrences\n * replaced by the corresponding LaTeX code.\n */\n static parseMathmlToLatex(content, characters) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n const openTarget = `${characters.tagOpener}annotation encoding=${characters.doubleQuote}LaTeX${characters.doubleQuote}${characters.tagCloser}`;\n const closeTarget = `${characters.tagOpener}/annotation${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n let mathml;\n let startAnnotation;\n let closeAnnotation;\n\n while (start !== -1) {\n output += content.substring(end, start);\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else {\n end += mathTagEnd.length;\n }\n\n mathml = content.substring(start, end);\n\n startAnnotation = mathml.indexOf(openTarget);\n if (startAnnotation !== -1) {\n startAnnotation += openTarget.length;\n closeAnnotation = mathml.indexOf(closeTarget);\n let latex = mathml.substring(startAnnotation, closeAnnotation);\n if (characters === Constants.safeXmlCharacters) {\n latex = MathML.safeXmlDecode(latex);\n }\n output += `$$${latex}$$`;\n // Populate latex into cache.\n\n Latex.cache.populate(latex, mathml);\n } else {\n output += mathml;\n }\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n\n /**\n * Extracts the latex of a determined position in a text.\n * @param {Node} textNode - textNode to extract the LaTeX\n * @param {Number} caretPosition - Starting position to find LaTeX.\n * @param {Object} latexTags - Optional parameter representing tags between latex is inserted.\n * It has the 'open' attribute for the open tag and the 'close' attribute for the close tag.\n * \"$$\" by default.\n * @return {Object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.\n * @static\n */\n static getLatexFromTextNode(textNode, caretPosition, latexTags) {\n // TODO: Set LaTeX Tags as Core variable. Fix the call to this function (third argument).\n // Tags used for LaTeX formulas.\n const defaultLatexTags = {\n open: \"$$\",\n close: \"$$\",\n };\n // latexTags is an optional parameter. If is not set, use default latexTags.\n if (typeof latexTags === \"undefined\" || latexTags == null) {\n latexTags = defaultLatexTags;\n }\n // Looking for the first textNode.\n let startNode = textNode;\n\n while (startNode.previousSibling && startNode.previousSibling.nodeType === 3) {\n // TEXT_NODE.\n startNode = startNode.previousSibling;\n }\n\n /**\n * Returns the next latex position and node from a specific node and position.\n * @param {Node} currentNode - Node where searching latex.\n * @param {Number} currentPosition - Current position inside the currentNode.\n * @param {Object} latexTagsToUse - Tags used at latex beginning and latex final.\n * \"$$\" by default.\n * @param {Boolean} tag - Tag containing the current search.\n * @returns {Object} Object containing the current node and the position.\n */\n function getNextLatexPosition(currentNode, currentPosition, tag) {\n let position = currentNode.nodeValue.indexOf(tag, currentPosition);\n\n while (position === -1) {\n currentNode = currentNode.nextSibling;\n\n if (!currentNode) {\n // TEXT_NODE.\n return null; // Not found.\n }\n\n position = currentNode.nodeValue ? currentNode.nodeValue.indexOf(latexTags.close) : -1;\n }\n\n return {\n node: currentNode,\n position,\n };\n }\n\n /**\n * Determines if a node is previous, or not, to a second one.\n * @param {Node} node - Start node.\n * @param {Number} position - Start node position.\n * @param {Node} endNode - End node.\n * @param {Number} endPosition - End node position.\n * @returns {Boolean} True if the starting node is previous thant the en node. false otherwise.\n */\n function isPrevious(node, position, endNode, endPosition) {\n if (node === endNode) {\n return position <= endPosition;\n }\n while (node && node !== endNode) {\n node = node.nextSibling;\n }\n\n return node === endNode;\n }\n\n let start;\n let end = {\n node: startNode,\n position: 0,\n };\n // Is supposed that open and close tags has the same length.\n const tagLength = latexTags.open.length;\n do {\n start = getNextLatexPosition(end.node, end.position, latexTags.open);\n\n if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {\n return null;\n }\n\n end = getNextLatexPosition(start.node, start.position + tagLength, latexTags.close);\n\n if (end == null) {\n return null;\n }\n\n end.position += tagLength;\n } while (isPrevious(end.node, end.position, textNode, caretPosition));\n\n // Isolating latex.\n let latex;\n\n if (start.node === end.node) {\n latex = start.node.nodeValue.substring(start.position + tagLength, end.position - tagLength);\n } else {\n const index = start.position + tagLength;\n latex = start.node.nodeValue.substring(index, start.node.nodeValue.length);\n let currentNode = start.node;\n\n do {\n currentNode = currentNode.nextSibling;\n if (currentNode === end.node) {\n latex += end.node.nodeValue.substring(0, end.position - tagLength);\n } else {\n latex += currentNode.nodeValue ? currentNode.nodeValue : \"\";\n }\n } while (currentNode !== end.node);\n }\n\n return {\n latex,\n startNode: start.node,\n startPosition: start.position,\n endNode: end.node,\n endPosition: end.position,\n };\n }\n}\n\n/**\n * Text cache. Stores all processed LaTeX strings and it's correspondent MathML string.\n * @type {Cache}\n * @static\n */\nLatex._cache = new TextCache();\n","import translations from \"../lang/strings.json\";\n/**\n * This class represents a string manager. It's used to load localized strings.\n */\nexport default class StringManager {\n constructor() {\n throw new Error(\"Static class StringManager can not be instantiated.\");\n }\n\n /**\n * Returns the associated value of certain string key. If the associated value\n * doesn't exits returns the original key.\n * @param {string} key - string key\n * @param {string} lang - DEFAULT = null. Specify the language to translate the string\n * @returns {string} correspondent value. If doesn't exists original key.\n */\n static get(key, lang) {\n // Default language definition\n let { language } = this;\n\n // If parameter language, use it\n if (lang) {\n language = lang;\n }\n\n // Cut down on strings. e.g. en_US -> en\n if (language && language.length > 2) {\n language = language.slice(0, 2);\n }\n\n // Check if we support the language\n if (!this.strings.hasOwnProperty(language)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown language ${language} set in StringManager.`);\n language = \"en\";\n }\n\n // Check if the key is supported in the given language\n if (!this.strings[language].hasOwnProperty(key)) {\n // eslint-disable-line no-prototype-builtins\n console.warn(`Unknown key ${key} for language ${language} in StringManager.`);\n return key;\n }\n\n return this.strings[language][key];\n }\n}\n\n/**\n * Dictionary of dictionaries:\n * Key: language code\n * Value: Key: id of the string\n * Value: translation of the string\n */\nStringManager.strings = translations;\n\n/**\n * Language of the translations; English by default\n */\nStringManager.language = \"en\";\n","/* eslint-disable no-bitwise */\nimport DOMPurify from \"dompurify\";\nimport MathML from \"./mathml\";\nimport Configuration from \"./configuration\";\nimport Latex from \"./latex\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * This class represents an utility class.\n */\nexport default class Util {\n /**\n * Fires an event in a target.\n * @param {EventTarget} eventTarget - target where event should be fired.\n * @param {string} eventName event to fire.\n * @static\n */\n static fireEvent(eventTarget, eventName) {\n if (document.createEvent) {\n const eventObject = document.createEvent(\"HTMLEvents\");\n eventObject.initEvent(eventName, true, true);\n return !eventTarget.dispatchEvent(eventObject);\n }\n\n const eventObject = document.createEventObject();\n return eventTarget.fireEvent(`on${eventName}`, eventObject);\n }\n\n /**\n * Cross-browser addEventListener/attachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - callback function.\n * @static\n */\n static addEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.addEventListener) {\n eventTarget.addEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.attachEvent) {\n // Backwards compatibility.\n eventTarget.attachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * Cross-browser removeEventListener/detachEvent function.\n * @param {EventTarget} eventTarget - target to add the event.\n * @param {string} eventName - specifies the type of event.\n * @param {Function} callBackFunction - function to remove from the event target.\n * @static\n */\n static removeEvent(eventTarget, eventName, callBackFunction) {\n if (eventTarget.removeEventListener) {\n eventTarget.removeEventListener(eventName, callBackFunction, true);\n } else if (eventTarget.detachEvent) {\n eventTarget.detachEvent(`on${eventName}`, callBackFunction);\n }\n }\n\n /**\n * A map from event target to event handlers so we can remove the event\n * listeners in removeElementEvents\n *\n * @type {Map}\n * @static\n */\n static elementEventsMap = new Map();\n\n /**\n * Adds the a callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @param {Function} doubleClickHandler - function to run when on dblclick event.\n * @param {Function} mousedownHandler - function to run when on mousedown event.\n * @param {Function} mouseupHandler - function to run when on mouseup event.\n * @static\n */\n static addElementEvents(eventTarget, doubleClickHandler, mousedownHandler, mouseupHandler) {\n // Make sure not to leak event listeners if we've already added events to\n // this element\n Util.removeElementEvents(eventTarget);\n\n let entry = {};\n Util.elementEventsMap.set(eventTarget, entry);\n\n if (doubleClickHandler) {\n entry.callbackDblclick = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n doubleClickHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n }\n\n if (mousedownHandler) {\n entry.callbackMousedown = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mousedownHandler(element, realEvent);\n };\n\n Util.addEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n }\n\n if (mouseupHandler) {\n entry.callbackMouseup = (event) => {\n const realEvent = event || window.event;\n const element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;\n mouseupHandler(element, realEvent);\n };\n // Chrome doesn't trigger this event for eventTarget if we release the mouse button\n // while the mouse is outside the editor text field.\n // This is a workaround: we trigger the event independently of where the mouse\n // is when we release its button.\n Util.addEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.addEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n }\n\n /**\n * Remove all callback function, for a certain event target, to the following event types:\n * - dblclick\n * - mousedown\n * - mouseup\n * @param {EventTarget} eventTarget - event target.\n * @static\n */\n static removeElementEvents(eventTarget) {\n let entry = Util.elementEventsMap.get(eventTarget);\n if (!entry) {\n return;\n }\n\n Util.elementEventsMap.delete(eventTarget);\n\n Util.removeEvent(eventTarget, \"dblclick\", entry.callbackDblclick);\n Util.removeEvent(eventTarget, \"mousedown\", entry.callbackMousedown);\n Util.removeEvent(document, \"mouseup\", entry.callbackMouseup);\n Util.removeEvent(eventTarget, \"mouseup\", entry.callbackMouseup);\n }\n\n /**\n * Adds a class name to a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static addClass(element, className) {\n if (!Util.containsClass(element, className)) {\n element.className += ` ${className}`;\n }\n }\n\n /**\n * Checks if a HTMLElement contains a certain class.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the className.\n * @returns {boolean} true if the HTMLElement contains the class name. false otherwise.\n * @static\n */\n static containsClass(element, className) {\n if (element == null || !(\"className\" in element)) {\n return false;\n }\n\n const currentClasses = element.className.split(\" \");\n\n for (let i = currentClasses.length - 1; i >= 0; i -= 1) {\n if (currentClasses[i] === className) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Remove a certain class for a HTMLElement.\n * @param {HTMLElement} element - the HTML element.\n * @param {string} className - the class name.\n * @static\n */\n static removeClass(element, className) {\n let newClassName = \"\";\n const classes = element.className.split(\" \");\n\n for (let i = 0; i < classes.length; i += 1) {\n if (classes[i] !== className) {\n newClassName += `${classes[i]} `;\n }\n }\n element.className = newClassName.trim();\n }\n\n /**\n * Converts old xml initial text attribute (with ยซยป) to the correct one(with ยงlt;ยงgt;). It's\n * used to parse old applets.\n * @param {string} text - string containing safeXml characters\n * @returns {string} a string with safeXml characters parsed.\n * @static\n */\n static convertOldXmlinitialtextAttribute(text) {\n // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.\n // This could be removed in future.\n const val = \"value=\";\n\n const xitpos = text.indexOf(\"xmlinitialtext\");\n const valpos = text.indexOf(val, xitpos);\n const quote = text.charAt(valpos + val.length);\n const startquote = valpos + val.length + 1;\n const endquote = text.indexOf(quote, startquote);\n\n const value = text.substring(startquote, endquote);\n\n let newvalue = value.split(\"ยซ\").join(\"ยงlt;\");\n newvalue = newvalue.split(\"ยป\").join(\"ยงgt;\");\n newvalue = newvalue.split(\"&\").join(\"ยง\");\n newvalue = newvalue.split(\"ยจ\").join(\"ยงquot;\");\n\n text = text.split(value).join(newvalue);\n return text;\n }\n\n /**\n * Convert a string representation of key-value pairs to an object.\n * @param {string} keyValueString - String with key-value pairs in the format key1='value1', key2='value2'\n * @returns {Object} - Object containing the key-value pairs\n */\n static convertStringToObject(keyValueString) {\n if (!keyValueString || typeof keyValueString !== \"string\") {\n return {};\n }\n\n return keyValueString\n .split(\",\")\n .map((pair) => pair.trim().split(\"=\"))\n .reduce((resultObject, [key, value]) => {\n if (key && value) {\n resultObject[key] = value;\n }\n return resultObject;\n }, {});\n }\n\n /**\n * Cross-browser solution for creating new elements.\n * @param {string} tagName - tag name of the wished element.\n * @param {Object} attributes - an object where each key is a wished\n * attribute name and each value is its value.\n * @param {Object} [creator] - if supplied, this function will use\n * the \"createElement\" method from this param. Otherwise\n * document will be used as creator.\n * @returns {Element} The DOM element with the specified attributes assigned.\n * @static\n */\n static createElement(tagName, attributes, creator) {\n if (attributes === undefined) {\n attributes = {};\n }\n\n if (creator === undefined) {\n creator = document;\n }\n\n let element;\n\n /*\n * Internet Explorer fix:\n * If you create a new object dynamically, you can't set a non-standard attribute.\n * For example, you can't set the \"src\" attribute on an \"applet\" object.\n * Other browsers will throw an exception and will run the standard code.\n */\n try {\n let html = `<${tagName}`;\n\n Object.keys(attributes).forEach((attributeName) => {\n html += ` ${attributeName}=\"${Util.htmlEntities(attributes[attributeName])}\"`;\n });\n html += \">\";\n element = creator.createElement(html);\n } catch (e) {\n element = creator.createElement(tagName);\n Object.keys(attributes).forEach((attributeName) => {\n element.setAttribute(attributeName, attributes[attributeName]);\n });\n }\n return element;\n }\n\n /**\n * Creates new HTML from it's HTML code as string.\n * @param {string} objectCode - html code\n * @returns {Element} the HTML element.\n * @static\n */\n static createObject(objectCode, creator) {\n if (creator === undefined) {\n creator = document;\n }\n\n // Internet Explorer can't include \"param\" tag when is setting an innerHTML property.\n objectCode = objectCode\n .split(\"\").join(\"\").split(\"\").join(\"\");\n\n objectCode = objectCode\n .split(\"\").join(\"
\").split(\"\").join(\"
\");\n\n const container = Util.createElement(\"div\", {}, creator);\n container.innerHTML = objectCode;\n\n function recursiveParamsFix(object) {\n if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisParam\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const param = Util.createElement(\"param\", attributesParsed, creator);\n\n // IE fix.\n if (param.NAME) {\n param.name = param.NAME;\n param.value = param.VALUE;\n }\n\n param.removeAttribute(\"wirisObject\");\n object.parentNode.replaceChild(param, object);\n } else if (object.getAttribute && object.getAttribute(\"wirisObject\") === \"WirisApplet\") {\n const attributesParsed = {};\n\n for (let i = 0; i < object.attributes.length; i += 1) {\n if (object.attributes[i].nodeValue !== null) {\n attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;\n }\n }\n\n const applet = Util.createElement(\"applet\", attributesParsed, creator);\n applet.removeAttribute(\"wirisObject\");\n\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n\n if (object.childNodes[i].nodeName.toLowerCase() === \"param\") {\n applet.appendChild(object.childNodes[i]);\n i -= 1; // When we insert the object child into the applet, object loses one child.\n }\n }\n\n object.parentNode.replaceChild(applet, object);\n } else {\n for (let i = 0; i < object.childNodes.length; i += 1) {\n recursiveParamsFix(object.childNodes[i]);\n }\n }\n }\n\n recursiveParamsFix(container);\n return container.firstChild;\n }\n\n /**\n * Converts an Element to its HTML code.\n * @param {Element} element - entry element.\n * @return {string} the HTML code of the input element.\n * @static\n */\n static createObjectCode(element) {\n // In case that the image was not created, the object can be null or undefined.\n if (typeof element === \"undefined\" || element === null) {\n return null;\n }\n\n if (element.nodeType === 1) {\n // ELEMENT_NODE.\n let output = `<${element.tagName}`;\n\n for (let i = 0; i < element.attributes.length; i += 1) {\n if (element.attributes[i].specified) {\n output += ` ${element.attributes[i].name}=\"${Util.htmlEntities(element.attributes[i].value)}\"`;\n }\n }\n\n if (element.childNodes.length > 0) {\n output += \">\";\n\n for (let i = 0; i < element.childNodes.length; i += 1) {\n output += Util.createObject(element.childNodes[i]);\n }\n\n output += ``;\n } else if (element.nodeName === \"DIV\" || element.nodeName === \"SCRIPT\") {\n output += `>`;\n } else {\n output += \"/>\";\n }\n\n return output;\n }\n\n if (element.nodeType === 3) {\n // TEXT_NODE.\n return Util.htmlEntities(element.nodeValue);\n }\n\n return \"\";\n }\n\n /**\n * Concatenates two URL paths.\n * @param {string} path1 - first URL path\n * @param {string} path2 - second URL path\n * @returns {string} new URL.\n */\n static concatenateUrl(path1, path2) {\n let separator = \"\";\n if (path1.indexOf(\"/\") !== path1.length && path2.indexOf(\"/\") !== 0) {\n separator = \"/\";\n }\n return (path1 + separator + path2).replace(/([^:]\\/)\\/+/g, \"$1\");\n }\n\n /**\n * Parses a text and replaces all HTML special characters by their correspondent entities.\n * @param {string} input - text to be parsed.\n * @returns {string} the input text with all their special characters replaced by their entities.\n * @static\n */\n static htmlEntities(input) {\n return input.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\").split('\"').join(\""\");\n }\n\n /**\n * Sanitize HTML to prevent XSS injections.\n * @param {string} html - html to be sanitize.\n * @returns {string} html sanitized.\n * @static\n */\n static htmlSanitize(html) {\n const annotationRegex = /\\/;\n // Get all the annotation content including the tags.\n const annotation = html.match(annotationRegex);\n // Sanitize html code without removing our supported MathML tags and attributes.\n html = DOMPurify.sanitize(html, {\n ADD_TAGS: [\"semantics\", \"annotation\", \"mstack\", \"msline\", \"msrow\", \"none\"],\n ADD_ATTR: [\"linebreak\", \"charalign\", \"stackalign\"],\n });\n // Readd old annotation content.\n return html.replace(annotationRegex, annotation);\n }\n\n /**\n * Parses a text and replaces all the HTML entities by their characters.\n * @param {string} input - text to be parsed\n * @returns {string} the input text with all their entities replaced by characters.\n * @static\n */\n static htmlEntitiesDecode(input) {\n // Textarea element decodes when inner html is set.\n const textarea = document.createElement(\"textarea\");\n textarea.innerHTML = input;\n return textarea.value;\n }\n\n /**\n * Returns a cross-browser http request.\n * @return {object} httpRequest request object.\n * @returns {XMLHttpRequest|ActiveXObject} the proper request object.\n */\n static createHttpRequest() {\n const currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf(\"/\") + 1);\n if (currentPath.substr(0, 7) === \"file://\") {\n throw StringManager.get(\"exception_cross_site\");\n }\n\n if (typeof XMLHttpRequest !== \"undefined\") {\n return new XMLHttpRequest();\n }\n\n try {\n return new ActiveXObject(\"Msxml2.XMLHTTP\");\n } catch (e) {\n try {\n return new ActiveXObject(\"Microsoft.XMLHTTP\");\n } catch (oc) {\n return null;\n }\n }\n }\n\n /**\n * Converts a hash to a HTTP query.\n * @param {Object[]} properties - a key/value object.\n * @returns {string} a HTTP query containing all the key value pairs with\n * all the special characters replaced by their entities.\n * @static\n */\n static httpBuildQuery(properties) {\n let result = \"\";\n\n Object.keys(properties).forEach((i) => {\n if (properties[i] != null) {\n result += `${Util.urlEncode(i)}=${Util.urlEncode(properties[i])}&`;\n }\n });\n\n // Deleting last '&' empty character.\n if (result.substring(result.length - 1) === \"&\") {\n result = result.substring(0, result.length - 1);\n }\n\n return result;\n }\n\n /**\n * Convert a hash to string sorting keys to get a deterministic output\n * @param {Object[]} hash - a key/value object.\n * @returns {string} a string with the form key1=value1...keyn=valuen\n * @static\n */\n static propertiesToString(hash) {\n // 1. Sort keys. We sort the keys because we want a deterministic output.\n const keys = [];\n Object.keys(hash).forEach((key) => {\n if (Object.prototype.hasOwnProperty.call(hash, key)) {\n keys.push(key);\n }\n });\n\n const n = keys.length;\n for (let i = 0; i < n; i += 1) {\n for (let j = i + 1; j < n; j += 1) {\n const s1 = keys[i];\n const s2 = keys[j];\n if (Util.compareStrings(s1, s2) > 0) {\n // Swap.\n keys[i] = s2;\n keys[j] = s1;\n }\n }\n }\n\n // 2. Generate output.\n let output = \"\";\n for (let i = 0; i < n; i += 1) {\n const key = keys[i];\n output += key;\n output += \"=\";\n let value = hash[key];\n value = value.replace(\"\\\\\", \"\\\\\\\\\");\n value = value.replace(\"\\n\", \"\\\\n\");\n value = value.replace(\"\\r\", \"\\\\r\");\n value = value.replace(\"\\t\", \"\\\\t\");\n\n output += value;\n output += \"\\n\";\n }\n return output;\n }\n\n /**\n * Compare two strings using charCodeAt method\n * @param {string} a - first string to compare.\n * @param {string} b - second string to compare.\n * @returns {number} the difference between a and b\n * @static\n */\n static compareStrings(a, b) {\n let i;\n const an = a.length;\n const bn = b.length;\n const n = an > bn ? bn : an;\n for (i = 0; i < n; i += 1) {\n const c = Util.fixedCharCodeAt(a, i) - Util.fixedCharCodeAt(b, i);\n if (c !== 0) {\n return c;\n }\n }\n return a.length - b.length;\n }\n\n /**\n * Fix charCodeAt() JavaScript function to handle non-Basic-Multilingual-Plane characters.\n * @param {string} string - input string\n * @param {number} idx - an integer greater than or equal to 0\n * and less than the length of the string\n * @returns {number} an integer representing the UTF-16 code of the string at the given index.\n * @static\n */\n static fixedCharCodeAt(string, idx) {\n idx = idx || 0;\n const code = string.charCodeAt(idx);\n let hi;\n let low;\n\n /* High surrogate (could change last hex to 0xDB7F to treat high\n private surrogates as single characters) */\n\n if (code >= 0xd800 && code <= 0xdbff) {\n hi = code;\n low = string.charCodeAt(idx + 1);\n if (Number.isNaN(low)) {\n throw StringManager.get(\"exception_high_surrogate\");\n }\n return (hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;\n }\n\n if (code >= 0xdc00 && code <= 0xdfff) {\n // Low surrogate.\n /* We return false to allow loops to skip this iteration since should have\n already handled high surrogate above in the previous iteration. */\n return false;\n }\n return code;\n }\n\n /**\n * Returns an URL with it's query params converted into array.\n * @param {string} url - input URL.\n * @returns {Object[]} an array containing all URL query params.\n * @static\n */\n static urlToAssArray(url) {\n let i;\n i = url.indexOf(\"?\");\n if (i > 0) {\n const query = url.substring(i + 1);\n const ss = query.split(\"&\");\n const h = {};\n for (i = 0; i < ss.length; i += 1) {\n const s = ss[i];\n const kv = s.split(\"=\");\n if (kv.length > 1) {\n h[kv[0]] = decodeURIComponent(kv[1].replace(/\\+/g, \" \"));\n }\n }\n return h;\n }\n return {};\n }\n\n /**\n * Returns an encoded URL by replacing each instance of certain characters by\n * one, two, three or four escape sequences using encodeURIComponent method.\n * !'()* . will not be encoded.\n *\n * @param {string} clearString - URL string to be encoded\n * @returns {string} URL with it's special characters replaced.\n * @static\n */\n static urlEncode(clearString) {\n let output = \"\";\n // Method encodeURIComponent doesn't encode !'()*~ .\n output = encodeURIComponent(clearString);\n return output;\n }\n\n // TODO: To parser?\n /**\n * Converts the HTML of a image into the output code that WIRIS must return.\n * By default returns the MathML stored on data-mahml attribute (if imgCode is a formula)\n * or the Wiriscas attribute of a WIRIS applet.\n * @param {string} imgCode - the html code from a formula or a CAS image.\n * @param {boolean} convertToXml - true if the image should be converted to XML.\n * @param {boolean} convertToSafeXml - true if the image should be converted to safeXML.\n * @returns {string} the XML or safeXML of a WIRIS image.\n * @static\n */\n static getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {\n const imgObject = Util.createObject(imgCode);\n if (imgObject) {\n if (\n imgObject.className === Configuration.get(\"imageClassName\") ||\n imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"))\n ) {\n if (!convertToXml) {\n return imgCode;\n }\n\n const dataMathML = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n // To handle annotations, first we need the MathML in XML.\n let mathML = MathML.safeXmlDecode(dataMathML);\n\n if (!Configuration.get(\"saveHandTraces\")) {\n mathML = MathML.removeAnnotation(mathML, \"application/json\");\n }\n\n if (mathML == null) {\n mathML = imgObject.getAttribute(\"alt\");\n }\n\n if (convertToSafeXml) {\n const safeMathML = MathML.safeXmlEncode(mathML);\n return safeMathML;\n }\n\n return mathML;\n }\n }\n return imgCode;\n }\n\n /**\n * Gets the node length in characters.\n * @param {Node} node - HTML node.\n * @returns {number} node length.\n * @static\n */\n static getNodeLength(node) {\n const staticNodeLengths = {\n IMG: 1,\n BR: 1,\n };\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return node.nodeValue.length;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n let length = staticNodeLengths[node.nodeName.toUpperCase()];\n\n if (length === undefined) {\n length = 0;\n }\n\n for (let i = 0; i < node.childNodes.length; i += 1) {\n length += Util.getNodeLength(node.childNodes[i]);\n }\n return length;\n }\n return 0;\n }\n\n /**\n * Gets a selected node or text from an editable HTMLElement.\n * If the caret is on a text node, concatenates it with all the previous and next text nodes.\n * @param {HTMLElement} target - the editable HTMLElement.\n * @param {boolean} isIframe - specifies if the target is an iframe or not\n * @param {boolean} forceGetSelection - if true, ignores IE system to get\n * the current selection and uses window.getSelection()\n * @returns {object} an object with the 'node' key set if the item is an\n * element or the keys 'node' and 'caretPosition' if the element is text.\n * @static\n */\n static getSelectedItem(target, isIframe, forceGetSelection) {\n let windowTarget;\n\n if (isIframe) {\n windowTarget = target.contentWindow;\n windowTarget.focus();\n } else {\n windowTarget = window;\n target.focus();\n }\n\n if (document.selection && !forceGetSelection) {\n const range = windowTarget.document.selection.createRange();\n\n if (range.parentElement) {\n if (range.htmlText.length > 0) {\n if (range.text.length === 0) {\n return Util.getSelectedItem(target, isIframe, true);\n }\n\n return null;\n }\n\n windowTarget.document.execCommand(\"InsertImage\", false, \"#\");\n let temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() !== \"IMG\") {\n // IE9 fix: parentElement() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML('');\n temporalObject = windowTarget.document.getElementById(\"wrs_openEditorWindow_temporalObject\");\n }\n\n let node;\n let caretPosition;\n\n if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType === 3) {\n // TEXT_NODE.\n node = temporalObject.nextSibling;\n caretPosition = 0;\n } else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType === 3) {\n node = temporalObject.previousSibling;\n caretPosition = node.nodeValue.length;\n } else {\n node = windowTarget.document.createTextNode(\"\");\n temporalObject.parentNode.insertBefore(node, temporalObject);\n caretPosition = 0;\n }\n\n temporalObject.parentNode.removeChild(temporalObject);\n\n return {\n node,\n caretPosition,\n };\n }\n\n if (range.length > 1) {\n return null;\n }\n\n return {\n node: range.item(0),\n };\n }\n\n if (windowTarget.getSelection) {\n let range;\n const selection = windowTarget.getSelection();\n\n try {\n range = selection.getRangeAt(0);\n } catch (e) {\n range = windowTarget.document.createRange();\n }\n\n const node = range.startContainer;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n return {\n node,\n caretPosition: range.startOffset,\n };\n }\n\n if (node !== range.endContainer) {\n return null;\n }\n\n if (node.nodeType === 1) {\n // ELEMENT_NODE.\n const position = range.startOffset;\n\n if (node.childNodes[position]) {\n // In case that a formula is detected but not selected,\n // we create an empty span where we could insert the new formula.\n if (range.startOffset === range.endOffset) {\n if (\n position !== 0 &&\n node.childNodes[position - 1].localName === \"span\" &&\n node.childNodes[position].classList?.contains(\"Wirisformula\")\n ) {\n node.childNodes[position - 1].remove();\n return Util.getSelectedItem(target, isIframe, forceGetSelection);\n }\n if (node.childNodes[position].classList?.contains(\"Wirisformula\")) {\n if (\n (position > 0 && node.childNodes[position - 1].classList?.contains(\"Wirisformula\")) ||\n position === 0\n ) {\n const emptySpan = document.createElement(\"span\");\n node.insertBefore(emptySpan, node.childNodes[position]);\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n return {\n node: node.childNodes[position],\n };\n }\n }\n }\n\n return null;\n }\n\n /**\n * Returns null if there isn't any item or if it is malformed.\n * Otherwise returns an object containing the node with the MathML image\n * and the cursor position inside the textarea.\n * @param {HTMLTextAreaElement} textarea - textarea element.\n * @returns {Object} An object containing the node, the index of the\n * beginning of the selected text, caret position and the start and end position of the\n * text node.\n * @static\n */\n static getSelectedItemOnTextarea(textarea) {\n const textNode = document.createTextNode(textarea.value);\n const textNodeValues = Latex.getLatexFromTextNode(textNode, textarea.selectionStart);\n if (textNodeValues === null) {\n return null;\n }\n\n return {\n node: textNode,\n caretPosition: textarea.selectionStart,\n startPosition: textNodeValues.startPosition,\n endPosition: textNodeValues.endPosition,\n };\n }\n\n /**\n * Looks for elements that match the given name in a HTML code string.\n * Important: this function is very concrete for WIRIS code.\n * It takes as preconditions lots of behaviors that are not the general case.\n * @param {string} code - HTML code.\n * @param {string} name - element name.\n * @param {boolean} autoClosed - true if the elements are autoClosed.\n * @return {Object[]} an object containing all HTML elements of code matching the name argument.\n * @static\n */\n static getElementsByNameFromString(code, name, autoClosed) {\n const elements = [];\n code = code.toLowerCase();\n name = name.toLowerCase();\n let start = code.indexOf(`<${name} `);\n\n while (start !== -1) {\n // Look for nodes.\n let endString;\n\n if (autoClosed) {\n endString = \">\";\n } else {\n endString = ``;\n }\n\n let end = code.indexOf(endString, start);\n\n if (end !== -1) {\n end += endString.length;\n elements.push({\n start,\n end,\n });\n } else {\n end = start + 1;\n }\n\n start = code.indexOf(`<${name} `, end);\n }\n\n return elements;\n }\n\n /**\n * Returns the numeric value of a base64 character.\n * @param {string} character - base64 character.\n * @returns {number} base64 character numeric value.\n * @static\n */\n static decode64(character) {\n const PLUS = \"+\".charCodeAt(0);\n const SLASH = \"/\".charCodeAt(0);\n const NUMBER = \"0\".charCodeAt(0);\n const LOWER = \"a\".charCodeAt(0);\n const UPPER = \"A\".charCodeAt(0);\n const PLUS_URL_SAFE = \"-\".charCodeAt(0);\n const SLASH_URL_SAFE = \"_\".charCodeAt(0);\n const code = character.charCodeAt(0);\n\n if (code === PLUS || code === PLUS_URL_SAFE) {\n return 62; // Char '+'.\n }\n if (code === SLASH || code === SLASH_URL_SAFE) {\n return 63; // Char '/'.\n }\n if (code < NUMBER) {\n return -1; // No match.\n }\n if (code < NUMBER + 10) {\n return code - NUMBER + 26 + 26;\n }\n if (code < UPPER + 26) {\n return code - UPPER;\n }\n if (code < LOWER + 26) {\n return code - LOWER + 26;\n }\n\n return null;\n }\n\n /**\n * Converts a base64 string to a array of bytes.\n * @param {string} b64String - base64 string.\n * @param {number} length - dimension of byte array (by default whole string).\n * @return {Object[]} the resultant byte array.\n * @static\n */\n static b64ToByteArray(b64String, length) {\n let tmp;\n\n if (b64String.length % 4 > 0) {\n throw new Error(\"Invalid string. Length must be a multiple of 4\"); // Tipped base64. Length is fixed.\n }\n\n const arr = [];\n\n let l;\n let placeHolders;\n if (!length) {\n // All b64String string.\n if (b64String.charAt(b64String.length - 2) === \"=\") {\n placeHolders = 2;\n } else if (b64String.charAt(b64String.length - 1) === \"=\") {\n placeHolders = 1;\n } else {\n placeHolders = 0;\n }\n l = placeHolders > 0 ? b64String.length - 4 : b64String.length;\n } else {\n l = length;\n }\n\n let i;\n for (i = 0; i < l; i += 4) {\n // Ignoring code checker standards (bitewise operators).\n // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 18) |\n (Util.decode64(b64String.charAt(i + 1)) << 12) |\n (Util.decode64(b64String.charAt(i + 2)) << 6) |\n Util.decode64(b64String.charAt(i + 3));\n\n arr.push((tmp >> 16) & 0xff);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n\n if (placeHolders) {\n if (placeHolders === 2) {\n // Ignoring code checker standards (bitewise operators).\n // @codingStandardsIgnoreStart\n // eslint-disable-next-line max-len\n tmp = (Util.decode64(b64String.charAt(i)) << 2) | (Util.decode64(b64String.charAt(i + 1)) >> 4);\n arr.push(tmp & 0xff);\n } else if (placeHolders === 1) {\n // eslint-disable-next-line max-len\n tmp =\n (Util.decode64(b64String.charAt(i)) << 10) |\n (Util.decode64(b64String.charAt(i + 1)) << 4) |\n (Util.decode64(b64String.charAt(i + 2)) >> 2);\n arr.push((tmp >> 8) & 0xff);\n arr.push(tmp & 0xff);\n // @codingStandardsIgnoreEnd\n }\n }\n return arr;\n }\n\n /**\n * Returns the first 32-bit signed integer from a byte array.\n * @param {Object[]} bytes - array of bytes.\n * @returns {number} the 32-bit signed integer.\n * @static\n */\n static readInt32(bytes) {\n if (bytes.length < 4) {\n return false;\n }\n const int32 = bytes.splice(0, 4);\n // @codingStandardsIgnoreStartยก\n return (int32[0] << 24) | (int32[1] << 16) | (int32[2] << 8) | (int32[3] << 0);\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read the first byte from a byte array.\n * @param {Object} bytes - input byte array.\n * @returns {number} first byte of the byte array.\n * @static\n */\n static readByte(bytes) {\n // @codingStandardsIgnoreStart\n return bytes.shift() << 0;\n // @codingStandardsIgnoreEnd\n }\n\n /**\n * Read an arbitrary number of bytes, from a fixed position on a byte array.\n * @param {Object[]} bytes - byte array.\n * @param {number} pos - start position.\n * @param {number} len - number of bytes to read.\n * @returns {Object[]} the byte array.\n * @static\n */\n static readBytes(bytes, pos, len) {\n return bytes.splice(pos, len);\n }\n\n /**\n * Inserts or modifies formulas or CAS on a textarea.\n * @param {HTMLTextAreaElement} textarea - textarea target.\n * @param {string} text - text to add in the textarea. For example, to add the link to the image,\n * call this function as (textarea, Parser.createImageSrc(mathml));\n * @static\n */\n static updateTextArea(textarea, text) {\n if (textarea && text) {\n textarea.focus();\n\n if (textarea.selectionStart != null) {\n const { selectionEnd } = textarea;\n const selectionStart = textarea.value.substring(0, textarea.selectionStart);\n const selectionEndSub = textarea.value.substring(selectionEnd, textarea.value.length);\n textarea.value = selectionStart + text + selectionEndSub;\n textarea.selectionEnd = selectionEnd + text.length;\n } else {\n const selection = document.selection.createRange();\n selection.text = text;\n }\n }\n }\n\n /**\n * Modifies existing formula on a textarea.\n * @param {HTMLTextAreaElement} textarea - text area target.\n * @param {string} text - text to add in the textarea. For example, if you want to add the link\n * to the image,you can call this function as\n * Util.updateTextarea(textarea, Parser.createImageSrc(mathml));\n * @param {number} start - beginning index from textarea where it needs to be replaced by text.\n * @param {number} end - ending index from textarea where it needs to be replaced by text\n * @static\n */\n static updateExistingTextOnTextarea(textarea, text, start, end) {\n textarea.focus();\n const textareaStart = textarea.value.substring(0, start);\n textarea.value = textareaStart + text + textarea.value.substring(end, textarea.value.length);\n textarea.selectionEnd = start + text.length;\n }\n\n /**\n * Add a parameter with it's correspondent value to an URL (GET).\n * @param {string} path - URL path\n * @param {string} parameter - parameter\n * @param {string} value - value\n * @static\n */\n static addArgument(path, parameter, value) {\n let sep;\n if (path.indexOf(\"?\") > 0) {\n sep = \"&\";\n } else {\n sep = \"?\";\n }\n return `${path + sep + parameter}=${value}`;\n }\n}\n","import Configuration from \"./configuration\";\nimport Util from \"./util\";\n\n/**\n * @classdesc\n * This class represents MathType Image class. Contains all the logic related\n * to MathType images manipulation.\n * All MathType images are generated using the appropriate MathType\n * integration service: showimage or createimage.\n *\n * There are two available image formats:\n * - svg (default)\n * - png\n *\n * There are two formats for the image src attribute:\n * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64.\n * - A link to the showimage service.\n */\nexport default class Image {\n /**\n * Removes data attributes from an image.\n * @param {HTMLImageElement} img - Image where remove data attributes.\n */\n static removeImgDataAttributes(img) {\n const attributesToRemove = [];\n const { attributes } = img;\n\n Object.keys(attributes).forEach((key) => {\n const attribute = attributes[key];\n if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf(\"data-\") === 0) {\n // Is preferred keep an array and remove after the search\n // because when attribute is removed the array of attributes\n // is modified.\n attributesToRemove.push(attribute.name);\n }\n });\n\n attributesToRemove.forEach((attribute) => {\n img.removeAttribute(attribute);\n });\n }\n\n /**\n * @static\n * Clones all MathType image attributes from a HTMLImageElement to another.\n * @param {HTMLImageElement} originImg - The original image.\n * @param {HTMLImageElement} destImg - The destination image.\n */\n static clone(originImg, destImg) {\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (!originImg.hasAttribute(customEditorAttributeName)) {\n destImg.removeAttribute(customEditorAttributeName);\n }\n\n const mathmlAttributeName = Configuration.get(\"imageMathmlAttribute\");\n const imgAttributes = [\n mathmlAttributeName,\n customEditorAttributeName,\n \"alt\",\n \"height\",\n \"width\",\n \"style\",\n \"src\",\n \"role\",\n ];\n\n imgAttributes.forEach((iterator) => {\n const originAttribute = originImg.getAttribute(iterator);\n if (originAttribute) {\n destImg.setAttribute(iterator, originAttribute);\n }\n });\n }\n\n /**\n * Determines whether an img src contains an SVG.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src contains an SVG, false otherwise\n */\n static isSvg(img) {\n return img.src.startsWith(\"data:image/svg+xml;\");\n }\n\n /**\n * Determines whether an img src is encoded in base64 or not.\n * @param {HTMLImageElement} img the img element to inspect\n * @returns true if the img src is encoded in base64, false otherwise\n */\n static isBase64(img) {\n return img.src.startsWith(\"data:image/svg+xml;base64,\") || img.src.startsWith(\"data:image/png;base64,\");\n }\n\n /**\n * Calculates the metrics of a MathType image given the the service response and the image format.\n * @param {HTMLImageElement} img - The HTMLImageElement.\n * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL.\n * @param {Boolean} jsonResponse - True the response of the image service is a\n * JSON object. False otherwise.\n */\n static setImgSize(img, uri, jsonResponse) {\n let ar;\n let base64String;\n let bytes;\n let svgString;\n if (jsonResponse) {\n // Cleaning data:image/png;base64.\n if (Image.isSvg(img)) {\n // SVG format.\n // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.\n if (!Image.isBase64(img)) {\n ar = Image.getMetricsFromSvgString(uri);\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n svgString = \"\";\n bytes = Util.b64ToByteArray(base64String, base64String.length);\n for (let i = 0; i < bytes.length; i += 1) {\n svgString += String.fromCharCode(bytes[i]);\n }\n ar = Image.getMetricsFromSvgString(svgString);\n }\n // PNG format: we store all metrics information in the first 88 bytes.\n } else {\n base64String = img.src.substr(img.src.indexOf(\"base64,\") + 7, img.src.length);\n bytes = Util.b64ToByteArray(base64String, 88);\n ar = Image.getMetricsFromBytes(bytes);\n }\n // Backwards compatibility: we store the metrics into createimage response.\n } else {\n ar = Util.urlToAssArray(uri);\n }\n let width = ar.cw;\n if (!width) {\n return;\n }\n let height = ar.ch;\n let baseline = ar.cb;\n const { dpi } = ar;\n if (dpi) {\n width = (width * 96) / dpi;\n height = (height * 96) / dpi;\n baseline = (baseline * 96) / dpi;\n }\n img.width = width;\n img.height = height;\n img.style.verticalAlign = `-${height - baseline}px`;\n }\n\n /**\n * Calculates the metrics of an image which has been resized. Is used to restore the original\n * metrics of a resized image.\n * @param {HTMLImageElement } img - The resized HTMLImageElement.\n */\n static fixAfterResize(img) {\n img.removeAttribute(\"style\");\n img.removeAttribute(\"width\");\n img.removeAttribute(\"height\");\n // In order to avoid resize with max-width css property.\n img.style.maxWidth = \"none\";\n\n const processImg = (img) => {\n if (img.src.indexOf(\"data:image\") !== -1) {\n if (img.src.indexOf(\"data:image/svg+xml\") !== -1) {\n // Image is in base64: decode it in order to calculate the size, and then bring it back to base64\n // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it\n // (which would actually make more sense for readibility and efficiency).\n if (img.src.indexOf(\"data:image/svg+xml;base64,\") !== -1) {\n // 'data:image/svg+xml;base64,'.length === 26\n const base64String = img.getAttribute(\"src\").substring(26);\n const svgString = window.atob(base64String);\n const encodedSvgString = encodeURIComponent(svgString);\n img.setAttribute(\"src\", `data:image/svg+xml;charset=utf8,${encodedSvgString}`);\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n // Return src to base64!\n img.setAttribute(\"src\", `data:image/svg+xml;base64,${base64String}`);\n } else {\n // 'data:image/svg+xml;charset=utf8,'.length === 32.\n const svg = decodeURIComponent(img.src.substring(32, img.src.length));\n Image.setImgSize(img, svg, true);\n }\n } else {\n // 'data:image/png;base64,' === 22.\n const base64 = img.src.substring(22, img.src.length);\n Image.setImgSize(img, base64, true);\n }\n } else {\n Image.setImgSize(img, img.src);\n }\n };\n\n // If the image doesn't contain a blob, just process it normally\n if (img.src.indexOf(\"blob:\") === -1) {\n processImg(img);\n // if it does contain a blob, then read that, replace the src with the decoded content, and process it\n } else {\n const reader = new FileReader();\n reader.onload = function () {\n img.setAttribute(\"src\", reader.result);\n processImg(img);\n };\n fetch(img.src)\n .then((r) => r.blob())\n .then((blob) => {\n reader.readAsDataURL(blob);\n });\n }\n }\n\n /**\n * Returns the metrics (height, width and baseline) contained in a SVG image generated\n * by the MathType image service. This image contains as an extra custom attribute:\n * the baseline (wrs:baseline).\n * @param {String} svgString - The SVG image.\n * @return {Array} - The image metrics.\n */\n static getMetricsFromSvgString(svgString) {\n let first = svgString.indexOf('height=\"');\n let last = svgString.indexOf('\"', first + 8, svgString.length);\n const height = svgString.substring(first + 8, last);\n\n first = svgString.indexOf('width=\"');\n last = svgString.indexOf('\"', first + 7, svgString.length);\n const width = svgString.substring(first + 7, last);\n\n first = svgString.indexOf('wrs:baseline=\"');\n last = svgString.indexOf('\"', first + 14, svgString.length);\n const baseline = svgString.substring(first + 14, last);\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n if (typeof baseline !== \"undefined\") {\n arr.cb = baseline;\n }\n return arr;\n }\n return [];\n }\n\n /**\n * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array.\n * @param {Array.} bytes - png byte array.\n * @return {Array} The png metrics.\n */\n static getMetricsFromBytes(bytes) {\n Util.readBytes(bytes, 0, 8);\n let width;\n let height;\n let typ;\n let baseline;\n let dpi;\n while (bytes.length >= 4) {\n typ = Util.readInt32(bytes);\n if (typ === 0x49484452) {\n width = Util.readInt32(bytes);\n height = Util.readInt32(bytes);\n // Read 5 bytes.\n Util.readInt32(bytes);\n Util.readByte(bytes);\n } else if (typ === 0x62615345) {\n // Baseline: 'baSE'.\n baseline = Util.readInt32(bytes);\n } else if (typ === 0x70485973) {\n // Dpis: 'pHYs'.\n dpi = Util.readInt32(bytes);\n dpi = Math.round(dpi / 39.37);\n Util.readInt32(bytes);\n Util.readByte(bytes);\n }\n Util.readInt32(bytes);\n }\n\n if (typeof width !== \"undefined\") {\n const arr = [];\n arr.cw = width;\n arr.ch = height;\n arr.dpi = dpi;\n if (baseline) {\n arr.cb = baseline;\n }\n\n return arr;\n }\n return [];\n }\n}\n","import TextCache from \"./textcache\";\nimport ServiceProvider from \"./serviceprovider\";\nimport MathML from \"./mathml\";\nimport StringManager from \"./stringmanager\";\n\n/**\n * @classdesc\n * This class represents MathType accessible class. Converts MathML to accessible text and manages\n * the associated client-side cache.\n */\nexport default class Accessibility {\n /**\n * Static property.\n * Accessibility cache, each entry contains a MathML and its correspondent accessibility text.\n * @type {TextCache}\n */\n static get cache() {\n return Accessibility._cache;\n }\n\n /**\n * Static property setter.\n * Set accessibility cache.\n * @param {TextCahe} value - The property value.\n * @ignore\n */\n static set cache(value) {\n Accessibility._cache = value;\n }\n\n /**\n * Converts MathML strings to its accessible text representation.\n * @param {String} mathML - MathML to be converted to accessible text.\n * @param {String} [language] - Language of the accessible text. 'en' by default.\n * @param {Array.} [data] - Parameters to send to mathml2accessible service.\n * @return {String} Accessibility text.\n */\n static mathMLToAccessible(mathML, language, data) {\n if (typeof language === \"undefined\") {\n language = \"en\";\n }\n // Check MathML class. If the class is chemistry,\n // we add chemistry to data to force accessibility service\n // to load chemistry grammar.\n if (MathML.containClass(mathML, \"wrs_chemistry\")) {\n data.mode = \"chemistry\";\n }\n // Ignore accesibility styles\n data.ignoreStyles = true;\n let accessibleText = \"\";\n\n if (Accessibility.cache.get(mathML)) {\n accessibleText = Accessibility.cache.get(mathML);\n } else {\n data.service = \"mathml2accessible\";\n data.lang = language;\n const accessibleJsonResponse = JSON.parse(ServiceProvider.getService(\"service\", data));\n if (accessibleJsonResponse.status !== \"error\") {\n accessibleText = accessibleJsonResponse.result.text;\n Accessibility.cache.populate(mathML, accessibleText);\n } else {\n accessibleText = StringManager.get(\"error_convert_accessibility\");\n }\n }\n\n return accessibleText;\n }\n}\n\n/**\n * Contains an instance of TextCache class to manage the JavaScript accessible cache.\n * Each entry of the cache object contains the MathML and it's correspondent accessibility text.\n * @private\n * @type {TextCache}\n */\nAccessibility._cache = new TextCache();\n","import Util from \"./util\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport Image from \"./image\";\nimport Accessibility from \"./accessibility\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Configuration from \"./configuration\";\nimport Constants from \"./constants\";\n// eslint-disable-next-line no-unused-vars\nimport md5 from \"./md5\";\n\n/**\n * @classdesc\n * This class represent a MahML parser. Converts MathML into formulas depending on the\n * image format (SVG, PNG, base64) and the save mode (XML, safeXML, Image) configured\n * in the backend.\n */\nexport default class Parser {\n /**\n * Converts a MathML string to an img element.\n * @param {Document} creator - Document object to call createElement method.\n * @param {string} mathml - MathML code\n * @param {Object[]} wirisProperties - object containing WIRIS custom properties\n * @param {language} language - custom language for accessibility.\n * @returns {HTMLImageElement} the formula image corresponding to initial MathML string.\n * @static\n */\n static mathmlToImgObject(creator, mathml, wirisProperties, language) {\n const imgObject = creator.createElement(\"img\");\n imgObject.align = \"middle\";\n imgObject.style.maxWidth = \"none\";\n let data = wirisProperties || {};\n\n // Take into account the backend config\n const wirisEditorProperties = Configuration.get(\"editorParameters\");\n data = { ...wirisEditorProperties, ...data };\n\n data.mml = mathml;\n data.lang = language;\n // Request metrics of the generated image.\n data.metrics = \"true\";\n data.centerbaseline = \"false\";\n\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n // Render js params: _wrs_int_wirisProperties contains some js render params.\n // Since MathML can support render params, js params should be send only to editor.\n\n imgObject.className = Configuration.get(\"imageClassName\");\n\n if (mathml.indexOf('class=\"') !== -1) {\n // We check here if the MathML has been created from a customEditor (such chemistry)\n // to add custom editor name attribute to img object (if necessary).\n let mathmlSubstring = mathml.substring(mathml.indexOf('class=\"') + 'class=\"'.length, mathml.length);\n mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('\"'));\n mathmlSubstring = mathmlSubstring.substring(4, mathmlSubstring.length);\n imgObject.setAttribute(Configuration.get(\"imageCustomEditorName\"), mathmlSubstring);\n }\n\n // Performance enabled.\n if (\n Configuration.get(\"wirisPluginPerformance\") &&\n (Configuration.get(\"saveMode\") === \"xml\" || Configuration.get(\"saveMode\") === \"safeXml\")\n ) {\n let result = JSON.parse(Parser.createShowImageSrc(data, language));\n if (result.status === \"warning\") {\n // POST call.\n // if the mathml is malformed, this function will throw an exception.\n try {\n result = JSON.parse(ServiceProvider.getService(\"showimage\", data));\n } catch (e) {\n return null;\n }\n }\n ({ result } = result);\n if (result.format === \"png\") {\n imgObject.src = `data:image/png;base64,${result.content}`;\n } else {\n imgObject.src = `data:image/svg+xml;charset=utf8,${Util.urlEncode(result.content)}`;\n }\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n Image.setImgSize(imgObject, result.content, true);\n\n if (Configuration.get(\"enableAccessibility\")) {\n if (typeof result.alt === \"undefined\") {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n } else {\n imgObject.alt = result.alt;\n }\n }\n } else {\n const result = Parser.createImageSrc(mathml, data);\n imgObject.setAttribute(Configuration.get(\"imageMathmlAttribute\"), MathML.safeXmlEncode(mathml));\n imgObject.src = result;\n Image.setImgSize(\n imgObject,\n result,\n Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\",\n );\n if (Configuration.get(\"enableAccessibility\")) {\n imgObject.alt = Accessibility.mathMLToAccessible(mathml, language, data);\n }\n }\n\n if (typeof Parser.observer !== \"undefined\") {\n Parser.observer.observe(imgObject);\n }\n\n // Role math https://www.w3.org/TR/wai-aria/roles#math.\n imgObject.setAttribute(\"role\", \"math\");\n return imgObject;\n }\n\n /**\n * Returns the source to showimage service by calling createimage service. The\n * output of the createimage service is a URL path pointing to showimage service.\n * This method is called when performance is disabled.\n * @param {string} mathml - MathML code.\n * @param {Object[]} data - data object containing service parameters.\n * @returns {string} the showimage path.\n */\n static createImageSrc(mathml, data) {\n // Full base64 method (edit & save).\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"default\") {\n data.base64 = true;\n }\n\n let result = ServiceProvider.getService(\"createimage\", data);\n\n if (result.indexOf(\"@BASE@\") !== -1) {\n // Replacing '@BASE@' with the base URL of createimage.\n const baseParts = ServiceProvider.getServicePath(\"createimage\").split(\"/\");\n baseParts.pop();\n result = result.split(\"@BASE@\").join(baseParts.join(\"/\"));\n }\n\n return result;\n }\n\n /**\n * Parses initial HTML code. If the HTML contains data generated by WIRIS,\n * this data would be converted as following:\n *
\n   * MathML code: Image containing the corresponding MathML formulas.\n   * MathML code with LaTeX annotation : LaTeX string.\n   * 
\n * @param {string} code - HTML code containing MathML data.\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code with the original MathML converted into LaTeX and images.\n */\n static initParse(code, language) {\n /* Note: The code inside this function has been inverted.\n If you invert again the code then you cannot use correctly LaTeX\n in Moodle.\n */\n code = Parser.initParseSaveMode(code, language);\n return Parser.initParseEditMode(code);\n }\n\n /**\n * Parses initial HTML code depending on the save mode. Transforms all MathML\n * occurrences for it's correspondent image or LaTeX.\n * @param {string} code - HTML code to be parsed\n * @param {string} language - language to create image alt text.\n * @returns {string} HTML code parsed.\n */\n static initParseSaveMode(code, language) {\n if (Configuration.get(\"saveMode\")) {\n // Converting XML to tags.\n code = Latex.parseMathmlToLatex(code, Constants.safeXmlCharacters);\n code = Latex.parseMathmlToLatex(code, Constants.xmlCharacters);\n code = Parser.parseMathmlToImg(code, Constants.safeXmlCharacters, language);\n code = Parser.parseMathmlToImg(code, Constants.xmlCharacters, language);\n if (Configuration.get(\"saveMode\") === \"base64\" && Configuration.get(\"base64savemode\") === \"image\") {\n code = Parser.codeImgTransform(code, \"base642showimage\");\n }\n }\n return code;\n }\n\n /**\n * Parses initial HTML code depending on the edit mode.\n * If 'latex' parseMode is enabled all MathML containing an annotation with encoding='LaTeX' will\n * be converted into a LaTeX string instead of an image.\n * @param {string} code - HTML code containing MathML.\n * @returns {string} parsed HTML code.\n */\n static initParseEditMode(code) {\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n const imgList = Util.getElementsByNameFromString(code, \"img\", true);\n const token = 'encoding=\"LaTeX\">';\n // While replacing images with latex, the indexes of the found images changes\n // respecting the original code, so this carry is needed.\n let carry = 0;\n\n for (let i = 0; i < imgList.length; i += 1) {\n const imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);\n\n if (imgCode.indexOf(` class=\"${Configuration.get(\"imageClassName\")}\"`) !== -1) {\n let mathmlStartToken = ` ${Configuration.get(\"imageMathmlAttribute\")}=\"`;\n let mathmlStart = imgCode.indexOf(mathmlStartToken);\n\n if (mathmlStart === -1) {\n mathmlStartToken = ' alt=\"';\n mathmlStart = imgCode.indexOf(mathmlStartToken);\n }\n\n if (mathmlStart !== -1) {\n mathmlStart += mathmlStartToken.length;\n const mathmlEnd = imgCode.indexOf('\"', mathmlStart);\n const mathml = Util.htmlSanitize(MathML.safeXmlDecode(imgCode.substring(mathmlStart, mathmlEnd)));\n let latexStartPosition = mathml.indexOf(token);\n\n if (latexStartPosition !== -1) {\n latexStartPosition += token.length;\n const latexEndPosition = mathml.indexOf(\"\", latexStartPosition);\n const latex = mathml.substring(latexStartPosition, latexEndPosition);\n\n const replaceText = `$$${Util.htmlEntitiesDecode(latex)}$$`;\n const start = code.substring(0, imgList[i].start + carry);\n const end = code.substring(imgList[i].end + carry);\n code = start + replaceText + end;\n carry += replaceText.length - (imgList[i].end - imgList[i].start);\n }\n }\n }\n }\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code. The end HTML code is HTML code with embedded images\n * or LaTeX formulas created with MathType.
\n * By default this method converts the formula images and LaTeX strings in MathML.
\n * If image mode is enabled the images will not be converted into MathML. For further information see {@link https://docs.wiris.com/mathtype/en/mathtype-integrations/mathtype-web-interface-features/full-mathml-mode---wirisplugins-js.html}.\n * @param {string} code - HTML to be parsed\n * @returns {string} the HTML code parsed.\n */\n static endParse(code) {\n // Transform LaTeX ocurrences to MathML elements.\n const codeEndParsedEditMode = Parser.endParseEditMode(code);\n // Transform img elements to MathML elements.\n const codeEndParseSaveMode = Parser.endParseSaveMode(codeEndParsedEditMode);\n return codeEndParseSaveMode;\n }\n\n /**\n * Parses end HTML code depending on the edit mode.\n * - LaTeX is an enabled parse mode, all LaTeX occurrences will be converted into MathML.\n * @param {string} code - HTML code to be parsed.\n * @returns {string} HTML code parsed.\n */\n static endParseEditMode(code) {\n // Converting LaTeX to images.\n if (Configuration.get(\"parseModes\").indexOf(\"latex\") !== -1) {\n let output = \"\";\n let endPosition = 0;\n let startPosition = code.indexOf(\"$$\");\n while (startPosition !== -1) {\n output += code.substring(endPosition, startPosition);\n endPosition = code.indexOf(\"$$\", startPosition + 2);\n\n if (endPosition !== -1) {\n // Before, it was a condition here to execute the next codelines\n // 'latex.indexOf('<') == -1'.\n // We don't know why it was used, but seems to have a conflict with\n // latex formulas that contains '<'.\n const latex = code.substring(startPosition + 2, endPosition);\n const decodedLatex = Util.htmlEntitiesDecode(latex);\n let mathml = Util.htmlSanitize(Latex.getMathMLFromLatex(decodedLatex, true));\n if (!Configuration.get(\"saveHandTraces\")) {\n // Remove hand traces.\n mathml = MathML.removeAnnotation(mathml, \"application/json\");\n }\n output += mathml;\n endPosition += 2;\n } else {\n output += \"$$\";\n endPosition = startPosition + 2;\n }\n\n startPosition = code.indexOf(\"$$\", endPosition);\n }\n\n output += code.substring(endPosition, code.length);\n code = output;\n }\n\n return code;\n }\n\n /**\n * Parses end HTML code depending on the save mode. Converts all\n * images into the element determined by the save mode:\n * - xml: Parses images formulas into MathML.\n * - safeXml: Parses images formulas into safeMAthML\n * - base64: Parses images into base64 images.\n * - image: Parse images into images (no parsing)\n * @param {string} code - HTML code to be parsed\n * @returns {string} HTML code parsed.\n */\n static endParseSaveMode(code) {\n const savemode = Configuration.get(\"saveMode\");\n const base64savemode = Configuration.get(\"base64savemode\");\n\n if (savemode) {\n if (savemode === \"safeXml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"xml\") {\n code = Parser.codeImgTransform(code, \"img2mathml\");\n } else if (savemode === \"base64\" && base64savemode === \"image\") {\n code = Parser.codeImgTransform(code, \"img264\");\n }\n }\n\n return code;\n }\n\n /**\n * Auxiliar function that builds the data object to send to the showimage endpoint\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object with the data to send to showimage.\n */\n static createShowImageSrcData(data, language) {\n const dataMd5 = {};\n const renderParams = [\n \"mml\",\n \"color\",\n \"centerbaseline\",\n \"zoom\",\n \"dpi\",\n \"fontSize\",\n \"fontFamily\",\n \"defaultStretchy\",\n \"backgroundColor\",\n \"format\",\n ];\n renderParams.forEach((param) => {\n if (typeof data[param] !== \"undefined\") {\n dataMd5[param] = data[param];\n }\n });\n // Data variables to get.\n const dataObject = {};\n Object.keys(data).forEach((key) => {\n // We don't need mathml in this request we try to get cached.\n // Only need the formula md5 calculated before.\n if (key !== \"mml\") {\n dataObject[key] = data[key];\n }\n });\n\n dataObject.formula = com.wiris.js.JsPluginTools.md5encode(Util.propertiesToString(dataMd5));\n dataObject.lang = typeof language === \"undefined\" ? \"en\" : language;\n dataObject.version = Configuration.get(\"version\");\n\n return dataObject;\n }\n\n /**\n * Returns the result to call showimage service with the formula md5 as parameter.\n * The result could be:\n * - {'status' : warning'} : The image associated to the MathML md5 is not in cache.\n * - {'status' : 'ok' ...} : The image associated to the MathML md5 is in cache.\n * @param {Object[]} data - object containing showimage service parameters.\n * @param {string} language - string containing the language of the formula.\n * @returns {Object} JSON object containing showimage response.\n */\n static createShowImageSrc(data, language) {\n const dataObject = this.createShowImageSrcData(data, language);\n const result = ServiceProvider.getService(\"showimage\", Util.httpBuildQuery(dataObject), true);\n return result;\n }\n\n /**\n * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src)\n * or showimage img tags (i.e with showimage.php on src)\n * @param {string} code - HTML code\n * @param {string} mode - base642showimage or img2mathml or img264 transform.\n * @returns {string} html - code transformed.\n */\n static codeImgTransform(code, mode) {\n let output = \"\";\n let endPosition = 0;\n const pattern = /\") {\n endPosition = i + 1;\n }\n\n i += 1;\n }\n\n if (endPosition < startPosition) {\n // The img tag is stripped.\n output += code.substring(startPosition, code.length);\n return output;\n }\n let imgCode = code.substring(startPosition, endPosition);\n const imgObject = Util.createObject(imgCode);\n let xmlCode = imgObject.getAttribute(Configuration.get(\"imageMathmlAttribute\"));\n let convertToXml;\n let convertToSafeXml;\n\n if (mode === \"base642showimage\") {\n if (xmlCode == null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n imgCode = Parser.mathmlToImgObject(document, xmlCode, null, null);\n output += Util.createObjectCode(imgCode);\n } else if (mode === \"img2mathml\") {\n if (Configuration.get(\"saveMode\")) {\n if (Configuration.get(\"saveMode\") === \"safeXml\") {\n convertToXml = true;\n convertToSafeXml = true;\n } else if (Configuration.get(\"saveMode\") === \"xml\") {\n convertToXml = true;\n convertToSafeXml = false;\n }\n }\n output += Util.getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);\n } else if (mode === \"img264\") {\n if (xmlCode === null) {\n xmlCode = imgObject.getAttribute(\"alt\");\n }\n xmlCode = MathML.safeXmlDecode(xmlCode);\n\n const properties = {};\n properties.base64 = \"true\";\n imgCode = Parser.mathmlToImgObject(document, xmlCode, properties, null);\n // Metrics.\n Image.setImgSize(imgCode, imgCode.src, true);\n output += Util.createObjectCode(imgCode);\n }\n }\n output += code.substring(endPosition, code.length);\n return output;\n }\n\n /**\n * Converts all occurrences of MathML to the corresponding image.\n * @param {string} content - string with valid MathML code.\n * The MathML code doesn't contain semantics.\n * @param {Constants} characters - Constant object containing xmlCharacters\n * or safeXmlCharacters relation.\n * @param {string} language - a valid language code\n * in order to generate formula accessibility.\n * @returns {string} The input string with all the MathML\n * occurrences replaced by the corresponding image.\n */\n static parseMathmlToImg(content, characters, language) {\n let output = \"\";\n const mathTagBegin = `${characters.tagOpener}math`;\n const mathTagEnd = `${characters.tagOpener}/math${characters.tagCloser}`;\n let start = content.indexOf(mathTagBegin);\n let end = 0;\n\n while (start !== -1) {\n output += content.substring(end, start);\n // Avoid WIRIS images to be parsed.\n const imageMathmlAtrribute = content.indexOf(Configuration.get(\"imageMathmlAttribute\"));\n end = content.indexOf(mathTagEnd, start);\n\n if (end === -1) {\n end = content.length - 1;\n } else if (imageMathmlAtrribute !== -1) {\n // First close tag of img attribute\n // If a mathmlAttribute exists should be inside a img tag.\n end += content.indexOf(\"/>\", start);\n } else {\n end += mathTagEnd.length;\n }\n\n if (!MathML.isMathmlInAttribute(content, start) && imageMathmlAtrribute === -1) {\n let mathml = content.substring(start, end);\n mathml =\n characters.id === Constants.safeXmlCharacters.id\n ? MathML.safeXmlDecode(mathml)\n : MathML.mathMLEntities(mathml);\n output += Util.createObjectCode(Parser.mathmlToImgObject(document, mathml, null, language));\n } else {\n output += content.substring(start, end);\n }\n\n start = content.indexOf(mathTagBegin, end);\n }\n\n output += content.substring(end, content.length);\n return output;\n }\n}\n\n// Mutation observers to avoid wiris image formulas class be removed.\nif (typeof MutationObserver !== \"undefined\") {\n const mutationObserver = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (\n mutation.oldValue === Configuration.get(\"imageClassName\") &&\n mutation.attributeName === \"class\" &&\n mutation.target.className.indexOf(Configuration.get(\"imageClassName\")) === -1\n ) {\n mutation.target.className = Configuration.get(\"imageClassName\");\n }\n });\n });\n\n Parser.observer = Object.create(mutationObserver);\n Parser.observer.Config = { attributes: true, attributeOldValue: true };\n // We use own default config.\n Parser.observer.observe = function (target) {\n Object.getPrototypeOf(this).observe(target, this.Config);\n };\n}\n","/* eslint-disable class-methods-use-this */\n/* eslint-disable no-unused-vars */\n/* eslint-disable no-extra-semi */\n\n// The rules above are disabled because we are implementing\n// an external interface.\n\nexport default class EditorListener {\n /**\n * @classdesc\n * Determines if the content of the\n * MathType Editor has changes.\n * @implements {EditorListeners}\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the content of the editor has changed.\n * @type {Boolean}\n */\n this.isContentChanged = false;\n\n /**\n * Indicates if the listener should be waiting for changes in the editor.\n * @type {Boolean}\n */\n this.waitingForChanges = false;\n }\n\n /**\n * Sets {@link EditorListener.isContentChanged} property.\n * @param {Boolean} value - The new vlue.\n */\n setIsContentChanged(value) {\n this.isContentChanged = value;\n }\n\n /**\n * Returns true if the content of the editor has been changed, false otherwise.\n * @return {Boolean}\n */\n getIsContentChanged() {\n return this.isContentChanged;\n }\n\n /**\n * Determines if the EditorListener should wait for any changes.\n * @param {Boolean} value - True if the editor should wait for changes, false otherwise.\n */\n setWaitingForChanges(value) {\n this.waitingForChanges = value;\n }\n\n /**\n * EditorListener method to overwrite.\n * @type {JsEditor}\n * @ignore\n */\n caretPositionChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @type {JsEditor}\n * @ignore\n */\n clipboardChanged(_editor) {}\n\n /**\n * Determines if the content of an editor has been changed.\n * @param {JsEditor} editor - editor object.\n */\n contentChanged(_editor) {\n if (this.waitingForChanges === true && this.isContentChanged === false) {\n this.isContentChanged = true;\n }\n }\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} editor - The editor instance.\n */\n styleChanged(_editor) {}\n\n /**\n * EditorListener method to overwrite\n * @param {JsEditor} - The editor instance.\n */\n transformationReceived(_editor) {}\n}\n","let wasm;\n\nconst cachedTextDecoder =\n typeof TextDecoder !== \"undefined\"\n ? new TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true })\n : {\n decode: () => {\n throw Error(\"TextDecoder not available\");\n },\n };\n\nif (typeof TextDecoder !== \"undefined\") {\n cachedTextDecoder.decode();\n}\n\nlet cachedUint8Memory0 = null;\n\nfunction getUint8Memory0() {\n if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {\n cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachedUint8Memory0;\n}\n\nfunction getStringFromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\n}\n\nconst heap = new Array(128).fill(undefined);\n\nheap.push(undefined, null, true, false);\n\nlet heap_next = heap.length;\n\nfunction addHeapObject(obj) {\n if (heap_next === heap.length) heap.push(heap.length + 1);\n const idx = heap_next;\n heap_next = heap[idx];\n\n heap[idx] = obj;\n return idx;\n}\n\nfunction getObject(idx) {\n return heap[idx];\n}\n\nfunction dropObject(idx) {\n if (idx < 132) return;\n heap[idx] = heap_next;\n heap_next = idx;\n}\n\nfunction takeObject(idx) {\n const ret = getObject(idx);\n dropObject(idx);\n return ret;\n}\n\nlet WASM_VECTOR_LEN = 0;\n\nconst cachedTextEncoder =\n typeof TextEncoder !== \"undefined\"\n ? new TextEncoder(\"utf-8\")\n : {\n encode: () => {\n throw Error(\"TextEncoder not available\");\n },\n };\n\nconst encodeString =\n typeof cachedTextEncoder.encodeInto === \"function\"\n ? function (arg, view) {\n return cachedTextEncoder.encodeInto(arg, view);\n }\n : function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length,\n };\n };\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length, 1) >>> 0;\n getUint8Memory0()\n .subarray(ptr, ptr + buf.length)\n .set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len, 1) >>> 0;\n\n const mem = getUint8Memory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7f) break;\n mem[ptr + offset] = code;\n }\n\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\n const ret = encodeString(arg, view);\n\n offset += ret.written;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction isLikeNone(x) {\n return x === undefined || x === null;\n}\n\nlet cachedInt32Memory0 = null;\n\nfunction getInt32Memory0() {\n if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {\n cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);\n }\n return cachedInt32Memory0;\n}\n\nlet cachedFloat64Memory0 = null;\n\nfunction getFloat64Memory0() {\n if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {\n cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);\n }\n return cachedFloat64Memory0;\n}\n\nlet cachedBigInt64Memory0 = null;\n\nfunction getBigInt64Memory0() {\n if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {\n cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);\n }\n return cachedBigInt64Memory0;\n}\n\nfunction debugString(val) {\n // primitive types\n const type = typeof val;\n if (type == \"number\" || type == \"boolean\" || val == null) {\n return `${val}`;\n }\n if (type == \"string\") {\n return `\"${val}\"`;\n }\n if (type == \"symbol\") {\n const description = val.description;\n if (description == null) {\n return \"Symbol\";\n } else {\n return `Symbol(${description})`;\n }\n }\n if (type == \"function\") {\n const name = val.name;\n if (typeof name == \"string\" && name.length > 0) {\n return `Function(${name})`;\n } else {\n return \"Function\";\n }\n }\n // objects\n if (Array.isArray(val)) {\n const length = val.length;\n let debug = \"[\";\n if (length > 0) {\n debug += debugString(val[0]);\n }\n for (let i = 1; i < length; i++) {\n debug += \", \" + debugString(val[i]);\n }\n debug += \"]\";\n return debug;\n }\n // Test for built-in\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\n let className;\n if (builtInMatches.length > 1) {\n className = builtInMatches[1];\n } else {\n // Failed to match the standard '[object ClassName]'\n return toString.call(val);\n }\n if (className == \"Object\") {\n // we're a user defined class or Object\n // JSON.stringify avoids problems with cycles, and is generally much\n // easier than looping through ownProperties of `val`.\n try {\n return \"Object(\" + JSON.stringify(val) + \")\";\n } catch (_) {\n return \"Object\";\n }\n }\n // errors\n if (val instanceof Error) {\n return `${val.name}: ${val.message}\\n${val.stack}`;\n }\n // TODO we could test for more things here, like `Set`s and `Map`s.\n return className;\n}\n\nfunction makeClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n try {\n return f(state.a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b);\n state.a = 0;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_46(arg0, arg1, arg2) {\n wasm.__wbindgen_export_3(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction makeMutClosure(arg0, arg1, dtor, f) {\n const state = { a: arg0, b: arg1, cnt: 1, dtor };\n const real = (...args) => {\n // First up with a closure we increment the internal reference\n // count. This ensures that the Rust closure environment won't\n // be deallocated while we're invoking it.\n state.cnt++;\n const a = state.a;\n state.a = 0;\n try {\n return f(a, state.b, ...args);\n } finally {\n if (--state.cnt === 0) {\n wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);\n } else {\n state.a = a;\n }\n }\n };\n real.original = state;\n\n return real;\n}\nfunction __wbg_adapter_49(arg0, arg1) {\n wasm.__wbindgen_export_4(arg0, arg1);\n}\n\nfunction __wbg_adapter_52(arg0, arg1, arg2) {\n wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));\n}\n\nfunction handleError(f, args) {\n try {\n return f.apply(this, args);\n } catch (e) {\n wasm.__wbindgen_export_6(addHeapObject(e));\n }\n}\nfunction __wbg_adapter_103(arg0, arg1, arg2, arg3) {\n wasm.__wbindgen_export_7(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));\n}\n\n/**\n */\nexport function main_js() {\n wasm.main_js();\n}\n\nfunction getArrayU8FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);\n}\n/**\n */\nexport const Level = Object.freeze({\n Err: 0,\n 0: \"Err\",\n Warn: 1,\n 1: \"Warn\",\n Info: 2,\n 2: \"Info\",\n Debug: 3,\n 3: \"Debug\",\n});\n/**\n */\nexport class Telemeter {\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n\n return ptr;\n }\n\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_telemeter_free(ptr);\n }\n /**\n * @param {any} solution\n * @param {any} hosts\n * @param {any} config\n */\n constructor(solution, hosts, config) {\n try {\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\n wasm.telemeter_new(retptr, addHeapObject(solution), addHeapObject(hosts), addHeapObject(config));\n var r0 = getInt32Memory0()[retptr / 4 + 0];\n var r1 = getInt32Memory0()[retptr / 4 + 1];\n var r2 = getInt32Memory0()[retptr / 4 + 2];\n if (r2) {\n throw takeObject(r1);\n }\n this.__wbg_ptr = r0 >>> 0;\n return this;\n } finally {\n wasm.__wbindgen_add_to_stack_pointer(16);\n }\n }\n /**\n * @param {string} sender_id\n * @returns {Promise}\n */\n identify(sender_id) {\n const ptr0 = passStringToWasm0(sender_id, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_identify(this.__wbg_ptr, ptr0, len0);\n return takeObject(ret);\n }\n /**\n * @param {string} event_type\n * @param {any} event_payload\n * @returns {Promise}\n */\n track(event_type, event_payload) {\n const ptr0 = passStringToWasm0(event_type, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_track(this.__wbg_ptr, ptr0, len0, addHeapObject(event_payload));\n return takeObject(ret);\n }\n /**\n * @param {any} level\n * @param {string} message\n * @param {any} payload\n * @returns {Promise}\n */\n log(level, message, payload) {\n const ptr0 = passStringToWasm0(message, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.telemeter_log(this.__wbg_ptr, addHeapObject(level), ptr0, len0, addHeapObject(payload));\n return takeObject(ret);\n }\n /**\n * @returns {Promise}\n */\n finish() {\n const ptr = this.__destroy_into_raw();\n const ret = wasm.telemeter_finish(ptr);\n return takeObject(ret);\n }\n /**\n * @param {boolean | undefined} [new_debug_status]\n */\n debug(new_debug_status) {\n wasm.telemeter_debug(this.__wbg_ptr, isLikeNone(new_debug_status) ? 0xffffff : new_debug_status ? 1 : 0);\n }\n}\n\nasync function __wbg_load(module, imports) {\n if (typeof Response === \"function\" && module instanceof Response) {\n if (typeof WebAssembly.instantiateStreaming === \"function\") {\n try {\n return await WebAssembly.instantiateStreaming(module, imports);\n } catch (e) {\n if (module.headers.get(\"Content-Type\") != \"application/wasm\") {\n console.warn(\n \"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\",\n e,\n );\n } else {\n throw e;\n }\n }\n }\n\n const bytes = await module.arrayBuffer();\n return await WebAssembly.instantiate(bytes, imports);\n } else {\n const instance = await WebAssembly.instantiate(module, imports);\n\n if (instance instanceof WebAssembly.Instance) {\n return { instance, module };\n } else {\n return instance;\n }\n }\n}\n\nfunction __wbg_get_imports() {\n const imports = {};\n imports.wbg = {};\n imports.wbg.__wbindgen_string_new = function (arg0, arg1) {\n const ret = getStringFromWasm0(arg0, arg1);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_c728d68b8b34487e = function () {\n const ret = new Object();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_status_7841bb47be2a8f16 = function (arg0) {\n const ret = getObject(arg0).status;\n return ret;\n };\n imports.wbg.__wbg_headers_ea7ef583d1564b08 = function (arg0) {\n const ret = getObject(arg0).headers;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new0_ad75dd38f92424e2 = function () {\n const ret = new Date();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getTime_ed6ee333b702f8fc = function (arg0) {\n const ret = getObject(arg0).getTime();\n return ret;\n };\n imports.wbg.__wbindgen_object_drop_ref = function (arg0) {\n takeObject(arg0);\n };\n imports.wbg.__wbindgen_is_object = function (arg0) {\n const val = getObject(arg0);\n const ret = typeof val === \"object\" && val !== null;\n return ret;\n };\n imports.wbg.__wbg_crypto_58f13aa23ffcb166 = function (arg0) {\n const ret = getObject(arg0).crypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_process_5b786e71d465a513 = function (arg0) {\n const ret = getObject(arg0).process;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_versions_c2ab80650590b6a2 = function (arg0) {\n const ret = getObject(arg0).versions;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_node_523d7bd03ef69fba = function (arg0) {\n const ret = getObject(arg0).node;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_string = function (arg0) {\n const ret = typeof getObject(arg0) === \"string\";\n return ret;\n };\n imports.wbg.__wbg_msCrypto_abcb1295e768d1f2 = function (arg0) {\n const ret = getObject(arg0).msCrypto;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_require_2784e593a4674877 = function () {\n return handleError(function () {\n const ret = module.require;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_newwithlength_13b5319ab422dcf6 = function (arg0) {\n const ret = new Uint8Array(arg0 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_4a9aa5157afeb382 = function (arg0, arg1) {\n const ret = getObject(arg0)[arg1 >>> 0];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_next_1989a20442400aaa = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).next();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_done_bc26bf4ada718266 = function (arg0) {\n const ret = getObject(arg0).done;\n return ret;\n };\n imports.wbg.__wbg_value_0570714ff7d75f35 = function (arg0) {\n const ret = getObject(arg0).value;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_iterator_7ee1a391d310f8e4 = function () {\n const ret = Symbol.iterator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_get_2aff440840bb6202 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.get(getObject(arg0), getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_next_15da6a3df9290720 = function (arg0) {\n const ret = getObject(arg0).next;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_is_function = function (arg0) {\n const ret = typeof getObject(arg0) === \"function\";\n return ret;\n };\n imports.wbg.__wbg_call_669127b9d730c650 = function () {\n return handleError(function (arg0, arg1) {\n const ret = getObject(arg0).call(getObject(arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_object_clone_ref = function (arg0) {\n const ret = getObject(arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_self_3fad056edded10bd = function () {\n return handleError(function () {\n const ret = self.self;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_window_a4f46c98a61d4089 = function () {\n return handleError(function () {\n const ret = window.window;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_globalThis_17eff828815f7d84 = function () {\n return handleError(function () {\n const ret = globalThis.globalThis;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_global_46f939f6541643c5 = function () {\n return handleError(function () {\n const ret = global.global;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_is_undefined = function (arg0) {\n const ret = getObject(arg0) === undefined;\n return ret;\n };\n imports.wbg.__wbg_newnoargs_ccdcae30fd002262 = function (arg0, arg1) {\n const ret = new Function(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_isArray_38525be7442aa21e = function (arg0) {\n const ret = Array.isArray(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_call_53fc3abd42e24ec8 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_isSafeInteger_c38b0a16d0c7cef7 = function (arg0) {\n const ret = Number.isSafeInteger(getObject(arg0));\n return ret;\n };\n imports.wbg.__wbg_new_feb65b865d980ae2 = function (arg0, arg1) {\n try {\n var state0 = { a: arg0, b: arg1 };\n var cb0 = (arg0, arg1) => {\n const a = state0.a;\n state0.a = 0;\n try {\n return __wbg_adapter_103(a, state0.b, arg0, arg1);\n } finally {\n state0.a = a;\n }\n };\n const ret = new Promise(cb0);\n return addHeapObject(ret);\n } finally {\n state0.a = state0.b = 0;\n }\n };\n imports.wbg.__wbindgen_memory = function () {\n const ret = wasm.memory;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_buffer_344d9b41efe96da7 = function (arg0) {\n const ret = getObject(arg0).buffer;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_d8a000788389a31e = function (arg0) {\n const ret = new Uint8Array(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_set_dcfd613a3420f908 = function (arg0, arg1, arg2) {\n getObject(arg0).set(getObject(arg1), arg2 >>> 0);\n };\n imports.wbg.__wbg_length_a5587d6cd79ab197 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbindgen_string_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"string\" ? obj : undefined;\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_stringify_4039297315a25b00 = function () {\n return handleError(function (arg0) {\n const ret = JSON.stringify(getObject(arg0));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_set_40f7786a25a9cc7e = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_has_cdf8b85f6e903c80 = function () {\n return handleError(function (arg0, arg1) {\n const ret = Reflect.has(getObject(arg0), getObject(arg1));\n return ret;\n }, arguments);\n };\n imports.wbg.__wbg_fetch_701fcd2bde06379a = function (arg0, arg1) {\n const ret = getObject(arg0).fetch(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_fetch_b5d6bebed1e6c2d2 = function (arg0) {\n const ret = fetch(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_newwithbyteoffsetandlength_2dc04d99088b15e3 = function (arg0, arg1, arg2) {\n const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_new_e4960143e41697a4 = function () {\n return handleError(function () {\n const ret = new AbortController();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_signal_1ed842bebd6ae322 = function (arg0) {\n const ret = getObject(arg0).signal;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_abort_8355f201f30300bb = function (arg0) {\n getObject(arg0).abort();\n };\n imports.wbg.__wbindgen_error_new = function (arg0, arg1) {\n const ret = new Error(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {\n const ret = getObject(arg0) == getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_boolean_get = function (arg0) {\n const v = getObject(arg0);\n const ret = typeof v === \"boolean\" ? (v ? 1 : 0) : 2;\n return ret;\n };\n imports.wbg.__wbindgen_number_get = function (arg0, arg1) {\n const obj = getObject(arg1);\n const ret = typeof obj === \"number\" ? obj : undefined;\n getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbg_instanceof_Uint8Array_19e6f142a5e7e1e1 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Uint8Array;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_instanceof_ArrayBuffer_c7cc317e5c29cc0d = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof ArrayBuffer;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_entries_6d727b73ee02b7ce = function (arg0) {\n const ret = Object.entries(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_String_917f38a1211cf44b = function (arg0, arg1) {\n const ret = String(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_warn_ade8d3b7ecee11ff = function (arg0, arg1) {\n console.warn(getObject(arg0), getObject(arg1));\n };\n imports.wbg.__wbg_readyState_13e55da5ad6d64e2 = function (arg0) {\n const ret = getObject(arg0).readyState;\n return ret;\n };\n imports.wbg.__wbg_warn_4affe1093892a4ef = function (arg0) {\n console.warn(getObject(arg0));\n };\n imports.wbg.__wbg_close_f4135085ec3fc8f0 = function () {\n return handleError(function (arg0) {\n getObject(arg0).close();\n }, arguments);\n };\n imports.wbg.__wbg_new_b9b318679315404f = function () {\n return handleError(function (arg0, arg1) {\n const ret = new WebSocket(getStringFromWasm0(arg0, arg1));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setbinaryType_dcb62e0f2b346301 = function (arg0, arg1) {\n getObject(arg0).binaryType = takeObject(arg1);\n };\n imports.wbg.__wbg_log_7811587c4c6d2844 = function (arg0) {\n console.log(getObject(arg0));\n };\n imports.wbg.__wbg_error_f0a6627f4b23c19d = function (arg0) {\n console.error(getObject(arg0));\n };\n imports.wbg.__wbg_info_3ca7870690403fee = function (arg0) {\n console.info(getObject(arg0));\n };\n imports.wbg.__wbg_document_183cf1eecfdbffee = function (arg0) {\n const ret = getObject(arg0).document;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n };\n imports.wbg.__wbg_visibilityState_9721703a5ef75faf = function (arg0) {\n const ret = getObject(arg0).visibilityState;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getwithrefkey_3b3c46ba20582127 = function (arg0, arg1) {\n const ret = getObject(arg0)[getObject(arg1)];\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_length_cace2e0b3ddc0502 = function (arg0) {\n const ret = getObject(arg0).length;\n return ret;\n };\n imports.wbg.__wbg_addEventListener_0f2891b0794e07fa = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbg_removeEventListener_104d11302bb212d1 = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));\n }, arguments);\n };\n imports.wbg.__wbindgen_is_bigint = function (arg0) {\n const ret = typeof getObject(arg0) === \"bigint\";\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_i64 = function (arg0) {\n const ret = arg0;\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_in = function (arg0, arg1) {\n const ret = getObject(arg0) in getObject(arg1);\n return ret;\n };\n imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {\n const ret = BigInt.asUintN(64, arg0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {\n const ret = getObject(arg0) === getObject(arg1);\n return ret;\n };\n imports.wbg.__wbg_localStorage_e11f72e996a4f5d9 = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).localStorage;\n return isLikeNone(ret) ? 0 : addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_getItem_c81cd3ae30cd579a = function () {\n return handleError(function (arg0, arg1, arg2, arg3) {\n const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n var len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n }, arguments);\n };\n imports.wbg.__wbg_navigator_7078da62d92ff5ad = function (arg0) {\n const ret = getObject(arg0).navigator;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_mediaDevices_e00b1f64d2b9939f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).mediaDevices;\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_enumerateDevices_619d52f5eef34c2f = function () {\n return handleError(function (arg0) {\n const ret = getObject(arg0).enumerateDevices();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_setItem_fe04f524052a3839 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbindgen_cb_drop = function (arg0) {\n const obj = takeObject(arg0).original;\n if (obj.cnt-- == 1) {\n obj.a = 0;\n return true;\n }\n const ret = false;\n return ret;\n };\n imports.wbg.__wbg_deviceId_58f7da2228a26c02 = function (arg0, arg1) {\n const ret = getObject(arg1).deviceId;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_instanceof_Response_944e2745b5db71f5 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Response;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_randomFillSync_a0d98aa11c81fe89 = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).randomFillSync(takeObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbg_subarray_6ca5cfa7fbb9abbe = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_getRandomValues_504510b5564925af = function () {\n return handleError(function (arg0, arg1) {\n getObject(arg0).getRandomValues(getObject(arg1));\n }, arguments);\n };\n imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {\n const v = getObject(arg1);\n const ret = typeof v === \"bigint\" ? v : undefined;\n getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;\n getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);\n };\n imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {\n const ret = debugString(getObject(arg1));\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbindgen_throw = function (arg0, arg1) {\n throw new Error(getStringFromWasm0(arg0, arg1));\n };\n imports.wbg.__wbg_then_89e1c559530b85cf = function (arg0, arg1) {\n const ret = getObject(arg0).then(getObject(arg1));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_e5949c35d772a669 = function (arg0) {\n queueMicrotask(getObject(arg0));\n };\n imports.wbg.__wbg_then_1bbc9edafd859b06 = function (arg0, arg1, arg2) {\n const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_queueMicrotask_2be8b97a81fe4d00 = function (arg0) {\n const ret = getObject(arg0).queueMicrotask;\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_resolve_a3252b2860f0a09e = function (arg0) {\n const ret = Promise.resolve(getObject(arg0));\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_url_1f609e63ff1a7983 = function (arg0, arg1) {\n const ret = getObject(arg1).url;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);\n const len1 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len1;\n getInt32Memory0()[arg0 / 4 + 0] = ptr1;\n };\n imports.wbg.__wbg_send_2860805104507701 = function () {\n return handleError(function (arg0, arg1, arg2) {\n getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2));\n }, arguments);\n };\n imports.wbg.__wbg_instanceof_Window_cde2416cf5126a72 = function (arg0) {\n let result;\n try {\n result = getObject(arg0) instanceof Window;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n };\n imports.wbg.__wbg_new_19676474aa414d62 = function () {\n return handleError(function () {\n const ret = new Headers();\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbg_append_feec4143bbf21904 = function () {\n return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));\n }, arguments);\n };\n imports.wbg.__wbg_newwithstrandinit_29038da14d09e330 = function () {\n return handleError(function (arg0, arg1, arg2) {\n const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));\n return addHeapObject(ret);\n }, arguments);\n };\n imports.wbg.__wbindgen_closure_wrapper1532 = function (arg0, arg1, arg2) {\n const ret = makeClosure(arg0, arg1, 76, __wbg_adapter_46);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1602 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_49);\n return addHeapObject(ret);\n };\n imports.wbg.__wbindgen_closure_wrapper1834 = function (arg0, arg1, arg2) {\n const ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_52);\n return addHeapObject(ret);\n };\n\n return imports;\n}\n\nfunction __wbg_init_memory(imports, maybe_memory) {}\n\nfunction __wbg_finalize_init(instance, module) {\n wasm = instance.exports;\n __wbg_init.__wbindgen_wasm_module = module;\n cachedBigInt64Memory0 = null;\n cachedFloat64Memory0 = null;\n cachedInt32Memory0 = null;\n cachedUint8Memory0 = null;\n\n wasm.__wbindgen_start();\n return wasm;\n}\n\nfunction initSync(module) {\n if (wasm !== undefined) return wasm;\n\n const imports = __wbg_get_imports();\n\n __wbg_init_memory(imports);\n\n if (!(module instanceof WebAssembly.Module)) {\n module = new WebAssembly.Module(module);\n }\n\n const instance = new WebAssembly.Instance(module, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nasync function __wbg_init(input) {\n if (wasm !== undefined) return wasm;\n\n if (typeof input === \"undefined\") {\n input = new URL(\"telemeter_wasm_bg.wasm\", import.meta.url);\n }\n const imports = __wbg_get_imports();\n\n if (\n typeof input === \"string\" ||\n (typeof Request === \"function\" && input instanceof Request) ||\n (typeof URL === \"function\" && input instanceof URL)\n ) {\n input = fetch(input);\n }\n\n __wbg_init_memory(imports);\n\n const { instance, module } = await __wbg_load(await input, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nexport { initSync };\nexport default __wbg_init;\n","/* eslint-disable-next-line */\nimport init, { Telemeter as TelemeterWASM } from \"../telemeter-wasm\";\n\n/**\n * @classdesc\n * This class implements the @wiris/telemeter-wasm. A utility that helps our Solutions to send the data\n * to Telemetry in a more comfortable and homogeneous way.\n */\nexport default class Telemeter {\n /**\n * Inits Telemeter class.\n * The parameters structures are defiended on {@link [Telemeter API](https://github.com/wiris/telemeter/blob/main/docs/USAGE.md#telemeter-api)}\n * @param {Object} telemeterAttributes.solution - The product that send data to Telemetry.\n * @param {Object} telemeterAttributes.hosts - Data about the environment where solution is integrated.\n * @param {Object} telemeterAttributes.config - Configuration parameters.\n */\n static init(telemeterAttributes) {\n if (!this.telemeter && !this.waitingForInit) {\n this.waitingForInit = true;\n init(telemeterAttributes.url)\n .then(() => {\n this.telemeter = new TelemeterWASM(\n telemeterAttributes.solution,\n telemeterAttributes.hosts,\n telemeterAttributes.config,\n );\n })\n .catch((error) => {\n console.log(error);\n })\n .finally(() => (this.waitingForInit = false));\n }\n }\n\n /**\n * Closes the Telemetry Session. After calling this method no data will be added to the Telemetry Session.\n */\n static async finish() {\n if (!this.telemeter) return;\n\n try {\n const local_telemeter = this.telemeter;\n this.telemeter = undefined;\n await local_telemeter.finish();\n } catch (e) {\n console.error(e);\n }\n }\n}\n","import Configuration from \"./configuration\";\nimport Core from \"./core.src\";\nimport EditorListener from \"./editorlistener\";\nimport Listeners from \"./listeners\";\nimport MathML from \"./mathml\";\nimport Util from \"./util\";\nimport Telemeter from \"./telemeter\";\n\nexport default class ContentManager {\n /**\n * @classdesc\n * This class represents a modal dialog, managing the following:\n * - The insertion of content into the current instance of the {@link ModalDialog} class.\n * - The actions to be done once the modal object has been submitted\n * (submitAction} method).\n * - The update of the content when the {@link ModalDialog} class is also updated,\n * for example when ModalDialog is re-opened.\n * - The communication between the {@link ModalDialog} class and itself, if the content\n * has been changed (hasChanges} method).\n * @constructs\n * @param {Object} contentManagerAttributes - Object containing all attributes needed to\n * create a new instance.\n */\n constructor(contentManagerAttributes) {\n /**\n * An object containing MathType editor parameters. See\n * http://docs.wiris.com/en/mathtype/mathtype_web/sdk-api/parameters for further information.\n * @type {Object}\n */\n this.editorAttributes = {};\n if (\"editorAttributes\" in contentManagerAttributes) {\n this.editorAttributes = contentManagerAttributes.editorAttributes;\n } else {\n throw new Error(\"ContentManager constructor error: editorAttributes property missed.\");\n }\n\n /**\n * CustomEditors instance. Contains the custom editors.\n * @type {CustomEditors}\n */\n this.customEditors = null;\n if (\"customEditors\" in contentManagerAttributes) {\n this.customEditors = contentManagerAttributes.customEditors;\n }\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @type {Object}\n * @property {String} editor - Editor name. Usually the HTML editor.\n * @property {String} mode - Save mode. Xml by default.\n * @property {String} version - Plugin version.\n */\n this.environment = {};\n if (\"environment\" in contentManagerAttributes) {\n this.environment = contentManagerAttributes.environment;\n } else {\n throw new Error(\"ContentManager constructor error: environment property missed\");\n }\n\n /**\n * ContentManager language.\n * @type {String}\n */\n this.language = \"\";\n if (\"language\" in contentManagerAttributes) {\n this.language = contentManagerAttributes.language;\n } else {\n throw new Error(\"ContentManager constructor error: language property missed\");\n }\n\n /**\n * {@link EditorListener} instance. Manages the changes inside the editor.\n * @type {EditorListener}\n */\n this.editorListener = new EditorListener();\n\n /**\n * MathType editor instance.\n * @type {JsEditor}\n */\n this.editor = null;\n\n /**\n * Navigator user agent.\n * @type {String}\n */\n this.ua = navigator.userAgent.toLowerCase();\n\n /**\n * Mobile device properties object\n * @type {DeviceProperties}\n */\n this.deviceProperties = {};\n this.deviceProperties.isAndroid = this.ua.indexOf(\"android\") > -1;\n this.deviceProperties.isIOS = ContentManager.isIOS();\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.toolbar = null;\n\n /**\n * Custom editor toolbar.\n * @type {String}\n */\n this.dbclick = null;\n\n /**\n * Instance of the {@link ModalDialog} class associated with the current\n * {@link ContentManager} instance.\n * @type {ModalDialog}\n */\n this.modalDialogInstance = null;\n\n /**\n * ContentManager listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n /**\n * MathML associated to the ContentManager instance.\n * @type {String}\n */\n this.mathML = null;\n\n /**\n * Indicates if the edited element is a new one or not.\n * @type {Boolean}\n */\n this.isNewElement = true;\n\n /**\n * {@link IntegrationModel} instance. Needed to call wrapper methods.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n }\n\n /**\n * Adds a new listener to the current {@link ContentManager} instance.\n * @param {Object} listener - The listener to be added.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Sets an instance of {@link IntegrationModel} class to the current {@link ContentManager}\n * instance.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} instance.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link ModalDialog} instance into the current {@link ContentManager} instance.\n * @param {ModalDialog} modalDialogInstance - The {@link ModalDialog} instance\n */\n setModalDialogInstance(modalDialogInstance) {\n this.modalDialogInstance = modalDialogInstance;\n }\n\n /**\n * Inserts the content into the current {@link ModalDialog} instance updating\n * the title and inserting the JavaScript editor.\n */\n insert() {\n // Before insert the editor we update the modal object title to avoid weird render display.\n this.updateTitle(this.modalDialogInstance);\n this.insertEditor(this.modalDialogInstance);\n }\n\n /**\n * Inserts MathType editor into the {@link ModalDialog.contentContainer}. It waits until\n * editor's JavaScript is loaded.\n */\n insertEditor() {\n if (ContentManager.isEditorLoaded()) {\n this.editor = window.com.wiris.jsEditor.JsEditor.newInstance(this.editorAttributes);\n this.editor.insertInto(this.modalDialogInstance.contentContainer);\n this.editor.focus();\n\n // `editor.action(\"rtl\");` toggles the RTL mode based on the current state, it doesn't just switch to RTL.\n if (this.modalDialogInstance.rtl && !this.editor.getEditorModel().isRTL()) {\n this.editor.action(\"rtl\");\n }\n // Setting div in rtl in case of it's activated.\n if (this.editor.getEditorModel().isRTL()) {\n this.editor.element.style.direction = \"rtl\";\n }\n\n // Editor listener: this object manages the changes logic of editor.\n this.editor.getEditorModel().addEditorListener(this.editorListener);\n\n // iOS events.\n if (this.modalDialogInstance.deviceProperties.isIOS) {\n setTimeout(function () {\n // Make sure the modalDialogInstance is available when the timeout is over\n // to avoid throw errors and stop execution.\n if (this.hasOwnProperty(\"modalDialogInstance\")) this.modalDialogInstance.hideKeyboard(); // eslint-disable-line no-prototype-builtins\n }, 400);\n\n const formulaDisplayDiv = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n Util.addEvent(formulaDisplayDiv, \"focus\", this.modalDialogInstance.handleOpenedIosSoftkeyboard);\n Util.addEvent(formulaDisplayDiv, \"blur\", this.modalDialogInstance.handleClosedIosSoftkeyboard);\n }\n // Fire onLoad event. Necessary to set the MathML into the editor\n // after is loaded.\n this.listeners.fire(\"onLoad\", {});\n } else {\n setTimeout(ContentManager.prototype.insertEditor.bind(this), 100);\n }\n }\n\n /**\n * Initializes the current class by loading MathType script.\n */\n init() {\n if (!ContentManager.isEditorLoaded()) {\n this.addEditorAsExternalDependency();\n }\n }\n\n /**\n * Adds script element to the DOM to include editor externally.\n */\n addEditorAsExternalDependency() {\n const script = document.createElement(\"script\");\n script.type = \"text/javascript\";\n let editorUrl = Configuration.get(\"editorUrl\");\n\n // We create an object url for parse url string and work more efficiently.\n const anchorElement = document.createElement(\"a\");\n\n ContentManager.setHrefToAnchorElement(anchorElement, editorUrl);\n ContentManager.setProtocolToAnchorElement(anchorElement);\n\n editorUrl = ContentManager.getURLFromAnchorElement(anchorElement);\n\n // Load editor URL. We add stats as GET params.\n const stats = this.getEditorStats();\n script.src = `${editorUrl}?lang=${this.language}&stats-editor=${stats.editor}&stats-mode=${stats.mode}&stats-version=${stats.version}`;\n\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n }\n\n /**\n * Sets the specified url to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set 'url'.\n * @param {String} url - URL to set.\n */\n static setHrefToAnchorElement(anchorElement, url) {\n anchorElement.href = url;\n }\n\n /**\n * Sets the current protocol to the anchor element.\n * @param {HTMLAnchorElement} anchorElement - Element where set its protocol.\n */\n static setProtocolToAnchorElement(anchorElement) {\n // Change to https if necessary.\n if (window.location.href.indexOf(\"https://\") === 0) {\n // It check if browser is https and configuration is http.\n // If this is so, we will replace protocol.\n if (anchorElement.protocol === \"http:\") {\n anchorElement.protocol = \"https:\";\n }\n }\n }\n\n /**\n * Returns the url of the anchor element adding the current port\n * if it is needed.\n * @param {HTMLAnchorElement} anchorElement - Element where extract the url.\n * @returns {String}\n */\n static getURLFromAnchorElement(anchorElement) {\n // Check protocol and remove port if it's standard.\n const removePort = anchorElement.port === \"80\" || anchorElement.port === \"443\" || anchorElement.port === \"\";\n return `${anchorElement.protocol}//${anchorElement.hostname}${removePort ? \"\" : `:${anchorElement.port}`}${anchorElement.pathname.startsWith(\"/\") ? anchorElement.pathname : `/${anchorElement.pathname}`}`; // eslint-disable-line max-len\n }\n\n /**\n * Returns object with editor stats.\n *\n * @typedef {Object} EditorStatsObject\n * @property {string} editor - Editor name.\n * @property {string} mode - Current configuration for formula save mode.\n * @property {string} version - Current plugins version.\n * @returns {EditorStatsObject}\n */\n getEditorStats() {\n // Editor stats. Use environment property to set it.\n const stats = {};\n if (\"editor\" in this.environment) {\n stats.editor = this.environment.editor;\n } else {\n stats.editor = \"unknown\";\n }\n\n if (\"mode\" in this.environment) {\n stats.mode = this.environment.mode;\n } else {\n stats.mode = Configuration.get(\"saveMode\");\n }\n\n if (\"version\" in this.environment) {\n stats.version = this.environment.version;\n } else {\n stats.version = Configuration.get(\"version\");\n }\n\n return stats;\n }\n\n /**\n * Returns true if device is iOS. Otherwise, false.\n * @returns {Boolean}\n */\n static isIOS() {\n return (\n [\"iPad Simulator\", \"iPhone Simulator\", \"iPod Simulator\", \"iPad\", \"iPhone\", \"iPod\"].includes(navigator.platform) ||\n // iPad on iOS 13 detection\n (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\n );\n }\n\n /**\n * Returns true if device is Mobile. Otherwise, false.\n * @returns {Boolean}\n */\n static isMobile() {\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n }\n\n /**\n * Returns true if editor is loaded. Otherwise, false.\n * @returns {Boolean}\n */\n static isEditorLoaded() {\n // To know if editor JavaScript is loaded we need to wait until\n // window.com.wiris.jsEditor.JsEditor.newInstance is ready.\n return (\n window.com &&\n window.com.wiris &&\n window.com.wiris.jsEditor &&\n window.com.wiris.jsEditor.JsEditor &&\n window.com.wiris.jsEditor.JsEditor.newInstance\n );\n }\n\n /**\n * Sets the {@link ContentManager.editor} initial content.\n */\n setInitialContent() {\n if (!this.isNewElement) {\n this.setMathML(this.mathML);\n }\n }\n\n /**\n * Sets a MathML into {@link ContentManager.editor} instance.\n * @param {String} mathml - MathML string.\n * @param {Boolean} focusDisabled - If true editor don't get focus after the MathML is set.\n * False by default.\n */\n setMathML(mathml, focusDisabled) {\n // By default focus is enabled.\n if (typeof focusDisabled === \"undefined\") {\n focusDisabled = false;\n }\n // Using setMathML method is not a change produced by the user but for the API\n // so we set to false the contentChange property of editorListener.\n this.editor.setMathMLWithCallback(mathml, () => {\n this.editorListener.setWaitingForChanges(true);\n });\n\n // We need to wait a little until the callback finish.\n setTimeout(() => {\n this.editorListener.setIsContentChanged(false);\n }, 500);\n\n // In some scenarios - like closing modal object - editor mustn't be focused.\n if (!focusDisabled) {\n this.onFocus();\n }\n }\n\n /**\n * Sets the focus to the current instance of {@link ContentManager.editor}. Triggered by\n * {@link ModalDialog.focus}.\n */\n onFocus() {\n if (typeof this.editor !== \"undefined\" && this.editor != null) {\n this.editor.focus();\n\n // On WordPress integration, the focus gets lost right after setting it.\n // To fix this, we enforce another focus some milliseconds after this behaviour.\n setTimeout(() => {\n this.editor.focus();\n }, 100);\n }\n }\n\n /**\n * Updates the edition area by calling {@link IntegrationModel.updateFormula}.\n * Triggered by {@link ModalDialog.submitAction}.\n */\n submitAction() {\n if (!this.editor.isFormulaEmpty()) {\n let mathML = this.editor.getMathMLWithSemantics();\n // Add class for custom editors.\n if (this.customEditors.getActiveEditor() !== null) {\n const { toolbar } = this.customEditors.getActiveEditor();\n mathML = MathML.addCustomEditorClassAttribute(mathML, toolbar);\n } else {\n // We need - if exists - the editor name from MathML\n // class attribute.\n Object.keys(this.customEditors.editors).forEach((key) => {\n mathML = MathML.removeCustomEditorClassAttribute(mathML, key);\n });\n }\n const mathmlEntitiesEncoded = MathML.mathMLEntities(mathML);\n this.integrationModel.updateFormula(mathmlEntitiesEncoded);\n } else {\n this.integrationModel.updateFormula(null);\n }\n\n this.customEditors.disable();\n this.integrationModel.notifyWindowClosed();\n\n // Set disabled focus to prevent lost focus.\n this.setEmptyMathML();\n this.customEditors.disable();\n }\n\n /**\n * Sets an empty MathML as {@link ContentManager.editor} content.\n * This will open the MT/CT editor with the hand mode.\n * It adds dir rtl in case of it's activated.\n */\n setEmptyMathML() {\n const isMobile = this.deviceProperties.isAndroid || this.deviceProperties.isIOS;\n const isRTL = this.editor.getEditorModel().isRTL();\n\n if (isMobile || this.integrationModel.forcedHandMode) {\n // For mobile devices or forced hand mode, set an empty annotation MATHML to maintain the editor in Hand mode.\n const mathML = `[]`;\n this.setMathML(mathML, true);\n } else {\n // For non-mobile devices or not forced hand mode, set the empty MathML without an annotation.\n const mathML = ``;\n this.setMathML(mathML, true);\n }\n }\n\n /**\n * Open event. Triggered by {@link ModalDialog.open}. Does the following:\n * - Updates the {@link ContentManager.editor} content\n * (with an empty MathML or an existing formula),\n * - Updates the {@link ContentManager.editor} toolbar.\n * - Recovers the the focus.\n */\n onOpen() {\n if (this.isNewElement) {\n this.setEmptyMathML();\n } else {\n this.setMathML(this.mathML);\n }\n const toolbar = this.updateToolbar();\n this.onFocus();\n\n if (this.deviceProperties.isIOS) {\n const zoom = document.documentElement.clientWidth / window.innerWidth;\n\n if (zoom !== 1) {\n // Open editor in Keyboard mode if user use iOS, Safari and page is zoomed.\n this.setKeyboardMode();\n }\n }\n\n const trigger = this.dbclick ? \"formula\" : \"button\";\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"OPENED_MTCT_EDITOR\", {\n toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking OPENED_MTCT_EDITOR\", error);\n }\n\n Core.globalListeners.fire(\"onModalOpen\", {});\n\n if (this.integrationModel.forcedHandMode) {\n this.hideHandModeButton();\n\n // In case we have a keyboard written formula, we still want it to be opened with handMode.\n if (this.mathML && !this.mathML.includes('') && !this.isNewElement) {\n this.openHandOnKeyboardMathML(this.mathML, this.editor);\n }\n }\n }\n\n /**\n * Change Editor in keyboard mode when is loaded\n */\n setKeyboardMode() {\n const wrsEditor = document.getElementsByClassName(\"wrs_handOpen wrs_disablePalette\")[0];\n if (wrsEditor) {\n wrsEditor.classList.remove(\"wrs_handOpen\");\n wrsEditor.classList.remove(\"wrs_disablePalette\");\n } else {\n setTimeout(ContentManager.prototype.setKeyboardMode.bind(this), 100);\n }\n }\n\n /**\n * Hides the hand <-> keyboard mode switch.\n *\n * This method relies completely on the classes used on different HTML elements within the editor itself, meaning\n * any change on those classes will make this code stop working properly.\n *\n * On top of that, some of those classes are changed on runtime (for example, the one that makes some buttons change).\n * This forces us to use some delayed code (this is, a timeout) to make sure everything exists when we need it.\n * @param {*} forced (boolean) Forces the user to stay in Hand mode by hiding the keyboard mode button.\n */\n hideHandModeButton(forced = true) {\n if (this.handSwitchHidden) {\n return; // hand <-> keyboard button already hidden.\n }\n\n // \"Open hand mode\" button takes a little bit to be available.\n // This selector gets the hand <-> keyboard mode switch\n const handModeButtonSelector =\n \"div.wrs_editor.wrs_flexEditor.wrs_withHand.wrs_animated .wrs_handWrapper input[type=button]\";\n\n // If in \"forced mode\", we hide the \"keyboard button\" so the user can't can't change between hand and keyboard modes.\n // We use an observer to ensure that the button it hidden as soon as it appears.\n if (forced) {\n const mutationInstance = new MutationObserver((mutations) => {\n const handModeButton = document.querySelector(handModeButtonSelector);\n if (handModeButton) {\n handModeButton.hidden = true;\n this.handSwitchHidden = true;\n mutationInstance.disconnect();\n }\n });\n mutationInstance.observe(document.body, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n }\n\n /**\n * It will open any formula written in Keyboard mode with the hand mode with the default hand trace.\n *\n * @param {String} mathml The original KeyBoard MathML\n * @param {Object} editor The editor object.\n */\n async openHandOnKeyboardMathML(mathml, editor) {\n // First, as an editor requirement, we need to update the editor object with the current MathML formula.\n // Once the MathML formula is updated to the one we want to open with handMode, we will be able to proceed.\n await new Promise((resolve) => {\n editor.setMathMLWithCallback(mathml, resolve);\n });\n\n // We wait until the hand editor object exists.\n await this.waitForHand(editor);\n\n // Logic to get the hand traces and open the formula in hand mode.\n // This logic comes from the editor.\n const handEditor = editor.hand;\n editor.handTemporalMathML = editor.getMathML();\n const handCoordinates = editor.editorModel.getHandStrokes();\n handEditor.setStrokes(handCoordinates);\n handEditor.fitStrokes(true);\n editor.openHand();\n }\n\n /**\n * Waits until the hand editor object exists.\n * @param {Obect} editor The editor object.\n */\n async waitForHand(editor) {\n while (!editor.hand) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n\n /**\n * Sets the correct toolbar depending if exist other custom toolbars\n * at the same time (e.g: Chemistry).\n */\n updateToolbar() {\n this.updateTitle(this.modalDialogInstance);\n const customEditor = this.customEditors.getActiveEditor();\n\n let toolbar;\n if (customEditor) {\n toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties.toolbar;\n\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n }\n } else {\n toolbar = this.getToolbar();\n if (this.toolbar == null || this.toolbar !== toolbar) {\n this.setToolbar(toolbar);\n this.customEditors.disable();\n }\n }\n\n return toolbar;\n }\n\n /**\n * Updates the current {@link ModalDialog.title}. If a {@link CustomEditors} is enabled\n * sets the custom editor title. Otherwise sets the default title.\n */\n updateTitle() {\n const customEditor = this.customEditors.getActiveEditor();\n if (customEditor) {\n this.modalDialogInstance.setTitle(customEditor.title);\n } else {\n this.modalDialogInstance.setTitle(\"MathType\");\n }\n }\n\n /**\n * Returns the editor toolbar, depending on the configuration local or server side.\n * @returns {String} - Toolbar identifier.\n */\n getToolbar() {\n let toolbar = \"general\";\n if (\"toolbar\" in this.editorAttributes) {\n ({ toolbar } = this.editorAttributes);\n }\n // TODO: Change global integration variable for integration custom toolbar.\n if (toolbar === \"general\") {\n // eslint-disable-next-line camelcase\n toolbar =\n typeof _wrs_int_wirisProperties === \"undefined\" || typeof _wrs_int_wirisProperties.toolbar === \"undefined\"\n ? \"general\"\n : _wrs_int_wirisProperties.toolbar;\n }\n\n return toolbar;\n }\n\n /**\n * Sets the current {@link ContentManager.editor} instance toolbar.\n * @param {String} toolbar - The toolbar name.\n */\n setToolbar(toolbar) {\n this.toolbar = toolbar;\n this.editor.setParams({ toolbar: this.toolbar });\n }\n\n /**\n * Sets the custom headers added on editor requests.\n * @returns {Object} headers - key value headers.\n */\n setCustomHeaders(headers) {\n let headersObj = {};\n\n // We control that we only get String or Object as the input.\n if (typeof headers === \"object\") {\n headersObj = headers;\n } else if (typeof headers === \"string\") {\n headersObj = Util.convertStringToObject(headers);\n }\n\n this.editor.setParams({ customHeaders: headersObj });\n return headersObj;\n }\n\n /**\n * Returns true if the content of the editor has been changed. The logic of the changes\n * is delegated to {@link EditorListener} class.\n * @returns {Boolean} True if the editor content has been changed. False otherwise.\n */\n hasChanges() {\n return !this.editor.isFormulaEmpty() && this.editorListener.getIsContentChanged();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined && keyboardEvent.repeat === false) {\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n // Code to detect Esc event.\n // There should be only one element with class name 'wrs_pressed' at the same time.\n let list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor3RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_expandButton wrs_expandButtonFor2RowsLayout wrs_pressed\");\n if (list.length === 0) {\n list = document.getElementsByClassName(\"wrs_select wrs_pressed\");\n if (list.length === 0) {\n this.modalDialogInstance.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.modalDialogInstance.submitButton) {\n // Focus is on OK button.\n this.editor.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.querySelector('[title=\"Manual\"]') === document.activeElement) {\n // Focus is on minimize button (_).\n this.modalDialogInstance.closeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.minimizeDiv) {\n // Focus on cancel button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n this.modalDialogInstance.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.modalDialogInstance.cancelButton) {\n // Focus is on X button.\n this.modalDialogInstance.minimizeDiv.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (document.activeElement === this.modalDialogInstance.closeDiv) {\n // Focus on help button.\n if (!(this.modalDialogInstance.properties.state === \"minimized\")) {\n const element = document.querySelector('[title=\"Manual\"]');\n element.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n } else {\n // There should be only one element with class name 'wrs_formulaDisplay'.\n const element = document.getElementsByClassName(\"wrs_formulaDisplay\")[0];\n if (element.getAttribute(\"class\") === \"wrs_formulaDisplay wrs_focused\") {\n // Focus is on formuladisplay.\n this.modalDialogInstance.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n }\n }\n}\n","/**\n * A custom editor is MathType editor with a different\n * @typedef {Object} CustomEditor\n * @property {String} CustomEditor.name - Custom editor name.\n * @property {String} CustomEditor.toolbar - Custom editor toolbar.\n * @property {String} CustomEditor.icon - Custom editor icon.\n * @property {String} CustomEditor.confVariable - Configuration property to manage\n * the availability of the custom editor.\n * @property {String} CustomEditor.title - Custom editor modal dialog title.\n * @property {String} CustomEditor.tooltip - Custom editor icon tooltip.\n */\n\nexport default class CustomEditors {\n /**\n * @classdesc\n * This class represents the MathType custom editors manager.\n * A custom editor is MathType editor with a custom toolbar.\n * This class associates a {@link CustomEditor} to:\n * - It's own formulas\n * - A custom toolbar\n * - An icon to open it from a HTML editor.\n * - A tooltip for the icon.\n * - A global variable to enable or disable it globally.\n * @constructs\n */\n constructor() {\n /**\n * The custom editors.\n * @type {Array.}\n */\n\n this.editors = [];\n /**\n * The active editor name.\n * @type {String}\n */\n this.activeEditor = \"default\";\n }\n\n /**\n * Adds a {@link CustomEditor} to editors array.\n * @param {String} editorName - The editor name.\n * @param {CustomEditor} editorParams - The custom editor parameters.\n */\n addEditor(editorName, editorParams) {\n const customEditor = {};\n customEditor.name = editorParams.name;\n customEditor.toolbar = editorParams.toolbar;\n customEditor.icon = editorParams.icon;\n customEditor.confVariable = editorParams.confVariable;\n customEditor.title = editorParams.title;\n customEditor.tooltip = editorParams.tooltip;\n this.editors[editorName] = customEditor;\n }\n\n /**\n * Enables a {@link CustomEditor}.\n * @param {String} customEditorName - The custom editor name.\n */\n enable(customEditorName) {\n this.activeEditor = customEditorName;\n }\n\n /**\n * Disables a {@link CustomEditor}.\n */\n disable() {\n this.activeEditor = \"default\";\n }\n\n /**\n * Returns the active editor.\n * @return {CustomEditor} - A {@link CustomEditor} if a custom editor is enabled. Null otherwise.\n */\n getActiveEditor() {\n if (this.activeEditor !== \"default\") {\n return this.editors[this.activeEditor];\n }\n return null;\n }\n}\n","/**\n * Represents the configuration properties generated from the frontend (JavaScript variables).\n * @type {Object}\n * @property {string} imageClassName - Default MathType formula image class.\n * @property {string} imageClassName - Default MathType CAS image class.\n * @ignore\n */\nconst jsProperties = {\n imageCustomEditorName: \"data-custom-editor\",\n imageClassName: \"Wirisformula\",\n CASClassName: \"Wiriscas\",\n};\nexport default jsProperties;\n","export default class Event {\n /**\n * @classdesc\n * This class represents a custom event. Events should be fired by the {@link Listener} class.\n *\n * ```js\n * let customEvent = new Event();\n * customEvent.properties = {};\n *\n * let listeners = new Listeners();\n * listeners.newListener(eventName, callback);\n *\n * listeners.fire(eventName, customEvent) *\n * ```\n * @constructs\n */\n constructor() {\n /**\n * Indicates if the event should be cancelled.\n * @type {Boolean}\n */\n\n this.cancelled = false;\n /**\n * Indicates if the event should be prevented.\n * @type {Boolean}\n */\n this.defaultPrevented = false;\n }\n\n /**\n * Cancels the event.\n */\n cancel() {\n this.cancelled = true;\n }\n\n /**\n * Prevents the default action.\n */\n preventDefault() {\n this.defaultPrevented = true;\n }\n}\n","import IntegrationModel from \"./integrationmodel\";\n\n/**\n\n */\nexport default class PopUpMessage {\n /**\n * @classdesc\n * This class represents a dialog message overlaying a DOM element in order to\n * accept / cancel discard changes. The dialog can be closed i.e the overlay disappears\n * o canceled. In this last case a callback function should be called.\n * @constructs\n * @param {Object} popupMessageAttributes - Object containing popup properties.\n * @param {HTMLElement} popupMessageAttributes.overlayElement - Element to overlay.\n * @param {Object} popupMessageAttributes.callbacks - Contains callback\n * methods for close and cancel actions.\n * @param {Object} popupMessageAttributes.strings - Contains all the strings needed.\n */\n constructor(popupMessageAttributes) {\n /**\n * Element to be overlaid when the popup appears.\n */\n this.overlayElement = popupMessageAttributes.overlayElement;\n\n this.callbacks = popupMessageAttributes.callbacks;\n\n /**\n * HTMLElement element to wrap all HTML elements inside the popupMessage.\n */\n this.overlayWrapper = this.overlayElement.appendChild(document.createElement(\"div\"));\n this.overlayWrapper.setAttribute(\"class\", \"wrs_popupmessage_overlay_envolture\");\n\n /**\n * HTMLElement to display the popup message, close button and cancel button.\n */\n this.message = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n this.message.id = \"wrs_popupmessage\";\n this.message.setAttribute(\"class\", \"wrs_popupmessage_panel\");\n this.message.setAttribute(\"role\", \"dialog\");\n this.message.setAttribute(\"aria-describedby\", \"description_txt\");\n const paragraph = document.createElement(\"p\");\n const text = document.createTextNode(popupMessageAttributes.strings.message);\n paragraph.appendChild(text);\n paragraph.id = \"description_txt\";\n this.message.appendChild(paragraph);\n\n /**\n * HTML element overlaying the overlayElement.\n */\n const overlay = this.overlayWrapper.appendChild(document.createElement(\"div\"));\n overlay.setAttribute(\"class\", \"wrs_popupmessage_overlay\");\n // We create a overlay that close popup message on click in there\n overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n /**\n * HTML element containing cancel and close buttons.\n */\n this.buttonArea = this.message.appendChild(document.createElement(\"div\"));\n this.buttonArea.setAttribute(\"class\", \"wrs_popupmessage_button_area\");\n this.buttonArea.id = \"wrs_popup_button_area\";\n\n // Close button arguments.\n const buttonSubmitArguments = {\n class: \"wrs_button_accept\",\n innerHTML: popupMessageAttributes.strings.submitString,\n id: \"wrs_popup_accept_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-close-button\",\n };\n\n /**\n * Close button arguments.\n */\n this.closeButton = this.createButton(buttonSubmitArguments, this.closeAction.bind(this));\n this.buttonArea.appendChild(this.closeButton);\n\n // Cancel button arguments.\n const buttonCancelArguments = {\n class: \"wrs_button_cancel\",\n innerHTML: popupMessageAttributes.strings.cancelString,\n id: \"wrs_popup_cancel_button\",\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cd-cancel-button\",\n };\n\n /**\n * Cancel button.\n */\n this.cancelButton = this.createButton(buttonCancelArguments, this.cancelAction.bind(this));\n this.buttonArea.appendChild(this.cancelButton);\n }\n\n /**\n * This method create a button with arguments and return button dom object\n * @param {Object} parameters - An object containing id, class and innerHTML button text.\n * @param {String} parameters.id - Button id.\n * @param {String} parameters.class - Button class name.\n * @param {String} parameters.innerHTML - Button innerHTML text.\n * @param {Object} callback- Callback method to call on click event.\n * @returns {HTMLElement} HTML button.\n */\n // eslint-disable-next-line class-methods-use-this\n createButton(parameters, callback) {\n let element = {};\n element = document.createElement(\"button\");\n element.setAttribute(\"id\", parameters.id);\n element.setAttribute(\"class\", parameters.class);\n element.innerHTML = parameters.innerHTML;\n element.addEventListener(\"click\", callback);\n if (parameters[\"data-testid\"]) {\n element.setAttribute(\"data-testid\", parameters[\"data-testid\"]);\n }\n\n return element;\n }\n\n /**\n * Shows the popupmessage containing a message, and two buttons\n * to cancel the action or close the modal dialog.\n */\n show() {\n if (this.overlayWrapper.style.display !== \"block\") {\n // Clear focus with blur for prevent press any key.\n document.activeElement.blur();\n this.overlayWrapper.style.display = \"block\";\n this.closeButton.focus();\n } else {\n this.overlayWrapper.style.display = \"none\";\n // _wrs_modalWindow.focus(); This throws an error of not existing _wrs_modalWindow\n }\n }\n\n /**\n * This method cancels the popupMessage: the dialog disappears revealing the overlaid element.\n * A callback method is called (if defined). For example a method to focus the overlaid element.\n */\n cancelAction() {\n this.overlayWrapper.style.display = \"none\";\n if (typeof this.callbacks.cancelCallback !== \"undefined\") {\n this.callbacks.cancelCallback();\n // Set temporal image to null to prevent loading\n // an existent formula when starting one from scratch. Make focus come back too.\n // IntegrationModel.setActionsOnCancelButtons();\n }\n }\n\n /**\n * This method closes the popupMessage: the dialog disappears and the close callback is called.\n * For example to close the overlaid element.\n */\n closeAction() {\n this.cancelAction();\n if (typeof this.callbacks.closeCallback !== \"undefined\") {\n this.callbacks.closeCallback();\n }\n IntegrationModel.setActionsOnCancelButtons();\n }\n\n /**\n * Handle keyboard events detected in modal when elements of this class intervene.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Code to detect Esc event.\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n this.cancelAction();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.closeButton) {\n this.cancelButton.focus();\n } else {\n this.closeButton.focus();\n }\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n }\n }\n }\n}\n","/**\n * This module provides protection against external focus management scripts\n * that might interfere with the MathType editor modal.\n */\n\n/**\n * focusProtection function creates and returns methods to prevent external scripts from\n * interfering with the focus of the MathType modal dialog.\n *\n * @returns {Object} An object with protect and unprotect methods.\n */\nconst focusProtection = () => {\n /**\n * Initialize focus protection on the given modal element.\n *\n * @param {HTMLElement} modalElement - The modal element to protect\n * @param {HTMLElement} overlayElement - The overlay element of the modal (not used in current implementation)\n * @param {HTMLElement} editorElement - The editor element inside the modal\n */\n const protect = (modalElement, overlayElement, editorElement) => {\n if (!modalElement || !overlayElement || !editorElement) {\n console.warn(\"FocusProtection: Missing required elements\");\n return;\n }\n\n // Apply the focus protection\n overrideFocusBehavior(modalElement, editorElement);\n };\n\n /**\n * Apply focus protection by overriding focus-related methods\n *\n * @param {HTMLElement} modalElement - The modal element\n * @param {HTMLElement} editorElement - The editor element to keep focused\n * @private\n */\n const overrideFocusBehavior = (modalElement, editorElement) => {\n // Store original focus methods to be able to restore them\n const originalElementFocus = HTMLElement.prototype.focus;\n const originalElementBlur = HTMLElement.prototype.blur;\n\n // Override the focus method for all elements\n HTMLElement.prototype.focus = function (...args) {\n // If the modal is open and this is not part of the modal, prevent focus\n if (modalElement.style.display !== \"none\" && !modalElement.contains(this) && this !== document.body) {\n // If some external script is trying to focus another element, prevent it\n // and restore focus to the editor\n if (editorElement) {\n // Use the original focus method to avoid infinite recursion\n originalElementFocus.apply(editorElement, args);\n }\n return;\n }\n\n // Otherwise, allow the focus to happen\n originalElementFocus.apply(this, args);\n };\n\n // Store the methods to remove them when the modal is closed\n modalElement.originalElementFocus = originalElementFocus;\n modalElement.originalElementBlur = originalElementBlur;\n };\n\n /**\n * Remove focus protection from the modal\n *\n * @param {HTMLElement} modalElement - The modal element to unprotect\n */\n const unprotect = (modalElement) => {\n if (!modalElement) {\n return;\n }\n\n // Restore original focus methods\n if (modalElement.originalElementFocus) {\n HTMLElement.prototype.focus = modalElement.originalElementFocus;\n delete modalElement.originalElementFocus;\n }\n\n if (modalElement.originalElementBlur) {\n HTMLElement.prototype.blur = modalElement.originalElementBlur;\n delete modalElement.originalElementBlur;\n }\n };\n\n return {\n protect,\n unprotect,\n };\n};\n\nexport default focusProtection;\n","// eslint-disable-next-line max-classes-per-file\nimport PopUpMessage from \"./popupmessage\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport Listeners from \"./listeners\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Telemeter from \"./telemeter\";\nimport IntegrationModel from \"./integrationmodel\";\nimport Core from \"./core.src\";\nimport focusProtection from \"./focusprotection\";\nimport closeIcon from \"../styles/icons/general/close_icon.svg\"; //eslint-disable-line\nimport closeHoverIcon from \"../styles/icons/hover/close_icon_h.svg\"; //eslint-disable-line\nimport fullsIcon from \"../styles/icons/general/fulls_icon.svg\"; //eslint-disable-line\nimport fullsHoverIcon from \"../styles/icons/hover/fulls_icon_h.svg\"; //eslint-disable-line\nimport minIcon from \"../styles/icons/general/min_icon.svg\"; //eslint-disable-line\nimport minHoverIcon from \"../styles/icons/hover/min_icon_h.svg\"; //eslint-disable-line\nimport minsIcon from \"../styles/icons/general/mins_icon.svg\"; //eslint-disable-line\nimport minsHoverIcon from \"../styles/icons/hover/mins_icon_h.svg\"; //eslint-disable-line\nimport maxIcon from \"../styles/icons/general/max_icon.svg\"; //eslint-disable-line\nimport maxHoverIcon from \"../styles/icons/hover/max_icon_h.svg\"; //eslint-disable-line\nconst { unprotect, protect } = focusProtection();\n\n/**\n * @typedef {Object} DeviceProperties\n * @property {String} DeviceProperties.orientation - Indicates of the orientation of the device.\n * @property {Boolean} DeviceProperties.isAndroid - True if the device is Android. False otherwise.\n * @property {Boolean} DeviceProperties.isIOS - True if the device is iOS. False otherwise.\n * @property {Boolean} DeviceProperties.isMobile - True if the device is a mobile one.\n * False otherwise.\n * @property {Boolean} DeviceProperties.isDesktop - True if the device is a desktop one.\n * False otherwise.\n */\n\nexport default class ModalDialog {\n /**\n * @classdesc\n * This class represents a modal dialog. The modal dialog admits\n * a {@link ContentManager} instance to manage the content of the dialog.\n * @constructs\n * @param {Object} modalDialogAttributes - An object containing all modal dialog attributes.\n */\n constructor(modalDialogAttributes) {\n this.attributes = modalDialogAttributes;\n\n // Metrics.\n const ua = navigator.userAgent.toLowerCase();\n const isAndroid = ua.indexOf(\"android\") > -1;\n const isIOS = ContentManager.isIOS();\n this.iosSoftkeyboardOpened = false;\n this.iosMeasureUnit = ua.indexOf(\"crios\") === -1 ? \"%\" : \"vh\";\n this.iosDivHeight = `auto`;\n\n const deviceWidth = window.outerWidth;\n const deviceHeight = window.outerHeight;\n\n const landscape = deviceWidth > deviceHeight;\n const portrait = deviceWidth < deviceHeight;\n\n // TODO: Detect isMobile without using editor metrics.\n const isLandscape = landscape && this.attributes.height > deviceHeight;\n const isPortrait = portrait && this.attributes.width > deviceWidth;\n const isMobile = ContentManager.isMobile();\n\n // Obtain number of current instance.\n this.instanceId = document.getElementsByClassName(\"wrs_modal_dialogContainer\").length;\n\n // Device object properties.\n\n /**\n * @type {DeviceProperties}\n */\n this.deviceProperties = {\n orientation: landscape ? \"landscape\" : \"portrait\",\n isAndroid,\n isIOS,\n isMobile,\n isDesktop: !isMobile && !isIOS && !isAndroid,\n };\n\n this.properties = {\n created: false,\n state: \"\",\n previousState: \"\",\n position: { bottom: 0, right: 10 },\n size: { height: 338, width: 580 },\n };\n\n /**\n * Object to keep website's style before change it on lock scroll for mobile devices.\n * @type {Object}\n * @property {String} bodyStylePosition - Previous body style position.\n * @property {String} bodyStyleOverflow - Previous body style overflow.\n * @property {String} htmlStyleOverflow - Previous body style overflow.\n * @property {String} windowScrollX - Previous window's scroll Y.\n * @property {String} windowScrollY - Previous window's scroll X.\n */\n this.websiteBeforeLockParameters = null;\n\n let attributes = {};\n attributes.class = \"wrs_modal_overlay\";\n attributes.id = this.getElementId(attributes.class);\n this.overlay = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title_bar\";\n attributes.id = this.getElementId(attributes.class);\n this.titleBar = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_title\";\n attributes.id = this.getElementId(attributes.class);\n this.title = Util.createElement(\"div\", attributes);\n this.title.innerHTML = \"offline\";\n\n attributes = {};\n attributes.class = \"wrs_modal_close_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"close\");\n attributes.style = {};\n this.closeDiv = Util.createElement(\"a\", attributes);\n this.closeDiv.setAttribute(\"role\", \"button\");\n this.closeDiv.setAttribute(\"tabindex\", 3);\n // Apply styles and events after the creation as createElement doesn't process them correctly\n const generalStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeIcon)})`;\n const hoverStyleClose = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(closeHoverIcon)})`;\n this.closeDiv.setAttribute(\"style\", generalStyleClose);\n this.closeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleClose));\n this.closeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleClose));\n // To identifiy the element in automated testing\n this.closeDiv.setAttribute(\"data-testid\", \"mtcteditor-close-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_stack_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"exit_fullscreen\");\n this.stackDiv = Util.createElement(\"a\", attributes);\n this.stackDiv.setAttribute(\"role\", \"button\");\n this.stackDiv.setAttribute(\"tabindex\", 2);\n const generalStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsIcon)})`;\n const hoverStyleStack = `background-size: 10px; background-image: url(data:image/svg+xml;base64,${window.btoa(minsHoverIcon)})`;\n this.stackDiv.setAttribute(\"style\", generalStyleStack);\n this.stackDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleStack));\n this.stackDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleStack));\n // To identifiy the element in automated testing\n this.stackDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-disable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_maximize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"fullscreen\");\n this.maximizeDiv = Util.createElement(\"a\", attributes);\n this.maximizeDiv.setAttribute(\"role\", \"button\");\n this.maximizeDiv.setAttribute(\"tabindex\", 2);\n const generalStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsIcon)})`;\n const hoverStyleMaximize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(fullsHoverIcon)})`;\n this.maximizeDiv.setAttribute(\"style\", generalStyleMaximize);\n this.maximizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMaximize));\n this.maximizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMaximize));\n // To identifiy the element in automated testing\n this.maximizeDiv.setAttribute(\"data-testid\", \"mtcteditor-fullscreen-enable-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_minimize_button\";\n attributes.id = this.getElementId(attributes.class);\n attributes.title = StringManager.get(\"minimize\");\n this.minimizeDiv = Util.createElement(\"a\", attributes);\n this.minimizeDiv.setAttribute(\"role\", \"button\");\n this.minimizeDiv.setAttribute(\"tabindex\", 1);\n const generalStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyleMinimize = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyleMinimize);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyleMinimize));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyleMinimize));\n // To identify the element in automated testing\n this.minimizeDiv.setAttribute(\"data-testid\", \"mtcteditor-minimize-button\");\n\n attributes = {};\n attributes.class = \"wrs_modal_dialogContainer\";\n attributes.id = this.getElementId(attributes.class);\n attributes.role = \"dialog\";\n this.container = Util.createElement(\"div\", attributes);\n this.container.setAttribute(\"aria-labeledby\", \"wrs_modal_title[0]\");\n\n attributes = {};\n attributes.class = \"wrs_modal_wrapper\";\n attributes.id = this.getElementId(attributes.class);\n this.wrapper = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_content_container\";\n attributes.id = this.getElementId(attributes.class);\n this.contentContainer = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_controls\";\n attributes.id = this.getElementId(attributes.class);\n this.controls = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_buttons_container\";\n attributes.id = this.getElementId(attributes.class);\n this.buttonContainer = Util.createElement(\"div\", attributes);\n\n // Buttons: all button must be created using createSubmitButton method.\n this.submitButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_accept\"),\n class: \"wrs_modal_button_accept\",\n innerHTML: StringManager.get(\"accept\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-insert-button\",\n },\n this.submitAction.bind(this),\n );\n\n this.cancelButton = this.createSubmitButton(\n {\n id: this.getElementId(\"wrs_modal_button_cancel\"),\n class: \"wrs_modal_button_cancel\",\n innerHTML: StringManager.get(\"cancel\"),\n // To identifiy the element in automated testing\n \"data-testid\": \"mtcteditor-cancel-button\",\n },\n this.cancelAction.bind(this),\n );\n\n this.contentManager = null;\n\n // Overlay popup.\n const popupStrings = {\n cancelString: StringManager.get(\"cancel\"),\n submitString: StringManager.get(\"close\"),\n message: StringManager.get(\"close_modal_warning\"),\n };\n\n const callbacks = {\n closeCallback: () => {\n this.close(\"mtc_close\");\n },\n cancelCallback: () => {\n this.focus();\n },\n };\n\n const popupupProperties = {\n overlayElement: this.container,\n callbacks,\n strings: popupStrings,\n };\n\n this.popup = new PopUpMessage(popupupProperties);\n\n /**\n * Indicates if directionality of the modal dialog is RTL. false by default.\n * @type {Boolean}\n */\n this.rtl = false;\n if (\"rtl\" in this.attributes) {\n this.rtl = this.attributes.rtl;\n }\n\n // Event handlers need modal instance context.\n this.handleOpenedIosSoftkeyboard = this.handleOpenedIosSoftkeyboard.bind(this);\n this.handleClosedIosSoftkeyboard = this.handleClosedIosSoftkeyboard.bind(this);\n }\n\n /**\n * This method sets an ContentManager instance to ModalDialog. ContentManager\n * manages the logic of ModalDialog content: submit, update, close and changes.\n * @param {ContentManager} contentManager - ContentManager instance.\n */\n setContentManager(contentManager) {\n this.contentManager = contentManager;\n }\n\n /**\n * Returns the modal contentElement object.\n * @returns {ContentManager} the instance of the ContentManager class.\n */\n getContentManager() {\n return this.contentManager;\n }\n\n /**\n * This method is called when the modal object has been submitted. Calls\n * contentElement submitAction method - if exists - and closes the modal\n * object. No logic about the content should be placed here,\n * contentElement.submitAction is the responsible of the content logic.\n */\n async submitAction() {\n if (typeof this.contentManager.submitAction !== \"undefined\") {\n this.contentManager.submitAction();\n }\n\n await this.close(\"mtc_insert\");\n }\n\n /**\n * Performs the cancel action.\n * If there are no changes in the content, it closes the modal.\n * Otherwise, it shows a pop-up message to confirm the cancel action.\n * @returns {Promise} - A promise that resolves when the modal is closed.\n */\n async cancelAction() {\n if (typeof this.contentManager.hasChanges === \"undefined\" || !this.contentManager.hasChanges()) {\n IntegrationModel.setActionsOnCancelButtons();\n await this.close(\"mtc_close\");\n } else {\n this.showPopUpMessage();\n }\n }\n\n /**\n * Returns a button element.\n * @param {Object} properties - Input button properties.\n * @param {String} properties.class - Input button class.\n * @param {String} properties.innerHTML - Input button innerHTML.\n * @param {Object} callback - Callback function associated to click event.\n * @returns {HTMLButtonElement} The button element.\n *\n */\n // eslint-disable-next-line class-methods-use-this\n createSubmitButton(properties, callback) {\n class SubmitButton {\n constructor() {\n this.element = document.createElement(\"button\");\n this.element.id = properties.id;\n this.element.className = properties.class;\n this.element.innerHTML = properties.innerHTML;\n this.element.dataset.testid = properties[\"data-testid\"];\n Util.addEvent(this.element, \"click\", callback);\n }\n\n getElement() {\n return this.element;\n }\n }\n return new SubmitButton(properties, callback).getElement();\n }\n\n /**\n * Creates the modal window object inserting a contentElement object.\n */\n create() {\n /* Modal Window Structure\n _____________________________________________________________________________________\n |wrs_modal_dialog_Container |\n | _________________________________________________________________________________ |\n | |title_bar minimize_button stack_button close_button | |\n | |_______________________________________________________________________________| |\n | |wrapper | |\n | | _____________________________________________________________________________ | |\n | | |content | | |\n | | | | | |\n | | | | | |\n | | |___________________________________________________________________________| | |\n | | _____________________________________________________________________________ | |\n | | |controls | | |\n | | | ___________________________________ | | |\n | | | |buttonContainer | | | |\n | | | | _______________________________ | | | |\n | | | | |button_accept | button_cancel| | | | |\n | | | |_|_____________ |______________|_| | | |\n | | |___________________________________________________________________________| | |\n | |_______________________________________________________________________________| |\n |___________________________________________________________________________________| */\n\n this.titleBar.appendChild(this.closeDiv);\n this.titleBar.appendChild(this.stackDiv);\n this.titleBar.appendChild(this.maximizeDiv);\n this.titleBar.appendChild(this.minimizeDiv);\n this.titleBar.appendChild(this.title);\n\n if (this.deviceProperties.isDesktop) {\n this.container.appendChild(this.titleBar);\n }\n\n this.wrapper.appendChild(this.contentContainer);\n this.wrapper.appendChild(this.controls);\n\n this.controls.appendChild(this.buttonContainer);\n\n this.buttonContainer.appendChild(this.submitButton);\n this.buttonContainer.appendChild(this.cancelButton);\n\n this.container.appendChild(this.wrapper);\n\n // Check if browser has scrollBar before modal has modified.\n this.recalculateScrollBar();\n\n document.body.appendChild(this.container);\n document.body.appendChild(this.overlay);\n\n if (this.deviceProperties.isDesktop) {\n // Desktop.\n this.createModalWindowDesktop();\n this.createResizeButtons();\n\n this.addListeners();\n // Maximize window only when the configuration is set and the device is not iOS or Android.\n if (Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n } else if (this.deviceProperties.isAndroid) {\n this.createModalWindowAndroid();\n } else if (this.deviceProperties.isIOS) {\n this.createModalWindowIos();\n }\n\n if (this.contentManager != null) {\n this.contentManager.insert(this);\n }\n\n this.properties.open = true;\n this.properties.created = true;\n\n // Checks language directionality.\n if (this.isRTL()) {\n this.container.style.right = `${window.innerWidth - this.scrollbarWidth - this.container.offsetWidth}px`;\n this.container.className += \" wrs_modal_rtl\";\n }\n }\n\n /**\n * Creates a button in the modal object to resize it.\n */\n createResizeButtons() {\n // This is a definition of Resize Button Bottom-Right.\n this.resizerBR = document.createElement(\"div\");\n this.resizerBR.className = \"wrs_bottom_right_resizer\";\n this.resizerBR.innerHTML = \"โ—ข\";\n // To identifiy the element in automated testing\n this.resizerBR.dataset.testid = \"mtcteditor-resize-button-right\";\n // This is a definition of Resize Button Top-Left.\n this.resizerTL = document.createElement(\"div\");\n this.resizerTL.className = \"wrs_bottom_left_resizer\";\n // To identifiy the element in automated testing\n this.resizerTL.dataset.testid = \"mtcteditor-resize-button-left\";\n // Append resize buttons to modal.\n this.container.appendChild(this.resizerBR);\n this.titleBar.appendChild(this.resizerTL);\n // Add events to resize on click and drag.\n Util.addEvent(this.resizerBR, \"mousedown\", this.activateResizeStateBR.bind(this));\n Util.addEvent(this.resizerTL, \"mousedown\", this.activateResizeStateTL.bind(this));\n }\n\n /**\n * Initialize variables for Bottom-Right resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateBR(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, false);\n }\n\n /**\n * Initialize variables for Top-Left resize button\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n activateResizeStateTL(mouseEvent) {\n this.initializeResizeProperties(mouseEvent, true);\n }\n\n /**\n * Common method to initialize variables at resize.\n * @param {MouseEvent} mouseEvent - Mouse event.\n */\n initializeResizeProperties(mouseEvent, leftOption) {\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n this.resizeDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Save Initial state of modal to compare on drag and obtain the difference.\n this.initialWidth = parseInt(this.container.style.width, 10);\n this.initialHeight = parseInt(this.container.style.height, 10);\n if (!leftOption) {\n this.initialRight = parseInt(this.container.style.right, 10);\n this.initialBottom = parseInt(this.container.style.bottom, 10);\n } else {\n this.leftScale = true;\n }\n if (!this.initialRight) {\n this.initialRight = 0;\n }\n if (!this.initialBottom) {\n this.initialBottom = 0;\n }\n // Disable mouse events on editor when we start to drag modal.\n document.body.style[\"user-select\"] = \"none\";\n }\n\n /**\n * This method opens the modal window, restoring the previous state, position and metrics,\n * if exists. By default the modal object opens in stack mode.\n */\n open() {\n // Removing close class.\n this.removeClass(\"wrs_closed\");\n // Hiding keyboard for mobile devices.\n const { isIOS } = this.deviceProperties;\n const { isAndroid } = this.deviceProperties;\n const { isMobile } = this.deviceProperties;\n if (isIOS || isAndroid || isMobile) {\n // Restore scale to 1.\n this.restoreWebsiteScale();\n this.lockWebsiteScroll();\n // Due to editor wait we need to wait until editor focus.\n setTimeout(() => {\n this.hideKeyboard();\n }, 400);\n }\n\n // New modal window. He need to create the whole object.\n if (!this.properties.created) {\n this.create();\n } else {\n // Previous state closed. Open method can be called even the previous state is open,\n // for example updating the content of the modal object.\n if (!this.properties.open) {\n this.properties.open = true;\n\n // Restoring the previous open state: if the modal object has been closed\n // re-open it should preserve the state and the metrics.\n if (!this.deviceProperties.isAndroid && !this.deviceProperties.isIOS) {\n this.restoreState();\n }\n }\n\n // Maximize window only when the configuration is set and the device is not iOs or Android.\n if (this.deviceProperties.isDesktop && Configuration.get(\"modalWindowFullScreen\")) {\n this.maximize();\n }\n\n // In iOS we need to recalculate the size of the modal object because\n // iOS keyboard is a float div which can overlay the modal object.\n if (this.deviceProperties.isIOS) {\n this.iosSoftkeyboardOpened = false;\n }\n }\n\n if (!ContentManager.isEditorLoaded()) {\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.displayEditor();\n });\n this.contentManager.addListener(listener);\n } else {\n this.displayEditor();\n }\n }\n\n /**\n * Prepares and displays the editor in the modal.\n *\n * This method is responsible for displaying the MathType editor inside the modal container.\n *\n * For Moodle environments, it applies focus protection to prevent external scripts\n * from hijacking focus away from the editor while it's open. This is particularly\n * important in Moodle which may have its own focus management scripts.\n * @returns {void}\n */\n displayEditor() {\n if (this.contentManager.integrationModel.isMoodle) {\n protect(this.container, this.overlay, this.contentContainer);\n }\n\n // Initialize and open the editor using the contentManager.\n this.contentManager.onOpen(this);\n }\n\n /**\n * Closes the modal.\n * Removes specific CSS classes, saves modal properties, unlocks website scroll,\n * sets the 'open' property to false, and triggers the 'onModalClose' event.\n * If a close trigger is defined, it tracks the telemetry event 'CLOSED_MTCT_EDITOR' with the trigger.\n * @returns {Promise} A promise that resolves when the modal is closed.\n */\n async close(trigger) {\n // Remove focus protection before closing\n unprotect(this.container);\n\n this.removeClass(\"wrs_maximized\");\n this.removeClass(\"wrs_minimized\");\n this.removeClass(\"wrs_stack\");\n this.addClass(\"wrs_closed\");\n this.saveModalProperties();\n this.unlockWebsiteScroll();\n this.properties.open = false;\n\n if (trigger) {\n try {\n await Telemeter.telemeter.track(\"CLOSED_MTCT_EDITOR\", {\n toolbar: this.contentManager.toolbar,\n trigger,\n });\n } catch (error) {\n console.error(\"Error tracking CLOSED_MTCT_EDITOR\", error);\n }\n }\n\n Core.globalListeners.fire(\"onModalClose\", {});\n }\n\n /**\n * Closes modal window and destroys the object.\n */\n destroy() {\n // Remove focus protection before destroying\n unprotect(this.container);\n\n // Close modal window.\n this.close();\n // Remove listeners and destroy the object.\n this.removeListeners();\n this.overlay.remove();\n this.container.remove();\n // Reset properties to allow open again.\n this.properties.created = false;\n }\n\n /**\n * Sets the website scale to one.\n */\n // eslint-disable-next-line class-methods-use-this\n restoreWebsiteScale() {\n let viewportmeta = document.querySelector(\"meta[name=viewport]\");\n // Let the equal symbols in order to search and make meta's final content.\n const contentAttrsToUpdate = [\"initial-scale=\", \"minimum-scale=\", \"maximum-scale=\"];\n const contentAttrsValuesToUpdate = [\"1.0\", \"1.0\", \"1.0\"];\n const setMetaAttrFunc = (viewportelement, contentAttrs) => {\n const contentAttr = viewportelement.getAttribute(\"content\");\n // If it exists, we need to maintain old values and put our values.\n if (contentAttr) {\n const attrArray = contentAttr.split(\",\");\n let finalContentMeta = \"\";\n const oldAttrs = [];\n for (let i = 0; i < attrArray.length; i += 1) {\n let isAttrToUpdate = false;\n let j = 0;\n while (!isAttrToUpdate && j < contentAttrs.length) {\n if (attrArray[i].indexOf(contentAttrs[j])) {\n isAttrToUpdate = true;\n }\n j += 1;\n }\n\n if (!isAttrToUpdate) {\n oldAttrs.push(attrArray[i]);\n }\n }\n\n for (let i = 0; i < contentAttrs.length; i += 1) {\n const attr = contentAttrs[i] + contentAttrsValuesToUpdate[i];\n finalContentMeta += i === 0 ? attr : `,${attr}`;\n }\n\n for (let i = 0; i < oldAttrs.length; i += 1) {\n finalContentMeta += `,${oldAttrs[i]}`;\n }\n viewportelement.setAttribute(\"content\", finalContentMeta);\n // It needs to set to empty because setAttribute refresh only when attribute is different.\n viewportelement.setAttribute(\"content\", \"\");\n viewportelement.setAttribute(\"content\", contentAttr);\n } else {\n viewportelement.setAttribute(\"content\", \"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0\");\n viewportelement.removeAttribute(\"content\");\n }\n };\n\n if (!viewportmeta) {\n viewportmeta = document.createElement(\"meta\");\n document.getElementsByTagName(\"head\")[0].appendChild(viewportmeta);\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n viewportmeta.remove();\n } else {\n setMetaAttrFunc(viewportmeta, contentAttrsToUpdate, contentAttrsValuesToUpdate);\n }\n }\n\n /**\n * Locks website scroll for mobile devices.\n */\n lockWebsiteScroll() {\n this.websiteBeforeLockParameters = {\n bodyStylePosition: document.body.style.position ? document.body.style.position : \"\",\n bodyStyleOverflow: document.body.style.overflow ? document.body.style.overflow : \"\",\n htmlStyleOverflow: document.documentElement.style.overflow ? document.documentElement.style.overflow : \"\",\n windowScrollX: window.scrollX,\n windowScrollY: window.scrollY,\n };\n }\n\n /**\n * Unlocks website scroll for mobile devices.\n */\n unlockWebsiteScroll() {\n if (this.websiteBeforeLockParameters) {\n document.body.style.position = this.websiteBeforeLockParameters.bodyStylePosition;\n document.body.style.overflow = this.websiteBeforeLockParameters.bodyStyleOverflow;\n document.documentElement.style.overflow = this.websiteBeforeLockParameters.htmlStyleOverflow;\n const { windowScrollX } = this.websiteBeforeLockParameters;\n const { windowScrollY } = this.websiteBeforeLockParameters;\n window.scrollTo(windowScrollX, windowScrollY);\n this.websiteBeforeLockParameters = null;\n }\n }\n\n /**\n * Util function to known if browser is IE11.\n * @returns {Boolean} true if the browser is IE11. false otherwise.\n */\n // eslint-disable-next-line class-methods-use-this\n isIE11() {\n if (\n navigator.userAgent.search(\"Msie/\") >= 0 ||\n navigator.userAgent.search(\"Trident/\") >= 0 ||\n navigator.userAgent.search(\"Edge/\") >= 0\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Returns if the current language type is RTL.\n * @return {Boolean} true if current language is RTL. false otherwise.\n */\n isRTL() {\n if (this.attributes.language === \"ar\" || this.attributes.language === \"he\") {\n return true;\n }\n return this.rtl;\n }\n\n /**\n * Adds a class to all modal ModalDialog DOM elements.\n * @param {String} className - Class name.\n */\n addClass(className) {\n Util.addClass(this.overlay, className);\n Util.addClass(this.titleBar, className);\n Util.addClass(this.overlay, className);\n Util.addClass(this.container, className);\n Util.addClass(this.contentContainer, className);\n Util.addClass(this.stackDiv, className);\n Util.addClass(this.minimizeDiv, className);\n Util.addClass(this.maximizeDiv, className);\n Util.addClass(this.wrapper, className);\n }\n\n /**\n * Remove a class from all modal DOM elements.\n * @param {String} className - Class name.\n */\n removeClass(className) {\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.titleBar, className);\n Util.removeClass(this.overlay, className);\n Util.removeClass(this.container, className);\n Util.removeClass(this.contentContainer, className);\n Util.removeClass(this.stackDiv, className);\n Util.removeClass(this.minimizeDiv, className);\n Util.removeClass(this.maximizeDiv, className);\n Util.removeClass(this.wrapper, className);\n }\n\n /**\n * Create modal dialog for desktop.\n */\n createModalWindowDesktop() {\n this.addClass(\"wrs_modal_desktop\");\n this.stack();\n }\n\n /**\n * Create modal dialog for non android devices.\n */\n createModalWindowAndroid() {\n this.addClass(\"wrs_modal_android\");\n window.addEventListener(\"resize\", this.orientationChangeAndroidSoftkeyboard.bind(this));\n }\n\n /**\n * Create modal dialog for iOS devices.\n */\n createModalWindowIos() {\n this.addClass(\"wrs_modal_ios\");\n // Refresh the size when the orientation is changed.\n window.addEventListener(\"resize\", this.orientationChangeIosSoftkeyboard.bind(this));\n }\n\n /**\n * Restore previous state, position and size of previous stacked modal dialog.\n */\n restoreState() {\n if (this.properties.state === \"maximized\") {\n // Reset states for prevent return to stack state.\n this.maximize();\n } else if (this.properties.state === \"minimized\") {\n // Reset states for prevent return to stack state.\n this.properties.state = this.properties.previousState;\n this.properties.previousState = \"\";\n this.minimize();\n } else {\n this.stack();\n }\n }\n\n /**\n * Stacks the modal object.\n */\n stack() {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"stack\";\n this.removeClass(\"wrs_maximized\");\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n this.addClass(\"wrs_stack\");\n\n // Change maximize/minimize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n this.restoreModalProperties();\n\n if (typeof this.resizerBR !== \"undefined\" && typeof this.resizerTL !== \"undefined\") {\n this.setResizeButtonsVisibility();\n }\n\n // Need recalculate position of actual modal because window can was changed in fullscreenmode.\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n this.focus();\n }\n\n /**\n * Minimizes the modal object.\n */\n minimize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n this.title.style.cursor = \"pointer\";\n if (this.properties.state === \"minimized\" && this.properties.previousState === \"stack\") {\n this.stack();\n } else if (this.properties.state === \"minimized\" && this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n // Setting css to prevent important tag into css style.\n this.container.style.height = \"30px\";\n this.container.style.width = \"250px\";\n this.container.style.bottom = \"0px\";\n this.container.style.right = \"10px\";\n\n this.removeListeners();\n this.properties.previousState = this.properties.state;\n this.properties.state = \"minimized\";\n this.setResizeButtonsVisibility();\n this.minimizeDiv.title = StringManager.get(\"maximize\");\n\n if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.removeClass(\"wrs_stack\");\n } else {\n this.removeClass(\"wrs_maximized\");\n }\n this.addClass(\"wrs_minimized\");\n\n // Change minimize icon to maximize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(maxHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n }\n }\n\n /**\n * Maximizes the modal object.\n */\n maximize() {\n // Saving width, height, top and bottom parameters to restore when opening.\n this.saveModalProperties();\n if (this.properties.state !== \"maximized\") {\n this.properties.previousState = this.properties.state;\n this.properties.state = \"maximized\";\n }\n // Don't permit resize on maximize mode.\n this.setResizeButtonsVisibility();\n\n if (Util.containsClass(this.overlay, \"wrs_minimized\")) {\n this.minimizeDiv.title = StringManager.get(\"minimize\");\n this.removeClass(\"wrs_minimized\");\n } else if (Util.containsClass(this.overlay, \"wrs_stack\")) {\n this.container.style.left = null;\n this.container.style.top = null;\n this.removeClass(\"wrs_stack\");\n }\n\n this.addClass(\"wrs_maximized\");\n\n // Change maximize icon to minimize icon\n const generalStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minIcon)})`;\n const hoverStyle = `background-size: 10px; background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,${window.btoa(minHoverIcon)})`;\n this.minimizeDiv.setAttribute(\"style\", generalStyle);\n this.minimizeDiv.addEventListener(\"mouseover\", (e) => (e.target.style = hoverStyle));\n this.minimizeDiv.addEventListener(\"mouseout\", (e) => (e.target.style = generalStyle));\n\n // Set size to 80% screen with a max size.\n this.setSize(parseInt(window.innerHeight * 0.8, 10), parseInt(window.innerWidth * 0.8, 10));\n if (this.container.clientHeight > 700) {\n this.container.style.height = \"700px\";\n }\n if (this.container.clientWidth > 1200) {\n this.container.style.width = \"1200px\";\n }\n\n // Setting modal position in center on screen.\n const { innerHeight } = window;\n const { innerWidth } = window;\n const { offsetHeight } = this.container;\n const { offsetWidth } = this.container;\n const bottom = innerHeight / 2 - offsetHeight / 2;\n const right = innerWidth / 2 - offsetWidth / 2;\n\n this.setPosition(bottom, right);\n this.recalculateScale();\n this.recalculatePosition();\n this.recalculateSize();\n this.focus();\n }\n\n /**\n * Expand again the modal object from a minimized state.\n */\n reExpand() {\n if (this.properties.state === \"minimized\") {\n if (this.properties.previousState === \"maximized\") {\n this.maximize();\n } else {\n this.stack();\n }\n this.title.style.cursor = \"\";\n }\n }\n\n /**\n * Sets modal size.\n * @param {Number} height - Height of the ModalDialog\n * @param {Number} width - Width of the ModalDialog.\n */\n setSize(height, width) {\n this.container.style.height = `${height}px`;\n this.container.style.width = `${width}px`;\n this.recalculateSize();\n }\n\n /**\n * Sets modal position using bottom and right style attributes.\n * @param {number} bottom - bottom attribute.\n * @param {number} right - right attribute.\n */\n setPosition(bottom, right) {\n this.container.style.bottom = `${bottom}px`;\n this.container.style.right = `${right}px`;\n }\n\n /**\n * Saves position and size parameters of and open ModalDialog. This attributes\n * are needed to restore it on re-open.\n */\n saveModalProperties() {\n // Saving values of modal only when modal is in stack state.\n if (this.properties.state === \"stack\") {\n this.properties.position.bottom = parseInt(this.container.style.bottom, 10);\n this.properties.position.right = parseInt(this.container.style.right, 10);\n this.properties.size.width = parseInt(this.container.style.width, 10);\n this.properties.size.height = parseInt(this.container.style.height, 10);\n }\n }\n\n /**\n * Restore ModalDialog position and size parameters.\n */\n restoreModalProperties() {\n if (this.properties.state === \"stack\") {\n // Restoring Bottom and Right values from last modal.\n this.setPosition(this.properties.position.bottom, this.properties.position.right);\n // Restoring Height and Left values from last modal.\n this.setSize(this.properties.size.height, this.properties.size.width);\n }\n }\n\n /**\n * Sets the modal dialog initial size.\n */\n recalculateSize() {\n this.contentContainer.style.height = `${parseInt(this.wrapper.offsetHeight - 50, 10)}px`;\n }\n\n /**\n * Enable or disable visibility of resize buttons in modal window depend on state.\n */\n setResizeButtonsVisibility() {\n if (this.properties.state === \"stack\") {\n this.resizerTL.style.visibility = \"visible\";\n this.resizerBR.style.visibility = \"visible\";\n } else {\n this.resizerTL.style.visibility = \"hidden\";\n this.resizerBR.style.visibility = \"hidden\";\n }\n }\n\n /**\n * Makes an object draggable adding mouse and touch events.\n */\n addListeners() {\n // Button events (maximize, minimize, stack and close).\n this.maximizeDiv.addEventListener(\"click\", this.maximize.bind(this), true);\n this.stackDiv.addEventListener(\"click\", this.stack.bind(this), true);\n this.minimizeDiv.addEventListener(\"click\", this.minimize.bind(this), true);\n this.closeDiv.addEventListener(\"click\", this.cancelAction.bind(this));\n this.maximizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n }\n },\n true,\n );\n this.stackDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.minimizeDiv.addEventListener(\n \"keypress\",\n (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n },\n true,\n );\n this.closeDiv.addEventListener(\"keypress\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \" || e.keyCode === 13 || e.keyCode === 32) {\n // Handle enter and space.\n e.target.click();\n e.preventDefault();\n }\n });\n this.title.addEventListener(\"click\", this.reExpand.bind(this));\n\n // Overlay events (close).\n this.overlay.addEventListener(\"click\", this.cancelAction.bind(this));\n\n // Mouse events.\n Util.addEvent(window, \"mousedown\", this.startDrag.bind(this));\n Util.addEvent(window, \"mouseup\", this.stopDrag.bind(this));\n Util.addEvent(window, \"mousemove\", this.drag.bind(this));\n Util.addEvent(window, \"resize\", this.onWindowResize.bind(this));\n // Key events.\n Util.addEvent(window, \"keydown\", this.onKeyDown.bind(this));\n }\n\n /**\n * Removes draggable events from an object.\n */\n removeListeners() {\n // Mouse events.\n Util.removeEvent(window, \"mousedown\", this.startDrag);\n Util.removeEvent(window, \"mouseup\", this.stopDrag);\n Util.removeEvent(window, \"mousemove\", this.drag);\n Util.removeEvent(window, \"resize\", this.onWindowResize);\n // Key events.\n Util.removeEvent(window, \"keydown\", this.onKeyDown);\n }\n\n /**\n * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)\n * @param {MouseEvent} mouseEvent - Mouse event.\n * @return {Object} With the X and Y coordinates.\n */\n // eslint-disable-next-line class-methods-use-this\n eventClient(mouseEvent) {\n if (typeof mouseEvent.clientX === \"undefined\" && mouseEvent.changedTouches) {\n const client = {\n X: mouseEvent.changedTouches[0].clientX,\n Y: mouseEvent.changedTouches[0].clientY,\n };\n return client;\n }\n const client = {\n X: mouseEvent.clientX,\n Y: mouseEvent.clientY,\n };\n return client;\n }\n\n /**\n * Start drag function: set the object dragDataObject with the draggable\n * object offsets coordinates.\n * when drag starts (on touchstart or mousedown events).\n * @param {MouseEvent} mouseEvent - Touchstart or mousedown event.\n */\n startDrag(mouseEvent) {\n if (this.properties.state === \"minimized\") {\n return;\n }\n if (mouseEvent.target === this.title) {\n if (typeof this.dragDataObject === \"undefined\" || this.dragDataObject === null) {\n // Save first click mouse point on screen.\n this.dragDataObject = {\n x: this.eventClient(mouseEvent).X,\n y: this.eventClient(mouseEvent).Y,\n };\n // Reset last drag position when start drag.\n this.lastDrag = {\n x: \"0px\",\n y: \"0px\",\n };\n // Init right and bottom values for window modal if it isn't exist.\n if (this.container.style.right === \"\") {\n this.container.style.right = \"0px\";\n }\n if (this.container.style.bottom === \"\") {\n this.container.style.bottom = \"0px\";\n }\n\n // Needed for IE11 for apply disabled mouse events on editor because\n // internet explorer needs a dynamic object to apply this property.\n if (this.isIE11()) {\n // this.iframe.style['position'] = 'relative';\n }\n // Apply class for disable involuntary select text when drag.\n Util.addClass(document.body, \"wrs_noselect\");\n Util.addClass(this.overlay, \"wrs_overlay_active\");\n // Obtain screen limits for prevent overflow.\n this.limitWindow = this.getLimitWindow();\n }\n }\n }\n\n /**\n * Updates dragDataObject with the draggable object coordinates when\n * the draggable object is being moved.\n * @param {MouseEvent} mouseEvent - The mouse event.\n */\n drag(mouseEvent) {\n if (this.dragDataObject) {\n mouseEvent.preventDefault();\n // Calculate max and min between actual mouse position and limit of screeen.\n // It restric the movement of modal into window.\n let limitY = Math.min(this.eventClient(mouseEvent).Y, this.limitWindow.minPointer.y);\n limitY = Math.max(this.limitWindow.maxPointer.y, limitY);\n let limitX = Math.min(this.eventClient(mouseEvent).X, this.limitWindow.minPointer.x);\n limitX = Math.max(this.limitWindow.maxPointer.x, limitX);\n // Subtract limit with first position to obtain relative pixels increment\n // to the anchor point.\n const dragX = `${limitX - this.dragDataObject.x}px`;\n const dragY = `${limitY - this.dragDataObject.y}px`;\n // Save last valid position of modal before window overflow.\n this.lastDrag = {\n x: dragX,\n y: dragY,\n };\n // This move modal with hardware acceleration.\n this.container.style.transform = `translate3d(${dragX},${dragY},0)`;\n }\n if (this.resizeDataObject) {\n const { innerWidth } = window;\n const { innerHeight } = window;\n let limitX = Math.min(this.eventClient(mouseEvent).X, innerWidth - this.scrollbarWidth - 7);\n let limitY = Math.min(this.eventClient(mouseEvent).Y, innerHeight - 7);\n if (limitX < 0) {\n limitX = 0;\n }\n\n if (limitY < 0) {\n limitY = 0;\n }\n\n let scaleMultiplier;\n if (this.leftScale) {\n scaleMultiplier = -1;\n } else {\n scaleMultiplier = 1;\n }\n\n this.container.style.width = `${this.initialWidth + scaleMultiplier * (limitX - this.resizeDataObject.x)}px`;\n this.container.style.height = `${this.initialHeight + scaleMultiplier * (limitY - this.resizeDataObject.y)}px`;\n if (!this.leftScale) {\n if (this.resizeDataObject.x - limitX - this.initialWidth < -580) {\n this.container.style.right = `${this.initialRight - (limitX - this.resizeDataObject.x)}px`;\n } else {\n this.container.style.right = `${this.initialRight + this.initialWidth - 580}px`;\n this.container.style.width = \"580px\";\n }\n if (this.resizeDataObject.y - limitY < this.initialHeight - 338) {\n this.container.style.bottom = `${this.initialBottom - (limitY - this.resizeDataObject.y)}px`;\n } else {\n this.container.style.bottom = `${this.initialBottom + this.initialHeight - 338}px`;\n this.container.style.height = \"338px\";\n }\n }\n this.recalculateScale();\n this.recalculatePosition();\n }\n }\n\n /**\n * Returns the boundaries of actual window to limit modal movement.\n * @return {Object} Object containing mouseX and mouseY coordinates of actual mouse on screen.\n */\n getLimitWindow() {\n // Obtain dimensions of window page.\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight;\n\n // Calculate relative position of mouse point into window.\n const { offsetHeight } = this.container;\n const contStyleBottom = parseInt(this.container.style.bottom, 10);\n const contStyleRight = parseInt(this.container.style.right, 10);\n\n const { pageXOffset } = window;\n const dragY = this.dragDataObject.y;\n const dragX = this.dragDataObject.x;\n\n const offSetToolbarY = offsetHeight + contStyleBottom - (maxHeight - (dragY - pageXOffset));\n const offSetToolbarX = maxWidth - this.scrollbarWidth - (dragX - pageXOffset) - contStyleRight;\n\n // Calculate limits with sizes of window, modal and mouse position.\n const minPointerY = maxHeight - this.container.offsetHeight + offSetToolbarY;\n const maxPointerY = this.title.offsetHeight - (this.title.offsetHeight - offSetToolbarY);\n const minPointerX = maxWidth - offSetToolbarX - this.scrollbarWidth;\n const maxPointerX = this.container.offsetWidth - offSetToolbarX;\n const minPointer = { x: minPointerX, y: minPointerY };\n const maxPointer = { x: maxPointerX, y: maxPointerY };\n return { minPointer, maxPointer };\n }\n\n /**\n * Returns the scrollbar width size of browser\n * @returns {Number} The scrollbar width.\n */\n // eslint-disable-next-line class-methods-use-this\n getScrollBarWidth() {\n // Create a paragraph with full width of page.\n const inner = document.createElement(\"p\");\n inner.style.width = \"100%\";\n inner.style.height = \"200px\";\n\n // Create a hidden div to compare sizes.\n const outer = document.createElement(\"div\");\n outer.style.position = \"absolute\";\n outer.style.top = \"0px\";\n outer.style.left = \"0px\";\n outer.style.visibility = \"hidden\";\n outer.style.width = \"200px\";\n outer.style.height = \"150px\";\n outer.style.overflow = \"hidden\";\n outer.appendChild(inner);\n\n document.body.appendChild(outer);\n const widthOuter = inner.offsetWidth;\n\n // Change type overflow of paragraph for measure scrollbar.\n outer.style.overflow = \"scroll\";\n let widthInner = inner.offsetWidth;\n\n // If measure is the same, we compare with internal div.\n if (widthOuter === widthInner) {\n widthInner = outer.clientWidth;\n }\n document.body.removeChild(outer);\n\n return widthOuter - widthInner;\n }\n\n /**\n * Set the dragDataObject to null.\n */\n stopDrag() {\n // Due to we have multiple events that call this function, we need only to execute\n // the next modifiers one time,\n // when the user stops to drag and dragDataObject is not null (the object to drag is attached).\n if (this.dragDataObject || this.resizeDataObject) {\n // If modal doesn't change, it's not necessary to set position with interpolation.\n this.container.style.transform = \"\";\n if (this.dragDataObject) {\n this.container.style.right = `${parseInt(this.container.style.right, 10) - parseInt(this.lastDrag.x, 10)}px`;\n this.container.style.bottom = `${parseInt(this.container.style.bottom, 10) - parseInt(this.lastDrag.y, 10)}px`;\n }\n // We make focus on editor after drag modal windows to prevent lose focus.\n this.focus();\n // Restore mouse events on iframe.\n // this.iframe.style['pointer-events'] = 'auto';\n document.body.style[\"user-select\"] = \"\";\n // Restore static state of iframe if we use Internet Explorer.\n if (this.isIE11()) {\n // this.iframe.style['position'] = null;\n }\n // Active text select event.\n Util.removeClass(document.body, \"wrs_noselect\");\n Util.removeClass(this.overlay, \"wrs_overlay_active\");\n }\n this.dragDataObject = null;\n this.resizeDataObject = null;\n this.initialWidth = null;\n this.leftScale = null;\n }\n\n /**\n * Recalculates scale for modal when resize browser window.\n */\n onWindowResize() {\n this.recalculateScrollBar();\n this.recalculatePosition();\n this.recalculateScale();\n }\n\n /**\n * Triggers keyboard events:\n * - Tab key tab to go to submit button.\n * - Esc key to close the modal dialog.\n * @param {KeyboardEvent} keyboardEvent - The keyboard event.\n */\n onKeyDown(keyboardEvent) {\n if (keyboardEvent.key !== undefined) {\n // Popupmessage is not oppened.\n if (this.popup.overlayWrapper.style.display !== \"block\") {\n // Code to detect Esc event\n if (keyboardEvent.key === \"Escape\" || keyboardEvent.key === \"Esc\") {\n if (this.properties.open) {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.shiftKey && keyboardEvent.key === \"Tab\") {\n // Code to detect shift Tab event.\n if (document.activeElement === this.cancelButton) {\n this.submitButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n } else if (keyboardEvent.key === \"Tab\") {\n // Code to detect Tab event.\n if (document.activeElement === this.submitButton) {\n this.cancelButton.focus();\n keyboardEvent.stopPropagation();\n keyboardEvent.preventDefault();\n } else {\n this.contentManager.onKeyDown(keyboardEvent);\n }\n }\n } else {\n // Popupmessage oppened.\n this.popup.onKeyDown(keyboardEvent);\n }\n }\n }\n\n /**\n * Recalculating position for modal dialog when the browser is resized.\n */\n recalculatePosition() {\n this.container.style.right = `${Math.min(parseInt(this.container.style.right, 10), window.innerWidth - this.scrollbarWidth - this.container.offsetWidth)}px`;\n if (parseInt(this.container.style.right, 10) < 0) {\n this.container.style.right = \"0px\";\n }\n this.container.style.bottom = `${Math.min(parseInt(this.container.style.bottom, 10), window.innerHeight - this.container.offsetHeight)}px`;\n if (parseInt(this.container.style.bottom, 10) < 0) {\n this.container.style.bottom = \"0px\";\n }\n }\n\n /**\n * Recalculating scale for modal when the browser is resized.\n */\n recalculateScale() {\n let sizeModified = false;\n\n if (parseInt(this.container.style.width, 10) > 580) {\n this.container.style.width = `${Math.min(parseInt(this.container.style.width, 10), window.innerWidth - this.scrollbarWidth)}px`;\n sizeModified = true;\n } else {\n this.container.style.width = \"580px\";\n sizeModified = true;\n }\n\n if (parseInt(this.container.style.height, 10) > 338) {\n this.container.style.height = `${Math.min(parseInt(this.container.style.height, 10), window.innerHeight)}px`;\n sizeModified = true;\n } else {\n this.container.style.height = \"338px\";\n sizeModified = true;\n }\n\n if (sizeModified) {\n this.recalculateSize();\n }\n }\n\n /**\n * Recalculating width of browser scroll bar.\n */\n recalculateScrollBar() {\n this.hasScrollBar = window.innerWidth > document.documentElement.clientWidth;\n if (this.hasScrollBar) {\n this.scrollbarWidth = this.getScrollBarWidth();\n } else {\n this.scrollbarWidth = 0;\n }\n }\n\n /**\n * Hide soft keyboards on iOS devices.\n */\n // eslint-disable-next-line class-methods-use-this\n hideKeyboard() {\n // iOS keyboard can't be detected or hide directly from JavaScript.\n // So, this method simulates that user focus a text input and blur\n // the selection.\n const inputField = document.createElement(\"input\");\n this.container.appendChild(inputField);\n inputField.focus();\n inputField.blur();\n // Is removed to not see it.\n inputField.remove();\n }\n\n /**\n * Focus to contentManager object.\n */\n focus() {\n if (this.contentManager != null && typeof this.contentManager.onFocus !== \"undefined\") {\n this.contentManager.onFocus();\n }\n }\n\n /**\n * Returns true when the device is on portrait mode.\n */\n // eslint-disable-next-line class-methods-use-this\n portraitMode() {\n return window.innerHeight > window.innerWidth;\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is opened.\n */\n handleOpenedIosSoftkeyboard() {\n if (!this.iosSoftkeyboardOpened && this.iosDivHeight != null && this.iosDivHeight === `auto`) {\n if (this.portraitMode()) {\n this.setContainerHeight(`60${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`35${this.iosMeasureUnit}`);\n }\n }\n this.iosSoftkeyboardOpened = true;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Event handler that change container size when IOS soft keyboard is closed.\n */\n handleClosedIosSoftkeyboard() {\n this.iosSoftkeyboardOpened = false;\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Change container sizes when orientation is changed on iOS.\n */\n orientationChangeIosSoftkeyboard() {\n if (this.iosSoftkeyboardOpened) {\n if (this.portraitMode()) {\n this.setContainerHeight(`65${this.iosMeasureUnit}`);\n } else {\n this.setContainerHeight(`45${this.iosMeasureUnit}`);\n }\n } else {\n this.wrapper.style.flexGrow = \"1\";\n }\n }\n\n /**\n * Change container sizes when orientation is changed on Android.\n */\n orientationChangeAndroidSoftkeyboard() {\n this.wrapper.style.flexGrow = \"1\";\n }\n\n /**\n * Set iframe container height.\n * @param {Number} height - New height.\n */\n setContainerHeight(height) {\n this.iosDivHeight = height;\n this.wrapper.style.height = height;\n }\n\n /**\n * Check content of editor before close action.\n */\n showPopUpMessage() {\n if (this.properties.state === \"minimized\") {\n this.stack();\n }\n this.popup.show();\n }\n\n /**\n * Sets the title of the modal dialog.\n * @param {String} title - Modal dialog title.\n */\n setTitle(title) {\n this.title.innerHTML = title;\n }\n\n /**\n * Returns the id of an element, adding the instance number to\n * the element class name:\n * className --> className[idNumber]\n * @param {String} className - The element class name.\n * @returns {String} A string appending the instance id to the className.\n */\n getElementId(className) {\n return `${className}[${this.instanceId}]`;\n }\n}\n","/* eslint-disable */\nvar polyfills;\nexport default polyfills;\n\n// Polyfills.\n/*! http://mths.be/codepointat v0.1.0 by @mathias */\nif (!String.prototype.codePointAt) {\n (function () {\n \"use strict\"; // needed to support `apply`/`call` with `undefined`/`null`\n var codePointAt = function (position) {\n if (this == null) {\n throw TypeError();\n }\n var string = String(this);\n var size = string.length;\n // `ToInteger`\n var index = position ? Number(position) : 0;\n if (index != index) {\n // better `isNaN`\n index = 0;\n }\n // Account for out-of-bounds indices:\n if (index < 0 || index >= size) {\n return undefined;\n }\n // Get the first code unit\n var first = string.charCodeAt(index);\n var second;\n if (\n // check if itโ€™s the start of a surrogate pair\n first >= 0xd800 &&\n first <= 0xdbff && // high surrogate\n size > index + 1 // there is a next code unit\n ) {\n second = string.charCodeAt(index + 1);\n if (second >= 0xdc00 && second <= 0xdfff) {\n // low surrogate\n // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae\n return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;\n }\n }\n return first;\n };\n if (Object.defineProperty) {\n Object.defineProperty(String.prototype, \"codePointAt\", {\n value: codePointAt,\n configurable: true,\n writable: true,\n });\n } else {\n String.prototype.codePointAt = codePointAt;\n }\n })();\n}\n\n// Object.assign polyfill.\nif (typeof Object.assign != \"function\") {\n // Must be writable: true, enumerable: false, configurable: true\n Object.defineProperty(Object, \"assign\", {\n value: function assign(target, varArgs) {\n // .length of function is 2\n \"use strict\";\n if (target == null) {\n // TypeError if undefined or null\n throw new TypeError(\"Cannot convert undefined or null to object\");\n }\n\n var to = Object(target);\n\n for (var index = 1; index < arguments.length; index++) {\n var nextSource = arguments[index];\n\n if (nextSource != null) {\n // Skip over if undefined or null\n for (var nextKey in nextSource) {\n // Avoid bugs when hasOwnProperty is shadowed\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\n to[nextKey] = nextSource[nextKey];\n }\n }\n }\n }\n return to;\n },\n writable: true,\n configurable: true,\n });\n}\n\n// https://tc39.github.io/ecma262/#sec-array.prototype.includes\nif (!Array.prototype.includes) {\n Object.defineProperty(Array.prototype, \"includes\", {\n value: function (searchElement, fromIndex) {\n if (this == null) {\n throw new TypeError('\"this\" s null or is not defined');\n }\n\n // 1. Let O be ? ToObject(this value).\n var o = Object(this);\n\n // 2. Let len be ? ToLength(? Get(O, \"length\")).\n var len = o.length >>> 0;\n\n // 3. if len is 0, return false.\n if (len === 0) {\n return false;\n }\n\n // 4. Let n be ? ToInteger(fromIndex).\n // (if fromIndex is undefinedo, this step generates the value 0.)\n var n = fromIndex | 0;\n\n // 5. if n โ‰ฅ 0, then\n // a. Let k be n.\n // 6. Else n < 0,\n // a. Let k be len + n.\n // b. if k < 0, let k be 0.\n var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\n\n function sameValueZero(x, y) {\n return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\n }\n\n // 7. Repeat while k < len\n while (k < len) {\n // a. let element k be the result of ? Get(O, ! ToString(k)).\n // b. if SameValueZero(searchElement, elementK) is true, return true.\n if (sameValueZero(o[k], searchElement)) {\n return true;\n }\n // c. Increase k by 1.\n k++;\n }\n\n // 8. Return false\n return false;\n },\n });\n}\n\nif (!String.prototype.includes) {\n String.prototype.includes = function (search, start) {\n \"use strict\";\n\n if (search instanceof RegExp) {\n throw TypeError(\"first argument must not be a RegExp\");\n }\n if (start === undefined) {\n start = 0;\n }\n return this.indexOf(search, start) !== -1;\n };\n}\n\nif (!String.prototype.startsWith) {\n Object.defineProperty(String.prototype, \"startsWith\", {\n value: function (search, rawPos) {\n var pos = rawPos > 0 ? rawPos | 0 : 0;\n return this.substring(pos, pos + search.length) === search;\n },\n });\n}\n","import Parser from \"./parser\";\nimport Util from \"./util\";\nimport StringManager from \"./stringmanager\";\nimport ContentManager from \"./contentmanager\";\nimport Latex from \"./latex\";\nimport MathML from \"./mathml\";\nimport CustomEditors from \"./customeditors\";\nimport Configuration from \"./configuration\";\nimport jsProperties from \"./jsvariables\";\nimport Event from \"./event\";\nimport Listeners from \"./listeners\";\nimport Image from \"./image\";\nimport ServiceProvider from \"./serviceprovider\";\nimport ModalDialog from \"./modal\";\nimport Telemeter from \"./telemeter\";\nimport \"./polyfills\";\nimport \"../styles/styles.css\";\n\n/**\n * @typedef {Object} CoreProperties\n * @property {ServiceProviderProperties} serviceProviderProperties\n * - The ServiceProvider class properties. *\n */\nexport default class Core {\n /**\n * @classdesc\n * This class represents MathType integration Core, managing the following:\n * - Integration initialization.\n * - Event managing.\n * - Insertion of formulas into the edit area.\n * ```js\n * let core = new Core();\n * core.addListener(listener);\n * core.language = 'en';\n *\n * // Initializing Core class.\n * core.init(configurationService);\n * ```\n * @constructs\n * Core constructor.\n * @param {CoreProperties}\n */\n constructor(coreProperties) {\n /**\n * Language. Needed for accessibility and locales. 'en' by default.\n * @type {String}\n */\n this.language = \"en\";\n\n /**\n * Edit mode, 'images' by default. Admits the following values:\n * - images\n * - latex\n * @type {String}\n */\n this.editMode = \"images\";\n\n /**\n * Modal dialog instance.\n * @type {ModalDialog}\n */\n this.modalDialog = null;\n\n /**\n * The instance of {@link CustomEditors}. By default\n * the only custom editor is the Chemistry editor.\n * @type {CustomEditors}\n */\n this.customEditors = new CustomEditors();\n\n /**\n * Chemistry editor.\n * @type {CustomEditor}\n */\n const chemEditorParams = {\n name: \"Chemistry\",\n toolbar: \"chemistry\",\n icon: \"chem.png\",\n confVariable: \"chemEnabled\",\n title: \"ChemType\",\n tooltip: \"Insert a chemistry formula - ChemType\", // TODO: Localize tooltip.\n };\n\n this.customEditors.addEditor(\"chemistry\", chemEditorParams);\n\n /**\n * Environment properties. This object contains data about the integration platform.\n * @typedef IntegrationEnvironment\n * @property {String} IntegrationEnvironment.editor - Editor name. For example the HTML editor.\n * @property {String} IntegrationEnvironment.mode - Integration save mode.\n * @property {String} IntegrationEnvironment.version - Integration version.\n *\n */\n\n /**\n * The environment properties object.\n * @type {IntegrationEnvironment}\n */\n this.environment = {};\n\n /**\n * @typedef EditionProperties\n * @property {Boolean} editionProperties.isNewElement - True if the formula is a new one.\n * False otherwise.\n * @property {HTMLImageElement} editionProperties.temporalImage- The image element.\n * Null if the formula is new.\n * @property {Range} editionProperties.latexRange - Tha range that contains the LaTeX formula.\n * @property {Range} editionProperties.range - The range that contains the image element.\n * @property {String} editionProperties.editMode - The edition mode. 'images' by default.\n */\n\n /**\n * The properties of the current edition process.\n * @type {EditionProperties}\n */\n this.editionProperties = {};\n\n this.editionProperties.isNewElement = true;\n this.editionProperties.temporalImage = null;\n this.editionProperties.latexRange = null;\n this.editionProperties.range = null;\n this.editionProperties.editionStartTime = null;\n\n /**\n * The {@link IntegrationModel} instance.\n * @type {IntegrationModel}\n */\n this.integrationModel = null;\n\n /**\n * The {@link ContentManager} instance.\n * @type {ContentManager}\n */\n this.contentManager = null;\n\n /**\n * The current browser.\n * @type {String}\n */\n this.browser = (() => {\n const ua = navigator.userAgent;\n let browser = \"none\";\n if (ua.search(\"Edge/\") >= 0) {\n browser = \"EDGE\";\n } else if (ua.search(\"Chrome/\") >= 0) {\n browser = \"CHROME\";\n } else if (ua.search(\"Trident/\") >= 0) {\n browser = \"IE\";\n } else if (ua.search(\"Firefox/\") >= 0) {\n browser = \"FIREFOX\";\n } else if (ua.search(\"Safari/\") >= 0) {\n browser = \"SAFARI\";\n }\n return browser;\n })();\n\n /**\n * Plugin listeners.\n * @type {Array.}\n */\n this.listeners = new Listeners();\n\n /**\n * Service provider properties.\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = {};\n if (\"serviceProviderProperties\" in coreProperties) {\n this.serviceProviderProperties = coreProperties.serviceProviderProperties;\n } else {\n throw new Error(\"serviceProviderProperties property missing.\");\n }\n }\n\n /**\n * Static property.\n * Core listeners.\n * @private\n * @type {Listeners}\n */\n static get globalListeners() {\n return Core._globalListeners;\n }\n\n /**\n * Static property setter.\n * Set core listeners.\n * @param {Listeners} value - The property value.\n * @ignore\n */\n static set globalListeners(value) {\n Core._globalListeners = value;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * True when Core.init was called. Otherwise, false.\n * @private\n * @type {Boolean}\n */\n static get initialized() {\n return Core._initialized;\n }\n\n /**\n * Core state. Says if it was loaded previously.\n * @param {Boolean} value - True to say that Core.init was called. Otherwise, false.\n * @ignore\n */\n static set initialized(value) {\n Core._initialized = value;\n }\n\n /**\n * Sets the {@link Core.integrationModel} property.\n * @param {IntegrationModel} integrationModel - The {@link IntegrationModel} property.\n */\n setIntegrationModel(integrationModel) {\n this.integrationModel = integrationModel;\n }\n\n /**\n * Sets the {@link Core.environment} property.\n * @param {IntegrationEnvironment} integrationEnvironment -\n * The {@link IntegrationEnvironment} object.\n */\n setEnvironment(integrationEnvironment) {\n if (\"editor\" in integrationEnvironment) {\n this.environment.editor = integrationEnvironment.editor;\n }\n if (\"mode\" in integrationEnvironment) {\n this.environment.mode = integrationEnvironment.mode;\n }\n if (\"version\" in integrationEnvironment) {\n this.environment.version = integrationEnvironment.version;\n }\n }\n\n /**\n * Sets the custom headers added on editor requests if contentManager isn't undefined.\n * @returns {Object} headers - key value headers.\n */\n setHeaders(headers) {\n const headerObject = this?.contentManager?.setCustomHeaders(headers) || headers;\n Configuration.set(\"customHeaders\", headerObject);\n }\n\n /**\n * Returns the current {@link ModalDialog} instance.\n * @returns {ModalDialog} The current {@link ModalDialog} instance.\n */\n getModalDialog() {\n return this.modalDialog;\n }\n\n /**\n * Inits the {@link Core} class, doing the following:\n * - Calls asynchronously configuration service, retrieving the backend configuration in a JSON.\n * - Updates {@link Configuration} class with the previous configuration properties.\n * - Updates the {@link ServiceProvider} class using the configuration service path as reference.\n * - Loads language strings.\n * - Fires onLoad event.\n * @param {Object} serviceParameters - Service parameters.\n */\n init() {\n if (!Core.initialized) {\n const serviceProviderListener = Listeners.newListener(\"onInit\", () => {\n const jsConfiguration = ServiceProvider.getService(\"configurationjs\", \"\", \"get\");\n const jsonConfiguration = JSON.parse(jsConfiguration);\n Configuration.addConfiguration(jsonConfiguration);\n // Adding JavaScript (not backend) configuration variables.\n Configuration.addConfiguration(jsProperties);\n // Fire 'onLoad' event:\n // All integration must listen this event in order to know if the plugin\n // has been properly loaded.\n StringManager.language = this.language;\n this.listeners.fire(\"onLoad\", {});\n });\n\n ServiceProvider.addListener(serviceProviderListener);\n ServiceProvider.init(this.serviceProviderProperties);\n\n Core.initialized = true;\n } else {\n // Case when there are more than two editor instances.\n // After the first editor all the other editors don't need to load any file or service.\n this.listeners.fire(\"onLoad\", {});\n }\n }\n\n /**\n * Adds a {@link Listener} to the current instance of the {@link Core} class.\n * @param {Listener} listener - The listener object.\n */\n addListener(listener) {\n this.listeners.add(listener);\n }\n\n /**\n * Adds the global {@link Listener} instance to {@link Core} class.\n * @param {Listener} listener - The event listener to be added.\n * @static\n */\n static addGlobalListener(listener) {\n Core.globalListeners.add(listener);\n }\n\n beforeUpdateFormula(mathml, wirisProperties) {\n /**\n * This event is fired before updating the formula.\n * @type {Object}\n * @property {String} mathml - MathML to be transformed.\n * @property {String} editMode - Edit mode.\n * @property {Object} wirisProperties - Extra attributes for the formula.\n * @property {String} language - Formula language.\n */\n const beforeUpdateEvent = new Event();\n\n beforeUpdateEvent.mathml = mathml;\n\n // Cloning wirisProperties object\n // We don't want wirisProperties object modified.\n beforeUpdateEvent.wirisProperties = {};\n\n if (wirisProperties != null) {\n Object.keys(wirisProperties).forEach((attr) => {\n beforeUpdateEvent.wirisProperties[attr] = wirisProperties[attr];\n });\n }\n\n // Read only.\n beforeUpdateEvent.language = this.language;\n beforeUpdateEvent.editMode = this.editMode;\n\n if (this.listeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onBeforeFormulaInsertion\", beforeUpdateEvent)) {\n return {};\n }\n\n return {\n mathml: beforeUpdateEvent.mathml,\n wirisProperties: beforeUpdateEvent.wirisProperties,\n };\n }\n\n /**\n * Converts a MathML into it's correspondent image and inserts the image is\n * inserted in a HTMLElement target by creating\n * a new image or updating an existing one.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the insertion.\n * @param {Window} windowTarget - The window element where the editable content is.\n * @param {String} mathml - The MathML.\n * @param {Array.} wirisProperties - The extra attributes for the formula.\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n /**\n * It is the object with the information of the node or latex to insert.\n * @typedef ReturnObject\n * @property {Node} [node] - The DOM node to insert.\n * @property {String} [latex] - The latex to insert.\n */\n const returnObject = {};\n\n if (!mathml) {\n this.insertElementOnSelection(null, focusElement, windowTarget);\n } else if (this.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n // this.integrationModel.getNonLatexNode is an integration wrapper\n // to have special behaviours for nonLatex.\n // Not all the integrations have special behaviours for nonLatex.\n if (!!this.integrationModel.fillNonLatexNode && !returnObject.latex) {\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.latex = returnObject.latex;\n this.integrationModel.fillNonLatexNode(afterUpdateEvent, windowTarget, mathml);\n } else {\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n }\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n } else {\n returnObject.node = Parser.mathmlToImgObject(windowTarget.document, mathml, wirisProperties, this.language);\n\n this.insertElementOnSelection(returnObject.node, focusElement, windowTarget);\n }\n\n return returnObject;\n }\n\n afterUpdateFormula(focusElement, windowTarget, node, latex) {\n /**\n * This event is fired after update the formula.\n * @type {Event}\n * @param {String} editMode - edit mode.\n * @param {Object} windowTarget - target window.\n * @param {Object} focusElement - target element to be focused after update.\n * @param {String} latex - LaTeX generated by the formula (editMode=latex).\n * @param {Object} node - node generated after update the formula (text if LaTeX img otherwise).\n */\n const afterUpdateEvent = new Event();\n afterUpdateEvent.editMode = this.editMode;\n afterUpdateEvent.windowTarget = windowTarget;\n afterUpdateEvent.focusElement = focusElement;\n afterUpdateEvent.node = node;\n afterUpdateEvent.latex = latex;\n\n if (this.listeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n if (Core.globalListeners.fire(\"onAfterFormulaInsertion\", afterUpdateEvent)) {\n return {};\n }\n\n return {};\n }\n\n /**\n * Sets the caret after a given Node and set the focus to the owner document.\n * @param {Node} node - The Node element.\n */\n placeCaretAfterNode(node) {\n if (node === null) return;\n\n this.integrationModel.getSelection();\n const nodeDocument = node.ownerDocument;\n if (typeof nodeDocument.getSelection !== \"undefined\" && !!node.parentElement) {\n const range = nodeDocument.createRange();\n range.setStartAfter(node);\n range.collapse(true);\n const selection = nodeDocument.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n nodeDocument.body.focus();\n }\n }\n\n /**\n * Replaces a Selection object with an HTMLElement.\n * @param {HTMLElement} element - The HTMLElement to replace the selection.\n * @param {HTMLElement} focusElement - The HTMLElement to be focused after the replace.\n * @param {Window} windowTarget - The window target.\n */\n insertElementOnSelection(element, focusElement, windowTarget) {\n let mathmlOrigin = null;\n if (this.editionProperties.isNewElement) {\n if (element) {\n if (focusElement.type === \"textarea\") {\n Util.updateTextArea(focusElement, element.textContent);\n } else if (document.selection && document.getSelection === 0) {\n let range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n\n if (!(\"parentElement\" in range)) {\n windowTarget.document.execCommand(\"delete\", false);\n range = windowTarget.document.selection.createRange();\n windowTarget.document.execCommand(\"InsertImage\", false, element.src);\n }\n\n if (\"parentElement\" in range) {\n const temporalObject = range.parentElement();\n\n if (temporalObject.nodeName.toUpperCase() === \"IMG\") {\n temporalObject.parentNode.replaceChild(element, temporalObject);\n } else {\n // IE9 fix: parentNode() does not return the IMG node,\n // returns the parent DIV node. In IE < 9, pasteHTML does not work well.\n range.pasteHTML(Util.createObjectCode(element));\n }\n }\n } else {\n let range = null;\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n if (this.editionProperties.range) {\n ({ range } = this.editionProperties);\n this.editionProperties.range = null;\n } else {\n const editorSelection = this.integrationModel.getSelection();\n range = editorSelection.getRangeAt(0);\n }\n\n // Delete if something was surrounded.\n range.deleteContents();\n\n let node = range.startContainer;\n const position = range.startOffset;\n\n if (node.nodeType === 3) {\n // TEXT_NODE.\n node = node.splitText(position);\n node.parentNode.insertBefore(element, node);\n } else if (node.nodeType === 1) {\n // ELEMENT_NODE.\n node.insertBefore(element, node.childNodes[position]);\n }\n\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n focusElement.focus();\n } else {\n const editorSelection = this.integrationModel.getSelection();\n editorSelection.removeAllRanges();\n\n if (this.editionProperties.range) {\n const { range } = this.editionProperties;\n this.editionProperties.range = null;\n editorSelection.addRange(range);\n }\n }\n } else if (this.editionProperties.latexRange) {\n if (document.selection && document.getSelection === 0) {\n this.editionProperties.isNewElement = true;\n this.editionProperties.latexRange.select();\n this.insertElementOnSelection(element, focusElement, windowTarget);\n } else {\n this.editionProperties.latexRange.deleteContents();\n this.editionProperties.latexRange.insertNode(element);\n this.placeCaretAfterNode(element);\n }\n } else if (focusElement.type === \"textarea\") {\n let item;\n // Wrapper for some integrations that can have special behaviours to show latex.\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n item = this.integrationModel.getSelectedItem(focusElement, false);\n } else {\n item = Util.getSelectedItemOnTextarea(focusElement);\n }\n Util.updateExistingTextOnTextarea(focusElement, element.textContent, item.startPosition, item.endPosition);\n } else {\n mathmlOrigin = this.editionProperties.temporalImage?.dataset.mathml;\n if (element && element.nodeName.toLowerCase() === \"img\") {\n // Editor empty, formula has been erased on edit.\n // There are editors (e.g: CKEditor) that put attributes in images.\n // We don't allow that behaviour in our images.\n Image.removeImgDataAttributes(this.editionProperties.temporalImage);\n // Clone is needed to maintain event references to temporalImage.\n Image.clone(element, this.editionProperties.temporalImage);\n } else {\n this.editionProperties.temporalImage.remove();\n }\n this.placeCaretAfterNode(this.editionProperties.temporalImage);\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const mathml = element?.dataset?.mathml;\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove the desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n }\n\n /**\n * Opens a modal dialog containing MathType editor..\n * @param {HTMLElement} target - The target HTMLElement where formulas should be inserted.\n * @param {Boolean} isIframe - True if the target HTMLElement is an iframe. False otherwise.\n */\n openModalDialog(target, isIframe) {\n // Count the time since the editor is open\n this.editionProperties.editionStartTime = Date.now();\n\n // Textarea elements don't have normal document ranges. It only accepts latex edit.\n this.editMode = \"images\";\n\n // In IE is needed keep the range due to after focus the modal window\n // it can't be retrieved the last selection.\n try {\n if (isIframe) {\n // Is needed focus the target first.\n target.contentWindow.focus();\n const selection = target.contentWindow.getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n } else {\n // Is needed focus the target first.\n target.focus();\n const selection = getSelection();\n this.editionProperties.range = selection.getRangeAt(0);\n }\n } catch (e) {\n this.editionProperties.range = null;\n }\n\n if (isIframe === undefined) {\n isIframe = true;\n }\n\n this.editionProperties.latexRange = null;\n\n if (target) {\n let selectedItem;\n if (typeof this.integrationModel.getSelectedItem !== \"undefined\") {\n selectedItem = this.integrationModel.getSelectedItem(target, isIframe);\n } else {\n selectedItem = Util.getSelectedItem(target, isIframe);\n }\n\n // Check LaTeX if and only if the node is a text node (nodeType==3).\n if (selectedItem) {\n // Case when image was selected and button pressed.\n if (!selectedItem.caretPosition && Util.containsClass(selectedItem.node, Configuration.get(\"imageClassName\"))) {\n this.editionProperties.temporalImage = selectedItem.node;\n this.editionProperties.isNewElement = false;\n } else if (selectedItem.node.nodeType === 3) {\n // If it's a text node means that editor is working with LaTeX.\n if (this.integrationModel.getMathmlFromTextNode) {\n // If integration has this function it isn't set range due to we don't\n // know if it will be put into a textarea as a text or image.\n const mathml = this.integrationModel.getMathmlFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (mathml) {\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n }\n } else {\n const latexResult = Latex.getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);\n if (latexResult) {\n const mathml = Latex.getMathMLFromLatex(latexResult.latex);\n this.editMode = \"latex\";\n this.editionProperties.isNewElement = false;\n this.editionProperties.temporalImage = document.createElement(\"img\");\n this.editionProperties.temporalImage.setAttribute(\n Configuration.get(\"imageMathmlAttribute\"),\n MathML.safeXmlEncode(mathml),\n );\n const windowTarget = isIframe ? target.contentWindow : window;\n\n if (target.tagName.toLowerCase() !== \"textarea\") {\n if (document.selection) {\n let leftOffset = 0;\n let previousNode = latexResult.startNode.previousSibling;\n\n while (previousNode) {\n leftOffset += Util.getNodeLength(previousNode);\n previousNode = previousNode.previousSibling;\n }\n\n this.editionProperties.latexRange = windowTarget.document.selection.createRange();\n this.editionProperties.latexRange.moveToElementText(latexResult.startNode.parentNode);\n this.editionProperties.latexRange.move(\"character\", leftOffset + latexResult.startPosition);\n this.editionProperties.latexRange.moveEnd(\"character\", latexResult.latex.length + 4); // Plus 4 for the '$$' characters.\n } else {\n this.editionProperties.latexRange = windowTarget.document.createRange();\n this.editionProperties.latexRange.setStart(latexResult.startNode, latexResult.startPosition);\n this.editionProperties.latexRange.setEnd(latexResult.endNode, latexResult.endPosition);\n }\n }\n }\n }\n }\n } else if (target.tagName.toLowerCase() === \"textarea\") {\n // By default editMode is 'images', but when target is a textarea it needs to be 'latex'.\n this.editMode = \"latex\";\n }\n }\n\n // Setting an object with the editor parameters.\n // Editor parameters can be customized in several ways:\n // 1 - editorAttributes: Contains the default editor attributes,\n // usually the metrics in a comma separated string. Always exists.\n // 2 - editorParameters: Object containing custom editor parameters.\n // These parameters are defined in the backend. So they affects all integration instances.\n\n // The backend send the default editor attributes in a coma separated\n // with the following structure: key1=value1,key2=value2...\n const defaultEditorAttributesArray = Configuration.get(\"editorAttributes\").split(\", \");\n const defaultEditorAttributes = {};\n for (let i = 0, len = defaultEditorAttributesArray.length; i < len; i += 1) {\n const tempAttribute = defaultEditorAttributesArray[i].split(\"=\");\n const key = tempAttribute[0];\n const value = tempAttribute[1];\n defaultEditorAttributes[key] = value;\n }\n // Custom editor parameters.\n const editorAttributes = {\n language: this.language, // Default language value\n };\n // Editor parameters in backend, usually configuration.ini.\n const serverEditorParameters = Configuration.get(\"editorParameters\");\n // Editor parameters through JavaScript configuration.\n const clientEditorParameters = this.integrationModel.editorParameters;\n Object.assign(editorAttributes, defaultEditorAttributes, serverEditorParameters);\n Object.assign(editorAttributes, defaultEditorAttributes, clientEditorParameters);\n\n // Now, update backwards: if user has set a custom language, pass that back to core properties\n this.language = editorAttributes.language;\n StringManager.language = this.language;\n\n editorAttributes.rtl = this.integrationModel.rtl;\n\n const customHeaders = Configuration.get(\"customHeaders\");\n // This is not being used in the code, we are keeping it just in case it's needed.\n // We check if it is a string since we have a setter that will make the customHeaders an object. And we do the conversion for the case when we get the headers from the backend.\n editorAttributes.customHeaders =\n typeof customHeaders === \"string\" ? Util.convertStringToObject(customHeaders) : customHeaders;\n\n const contentManagerAttributes = {};\n contentManagerAttributes.editorAttributes = editorAttributes;\n contentManagerAttributes.language = this.language;\n contentManagerAttributes.customEditors = this.customEditors;\n contentManagerAttributes.environment = this.environment;\n\n if (this.modalDialog == null) {\n this.modalDialog = new ModalDialog(editorAttributes);\n this.contentManager = new ContentManager(contentManagerAttributes);\n // When an instance of ContentManager is created we need to wait until\n // the ContentManager is ready by listening 'onLoad' event.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n });\n this.contentManager.addListener(listener);\n this.contentManager.init();\n this.modalDialog.setContentManager(this.contentManager);\n this.contentManager.setModalDialogInstance(this.modalDialog);\n } else {\n this.contentManager.dbclick = this.editionProperties.dbclick;\n this.contentManager.isNewElement = this.editionProperties.isNewElement;\n if (this.editionProperties.temporalImage != null) {\n const mathML = MathML.safeXmlDecode(\n this.editionProperties.temporalImage.getAttribute(Configuration.get(\"imageMathmlAttribute\")),\n );\n this.contentManager.mathML = mathML;\n }\n }\n this.contentManager.setIntegrationModel(this.integrationModel);\n this.modalDialog.open();\n }\n\n /**\n * Returns the {@link CustomEditors} instance.\n * @return {CustomEditors} The current {@link CustomEditors} instance.\n */\n getCustomEditors() {\n return this.customEditors;\n }\n}\n\n/**\n * Core static listeners.\n * @type {Listeners}\n * @private\n */\nCore._globalListeners = new Listeners();\n\n/**\n * Resources state. Says if they were loaded or not.\n * @type {Boolean}\n * @private\n */\nCore._initialized = false;\n","// eslint-disable-next-line no-unused-vars, import/named\nimport Core from \"./core.src\";\nimport Image from \"./image\";\nimport Listeners from \"./listeners\";\nimport Util from \"./util\";\nimport Configuration from \"./configuration\";\nimport ServiceProvider from \"./serviceprovider\";\nimport Telemeter from \"./telemeter\";\nimport warnIcon from \"../styles/icons/general/warn_icon.svg\"; //eslint-disable-line\n\n/**\n * @typedef {Object} IntegrationModelProperties\n * @property {string} configurationService - Configuration service path.\n * This parameter is needed to determine all services paths.\n * @property {HTMLElement} integrationModelProperties.target - HTML target.\n * @property {string} integrationModelProperties.scriptName - Integration script name.\n * Usually the name of the integration script.\n * @property {Object} integrationModelProperties.environment - integration environment properties.\n * @property {Object} [integrationModelProperties.callbackMethodArguments] - object containing\n * callback method arguments.\n * @property {string} [integrationModelProperties.version] - integration version number.\n * @property {Object} [integrationModelProperties.editorObject] - object containing\n * the integration editor instance.\n * @property {boolean} [integrationModelProperties.rtl] - true if the editor is in RTL mode.\n * false otherwise.\n * @property {ServiceProviderProperties} [integrationModelProperties.serviceProviderProperties]\n * - The service parameters.\n * @property {Object} [integrationModelProperties.integrationParameters]\n * - Overwritten integration parameters.\n */\n\nexport default class IntegrationModel {\n /**\n * @classdesc\n * This class represents an integration model, allowing the integration script to\n * communicate with Core class. Each integration must extend this class.\n * @constructs\n * @param {IntegrationModelProperties} integrationModelProperties\n */\n constructor(integrationModelProperties) {\n /**\n * Language. Needed for accessibility and locales. English by default.\n */\n this.language = \"en\";\n\n /**\n * Service parameters\n * @type {ServiceProviderProperties}\n */\n this.serviceProviderProperties = integrationModelProperties.serviceProviderProperties ?? {};\n\n /**\n * Configuration service path. The integration service is needed by Core class to\n * load all the backend configuration into the frontend and also to create the paths\n * of all services (all services lives in the same route). Mandatory property.\n */\n this.configurationService = \"\";\n if (\"configurationService\" in integrationModelProperties) {\n this.serviceProviderProperties.URI = integrationModelProperties.configurationService;\n console.warn(\"Deprecated property configurationService. Use serviceParameters on instead.\", [\n integrationModelProperties.configurationService,\n ]);\n }\n\n /**\n * Plugin version. Needed to stats and caching.\n * @type {string}\n */\n this.version = \"version\" in integrationModelProperties ? integrationModelProperties.version : \"\";\n\n /**\n * DOM target in which the plugin works. Needed to associate events, insert formulas, etc.\n * Mandatory property.\n */\n this.target = null;\n if (\"target\" in integrationModelProperties) {\n this.target = integrationModelProperties.target;\n } else {\n throw new Error(\"IntegrationModel constructor error: target property missed.\");\n }\n\n /**\n * Integration script name. Needed to know the plugin path.\n */\n if (\"scriptName\" in integrationModelProperties) {\n this.scriptName = integrationModelProperties.scriptName;\n }\n\n /**\n * Object containing the arguments needed by the callback function.\n */\n this.callbackMethodArguments = integrationModelProperties.callbackMethodArguments ?? {};\n\n /**\n * Contains information about the integration environment:\n * like the name of the editor, the version, etc.\n */\n this.environment = integrationModelProperties.environment ?? {};\n\n /**\n * Indicates if the DOM target is - or not - and iframe.\n */\n this.isIframe = false;\n if (this.target != null) {\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Instance of the integration editor object. Usually the entry point to access the API\n * of a HTML editor.\n */\n this.editorObject = integrationModelProperties.editorObject ?? null;\n\n /**\n * Specifies if the direction of the text is RTL. false by default.\n */\n this.rtl = integrationModelProperties.rtl ?? false;\n\n /**\n * Specifies if the integration model exposes the locale strings. false by default.\n */\n this.managesLanguage = integrationModelProperties.managesLanguage ?? false;\n\n /**\n * Specify if editor will open in hand mode only\n */\n this.forcedHandMode = integrationModelProperties?.integrationParameters?.forcedHandMode ?? false;\n\n /**\n * Indicates if an image is selected. Needed to resize the image to the original size in case\n * the image is resized.\n * @type {boolean}\n */\n this.temporalImageResizing = false;\n\n /**\n * The Core class instance associated to the integration model.\n * @type {Core}\n */\n this.core = null;\n\n /**\n * Integration model listeners.\n * @type {Listeners}\n */\n this.listeners = new Listeners();\n\n // Parameters overwrite.\n if (\"integrationParameters\" in integrationModelProperties) {\n IntegrationModel.integrationParameters.forEach((parameter) => {\n if (parameter in integrationModelProperties.integrationParameters) {\n // Don't add empty parameters.\n const value = integrationModelProperties.integrationParameters[parameter];\n if (Object.keys(value).length !== 0) {\n this[parameter] = value;\n }\n }\n });\n }\n }\n\n /**\n * Init function. Usually called from the integration side once the core.js file is loaded.\n */\n init() {\n // Check if language is an object and select the property necessary\n this.language = this.getLanguage();\n\n // We need to wait until Core class is loaded ('onLoad' event) before\n // call the callback method.\n const listener = Listeners.newListener(\"onLoad\", () => {\n this.callbackFunction(this.callbackMethodArguments);\n });\n\n // Backwards compatibility.\n if (this.serviceProviderProperties.URI.indexOf(\"configuration\") !== -1) {\n const uri = this.serviceProviderProperties.URI;\n const server = ServiceProvider.getServerLanguageFromService(uri);\n this.serviceProviderProperties.server = server;\n const configurationIndex = this.serviceProviderProperties.URI.indexOf(\"configuration\");\n const subsTring = this.serviceProviderProperties.URI.substring(0, configurationIndex);\n this.serviceProviderProperties.URI = subsTring;\n }\n\n let serviceParametersURI = this.serviceProviderProperties.URI;\n serviceParametersURI =\n serviceParametersURI.indexOf(\"/\") === 0 || serviceParametersURI.indexOf(\"http\") === 0\n ? serviceParametersURI\n : Util.concatenateUrl(this.getPath(), serviceParametersURI);\n\n this.serviceProviderProperties.URI = serviceParametersURI;\n\n const coreProperties = {};\n coreProperties.serviceProviderProperties = this.serviceProviderProperties;\n\n this.setCore(new Core(coreProperties));\n this.core.addListener(listener);\n this.core.language = this.language;\n\n // Initializing Core class.\n this.core.init();\n // TODO: Move to Core constructor.\n this.core.setEnvironment(this.environment);\n\n // No internet connection modal.\n let attributes = {};\n attributes.class = attributes.id = \"wrs_modal_offline\";\n this.offlineModal = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_content_offline\";\n this.offlineModalContent = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_close\";\n this.offlineModalClose = Util.createElement(\"span\", attributes);\n this.offlineModalClose.innerHTML = \"×\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_warn\";\n this.offlineModalWarn = Util.createElement(\"span\", attributes);\n const generalStyle = `background-image: url(data:image/svg+xml;base64,${window.btoa(warnIcon)})`;\n this.offlineModalWarn.setAttribute(\"style\", generalStyle);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_container\";\n this.offlineModalMessage = Util.createElement(\"div\", attributes);\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text_warn\";\n this.offlineModalMessage1 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage1.innerHTML = \"You are not online!\";\n\n attributes = {};\n attributes.class = \"wrs_modal_offline_text\";\n this.offlineModalMessage2 = Util.createElement(\"p\", attributes);\n this.offlineModalMessage2.innerHTML = `Thank you for using MathType. We have detected you are disconnected from the network. We remind you that you'll need to be connected to use MathType.

Please refresh the page if this message continues appearing.`;\n\n // Append offline modal elements\n this.offlineModalContent.appendChild(this.offlineModalClose);\n this.offlineModalMessage.appendChild(this.offlineModalMessage1);\n this.offlineModalMessage.appendChild(this.offlineModalMessage2);\n this.offlineModalContent.appendChild(this.offlineModalMessage);\n this.offlineModalContent.appendChild(this.offlineModalWarn);\n this.offlineModal.appendChild(this.offlineModalContent);\n document.body.appendChild(this.offlineModal);\n\n const modal = document.getElementById(\"wrs_modal_offline\");\n this.offlineModalClose.addEventListener(\"click\", () => {\n modal.style.display = \"none\";\n });\n\n // Store editor name for telemetry purposes.\n let editorName = this.environment.editor;\n editorName = editorName.slice(0, -1); // Remove version number from editors\n if (editorName.includes(\"TinyMCE\")) editorName = \"TinyMCE\"; // Remove version from Tinymce editor.\n if (editorName.includes(\"Generic\")) editorName = \"Generic\"; // Remove version from Generic editor.\n let solutionTelemeter = editorName;\n\n // If moodle, add information to hosts and solution.\n const isMoodle = !!(typeof M === \"object\" && M !== null);\n let lms;\n\n if (isMoodle) {\n solutionTelemeter = \"Moodle\";\n lms = {\n nam: \"moodle\",\n fam: \"lms\",\n ver: this.environment.moodleVersion,\n category: this.environment.moodleCourseCategory,\n course: this.environment.moodleCourseName,\n };\n if (!editorName.includes(\"TinyMCE\")) {\n editorName = \"Atto\";\n }\n }\n\n // Get the OS and its version.\n const OSData = this.getOS();\n\n // Get the broswer and its version.\n const broswerData = this.getBrowser();\n\n // Create list of hosts to send to telemetry.\n let hosts = [\n {\n nam: broswerData.detectedBrowser,\n fam: \"browser\",\n ver: broswerData.versionBrowser,\n },\n {\n nam: editorName.toLowerCase(),\n fam: \"html-editor\",\n ver: this.environment.editorVersion,\n },\n {\n nam: OSData.detectedOS,\n fam: \"os\",\n ver: OSData.versionOS,\n },\n {\n nam: window.location.hostname,\n fam: \"domain\",\n },\n lms,\n ];\n\n // Filter hosts to remove empty objects and empty keys.\n hosts = hosts.filter((element) => {\n if (element) Object.keys(element).forEach((key) => (element[key] === \"unknown\" ? delete element[key] : {}));\n return element !== undefined;\n });\n\n // Initialize telemeter\n Telemeter.init({\n solution: {\n name: `MathType for ${solutionTelemeter}`,\n version: this.version,\n },\n hosts,\n config: {\n test: false, // True to use the staging Telemetry endpoint instead of the production one.\n debug: false, // True to show more information about Telemeter's execution and use dev-tools.\n dry_run: false, // True to skip sending data to the Telemetry endpoint (for example for unit tests).\n api_key: \"eda2ce9b-0e8a-46f2-acdd-c228a615314e\", // to auth against Telemetry. Data team is the responsible of providing it.\n },\n url: undefined,\n });\n }\n\n /**\n * Returns the Browser name and its version.\n * @return {array} - detectedBrowser = Operating System name.\n * versionBrowser = Operating System version.\n */\n getBrowser() {\n // default value for OS just in case nothing is detected\n let detectedBrowser = \"unknown\";\n let versionBrowser = \"unknown\";\n\n const userAgent = window.navigator.userAgent;\n\n if (/Brave/.test(userAgent)) {\n detectedBrowser = \"brave\";\n if (userAgent.indexOf(\"Brave/\")) {\n const start = userAgent.indexOf(\"Brave\") + 6;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (userAgent.indexOf(\"Edg/\") !== -1) {\n detectedBrowser = \"edge_chromium\";\n const start = userAgent.indexOf(\"Edg/\") + 4;\n versionBrowser = userAgent\n .substring(start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Edg/.test(userAgent)) {\n detectedBrowser = \"edge\";\n let start = userAgent.indexOf(\"Edg\") + 3;\n start += userAgent.substring(start).indexOf(\"/\");\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Firefox/.test(userAgent) || /FxiOS/.test(userAgent)) {\n detectedBrowser = \"firefox\";\n let start = userAgent.indexOf(\"Firefox\");\n start = start === -1 ? userAgent.indexOf(\"FxiOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/OPR/.test(userAgent)) {\n detectedBrowser = \"opera\";\n const start = userAgent.indexOf(\"OPR/\") + 4;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Chrome/.test(userAgent) || /CriOS/.test(userAgent)) {\n detectedBrowser = \"chrome\";\n let start = userAgent.indexOf(\"Chrome\");\n start = start === -1 ? userAgent.indexOf(\"CriOS\") : start;\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n } else if (/Safari/.test(userAgent)) {\n detectedBrowser = \"safari\";\n let start = userAgent.indexOf(\"Version/\");\n start = start + userAgent.substring(start).indexOf(\"/\") + 1;\n let end = userAgent.substring(start).indexOf(\" \");\n end = end === -1 ? userAgent.lastIndexOf(\"\") : end;\n versionBrowser = userAgent.substring(start, end + start).replace(\"_\", \".\");\n }\n\n return { detectedBrowser, versionBrowser };\n }\n\n /**\n * Returns the operating system and its version.\n * @return {array} - detectedOS = Operating System name.\n * versionOS = Operating System version.\n */\n getOS() {\n // default value for OS just in case nothing is detected\n let detectedOS = \"unknown\";\n let versionOS = \"unknown\";\n\n // Retrieve properties to easily detect the OS\n const userAgent = window.navigator.userAgent;\n const platform = window.navigator.platform;\n const macosPlatforms = [\"Macintosh\", \"MacIntel\", \"MacPPC\", \"Mac68K\"];\n const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\n const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\n\n // Find OS and their respective versions\n if (macosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"macos\";\n if (userAgent.indexOf(\"OS X\") !== -1) {\n const start = userAgent.indexOf(\"OS X\") + 5;\n const end = userAgent.substring(start).indexOf(\" \");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (iosPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"ios\";\n if (userAgent.indexOf(\"OS \") !== -1) {\n const start = userAgent.indexOf(\"OS \") + 3;\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n }\n } else if (windowsPlatforms.indexOf(platform) !== -1) {\n detectedOS = \"windows\";\n const start = userAgent.indexOf(\"Windows\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/Android/.test(userAgent)) {\n detectedOS = \"android\";\n const start = userAgent.indexOf(\"Android\");\n let end = userAgent.substring(start).indexOf(\";\");\n if (end === -1) {\n end = userAgent.substring(start).indexOf(\")\");\n }\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (/CrOS/.test(userAgent)) {\n detectedOS = \"chromeos\";\n let start = userAgent.indexOf(\"CrOS \") + 5;\n start += userAgent.substring(start).indexOf(\" \");\n const end = userAgent.substring(start).indexOf(\")\");\n versionOS = userAgent\n .substring(start, end + start)\n .replace(\"_\", \".\")\n .replace(/[^\\d.-]/g, \"\");\n } else if (detectedOS === \"unknown\" && /Linux/.test(platform)) {\n detectedOS = \"linux\";\n }\n\n return { detectedOS, versionOS };\n }\n\n /**\n * Returns the absolute path of the integration script.\n * @return {string} - Absolute path for the integration script.\n */\n getPath() {\n if (typeof this.scriptName === \"undefined\") {\n throw new Error(\"scriptName property needed for getPath.\");\n }\n const col = document.getElementsByTagName(\"script\");\n let path = \"\";\n for (let i = 0; i < col.length; i += 1) {\n const j = col[i].src.lastIndexOf(this.scriptName);\n if (j >= 0) {\n path = col[i].src.substr(0, j - 1);\n }\n }\n return path;\n }\n\n /**\n * Returns integration model plugin version\n * @param {string} - Plugin version\n */\n getVersion() {\n return this.version;\n }\n\n /**\n * Sets the language property.\n * @param {string} language - language code.\n */\n setLanguage(language) {\n this.language = language;\n }\n\n /**\n * Sets a Core instance.\n * @param {Core} core - instance of Core class.\n */\n setCore(core) {\n this.core = core;\n core.setIntegrationModel(this);\n }\n\n /**\n * Returns the Core instance.\n * @returns {Core} instance of Core class.\n */\n getCore() {\n return this.core;\n }\n\n /**\n * Sets the object target and updates the iframe property.\n * @param {HTMLElement} target - target object.\n */\n setTarget(target) {\n this.target = target;\n this.isIframe = this.target.tagName.toUpperCase() === \"IFRAME\";\n }\n\n /**\n * Sets the editor object.\n * @param {Object} editorObject - The editor object.\n */\n setEditorObject(editorObject) {\n this.editorObject = editorObject;\n }\n\n /**\n * Opens formula editor to editing a new formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openNewFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.dbclick = false;\n this.core.editionProperties.isNewElement = true;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Opens formula editor to editing an existing formula. Can be overwritten in order to make some\n * actions from integration part before the formula is edited.\n */\n openExistingFormulaEditor() {\n if (window.navigator.onLine) {\n this.core.editionProperties.isNewElement = false;\n this.core.openModalDialog(this.target, this.isIframe);\n } else {\n const modal = document.getElementById(\"wrs_modal_offline\");\n modal.style.display = \"block\";\n }\n }\n\n /**\n * Wrapper to Core.updateFormula method.\n * Transform a MathML into a image formula.\n * Then the image formula is inserted in the specified target, creating a new image (new formula)\n * or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n */\n updateFormula(mathml) {\n if (this.editorParameters) {\n mathml = com.wiris.editor.util.EditorUtils.addAnnotation(\n mathml,\n \"application/vnd.wiris.mtweb-params+json\",\n JSON.stringify(this.editorParameters),\n );\n }\n let focusElement;\n let windowTarget;\n const wirisProperties = null;\n\n if (this.isIframe) {\n focusElement = this.target.contentWindow;\n windowTarget = this.target.contentWindow;\n } else {\n focusElement = this.target;\n windowTarget = window;\n }\n\n let obj = this.core.beforeUpdateFormula(mathml, wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n obj = this.insertFormula(focusElement, windowTarget, obj.mathml, obj.wirisProperties);\n\n if (!obj) {\n return \"\";\n }\n\n return this.core.afterUpdateFormula(obj.focusElement, obj.windowTarget, obj.node, obj.latex);\n }\n\n /**\n * Wrapper to Core.insertFormula method.\n * Inserts the formula in the specified target, creating\n * a new image (new formula) or updating an existing one.\n * @param {string} mathml - MathML to generate the formula.\n * @param {string} editMode - Edit Mode (LaTeX or images).\n * @returns {ReturnObject} - Object with the information of the node or latex to insert.\n */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n const obj = this.core.insertFormula(focusElement, windowTarget, mathml, wirisProperties);\n\n // Delete temporal image when inserted\n this.core.editionProperties.temporalImage = null;\n\n return obj;\n }\n\n /**\n * Returns the target selection.\n * @returns {Selection} target selection.\n */\n getSelection() {\n if (this.isIframe) {\n this.target.contentWindow.focus();\n return this.target.contentWindow.getSelection();\n }\n this.target.focus();\n return window.getSelection();\n }\n\n /**\n * Add events to formulas in the DOM target. The events added are the following:\n * - doubleClickHandler: handles Double-click event on formulas by opening an editor\n * to edit them.\n * - mouseDownHandler: handles mouse down event on formulas by saving the size of the formula\n * in case the the formula is resized.\n * - mouseUpHandler: handles mouse up event on formulas by restoring the saved formula size\n * in case the formula is resized.\n */\n addEvents() {\n const eventTarget = this.isIframe ? this.target.contentWindow.document : this.target;\n Util.addElementEvents(\n eventTarget,\n (element, event) => {\n this.doubleClickHandler(element, event);\n // Avoid creating the double click listener more than once for each element.\n // This also allows CKEditor4 to add their own double click listener.\n event.preventDefault();\n },\n (element, event) => {\n this.mousedownHandler(element, event);\n },\n (element, event) => {\n this.mouseupHandler(element, event);\n },\n );\n }\n\n /**\n * Remove events to formulas in the DOM target.\n */\n removeEvents() {\n const eventTarget =\n this.isIframe && this.target.contentWindow?.document ? this.target.contentWindow.document : this.target;\n\n if (!eventTarget) {\n return;\n }\n\n Util.removeElementEvents(eventTarget);\n }\n\n /**\n * Remove events, modals and set this.editorObject to null in order to prevent memory leaks.\n */\n destroy() {\n this.removeEvents();\n // Destroy modal dialog if exists.\n if (this.core.modalDialog) {\n this.core.modalDialog.destroy();\n }\n\n // Remove offline modal dialog if exists.\n if (this.offlineModal) {\n this.offlineModal.remove();\n }\n\n this.editorObject = null;\n }\n\n /**\n * Handles a Double-click on the target element. Opens an editor\n * to re-edit the double-clicked formula.\n * @param {HTMLElement} element - DOM object target.\n */\n doubleClickHandler(element) {\n this.core.editionProperties.dbclick = true;\n if (element.nodeName.toLowerCase() === \"img\") {\n this.core.getCustomEditors().disable();\n const customEditorAttributeName = Configuration.get(\"imageCustomEditorName\");\n if (element.hasAttribute(customEditorAttributeName)) {\n const customEditor = element.getAttribute(customEditorAttributeName);\n this.core.getCustomEditors().enable(customEditor);\n }\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.core.editionProperties.temporalImage = element;\n this.core.editionProperties.isNewElement = true;\n this.openExistingFormulaEditor();\n }\n }\n }\n\n /**\n * Handles a mouse up event on the target element. Restores the image size to avoid\n * resizing formulas.\n */\n mouseupHandler() {\n if (this.temporalImageResizing) {\n setTimeout(() => {\n Image.fixAfterResize(this.temporalImageResizing);\n }, 10);\n }\n }\n\n /**\n * Handles a mouse down event on the target element. Saves the formula size to avoid\n * resizing formulas.\n * @param {HTMLElement} element - target element.\n */\n mousedownHandler(element) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n this.temporalImageResizing = element;\n }\n }\n }\n\n /**\n * Returns the integration language. By default the browser agent. This method\n * should be overwritten to obtain the integration language, for example using the\n * plugin API of an HTML editor.\n * @returns {string} integration language.\n */\n getLanguage() {\n return this.getBrowserLanguage();\n }\n\n /**\n * Returns the browser language.\n * @returns {string} the browser language.\n */\n // eslint-disable-next-line class-methods-use-this\n getBrowserLanguage() {\n let language = \"en\";\n if (navigator.userLanguage) {\n language = navigator.userLanguage.substring(0, 2);\n } else if (navigator.language) {\n language = navigator.language.substring(0, 2);\n } else {\n language = \"en\";\n }\n return language;\n }\n\n /**\n * This function is called once the {@link Core} is loaded. IntegrationModel class\n * will fire this method when {@link Core} 'onLoad' event is fired.\n * This method should content all the logic to init\n * the integration.\n */\n callbackFunction() {\n // It's needed to wait until the integration target is ready. The event is fired\n // from the integration side.\n const listener = Listeners.newListener(\"onTargetReady\", () => {\n this.addEvents(this.target);\n });\n this.listeners.add(listener);\n }\n\n /**\n * Function called when the content submits an action.\n */\n // eslint-disable-next-line class-methods-use-this\n notifyWindowClosed() {\n // Nothing.\n }\n\n /**\n * Wrapper.\n * Extracts mathml of a determined text node. This function is used as a wrapper inside core.js\n * in order to get mathml from a text node that can contain normal LaTeX or other chosen text.\n * @param {string} textNode - text node to extract the MathML.\n * @param {int} caretPosition - caret position inside the text node.\n * @returns {string} MathML inside the text node.\n */\n\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getMathmlFromTextNode(textNode, caretPosition) {}\n\n /**\n * Wrapper\n * It fills wrs event object of nonLatex with the desired data.\n * @param {Object} event - event object.\n * @param {Object} window dom window object.\n * @param {string} mathml valid mathml.\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n fillNonLatexNode(event, window, mathml) {}\n\n /**\n Wrapper.\n * Returns selected item from the target.\n * @param {HTMLElement} target - target element\n * @param {boolean} iframe\n */\n // eslint-disable-next-line class-methods-use-this, no-unused-vars\n getSelectedItem(target, isIframe) {}\n\n // Set temporal image to null and make focus come back.\n static setActionsOnCancelButtons() {\n // Make focus come back on the previous place it was when click cancel button\n const currentInstance = WirisPlugin.currentInstance;\n const editorSelection = currentInstance.getSelection();\n editorSelection.removeAllRanges();\n\n if (currentInstance.core.editionProperties.range) {\n const { range } = currentInstance.core.editionProperties;\n currentInstance.core.editionProperties.range = null;\n editorSelection.addRange(range);\n if (range.startOffset !== range.endOffset) {\n currentInstance.core.placeCaretAfterNode(currentInstance.core.editionProperties.temporalImage);\n }\n }\n\n // eslint-disable-next-line no-undef\n if (WirisPlugin.currentInstance) {\n WirisPlugin.currentInstance.core.editionProperties.temporalImage = null; // eslint-disable-line\n }\n }\n}\n\n// To know if the integration that extends this class implements\n// wrapper methods, they are set as undefined.\nIntegrationModel.prototype.getMathmlFromTextNode = undefined;\nIntegrationModel.prototype.fillNonLatexNode = undefined;\nIntegrationModel.prototype.getSelectedItem = undefined;\n\n/**\n * An object containing a list with the overwritable class constructor properties.\n * @type {Object}\n */\nIntegrationModel.integrationParameters = [\"serviceProviderProperties\", \"editorParameters\"];\n","/* eslint-disable */\nvar md5;\nexport default md5;\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n delete Array.prototype.__class__;\n})();\n\n(function () {\n var HxOverrides = function () {};\n HxOverrides.__name__ = true;\n HxOverrides.dateStr = function (date) {\n var m = date.getMonth() + 1;\n var d = date.getDate();\n var h = date.getHours();\n var mi = date.getMinutes();\n var s = date.getSeconds();\n return (\n date.getFullYear() +\n \"-\" +\n (m < 10 ? \"0\" + m : \"\" + m) +\n \"-\" +\n (d < 10 ? \"0\" + d : \"\" + d) +\n \" \" +\n (h < 10 ? \"0\" + h : \"\" + h) +\n \":\" +\n (mi < 10 ? \"0\" + mi : \"\" + mi) +\n \":\" +\n (s < 10 ? \"0\" + s : \"\" + s)\n );\n };\n HxOverrides.strDate = function (s) {\n switch (s.length) {\n case 8:\n var k = s.split(\":\");\n var d = new Date();\n d.setTime(0);\n d.setUTCHours(k[0]);\n d.setUTCMinutes(k[1]);\n d.setUTCSeconds(k[2]);\n return d;\n case 10:\n var k = s.split(\"-\");\n return new Date(k[0], k[1] - 1, k[2], 0, 0, 0);\n case 19:\n var k = s.split(\" \");\n var y = k[0].split(\"-\");\n var t = k[1].split(\":\");\n return new Date(y[0], y[1] - 1, y[2], t[0], t[1], t[2]);\n default:\n throw \"Invalid date format : \" + s;\n }\n };\n HxOverrides.cca = function (s, index) {\n var x = s.charCodeAt(index);\n if (x != x) return undefined;\n return x;\n };\n HxOverrides.substr = function (s, pos, len) {\n if (pos != null && pos != 0 && len != null && len < 0) return \"\";\n if (len == null) len = s.length;\n if (pos < 0) {\n pos = s.length + pos;\n if (pos < 0) pos = 0;\n } else if (len < 0) len = s.length + len - pos;\n return s.substr(pos, len);\n };\n HxOverrides.remove = function (a, obj) {\n var i = 0;\n var l = a.length;\n while (i < l) {\n if (a[i] == obj) {\n a.splice(i, 1);\n return true;\n }\n i++;\n }\n return false;\n };\n HxOverrides.iter = function (a) {\n return {\n cur: 0,\n arr: a,\n hasNext: function () {\n return this.cur < this.arr.length;\n },\n next: function () {\n return this.arr[this.cur++];\n },\n };\n };\n var IntIter = function (min, max) {\n this.min = min;\n this.max = max;\n };\n IntIter.__name__ = true;\n IntIter.prototype = {\n next: function () {\n return this.min++;\n },\n hasNext: function () {\n return this.min < this.max;\n },\n __class__: IntIter,\n };\n var Std = function () {};\n Std.__name__ = true;\n Std[\"is\"] = function (v, t) {\n return js.Boot.__instanceof(v, t);\n };\n Std.string = function (s) {\n return js.Boot.__string_rec(s, \"\");\n };\n Std[\"int\"] = function (x) {\n return x | 0;\n };\n Std.parseInt = function (x) {\n var v = parseInt(x, 10);\n if (v == 0 && (HxOverrides.cca(x, 1) == 120 || HxOverrides.cca(x, 1) == 88)) v = parseInt(x);\n if (isNaN(v)) return null;\n return v;\n };\n Std.parseFloat = function (x) {\n return parseFloat(x);\n };\n Std.random = function (x) {\n return Math.floor(Math.random() * x);\n };\n var com = com || {};\n if (!com.wiris) com.wiris = {};\n if (!com.wiris.js) com.wiris.js = {};\n com.wiris.js.JsPluginTools = function () {\n this.tryReady();\n };\n com.wiris.js.JsPluginTools.__name__ = true;\n com.wiris.js.JsPluginTools.main = function () {\n var ev;\n ev = com.wiris.js.JsPluginTools.getInstance();\n haxe.Timer.delay($bind(ev, ev.tryReady), 100);\n };\n com.wiris.js.JsPluginTools.getInstance = function () {\n if (com.wiris.js.JsPluginTools.instance == null)\n com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();\n return com.wiris.js.JsPluginTools.instance;\n };\n com.wiris.js.JsPluginTools.bypassEncapsulation = function () {\n if (window.com == null) window.com = {};\n if (window.com.wiris == null) window.com.wiris = {};\n if (window.com.wiris.js == null) window.com.wiris.js = {};\n if (window.com.wiris.js.JsPluginTools == null)\n window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();\n };\n com.wiris.js.JsPluginTools.prototype = {\n md5encode: function (content) {\n return haxe.Md5.encode(content);\n },\n doLoad: function () {\n this.ready = true;\n com.wiris.js.JsPluginTools.instance = this;\n com.wiris.js.JsPluginTools.bypassEncapsulation();\n },\n tryReady: function () {\n this.ready = false;\n if (js.Lib.document.readyState) {\n this.doLoad();\n this.ready = true;\n }\n if (!this.ready) haxe.Timer.delay($bind(this, this.tryReady), 100);\n },\n __class__: com.wiris.js.JsPluginTools,\n };\n var haxe = haxe || {};\n haxe.Log = function () {};\n haxe.Log.__name__ = true;\n haxe.Log.trace = function (v, infos) {\n js.Boot.__trace(v, infos);\n };\n haxe.Log.clear = function () {\n js.Boot.__clear_trace();\n };\n haxe.Md5 = function () {};\n haxe.Md5.__name__ = true;\n haxe.Md5.encode = function (s) {\n return new haxe.Md5().doEncode(s);\n };\n haxe.Md5.prototype = {\n doEncode: function (str) {\n var x = this.str2blks(str);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var step;\n var i = 0;\n while (i < x.length) {\n var olda = a;\n var oldb = b;\n var oldc = c;\n var oldd = d;\n step = 0;\n a = this.ff(a, b, c, d, x[i], 7, -680876936);\n d = this.ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = this.ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = this.ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = this.ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = this.ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = this.ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = this.ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = this.ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = this.ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = this.ff(c, d, a, b, x[i + 10], 17, -42063);\n b = this.ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = this.ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = this.ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = this.ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = this.ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = this.gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = this.gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = this.gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = this.gg(b, c, d, a, x[i], 20, -373897302);\n a = this.gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = this.gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = this.gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = this.gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = this.gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = this.gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = this.gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = this.gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = this.gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = this.gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = this.gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = this.gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = this.hh(a, b, c, d, x[i + 5], 4, -378558);\n d = this.hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = this.hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = this.hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = this.hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = this.hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = this.hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = this.hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = this.hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = this.hh(d, a, b, c, x[i], 11, -358537222);\n c = this.hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = this.hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = this.hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = this.hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = this.hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = this.hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = this.ii(a, b, c, d, x[i], 6, -198630844);\n d = this.ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = this.ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = this.ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = this.ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = this.ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = this.ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = this.ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = this.ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = this.ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = this.ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = this.ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = this.ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = this.ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = this.ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = this.ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = this.addme(a, olda);\n b = this.addme(b, oldb);\n c = this.addme(c, oldc);\n d = this.addme(d, oldd);\n i += 16;\n }\n return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);\n },\n ii: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(c, this.bitOR(b, ~d)), a, b, x, s, t);\n },\n hh: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitXOR(this.bitXOR(b, c), d), a, b, x, s, t);\n },\n gg: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, d), this.bitAND(c, ~d)), a, b, x, s, t);\n },\n ff: function (a, b, c, d, x, s, t) {\n return this.cmn(this.bitOR(this.bitAND(b, c), this.bitAND(~b, d)), a, b, x, s, t);\n },\n cmn: function (q, a, b, x, s, t) {\n return this.addme(this.rol(this.addme(this.addme(a, q), this.addme(x, t)), s), b);\n },\n rol: function (num, cnt) {\n return (num << cnt) | (num >>> (32 - cnt));\n },\n str2blks: function (str) {\n var nblk = ((str.length + 8) >> 6) + 1;\n var blks = new Array();\n var _g1 = 0,\n _g = nblk * 16;\n while (_g1 < _g) {\n var i = _g1++;\n blks[i] = 0;\n }\n var i = 0;\n while (i < str.length) {\n blks[i >> 2] |= HxOverrides.cca(str, i) << (((str.length * 8 + i) % 4) * 8);\n i++;\n }\n blks[i >> 2] |= 128 << (((str.length * 8 + i) % 4) * 8);\n var l = str.length * 8;\n var k = nblk * 16 - 2;\n blks[k] = l & 255;\n blks[k] |= ((l >>> 8) & 255) << 8;\n blks[k] |= ((l >>> 16) & 255) << 16;\n blks[k] |= ((l >>> 24) & 255) << 24;\n return blks;\n },\n rhex: function (num) {\n var str = \"\";\n var hex_chr = \"0123456789abcdef\";\n var _g = 0;\n while (_g < 4) {\n var j = _g++;\n str += hex_chr.charAt((num >> (j * 8 + 4)) & 15) + hex_chr.charAt((num >> (j * 8)) & 15);\n }\n return str;\n },\n addme: function (x, y) {\n var lsw = (x & 65535) + (y & 65535);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 65535);\n },\n bitAND: function (a, b) {\n var lsb = a & 1 & (b & 1);\n var msb31 = (a >>> 1) & (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitXOR: function (a, b) {\n var lsb = (a & 1) ^ (b & 1);\n var msb31 = (a >>> 1) ^ (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n bitOR: function (a, b) {\n var lsb = (a & 1) | (b & 1);\n var msb31 = (a >>> 1) | (b >>> 1);\n return (msb31 << 1) | lsb;\n },\n __class__: haxe.Md5,\n };\n haxe.Timer = function (time_ms) {\n var me = this;\n this.id = window.setInterval(function () {\n me.run();\n }, time_ms);\n };\n haxe.Timer.__name__ = true;\n haxe.Timer.delay = function (f, time_ms) {\n var t = new haxe.Timer(time_ms);\n t.run = function () {\n t.stop();\n f();\n };\n return t;\n };\n haxe.Timer.measure = function (f, pos) {\n var t0 = haxe.Timer.stamp();\n var r = f();\n haxe.Log.trace(haxe.Timer.stamp() - t0 + \"s\", pos);\n return r;\n };\n haxe.Timer.stamp = function () {\n return new Date().getTime() / 1000;\n };\n haxe.Timer.prototype = {\n run: function () {},\n stop: function () {\n if (this.id == null) return;\n window.clearInterval(this.id);\n this.id = null;\n },\n __class__: haxe.Timer,\n };\n var js = js || {};\n js.Boot = function () {};\n js.Boot.__name__ = true;\n js.Boot.__unhtml = function (s) {\n return s.split(\"&\").join(\"&\").split(\"<\").join(\"<\").split(\">\").join(\">\");\n };\n js.Boot.__trace = function (v, i) {\n var msg = i != null ? i.fileName + \":\" + i.lineNumber + \": \" : \"\";\n msg += js.Boot.__string_rec(v, \"\");\n var d;\n if (typeof document != \"undefined\" && (d = document.getElementById(\"haxe:trace\")) != null)\n d.innerHTML += js.Boot.__unhtml(msg) + \"
\";\n else if (typeof console != \"undefined\" && console.log != null) console.log(msg);\n };\n js.Boot.__clear_trace = function () {\n var d = document.getElementById(\"haxe:trace\");\n if (d != null) d.innerHTML = \"\";\n };\n js.Boot.isClass = function (o) {\n return o.__name__;\n };\n js.Boot.isEnum = function (e) {\n return e.__ename__;\n };\n js.Boot.getClass = function (o) {\n return o.__class__;\n };\n js.Boot.__string_rec = function (o, s) {\n if (o == null) return \"null\";\n if (s.length >= 5) return \"<...>\";\n var t = typeof o;\n if (t == \"function\" && (o.__name__ || o.__ename__)) t = \"object\";\n switch (t) {\n case \"object\":\n if (o instanceof Array) {\n if (o.__enum__) {\n if (o.length == 2) return o[0];\n var str = o[0] + \"(\";\n s += \"\\t\";\n var _g1 = 2,\n _g = o.length;\n while (_g1 < _g) {\n var i = _g1++;\n if (i != 2) str += \",\" + js.Boot.__string_rec(o[i], s);\n else str += js.Boot.__string_rec(o[i], s);\n }\n return str + \")\";\n }\n var l = o.length;\n var i;\n var str = \"[\";\n s += \"\\t\";\n var _g = 0;\n while (_g < l) {\n var i1 = _g++;\n str += (i1 > 0 ? \",\" : \"\") + js.Boot.__string_rec(o[i1], s);\n }\n str += \"]\";\n return str;\n }\n var tostr;\n try {\n tostr = o.toString;\n } catch (e) {\n return \"???\";\n }\n if (tostr != null && tostr != Object.toString) {\n var s2 = o.toString();\n if (s2 != \"[object Object]\") return s2;\n }\n var k = null;\n var str = \"{\\n\";\n s += \"\\t\";\n var hasp = o.hasOwnProperty != null;\n for (var k in o) {\n if (hasp && !o.hasOwnProperty(k)) {\n continue;\n }\n if (\n k == \"prototype\" ||\n k == \"__class__\" ||\n k == \"__super__\" ||\n k == \"__interfaces__\" ||\n k == \"__properties__\"\n ) {\n continue;\n }\n if (str.length != 2) str += \", \\n\";\n str += s + k + \" : \" + js.Boot.__string_rec(o[k], s);\n }\n s = s.substring(1);\n str += \"\\n\" + s + \"}\";\n return str;\n case \"function\":\n return \"\";\n case \"string\":\n return o;\n default:\n return String(o);\n }\n };\n js.Boot.__interfLoop = function (cc, cl) {\n if (cc == null) return false;\n if (cc == cl) return true;\n var intf = cc.__interfaces__;\n if (intf != null) {\n var _g1 = 0,\n _g = intf.length;\n while (_g1 < _g) {\n var i = _g1++;\n var i1 = intf[i];\n if (i1 == cl || js.Boot.__interfLoop(i1, cl)) return true;\n }\n }\n return js.Boot.__interfLoop(cc.__super__, cl);\n };\n js.Boot.__instanceof = function (o, cl) {\n try {\n if (o instanceof cl) {\n if (cl == Array) return o.__enum__ == null;\n return true;\n }\n if (js.Boot.__interfLoop(o.__class__, cl)) return true;\n } catch (e) {\n if (cl == null) return false;\n }\n switch (cl) {\n case Int:\n return Math.ceil(o % 2147483648.0) === o;\n case Float:\n return typeof o == \"number\";\n case Bool:\n return o === true || o === false;\n case String:\n return typeof o == \"string\";\n case Dynamic:\n return true;\n default:\n if (o == null) return false;\n if (cl == Class && o.__name__ != null) return true;\n else null;\n if (cl == Enum && o.__ename__ != null) return true;\n else null;\n return o.__enum__ == cl;\n }\n };\n js.Boot.__cast = function (o, t) {\n if (js.Boot.__instanceof(o, t)) return o;\n else throw \"Cannot cast \" + Std.string(o) + \" to \" + Std.string(t);\n };\n js.Lib = function () {};\n js.Lib.__name__ = true;\n js.Lib.debug = function () {\n debugger;\n };\n js.Lib.alert = function (v) {\n alert(js.Boot.__string_rec(v, \"\"));\n };\n js.Lib.eval = function (code) {\n return eval(code);\n };\n js.Lib.setErrorHandler = function (f) {\n js.Lib.onerror = f;\n };\n var $_;\n function $bind(o, m) {\n var f = function () {\n return f.method.apply(f.scope, arguments);\n };\n f.scope = o;\n f.method = m;\n return f;\n }\n if (Array.prototype.indexOf)\n HxOverrides.remove = function (a, o) {\n var i = a.indexOf(o);\n if (i == -1) return false;\n a.splice(i, 1);\n return true;\n };\n else null;\n Math.__name__ = [\"Math\"];\n Math.NaN = Number.NaN;\n Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;\n Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n Math.isFinite = function (i) {\n return isFinite(i);\n };\n Math.isNaN = function (i) {\n return isNaN(i);\n };\n String.prototype.__class__ = String;\n String.__name__ = true;\n Array.prototype.__class__ = Array;\n Array.__name__ = true;\n Date.prototype.__class__ = Date;\n Date.__name__ = [\"Date\"];\n var Int = { __name__: [\"Int\"] };\n var Dynamic = { __name__: [\"Dynamic\"] };\n var Float = Number;\n Float.__name__ = [\"Float\"];\n var Bool = Boolean;\n Bool.__ename__ = [\"Bool\"];\n var Class = { __name__: [\"Class\"] };\n var Enum = {};\n var Void = { __ename__: [\"Void\"] };\n if (typeof document != \"undefined\") js.Lib.document = document;\n if (typeof window != \"undefined\") {\n js.Lib.window = window;\n js.Lib.window.onerror = function (msg, url, line) {\n var f = js.Lib.onerror;\n if (f == null) return false;\n return f(msg, [url + \":\" + line]);\n };\n }\n com.wiris.js.JsPluginTools.main();\n})();\ndelete Array.prototype.__class__;\n// @codingStandardsIgnoreEnd\n","import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\n\n/**\n * This class represents the MathType integration for CKEditor5.\n * @extends {IntegrationModel}\n */\nexport default class CKEditor5Integration extends IntegrationModel {\n constructor(ckeditorIntegrationModelProperties) {\n const editor = ckeditorIntegrationModelProperties.editorObject;\n\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\n }\n /**\n * CKEditor5 Integration.\n *\n * @param {integrationModelProperties} integrationModelAttributes\n */\n super(ckeditorIntegrationModelProperties);\n\n /**\n * Folder name used for the integration inside CKEditor plugins folder.\n */\n this.integrationFolderName = \"ckeditor_wiris\";\n }\n\n /**\n * @inheritdoc\n * @returns {string} - The CKEditor instance language.\n * @override\n */\n getLanguage() {\n // Returns the CKEDitor instance language taking into account that the language can be an object.\n // Try to get editorParameters.language, fail silently otherwise\n try {\n return this.editorParameters.language;\n } catch (e) {}\n const languageObject = this.editorObject.config.get(\"language\");\n if (languageObject != null) {\n if (typeof languageObject === \"object\") {\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\n return languageObject.ui;\n }\n return this.editorObject.locale.uiLanguage;\n }\n return languageObject;\n }\n return super.getLanguage();\n }\n\n /**\n * Adds callbacks to the following CKEditor listeners:\n * - 'focus' - updates the current instance.\n * - 'contentDom' - adds 'doubleclick' callback.\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\n * - 'setData' - parses the data converting MathML into images.\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\n * - 'mode' - recalculates the active element.\n */\n addEditorListeners() {\n const editor = this.editorObject;\n\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\n this.checkElement();\n }\n }\n\n /**\n * Checks the current container and assign events in case that it doesn't have them.\n * CKEditor replaces several times the element element during its execution,\n * so we must assign the events again to editor element.\n */\n checkElement() {\n const editor = this.editorObject;\n const newElement = editor.sourceElement;\n\n // If the element wasn't treated, add the events.\n if (!newElement.wirisActive) {\n this.setTarget(newElement);\n this.addEvents();\n // Set the element as treated\n newElement.wirisActive = true;\n }\n }\n\n /**\n * @inheritdoc\n * @param {HTMLElement} element - HTMLElement target.\n * @param {MouseEvent} event - event which trigger the handler.\n */\n doubleClickHandler(element, event) {\n this.core.editionProperties.dbclick = true;\n if (this.editorObject.isReadOnly === false) {\n if (element.nodeName.toLowerCase() === \"img\") {\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\n // doubleclick event ends here.\n if (typeof event.stopPropagation !== \"undefined\") {\n // old I.E compatibility.\n event.stopPropagation();\n } else {\n event.returnValue = false;\n }\n this.core.getCustomEditors().disable();\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\n if (customEditorAttr) {\n this.core.getCustomEditors().enable(customEditorAttr);\n }\n this.core.editionProperties.temporalImage = element;\n this.openExistingFormulaEditor();\n }\n }\n }\n }\n\n /** @inheritdoc */\n static getCorePath() {\n return null; // TODO\n }\n\n /** @inheritdoc */\n callbackFunction() {\n super.callbackFunction();\n this.addEditorListeners();\n }\n\n openNewFormulaEditor() {\n // Store the editor selection as it will be lost upon opening the modal\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\n\n // Focus on the selected editor when multiple editor instances are present\n WirisPlugin.currentInstance = this;\n\n return super.openNewFormulaEditor();\n }\n\n /**\n * Replaces old formula with new MathML or inserts it in caret position if new\n * @param {String} mathml MathML to update old one or insert\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\n */\n insertMathml(mathml) {\n // This returns the value returned by the callback function (writer => {...})\n return this.editorObject.model.change((writer) => {\n const core = this.getCore();\n const selection = this.editorObject.model.document.selection;\n\n const modelElementNew = writer.createElement(\"mathml\", {\n formula: mathml,\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\n });\n\n // Obtain the DOM object corresponding to the formula\n if (core.editionProperties.isNewElement) {\n // Don't bother inserting anything at all if the MathML is empty.\n if (!mathml) return;\n\n const viewSelection =\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\n\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\n\n // Remove selection\n if (!viewSelection.isCollapsed) {\n for (const range of viewSelection.getRanges()) {\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\n }\n }\n\n // Set carret after the formula\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\n writer.setSelection(position);\n } else {\n const img = core.editionProperties.temporalImage;\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\n\n // Insert the new and remove the old one\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\n\n // If the given MathML is empty, don't insert a new formula.\n if (mathml) {\n this.editorObject.model.insertObject(modelElementNew, position);\n }\n writer.remove(modelElementOld);\n }\n\n // eslint-disable-next-line consistent-return\n return modelElementNew;\n });\n }\n\n /**\n * Finds the text node corresponding to given DOM text element.\n * @param {element} viewElement Element to find corresponding text node of.\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\n */\n findText(viewElement) {\n // eslint-disable-line consistent-return\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\n let pivot = viewElement;\n let element;\n while (!element) {\n element = this.editorObject.editing.mapper.toModelElement(\n this.editorObject.editing.view.domConverter.domToView(pivot),\n );\n pivot = pivot.parentElement;\n }\n\n // Navigate through all the subtree under `pivot` in order to find the correct text node\n const range = this.editorObject.model.createRangeIn(element);\n const descendants = Array.from(range.getItems());\n for (const node of descendants) {\n let viewElementData = viewElement.data;\n if (viewElement.nodeType === 3) {\n // Remove invisible white spaces\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\n }\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\n return node.textNode;\n }\n }\n }\n\n /** @inheritdoc */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n // eslint-disable-line no-unused-vars\n const returnObject = {};\n\n let mathmlOrigin;\n if (!mathml) {\n this.insertMathml(\"\");\n } else if (this.core.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n\n this.editorObject.model.change((writer) => {\n const { latexRange } = this.core.editionProperties;\n\n const startNode = this.findText(latexRange.startContainer);\n const endNode = this.findText(latexRange.endContainer);\n\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\n\n let range = writer.createRange(startPosition, endPosition);\n\n // When Latex is next to image/formula.\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\n // Get the position of the latex to be replaced.\n const latexEdited = `$$${Latex.getLatexFromMathML(\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\n )}$$`;\n let data = latexRange.startContainer.data;\n\n // Remove invisible characters.\n data = data.replaceAll(String.fromCharCode(8288), \"\");\n\n // Get to the start of the latex we are editing.\n const offset = data.indexOf(latexEdited);\n const dataOffset = data.substring(offset);\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\n const substring = dataOffset.substr(0, second$);\n data = data.replace(substring, \"\");\n\n if (!data) {\n startPosition = writer.createPositionBefore(startNode);\n range = startNode;\n } else {\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\n range = writer.createRange(startPosition, endPosition);\n }\n }\n\n writer.remove(range);\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\n });\n } else {\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\n try {\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\n windowTarget.document,\n );\n } catch (e) {\n const x = e.toString();\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\n this.core.modalDialog.cancelAction();\n }\n }\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.core.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\n We probably should add it here as well, but we should look further into how */\n // this.editorObject.fire('change');\n\n // Remove temporal image of inserted formula\n this.core.editionProperties.temporalImage = null;\n\n return returnObject;\n }\n\n /**\n * Function called when the content submits an action.\n */\n notifyWindowClosed() {\n this.editorObject.editing.view.focus();\n }\n}\n","/* eslint-disable max-classes-per-file */\nimport { Command } from \"ckeditor5/src/core.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\n/**\n * Command for opening the MathType editor\n */\nexport class MathTypeCommand extends Command {\n execute(options = {}) {\n // Check we get a valid integration\n // eslint-disable-next-line no-prototype-builtins\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\n }\n\n // Save the integration instance as a property of the command.\n this.integration = options.integration;\n\n // Set custom editor or disable it\n this.setEditor();\n\n // Open the editor\n this.openEditor();\n }\n\n /**\n * Sets the appropriate custom editor, if any, or disables them.\n */\n setEditor() {\n // It's possible that a custom editor was last used.\n // We need to disable it to avoid wrong behaviors.\n this.integration.core.getCustomEditors().disable();\n }\n\n /**\n * Checks whether we are editing an existing formula or a new one and opens the editor.\n */\n openEditor() {\n this.integration.core.editionProperties.dbclick = false;\n const image = this._getSelectedImage();\n if (\n typeof image !== \"undefined\" &&\n image !== null &&\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\n ) {\n this.integration.core.editionProperties.temporalImage = image;\n this.integration.openExistingFormulaEditor();\n } else {\n this.integration.openNewFormulaEditor();\n }\n }\n\n /**\n * Gets the currently selected formula image\n * @returns {Element} selected image, if any, undefined otherwise\n */\n _getSelectedImage() {\n const { selection } = this.editor.editing.view.document;\n\n // If we can not extract the formula, fall back to default behavior.\n if (selection.isCollapsed || selection.rangeCount !== 1) {\n return;\n }\n\n // Look for the wrapping the formula and then for the inside\n\n const range = selection.getFirstRange();\n\n let image;\n\n for (const span of range) {\n if (span.item.name !== \"span\") {\n return;\n }\n image = span.item.getChild(0);\n break;\n }\n\n if (!image) {\n return;\n }\n\n // eslint-disable-next-line consistent-return\n return this.editor.editing.view.domConverter.mapViewToDom(image);\n }\n}\n\n/**\n * Command for opening the ChemType editor\n */\nexport class ChemTypeCommand extends MathTypeCommand {\n setEditor() {\n this.integration.core.getCustomEditors().enable(\"chemistry\");\n }\n}\n","// CKEditor imports\nimport { Plugin } from \"ckeditor5/src/core.js\";\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\n\n// MathType API imports\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\n\n// Local imports\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\n\nimport packageInfo from \"../package.json\";\n\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\n\nexport default class MathType extends Plugin {\n static get requires() {\n return [Widget];\n }\n\n static get pluginName() {\n return \"MathType\";\n }\n\n init() {\n // Create the MathType API Integration object\n const integration = this._addIntegration();\n currentInstance = integration;\n\n // Add the MathType and ChemType commands to the editor\n this._addCommands();\n\n // Add the buttons for MathType and ChemType\n this._addViews(integration);\n\n // Registers the element in the schema\n this._addSchema();\n\n // Add the downcast and upcast converters\n this._addConverters(integration);\n\n // Expose the WirisPlugin variable to the window\n this._exposeWiris();\n }\n\n /**\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\n */\n destroy() {\n // eslint-disable-line class-methods-use-this\n currentInstance?.destroy();\n }\n\n /**\n * Create the MathType API Integration object\n * @returns {CKEditor5Integration} the integration object\n */\n _addIntegration() {\n const { editor } = this;\n\n /**\n * Integration model constructor attributes.\n * @type {integrationModelProperties}\n */\n const integrationProperties = {};\n integrationProperties.environment = {};\n integrationProperties.environment.editor = \"CKEditor5\";\n integrationProperties.environment.editorVersion = \"5.x\";\n integrationProperties.version = packageInfo.version;\n integrationProperties.editorObject = editor;\n integrationProperties.serviceProviderProperties = {};\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\n integrationProperties.serviceProviderProperties.server = \"java\";\n integrationProperties.target = editor.sourceElement;\n integrationProperties.scriptName = \"bundle.js\";\n integrationProperties.managesLanguage = true;\n // etc\n\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\n let integration;\n if (integrationProperties.target) {\n // Instance of the integration associated to this editor instance\n integration = new CKEditor5Integration(integrationProperties);\n integration.init();\n integration.listeners.fire(\"onTargetReady\", {});\n\n integration.checkElement();\n\n this.listenTo(\n editor.editing.view.document,\n \"click\",\n (evt, data) => {\n // Is Double-click\n if (data.domEvent.detail === 2) {\n integration.doubleClickHandler(data.domTarget, data.domEvent);\n evt.stop();\n }\n },\n { priority: \"highest\" },\n );\n }\n\n return integration;\n }\n\n /**\n * Add the MathType and ChemType commands to the editor\n */\n _addCommands() {\n const { editor } = this;\n\n // Add command to open the formula editor\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\n\n // Add command to open the chemistry formula editor\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\n }\n\n /**\n * Add the buttons for MathType and ChemType\n * @param {CKEditor5Integration} integration the integration object\n */\n _addViews(integration) {\n const { editor } = this;\n\n // Check if MathType editor is enabled\n if (Configuration.get(\"editorEnabled\")) {\n // Add button for the formula editor\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\n view.set({\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\n icon: mathIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"MathType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Check if ChemType editor is enabled\n if (Configuration.get(\"chemEnabled\")) {\n // Add button for the chemistry formula editor\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\n\n view.set({\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\n icon: chemIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"ChemType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Observer for the Double-click event\n editor.editing.view.addObserver(ClickObserver);\n }\n\n /**\n * Registers the element in the schema\n */\n _addSchema() {\n const { schema } = this.editor.model;\n\n schema.register(\"mathml\", {\n inheritAllFrom: \"$inlineObject\",\n allowAttributes: [\"formula\", \"htmlContent\"],\n });\n }\n\n /**\n * Add the downcast and upcast converters\n */\n _addConverters(integration) {\n const { editor } = this;\n\n // Editing view -> Model\n editor.conversion.for(\"upcast\").elementToElement({\n view: {\n name: \"span\",\n classes: \"ck-math-widget\",\n },\n model: (viewElement, { writer: modelWriter }) => {\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\n return modelWriter.createElement(\"mathml\", {\n formula,\n });\n },\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // When element was already consumed then skip it.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // If we encounter any with a LaTeX annotation inside,\n // convert it into a \"$$...$$\" string.\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\n\n // Get the formula of the (which is all its children).\n const processor = new XmlDataProcessor(editor.editing.view.document);\n\n // Only god knows why the following line makes viewItem lose all of its children,\n // so we obtain isLatex before doing this because we need viewItem's children for that.\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\n\n // and obtain the attributes of too!\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n\n // We process the document fragment\n let formula = processor.toData(viewDocumentFragment) || \"\";\n\n // And obtain the complete formula\n formula = Util.htmlSanitize(`${formula}`);\n\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\n\n /* Model node that contains what's going to actually be inserted. This can be either:\n - A element with a formula attribute set to the given formula, or\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\n const modelNode = isLatex\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\n : writer.createElement(\"mathml\", { formula });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\n \"element:img\",\n (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // Only upcast when is wiris formula\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\n return;\n }\n\n // The following code ensures that the element's name, attributes, and classes are consumed.\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\n\n // Check if we can consume the element name.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // Consume the name, attributes, and classes so nothing else processes it.\n consumable.consume(viewItem, { name: true });\n for (const attrName of viewItem.getAttributes()) {\n consumable.consume(viewItem, { attributes: [attrName] });\n }\n\n for (const className of viewItem.getClassNames()) {\n consumable.consume(viewItem, { classes: [className] });\n }\n\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n const htmlContent = Util.htmlSanitize(``);\n\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n },\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\n { priority: \"high\" },\n );\n\n /**\n * Whether the given view element has a LaTeX annotation element.\n * @param {*} math\n * @returns {bool}\n */\n function mathIsLatex(math) {\n const semantics = math.getChild(0);\n if (!semantics || semantics.name !== \"semantics\") return false;\n for (const child of semantics.getChildren()) {\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\n return true;\n }\n }\n return false;\n }\n\n function createViewWidget(modelItem, { writer: viewWriter }) {\n const widgetElement = viewWriter.createContainerElement(\"span\", {\n class: \"ck-math-widget\",\n });\n\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\n\n if (mathUIElement) {\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\n }\n\n return toWidget(widgetElement, viewWriter);\n }\n\n function createViewImage(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n const formula = modelItem.getAttribute(\"formula\");\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\n\n if (!formula && !htmlContent) {\n return null;\n }\n\n let imgElement = null;\n\n if (htmlContent) {\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\n } else if (formula) {\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\n\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\n\n // Add HTML element () to model\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\n }\n\n /* Although we use the HtmlDataProcessor to obtain the attributes,\n * we must create a new EmptyElement which is independent of the\n * DataProcessor being used by this editor instance\n */\n if (imgElement) {\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\n renderUnsafeAttributes: [\"src\"],\n });\n }\n\n return null;\n }\n\n // Model -> Editing view\n editor.conversion.for(\"editingDowncast\").elementToElement({\n model: \"mathml\",\n view: createViewWidget,\n });\n\n // Model -> Data view\n editor.conversion.for(\"dataDowncast\").elementToElement({\n model: \"mathml\",\n view: createDataString, // eslint-disable-line no-use-before-define\n });\n\n /**\n * Makes a copy of the given view node.\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\n * @returns {module:engine/view/node~Node} Copy of the node.\n */\n function clone(viewWriter, sourceNode) {\n if (sourceNode.is(\"text\")) {\n return viewWriter.createText(sourceNode.data);\n }\n if (sourceNode.is(\"element\")) {\n if (sourceNode.is(\"emptyElement\")) {\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\n }\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\n for (const child of sourceNode.getChildren()) {\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\n }\n return element;\n }\n\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\n }\n\n function createDataString(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n // Load img element\n const mathString =\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\n\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\n\n return clone(viewWriter, sourceMathElement);\n }\n\n // This stops the view selection getting into the s and messing up caret movement\n editor.editing.mapper.on(\n \"viewToModelPosition\",\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\n );\n\n // Keep a reference to the original get and set function.\n const { get, set } = editor.data;\n\n /**\n * Hack to transform $$latex$$ into in editor.getData()'s output.\n */\n editor.data.on(\n \"get\",\n (e) => {\n const output = e.return;\n const parsedResult = Parser.endParse(output);\n\n // Cleans all the semantics tag for safexml\n // including the handwritten data points\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\n },\n { priority: \"low\" },\n );\n\n /**\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\n */\n editor.data.on(\n \"set\",\n (e, args) => {\n // Retrieve the data to be set on the CKEditor.\n let modifiedData = args[0];\n // Regex to find all mathml formulas.\n const regexp = /(]*>)|()/gm;\n const formulas = [];\n let formula;\n\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\n while ((formula = regexp.exec(modifiedData)) !== null) {\n formulas.push(formula[0]);\n }\n\n // Loop to find LaTeX and formula images and replace the MathML for the both.\n formulas.forEach((formula) => {\n if (formula.includes('encoding=\"LaTeX\"')) {\n // LaTeX found.\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\n modifiedData = modifiedData.replace(formula, latex);\n } else if (formula.includes(\"\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n"; +var mathIcon = "\n\n\n\n\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n"; -var chemIcon = "\r\n\r\n\r\n\r\n\r\n\r\n"; +var chemIcon = "\n\n\n\n\n\n"; -var version = "8.14.0"; +var version = "8.13.4"; var packageInfo = { version: version}; diff --git a/packages/ckeditor5/dist/index.js.map b/packages/ckeditor5/dist/index.js.map index 64c2f4288..5bee29146 100644 --- a/packages/ckeditor5/dist/index.js.map +++ b/packages/ckeditor5/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../src/integration.js","../src/commands.js","../src/plugin.js"],"sourcesContent":["import IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Telemeter from \"@wiris/mathtype-html-integration-devkit/src/telemeter.js\";\r\n\r\n/**\r\n * This class represents the MathType integration for CKEditor5.\r\n * @extends {IntegrationModel}\r\n */\r\nexport default class CKEditor5Integration extends IntegrationModel {\r\n constructor(ckeditorIntegrationModelProperties) {\r\n const editor = ckeditorIntegrationModelProperties.editorObject;\r\n\r\n if (typeof editor.config !== \"undefined\" && typeof editor.config.get(\"mathTypeParameters\") !== \"undefined\") {\r\n ckeditorIntegrationModelProperties.integrationParameters = editor.config.get(\"mathTypeParameters\");\r\n }\r\n /**\r\n * CKEditor5 Integration.\r\n *\r\n * @param {integrationModelProperties} integrationModelAttributes\r\n */\r\n super(ckeditorIntegrationModelProperties);\r\n\r\n /**\r\n * Folder name used for the integration inside CKEditor plugins folder.\r\n */\r\n this.integrationFolderName = \"ckeditor_wiris\";\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @returns {string} - The CKEditor instance language.\r\n * @override\r\n */\r\n getLanguage() {\r\n // Returns the CKEDitor instance language taking into account that the language can be an object.\r\n // Try to get editorParameters.language, fail silently otherwise\r\n try {\r\n return this.editorParameters.language;\r\n } catch (e) {}\r\n const languageObject = this.editorObject.config.get(\"language\");\r\n if (languageObject != null) {\r\n if (typeof languageObject === \"object\") {\r\n if (Object.prototype.hasOwnProperty.call(languageObject, \"ui\")) {\r\n return languageObject.ui;\r\n }\r\n return this.editorObject.locale.uiLanguage;\r\n }\r\n return languageObject;\r\n }\r\n return super.getLanguage();\r\n }\r\n\r\n /**\r\n * Adds callbacks to the following CKEditor listeners:\r\n * - 'focus' - updates the current instance.\r\n * - 'contentDom' - adds 'doubleclick' callback.\r\n * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.\r\n * - 'setData' - parses the data converting MathML into images.\r\n * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.\r\n * - 'getData' - parses the data converting images into selected save mode (MathML by default).\r\n * - 'mode' - recalculates the active element.\r\n */\r\n addEditorListeners() {\r\n const editor = this.editorObject;\r\n\r\n if (typeof editor.config.wirislistenersdisabled === \"undefined\" || !editor.config.wirislistenersdisabled) {\r\n this.checkElement();\r\n }\r\n }\r\n\r\n /**\r\n * Checks the current container and assign events in case that it doesn't have them.\r\n * CKEditor replaces several times the element element during its execution,\r\n * so we must assign the events again to editor element.\r\n */\r\n checkElement() {\r\n const editor = this.editorObject;\r\n const newElement = editor.sourceElement;\r\n\r\n // If the element wasn't treated, add the events.\r\n if (!newElement.wirisActive) {\r\n this.setTarget(newElement);\r\n this.addEvents();\r\n // Set the element as treated\r\n newElement.wirisActive = true;\r\n }\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n * @param {HTMLElement} element - HTMLElement target.\r\n * @param {MouseEvent} event - event which trigger the handler.\r\n */\r\n doubleClickHandler(element, event) {\r\n this.core.editionProperties.dbclick = true;\r\n if (this.editorObject.isReadOnly === false) {\r\n if (element.nodeName.toLowerCase() === \"img\") {\r\n if (Util.containsClass(element, Configuration.get(\"imageClassName\"))) {\r\n // Some plugins (image2, image) open a dialog on Double-click. On formulas\r\n // doubleclick event ends here.\r\n if (typeof event.stopPropagation !== \"undefined\") {\r\n // old I.E compatibility.\r\n event.stopPropagation();\r\n } else {\r\n event.returnValue = false;\r\n }\r\n this.core.getCustomEditors().disable();\r\n const customEditorAttr = element.getAttribute(Configuration.get(\"imageCustomEditorName\"));\r\n if (customEditorAttr) {\r\n this.core.getCustomEditors().enable(customEditorAttr);\r\n }\r\n this.core.editionProperties.temporalImage = element;\r\n this.openExistingFormulaEditor();\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n static getCorePath() {\r\n return null; // TODO\r\n }\r\n\r\n /** @inheritdoc */\r\n callbackFunction() {\r\n super.callbackFunction();\r\n this.addEditorListeners();\r\n }\r\n\r\n openNewFormulaEditor() {\r\n // Store the editor selection as it will be lost upon opening the modal\r\n this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;\r\n\r\n // Focus on the selected editor when multiple editor instances are present\r\n WirisPlugin.currentInstance = this;\r\n\r\n return super.openNewFormulaEditor();\r\n }\r\n\r\n /**\r\n * Replaces old formula with new MathML or inserts it in caret position if new\r\n * @param {String} mathml MathML to update old one or insert\r\n * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image\r\n */\r\n insertMathml(mathml) {\r\n // This returns the value returned by the callback function (writer => {...})\r\n return this.editorObject.model.change((writer) => {\r\n const core = this.getCore();\r\n const selection = this.editorObject.model.document.selection;\r\n\r\n const modelElementNew = writer.createElement(\"mathml\", {\r\n formula: mathml,\r\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\r\n });\r\n\r\n // Obtain the DOM object corresponding to the formula\r\n if (core.editionProperties.isNewElement) {\r\n // Don't bother inserting anything at all if the MathML is empty.\r\n if (!mathml) return;\r\n\r\n const viewSelection =\r\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\r\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\r\n\r\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\r\n\r\n // Remove selection\r\n if (!viewSelection.isCollapsed) {\r\n for (const range of viewSelection.getRanges()) {\r\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\r\n }\r\n }\r\n\r\n // Set carret after the formula\r\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\r\n writer.setSelection(position);\r\n } else {\r\n const img = core.editionProperties.temporalImage;\r\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\r\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\r\n\r\n // Insert the new and remove the old one\r\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\r\n\r\n // If the given MathML is empty, don't insert a new formula.\r\n if (mathml) {\r\n this.editorObject.model.insertObject(modelElementNew, position);\r\n }\r\n writer.remove(modelElementOld);\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return modelElementNew;\r\n });\r\n }\r\n\r\n /**\r\n * Finds the text node corresponding to given DOM text element.\r\n * @param {element} viewElement Element to find corresponding text node of.\r\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\r\n */\r\n findText(viewElement) {\r\n // eslint-disable-line consistent-return\r\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\r\n let pivot = viewElement;\r\n let element;\r\n while (!element) {\r\n element = this.editorObject.editing.mapper.toModelElement(\r\n this.editorObject.editing.view.domConverter.domToView(pivot),\r\n );\r\n pivot = pivot.parentElement;\r\n }\r\n\r\n // Navigate through all the subtree under `pivot` in order to find the correct text node\r\n const range = this.editorObject.model.createRangeIn(element);\r\n const descendants = Array.from(range.getItems());\r\n for (const node of descendants) {\r\n let viewElementData = viewElement.data;\r\n if (viewElement.nodeType === 3) {\r\n // Remove invisible white spaces\r\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\r\n }\r\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\r\n return node.textNode;\r\n }\r\n }\r\n }\r\n\r\n /** @inheritdoc */\r\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\r\n // eslint-disable-line no-unused-vars\r\n const returnObject = {};\r\n\r\n let mathmlOrigin;\r\n if (!mathml) {\r\n this.insertMathml(\"\");\r\n } else if (this.core.editMode === \"latex\") {\r\n returnObject.latex = Latex.getLatexFromMathML(mathml);\r\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\r\n\r\n this.editorObject.model.change((writer) => {\r\n const { latexRange } = this.core.editionProperties;\r\n\r\n const startNode = this.findText(latexRange.startContainer);\r\n const endNode = this.findText(latexRange.endContainer);\r\n\r\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\r\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\r\n\r\n let range = writer.createRange(startPosition, endPosition);\r\n\r\n // When Latex is next to image/formula.\r\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\r\n // Get the position of the latex to be replaced.\r\n const latexEdited = `$$${Latex.getLatexFromMathML(\r\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\r\n )}$$`;\r\n let data = latexRange.startContainer.data;\r\n\r\n // Remove invisible characters.\r\n data = data.replaceAll(String.fromCharCode(8288), \"\");\r\n\r\n // Get to the start of the latex we are editing.\r\n const offset = data.indexOf(latexEdited);\r\n const dataOffset = data.substring(offset);\r\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\r\n const substring = dataOffset.substr(0, second$);\r\n data = data.replace(substring, \"\");\r\n\r\n if (!data) {\r\n startPosition = writer.createPositionBefore(startNode);\r\n range = startNode;\r\n } else {\r\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\r\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\r\n range = writer.createRange(startPosition, endPosition);\r\n }\r\n }\r\n\r\n writer.remove(range);\r\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\r\n });\r\n } else {\r\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\r\n try {\r\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\r\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\r\n windowTarget.document,\r\n );\r\n } catch (e) {\r\n const x = e.toString();\r\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\r\n this.core.modalDialog.cancelAction();\r\n }\r\n }\r\n }\r\n\r\n // Build the telemeter payload separated to delete null/undefined entries.\r\n const payload = {\r\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\r\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\r\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\r\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\r\n toolbar: this.core.modalDialog.contentManager.toolbar,\r\n size: mathml?.length,\r\n };\r\n\r\n // Remove desired null keys.\r\n Object.keys(payload).forEach((key) => {\r\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\r\n });\r\n\r\n // Call Telemetry service to track the event.\r\n try {\r\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\r\n ...payload,\r\n });\r\n } catch (error) {\r\n console.error(\"Error tracking INSERTED_FORMULA\", error);\r\n }\r\n\r\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\r\n We probably should add it here as well, but we should look further into how */\r\n // this.editorObject.fire('change');\r\n\r\n // Remove temporal image of inserted formula\r\n this.core.editionProperties.temporalImage = null;\r\n\r\n return returnObject;\r\n }\r\n\r\n /**\r\n * Function called when the content submits an action.\r\n */\r\n notifyWindowClosed() {\r\n this.editorObject.editing.view.focus();\r\n }\r\n}\r\n","/* eslint-disable max-classes-per-file */\r\nimport { Command } from \"ckeditor5/src/core.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\n/**\r\n * Command for opening the MathType editor\r\n */\r\nexport class MathTypeCommand extends Command {\r\n execute(options = {}) {\r\n // Check we get a valid integration\r\n // eslint-disable-next-line no-prototype-builtins\r\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\r\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\r\n }\r\n\r\n // Save the integration instance as a property of the command.\r\n this.integration = options.integration;\r\n\r\n // Set custom editor or disable it\r\n this.setEditor();\r\n\r\n // Open the editor\r\n this.openEditor();\r\n }\r\n\r\n /**\r\n * Sets the appropriate custom editor, if any, or disables them.\r\n */\r\n setEditor() {\r\n // It's possible that a custom editor was last used.\r\n // We need to disable it to avoid wrong behaviors.\r\n this.integration.core.getCustomEditors().disable();\r\n }\r\n\r\n /**\r\n * Checks whether we are editing an existing formula or a new one and opens the editor.\r\n */\r\n openEditor() {\r\n this.integration.core.editionProperties.dbclick = false;\r\n const image = this._getSelectedImage();\r\n if (\r\n typeof image !== \"undefined\" &&\r\n image !== null &&\r\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\r\n ) {\r\n this.integration.core.editionProperties.temporalImage = image;\r\n this.integration.openExistingFormulaEditor();\r\n } else {\r\n this.integration.openNewFormulaEditor();\r\n }\r\n }\r\n\r\n /**\r\n * Gets the currently selected formula image\r\n * @returns {Element} selected image, if any, undefined otherwise\r\n */\r\n _getSelectedImage() {\r\n const { selection } = this.editor.editing.view.document;\r\n\r\n // If we can not extract the formula, fall back to default behavior.\r\n if (selection.isCollapsed || selection.rangeCount !== 1) {\r\n return;\r\n }\r\n\r\n // Look for the wrapping the formula and then for the inside\r\n\r\n const range = selection.getFirstRange();\r\n\r\n let image;\r\n\r\n for (const span of range) {\r\n if (span.item.name !== \"span\") {\r\n return;\r\n }\r\n image = span.item.getChild(0);\r\n break;\r\n }\r\n\r\n if (!image) {\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line consistent-return\r\n return this.editor.editing.view.domConverter.mapViewToDom(image);\r\n }\r\n}\r\n\r\n/**\r\n * Command for opening the ChemType editor\r\n */\r\nexport class ChemTypeCommand extends MathTypeCommand {\r\n setEditor() {\r\n this.integration.core.getCustomEditors().enable(\"chemistry\");\r\n }\r\n}\r\n","// CKEditor imports\r\nimport { Plugin } from \"ckeditor5/src/core.js\";\r\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\r\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\r\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\r\n\r\n// MathType API imports\r\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\r\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\r\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\r\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\r\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\r\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\r\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\r\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\r\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\r\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\r\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\r\n\r\n// Local imports\r\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\r\nimport CKEditor5Integration from \"./integration.js\";\r\n\r\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\r\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\r\n\r\nimport packageInfo from \"../package.json\";\r\n\r\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\r\n\r\nexport default class MathType extends Plugin {\r\n static get requires() {\r\n return [Widget];\r\n }\r\n\r\n static get pluginName() {\r\n return \"MathType\";\r\n }\r\n\r\n init() {\r\n // Create the MathType API Integration object\r\n const integration = this._addIntegration();\r\n currentInstance = integration;\r\n\r\n // Add the MathType and ChemType commands to the editor\r\n this._addCommands();\r\n\r\n // Add the buttons for MathType and ChemType\r\n this._addViews(integration);\r\n\r\n // Registers the element in the schema\r\n this._addSchema();\r\n\r\n // Add the downcast and upcast converters\r\n this._addConverters(integration);\r\n\r\n // Expose the WirisPlugin variable to the window\r\n this._exposeWiris();\r\n }\r\n\r\n /**\r\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\r\n */\r\n destroy() {\r\n // eslint-disable-line class-methods-use-this\r\n currentInstance?.destroy();\r\n }\r\n\r\n /**\r\n * Create the MathType API Integration object\r\n * @returns {CKEditor5Integration} the integration object\r\n */\r\n _addIntegration() {\r\n const { editor } = this;\r\n\r\n /**\r\n * Integration model constructor attributes.\r\n * @type {integrationModelProperties}\r\n */\r\n const integrationProperties = {};\r\n integrationProperties.environment = {};\r\n integrationProperties.environment.editor = \"CKEditor5\";\r\n integrationProperties.environment.editorVersion = \"5.x\";\r\n integrationProperties.version = packageInfo.version;\r\n integrationProperties.editorObject = editor;\r\n integrationProperties.serviceProviderProperties = {};\r\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\r\n integrationProperties.serviceProviderProperties.server = \"java\";\r\n integrationProperties.target = editor.sourceElement;\r\n integrationProperties.scriptName = \"bundle.js\";\r\n integrationProperties.managesLanguage = true;\r\n // etc\r\n\r\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\r\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\r\n let integration;\r\n if (integrationProperties.target) {\r\n // Instance of the integration associated to this editor instance\r\n integration = new CKEditor5Integration(integrationProperties);\r\n integration.init();\r\n integration.listeners.fire(\"onTargetReady\", {});\r\n\r\n integration.checkElement();\r\n\r\n this.listenTo(\r\n editor.editing.view.document,\r\n \"click\",\r\n (evt, data) => {\r\n // Is Double-click\r\n if (data.domEvent.detail === 2) {\r\n integration.doubleClickHandler(data.domTarget, data.domEvent);\r\n evt.stop();\r\n }\r\n },\r\n { priority: \"highest\" },\r\n );\r\n }\r\n\r\n return integration;\r\n }\r\n\r\n /**\r\n * Add the MathType and ChemType commands to the editor\r\n */\r\n _addCommands() {\r\n const { editor } = this;\r\n\r\n // Add command to open the formula editor\r\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\r\n\r\n // Add command to open the chemistry formula editor\r\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\r\n }\r\n\r\n /**\r\n * Add the buttons for MathType and ChemType\r\n * @param {CKEditor5Integration} integration the integration object\r\n */\r\n _addViews(integration) {\r\n const { editor } = this;\r\n\r\n // Check if MathType editor is enabled\r\n if (Configuration.get(\"editorEnabled\")) {\r\n // Add button for the formula editor\r\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\r\n view.set({\r\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\r\n icon: mathIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"MathType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Check if ChemType editor is enabled\r\n if (Configuration.get(\"chemEnabled\")) {\r\n // Add button for the chemistry formula editor\r\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\r\n const view = new ButtonView(locale);\r\n\r\n // View is enabled iff command is enabled\r\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\r\n\r\n view.set({\r\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\r\n icon: chemIcon,\r\n tooltip: true,\r\n });\r\n\r\n // Callback executed once the image is clicked.\r\n view.on(\"execute\", () => {\r\n editor.execute(\"ChemType\", {\r\n integration, // Pass integration as parameter\r\n });\r\n });\r\n\r\n return view;\r\n });\r\n }\r\n\r\n // Observer for the Double-click event\r\n editor.editing.view.addObserver(ClickObserver);\r\n }\r\n\r\n /**\r\n * Registers the element in the schema\r\n */\r\n _addSchema() {\r\n const { schema } = this.editor.model;\r\n\r\n schema.register(\"mathml\", {\r\n inheritAllFrom: \"$inlineObject\",\r\n allowAttributes: [\"formula\", \"htmlContent\"],\r\n });\r\n }\r\n\r\n /**\r\n * Add the downcast and upcast converters\r\n */\r\n _addConverters(integration) {\r\n const { editor } = this;\r\n\r\n // Editing view -> Model\r\n editor.conversion.for(\"upcast\").elementToElement({\r\n view: {\r\n name: \"span\",\r\n classes: \"ck-math-widget\",\r\n },\r\n model: (viewElement, { writer: modelWriter }) => {\r\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\r\n return modelWriter.createElement(\"mathml\", {\r\n formula,\r\n });\r\n },\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // When element was already consumed then skip it.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // If we encounter any with a LaTeX annotation inside,\r\n // convert it into a \"$$...$$\" string.\r\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\r\n\r\n // Get the formula of the (which is all its children).\r\n const processor = new XmlDataProcessor(editor.editing.view.document);\r\n\r\n // Only god knows why the following line makes viewItem lose all of its children,\r\n // so we obtain isLatex before doing this because we need viewItem's children for that.\r\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\r\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\r\n\r\n // and obtain the attributes of too!\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n\r\n // We process the document fragment\r\n let formula = processor.toData(viewDocumentFragment) || \"\";\r\n\r\n // And obtain the complete formula\r\n formula = Util.htmlSanitize(`${formula}`);\r\n\r\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\r\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\r\n\r\n /* Model node that contains what's going to actually be inserted. This can be either:\r\n - A element with a formula attribute set to the given formula, or\r\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\r\n const modelNode = isLatex\r\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\r\n : writer.createElement(\"mathml\", { formula });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n });\r\n\r\n // Data view -> Model\r\n editor.data.upcastDispatcher.on(\r\n \"element:img\",\r\n (evt, data, conversionApi) => {\r\n const { consumable, writer } = conversionApi;\r\n const { viewItem } = data;\r\n\r\n // Only upcast when is wiris formula\r\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\r\n return;\r\n }\r\n\r\n // The following code ensures that the element's name, attributes, and classes are consumed.\r\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\r\n\r\n // Check if we can consume the element name.\r\n if (!consumable.test(viewItem, { name: true })) {\r\n return;\r\n }\r\n\r\n // Consume the name, attributes, and classes so nothing else processes it.\r\n consumable.consume(viewItem, { name: true });\r\n for (const attrName of viewItem.getAttributes()) {\r\n consumable.consume(viewItem, { attributes: [attrName] });\r\n }\r\n\r\n for (const className of viewItem.getClassNames()) {\r\n consumable.consume(viewItem, { classes: [className] });\r\n }\r\n\r\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\r\n const htmlContent = Util.htmlSanitize(``);\r\n\r\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\r\n\r\n // Find allowed parent for element that we are going to insert.\r\n // If current parent does not allow to insert element but one of the ancestors does\r\n // then split nodes to allowed parent.\r\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\r\n\r\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\r\n if (!splitResult) {\r\n return;\r\n }\r\n\r\n // Insert element on allowed position.\r\n conversionApi.writer.insert(modelNode, splitResult.position);\r\n\r\n // Consume appropriate value from consumable values list.\r\n consumable.consume(viewItem, { name: true });\r\n\r\n const parts = conversionApi.getSplitParts(modelNode);\r\n\r\n // Set conversion result range.\r\n data.modelRange = writer.createRange(\r\n conversionApi.writer.createPositionBefore(modelNode),\r\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\r\n );\r\n\r\n // Now we need to check where the `modelCursor` should be.\r\n if (splitResult.cursorParent) {\r\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\r\n //\r\n // before: foo[]\r\n // after: foo[]\r\n\r\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\r\n } else {\r\n // Otherwise just continue after inserted element.\r\n data.modelCursor = data.modelRange.end;\r\n }\r\n },\r\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\r\n { priority: \"high\" },\r\n );\r\n\r\n /**\r\n * Whether the given view element has a LaTeX annotation element.\r\n * @param {*} math\r\n * @returns {bool}\r\n */\r\n function mathIsLatex(math) {\r\n const semantics = math.getChild(0);\r\n if (!semantics || semantics.name !== \"semantics\") return false;\r\n for (const child of semantics.getChildren()) {\r\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n function createViewWidget(modelItem, { writer: viewWriter }) {\r\n const widgetElement = viewWriter.createContainerElement(\"span\", {\r\n class: \"ck-math-widget\",\r\n });\r\n\r\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\r\n\r\n if (mathUIElement) {\r\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\r\n }\r\n\r\n return toWidget(widgetElement, viewWriter);\r\n }\r\n\r\n function createViewImage(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n const formula = modelItem.getAttribute(\"formula\");\r\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\r\n\r\n if (!formula && !htmlContent) {\r\n return null;\r\n }\r\n\r\n let imgElement = null;\r\n\r\n if (htmlContent) {\r\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\r\n } else if (formula) {\r\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\r\n\r\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\r\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\r\n\r\n // Add HTML element () to model\r\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\r\n }\r\n\r\n /* Although we use the HtmlDataProcessor to obtain the attributes,\r\n * we must create a new EmptyElement which is independent of the\r\n * DataProcessor being used by this editor instance\r\n */\r\n if (imgElement) {\r\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\r\n renderUnsafeAttributes: [\"src\"],\r\n });\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // Model -> Editing view\r\n editor.conversion.for(\"editingDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createViewWidget,\r\n });\r\n\r\n // Model -> Data view\r\n editor.conversion.for(\"dataDowncast\").elementToElement({\r\n model: \"mathml\",\r\n view: createDataString, // eslint-disable-line no-use-before-define\r\n });\r\n\r\n /**\r\n * Makes a copy of the given view node.\r\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\r\n * @returns {module:engine/view/node~Node} Copy of the node.\r\n */\r\n function clone(viewWriter, sourceNode) {\r\n if (sourceNode.is(\"text\")) {\r\n return viewWriter.createText(sourceNode.data);\r\n }\r\n if (sourceNode.is(\"element\")) {\r\n if (sourceNode.is(\"emptyElement\")) {\r\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\r\n }\r\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\r\n for (const child of sourceNode.getChildren()) {\r\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\r\n }\r\n return element;\r\n }\r\n\r\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\r\n }\r\n\r\n function createDataString(modelItem, { writer: viewWriter }) {\r\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\r\n\r\n // Load img element\r\n const mathString =\r\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\r\n\r\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\r\n\r\n return clone(viewWriter, sourceMathElement);\r\n }\r\n\r\n // This stops the view selection getting into the s and messing up caret movement\r\n editor.editing.mapper.on(\r\n \"viewToModelPosition\",\r\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\r\n );\r\n\r\n // Keep a reference to the original get and set function.\r\n const { get, set } = editor.data;\r\n\r\n /**\r\n * Hack to transform $$latex$$ into in editor.getData()'s output.\r\n */\r\n editor.data.on(\r\n \"get\",\r\n (e) => {\r\n const output = e.return;\r\n const parsedResult = Parser.endParse(output);\r\n\r\n // Cleans all the semantics tag for safexml\r\n // including the handwritten data points\r\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\r\n },\r\n { priority: \"low\" },\r\n );\r\n\r\n /**\r\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\r\n */\r\n editor.data.on(\r\n \"set\",\r\n (e, args) => {\r\n // Retrieve the data to be set on the CKEditor.\r\n let modifiedData = args[0];\r\n // Regex to find all mathml formulas.\r\n const regexp = /(]*>)|()/gm;\r\n const formulas = [];\r\n let formula;\r\n\r\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\r\n while ((formula = regexp.exec(modifiedData)) !== null) {\r\n formulas.push(formula[0]);\r\n }\r\n\r\n // Loop to find LaTeX and formula images and replace the MathML for the both.\r\n formulas.forEach((formula) => {\r\n if (formula.includes('encoding=\"LaTeX\"')) {\r\n // LaTeX found.\r\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\r\n modifiedData = modifiedData.replace(formula, latex);\r\n } else if (formula.includes(\" {...})\n return this.editorObject.model.change((writer) => {\n const core = this.getCore();\n const selection = this.editorObject.model.document.selection;\n\n const modelElementNew = writer.createElement(\"mathml\", {\n formula: mathml,\n ...Object.fromEntries(selection.getAttributes()), // To keep the format, such as style and font\n });\n\n // Obtain the DOM object corresponding to the formula\n if (core.editionProperties.isNewElement) {\n // Don't bother inserting anything at all if the MathML is empty.\n if (!mathml) return;\n\n const viewSelection =\n this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;\n const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());\n\n this.editorObject.model.insertObject(modelElementNew, modelPosition);\n\n // Remove selection\n if (!viewSelection.isCollapsed) {\n for (const range of viewSelection.getRanges()) {\n writer.remove(this.editorObject.editing.mapper.toModelRange(range));\n }\n }\n\n // Set carret after the formula\n const position = this.editorObject.model.createPositionAfter(modelElementNew);\n writer.setSelection(position);\n } else {\n const img = core.editionProperties.temporalImage;\n const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;\n const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);\n\n // Insert the new and remove the old one\n const position = this.editorObject.model.createPositionBefore(modelElementOld);\n\n // If the given MathML is empty, don't insert a new formula.\n if (mathml) {\n this.editorObject.model.insertObject(modelElementNew, position);\n }\n writer.remove(modelElementOld);\n }\n\n // eslint-disable-next-line consistent-return\n return modelElementNew;\n });\n }\n\n /**\n * Finds the text node corresponding to given DOM text element.\n * @param {element} viewElement Element to find corresponding text node of.\n * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.\n */\n findText(viewElement) {\n // eslint-disable-line consistent-return\n // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down\n let pivot = viewElement;\n let element;\n while (!element) {\n element = this.editorObject.editing.mapper.toModelElement(\n this.editorObject.editing.view.domConverter.domToView(pivot),\n );\n pivot = pivot.parentElement;\n }\n\n // Navigate through all the subtree under `pivot` in order to find the correct text node\n const range = this.editorObject.model.createRangeIn(element);\n const descendants = Array.from(range.getItems());\n for (const node of descendants) {\n let viewElementData = viewElement.data;\n if (viewElement.nodeType === 3) {\n // Remove invisible white spaces\n viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), \"\");\n }\n if (node.is(\"textProxy\") && node.data === viewElementData.replace(String.fromCharCode(160), \" \")) {\n return node.textNode;\n }\n }\n }\n\n /** @inheritdoc */\n insertFormula(focusElement, windowTarget, mathml, wirisProperties) {\n // eslint-disable-line no-unused-vars\n const returnObject = {};\n\n let mathmlOrigin;\n if (!mathml) {\n this.insertMathml(\"\");\n } else if (this.core.editMode === \"latex\") {\n returnObject.latex = Latex.getLatexFromMathML(mathml);\n returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);\n\n this.editorObject.model.change((writer) => {\n const { latexRange } = this.core.editionProperties;\n\n const startNode = this.findText(latexRange.startContainer);\n const endNode = this.findText(latexRange.endContainer);\n\n let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);\n let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);\n\n let range = writer.createRange(startPosition, endPosition);\n\n // When Latex is next to image/formula.\n if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {\n // Get the position of the latex to be replaced.\n const latexEdited = `$$${Latex.getLatexFromMathML(\n MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml),\n )}$$`;\n let data = latexRange.startContainer.data;\n\n // Remove invisible characters.\n data = data.replaceAll(String.fromCharCode(8288), \"\");\n\n // Get to the start of the latex we are editing.\n const offset = data.indexOf(latexEdited);\n const dataOffset = data.substring(offset);\n const second$ = dataOffset.substring(2).indexOf(\"$$\") + 4;\n const substring = dataOffset.substr(0, second$);\n data = data.replace(substring, \"\");\n\n if (!data) {\n startPosition = writer.createPositionBefore(startNode);\n range = startNode;\n } else {\n startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);\n endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);\n range = writer.createRange(startPosition, endPosition);\n }\n }\n\n writer.remove(range);\n writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);\n });\n } else {\n mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;\n try {\n returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(\n this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)),\n windowTarget.document,\n );\n } catch (e) {\n const x = e.toString();\n if (x.includes(\"CKEditorError: Cannot read property 'parent' of undefined\")) {\n this.core.modalDialog.cancelAction();\n }\n }\n }\n\n // Build the telemeter payload separated to delete null/undefined entries.\n const payload = {\n mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,\n mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,\n elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,\n editor_origin: null, // TODO read formula to find out whether it comes from Oxygen Desktop\n toolbar: this.core.modalDialog.contentManager.toolbar,\n size: mathml?.length,\n };\n\n // Remove desired null keys.\n Object.keys(payload).forEach((key) => {\n if (key === \"mathml_origin\" || key === \"editor_origin\") !payload[key] ? delete payload[key] : {};\n });\n\n // Call Telemetry service to track the event.\n try {\n Telemeter.telemeter.track(\"INSERTED_FORMULA\", {\n ...payload,\n });\n } catch (error) {\n console.error(\"Error tracking INSERTED_FORMULA\", error);\n }\n\n /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.\n We probably should add it here as well, but we should look further into how */\n // this.editorObject.fire('change');\n\n // Remove temporal image of inserted formula\n this.core.editionProperties.temporalImage = null;\n\n return returnObject;\n }\n\n /**\n * Function called when the content submits an action.\n */\n notifyWindowClosed() {\n this.editorObject.editing.view.focus();\n }\n}\n","/* eslint-disable max-classes-per-file */\nimport { Command } from \"ckeditor5/src/core.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\n/**\n * Command for opening the MathType editor\n */\nexport class MathTypeCommand extends Command {\n execute(options = {}) {\n // Check we get a valid integration\n // eslint-disable-next-line no-prototype-builtins\n if (!options.hasOwnProperty(\"integration\") || !(options.integration instanceof CKEditor5Integration)) {\n throw 'Must pass a valid CKEditor5Integration instance as attribute \"integration\" of options';\n }\n\n // Save the integration instance as a property of the command.\n this.integration = options.integration;\n\n // Set custom editor or disable it\n this.setEditor();\n\n // Open the editor\n this.openEditor();\n }\n\n /**\n * Sets the appropriate custom editor, if any, or disables them.\n */\n setEditor() {\n // It's possible that a custom editor was last used.\n // We need to disable it to avoid wrong behaviors.\n this.integration.core.getCustomEditors().disable();\n }\n\n /**\n * Checks whether we are editing an existing formula or a new one and opens the editor.\n */\n openEditor() {\n this.integration.core.editionProperties.dbclick = false;\n const image = this._getSelectedImage();\n if (\n typeof image !== \"undefined\" &&\n image !== null &&\n image.classList.contains(WirisPlugin.Configuration.get(\"imageClassName\"))\n ) {\n this.integration.core.editionProperties.temporalImage = image;\n this.integration.openExistingFormulaEditor();\n } else {\n this.integration.openNewFormulaEditor();\n }\n }\n\n /**\n * Gets the currently selected formula image\n * @returns {Element} selected image, if any, undefined otherwise\n */\n _getSelectedImage() {\n const { selection } = this.editor.editing.view.document;\n\n // If we can not extract the formula, fall back to default behavior.\n if (selection.isCollapsed || selection.rangeCount !== 1) {\n return;\n }\n\n // Look for the wrapping the formula and then for the inside\n\n const range = selection.getFirstRange();\n\n let image;\n\n for (const span of range) {\n if (span.item.name !== \"span\") {\n return;\n }\n image = span.item.getChild(0);\n break;\n }\n\n if (!image) {\n return;\n }\n\n // eslint-disable-next-line consistent-return\n return this.editor.editing.view.domConverter.mapViewToDom(image);\n }\n}\n\n/**\n * Command for opening the ChemType editor\n */\nexport class ChemTypeCommand extends MathTypeCommand {\n setEditor() {\n this.integration.core.getCustomEditors().enable(\"chemistry\");\n }\n}\n","// CKEditor imports\nimport { Plugin } from \"ckeditor5/src/core.js\";\nimport { ButtonView } from \"ckeditor5/src/ui.js\";\nimport { ClickObserver, HtmlDataProcessor, XmlDataProcessor, ViewUpcastWriter } from \"ckeditor5/src/engine.js\";\nimport { Widget, toWidget, viewToModelPositionOutsideModelElement } from \"ckeditor5/src/widget.js\";\n\n// MathType API imports\nimport IntegrationModel from \"@wiris/mathtype-html-integration-devkit/src/integrationmodel.js\";\nimport Core from \"@wiris/mathtype-html-integration-devkit/src/core.src.js\";\nimport Parser from \"@wiris/mathtype-html-integration-devkit/src/parser.js\";\nimport Util from \"@wiris/mathtype-html-integration-devkit/src/util.js\";\nimport Image from \"@wiris/mathtype-html-integration-devkit/src/image.js\";\nimport Configuration from \"@wiris/mathtype-html-integration-devkit/src/configuration.js\";\nimport Listeners from \"@wiris/mathtype-html-integration-devkit/src/listeners.js\";\nimport MathML from \"@wiris/mathtype-html-integration-devkit/src/mathml.js\";\nimport Latex from \"@wiris/mathtype-html-integration-devkit/src/latex.js\";\nimport StringManager from \"@wiris/mathtype-html-integration-devkit/src/stringmanager.js\";\nimport \"@wiris/mathtype-html-integration-devkit/src/md5.js\";\n\n// Local imports\nimport { MathTypeCommand, ChemTypeCommand } from \"./commands.js\";\nimport CKEditor5Integration from \"./integration.js\";\n\nimport mathIcon from \"../theme/icons/ckeditor5-formula.svg\";\nimport chemIcon from \"../theme/icons/ckeditor5-chem.svg\";\n\nimport packageInfo from \"../package.json\";\n\nexport let currentInstance = null; // eslint-disable-line import/no-mutable-exports\n\nexport default class MathType extends Plugin {\n static get requires() {\n return [Widget];\n }\n\n static get pluginName() {\n return \"MathType\";\n }\n\n init() {\n // Create the MathType API Integration object\n const integration = this._addIntegration();\n currentInstance = integration;\n\n // Add the MathType and ChemType commands to the editor\n this._addCommands();\n\n // Add the buttons for MathType and ChemType\n this._addViews(integration);\n\n // Registers the element in the schema\n this._addSchema();\n\n // Add the downcast and upcast converters\n this._addConverters(integration);\n\n // Expose the WirisPlugin variable to the window\n this._exposeWiris();\n }\n\n /**\n * Inherited from Plugin class: Executed when CKEditor5 is destroyed\n */\n destroy() {\n // eslint-disable-line class-methods-use-this\n currentInstance?.destroy();\n }\n\n /**\n * Create the MathType API Integration object\n * @returns {CKEditor5Integration} the integration object\n */\n _addIntegration() {\n const { editor } = this;\n\n /**\n * Integration model constructor attributes.\n * @type {integrationModelProperties}\n */\n const integrationProperties = {};\n integrationProperties.environment = {};\n integrationProperties.environment.editor = \"CKEditor5\";\n integrationProperties.environment.editorVersion = \"5.x\";\n integrationProperties.version = packageInfo.version;\n integrationProperties.editorObject = editor;\n integrationProperties.serviceProviderProperties = {};\n integrationProperties.serviceProviderProperties.URI = \"https://www.wiris.net/demo/plugins/app\";\n integrationProperties.serviceProviderProperties.server = \"java\";\n integrationProperties.target = editor.sourceElement;\n integrationProperties.scriptName = \"bundle.js\";\n integrationProperties.managesLanguage = true;\n // etc\n\n // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.\n // To avoid a wrong behavior, this integration only starts if the workspace container exists.\n let integration;\n if (integrationProperties.target) {\n // Instance of the integration associated to this editor instance\n integration = new CKEditor5Integration(integrationProperties);\n integration.init();\n integration.listeners.fire(\"onTargetReady\", {});\n\n integration.checkElement();\n\n this.listenTo(\n editor.editing.view.document,\n \"click\",\n (evt, data) => {\n // Is Double-click\n if (data.domEvent.detail === 2) {\n integration.doubleClickHandler(data.domTarget, data.domEvent);\n evt.stop();\n }\n },\n { priority: \"highest\" },\n );\n }\n\n return integration;\n }\n\n /**\n * Add the MathType and ChemType commands to the editor\n */\n _addCommands() {\n const { editor } = this;\n\n // Add command to open the formula editor\n editor.commands.add(\"MathType\", new MathTypeCommand(editor));\n\n // Add command to open the chemistry formula editor\n editor.commands.add(\"ChemType\", new ChemTypeCommand(editor));\n }\n\n /**\n * Add the buttons for MathType and ChemType\n * @param {CKEditor5Integration} integration the integration object\n */\n _addViews(integration) {\n const { editor } = this;\n\n // Check if MathType editor is enabled\n if (Configuration.get(\"editorEnabled\")) {\n // Add button for the formula editor\n editor.ui.componentFactory.add(\"MathType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"MathType\"), \"isEnabled\");\n view.set({\n label: StringManager.get(\"insert_math\", integration.getLanguage()),\n icon: mathIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"MathType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Check if ChemType editor is enabled\n if (Configuration.get(\"chemEnabled\")) {\n // Add button for the chemistry formula editor\n editor.ui.componentFactory.add(\"ChemType\", (locale) => {\n const view = new ButtonView(locale);\n\n // View is enabled iff command is enabled\n view.bind(\"isEnabled\").to(editor.commands.get(\"ChemType\"), \"isEnabled\");\n\n view.set({\n label: StringManager.get(\"insert_chem\", integration.getLanguage()),\n icon: chemIcon,\n tooltip: true,\n });\n\n // Callback executed once the image is clicked.\n view.on(\"execute\", () => {\n editor.execute(\"ChemType\", {\n integration, // Pass integration as parameter\n });\n });\n\n return view;\n });\n }\n\n // Observer for the Double-click event\n editor.editing.view.addObserver(ClickObserver);\n }\n\n /**\n * Registers the element in the schema\n */\n _addSchema() {\n const { schema } = this.editor.model;\n\n schema.register(\"mathml\", {\n inheritAllFrom: \"$inlineObject\",\n allowAttributes: [\"formula\", \"htmlContent\"],\n });\n }\n\n /**\n * Add the downcast and upcast converters\n */\n _addConverters(integration) {\n const { editor } = this;\n\n // Editing view -> Model\n editor.conversion.for(\"upcast\").elementToElement({\n view: {\n name: \"span\",\n classes: \"ck-math-widget\",\n },\n model: (viewElement, { writer: modelWriter }) => {\n const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute(\"data-mathml\"));\n return modelWriter.createElement(\"mathml\", {\n formula,\n });\n },\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\"element:math\", (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // When element was already consumed then skip it.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // If we encounter any with a LaTeX annotation inside,\n // convert it into a \"$$...$$\" string.\n const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define\n\n // Get the formula of the (which is all its children).\n const processor = new XmlDataProcessor(editor.editing.view.document);\n\n // Only god knows why the following line makes viewItem lose all of its children,\n // so we obtain isLatex before doing this because we need viewItem's children for that.\n const upcastWriter = new ViewUpcastWriter(editor.editing.view.document);\n const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());\n\n // and obtain the attributes of too!\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n\n // We process the document fragment\n let formula = processor.toData(viewDocumentFragment) || \"\";\n\n // And obtain the complete formula\n formula = Util.htmlSanitize(`${formula}`);\n\n // Replaces the < & > characters to its HTMLEntity to avoid render issues.\n formula = formula.replaceAll('\"<\"', '\"<\"').replaceAll('\">\"', '\">\"').replaceAll(\"><<\", \"><<\");\n\n /* Model node that contains what's going to actually be inserted. This can be either:\n - A element with a formula attribute set to the given formula, or\n - If the original had a LaTeX annotation, then the annotation surrounded by \"$$...$$\" */\n const modelNode = isLatex\n ? writer.createText(Parser.initParse(formula, integration.getLanguage()))\n : writer.createElement(\"mathml\", { formula });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n });\n\n // Data view -> Model\n editor.data.upcastDispatcher.on(\n \"element:img\",\n (evt, data, conversionApi) => {\n const { consumable, writer } = conversionApi;\n const { viewItem } = data;\n\n // Only upcast when is wiris formula\n if (viewItem.getClassNames().next().value !== \"Wirisformula\") {\n return;\n }\n\n // The following code ensures that the element's name, attributes, and classes are consumed.\n // This means that they are marked as handled so that other parts of the system or plugins don't process them again.\n\n // Check if we can consume the element name.\n if (!consumable.test(viewItem, { name: true })) {\n return;\n }\n\n // Consume the name, attributes, and classes so nothing else processes it.\n consumable.consume(viewItem, { name: true });\n for (const attrName of viewItem.getAttributes()) {\n consumable.consume(viewItem, { attributes: [attrName] });\n }\n\n for (const className of viewItem.getClassNames()) {\n consumable.consume(viewItem, { classes: [className] });\n }\n\n const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}=\"${value}\"`).join(\"\");\n const htmlContent = Util.htmlSanitize(``);\n\n const modelNode = writer.createElement(\"mathml\", { htmlContent });\n\n // Find allowed parent for element that we are going to insert.\n // If current parent does not allow to insert element but one of the ancestors does\n // then split nodes to allowed parent.\n const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);\n\n // When there is no split result it means that we can't insert element to model tree, so let's skip it.\n if (!splitResult) {\n return;\n }\n\n // Insert element on allowed position.\n conversionApi.writer.insert(modelNode, splitResult.position);\n\n // Consume appropriate value from consumable values list.\n consumable.consume(viewItem, { name: true });\n\n const parts = conversionApi.getSplitParts(modelNode);\n\n // Set conversion result range.\n data.modelRange = writer.createRange(\n conversionApi.writer.createPositionBefore(modelNode),\n conversionApi.writer.createPositionAfter(parts[parts.length - 1]),\n );\n\n // Now we need to check where the `modelCursor` should be.\n if (splitResult.cursorParent) {\n // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.\n //\n // before: foo[]\n // after: foo[]\n\n data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);\n } else {\n // Otherwise just continue after inserted element.\n data.modelCursor = data.modelRange.end;\n }\n },\n // Ensures MathType processes the Wiris formulas before other plugins, preventing conflicts.\n { priority: \"high\" },\n );\n\n /**\n * Whether the given view element has a LaTeX annotation element.\n * @param {*} math\n * @returns {bool}\n */\n function mathIsLatex(math) {\n const semantics = math.getChild(0);\n if (!semantics || semantics.name !== \"semantics\") return false;\n for (const child of semantics.getChildren()) {\n if (child.name === \"annotation\" && child.getAttribute(\"encoding\") === \"LaTeX\") {\n return true;\n }\n }\n return false;\n }\n\n function createViewWidget(modelItem, { writer: viewWriter }) {\n const widgetElement = viewWriter.createContainerElement(\"span\", {\n class: \"ck-math-widget\",\n });\n\n const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define\n\n if (mathUIElement) {\n viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);\n }\n\n return toWidget(widgetElement, viewWriter);\n }\n\n function createViewImage(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n const formula = modelItem.getAttribute(\"formula\");\n const htmlContent = modelItem.getAttribute(\"htmlContent\");\n\n if (!formula && !htmlContent) {\n return null;\n }\n\n let imgElement = null;\n\n if (htmlContent) {\n imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);\n } else if (formula) {\n const mathString = formula.replaceAll('ref=\"<\"', 'ref=\"<\"');\n\n const imgHtml = Parser.initParse(mathString, integration.getLanguage());\n imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);\n\n // Add HTML element () to model\n viewWriter.setAttribute(\"htmlContent\", imgHtml, modelItem);\n }\n\n /* Although we use the HtmlDataProcessor to obtain the attributes,\n * we must create a new EmptyElement which is independent of the\n * DataProcessor being used by this editor instance\n */\n if (imgElement) {\n return viewWriter.createEmptyElement(\"img\", imgElement.getAttributes(), {\n renderUnsafeAttributes: [\"src\"],\n });\n }\n\n return null;\n }\n\n // Model -> Editing view\n editor.conversion.for(\"editingDowncast\").elementToElement({\n model: \"mathml\",\n view: createViewWidget,\n });\n\n // Model -> Data view\n editor.conversion.for(\"dataDowncast\").elementToElement({\n model: \"mathml\",\n view: createDataString, // eslint-disable-line no-use-before-define\n });\n\n /**\n * Makes a copy of the given view node.\n * @param {module:engine/view/node~Node} sourceNode Node to copy.\n * @returns {module:engine/view/node~Node} Copy of the node.\n */\n function clone(viewWriter, sourceNode) {\n if (sourceNode.is(\"text\")) {\n return viewWriter.createText(sourceNode.data);\n }\n if (sourceNode.is(\"element\")) {\n if (sourceNode.is(\"emptyElement\")) {\n return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());\n }\n const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());\n for (const child of sourceNode.getChildren()) {\n viewWriter.insert(viewWriter.createPositionAt(element, \"end\"), clone(viewWriter, child));\n }\n return element;\n }\n\n throw new Exception(\"Given node has unsupported type.\"); // eslint-disable-line no-undef\n }\n\n function createDataString(modelItem, { writer: viewWriter }) {\n const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);\n\n // Load img element\n const mathString =\n modelItem.getAttribute(\"htmlContent\") || Parser.endParseSaveMode(modelItem.getAttribute(\"formula\"));\n\n const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);\n\n return clone(viewWriter, sourceMathElement);\n }\n\n // This stops the view selection getting into the s and messing up caret movement\n editor.editing.mapper.on(\n \"viewToModelPosition\",\n viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass(\"ck-math-widget\")),\n );\n\n // Keep a reference to the original get and set function.\n const { get, set } = editor.data;\n\n /**\n * Hack to transform $$latex$$ into in editor.getData()'s output.\n */\n editor.data.on(\n \"get\",\n (e) => {\n const output = e.return;\n const parsedResult = Parser.endParse(output);\n\n // Cleans all the semantics tag for safexml\n // including the handwritten data points\n e.return = MathML.removeSafeXMLSemantics(parsedResult);\n },\n { priority: \"low\" },\n );\n\n /**\n * Hack to transform with LaTeX into $$LaTeX$$ and formula images in editor.setData().\n */\n editor.data.on(\n \"set\",\n (e, args) => {\n // Retrieve the data to be set on the CKEditor.\n let modifiedData = args[0];\n // Regex to find all mathml formulas.\n const regexp = /(]*>)|()/gm;\n const formulas = [];\n let formula;\n\n // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.\n while ((formula = regexp.exec(modifiedData)) !== null) {\n formulas.push(formula[0]);\n }\n\n // Loop to find LaTeX and formula images and replace the MathML for the both.\n formulas.forEach((formula) => {\n if (formula.includes('encoding=\"LaTeX\"')) {\n // LaTeX found.\n const latex = `$$$${Latex.getLatexFromMathML(formula)}$$$`; // We add $$$ instead of $$ because the replace function ignores one $.\n modifiedData = modifiedData.replace(formula, latex);\n } else if (formula.includes(\" Date: Mon, 16 Feb 2026 14:07:23 +0100 Subject: [PATCH 26/29] test: delete duplicated e2e tests readme --- tests/e2e/README.md | 225 -------------------------------------------- 1 file changed, 225 deletions(-) delete mode 100644 tests/e2e/README.md diff --git a/tests/e2e/README.md b/tests/e2e/README.md deleted file mode 100644 index 8190d01a0..000000000 --- a/tests/e2e/README.md +++ /dev/null @@ -1,225 +0,0 @@ -# WIRIS HTML Editors E2E Tests - Playwright Migration - -This project contains end-to-end tests for WIRIS HTML editors using Playwright. - -## ๐Ÿš€ Getting Started - -### Prerequisites - -- Node.js 18+ -- npm or yarn - -### Installation - -```bash -# Install dependencies -npm install - -# Install Playwright browsers -npx playwright install -``` - -### Environment Configuration - -Copy the example environment file and configure it: - -```bash -cp .env.example .env -``` - -Configure the following environment variables: - -- `BASE_URL`: Base URL for the tests (default: https://integrations.wiris.kitchen/) -- `HTML_EDITOR`: Pipe-separated list of editors to test (e.g., "generic|ckeditor5|tinymce8") -- `TEST_BRANCH`: wiris/html-integrations branch to test - - -## ๐Ÿงช Running Tests - -### Basic Commands - -```bash -# Run all tests -npm run test - -# Run tests in headed mode (browser visible) -npm run test:headed - -# Run tests with UI mode -npm run test:ui - -# Run tests in debug mode -npm run test:debug -``` - -### Browser-Specific Tests - -```bash -# Run tests in Chrome only -npm run test:chrome - -# Run tests in Firefox only -npm run test:firefox - -# Run tests in Safari only (WebKit) -npm run test:webkit -``` - -### Test Categories - -```bash -# Run smoke tests -npm run test:smoke - -# Run regression tests -npm run test:regression -``` - -### Reports - -```bash -# Show test report -npm run show-report -``` - -## ๐Ÿ“ Project Structure - -``` -playwright/ -โ”œโ”€โ”€ tests/ # Test specifications -โ”‚ โ”œโ”€โ”€ insert/ # Insert equation tests -โ”‚ โ”œโ”€โ”€ edit/ # Edit equation tests -โ”‚ โ”œโ”€โ”€ latex/ # LaTeX tests -โ”‚ โ”œโ”€โ”€ modal/ # Modal dialog tests -โ”‚ โ”œโ”€โ”€ editor/ # Editor-specific tests -โ”‚ โ””โ”€โ”€ telemetry/ # Telemetry tests -โ”œโ”€โ”€ page-objects/ # Page Object Model -โ”‚ โ”œโ”€โ”€ html/ # HTML editor implementations -โ”‚ โ”‚ โ””โ”€โ”€ editor/ # Specific HTML editors -โ”‚ โ”œโ”€โ”€ base_editor.ts # Base editor class -โ”‚ โ”œโ”€โ”€ wiris_editor.ts # WIRIS editor modal -โ”‚ โ”œโ”€โ”€ equation_entry_form.ts # Equation entry form -โ”‚ โ”œโ”€โ”€ editor_manager.ts # Editor management -โ”‚ โ””โ”€โ”€ page.ts # Base page class -โ”œโ”€โ”€ enums/ # Enumerations -โ”‚ โ”œโ”€โ”€ equations.ts # Test equations -โ”‚ โ”œโ”€โ”€ toolbar.ts # Toolbar types -โ”‚ โ””โ”€โ”€ typing_mode.ts # Input modes -โ”œโ”€โ”€ interfaces/ # TypeScript interfaces -โ”‚ โ””โ”€โ”€ equation.ts # Equation interface -โ”œโ”€โ”€ helpers/ # Helper utilities -โ”‚ โ””โ”€โ”€ test-setup.ts # Test setup helpers -โ”œโ”€โ”€ playwright.config.ts # Playwright configuration -โ”œโ”€โ”€ package.json # Dependencies and scripts -โ””โ”€โ”€ README.md # This file -``` - -## ๐Ÿ”ง Configuration - -### Playwright Configuration - -The `playwright.config.ts` file contains: - -- **Test Directory**: `./tests` -- **Parallel Execution**: Enabled for faster test runs -- **Retries**: 1 retry for flaky tests (2 in CI) -- **Browsers**: Chrome, Firefox, and Safari (WebKit) -- **Reporters**: HTML, JUnit, and Allure -- **Screenshots**: On failure -- **Videos**: On failure -- **Traces**: On first retry - -### Editor Selection - -Configure which editors to test using the `HTML_EDITOR` environment variable: - -```bash -# Test specific editors -HTML_EDITOR="generic|ckeditor5" - -# Test all editors -HTML_EDITOR="generic|ckeditor4|ckeditor5|froala|tinymce5|tinymce6|tinymce7|tinymce8" -``` - -## ๐Ÿ—๏ธ Page Object Model - -The tests use the Page Object Model pattern for maintainability: - -### Base Classes - -- **`BasePage`**: Common page functionality -- **`BaseEditor`**: Abstract base for all editors with common methods - -### Editor Implementations - -Each editor (Generic, CKEditor, TinyMCE, Froala) has specific implementations for: -- Button selectors -- Edit field selectors -- Special behavior (contextual toolbars, iframes) - -### WIRIS Editor (MathType Web) - -The `WirisEditor` class handles the WIRIS equation editor modal: -- Equation input via keyboard and forms -- MathML and LaTeX entry modes -- Hand-drawn equation support - -## ๐Ÿงช Test Organization - -### Test Categories - -Tests are organized into categories using tags: - -- `@smoke`: Critical functionality tests -- `@regression`: Comprehensive test coverage (All tests) - -### Test Structure - -Each test follows this pattern: - -1. **Setup**: Initialize editor and WIRIS editor -2. **Open**: Navigate to the editor page -3. **Action**: Perform the test action (insert, edit, etc.) -4. **Verify**: Assert expected results - -### Cross-Browser Testing - -Tests run across multiple browsers: -- **Chromium**: Google Chrome -- **Firefox**: Mozilla Firefox -- **WebKit**: Safari - - -## ๐Ÿ› Debugging - -### Debug Mode - -Run tests in debug mode to step through: - -```bash -npm run test:debug -``` - -### Visual Debugging - -Use UI mode for visual test debugging: - -```bash -npm run test:ui -``` - -### Screenshots and Videos - -Failed tests automatically capture: -- Screenshots -- Videos (if enabled) -- Traces for debugging - -## ๐Ÿค Contributing - -1. Follow the existing Page Object Model pattern -2. Add appropriate test tags (`@smoke`, `@regression`) -3. Include JSDoc comments for new methods -4. Update this README for new features - -## ๐Ÿ“ˆ Continuous Integration From 28854442546b5a7b162295d08795545c94903083 Mon Sep 17 00:00:00 2001 From: Andreu Tomas Date: Mon, 16 Feb 2026 14:13:34 +0100 Subject: [PATCH 27/29] ci: rename e2e tests action --- ...{cypress-Run-tests-with-npm-packages.yml => run-e2e-tests.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{cypress-Run-tests-with-npm-packages.yml => run-e2e-tests.yml} (100%) diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/run-e2e-tests.yml similarity index 100% rename from .github/workflows/cypress-Run-tests-with-npm-packages.yml rename to .github/workflows/run-e2e-tests.yml From 2bf6cd9ba809e5d0573ef1a8143d6f09973a94cd Mon Sep 17 00:00:00 2001 From: sbaron-at-wiris Date: Wed, 18 Feb 2026 13:08:15 +0100 Subject: [PATCH 28/29] test: remove list reporter duplicate --- tests/e2e/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index aba5175ef..04c386927 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -41,7 +41,7 @@ export default defineConfig({ reporter: [ ['html', { open: isCI ? 'never' : 'on-failure', outputFolder: 'playwright-report/html' }], ['junit', { outputFile: 'test-results/results.xml' }], - isCI ? ['blob']: ['list'], + isCI ? ['blob']: ['null'], isCI ? ['github'] : ['list'], ], use: { From 1e94f8348b0460460957f2d0f2a6f3a356b631c9 Mon Sep 17 00:00:00 2001 From: David Adalid <159769517+dadalid-at-wiris@users.noreply.github.com> Date: Tue, 3 Mar 2026 12:34:59 +0100 Subject: [PATCH 29/29] test: add playwright extension --- .vscode/extensions.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index ce541f0a9..00c201f60 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -20,6 +20,7 @@ "ms-vsliveshare.vsliveshare", // Live Share "visualstudioexptteam.vscodeintellicode", // IntelliCode "thundergang.thunder-client", // Thunder Client + "ms-playwright.playwright", // Playwright // GITHUB "eamodio.gitlens", // GitLens