Add '_regroup/ckeditor5-math/' from commit '6231df7f0e9df7f4d6982b103c02400d4f0b8937'

git-subtree-dir: _regroup/ckeditor5-math
git-subtree-mainline: 034cd58833a2c9c7ba49f6217dc5aeff274e0174
git-subtree-split: 6231df7f0e9df7f4d6982b103c02400d4f0b8937
This commit is contained in:
Elian Doran 2025-05-04 21:20:21 +03:00
commit 78544e5c99
44 changed files with 12694 additions and 0 deletions

View File

@ -0,0 +1,20 @@
root = true
[*]
indent_style = tab
tab_width = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,jsx,ts}]
quote_type = single
[*.{yml,yaml}]
indent_style = space
indent_size = 4
[package.json]
indent_style = space
tab_width = 4

View File

@ -0,0 +1,60 @@
module.exports = {
extends: [
"ckeditor5",
"plugin:@typescript-eslint/strict",
"plugin:@typescript-eslint/stylistic-type-checked",
],
root: true,
plugins: ["@typescript-eslint"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
__tsconfigRootDir: __dirname,
ecmaVersion: "latest",
sourceType: "module",
},
globals: {
MathJax: true,
katex: true,
console: true,
},
ignorePatterns: [
// Ignore the entire `build/` (the DLL build).
"build/**",
// Ignore compiled JavaScript files, as they are generated automatically.
'src/**/*.js',
// Also, do not check typing declarations, too.
'src/**/*.d.ts'
],
rules: {
// This rule disallows importing core DLL packages directly. Imports should be done using the `ckeditor5` package.
// Also, importing non-DLL packages is not allowed. If the package requires other features to work, they should be
// specified as soft-requirements.
// Read more: https://ckeditor.com/docs/ckeditor5/latest/builds/guides/migration/migration-to-26.html#soft-requirements.
"ckeditor5-rules/ckeditor-imports": "error",
// This rule could not be found ???
"ckeditor5-rules/use-require-for-debug-mode-imports": "off",
"no-void": ["error", { allowAsStatement: true }],
},
overrides: [
{
files: [ 'tests/**/*.[jt]s', 'sample/**/*.[jt]s' ],
rules: {
// To write complex tests, you may need to import files that are not exported in DLL files by default.
// Hence, imports CKEditor 5 packages in test files are not checked.
"ckeditor5-rules/ckeditor-imports": "off",
},
},
{
env: {
node: true,
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
};

18
_regroup/ckeditor5-math/.gitattributes vendored Normal file
View File

@ -0,0 +1,18 @@
* text=auto
*.htaccess eol=lf
*.cgi eol=lf
*.sh eol=lf
*.css text
*.htm text
*.html text
*.js text
*.json text
*.php text
*.txt text
*.md text
*.png -text
*.gif -text
*.jpg -text

View File

@ -0,0 +1,26 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
publish-package:
name: Publish package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
always-auth: true
- run: |
corepack enable &&
corepack install
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Publish package
run: yarn publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

10
_regroup/ckeditor5-math/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
node_modules/
build/
tmp/
sample/ckeditor.dist.js
package-lock.json
yarn-error.log
# Ignore compiled TypeScript files.
src/**/*.js
src/**/*.d.ts

View File

@ -0,0 +1 @@
18.12.1

View File

@ -0,0 +1 @@
src/

View File

@ -0,0 +1,11 @@
{
"embeddedLanguageFormatting": "off",
"overrides": [
{
"files": "*.json",
"options": {
"tabWidth": 2
}
}
]
}

View File

@ -0,0 +1,3 @@
{
"extends": "stylelint-config-ckeditor5"
}

View File

@ -0,0 +1,556 @@
# Changelog
## Current
- Add latest changes here
## [43.2.0](https://github.com/isaul32/ckeditor5-math/compare/v43.1.2...v43.2.0) (2024-10-14)
- Update dependencies for CKEditor 43.2.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v43.2.0/CHANGELOG.md))
([#153](https://github.com/isaul32/ckeditor5-math/pull/153))
**Note** This does **not** yet support the [new installation method](https://ckeditor.com/docs/ckeditor5/latest/updating/nim-migration/migration-to-new-installation-methods.html) for [custom plugins](https://ckeditor.com/docs/ckeditor5/latest/updating/nim-migration/custom-plugins.html). That will come in a future release.
## [43.1.2](https://github.com/isaul32/ckeditor5-math/compare/v43.1.1...v43.1.2) (2024-09-26)
- Downgrade ckeditor5-package-tools from 2 to 1. Fixes test for now.
## [43.1.1](https://github.com/isaul32/ckeditor5-math/compare/v41.4.2...v43.1.1) (2024-09-26)
- Update dependencies for CKEditor 43.1.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v43.1.1/CHANGELOG.md))
([#149](https://github.com/isaul32/ckeditor5-math/pull/149))
**Note** This does **not** yet support the [new installation method](https://ckeditor.com/docs/ckeditor5/latest/updating/nim-migration/migration-to-new-installation-methods.html) for [custom plugins](https://ckeditor.com/docs/ckeditor5/latest/updating/nim-migration/custom-plugins.html). That will come in a future release.
- Move tests to TypeScript, kudos @fedemp
([#135](https://github.com/isaul32/ckeditor5-math/pull/135))
## [41.4.2](https://github.com/isaul32/ckeditor5-math/compare/v41.3.1...v41.4.2) (2024-05-18)
- Update dependencies for CKEditor 41.4.2 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v41.4.2/CHANGELOG.md))
([#141](https://github.com/isaul32/ckeditor5-math/pull/141))
## [41.3.1](https://github.com/isaul32/ckeditor5-math/compare/v41.3.0...v41.3.1) (2024-04-16)
- Update dependencies for CKEditor 41.3.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v41.3.1/CHANGELOG.md))
([#137](https://github.com/isaul32/ckeditor5-math/pull/137))
## [41.3.0](https://github.com/isaul32/ckeditor5-math/compare/v41.2.3...v41.3.0) (2024-04-12)
- Update dependencies for CKEditor 41.3.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v41.3.0/CHANGELOG.md))
(#136)
## [41.2.3](https://github.com/isaul32/ckeditor5-math/compare/v41.2.2...v41.2.3) (2024-03-22)
- Update dependencies for CKEditor 41.2.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v41.2.1/CHANGELOG.md))
(#134)
## [41.2.2](https://github.com/isaul32/ckeditor5-math/compare/v41.2.1...v41.2.2) (2024-03-17)
- README updates ([#133](https://github.com/isaul32/ckeditor5-math/pull/133)):
- Note plugin supports DLL-based builds and is in TypeScript.
- Remove old TypeScript workaround sections.
- Chore: Typo fixes ([#133](https://github.com/isaul32/ckeditor5-math/pull/133))
## [41.2.1](https://github.com/isaul32/ckeditor5-math/compare/v41.2.0...v41.2.1) (2024-03-17)
- Rewritten in TypeScript, credit @fedemp
([#130](https://github.com/isaul32/ckeditor5-math/pull/130)).
## [41.2.0](https://github.com/isaul32/ckeditor5-math/compare/v41.1.0...v41.2.0) (2024-03-08)
- Update dependencies for CKEditor 41.2.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v41.2.0/CHANGELOG.md))
(#129)
## [41.1.0](https://github.com/isaul32/ckeditor5-math/compare/v41.0.0...v41.1.0) (2024-02-13)
- Update dependencies for CKEditor 41.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v41.1.0/CHANGELOG.md))
(#124)
## [41.0.0](https://github.com/isaul32/ckeditor5-math/compare/v40.2.0...v41.0.0) (2024-01-27)
- Update dependencies for CKEditor 41.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v41.0.0/CHANGELOG.md))
(#122)
- ui(CSS): Update `--ck-z-modal` to `--ck-z-panel` for CKEditor5 v41.0.0 (#122)
## [40.2.0](https://github.com/isaul32/ckeditor5-math/compare/v40.1.0...v40.2.0) (2023-12-12)
- Update dependencies for CKEditor 40.2.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v40.2.0/CHANGELOG.md))
(#121)
## [40.1.0](https://github.com/isaul32/ckeditor5-math/compare/v40.0.0...v40.1.0) (2023-11-17)
- Update dependencies for CKEditor 40.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v40.1.0/CHANGELOG.md))
(#120)
## [40.0.0](https://github.com/isaul32/ckeditor5-math/compare/v39.0.2...v40.0.0) (2023-10-13)
- Update dependencies for CKEditor 40.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v40.0.0/CHANGELOG.md))
(#116)
- Bump minimum Node.js version to 18+ per CKEditor 40 requirements (#116)
## [39.0.2](https://github.com/isaul32/ckeditor5-math/compare/v39.0.1...v39.0.2) (2023-09-06)
- Update dependencies for CKEditor 39.0.2 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v39.0.2/CHANGELOG.md))
(#115)
- Update lint packages (#115):
- eslint-config-ckeditor5: ^5.0.1 → ^5.1.1
- stylelint-config-ckeditor5: >=4.1.1 → >=5.1.1
## [39.0.1](https://github.com/isaul32/ckeditor5-math/compare/v39.0.0...v39.0.1) (2023-08-10)
- Update dependencies for CKEditor 39.0.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v39.0.1/CHANGELOG.md))
(#113)
## [39.0.0](https://github.com/isaul32/ckeditor5-math/compare/v38.1.1...v39.0.0) (2023-08-10)
- Update dependencies for CKEditor 39.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v39.0.0/CHANGELOG.md))
(#112)
## [38.1.1](https://github.com/isaul32/ckeditor5-math/compare/v38.1.0...v38.1.1) (2023-07-26)
- Update dependencies for CKEditor 38.1.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v38.1.1/CHANGELOG.md))
(#109)
## [38.1.0](https://github.com/isaul32/ckeditor5-math/compare/v38.0.1...v38.1.0) (2023-07-26)
- Update dependencies for CKEditor 38.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v38.1.0/CHANGELOG.md))
(#108)
- Version updates (#106, #108)
- ckeditor5-package-tools: v1.0.0-beta.10 -> v1.1.0
([release
notes](https://github.com/ckeditor/ckeditor5-package-generator/blob/v1.1.0/CHANGELOG.md))
- eslint-config-ckeditor5: >=4.1.1 -> ^5.0.1
([release
notes](https://github.com/ckeditor/ckeditor5-linters-config/blob/v5.1.0/CHANGELOG.md))
- README: Note typing workaround for TypeScript builds (#105)
1. Create a `d.ts` declaration file, e.g. `typings/ckeditor5-math.d.ts`
```typescript
declare module '@isaul32/ckeditor5-math';
declare module '@isaul32/ckeditor5-math/src/math';
declare module '@isaul32/ckeditor5-math/src/autoformatmath';
```
2. In your [`tsconfig.json`](https://www.typescriptlang.org/tsconfig)'s
root-level [`include`](https://www.typescriptlang.org/tsconfig#include)
option, make sure your declaration file is covered, e.g.
```json
{
"extends": "ckeditor5/tsconfig.json",
"include": [
"src",
"typings",
"../../typings"
]
}
```
## [38.0.1](https://github.com/isaul32/ckeditor5-math/compare/v38.0.0...v38.0.1) (2023-06-20)
- Update dependencies for CKEditor 38.0.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v38.0.1/CHANGELOG.md))
## [38.0.0](https://github.com/isaul32/ckeditor5-math/compare/v37.1.0...v38.0.0) (2023-06-20)
- Update dependencies for CKEditor 38.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v38.0.0/CHANGELOG.md))
## [37.1.0](https://github.com/isaul32/ckeditor5-math/compare/v37.0.2...v37.1.0) (2023-04-19)
- Update dependencies for CKEditor 37.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v37.1.0/CHANGELOG.md))
## [37.0.2](https://github.com/isaul32/ckeditor5-math/compare/v37.0.1...v37.0.2) (2023-04-05)
- Fix loading of ckeditor5-math when no config object is declared in build
(#98)
## [37.0.1](https://github.com/isaul32/ckeditor5-math/compare/v37.0.0...v36.0.1) (2023-04-05)
- Update dependencies for CKEditor 37.0.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v37.0.1/CHANGELOG.md))
## [37.0.0](https://github.com/isaul32/ckeditor5-math/compare/v36.0.6...v37.0.0) (2023-04-05)
- Update dependencies for CKEditor 37.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v37.0.0/CHANGELOG.md))
- Bump minimum node version to v16 (per CKEditor v37's requirements)
- Add a .nvmrc for nvm users
## [36.0.7](https://github.com/isaul32/ckeditor5-math/compare/v36.0.6...v36.0.7) (2023-04-05)
- Fix loading of ckeditor5-math when no config object is declared in build
(#97)
## [36.0.6](https://github.com/isaul32/ckeditor5-math/compare/v36.0.5...v36.0.6) (2023-04-05)
- :arrow_up: CKEditor package tools: 1.0.0-beta.8 -> beta.10
See also: https://github.com/ckeditor/ckeditor5-package-generator/blob/v1.0.0-beta.10/CHANGELOG.md
## [36.0.5](https://github.com/isaul32/ckeditor5-math/compare/v36.0.4...v36.0.5) (2023-04-03)
- Update docs for new package name (#91)
- Old: ckeditor5-math
- New: @isaul32/ckeditor5-math
- New feature: Make className of span element configurable (#82, thank you
@DanielKulbe)
## [36.0.4](https://github.com/isaul32/ckeditor5-math/compare/v36.0.3...v36.0.4) (2023-03-13)
- New package name
- Old: ckeditor5-math
- New: @isaul32/ckeditor5-math
## [36.0.3](https://github.com/isaul32/ckeditor5-math/compare/v36.0.2...v36.0.3) (2023-03-13)
- Move to a DLL compatible plugin (#83)
Credit: @DanielKulbe
## [36.0.2](https://github.com/isaul32/ckeditor5-math/compare/v36.0.1...v36.0.2) (2023-02-14)
- Fix `previewClassName` (#86)
## [36.0.1](https://github.com/isaul32/ckeditor5-math/compare/v36.0.0...v36.0.1) (2023-01-26)
- Update dependencies for CKEditor 36.0.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v36.0.1/CHANGELOG.md))
## [36.0.0](https://github.com/isaul32/ckeditor5-math/compare/v35.4.0...v36.0.0) (2023-01-26)
- Update dependencies for CKEditor 36.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v36.0.0/CHANGELOG.md))
## [35.4.0](https://github.com/isaul32/ckeditor5-math/compare/v35.3.2...v35.4.0) (2022-12-13)
- Update dependencies for CKEditor 35.4.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.4.0/CHANGELOG.md))
## [35.3.2](https://github.com/isaul32/ckeditor5-math/compare/v35.3.1...v35.3.2) (2022-11-23)
- Update dependencies for CKEditor 35.3.2 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.3.2/CHANGELOG.md))
## [35.3.1](https://github.com/isaul32/ckeditor5-math/compare/v35.3.0...v35.3.1) (2022-11-15)
- Update dependencies for CKEditor 35.3.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.3.1/CHANGELOG.md))
## [35.3.0](https://github.com/isaul32/ckeditor5-math/compare/v35.2.1...v35.3.0) (2022-11-03)
- Update dependencies for CKEditor 35.3.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.3.0/CHANGELOG.md))
## [35.2.1](https://github.com/isaul32/ckeditor5-math/compare/v35.2.0...v35.2.1) (2022-10-13)
- Update dependencies for CKEditor 35.2.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.2.1/CHANGELOG.md))
## [35.2.0](https://github.com/isaul32/ckeditor5-math/compare/v35.1.0...v35.2.0) (2022-10-13)
- Update dependencies for CKEditor 35.2.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.2.0/CHANGELOG.md))
## [35.1.0](https://github.com/isaul32/ckeditor5-math/compare/v35.0.1...v35.1.0) (2022-09-28)
- Update dependencies for CKEditor 35.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.1.0/CHANGELOG.md))
## [35.0.1](https://github.com/isaul32/ckeditor5-math/compare/v35.0.0...v35.0.1) (2022-09-28)
- Update dependencies for CKEditor 35.0.1 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.0.1/CHANGELOG.md))
## [35.0.0](https://github.com/isaul32/ckeditor5-math/compare/v34.2.0...v35.0.0) (2022-09-28)
- Update dependencies for CKEditor 35.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v35.0.0/CHANGELOG.md))
## [34.2.0](https://github.com/isaul32/ckeditor5-math/compare/v34.1.1...v34.2.0) (2022-09-28)
- Update dependencies for CKEditor 34.2.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v34.2.0/CHANGELOG.md))
## [34.1.1](https://github.com/isaul32/ckeditor5-math/compare/v34.1.0...v34.1.1) (2022-08-03)
- New configuration setting, `katexRenderOptions` (optional) - for KaTeX engines. Accepts object of `katex.render()` / `katex.renderToString()` [options](https://katex.org/docs/options.html):
```js
InlineEditor.defaultConfig = {
// ...
math: {
engine: 'katex'
katexRenderOptions: {
macros: {
"\\neq": "\\mathrel{\\char`≠}",
},
},
}
}
```
via PR [#64](https://github.com/isaul32/ckeditor5-math/pull/64) by [Tony
Narlock](https://www.git-pull.com).
## [34.1.0](https://github.com/isaul32/ckeditor5-math/compare/v34.0.0...v34.1.0) (2022-06-21)
- Update dependencies for CKEditor 34.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v34.1.0/CHANGELOG.md))
- Changelog:
- Begin adding dates to releases
- Remove _(current)_, which was being applied to old releases incorrectly up to last release
## [34.0.0](https://github.com/isaul32/ckeditor5-math/compare/v33.0.0...v34.0.0) (2022-05-12)
- Update dependencies for CKEditor 34.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v34.0.0/CHANGELOG.md))
- Begin keeping a yarn lockfile. This will make it easier to track changes
aren't due to sub-dependencies shifting across time and development
conditions.
## [33.0.0](https://github.com/isaul32/ckeditor5-math/compare/v32.0.0...v33.0.0)
- Update dependencies for CKEditor 33.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v33.0.0/CHANGELOG.md))
## [32.0.0](https://github.com/isaul32/ckeditor5-math/compare/v31.1.0...v32.0.0)
- Update dependencies for CKEditor 32.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v32.0.0/CHANGELOG.md))
- Update webpack, postcss-loader, mini-css-extract-plugin, minimum node version (12 to 14)
per above.
## [31.1.0](https://github.com/isaul32/ckeditor5-math/compare/v31.0.0...v31.1.0)
- Update dependencies for CKEditor 31.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v31.1.0/CHANGELOG.md))
## [31.0.0](https://github.com/isaul32/ckeditor5-math/compare/v30.0.0...v31.0.0)
- Update dependencies for CKEditor 31.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v31.0.0/CHANGELOG.md))
## [30.0.0](https://github.com/isaul32/ckeditor5-math/compare/v29.2.0...v30.0.0)
- Update dependencies for CKEditor 30.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v30.0.0/CHANGELOG.md))
## [29.2.0](https://github.com/isaul32/ckeditor5-math/compare/v29.1.0...v29.2.0)
- Update dependencies for CKEditor 29.2.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v29.2.0/CHANGELOG.md))
- Fix `yarn start`'s missing classic editor dependency
## [29.1.0](https://github.com/isaul32/ckeditor5-math/compare/v29.0.0...v29.1.0)
- Update dependencies for CKEditor 29.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v29.1.0/CHANGELOG.md))
## [29.0.0](https://github.com/isaul32/ckeditor5-math/compare/v28.0.0...v29.0.0)
- Update dependencies for CKEditor 29.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/blob/v29.0.0/CHANGELOG.md))
## [28.0.0](https://github.com/isaul32/ckeditor5-math/compare/v27.1.4...v28.0.0)
- Update dependencies for CKEditor 28.0.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/releases/tag/v28.0.0))
## [27.1.4](https://github.com/isaul32/ckeditor5-math/compare/v27.1.3...v27.1.4)
- #45: docs(README,CHANGELOG): Format with prettier 2.3.0
- #42: use `SwitchButton` to toggle "display mode". Thanks you @Jules-Bertholet!
## [27.1.3](https://github.com/isaul32/ckeditor5-math/compare/v27.1.2...27.1.3) (2021-05-16)
- #41: Prevent inserting empty equations, thank you @Jules-Bertholet.
## [27.1.2](https://github.com/isaul32/ckeditor5-math/compare/v27.1.1...v27.1.2) (2021-03-29)
- #38: You can now boot into a development instance with `yarn start` (supports
live reload)
- #40 (fixed #39): Support for upcasting Quill style tags
## [27.1.1](https://github.com/isaul32/ckeditor5-math/compare/v27.1.0...v27.1.1) (2021-03-29)
- Update dependencies for CKEditor 27.1.0 ([Release
notes](https://github.com/ckeditor/ckeditor5/releases/tag/v27.1.0))
## [27.1.0](https://github.com/isaul32/ckeditor5-math/compare/v27.0.1...v27.1.0) (2021-03-29)
- #33: New optional config variables: `previewClassName` and `popupClassName` as an array
of classes, this makes it easier to style the preview:
```javascript
{
"math": {
"popupClassName": ["myeditor"],
"previewClassName": ["myeditor"]
}
}
```
This assures the preview appended to `document.body` and the popup both are
accessible via `.myeditor`.
## [27.0.1](https://github.com/isaul32/ckeditor5-math/compare/v27.0.0...v27.0.1) (2021-03-29)
- Typo fix from #32.
## [27.0.0](https://github.com/isaul32/ckeditor5-math/compare/v26.0.0...v27.0.0) (2021-03-29)
- Update dependencies.
## [26.0.0](https://github.com/isaul32/ckeditor5-math/compare/v25.0.0...v26.0.0) (2021-03-04)
- Update dependencies.
## [25.0.0](https://github.com/isaul32/ckeditor5-math/compare/v24.0.1...v25.0.0) (2021-03-02)
- Update dependencies.
## [24.0.1](https://github.com/isaul32/ckeditor5-math/compare/24.0.0...v24.0.1) (2021-02-28)
- Fix balloon view position.
## [24.0.0](https://github.com/isaul32/ckeditor5-math/compare/v23.3.0...24.0.0) (2020-12-12)
- Update dependencies.
## [23.3.0](https://github.com/isaul32/ckeditor5-math/compare/23.2.2...v23.3.0) (2020-11-07)
- Add autoformat support. ([3354872](https://github.com/isaul32/ckeditor5-math/commit/3354872))
## [23.2.2](https://github.com/isaul32/ckeditor5-math/compare/v23.2.1...23.2.2) (2020-11-03)
- Fix placeholder handling. ([dc288ea](https://github.com/isaul32/ckeditor5-math/commit/dc288ea))
- Fix selection after entering inline expression. ([2fea2e2](https://github.com/isaul32/ckeditor5-math/commit/2fea2e2))
## [23.2.1](https://github.com/isaul32/ckeditor5-math/compare/v23.2.0...v23.2.1) (2020-10-18)
- Fix math editing button for balloon editor. ([3629401](https://github.com/isaul32/ckeditor5-math/commit/3629401))
## [23.2.0](https://github.com/isaul32/ckeditor5-math/compare/v23.1.0...v23.2.0) (2020-10-18)
- Add math editing button for balloon editor. ([aa0392c](https://github.com/isaul32/ckeditor5-math/commit/aa0392c))
## [23.1.0](https://github.com/isaul32/ckeditor5-math/compare/v23.0.0...v23.1.0) (2020-10-11)
- Add typesetting auto load feature. ([a665b64](https://github.com/isaul32/ckeditor5-math/commit/a665b64))
## [23.0.0](https://github.com/isaul32/ckeditor5-math/compare/v22.0.0...v23.0.0) (2020-10-02)
- Update dependencies.
## [22.0.0](https://github.com/isaul32/ckeditor5-math/compare/v21.0.0...v22.0.0) (2020-08-29)
- Separate schema to display and inline.
### Bug fixes
- Fix writer. ([7d0cd01](https://github.com/isaul32/ckeditor5-math/commit/7d0cd01))
### Other changes
- Update dependencies.
## [21.0.0](https://github.com/isaul32/ckeditor5-math/compare/v20.0.0...v21.0.0) (2020-08-03)
- Update dependencies.
## [20.0.0](https://github.com/isaul32/ckeditor5-math/compare/v19.0.0...v20.0.0) (2020-07-13)
- Update dependencies.
## [19.0.0](https://github.com/isaul32/ckeditor5-math/compare/v18.0.1...v19.0.0) (2020-05-12)
- Update dependencies.
## [18.0.1](https://github.com/isaul32/ckeditor5-math/compare/v18.0.0...v18.0.1) (2020-04-05)
### Bug fixes
- Fix spacebar before formula bug in Firefox. [isaul32/ckeditor5-math#2](https://github.com/isaul32/ckeditor5-math/issues/2). ([9d15010](https://github.com/isaul32/ckeditor5-math/commit/9d15010))
## [18.0.0](https://github.com/isaul32/ckeditor5-math/compare/v17.0.1...v18.0.0) (2020-03-30)
- Update dependencies.
## [17.0.1](https://github.com/isaul32/ckeditor5-math/compare/v17.0.0...v17.0.1) (2020-02-27)
### Bug fixes
- Fix missing dependencies.
## [17.0.0](https://github.com/isaul32/ckeditor5-math/compare/v1.0.3...v17.0.0) (2020-02-27)
### Bug fixes
- Fix dependencies resolving problem. Closes [isaul32/ckeditor5-math#1](https://github.com/isaul32/ckeditor5-math/issues/1). ([7d40a2c](https://github.com/isaul32/ckeditor5-math/commit/7d40a2c))
## [1.0.3](https://github.com/isaul32/ckeditor5-math/compare/v1.0.2...v1.0.3) (2019-10-11)
### Bug fixes
- Fix preview flickering effect. ([70fefa8](https://github.com/isaul32/ckeditor5-math/commit/70fefa8))
- Fix disabled eslint lines. ([1f96286](https://github.com/isaul32/ckeditor5-math/commit/1f96286))
### Other changes
- Update some tests.
- Update readme.
## [1.0.2](https://github.com/isaul32/ckeditor5-math/compare/v1.0.1...v1.0.2) (2019-10-07)
### Other changes
- Update readme.
- Add unique identifier for math preview. ([8b6804c](https://github.com/isaul32/ckeditor5-math/commit/98815fc))
## [1.0.1](https://github.com/isaul32/ckeditor5-math/compare/v1.0.0...v1.0.1) (2019-10-04)
### Bug fixes
- Fix preview rendering bug. ([070f84e](https://github.com/isaul32/ckeditor5-math/commit/070f84e))
### Other changes
- Remove paste from office dependency. ([8b6804c](https://github.com/isaul32/ckeditor5-math/commit/8b6804c))
## [1.0.0](https://github.com/isaul32/ckeditor5-math/compare/v1.0.0...v1.0.0) (2019-10-03)
Initial release.

View File

@ -0,0 +1,13 @@
Copyright 2019 Sauli Anto
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -0,0 +1,288 @@
# CKEditor 5 mathematical feature · [![GitHub license](https://img.shields.io/badge/license-ISC-blue.svg)](https://github.com/isaul32/ckeditor5-math/blob/master/LICENSE) [![npm version](https://img.shields.io/npm/v/@isaul32/ckeditor5-math.svg?style=flat)](https://www.npmjs.com/package/@isaul32/ckeditor5-math)
ckeditor5-math is a TeX-based mathematical plugin for CKEditor 5. You can use it to insert, edit, and view mathematical equations and formulas. This plugin supports [MathJax], [KaTeX] and custom typesetting engines.
[mathjax]: https://www.mathjax.org/
[katex]: https://katex.org/
## Table of contents
- [Features](#features)
- [Demos](#demos)
- [Screenshots](#screenshots)
- [Requirements](#requirements)
- [Examples](#examples)
- [Installation](#installation)
- [Styles for Lark theme](#styles-for-lark-theme)
- [Configuration & Usage](#configuration--usage)
- [Plugin options](#plugin-options)
- [Available typesetting engines](#available-typesetting-engines)
- [Supported input and output formats](#supported-input-and-output-formats)
- [Paste support](#paste-support)
- [From plain text](#from-plain-text)
- [Autoformat support](#autoformat-support)
- [Preview workaround](#preview-workaround)
## Features
- Written in TypeScript (as of v41.2.1)
- DLL build support (as of v36.0.3)
- TeX syntax
- Inline and display equations
- Preview view
- Multiple typesetting engines
- Have multiple input and output format
- Paste support
- from plain text
- Autoformat support
# Demos
- [Classic editor with MathJax](https://jsfiddle.net/isaul32/qktj9h7x/)
- [Classic editor with KaTex](https://jsfiddle.net/isaul32/3wjrkLdv/)
- [Balloon block editor with KaTex](https://jsfiddle.net/isaul32/q01mu8kp/)
## Screenshots
![Screenshot 1](/screenshots/1.png?raw=true "Screenshot 1")
![Screenshot 2](/screenshots/2.png?raw=true "Screenshot 2")
## Requirements
- Use same major version as your CKEditor 5 build
If you get duplicated modules error, you have mismatching versions.
## Examples
[Link to examples repository](https://github.com/isaul32/ckeditor5-math-examples)
## Installation
Use official classic or inline build as a base:
- [CKEditor 5 classic editor build](https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-build-classic)
- [CKEditor 5 inline editor build](https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-build-inline)
Install plugin with NPM or Yarn
`npm install @isaul32/ckeditor5-math --save-dev`
`yarn add @isaul32/ckeditor5-math --dev`
Add import into ckeditor.js file
```js
import Math from '@isaul32/ckeditor5-math/src/math';
import AutoformatMath from '@isaul32/ckeditor5-math/src/autoformatmath';
```
Add it to built-in plugins
```js
InlineEditor.builtinPlugins = [
// ...
Math,
AutoformatMath
];
```
**Add math button to toolbar**
```js
InlineEditor.defaultConfig = {
toolbar: {
items: [
// ...
'math'
]
}
};
```
### Styles for Lark theme
**Copy theme/ckeditor5-math folder** from [https://github.com/isaul32/ckeditor5/tree/master/packages/ckeditor5-theme-lark](https://github.com/isaul32/ckeditor5/tree/master/packages/ckeditor5-theme-lark) to your lark theme repository
### Using DLL builds
Use the [official DLL build](https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/alternative-setups/dll-builds.html) and additionally load the math plugin:
```html
<script src="path/to/node_modules/@isaul32/ckeditor5-math/build/math.js"></script>
<script>
CKEditor5.editorClassic.ClassicEditor
.create(editorElement, {
plugins: [
CKEditor5.math.Math,
...
],
...
});
</script>
```
## Configuration & Usage
### Plugin options
```js
InlineEditor.defaultConfig = {
// ...
math: {
engine: 'mathjax', // or katex or function. E.g. (equation, element, display) => { ... }
lazyLoad: undefined, // async () => { ... }, called once before rendering first equation if engine doesn't exist. After resolving promise, plugin renders equations.
outputType: 'script', // or span
className: 'math-tex', // class name to use with span output type, change e.g. MathJax processClass (v2) / processHtmlClass (v3) is set
forceOutputType: false, // forces output to use outputType
enablePreview: true, // Enable preview view
previewClassName: [], // Class names to add to previews
popupClassName: [], // Class names to add to math popup balloon
katexRenderOptions: {} // KaTeX only options for katex.render(ToString)
}
}
```
### Available typesetting engines
**MathJax**
- Tested with **latest 2.7**
- Has experimental (**CHTML**, **SVG**) support for **3.0.0** or newer version
[<img src="https://www.mathjax.org/badge/badge-square.svg" width="130" alt="KaTeX">](https://www.mathjax.org/)
**KaTeX**
- Tested with version **0.12.0**
[<img src="https://katex.org/img/katex-logo-black.svg" width="130" alt="KaTeX">](https://katex.org/)
- `katexRenderOptions` - pass [options](https://katex.org/docs/options.html).
```js
InlineEditor.defaultConfig = {
// ...
math: {
engine: 'katex'
katexRenderOptions: {
macros: {
"\\neq": "\\mathrel{\\char`≠}",
},
},
}
}
```
**Custom typesetting**
Custom typesetting is possible to implement with engine config. For example, custom typesetting feature can be used when use back end rendering.
```js
InlineEditor.defaultConfig = {
// ...
math: {
engine: ( equation, element, display, preview ) => {
// ...
}
}
}
```
- **equation** is equation in TeX format without delimiters.
- **element** is DOM element reserved for rendering.
- **display** is boolean. Typesetting should be inline when false.
- **preview** is boolean. Rendering in preview when true.
### Supported input and output formats
Supported input and output formats are:
```html
<!-- MathJax style http://docs.mathjax.org/en/v2.7-latest/advanced/model.html#how-mathematics-is-stored-in-the-page -->
<script type="math/tex">\sqrt{\frac{a}{b}}</script>
<script type="math/tex; mode=display">\sqrt{\frac{a}{b}}</script>
<!-- CKEditor 4 style https://ckeditor.com/docs/ckeditor4/latest/features/mathjax.html -->
<span class="math-tex">\( \sqrt{\frac{a}{b}} \)</span>
<span class="math-tex">\[ \sqrt{\frac{a}{b}} \]</span>
```
### Paste support
#### From plain text
Paste TeX equations with **delimiters**. For example:
```latex
\[ x=\frac{-b\pm\sqrt{b^2-4ac}}{2a} \]
```
or
```latex
\( x=\frac{-b\pm\sqrt{b^2-4ac}}{2a} \)
```
### Autoformat support
#### Inline mode
Ctrl+M can be used to add easily math formulas in inline mode.
#### Display mode
Autoformat for math can be used to add formula in display mode on a new line by adding `\[` or `$$`. This feature requires additional autoformat plugin to be added.
Add following lines into your build
```js
// ...
import AutoformatMath from '@isaul32/ckeditor5-math/src/autoformatmath';
InlineEditor.builtinPlugins = [
// ...
AutoformatMath
];
```
or use it with DLL build
```html
<script src="path/to/node_modules/@isaul32/ckeditor5-math/build/math.js"></script>
<script>
CKEditor5.editorInline.InlineEditorEditor
.create(editorElement, {
plugins: [
CKEditor5.math.AutoformatMath,
...
],
...
});
</script>
```
## Preview workaround
`.ck-reset_all *` css rules from ckeditor5-ui and ckeditor5-theme-lark break rendering in preview mode.
My solution for this is use rendering element outside of CKEditor DOM and place it to right place by using absolute position. Alternative solution could be using iframe, but then typesetting engine's scripts and styles have to copy to child document.
## Development
Contributions, improvements and bug fixes are welcome. To aid in this, try out
our developer environment w/ live reload support and [CKEditor 5 inspector].
![Development environment](/screenshots/development-environment.png?raw=true "Screenshot of
development environment")
To enter a development loop with hot reload support:
- `git clone https://github.com/isaul32/ckeditor5-math.git`
- `cd ckeditor5-math`
- `yarn`
- `yarn start`
- http://localhost:8080
[ckeditor 5 inspector]: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/development-tools.html#ckeditor-5-inspector

View File

@ -0,0 +1,23 @@
{
"plugins": [
{
"name": "Math",
"className": "Math",
"description": "Adds mathematical formulas to the editor.",
"path": "src/math.js",
"uiComponents": [
{
"name": "math",
"type": "Button",
"iconPath": "theme/icons/math.svg"
}
]
},
{
"name": "AutoformatMath",
"className": "AutoformatMath",
"description": "Implements autoformatting with mathematical formulas.",
"path": "src/autoformatmath.js"
}
]
}

View File

@ -0,0 +1,92 @@
{
"name": "@triliumnext/ckeditor5-math",
"version": "43.2.0-hotfix1",
"description": "Math feature for CKEditor 5.",
"keywords": [
"ckeditor",
"ckeditor5",
"ckeditor 5",
"ckeditor5-feature",
"ckeditor5-plugin",
"ckeditor5-math",
"katex"
],
"main": "src/index.js",
"dependencies": {
"@ckeditor/ckeditor5-core": "43.2.0",
"@ckeditor/ckeditor5-enter": "43.2.0",
"@ckeditor/ckeditor5-typing": "43.2.0",
"@ckeditor/ckeditor5-ui": "43.2.0",
"@ckeditor/ckeditor5-utils": "43.2.0",
"ckeditor5": "43.2.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "43.2.0",
"@ckeditor/ckeditor5-dev-utils": "^43.0.0",
"@ckeditor/ckeditor5-editor-classic": "43.2.0",
"@ckeditor/ckeditor5-engine": "43.2.0",
"@ckeditor/ckeditor5-heading": "43.2.0",
"@ckeditor/ckeditor5-image": "43.2.0",
"@ckeditor/ckeditor5-list": "43.2.0",
"@ckeditor/ckeditor5-paragraph": "43.2.0",
"@ckeditor/ckeditor5-table": "43.2.0",
"@ckeditor/ckeditor5-theme-lark": "43.2.0",
"typescript": "5.0.4",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=5.7.1"
},
"author": "Sauli Anto",
"license": "ISC",
"bugs": "https://github.com/TriliumNext/ckeditor5-math/issues",
"repository": {
"type": "git",
"url": "https://github.com/TriliumNext/ckeditor5-math.git"
},
"files": [
"lang",
"src/**/*.js",
"src/**/*.d.ts",
"build",
"theme",
"ckeditor5-metadata.json",
"CHANGELOG.md"
],
"scripts": {
"ts:build": "tsc -p ./tsconfig.release.json",
"ts:clear": "npx rimraf \"src/**/*.@(js|d.ts)\"",
"dll:build": "ckeditor5-package-tools dll:build",
"dll:serve": "http-server ./ -o sample/dll.html",
"lint": "eslint --quiet --ext .ts src/",
"lint:fix": "eslint --quiet --fix --ext .ts src/",
"stylelint": "stylelint --quiet --allow-empty-input 'theme/**/*.css'",
"test": "ckeditor5-package-tools test",
"prepare": "yarn run dll:build",
"prepublishOnly": "yarn run ts:build && ckeditor5-package-tools export-package-as-javascript",
"postpublish": "yarn run ts:clear && ckeditor5-package-tools export-package-as-typescript",
"start": "ckeditor5-package-tools start"
},
"lint-staged": {
"**/*.ts": [
"eslint --quiet"
],
"**/*.css": [
"stylelint --quiet --allow-empty-input"
]
},
"eslintIgnore": [
"node_modules/**",
"packages/*/node_modules/**",
"packages/*/build/**",
"packages/*/src/lib/**"
],
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"packageManager": "yarn@1.22.22"
}

View File

@ -0,0 +1,113 @@
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter';
import Indent from '@ckeditor/ckeditor5-indent/src/indent';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Link from '@ckeditor/ckeditor5-link/src/link';
import List from '@ckeditor/ckeditor5-list/src/list';
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock';
import Code from '@ckeditor/ckeditor5-basic-styles/src/code';
import { Math, AutoformatMath } from '../src/index';
/* global document, window */
ClassicEditor
.create( document.querySelector( '#editor' ), {
math: {
engine: 'katex',
katexRenderOptions: {
macros: {
'\\test': '\\mathrel{\\char`≠}'
}
}
},
plugins: [
Math,
AutoformatMath,
Essentials,
Autoformat,
BlockQuote,
Bold,
Heading,
Image,
ImageCaption,
ImageStyle,
ImageToolbar,
ImageUpload,
Indent,
Italic,
Link,
List,
MediaEmbed,
Paragraph,
Table,
TableToolbar,
CodeBlock,
Code,
Base64UploadAdapter
],
toolbar: [
'math',
'|',
'heading',
'|',
'bold',
'italic',
'link',
'code',
'bulletedList',
'numberedList',
'|',
'outdent',
'indent',
'|',
'uploadImage',
'blockQuote',
'insertTable',
'mediaEmbed',
'codeBlock',
'|',
'undo',
'redo'
],
image: {
toolbar: [
'imageStyle:inline',
'imageStyle:block',
'imageStyle:side',
'|',
'imageTextAlternative'
]
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
}
} )
.then( editor => {
window.editor = editor;
CKEditorInspector.attach( editor );
window.console.log( 'CKEditor 5 is ready.', editor );
} )
.catch( err => {
window.console.error( err.stack );
} );

View File

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/png" href="https://ckeditor.com/docs/ckeditor5/latest/assets/img/favicons/32x32.png" sizes="32x32">
<meta charset="utf-8">
<title>CKEditor 5 with ckeditor5-math DLL Sample</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/katex.min.css"
integrity="sha384-L+Gq2Cso/Y2x8fX4wausgiZT8z0QPZz7OqPuz4YqAycQJyrJT9NRLpjFBD6zlOia"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/katex.min.js"
integrity="sha384-z64WtjpyrKFsxox9eI4SI8eM9toXdoYeWb5Qh+8PO+eG54Bv9BZqf9xNhlcLf/sA"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/contrib/auto-render.min.js"
integrity="sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl"
crossorigin="anonymous"
onload="renderMathInElement(document.body, {'macros': {'\\test': '\\mathrel{\\char`≠}'}});"
></script>
<style>body { max-width: 800px; margin: 20px auto; }</style>
</head>
<body>
<h1>CKEditor 5 with ckeditor5-math DLL Sample</h1>
<div id="editor">
<h2>Production sample</h2>
<p>
This is a demo of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic editor build</a> that loads the <code>Math</code> and <code>AutoformatMath</code> plugin.
</p>
<p>
<code>Math</code> inserts mathematical formulas into the editor. You can click the CKEditor 5 Math icon in the toolbar and see the results.
</p>
<p><script type="math/tex">e=mc^2</script></p>
<p><script type="math/tex; mode=display">e=mc^2</script></p>
<p>
This should show "\test" as ≠ via katexRenderOptions.macros:
<script type="math/tex">\test</script>
</p>
<!-- Quill Style Tag -->
<p><span class="ql-formula" data-value="e=mc^2"></span></p>
</div>
<!-- DLL builds are served from the `node_modules/` directory -->
<script src="../node_modules/ckeditor5/build/ckeditor5-dll.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-editor-classic/build/editor-classic.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-code-block/build/code-block.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-essentials/build/essentials.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-basic-styles/build/basic-styles.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-heading/build/heading.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-autoformat/build/autoformat.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-block-quote/build/block-quote.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-image/build/image.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-link/build/link.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-indent/build/indent.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-media-embed/build/media-embed.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-list/build/list.js"></script>
<script src="../node_modules/@ckeditor/ckeditor5-table/build/table.js"></script>
<!-- The "ckeditor5-math" package DLL build is served from the `build/` directory -->
<script src="../build/math.js"></script>
<script>
console.log( 'Objects exported by the DLL build:', CKEditor5.math );
CKEditor5.editorClassic.ClassicEditor
.create( document.querySelector( '#editor' ), {
math: {
engine: 'katex',
katexRenderOptions: {
macros: {
'\\test': '\\mathrel{\\char`≠}'
}
}
},
plugins: [
CKEditor5.math.Math,
CKEditor5.math.AutoformatMath,
CKEditor5.essentials.Essentials,
CKEditor5.autoformat.Autoformat,
CKEditor5.blockQuote.BlockQuote,
CKEditor5.basicStyles.Bold,
CKEditor5.heading.Heading,
CKEditor5.image.Image,
CKEditor5.image.ImageCaption,
CKEditor5.image.ImageStyle,
CKEditor5.image.ImageToolbar,
CKEditor5.image.ImageUpload,
CKEditor5.indent.Indent,
CKEditor5.basicStyles.Italic,
CKEditor5.link.Link,
CKEditor5.list.List,
CKEditor5.mediaEmbed.MediaEmbed,
CKEditor5.paragraph.Paragraph,
CKEditor5.table.Table,
CKEditor5.table.TableToolbar,
CKEditor5.codeBlock.CodeBlock,
CKEditor5.basicStyles.Code,
CKEditor5.upload.Base64UploadAdapter
],
toolbar: [
'math',
'|',
'heading',
'|',
'bold',
'italic',
'link',
'code',
'bulletedList',
'numberedList',
'|',
'outdent',
'indent',
'|',
'uploadImage',
'blockQuote',
'insertTable',
'mediaEmbed',
'codeBlock',
'|',
'undo',
'redo'
],
image: {
toolbar: [
'imageStyle:inline',
'imageStyle:block',
'imageStyle:side',
'|',
'imageTextAlternative'
]
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
}
} )
.then( editor => {
window.editor = editor;
} )
.catch( error => {
console.error( 'There was a problem initializing the editor.', error );
} );
</script>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/png" href="https://ckeditor.com/docs/ckeditor5/latest/assets/img/favicons/32x32.png" sizes="32x32">
<meta charset="utf-8">
<title>CKEditor 5 with ckeditor5-math Development Sample</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/katex.min.css"
integrity="sha384-L+Gq2Cso/Y2x8fX4wausgiZT8z0QPZz7OqPuz4YqAycQJyrJT9NRLpjFBD6zlOia"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/katex.min.js"
integrity="sha384-z64WtjpyrKFsxox9eI4SI8eM9toXdoYeWb5Qh+8PO+eG54Bv9BZqf9xNhlcLf/sA"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/contrib/auto-render.min.js"
integrity="sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl"
crossorigin="anonymous"
onload="renderMathInElement(document.body, {'macros': {'\\test': '\\mathrel{\\char`≠}'}});"
></script>
<style>body { max-width: 800px; margin: 20px auto; }</style>
</head>
<body>
<h1>CKEditor 5 with ckeditor5-math Development Sample</h1>
<div id="editor">
<h2>Development environment</h2>
<p>
This is a demo of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic editor build</a> that loads the <code>Math</code> and <code>AutoformatMath</code> plugin.
</p>
<p>
<code>Math</code> inserts mathematical formulas into the editor. You can click the CKEditor 5 Math icon in the toolbar and see the results.
</p>
<p>
<script type="math/tex">e=mc^2</script>
</p>
<p>
<script type="math/tex; mode=display">e=mc^2</script>
</p>
<p>
This should show "\test" as ≠ via katexRenderOptions.macros:
<script type="math/tex">\test</script>
</p>
<!-- Quill Style Tag -->
<p>
<span class="ql-formula" data-value="e=mc^2"></span>
</p>
<h3>The directory structure</h3>
<p>
The code snippet below presents the directory structure.
</p>
<pre><code class="language-plaintext">.
├─ sample
│ ├─ dll.html # The editor initialized using the DLL builds. Check README for details.
│ ├─ index.html # The currently displayed file.
│ └─ ckeditor.js # The editor initialization script.
├─ src
│ ├─ autoformatmath.js # The AutoformatMath plugin.
│ ├─ math.js # The Math plugin.
│ ├─ index.js # The modules exported by the package when using the DLL builds.
│ └─ **/*.js # JavaScript source files.
├─ tests
│ └─ **/*.js # Test files
├─ theme
│ ├─ icons
│ │ ├─ math.svg # The Math icon displayed in the toolbar.
│ └─ mathform.css # Math editor styles.
├─ .editorconfig
├─ ...
└─ README.md</code></pre>
<h3>Reporting issues</h3>
<p>If you found a problem with CKEditor 5 or the package generator, please, report an issue:</p>
<p><a href="https://github.com/isaul32/ckeditor5-math/issues">CKEditor 5 Math</a></p>
</div>
<script src="./ckeditor.dist.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,37 @@
import Math from './math';
import MathCommand from './mathcommand';
import MathEditing from './mathediting';
import MathUI from './mathui';
import type { KatexOptions } from './typings-external';
declare module '@ckeditor/ckeditor5-core' {
interface PluginsMap {
[ Math.pluginName ]: Math;
[ MathEditing.pluginName ]: MathEditing;
[ MathUI.pluginName ]: MathUI;
}
interface CommandsMap {
math: MathCommand;
}
interface EditorConfig {
math?: {
engine?:
| 'mathjax'
| 'katex'
| ( ( equation: string, element: HTMLElement, display: boolean ) => void )
| undefined;
lazyLoad?: undefined | ( () => Promise<void> );
outputType?: 'script' | 'span' | undefined;
className?: string | undefined;
forceOutputType?: boolean | undefined;
enablePreview?: boolean | undefined;
previewClassName?: Array<string> | undefined;
popupClassName?: Array<string> | undefined;
katexRenderOptions?: Partial<KatexOptions> | undefined;
};
}
}

View File

@ -0,0 +1,59 @@
import { Plugin } from 'ckeditor5/src/core';
import { global, logWarning } from 'ckeditor5/src/utils';
// eslint-disable-next-line ckeditor5-rules/allow-imports-only-from-main-package-entry-point
import blockAutoformatEditing from '@ckeditor/ckeditor5-autoformat/src/blockautoformatediting';
import Math from './math';
import MathCommand from './mathcommand';
import MathUI from './mathui';
export default class AutoformatMath extends Plugin {
public static get requires() {
return [ Math, 'Autoformat' ] as const;
}
/**
* @inheritDoc
*/
public init(): void {
const editor = this.editor;
if ( !editor.plugins.has( 'Math' ) ) {
logWarning( 'autoformat-math-feature-missing', editor );
}
}
public afterInit(): void {
const editor = this.editor;
const command = editor.commands.get( 'math' );
if ( command instanceof MathCommand ) {
const callback = () => {
if ( !command.isEnabled ) {
return false;
}
command.display = true;
// Wait until selection is removed.
window.setTimeout(
() => {
const mathUIInstance = editor.plugins.get( 'MathUI' );
if ( mathUIInstance instanceof MathUI ) {
mathUIInstance._showUI();
}
},
50
);
};
// @ts-expect-error: blockAutoformatEditing expects an Autoformat instance even though it works with any Plugin instance
blockAutoformatEditing( editor, this, /^\$\$$/, callback );
// @ts-expect-error: blockAutoformatEditing expects an Autoformat instance even though it works with any Plugin instance
blockAutoformatEditing( editor, this, /^\\\[$/, callback );
}
}
public static get pluginName() {
return 'AutoformatMath' as const;
}
}

View File

@ -0,0 +1,137 @@
import { Clipboard } from 'ckeditor5/src/clipboard';
import { Plugin, type Editor } from 'ckeditor5/src/core';
import { LivePosition, LiveRange } from 'ckeditor5/src/engine';
import { Undo } from 'ckeditor5/src/undo';
import { global } from 'ckeditor5/src/utils';
import { extractDelimiters, hasDelimiters, delimitersCounts } from './utils';
export default class AutoMath extends Plugin {
public static get requires() {
return [ Clipboard, Undo ] as const;
}
public static get pluginName() {
return 'AutoMath' as const;
}
private _timeoutId: null | number;
private _positionToInsert: null | LivePosition;
constructor( editor: Editor ) {
super( editor );
this._timeoutId = null;
this._positionToInsert = null;
}
public init(): void {
const editor = this.editor;
const modelDocument = editor.model.document;
this.listenTo( editor.plugins.get( Clipboard ), 'inputTransformation', () => {
const firstRange = modelDocument.selection.getFirstRange();
if ( !firstRange ) {
return;
}
const leftLivePosition = LivePosition.fromPosition( firstRange.start );
leftLivePosition.stickiness = 'toPrevious';
const rightLivePosition = LivePosition.fromPosition( firstRange.end );
rightLivePosition.stickiness = 'toNext';
modelDocument.once( 'change:data', () => {
this._mathBetweenPositions(
leftLivePosition,
rightLivePosition
);
leftLivePosition.detach();
rightLivePosition.detach();
},
{ priority: 'high' }
);
}
);
editor.commands.get( 'undo' )?.on( 'execute', () => {
if ( this._timeoutId ) {
window.clearTimeout( this._timeoutId );
this._positionToInsert?.detach();
this._timeoutId = null;
this._positionToInsert = null;
}
}, { priority: 'high' } );
}
private _mathBetweenPositions(
leftPosition: LivePosition,
rightPosition: LivePosition
) {
const editor = this.editor;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mathConfig = this.editor.config.get( 'math' )!;
const equationRange = new LiveRange( leftPosition, rightPosition );
const walker = equationRange.getWalker( { ignoreElementEnd: true } );
let text = '';
// Get equation text
for ( const node of walker ) {
if ( node.item.is( '$textProxy' ) ) {
text += node.item.data;
}
}
text = text.trim();
// Skip if don't have delimiters
if ( !hasDelimiters( text ) || delimitersCounts( text ) !== 2 ) {
return;
}
const mathCommand = editor.commands.get( 'math' );
// Do not anything if math element cannot be inserted at the current position
if ( !mathCommand?.isEnabled ) {
return;
}
this._positionToInsert = LivePosition.fromPosition( leftPosition );
// With timeout user can undo conversation if want use plain text
this._timeoutId = window.setTimeout( () => {
editor.model.change( writer => {
this._timeoutId = null;
writer.remove( equationRange );
let insertPosition: LivePosition | null;
// Check if position where the math element should be inserted is still valid.
if ( this._positionToInsert?.root.rootName !== '$graveyard' ) {
insertPosition = this._positionToInsert;
}
editor.model.change( innerWriter => {
const params = Object.assign( extractDelimiters( text ), {
type: mathConfig.outputType
} );
const mathElement = innerWriter.createElement( params.display ? 'mathtex-display' : 'mathtex-inline', params
);
editor.model.insertContent( mathElement, insertPosition );
innerWriter.setSelection( mathElement, 'on' );
} );
this._positionToInsert?.detach();
this._positionToInsert = null;
} );
}, 100 );
}
}

View File

@ -0,0 +1,8 @@
/**
* @module math
*/
export { default as Math } from './math';
export { default as AutoformatMath } from './autoformatmath';
import "./augmentation.js";

View File

@ -0,0 +1,16 @@
import { Plugin } from 'ckeditor5/src/core';
import { Widget } from 'ckeditor5/src/widget';
import MathUI from './mathui';
import MathEditing from './mathediting';
import AutoMath from './automath';
export default class Math extends Plugin {
public static get requires() {
return [ MathEditing, MathUI, AutoMath, Widget ] as const;
}
public static get pluginName() {
return 'Math' as const;
}
}

View File

@ -0,0 +1,64 @@
import { Command } from 'ckeditor5/src/core';
import { getSelectedMathModelWidget } from './utils';
export default class MathCommand extends Command {
public override value: string | null = null;
public override execute(
equation: string,
display?: boolean,
outputType: 'script' | 'span' = 'script',
forceOutputType?: boolean
): void {
const model = this.editor.model;
const selection = model.document.selection;
const selectedElement = selection.getSelectedElement();
model.change( writer => {
let mathtex;
if (
selectedElement &&
( selectedElement.is( 'element', 'mathtex-inline' ) ||
selectedElement.is( 'element', 'mathtex-display' ) )
) {
// Update selected element
const typeAttr = selectedElement.getAttribute( 'type' );
// Use already set type if found and is not forced
const type = forceOutputType ?
outputType :
typeAttr || outputType;
mathtex = writer.createElement(
display ? 'mathtex-display' : 'mathtex-inline',
{ equation, type, display }
);
} else {
// Create new model element
mathtex = writer.createElement(
display ? 'mathtex-display' : 'mathtex-inline',
{ equation, type: outputType, display }
);
}
model.insertContent( mathtex );
} );
}
public display = false;
public override refresh(): void {
const model = this.editor.model;
const selection = model.document.selection;
const selectedElement = selection.getSelectedElement();
this.isEnabled =
selectedElement === null ||
selectedElement.is( 'element', 'mathtex-inline' ) ||
selectedElement.is( 'element', 'mathtex-display' );
const selectedEquation = getSelectedMathModelWidget( selection );
const value = selectedEquation?.getAttribute( 'equation' );
this.value = typeof value === 'string' ? value : null;
const display = selectedEquation?.getAttribute( 'display' );
this.display = typeof display === 'boolean' ? display : false;
}
}

View File

@ -0,0 +1,307 @@
import MathCommand from './mathcommand';
import { type Editor, Plugin } from 'ckeditor5/src/core';
import {
toWidget,
Widget,
viewToModelPositionOutsideModelElement
} from 'ckeditor5/src/widget';
import { renderEquation, extractDelimiters } from './utils';
import type { DowncastWriter, Element } from 'ckeditor5/src/engine';
import { CKEditorError, uid } from 'ckeditor5/src/utils';
export default class MathEditing extends Plugin {
public static get requires() {
return [ Widget ] as const;
}
public static get pluginName() {
return 'MathEditing' as const;
}
constructor( editor: Editor ) {
super( editor );
editor.config.define( 'math', {
engine: 'mathjax',
outputType: 'script',
className: 'math-tex',
forceOutputType: false,
enablePreview: true,
previewClassName: [],
popupClassName: [],
katexRenderOptions: {}
} );
}
public init(): void {
const editor = this.editor;
editor.commands.add( 'math', new MathCommand( editor ) );
this._defineSchema();
this._defineConverters();
editor.editing.mapper.on(
'viewToModelPosition',
viewToModelPositionOutsideModelElement(
editor.model,
viewElement => viewElement.hasClass( 'math' )
)
);
}
private _defineSchema() {
const schema = this.editor.model.schema;
schema.register( 'mathtex-inline', {
allowWhere: '$text',
isInline: true,
isObject: true,
allowAttributes: [ 'equation', 'type', 'display' ]
} );
schema.register( 'mathtex-display', {
inheritAllFrom: '$blockObject',
allowAttributes: [ 'equation', 'type', 'display' ]
} );
}
private _defineConverters() {
const conversion = this.editor.conversion;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mathConfig = this.editor.config.get( 'math' )!;
// View -> Model
conversion
.for( 'upcast' )
// MathJax inline way (e.g. <script type="math/tex">\sqrt{\frac{a}{b}}</script>)
.elementToElement( {
view: {
name: 'script',
attributes: {
type: 'math/tex'
}
},
model: ( viewElement, { writer } ) => {
const child = viewElement.getChild( 0 );
if ( child?.is( '$text' ) ) {
const equation = child.data.trim();
return writer.createElement( 'mathtex-inline', {
equation,
type: mathConfig.forceOutputType ?
mathConfig.outputType :
'script',
display: false
} );
}
return null;
}
} )
// MathJax display way (e.g. <script type="math/tex; mode=display">\sqrt{\frac{a}{b}}</script>)
.elementToElement( {
view: {
name: 'script',
attributes: {
type: 'math/tex; mode=display'
}
},
model: ( viewElement, { writer } ) => {
const child = viewElement.getChild( 0 );
if ( child?.is( '$text' ) ) {
const equation = child.data.trim();
return writer.createElement( 'mathtex-display', {
equation,
type: mathConfig.forceOutputType ?
mathConfig.outputType :
'script',
display: true
} );
}
return null;
}
} )
// CKEditor 4 way (e.g. <span class="math-tex">\( \sqrt{\frac{a}{b}} \)</span>)
.elementToElement( {
view: {
name: 'span',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
classes: [ mathConfig.className! ]
},
model: ( viewElement, { writer } ) => {
const child = viewElement.getChild( 0 );
if ( child?.is( '$text' ) ) {
const equation = child.data.trim();
const params = Object.assign( extractDelimiters( equation ), {
type: mathConfig.forceOutputType ?
mathConfig.outputType :
'span'
} );
return writer.createElement(
params.display ? 'mathtex-display' : 'mathtex-inline',
params
);
}
return null;
}
} )
// KaTeX from Quill: https://github.com/quilljs/quill/blob/develop/formats/formula.js
.elementToElement( {
view: {
name: 'span',
classes: [ 'ql-formula' ]
},
model: ( viewElement, { writer } ) => {
const equation = viewElement.getAttribute( 'data-value' );
if ( equation == null ) {
/**
* Couldn't find equation on current element
* @error missing-equation
*/
throw new CKEditorError( 'missing-equation', { pluginName: 'math' } );
}
return writer.createElement( 'mathtex-inline', {
equation: equation.trim(),
type: mathConfig.forceOutputType ?
mathConfig.outputType :
'script',
display: false
} );
}
} );
// Model -> View (element)
conversion
.for( 'editingDowncast' )
.elementToElement( {
model: 'mathtex-inline',
view: ( modelItem, { writer } ) => {
const widgetElement = createMathtexEditingView(
modelItem,
writer
);
return toWidget( widgetElement, writer );
}
} )
.elementToElement( {
model: 'mathtex-display',
view: ( modelItem, { writer } ) => {
const widgetElement = createMathtexEditingView(
modelItem,
writer
);
return toWidget( widgetElement, writer );
}
} );
// Model -> Data
conversion
.for( 'dataDowncast' )
.elementToElement( {
model: 'mathtex-inline',
view: createMathtexView
} )
.elementToElement( {
model: 'mathtex-display',
view: createMathtexView
} );
// Create view for editor
function createMathtexEditingView(
modelItem: Element,
writer: DowncastWriter
) {
const equation = String( modelItem.getAttribute( 'equation' ) );
const display = !!modelItem.getAttribute( 'display' );
const styles =
'user-select: none; ' +
( display ? '' : 'display: inline-block;' );
const classes =
'ck-math-tex ' +
( display ? 'ck-math-tex-display' : 'ck-math-tex-inline' );
const mathtexView = writer.createContainerElement(
display ? 'div' : 'span',
{
style: styles,
class: classes
}
);
const uiElement = writer.createUIElement(
'div',
null,
function( domDocument ) {
const domElement = this.toDomElement( domDocument );
void renderEquation(
equation,
domElement,
mathConfig.engine,
mathConfig.lazyLoad,
display,
false,
`math-editing-${ uid() }`,
mathConfig.previewClassName,
mathConfig.katexRenderOptions
);
return domElement;
}
);
writer.insert( writer.createPositionAt( mathtexView, 0 ), uiElement );
return mathtexView;
}
// Create view for data
function createMathtexView(
modelItem: Element,
{ writer }: { writer: DowncastWriter }
) {
const equation = modelItem.getAttribute( 'equation' );
if ( typeof equation != 'string' ) {
/**
* Couldn't find equation on current element
* @error missing-equation
*/
throw new CKEditorError( 'missing-equation', { pluginName: 'math' } );
}
const type = modelItem.getAttribute( 'type' );
const display = modelItem.getAttribute( 'display' );
if ( type === 'span' ) {
const mathtexView = writer.createContainerElement( 'span', {
class: mathConfig.className
} );
if ( display ) {
writer.insert(
writer.createPositionAt( mathtexView, 0 ),
writer.createText( '\\[' + equation + '\\]' )
);
} else {
writer.insert(
writer.createPositionAt( mathtexView, 0 ),
writer.createText( '\\(' + equation + '\\)' )
);
}
return mathtexView;
} else {
const mathtexView = writer.createContainerElement( 'script', {
type: display ? 'math/tex; mode=display' : 'math/tex'
} );
writer.insert(
writer.createPositionAt( mathtexView, 0 ),
writer.createText( equation )
);
return mathtexView;
}
}
}
}

View File

@ -0,0 +1,288 @@
import MathEditing from './mathediting';
import MainFormView from './ui/mainformview';
import mathIcon from '../theme/icons/math.svg';
import { Plugin } from 'ckeditor5/src/core';
import { ClickObserver } from 'ckeditor5/src/engine';
import {
ButtonView,
ContextualBalloon,
clickOutsideHandler
} from 'ckeditor5/src/ui';
import { CKEditorError, global, uid } from 'ckeditor5/src/utils';
import { getBalloonPositionData } from './utils';
import MathCommand from './mathcommand';
const mathKeystroke = 'Ctrl+M';
export default class MathUI extends Plugin {
public static get requires() {
return [ ContextualBalloon, MathEditing ] as const;
}
public static get pluginName() {
return 'MathUI' as const;
}
private _previewUid = `math-preview-${ uid() }`;
private _balloon: ContextualBalloon = this.editor.plugins.get( ContextualBalloon );
public formView: MainFormView | null = null;
public init(): void {
const editor = this.editor;
editor.editing.view.addObserver( ClickObserver );
this._createToolbarMathButton();
this.formView = this._createFormView();
this._enableUserBalloonInteractions();
}
public override destroy(): void {
super.destroy();
this.formView?.destroy();
// Destroy preview element
const previewEl = document.getElementById( this._previewUid );
if ( previewEl ) {
previewEl.parentNode?.removeChild( previewEl );
}
}
public _showUI(): void {
const editor = this.editor;
const mathCommand = editor.commands.get( 'math' );
if ( !mathCommand?.isEnabled ) {
return;
}
this._addFormView();
this._balloon.showStack( 'main' );
}
private _createFormView() {
const editor = this.editor;
const mathCommand = editor.commands.get( 'math' );
if ( !( mathCommand instanceof MathCommand ) ) {
/**
* Missing Math command
* @error math-command
*/
throw new CKEditorError( 'math-command' );
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mathConfig = editor.config.get( 'math' )!;
const formView = new MainFormView(
editor.locale,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mathConfig.engine!,
mathConfig.lazyLoad,
mathConfig.enablePreview,
this._previewUid,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mathConfig.previewClassName!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mathConfig.popupClassName!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mathConfig.katexRenderOptions!
);
formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' );
formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' );
// Form elements should be read-only when corresponding commands are disabled.
formView.mathInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value );
formView.saveButtonView.bind( 'isEnabled' ).to( mathCommand );
formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand );
// Listen to submit button click
this.listenTo( formView, 'submit', () => {
editor.execute( 'math', formView.equation, formView.displayButtonView.isOn, mathConfig.outputType, mathConfig.forceOutputType );
this._closeFormView();
} );
// Listen to cancel button click
this.listenTo( formView, 'cancel', () => {
this._closeFormView();
} );
// Close plugin ui, if esc is pressed (while ui is focused)
formView.keystrokes.set( 'esc', ( _data, cancel ) => {
this._closeFormView();
cancel();
} );
return formView;
}
private _addFormView() {
if ( this._isFormInPanel ) {
return;
}
const editor = this.editor;
const mathCommand = editor.commands.get( 'math' );
if ( !( mathCommand instanceof MathCommand ) ) {
/**
* Math command not found
* @error plugin-load
*/
throw new CKEditorError( 'plugin-load', { pluginName: 'math' } );
}
if ( this.formView == null ) {
return;
}
this._balloon.add( {
view: this.formView,
position: getBalloonPositionData( editor )
} );
if ( this._balloon.visibleView === this.formView ) {
this.formView.mathInputView.fieldView.element?.select();
}
// Show preview element
const previewEl = document.getElementById( this._previewUid );
if ( previewEl && this.formView.previewEnabled ) {
// Force refresh preview
this.formView.mathView?.updateMath();
}
this.formView.equation = mathCommand.value ?? '';
this.formView.displayButtonView.isOn = mathCommand.display || false;
}
/**
* @private
*/
public _hideUI(): void {
if ( !this._isFormInPanel ) {
return;
}
const editor = this.editor;
this.stopListening( editor.ui, 'update' );
this.stopListening( this._balloon, 'change:visibleView' );
editor.editing.view.focus();
// Remove form first because it's on top of the stack.
this._removeFormView();
}
private _closeFormView() {
const mathCommand = this.editor.commands.get( 'math' );
if ( mathCommand?.value != null ) {
this._removeFormView();
} else {
this._hideUI();
}
}
private _removeFormView() {
if ( this._isFormInPanel && this.formView ) {
this.formView.saveButtonView.focus();
this._balloon.remove( this.formView );
// Hide preview element
const previewEl = document.getElementById( this._previewUid );
if ( previewEl ) {
previewEl.style.visibility = 'hidden';
}
this.editor.editing.view.focus();
}
}
private _createToolbarMathButton() {
const editor = this.editor;
const mathCommand = editor.commands.get( 'math' );
if ( !mathCommand ) {
/**
* Math command not found
* @error plugin-load
*/
throw new CKEditorError( 'plugin-load', { pluginName: 'math' } );
}
const t = editor.t;
// Handle the `Ctrl+M` keystroke and show the panel.
editor.keystrokes.set( mathKeystroke, ( _keyEvtData, cancel ) => {
// Prevent focusing the search bar in FF and opening new tab in Edge. #153, #154.
cancel();
if ( mathCommand.isEnabled ) {
this._showUI();
}
} );
this.editor.ui.componentFactory.add( 'math', locale => {
const button = new ButtonView( locale );
button.isEnabled = true;
button.label = t( 'Insert math' );
button.icon = mathIcon;
button.keystroke = mathKeystroke;
button.tooltip = true;
button.isToggleable = true;
button.bind( 'isEnabled' ).to( mathCommand, 'isEnabled' );
this.listenTo( button, 'execute', () => {
this._showUI();
} );
return button;
} );
}
private _enableUserBalloonInteractions() {
const editor = this.editor;
const viewDocument = this.editor.editing.view.document;
this.listenTo( viewDocument, 'click', () => {
const mathCommand = editor.commands.get( 'math' );
if ( mathCommand?.isEnabled && mathCommand.value ) {
this._showUI();
}
} );
// Close the panel on the Esc key press when the editable has focus and the balloon is visible.
editor.keystrokes.set( 'Esc', ( _data, cancel ) => {
if ( this._isUIVisible ) {
this._hideUI();
cancel();
}
} );
// Close on click outside of balloon panel element.
if ( this.formView ) {
clickOutsideHandler( {
emitter: this.formView,
activator: () => !!this._isFormInPanel,
contextElements: this._balloon.view.element ? [ this._balloon.view.element ] : [],
callback: () => { this._hideUI(); }
} );
} else {
throw new Error( 'missing form view' );
}
}
private get _isUIVisible() {
const visibleView = this._balloon.visibleView;
return visibleView == this.formView;
}
private get _isFormInPanel() {
return this.formView && this._balloon.hasView( this.formView );
}
}

View File

@ -0,0 +1,156 @@
/**
* Basic typings for third party, external libraries (KaTeX, MathJax).
*/
export interface MathJax3 {
version: string;
tex2chtmlPromise?: ( input: string, options: { display: boolean } ) => Promise<HTMLElement>;
tex2svgPromise?: ( input: string, options: { display: boolean } ) => Promise<HTMLElement>;
}
export interface MathJax2 {
Hub: { Queue: ( callback: [string, MathJax2['Hub'], string | HTMLElement] | ( () => void ) ) => void };
}
export interface Katex {
render( equation: string, el: HTMLElement, options: KatexOptions ): void;
}
declare global {
// eslint-disable-next-line no-var
var CKEDITOR_MATH_LAZY_LOAD: undefined | Promise<void>;
// eslint-disable-next-line no-var
var MathJax: undefined | MathJax2 | MathJax3;
// eslint-disable-next-line no-var
var katex: undefined | Katex;
}
export interface KatexOptions {
/**
* If `true`, math will be rendered in display mode
* (math in display style and center math on page)
*
* If `false`, math will be rendered in inline mode
* @default false
*/
displayMode?: boolean | undefined;
/**
* Determines the markup language of the output. The valid choices are:
* - `html`: Outputs KaTeX in HTML only.
* - `mathml`: Outputs KaTeX in MathML only.
* - `htmlAndMathml`: Outputs HTML for visual rendering
* and includes MathML for accessibility.
*
* @default 'htmlAndMathml'
*/
output?: 'html' | 'mathml' | 'htmlAndMathml' | undefined;
/**
* If `true`, display math has \tags rendered on the left
* instead of the right, like \usepackage[leqno]{amsmath} in LaTeX.
*
* @default false
*/
leqno?: boolean | undefined;
/**
* If `true`, display math renders flush left with a 2em left margin,
* like \documentclass[fleqn] in LaTeX with the amsmath package.
*
* @default false
*/
fleqn?: boolean | undefined;
/**
* If `true`, KaTeX will throw a `ParseError` when
* it encounters an unsupported command or invalid LaTex
*
* If `false`, KaTeX will render unsupported commands as
* text, and render invalid LaTeX as its source code with
* hover text giving the error, in color given by errorColor
* @default true
*/
throwOnError?: boolean | undefined;
/**
* A Color string given in format `#XXX` or `#XXXXXX`
*/
errorColor?: string | undefined;
/**
* A collection of custom macros.
*
* See `src/macros.js` for its usage
*/
macros?: unknown;
/**
* Specifies a minimum thickness, in ems, for fraction lines,
* \sqrt top lines, {array} vertical lines, \hline, \hdashline,
* \underline, \overline, and the borders of \fbox, \boxed, and
* \fcolorbox.
*/
minRuleThickness?: number | undefined;
/**
* If `true`, `\color` will work like LaTeX's `\textcolor`
* and takes 2 arguments
*
* If `false`, `\color` will work like LaTeX's `\color`
* and takes 1 argument
*
* In both cases, `\textcolor` works as in LaTeX
*
* @default false
*/
colorIsTextColor?: boolean | undefined;
/**
* All user-specified sizes will be caped to `maxSize` ems
*
* If set to Infinity, users can make elements and space
* arbitrarily large
*
* @default Infinity
*/
maxSize?: number | undefined;
/**
* Limit the number of macro expansions to specified number
*
* If set to `Infinity`, marco expander will try to fully expand
* as in LaTex
*
* @default 1000
*/
maxExpand?: number | undefined;
/**
* If `false` or `"ignore"`, allow features that make
* writing in LaTex convenient but not supported by LaTex
*
* If `true` or `"error"`, throw an error for such transgressions
*
* If `"warn"`, warn about behavior via `console.warn`
*
* @default "warn"
*/
strict?: boolean | string | Function | undefined;
/**
* If `false` (do not trust input), prevent any commands that could enable adverse behavior, rendering them instead in errorColor.
*
* If `true` (trust input), allow all such commands.
*
* @default false
*/
trust?: boolean | ( ( context: object ) => boolean ) | undefined;
/**
* Place KaTeX code in the global group.
*
* @default false
*/
globalGroup?: boolean | undefined;
}

View File

@ -0,0 +1,284 @@
import { icons } from 'ckeditor5/src/core';
import {
ButtonView,
createLabeledInputText,
FocusCycler,
LabelView,
LabeledFieldView,
submitHandler,
SwitchButtonView,
View,
ViewCollection,
type InputTextView,
type FocusableView
} from 'ckeditor5/src/ui';
import { Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5/src/utils';
import { extractDelimiters, hasDelimiters } from '../utils';
import MathView from './mathview';
import '../../theme/mathform.css';
import type { KatexOptions } from '../typings-external';
const { check: checkIcon, cancel: cancelIcon } = icons;
class MathInputView extends LabeledFieldView<InputTextView> {
public value: null | string = null;
public isReadOnly = false;
constructor( locale: Locale ) {
super( locale, createLabeledInputText );
}
}
export default class MainFormView extends View {
public saveButtonView: ButtonView;
public mathInputView: MathInputView;
public displayButtonView: SwitchButtonView;
public cancelButtonView: ButtonView;
public previewEnabled: boolean;
public previewLabel?: LabelView;
public mathView?: MathView;
public override locale: Locale = new Locale();
public lazyLoad: undefined | ( () => Promise<void> );
constructor(
locale: Locale,
engine:
| 'mathjax'
| 'katex'
| ( (
equation: string,
element: HTMLElement,
display: boolean,
) => void ),
lazyLoad: undefined | ( () => Promise<void> ),
previewEnabled = false,
previewUid: string,
previewClassName: Array<string>,
popupClassName: Array<string>,
katexRenderOptions: KatexOptions
) {
super( locale );
const t = locale.t;
// Submit button
this.saveButtonView = this._createButton( t( 'Save' ), checkIcon, 'ck-button-save', null );
this.saveButtonView.type = 'submit';
// Equation input
this.mathInputView = this._createMathInput();
// Display button
this.displayButtonView = this._createDisplayButton();
// Cancel button
this.cancelButtonView = this._createButton( t( 'Cancel' ), cancelIcon, 'ck-button-cancel', 'cancel' );
this.previewEnabled = previewEnabled;
let children = [];
if ( this.previewEnabled ) {
// Preview label
this.previewLabel = new LabelView( locale );
this.previewLabel.text = t( 'Equation preview' );
// Math element
this.mathView = new MathView( engine, lazyLoad, locale, previewUid, previewClassName, katexRenderOptions );
this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' );
children = [
this.mathInputView,
this.displayButtonView,
this.previewLabel,
this.mathView
];
} else {
children = [
this.mathInputView,
this.displayButtonView
];
}
// Add UI elements to template
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-math-form',
...popupClassName
],
tabindex: '-1',
spellcheck: 'false'
},
children: [
{
tag: 'div',
attributes: {
class: [
'ck-math-view'
]
},
children
},
this.saveButtonView,
this.cancelButtonView
]
} );
}
public override render(): void {
super.render();
// Prevent default form submit event & trigger custom 'submit'
submitHandler( {
view: this
} );
// Register form elements to focusable elements
const childViews = [
this.mathInputView,
this.displayButtonView,
this.saveButtonView,
this.cancelButtonView
];
childViews.forEach( v => {
if ( v.element ) {
this._focusables.add( v );
this.focusTracker.add( v.element );
}
} );
// Listen to keypresses inside form element
if ( this.element ) {
this.keystrokes.listenTo( this.element );
}
}
public focus(): void {
this._focusCycler.focusFirst();
}
public get equation(): string {
return this.mathInputView.fieldView.element?.value ?? '';
}
public set equation( equation: string ) {
if ( this.mathInputView.fieldView.element ) {
this.mathInputView.fieldView.element.value = equation;
}
if ( this.previewEnabled && this.mathView ) {
this.mathView.value = equation;
}
}
public focusTracker: FocusTracker = new FocusTracker();
public keystrokes: KeystrokeHandler = new KeystrokeHandler();
private _focusables = new ViewCollection<FocusableView>();
private _focusCycler: FocusCycler = new FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
focusPrevious: 'shift + tab',
focusNext: 'tab'
}
} );
private _createMathInput() {
const t = this.locale.t;
// Create equation input
const mathInput = new MathInputView( this.locale );
const fieldView = mathInput.fieldView;
mathInput.infoText = t( 'Insert equation in TeX format.' );
const onInput = () => {
if ( fieldView.element != null ) {
let equationInput = fieldView.element.value.trim();
// If input has delimiters
if ( hasDelimiters( equationInput ) ) {
// Get equation without delimiters
const params = extractDelimiters( equationInput );
// Remove delimiters from input field
fieldView.element.value = params.equation;
equationInput = params.equation;
// update display button and preview
this.displayButtonView.isOn = params.display;
}
if ( this.previewEnabled && this.mathView ) {
// Update preview view
this.mathView.value = equationInput;
}
this.saveButtonView.isEnabled = !!equationInput;
}
};
fieldView.on( 'render', onInput );
fieldView.on( 'input', onInput );
return mathInput;
}
private _createButton(
label: string,
icon: string,
className: string,
eventName: string | null
) {
const button = new ButtonView( this.locale );
button.set( {
label,
icon,
tooltip: true
} );
button.extendTemplate( {
attributes: {
class: className
}
} );
if ( eventName ) {
button.delegate( 'execute' ).to( this, eventName );
}
return button;
}
private _createDisplayButton() {
const t = this.locale.t;
const switchButton = new SwitchButtonView( this.locale );
switchButton.set( {
label: t( 'Display mode' ),
withText: true
} );
switchButton.extendTemplate( {
attributes: {
class: 'ck-button-display-toggle'
}
} );
switchButton.on( 'execute', () => {
// Toggle state
switchButton.isOn = !switchButton.isOn;
if ( this.previewEnabled && this.mathView ) {
// Update preview view
this.mathView.display = switchButton.isOn;
}
} );
return switchButton;
}
}

View File

@ -0,0 +1,78 @@
import { View } from 'ckeditor5/src/ui';
import type { KatexOptions } from '../typings-external';
import { renderEquation } from '../utils';
import type { Locale } from 'ckeditor5/src/utils';
export default class MathView extends View {
public declare value: string;
public declare display: boolean;
public previewUid: string;
public previewClassName: Array<string>;
public katexRenderOptions: KatexOptions;
public engine:
| 'mathjax'
| 'katex'
| ( ( equation: string, element: HTMLElement, display: boolean ) => void );
public lazyLoad: undefined | ( () => Promise<void> );
constructor(
engine:
| 'mathjax'
| 'katex'
| ( (
equation: string,
element: HTMLElement,
display: boolean,
) => void ),
lazyLoad: undefined | ( () => Promise<void> ),
locale: Locale,
previewUid: string,
previewClassName: Array<string>,
katexRenderOptions: KatexOptions
) {
super( locale );
this.engine = engine;
this.lazyLoad = lazyLoad;
this.previewUid = previewUid;
this.katexRenderOptions = katexRenderOptions;
this.previewClassName = previewClassName;
this.set( 'value', '' );
this.set( 'display', false );
this.on( 'change', () => {
if ( this.isRendered ) {
this.updateMath();
}
} );
this.setTemplate( {
tag: 'div',
attributes: {
class: [ 'ck', 'ck-math-preview' ]
}
} );
}
public updateMath(): void {
if ( this.element ) {
void renderEquation(
this.value,
this.element,
this.engine,
this.lazyLoad,
this.display,
true,
this.previewUid,
this.previewClassName,
this.katexRenderOptions
);
}
}
public override render(): void {
super.render();
this.updateMath();
}
}

View File

@ -0,0 +1,346 @@
import type { Editor } from 'ckeditor5/src/core';
import type {
Element as CKElement,
DocumentSelection
} from 'ckeditor5/src/engine';
import { BalloonPanelView } from 'ckeditor5/src/ui';
import { CKEditorError, type PositioningFunction } from 'ckeditor5/src/utils';
import type { KatexOptions, MathJax2, MathJax3 } from './typings-external';
export function getSelectedMathModelWidget(
selection: DocumentSelection
): null | CKElement {
const selectedElement = selection.getSelectedElement();
if (
selectedElement &&
( selectedElement.is( 'element', 'mathtex-inline' ) ||
selectedElement.is( 'element', 'mathtex-display' ) )
) {
return selectedElement;
}
return null;
}
// Simple MathJax 3 version check
export function isMathJaxVersion3( MathJax: unknown ): MathJax is MathJax3 {
return (
MathJax != null && typeof MathJax == 'object' && 'version' in MathJax && typeof MathJax.version == 'string' &&
MathJax.version.split( '.' ).length === 3 &&
MathJax.version.split( '.' )[ 0 ] === '3'
);
}
// Simple MathJax 2 version check
export function isMathJaxVersion2( MathJax: unknown ): MathJax is MathJax2 {
return (
MathJax != null && typeof MathJax == 'object' && 'Hub' in MathJax );
}
// Check if equation has delimiters.
export function hasDelimiters( text: string ): RegExpMatchArray | null {
return text.match( /^(\\\[.*?\\\]|\\\(.*?\\\))$/ );
}
// Find delimiters count
export function delimitersCounts( text: string ): number | undefined {
return text.match( /(\\\[|\\\]|\\\(|\\\))/g )?.length;
}
// Extract delimiters and figure display mode for the model
export function extractDelimiters( equation: string ): {
equation: string;
display: boolean;
} {
equation = equation.trim();
// Remove delimiters (e.g. \( \) or \[ \])
const hasInlineDelimiters =
equation.includes( '\\(' ) && equation.includes( '\\)' );
const hasDisplayDelimiters =
equation.includes( '\\[' ) && equation.includes( '\\]' );
if ( hasInlineDelimiters || hasDisplayDelimiters ) {
equation = equation.substring( 2, equation.length - 2 ).trim();
}
return {
equation,
display: hasDisplayDelimiters
};
}
export async function renderEquation(
equation: string,
element: HTMLElement,
engine:
| 'katex'
| 'mathjax'
| undefined
| ( (
equation: string,
element: HTMLElement,
display: boolean,
) => void ) = 'katex',
lazyLoad?: () => Promise<void>,
display = false,
preview = false,
previewUid = '',
previewClassName: Array<string> = [],
katexRenderOptions: KatexOptions = {}
): Promise<void> {
if ( engine == 'mathjax' ) {
if ( isMathJaxVersion3( MathJax ) ) {
selectRenderMode(
element,
preview,
previewUid,
previewClassName,
el => {
renderMathJax3( equation, el, display, () => {
if ( preview ) {
moveAndScaleElement( element, el );
el.style.visibility = 'visible';
}
} );
}
);
} else {
selectRenderMode(
element,
preview,
previewUid,
previewClassName,
el => {
// Fixme: MathJax typesetting cause occasionally math processing error without asynchronous call
window.setTimeout( () => {
renderMathJax2( equation, el, display );
// Move and scale after rendering
if ( preview && isMathJaxVersion2( MathJax ) ) {
// eslint-disable-next-line new-cap
MathJax.Hub.Queue( () => {
moveAndScaleElement( element, el );
el.style.visibility = 'visible';
} );
}
} );
}
);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if ( engine === 'katex' && window.katex !== undefined ) {
selectRenderMode(
element,
preview,
previewUid,
previewClassName,
el => {
if ( katex ) {
katex.render( equation, el, {
throwOnError: false,
displayMode: display,
...katexRenderOptions
} );
}
if ( preview ) {
moveAndScaleElement( element, el );
el.style.visibility = 'visible';
}
}
);
} else if ( typeof engine === 'function' ) {
engine( equation, element, display );
} else {
if ( lazyLoad != null ) {
try {
window.CKEDITOR_MATH_LAZY_LOAD ??= lazyLoad();
element.innerHTML = equation;
await window.CKEDITOR_MATH_LAZY_LOAD;
await renderEquation(
equation,
element,
engine,
undefined,
display,
preview,
previewUid,
previewClassName,
katexRenderOptions
);
} catch ( err ) {
element.innerHTML = equation;
console.error(
`math-tex-typesetting-lazy-load-failed: Lazy load failed: ${ String( err ) }`
);
}
} else {
element.innerHTML = equation;
console.warn(
`math-tex-typesetting-missing: Missing the mathematical typesetting engine (${ String( engine ) }) for tex.`
);
}
}
}
export function getBalloonPositionData( editor: Editor ): {
target: Range | HTMLElement;
positions: Array<PositioningFunction>;
} {
const view = editor.editing.view;
const defaultPositions = BalloonPanelView.defaultPositions;
const selectedElement = view.document.selection.getSelectedElement();
if ( selectedElement ) {
return {
target: view.domConverter.viewToDom( selectedElement ),
positions: [
defaultPositions.southArrowNorth,
defaultPositions.southArrowNorthWest,
defaultPositions.southArrowNorthEast
]
};
} else {
const viewDocument = view.document;
const firstRange = viewDocument.selection.getFirstRange();
if ( !firstRange ) {
/**
* Missing first range.
* @error math-missing-range
*/
throw new CKEditorError( 'math-missing-range' );
}
return {
target: view.domConverter.viewRangeToDom(
firstRange
),
positions: [
defaultPositions.southArrowNorth,
defaultPositions.southArrowNorthWest,
defaultPositions.southArrowNorthEast
]
};
}
}
function selectRenderMode(
element: HTMLElement,
preview: boolean,
previewUid: string,
previewClassName: Array<string>,
cb: ( previewEl: HTMLElement ) => void
) {
if ( preview ) {
createPreviewElement(
element,
previewUid,
previewClassName,
previewEl => {
cb( previewEl );
}
);
} else {
cb( element );
}
}
function renderMathJax3( equation: string, element: HTMLElement, display: boolean, cb: () => void ) {
let promiseFunction: undefined | ( ( input: string, options: { display: boolean } ) => Promise<HTMLElement> ) = undefined;
if ( !isMathJaxVersion3( MathJax ) ) {
return;
}
if ( MathJax.tex2chtmlPromise ) {
promiseFunction = MathJax.tex2chtmlPromise;
} else if ( MathJax.tex2svgPromise ) {
promiseFunction = MathJax.tex2svgPromise;
}
if ( promiseFunction != null ) {
void promiseFunction( equation, { display } ).then( ( node: Element ) => {
if ( element.firstChild ) {
element.removeChild( element.firstChild );
}
element.appendChild( node );
cb();
} );
}
}
function renderMathJax2( equation: string, element: HTMLElement, display?: boolean ) {
if ( isMathJaxVersion2( MathJax ) ) {
if ( display ) {
element.innerHTML = '\\[' + equation + '\\]';
} else {
element.innerHTML = '\\(' + equation + '\\)';
}
// eslint-disable-next-line
MathJax.Hub.Queue(['Typeset', MathJax.Hub, element]);
}
}
function createPreviewElement(
element: HTMLElement,
previewUid: string,
previewClassName: Array<string>,
render: ( previewEl: HTMLElement ) => void
): void {
const previewEl = getPreviewElement( element, previewUid, previewClassName );
render( previewEl );
}
function getPreviewElement(
element: HTMLElement,
previewUid: string,
previewClassName: Array<string>
) {
let previewEl = document.getElementById( previewUid );
// Create if not found
if ( !previewEl ) {
previewEl = document.createElement( 'div' );
previewEl.setAttribute( 'id', previewUid );
previewEl.classList.add( ...previewClassName );
previewEl.style.visibility = 'hidden';
document.body.appendChild( previewEl );
let ticking = false;
const renderTransformation = () => {
if ( !ticking ) {
window.requestAnimationFrame( () => {
if ( previewEl ) {
moveElement( element, previewEl );
ticking = false;
}
} );
ticking = true;
}
};
// Create scroll listener for following
window.addEventListener( 'resize', renderTransformation );
window.addEventListener( 'scroll', renderTransformation );
}
return previewEl;
}
function moveAndScaleElement( parent: HTMLElement, child: HTMLElement ) {
// Move to right place
moveElement( parent, child );
// Scale parent element same as preview
const domRect = child.getBoundingClientRect();
parent.style.width = domRect.width + 'px';
parent.style.height = domRect.height + 'px';
}
function moveElement( parent: HTMLElement, child: HTMLElement ) {
const domRect = parent.getBoundingClientRect();
const left = window.scrollX + domRect.left;
const top = window.scrollY + domRect.top;
child.style.position = 'absolute';
child.style.left = left + 'px';
child.style.top = top + 'px';
child.style.zIndex = 'var(--ck-z-panel)';
child.style.pointerEvents = 'none';
}

View File

@ -0,0 +1,190 @@
import Mathematics from '../src/math';
import AutoMath from '../src/automath';
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Undo from '@ckeditor/ckeditor5-undo/src/undo';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import { expect } from 'chai';
import type { SinonFakeTimers } from 'sinon';
describe( 'AutoMath - integration', () => {
let editorElement: HTMLDivElement, editor: ClassicEditor;
beforeEach( async () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );
return ClassicEditor
.create( editorElement, {
plugins: [ Mathematics, AutoMath, Typing, Paragraph ],
math: {
engine: ( equation, element, display ) => {
if ( display ) {
element.innerHTML = '\\[' + equation + '\\]';
} else {
element.innerHTML = '\\(' + equation + '\\)';
}
}
}
} )
.then( newEditor => {
editor = newEditor;
} );
} );
afterEach( () => {
editorElement.remove();
return editor.destroy();
} );
it( 'should load Clipboard plugin', () => {
expect( editor.plugins.get( Clipboard ) ).to.instanceOf( Clipboard );
} );
it( 'should load Undo plugin', () => {
expect( editor.plugins.get( Undo ) ).to.instanceOf( Undo );
} );
it( 'has proper name', () => {
expect( AutoMath.pluginName ).to.equal( 'AutoMath' );
} );
describe( 'use fake timers', () => {
let clock: SinonFakeTimers;
beforeEach( () => {
clock = sinon.useFakeTimers();
} );
afterEach( () => {
clock.restore();
} );
it( 'replaces pasted text with mathtex element after 100ms', () => {
setData( editor.model, '<paragraph>[]</paragraph>' );
pasteHtml( editor, '\\[x^2\\]' );
expect( getData( editor.model ) ).to.equal(
'<paragraph>\\[x^2\\][]</paragraph>'
);
clock.tick( 100 );
expect( getData( editor.model ) ).to.equal(
'<paragraph>[<mathtex display="true" equation="x^2" type="script"></mathtex>]</paragraph>'
);
} );
it( 'replaces pasted text with inline mathtex element after 100ms', () => {
setData( editor.model, '<paragraph>[]</paragraph>' );
pasteHtml( editor, '\\(x^2\\)' );
expect( getData( editor.model ) ).to.equal(
'<paragraph>\\(x^2\\)[]</paragraph>'
);
clock.tick( 100 );
expect( getData( editor.model ) ).to.equal(
'<paragraph>[<mathtex display="false" equation="x^2" type="script"></mathtex>]</paragraph>'
);
} );
it( 'can undo auto-mathing', () => {
setData( editor.model, '<paragraph>[]</paragraph>' );
pasteHtml( editor, '\\[x^2\\]' );
expect( getData( editor.model ) ).to.equal(
'<paragraph>\\[x^2\\][]</paragraph>'
);
clock.tick( 100 );
editor.commands.execute( 'undo' );
expect( getData( editor.model ) ).to.equal(
'<paragraph>\\[x^2\\][]</paragraph>'
);
} );
it( 'works for not collapsed selection inside single element', () => {
setData( editor.model, '<paragraph>[Foo]</paragraph>' );
pasteHtml( editor, '\\[x^2\\]' );
clock.tick( 100 );
expect( getData( editor.model ) ).to.equal(
'<paragraph>[<mathtex display="true" equation="x^2" type="script"></mathtex>]</paragraph>'
);
} );
it( 'works for not collapsed selection over a few elements', () => {
setData( editor.model, '<paragraph>Fo[o</paragraph><paragraph>Ba]r</paragraph>' );
pasteHtml( editor, '\\[x^2\\]' );
clock.tick( 100 );
expect( getData( editor.model ) ).to.equal(
'<paragraph>Fo[<mathtex display="true" equation="x^2" type="script"></mathtex>]r</paragraph>'
);
} );
it( 'inserts mathtex in-place (collapsed selection)', () => {
setData( editor.model, '<paragraph>Foo []Bar</paragraph>' );
pasteHtml( editor, '\\[x^2\\]' );
clock.tick( 100 );
expect( getData( editor.model ) ).to.equal(
'<paragraph>Foo ' +
'[<mathtex display="true" equation="x^2" type="script"></mathtex>]' +
'Bar</paragraph>'
);
} );
it( 'inserts math in-place (non-collapsed selection)', () => {
setData( editor.model, '<paragraph>Foo [Bar] Baz</paragraph>' );
pasteHtml( editor, '\\[x^2\\]' );
clock.tick( 100 );
expect( getData( editor.model ) ).to.equal(
'<paragraph>Foo ' +
'[<mathtex display="true" equation="x^2" type="script"></mathtex>]' +
' Baz</paragraph>'
);
} );
it( 'does nothing if pasted two equation as text', () => {
setData( editor.model, '<paragraph>[]</paragraph>' );
pasteHtml( editor, '\\[x^2\\] \\[\\sqrt{x}2\\]' );
clock.tick( 100 );
expect( getData( editor.model ) ).to.equal(
'<paragraph>\\[x^2\\] \\[\\sqrt{x}2\\][]</paragraph>'
);
} );
} );
function pasteHtml( editor: ClassicEditor, html: string ) {
editor.editing.view.document.fire( 'paste', {
dataTransfer: createDataTransfer( { 'text/html': html } ),
preventDefault() {
return undefined;
}
} );
}
function createDataTransfer( data: Record<string, string> ) {
return {
getData( type: string ) {
return data[ type ];
}
};
}
} );

View File

@ -0,0 +1,14 @@
import { Math as MathDll, AutoformatMath as AutoformatMathDll } from '../src';
import Math from '../src/math';
import AutoformatMath from '../src/autoformatmath';
import { expect } from 'chai';
describe( 'CKEditor5 Math DLL', () => {
it( 'exports Math', () => {
expect( MathDll ).to.equal( Math );
} );
it( 'exports AutoformatMath', () => {
expect( AutoformatMathDll ).to.equal( AutoformatMath );
} );
} );

View File

@ -0,0 +1,49 @@
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import MathUI from '../src/mathui';
import type { EditorConfig } from '@ckeditor/ckeditor5-core/src/editor/editorconfig';
import { expect } from 'chai';
describe( 'Lazy load', () => {
let editorElement: HTMLDivElement;
let editor: ClassicEditor;
let lazyLoadInvoked: boolean;
let mathUIFeature: MathUI;
function buildEditor( config: EditorConfig ) {
return ClassicEditor
.create( editorElement, {
...config,
plugins: [ MathUI ]
} )
.then( newEditor => {
editor = newEditor;
mathUIFeature = editor.plugins.get( MathUI );
} );
}
beforeEach( () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );
lazyLoadInvoked = false;
} );
afterEach( () => {
editorElement.remove();
return editor.destroy();
} );
it( 'initializes lazy load for KaTeX', async () => {
await buildEditor( {
math: {
engine: 'katex',
lazyLoad: async () => {
lazyLoadInvoked = true;
}
}
} );
mathUIFeature._showUI();
expect( lazyLoadInvoked ).to.be.true;
} );
} );

View File

@ -0,0 +1,55 @@
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Mathematics from '../src/math';
import MathEditing from '../src/mathediting';
import MathUI from '../src/mathui';
import AutoMath from '../src/automath';
import Widget from '@ckeditor/ckeditor5-widget/src/widget';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import { expect } from 'chai';
describe( 'Math', () => {
let editorElement: HTMLDivElement, editor: ClassicEditor;
beforeEach( async () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );
return ClassicEditor
.create( editorElement, {
plugins: [ Mathematics ]
} )
.then( newEditor => {
editor = newEditor;
} );
} );
afterEach( () => {
editorElement.remove();
return editor.destroy();
} );
it( 'should be loaded', () => {
expect( editor.plugins.get( Mathematics ) ).to.instanceOf( Mathematics );
} );
it( 'should load MathEditing plugin', () => {
expect( editor.plugins.get( MathEditing ) ).to.instanceOf( MathEditing );
} );
it( 'should load Widget plugin', () => {
expect( editor.plugins.get( Widget ) ).to.instanceOf( Widget );
} );
it( 'should load MathUI plugin', () => {
expect( editor.plugins.get( MathUI ) ).to.instanceOf( MathUI );
} );
it( 'should load AutoMath plugin', () => {
expect( editor.plugins.get( AutoMath ) ).to.instanceOf( AutoMath );
} );
it( 'has proper name', () => {
expect( Mathematics.pluginName ).to.equal( 'Math' );
} );
} );

View File

@ -0,0 +1,479 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* globals document, Event */
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard.js';
import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js';
import MathUI from '../src/mathui';
import MainFormView from '../src/ui/mainformview';
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon.js';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview.js';
import View from '@ckeditor/ckeditor5-ui/src/view.js';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver.js';
import { expect } from 'chai';
import type { SinonSpy } from 'sinon';
describe( 'MathUI', () => {
let editorElement: HTMLDivElement;
let editor: ClassicEditor;
let mathUIFeature: MathUI;
let mathButton: ButtonView;
let balloon: ContextualBalloon;
let formView: MainFormView | null;
beforeEach( async () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );
return ClassicEditor
.create( editorElement, {
plugins: [ MathUI, Paragraph ],
math: {
engine: ( equation, element, display ) => {
if ( display ) {
element.innerHTML = '\\[' + equation + '\\]';
} else {
element.innerHTML = '\\(' + equation + '\\)';
}
}
}
} )
.then( newEditor => {
editor = newEditor;
mathUIFeature = editor.plugins.get( MathUI );
mathButton = editor.ui.componentFactory.create( 'math' ) as ButtonView;
balloon = editor.plugins.get( ContextualBalloon );
formView = mathUIFeature.formView;
// There is no point to execute BalloonPanelView attachTo and pin methods so lets override it.
sinon.stub( balloon.view, 'attachTo' ).returns( false );
sinon.stub( balloon.view, 'pin' ).returns();
formView?.render();
} );
} );
afterEach( () => {
editorElement.remove();
return editor.destroy();
} );
describe( 'init', () => {
it( 'should register click observer', () => {
expect( editor.editing.view.getObserver( ClickObserver ) ).to.be.instanceOf( ClickObserver );
} );
it( 'should create #formView', () => {
expect( formView ).to.be.instanceOf( MainFormView );
} );
describe( 'math toolbar button', () => {
it( 'should be registered', () => {
expect( mathButton ).to.be.instanceOf( ButtonView );
} );
it( 'should be toggleable button', () => {
expect( mathButton.isToggleable ).to.be.true;
} );
it( 'should be bound to the math command', () => {
const command = editor.commands.get( 'math' );
if ( !command ) {
throw new Error( 'Missing math command' );
}
command.isEnabled = true;
command.value = '\\sqrt{x^2}';
expect( mathButton.isEnabled ).to.be.true;
command.isEnabled = false;
command.value = undefined;
expect( mathButton.isEnabled ).to.be.false;
} );
it( 'should call #_showUI upon #execute', () => {
const spy = sinon.stub( mathUIFeature, '_showUI' ).returns( );
mathButton.fire( 'execute' );
sinon.assert.calledOnce( spy );
} );
} );
} );
describe( '_showUI()', () => {
let balloonAddSpy: SinonSpy;
beforeEach( () => {
balloonAddSpy = sinon.spy( balloon, 'add' );
editor.editing.view.document.isFocused = true;
} );
it( 'should not work if the math command is disabled', () => {
setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
const command = editor.commands.get( 'math' )!;
command.isEnabled = false;
mathUIFeature._showUI();
expect( balloon.visibleView ).to.be.null;
} );
it( 'should not throw if the UI is already visible', () => {
setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
mathUIFeature._showUI();
expect( () => {
mathUIFeature._showUI();
} ).to.not.throw();
} );
it( 'should add #mainFormView to the balloon and attach the balloon to the selection when text fragment is selected', () => {
setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
const selectedRange = editorElement.ownerDocument.getSelection()?.getRangeAt( 0 );
mathUIFeature._showUI();
expect( balloon.visibleView ).to.equal( formView );
sinon.assert.calledWithExactly( balloonAddSpy, {
view: formView,
position: {
target: selectedRange
}
} );
} );
it( 'should add #mainFormView to the balloon and attach the balloon to the selection when selection is collapsed', () => {
setModelData( editor.model, '<paragraph>f[]oo</paragraph>' );
const selectedRange = editorElement.ownerDocument.getSelection()?.getRangeAt( 0 );
mathUIFeature._showUI();
expect( balloon.visibleView ).to.equal( formView );
sinon.assert.calledWithExactly( balloonAddSpy, {
view: formView,
position: {
target: selectedRange
}
} );
} );
it( 'should disable #mainFormView element when math command is disabled', () => {
setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
mathUIFeature._showUI();
const command = editor.commands.get( 'math' )!;
command.isEnabled = true;
expect( formView!.mathInputView.isReadOnly ).to.be.false;
expect( formView!.saveButtonView.isEnabled ).to.be.true;
expect( formView!.cancelButtonView.isEnabled ).to.be.true;
command.isEnabled = false;
expect( formView!.mathInputView.isReadOnly ).to.be.true;
expect( formView!.saveButtonView.isEnabled ).to.be.false;
expect( formView!.cancelButtonView.isEnabled ).to.be.true;
} );
describe( '_hideUI()', () => {
beforeEach( () => {
mathUIFeature._showUI();
} );
it( 'should remove the UI from the balloon', () => {
expect( balloon.hasView( formView! ) ).to.be.true;
mathUIFeature._hideUI();
expect( balloon.hasView( formView! ) ).to.be.false;
} );
it( 'should focus the `editable` by default', () => {
const spy = sinon.spy( editor.editing.view, 'focus' );
mathUIFeature._hideUI();
// First call is from _removeFormView.
sinon.assert.calledTwice( spy );
} );
it( 'should focus the `editable` before before removing elements from the balloon', () => {
const focusSpy = sinon.spy( editor.editing.view, 'focus' );
const removeSpy = sinon.spy( balloon, 'remove' );
mathUIFeature._hideUI();
expect( focusSpy.calledBefore( removeSpy ) ).to.equal( true );
} );
it( 'should not throw an error when views are not in the `balloon`', () => {
mathUIFeature._hideUI();
expect( () => {
mathUIFeature._hideUI();
} ).to.not.throw();
} );
it( 'should clear ui#update listener from the ViewDocument', () => {
const spy = sinon.spy();
mathUIFeature.listenTo( editor.ui, 'update', spy );
mathUIFeature._hideUI();
editor.ui.fire( 'update' );
sinon.assert.notCalled( spy );
} );
} );
describe( 'keyboard support', () => {
it( 'should show the UI on Ctrl+M keystroke', () => {
const spy = sinon.stub( mathUIFeature, '_showUI' ).returns( );
const command = editor.commands.get( 'math' )!;
command.isEnabled = false;
const keydata = {
keyCode: keyCodes.m,
ctrlKey: true,
altKey: false,
shiftKey: false,
metaKey: false,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
editor.keystrokes.press( keydata );
sinon.assert.notCalled( spy );
command.isEnabled = true;
editor.keystrokes.press( keydata );
sinon.assert.calledOnce( spy );
} );
it( 'should prevent default action on Ctrl+M keystroke', () => {
const preventDefaultSpy = sinon.spy();
const stopPropagationSpy = sinon.spy();
const keyEvtData = {
altKey: false,
ctrlKey: true,
shiftKey: false,
metaKey: false,
keyCode: keyCodes.m,
preventDefault: preventDefaultSpy,
stopPropagation: stopPropagationSpy
};
editor.keystrokes.press( keyEvtData );
sinon.assert.calledOnce( preventDefaultSpy );
sinon.assert.calledOnce( stopPropagationSpy );
} );
it( 'should make stack with math visible on Ctrl+M keystroke - no math', () => {
const command = editor.commands.get( 'math' )!;
command.isEnabled = true;
balloon.add( {
view: new View(),
stackId: 'custom'
} );
const keyEvtData = {
keyCode: keyCodes.m,
ctrlKey: true,
altKey: false,
shiftKey: false,
metaKey: false,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
editor.keystrokes.press( keyEvtData );
expect( balloon.visibleView ).to.equal( formView );
} );
it( 'should make stack with math visible on Ctrl+M keystroke - math', () => {
setModelData( editor.model, '<paragraph><$text equation="x^2">f[]oo</$text></paragraph>' );
const customView = new View();
balloon.add( {
view: customView,
stackId: 'custom'
} );
expect( balloon.visibleView ).to.equal( customView );
editor.keystrokes.press( {
keyCode: keyCodes.m,
ctrlKey: true,
altKey: false,
shiftKey: false,
metaKey: false,
// @ts-expect-error - preventDefault
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
} );
expect( balloon.visibleView ).to.equal( formView );
} );
it( 'should hide the UI after Esc key press (from editor) and not focus the editable', () => {
const spy = sinon.spy( mathUIFeature, '_hideUI' );
const keyEvtData = {
altKey: false,
ctrlKey: false,
shiftKey: false,
metaKey: false,
keyCode: keyCodes.esc,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
// Balloon is visible.
mathUIFeature._showUI();
editor.keystrokes.press( keyEvtData );
sinon.assert.calledWithExactly( spy );
} );
it( 'should not hide the UI after Esc key press (from editor) when UI is open but is not visible', () => {
const spy = sinon.spy( mathUIFeature, '_hideUI' );
const keyEvtData = {
altKey: false,
shiftKey: false,
ctrlKey: false,
metaKey: false,
keyCode: keyCodes.esc,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
const viewMock = new View();
sinon.stub( viewMock, 'render' );
sinon.stub( viewMock, 'destroy' );
mathUIFeature._showUI();
// Some view precedes the math UI in the balloon.
balloon.add( { view: viewMock } );
editor.keystrokes.press( keyEvtData );
sinon.assert.notCalled( spy );
} );
} );
describe( 'mouse support', () => {
it( 'should hide the UI and not focus editable upon clicking outside the UI', () => {
const spy = sinon.spy( mathUIFeature, '_hideUI' );
mathUIFeature._showUI();
document.body.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) );
sinon.assert.calledWithExactly( spy );
} );
it( 'should not hide the UI upon clicking inside the the UI', () => {
const spy = sinon.spy( mathUIFeature, '_hideUI' );
mathUIFeature._showUI();
balloon.view.element!.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) );
sinon.assert.notCalled( spy );
} );
} );
describe( 'math form view', () => {
it( 'should mark the editor UI as focused when the #formView is focused', () => {
mathUIFeature._showUI();
expect( balloon.visibleView ).to.equal( formView );
editor.ui.focusTracker.isFocused = false;
formView!.element!.dispatchEvent( new Event( 'focus' ) );
expect( editor.ui.focusTracker.isFocused ).to.be.true;
} );
describe( 'binding', () => {
beforeEach( () => {
setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
} );
it( 'should bind mainFormView.mathInputView#value to math command value', () => {
const command = editor.commands.get( 'math' );
expect( formView!.mathInputView.value ).to.null;
command!.value = 'x^2';
expect( formView!.mathInputView.value ).to.equal( 'x^2' );
} );
it( 'should execute math command on mainFormView#submit event', () => {
const executeSpy = sinon.spy( editor, 'execute' );
formView!.mathInputView.value = 'x^2';
expect( formView!.mathInputView.fieldView.element!.value ).to.equal( 'x^2' );
formView!.mathInputView.fieldView.element!.value = 'x^2';
formView!.fire( 'submit' );
expect( executeSpy.calledOnce ).to.be.true;
expect( executeSpy.calledWith( 'math', 'x^2' ) ).to.be.true;
} );
it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => {
mathUIFeature._showUI();
formView!.fire( 'cancel' );
expect( balloon.visibleView ).to.be.null;
} );
it( 'should hide the balloon after Esc key press if math command does not have a value', () => {
const keyEvtData = {
altKey: false,
shiftKey: false,
metaKey: false,
ctrlKey: false,
keyCode: keyCodes.esc,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
mathUIFeature._showUI();
formView!.keystrokes.press( keyEvtData );
expect( balloon.visibleView ).to.be.null;
} );
it( 'should blur math input element before hiding the view', () => {
mathUIFeature._showUI();
const focusSpy = sinon.spy( formView!.saveButtonView, 'focus' );
const removeSpy = sinon.spy( balloon, 'remove' );
formView!.fire( 'cancel' );
expect( focusSpy.calledBefore( removeSpy ) ).to.equal( true );
} );
} );
} );
} );
} );

View File

@ -0,0 +1,9 @@
import type { SinonStatic } from 'sinon';
declare global {
// eslint-disable-next-line no-var
var sinon: SinonStatic;
}
export default {};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.44 10.78" height="40.74" width="58.35"><path d="M8.15 0c-.06 0-.1.02-.11.03a.12.12 0 0 0-.02.01 6.81 6.81 0 0 0-2.32 4.9v.9a6.82 6.82 0 0 0 2.32 4.9.12.12 0 0 0 .02 0c.02.02.06.04.11.04.07 0 .12-.03.16-.07a.22.22 0 0 0 0-.32.12.12 0 0 0-.02-.02A4.4 4.4 0 0 1 7 8.44a7.62 7.62 0 0 1-.5-2.6v-.9c0-.82.19-1.76.5-2.6A4.4 4.4 0 0 1 8.3.42.12.12 0 0 0 8.3.39a.22.22 0 0 0 .08-.16.22.22 0 0 0-.07-.16.22.22 0 0 0-.16-.07zm4.83 0a.22.22 0 0 0-.16.07.22.22 0 0 0-.07.16c0 .08.05.13.08.16a.12.12 0 0 0 .01.02c.52.39.98 1.1 1.3 1.94.3.83.49 1.77.49 2.6v.88c0 .83-.18 1.78-.5 2.6a4.4 4.4 0 0 1-1.29 1.95.22.22 0 0 0-.01.33c.03.04.08.07.15.07.05 0 .09-.02.12-.03a.12.12 0 0 0 .02-.01 6.82 6.82 0 0 0 2.32-4.9v-.9a6.81 6.81 0 0 0-2.32-4.9.12.12 0 0 0-.02 0c-.03-.02-.06-.04-.12-.04zm-8.5.46c-.4 0-1.13.23-1.46 1.32-.06.2-.11.45-.33 1.58h-.64c-.1 0-.19-.01-.28.03a.25.25 0 0 0-.12.12.38.38 0 0 0-.03.17c0 .04 0 .1.04.14.03.04.07.07.11.08.09.03.16.02.26.02h.56l-.77 4.04c-.1.51-.19 1-.32 1.36-.06.18-.14.32-.22.4-.08.1-.16.13-.26.13-.03 0-.1 0-.2-.03.11-.05.2-.13.26-.2a.7.7 0 0 0 .13-.4.48.48 0 0 0-.16-.38.53.53 0 0 0-.35-.12c-.34 0-.7.3-.7.76 0 .27.14.5.34.64s.44.2.68.2c.33 0 .61-.17.83-.4.21-.21.37-.48.47-.69.18-.35.32-.84.43-1.25a14.17 14.17 0 0 0 .18-.8l.61-3.26h.81c.1 0 .2.01.3-.03.04-.03.09-.07.11-.13.02-.05.03-.1.03-.17 0-.05-.01-.1-.05-.14a.23.23 0 0 0-.11-.07c-.08-.03-.16-.02-.25-.02h-.73l.2-1.07a26.3 26.3 0 0 1 .24-1.07c.08-.17.22-.3.39-.3l.21.05a.7.7 0 0 0-.25.2.7.7 0 0 0-.13.4c0 .15.06.28.16.37.1.08.22.12.35.12.34 0 .7-.3.7-.76 0-.28-.15-.5-.35-.64-.2-.14-.45-.2-.7-.2zm5.4 2.78c-.6 0-1.06.37-1.36.76-.16.2-.27.4-.35.57-.07.18-.12.3-.12.42 0 .1.08.18.14.2.06.03.1.02.1.02.06 0 .12 0 .18-.04.05-.05.07-.1.08-.17v.02c.35-1.09 1-1.3 1.3-1.3.09 0 .2.01.29.09.09.07.17.2.17.5 0 .27-.18 1-.57 2.48a1.8 1.8 0 0 1-.37.75.7.7 0 0 1-.52.26c-.04 0-.13 0-.22-.03a.68.68 0 0 0 .3-.56.47.47 0 0 0-.18-.39.55.55 0 0 0-.32-.1c-.4 0-.7.33-.7.74 0 .28.16.5.38.63.21.13.48.18.73.18.39 0 .69-.2.89-.41.09-.1.15-.19.2-.27.2.36.59.68 1.16.68.6 0 1.05-.37 1.35-.76.15-.2.27-.4.34-.57.08-.18.12-.3.12-.42a.24.24 0 0 0-.11-.2c-.06-.03-.12-.02-.13-.02a.26.26 0 0 0-.18.06c-.05.05-.06.1-.07.14-.34 1.1-1.02 1.3-1.3 1.3-.17 0-.27-.06-.35-.17a.72.72 0 0 1-.11-.4c0-.22.06-.45.18-.91l.36-1.45c.03-.14.1-.44.25-.7.15-.25.36-.46.68-.46.03 0 .12 0 .22.03a.7.7 0 0 0-.32.56c0 .11.04.23.13.33.08.1.22.16.4.16.14 0 .3-.06.44-.18a.73.73 0 0 0 .24-.55c0-.32-.2-.54-.42-.66a1.52 1.52 0 0 0-.68-.16c-.34 0-.62.16-.82.34a1.8 1.8 0 0 0-.3.35 1.32 1.32 0 0 0-.5-.54 1.37 1.37 0 0 0-.63-.15z" style="line-height:1.25;-inkscape-font-specification:'Latin Modern Math'" font-weight="400" font-size="10.58" font-family="Latin Modern Math" letter-spacing="-1.06" word-spacing="0"/></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,35 @@
.ck.ck-math-form {
display: flex;
align-items: flex-start;
flex-direction: row;
flex-wrap: nowrap;
padding: var(--ck-spacing-standard);
@media screen and (max-width: 600px) {
flex-wrap: wrap;
& .ck-math-view {
flex-basis: 100%;
& .ck-labeled-view {
flex-basis: 100%;
}
& .ck-label {
flex-basis: 100%;
}
}
& .ck-button {
flex-basis: 50%;
}
}
}
.ck-math-tex.ck-placeholder::before {
display: none !important;
}
.ck.ck-toolbar-container {
z-index: calc(var(--ck-z-panel) + 2);
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"lib": [
"DOM"
],
"noImplicitAny": true,
"noImplicitOverride": true,
"strict": true,
"module": "es6",
"target": "es2020",
"sourceMap": true,
"allowJs": true,
"moduleResolution": "node",
"typeRoots": [
"typings",
"node_modules/@types"
]
},
"include": [
"./sample",
"./src",
"./tests",
"./typings"
]
}

View File

@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"declaration": true
},
"exclude": [
"./tests/",
"./sample/"
]
}

View File

@ -0,0 +1,4 @@
declare module '*.svg' {
const src: string;
export default src;
}

File diff suppressed because it is too large Load Diff