diff --git a/_regroup/ckeditor5-mermaid/.editorconfig b/_regroup/ckeditor5-mermaid/.editorconfig
new file mode 100644
index 000000000..37ea8ff91
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/.editorconfig
@@ -0,0 +1,19 @@
+# Configurations to normalize the IDE behavior.
+# http://editorconfig.org/
+
+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
+
+[package.json]
+indent_style = space
+tab_width = 2
diff --git a/_regroup/ckeditor5-mermaid/.eslintrc.cjs b/_regroup/ckeditor5-mermaid/.eslintrc.cjs
new file mode 100644
index 000000000..d39acda4f
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/.eslintrc.cjs
@@ -0,0 +1,41 @@
+/* eslint-env node */
+
+'use strict';
+
+module.exports = {
+ extends: 'ckeditor5',
+ root: true,
+ ignorePatterns: [
+ // Ignore the entire `build/` (the DLL build).
+ 'build/**',
+ // Ignore the entire `dist/`.
+ 'dist/**'
+ ],
+ 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 disallows importing from any path other than the package main entrypoint.
+ 'ckeditor5-rules/allow-imports-only-from-main-package-entry-point': 'error',
+ // As required by the ECMAScript (ESM) standard, all imports must include a file extension.
+ // If the import does not include it, this rule will try to automatically detect the correct file extension.
+ 'ckeditor5-rules/require-file-extensions-in-imports': [
+ 'error',
+ {
+ extensions: [ '.ts', '.js', '.json' ]
+ }
+ ]
+ },
+ overrides: [
+ {
+ files: [ 'tests/**/*.js', 'sample/**/*.js' ],
+ 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'
+ }
+ }
+ ]
+};
diff --git a/_regroup/ckeditor5-mermaid/.gitattributes b/_regroup/ckeditor5-mermaid/.gitattributes
new file mode 100644
index 000000000..9c20d56f7
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/.gitattributes
@@ -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
diff --git a/_regroup/ckeditor5-mermaid/.gitignore b/_regroup/ckeditor5-mermaid/.gitignore
new file mode 100644
index 000000000..d7d45b9d4
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/.gitignore
@@ -0,0 +1,8 @@
+.nyc_output/
+coverage/
+node_modules/
+yarn.lock
+tmp/
+build/
+dist
+yarn.error
diff --git a/_regroup/ckeditor5-mermaid/.stylelintrc b/_regroup/ckeditor5-mermaid/.stylelintrc
new file mode 100644
index 000000000..e7e28c8be
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/.stylelintrc
@@ -0,0 +1,6 @@
+{
+ "extends": "stylelint-config-ckeditor5",
+ "ignoreFiles": [
+ "dist/**/*.css"
+ ]
+}
diff --git a/_regroup/ckeditor5-mermaid/LICENSE.md b/_regroup/ckeditor5-mermaid/LICENSE.md
new file mode 100644
index 000000000..f2b8704dd
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/LICENSE.md
@@ -0,0 +1,22 @@
+Software License Agreement
+==========================
+
+**CKEditor 5 mermaid feature** (https://ckeditor.com/ckeditor-5/)
+Copyright (c) 2003-2022, [CKSource](http://cksource.com) Holding sp. z o.o. All rights reserved.
+
+Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
+
+Sources of Intellectual Property Included in CKEditor 5 mermaid feature
+---------------------------------------------------------------------
+
+Where not otherwise indicated, all CKEditor 5 mermaid feature content is authored by CKSource engineers and consists of CKSource-owned intellectual property.
+
+The following libraries are included in CKEditor 5 mermaid feature under the [MIT license](https://opensource.org/licenses/MIT):
+
+* Lo-Dash - Copyright (c) JS Foundation and other contributors https://js.foundation/. Based on Underscore.js, copyright Jeremy Ashkenas.
+* mermaid - Copyright (c) 2014 - 2021 Knut Sveidqvist. (MIT License)
+
+Trademarks
+----------
+
+**CKEditor** is a trademark of [CKSource](http://cksource.com) Holding sp. z o.o. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders.
diff --git a/_regroup/ckeditor5-mermaid/README.md b/_regroup/ckeditor5-mermaid/README.md
new file mode 100644
index 000000000..04c56d87e
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/README.md
@@ -0,0 +1,24 @@
+CKEditor 5 Mermaid feature
+=================================
+
+Warning: This is an experimental plugin that comes with no support, use it at your own risk.
+
+This package contains a Mermaid feature for CKEditor 5.
+
+## CKEditor 5 Mermaid - Running manual test
+
+You can test all of the features of the `ckeditor5-mermaid` plugin with the manual test sample.
+
+After installing dependencies it's enough to execute the following script at the root of your local clone of the project:
+
+```
+yarn start
+```
+
+## License
+
+See [LICENSE.md](LICENSE.md) file.
+
+### Trademarks
+
+**CKEditor** is a trademark of [CKSource](https://cksource.com) Holding sp. z o.o. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders.
diff --git a/_regroup/ckeditor5-mermaid/package.json b/_regroup/ckeditor5-mermaid/package.json
new file mode 100644
index 000000000..c0785edae
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "@ckeditor/ckeditor5-mermaid",
+ "version": "0.0.4",
+ "description": "Mermaid widget for CKEditor 5.",
+ "private": true,
+ "keywords": [
+ "ckeditor",
+ "ckeditor5",
+ "ckeditor 5",
+ "ckeditor5-feature",
+ "ckeditor5-plugin",
+ "ckeditor5-mermaid"
+ ],
+ "type": "module",
+ "main": "src/index.js",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "author": "CKSource (https://cksource.com/)",
+ "homepage": "https://github.com/ckeditor/ckeditor5-mermaid",
+ "bugs": "https://github.com/ckeditor/ckeditor5-mermaid/issues",
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=5.7.1"
+ },
+ "files": [
+ "lang",
+ "src/**/*.js",
+ "src/**/*.css",
+ "theme",
+ "build",
+ "ckeditor5-metadata.json"
+ ],
+ "dependencies": {
+ "ckeditor5": "43.2.0",
+ "lodash-es": "^4.17.15"
+ },
+ "devDependencies": {
+ "@ckeditor/ckeditor5-dev-build-tools": "^42.0.0",
+ "@ckeditor/ckeditor5-inspector": "^4.0.0",
+ "@ckeditor/ckeditor5-package-tools": "^2.1.0",
+ "ckeditor5": "latest",
+ "eslint": "^7.32.0",
+ "eslint-config-ckeditor5": "^6.0.0",
+ "http-server": "^14.1.1",
+ "husky": "^4.2.5",
+ "lint-staged": "^12.0.0",
+ "stylelint": "^13.13.1",
+ "stylelint-config-ckeditor5": ">=6.0.0"
+ },
+ "peerDependencies": {
+ "ckeditor5": ">=43.0.0 || ^0.0.0-nightly"
+ },
+ "scripts": {
+ "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": "ckeditor5-package-tools export-package-as-javascript",
+ "postpublish": "ckeditor5-package-tools export-package-as-typescript",
+ "start": "ckeditor5-package-tools start"
+ },
+ "lint-staged": {
+ "**/*.js": [
+ "eslint --quiet"
+ ],
+ "**/*.css": [
+ "stylelint --quiet --allow-empty-input"
+ ]
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/sample/ckeditor.js b/_regroup/ckeditor5-mermaid/sample/ckeditor.js
new file mode 100644
index 000000000..623196c31
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/sample/ckeditor.js
@@ -0,0 +1,54 @@
+/**
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Typing } from '@ckeditor/ckeditor5-typing';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import { Undo } from '@ckeditor/ckeditor5-undo';
+import { Enter } from '@ckeditor/ckeditor5-enter';
+import { Clipboard } from '@ckeditor/ckeditor5-clipboard';
+import { Link } from '@ckeditor/ckeditor5-link';
+import { Bold, Italic } from '@ckeditor/ckeditor5-basic-styles';
+import { CodeBlock } from '@ckeditor/ckeditor5-code-block';
+
+import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
+
+import Mermaid from '../src/mermaid.js';
+
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ plugins: [
+ Typing,
+ Paragraph,
+ Undo,
+ Enter,
+ Clipboard,
+ Link,
+ Bold,
+ Italic,
+ CodeBlock,
+ Mermaid
+ ],
+ toolbar: [ 'bold', 'italic', 'link', 'undo', 'redo', 'codeBlock', 'mermaid' ],
+ codeBlock: {
+ languages: [
+ { language: 'plaintext', label: 'Plain text', class: '' },
+ { language: 'javascript', label: 'JavaScript' },
+ { language: 'python', label: 'Python' },
+ { language: 'mermaid', label: 'Mermaid' }
+ ]
+ }
+
+ } )
+ .then( editor => {
+ window.editor = editor;
+ CKEditorInspector.attach( editor );
+ window.console.log( 'CKEditor 5 is ready.', editor );
+ } )
+ .catch( err => {
+ console.error( err.stack );
+ } );
diff --git a/_regroup/ckeditor5-mermaid/sample/index.html b/_regroup/ckeditor5-mermaid/sample/index.html
new file mode 100644
index 000000000..7b8ac8fd3
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/sample/index.html
@@ -0,0 +1,119 @@
+
+
+
+
+ CKEditor 5 Mermaid widget – Development Sample
+
+
+
+
+CKEditor 5 Mermaid widget – Development Sample
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_regroup/ckeditor5-mermaid/scripts/build-dist.mjs b/_regroup/ckeditor5-mermaid/scripts/build-dist.mjs
new file mode 100644
index 000000000..55ef3099d
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/scripts/build-dist.mjs
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+
+/**
+ * @license Copyright (c) 2020-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* eslint-env node */
+
+import { createRequire } from 'module';
+import upath from 'upath';
+import chalk from 'chalk';
+import { build } from '@ckeditor/ckeditor5-dev-build-tools';
+
+function dist( path ) {
+ return upath.join( 'dist', path );
+}
+
+( async () => {
+ /**
+ * Step 1
+ */
+ console.log( chalk.cyan( '1/2: Generating NPM build...' ) );
+
+ const require = createRequire( import.meta.url );
+ const pkg = require( upath.resolve( process.cwd(), './package.json' ) );
+
+ await build( {
+ input: 'src/index.js',
+ output: dist( './index.js' ),
+ external: [
+ 'ckeditor5',
+ 'ckeditor5-premium-features',
+ ...Object.keys( {
+ ...pkg.dependencies,
+ ...pkg.peerDependencies
+ } )
+ ],
+ clean: true,
+ sourceMap: true,
+ translations: '**/*.po'
+ } );
+
+ /**
+ * Step 2
+ */
+ console.log( chalk.cyan( '2/2: Generating browser build...' ) );
+
+ await build( {
+ input: 'src/index.js',
+ output: dist( 'browser/index.js' ),
+ sourceMap: true,
+ minify: true,
+ browser: true,
+ name: '@ckeditor/ckeditor5-mermaid',
+ external: [
+ 'ckeditor5',
+ 'ckeditor5-premium-features'
+ ]
+ } );
+} )();
diff --git a/_regroup/ckeditor5-mermaid/src/commands/insertMermaidCommand.js b/_regroup/ckeditor5-mermaid/src/commands/insertMermaidCommand.js
new file mode 100644
index 000000000..8fbf52a90
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/commands/insertMermaidCommand.js
@@ -0,0 +1,52 @@
+/**
+ * @module mermaid/insertmermaidcommand
+ */
+
+import { Command } from 'ckeditor5/src/core.js';
+
+const MOCK_MERMAID_MARKUP = `flowchart TB
+A --> B
+B --> C`;
+
+/**
+ * The insert mermaid command.
+ *
+ * Allows to insert mermaid.
+ *
+ * @extends module:core/command~Command
+ */
+export default class InsertMermaidCommand extends Command {
+ /**
+ * @inheritDoc
+ */
+ refresh() {
+ const documentSelection = this.editor.model.document.selection;
+ const selectedElement = documentSelection.getSelectedElement();
+
+ if ( selectedElement && selectedElement.name === 'mermaid' ) {
+ this.isEnabled = false;
+ } else {
+ this.isEnabled = true;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ execute() {
+ const editor = this.editor;
+ const model = editor.model;
+ let mermaidItem;
+
+ model.change( writer => {
+ mermaidItem = writer.createElement( 'mermaid', {
+ displayMode: 'split',
+ source: MOCK_MERMAID_MARKUP
+ } );
+
+ model.insertContent( mermaidItem );
+ } );
+
+ return mermaidItem;
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/src/commands/mermaidPreviewCommand.js b/_regroup/ckeditor5-mermaid/src/commands/mermaidPreviewCommand.js
new file mode 100644
index 000000000..0fc0b15ec
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/commands/mermaidPreviewCommand.js
@@ -0,0 +1,50 @@
+/**
+ * @module mermaid/mermaidpreviewcommand
+ */
+
+import { Command } from 'ckeditor5/src/core.js';
+
+import { checkIsOn } from '../utils.js';
+
+/**
+ * The mermaid preview command.
+ *
+ * Allows to switch to a preview mode.
+ *
+ * @extends module:core/command~Command
+ */
+export default class MermaidPreviewCommand extends Command {
+ /**
+ * @inheritDoc
+ */
+ refresh() {
+ const editor = this.editor;
+ const documentSelection = editor.model.document.selection;
+ const selectedElement = documentSelection.getSelectedElement();
+ const isSelectedElementMermaid = selectedElement && selectedElement.name === 'mermaid';
+
+ if ( isSelectedElementMermaid || documentSelection.getLastPosition().findAncestor( 'mermaid' ) ) {
+ this.isEnabled = !!selectedElement;
+ } else {
+ this.isEnabled = false;
+ }
+
+ this.value = checkIsOn( editor, 'preview' );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ execute() {
+ const editor = this.editor;
+ const model = editor.model;
+ const documentSelection = this.editor.model.document.selection;
+ const mermaidItem = documentSelection.getSelectedElement() || documentSelection.getLastPosition().parent;
+
+ model.change( writer => {
+ if ( mermaidItem.getAttribute( 'displayMode' ) !== 'preview' ) {
+ writer.setAttribute( 'displayMode', 'preview', mermaidItem );
+ }
+ } );
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/src/commands/mermaidSourceViewCommand.js b/_regroup/ckeditor5-mermaid/src/commands/mermaidSourceViewCommand.js
new file mode 100644
index 000000000..043490154
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/commands/mermaidSourceViewCommand.js
@@ -0,0 +1,50 @@
+/**
+ * @module mermaid/mermaidsourceviewcommand
+ */
+
+import { Command } from 'ckeditor5/src/core.js';
+
+import { checkIsOn } from '../utils.js';
+
+/**
+ * The mermaid source view command.
+ *
+ * Allows to switch to a source view mode.
+ *
+ * @extends module:core/command~Command
+ */
+export default class MermaidSourceViewCommand extends Command {
+ /**
+ * @inheritDoc
+ */
+ refresh() {
+ const editor = this.editor;
+ const documentSelection = editor.model.document.selection;
+ const selectedElement = documentSelection.getSelectedElement();
+ const isSelectedElementMermaid = selectedElement && selectedElement.name === 'mermaid';
+
+ if ( isSelectedElementMermaid || documentSelection.getLastPosition().findAncestor( 'mermaid' ) ) {
+ this.isEnabled = !!selectedElement;
+ } else {
+ this.isEnabled = false;
+ }
+
+ this.value = checkIsOn( editor, 'source' );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ execute() {
+ const editor = this.editor;
+ const model = editor.model;
+ const documentSelection = this.editor.model.document.selection;
+ const mermaidItem = documentSelection.getSelectedElement() || documentSelection.getLastPosition().parent;
+
+ model.change( writer => {
+ if ( mermaidItem.getAttribute( 'displayMode' ) !== 'source' ) {
+ writer.setAttribute( 'displayMode', 'source', mermaidItem );
+ }
+ } );
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/src/commands/mermaidSplitViewCommand.js b/_regroup/ckeditor5-mermaid/src/commands/mermaidSplitViewCommand.js
new file mode 100644
index 000000000..c5f77dd21
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/commands/mermaidSplitViewCommand.js
@@ -0,0 +1,50 @@
+/**
+ * @module mermaid/mermaidsplitviewcommand
+ */
+
+import { Command } from 'ckeditor5/src/core.js';
+
+import { checkIsOn } from '../utils.js';
+
+/**
+ * The mermaid split view command.
+ *
+ * Allows to switch to a split view mode.
+ *
+ * @extends module:core/command~Command
+ */
+export default class MermaidSplitViewCommand extends Command {
+ /**
+ * @inheritDoc
+ */
+ refresh() {
+ const editor = this.editor;
+ const documentSelection = editor.model.document.selection;
+ const selectedElement = documentSelection.getSelectedElement();
+ const isSelectedElementMermaid = selectedElement && selectedElement.name === 'mermaid';
+
+ if ( isSelectedElementMermaid || documentSelection.getLastPosition().findAncestor( 'mermaid' ) ) {
+ this.isEnabled = !!selectedElement;
+ } else {
+ this.isEnabled = false;
+ }
+
+ this.value = checkIsOn( editor, 'split' );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ execute() {
+ const editor = this.editor;
+ const model = editor.model;
+ const documentSelection = this.editor.model.document.selection;
+ const mermaidItem = documentSelection.getSelectedElement() || documentSelection.getLastPosition().parent;
+
+ model.change( writer => {
+ if ( mermaidItem.getAttribute( 'displayMode' ) !== 'split' ) {
+ writer.setAttribute( 'displayMode', 'split', mermaidItem );
+ }
+ } );
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/src/index.js b/_regroup/ckeditor5-mermaid/src/index.js
new file mode 100644
index 000000000..6b37848d8
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/index.js
@@ -0,0 +1,19 @@
+/**
+ * @module mermaid
+ */
+
+import infoIcon from './../theme/icons/info.svg';
+import insertMermaidIcon from './../theme/icons/insert.svg';
+import previewModeIcon from './../theme/icons/preview-mode.svg';
+import splitModeIcon from './../theme/icons/split-mode.svg';
+import sourceModeIcon from './../theme/icons/source-mode.svg';
+
+export { default as Mermaid } from './mermaid.js';
+
+export const icons = {
+ infoIcon,
+ insertMermaidIcon,
+ previewModeIcon,
+ splitModeIcon,
+ sourceModeIcon
+};
diff --git a/_regroup/ckeditor5-mermaid/src/mermaid.js b/_regroup/ckeditor5-mermaid/src/mermaid.js
new file mode 100644
index 000000000..0a8aec3b6
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/mermaid.js
@@ -0,0 +1,27 @@
+/**
+ * @module mermaid/mermaid
+ */
+
+import { Plugin } from 'ckeditor5/src/core.js';
+
+import MermaidEditing from './mermaidediting.js';
+import MermaidToolbar from './mermaidtoolbar.js';
+import MermaidUI from './mermaidui.js';
+
+import '../theme/mermaid.css';
+
+export default class Mermaid extends Plugin {
+ /**
+ * @inheritDoc
+ */
+ static get requires() {
+ return [ MermaidEditing, MermaidToolbar, MermaidUI ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ static get pluginName() {
+ return 'Mermaid';
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/src/mermaidediting.js b/_regroup/ckeditor5-mermaid/src/mermaidediting.js
new file mode 100644
index 000000000..9e8fdd2e2
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/mermaidediting.js
@@ -0,0 +1,295 @@
+/**
+ * @module mermaid/mermaidediting
+ */
+
+import { Plugin } from 'ckeditor5/src/core.js';
+import { toWidget } from 'ckeditor5/src/widget.js';
+
+import { debounce } from 'lodash-es';
+
+import MermaidPreviewCommand from './commands/mermaidPreviewCommand.js';
+import MermaidSourceViewCommand from './commands/mermaidSourceViewCommand.js';
+import MermaidSplitViewCommand from './commands/mermaidSplitViewCommand.js';
+import InsertMermaidCommand from './commands/insertMermaidCommand.js';
+
+// Time in milliseconds.
+const DEBOUNCE_TIME = 300;
+
+/* global window */
+
+export default class MermaidEditing extends Plugin {
+ /**
+ * @inheritDoc
+ */
+ static get pluginName() {
+ return 'MermaidEditing';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ init() {
+ this._registerCommands();
+ this._defineConverters();
+ this._config = this.editor.config.get("mermaid");
+ }
+
+ /**
+ * @inheritDoc
+ */
+ afterInit() {
+ this.editor.model.schema.register( 'mermaid', {
+ allowAttributes: [ 'displayMode', 'source' ],
+ allowWhere: '$block',
+ isObject: true
+ } );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ _registerCommands() {
+ const editor = this.editor;
+
+ editor.commands.add( 'mermaidPreviewCommand', new MermaidPreviewCommand( editor ) );
+ editor.commands.add( 'mermaidSplitViewCommand', new MermaidSplitViewCommand( editor ) );
+ editor.commands.add( 'mermaidSourceViewCommand', new MermaidSourceViewCommand( editor ) );
+ editor.commands.add( 'insertMermaidCommand', new InsertMermaidCommand( editor ) );
+ }
+
+ /**
+ * Adds converters.
+ *
+ * @private
+ */
+ _defineConverters() {
+ const editor = this.editor;
+
+ editor.data.downcastDispatcher.on( 'insert:mermaid', this._mermaidDataDowncast.bind( this ) );
+ editor.editing.downcastDispatcher.on( 'insert:mermaid', this._mermaidDowncast.bind( this ) );
+ editor.editing.downcastDispatcher.on( 'attribute:source:mermaid', this._sourceAttributeDowncast.bind( this ) );
+
+ editor.data.upcastDispatcher.on( 'element:code', this._mermaidUpcast.bind( this ), { priority: 'high' } );
+
+ editor.conversion.for( 'editingDowncast' ).attributeToAttribute( {
+ model: {
+ name: 'mermaid',
+ key: 'displayMode'
+ },
+ view: modelAttributeValue => ( {
+ key: 'class',
+ value: 'ck-mermaid__' + modelAttributeValue + '-mode'
+ } )
+ } );
+ }
+
+ /**
+ *
+ * @private
+ * @param {*} evt
+ * @param {*} data
+ * @param {*} conversionApi
+ */
+ _mermaidDataDowncast( evt, data, conversionApi ) {
+ const model = this.editor.model;
+ const { writer, mapper } = conversionApi;
+
+ if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) {
+ return;
+ }
+
+ const targetViewPosition = mapper.toViewPosition( model.createPositionBefore( data.item ) );
+ // For downcast we're using only language-mermaid class. We don't set class to `mermaid language-mermaid` as
+ // multiple markdown converters that we have seen are using only `language-mermaid` class and not `mermaid` alone.
+ const code = writer.createContainerElement( 'code', {
+ class: 'language-mermaid'
+ } );
+ const pre = writer.createContainerElement( 'pre', {
+ spellcheck: 'false'
+ } );
+ const sourceTextNode = writer.createText( data.item.getAttribute( 'source' ) );
+
+ writer.insert( model.createPositionAt( code, 'end' ), sourceTextNode );
+ writer.insert( model.createPositionAt( pre, 'end' ), code );
+ writer.insert( targetViewPosition, pre );
+ mapper.bindElements( data.item, code );
+ }
+
+ /**
+ *
+ * @private
+ * @param {*} evt
+ * @param {*} data
+ * @param {*} conversionApi
+ */
+ _mermaidDowncast( evt, data, conversionApi ) {
+ const { writer, mapper, consumable } = conversionApi;
+ const { editor } = this;
+ const { model, t } = editor;
+ const that = this;
+
+ if ( !consumable.consume( data.item, 'insert' ) ) {
+ return;
+ }
+
+ const targetViewPosition = mapper.toViewPosition( model.createPositionBefore( data.item ) );
+
+ const wrapperAttributes = {
+ class: [ 'ck-mermaid__wrapper' ]
+ };
+ const textareaAttributes = {
+ class: [ 'ck-mermaid__editing-view' ],
+ placeholder: t( 'Insert mermaid source code' ),
+ 'data-cke-ignore-events': true
+ };
+
+ const wrapper = writer.createContainerElement( 'div', wrapperAttributes );
+ const editingContainer = writer.createUIElement( 'textarea', textareaAttributes, createEditingTextarea );
+ const previewContainer = writer.createUIElement( 'div', { class: [ 'ck-mermaid__preview' ] }, createMermaidPreview );
+
+ writer.insert( writer.createPositionAt( wrapper, 'start' ), previewContainer );
+ writer.insert( writer.createPositionAt( wrapper, 'start' ), editingContainer );
+
+ writer.insert( targetViewPosition, wrapper );
+
+ mapper.bindElements( data.item, wrapper );
+
+ return toWidget( wrapper, writer, {
+ widgetLabel: t( 'Mermaid widget' ),
+ hasSelectionHandle: true
+ } );
+
+ function createEditingTextarea( domDocument ) {
+ const domElement = this.toDomElement( domDocument );
+
+ domElement.value = data.item.getAttribute( 'source' );
+
+ const debouncedListener = debounce( event => {
+ editor.model.change( writer => {
+ writer.setAttribute( 'source', event.target.value, data.item );
+ } );
+ }, DEBOUNCE_TIME );
+
+ domElement.addEventListener( 'input', debouncedListener );
+
+ /* Workaround for internal #1544 */
+ domElement.addEventListener( 'focus', () => {
+ const model = editor.model;
+ const selectedElement = model.document.selection.getSelectedElement();
+
+ // Move the selection onto the mermaid widget if it's currently not selected.
+ if ( selectedElement !== data.item ) {
+ model.change( writer => writer.setSelection( data.item, 'on' ) );
+ }
+ }, true );
+
+ return domElement;
+ }
+
+ function createMermaidPreview( domDocument ) {
+ // Taking the text from the wrapper container element for now
+ const mermaidSource = data.item.getAttribute( 'source' );
+ const domElement = this.toDomElement( domDocument );
+
+ domElement.innerHTML = mermaidSource;
+
+ window.setTimeout( () => {
+ // @todo: by the looks of it the domElement needs to be hooked to tree in order to allow for rendering.
+ that._renderMermaid( domElement );
+ }, 100 );
+
+ return domElement;
+ }
+ }
+
+ /**
+ *
+ * @param {*} evt
+ * @param {*} data
+ * @param {*} conversionApi
+ * @returns
+ */
+ _sourceAttributeDowncast( evt, data, conversionApi ) {
+ // @todo: test whether the attribute was consumed.
+ const newSource = data.attributeNewValue;
+ const domConverter = this.editor.editing.view.domConverter;
+
+ if ( newSource ) {
+ const mermaidView = conversionApi.mapper.toViewElement( data.item );
+
+ for ( const child of mermaidView.getChildren() ) {
+ if ( child.name === 'textarea' && child.hasClass( 'ck-mermaid__editing-view' ) ) {
+ const domEditingTextarea = domConverter.viewToDom( child, window.document );
+
+ if ( domEditingTextarea.value != newSource ) {
+ domEditingTextarea.value = newSource;
+ }
+ } else if ( child.name === 'div' && child.hasClass( 'ck-mermaid__preview' ) ) {
+ // @todo: we could optimize this and not refresh mermaid if widget is in source mode.
+ const domPreviewWrapper = domConverter.viewToDom( child, window.document );
+
+ if ( domPreviewWrapper ) {
+ domPreviewWrapper.innerHTML = newSource;
+ domPreviewWrapper.removeAttribute( 'data-processed' );
+
+ this._renderMermaid( domPreviewWrapper );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * @private
+ * @param {*} evt
+ * @param {*} data
+ * @param {*} conversionApi
+ */
+ _mermaidUpcast( evt, data, conversionApi ) {
+ const viewCodeElement = data.viewItem;
+ const hasPreElementParent = !viewCodeElement.parent || !viewCodeElement.parent.is( 'element', 'pre' );
+ const hasCodeAncestors = data.modelCursor.findAncestor( 'code' );
+ const { consumable, writer } = conversionApi;
+
+ if ( !viewCodeElement.hasClass( 'language-mermaid' ) || hasPreElementParent || hasCodeAncestors ) {
+ return;
+ }
+
+ if ( !consumable.test( viewCodeElement, { name: true } ) ) {
+ return;
+ }
+ const mermaidSource = Array.from( viewCodeElement.getChildren() )
+ .filter( item => item.is( '$text' ) )
+ .map( item => item.data )
+ .join( '' );
+
+ const mermaidElement = writer.createElement( 'mermaid', {
+ source: mermaidSource,
+ displayMode: 'split'
+ } );
+
+ // Let's try to insert mermaid element.
+ if ( !conversionApi.safeInsert( mermaidElement, data.modelCursor ) ) {
+ return;
+ }
+
+ consumable.consume( viewCodeElement, { name: true } );
+
+ conversionApi.updateConversionResult( mermaidElement, data );
+ }
+
+ /**
+ * Renders Mermaid in a given `domElement`. Expect this domElement to have mermaid
+ * source set as text content.
+ *
+ * @param {HTMLElement} domElement
+ */
+ async _renderMermaid( domElement ) {
+ if (!window.mermaid && typeof this._config?.lazyLoad === "function") {
+ this.mermaid = await this._config.lazyLoad();
+ }
+
+ this.mermaid.init( this._config.config, domElement );
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/src/mermaidtoolbar.js b/_regroup/ckeditor5-mermaid/src/mermaidtoolbar.js
new file mode 100644
index 000000000..dd7940743
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/mermaidtoolbar.js
@@ -0,0 +1,51 @@
+/**
+ * @module mermaid/mermaidtoolbar
+ */
+
+import { Plugin } from 'ckeditor5/src/core.js';
+import { WidgetToolbarRepository } from 'ckeditor5/src/widget.js';
+
+export default class MermaidToolbar extends Plugin {
+ /**
+ * @inheritDoc
+ */
+ static get requires() {
+ return [ WidgetToolbarRepository ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ static get pluginName() {
+ return 'MermaidToolbar';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ afterInit() {
+ const editor = this.editor;
+ const t = editor.t;
+
+ const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+ const mermaidToolbarItems = [ 'mermaidSourceView', 'mermaidSplitView', 'mermaidPreview', '|', 'mermaidInfo' ];
+
+ if ( mermaidToolbarItems ) {
+ widgetToolbarRepository.register( 'mermaidToolbar', {
+ ariaLabel: t( 'Mermaid toolbar' ),
+ items: mermaidToolbarItems,
+ getRelatedElement: selection => getSelectedElement( selection )
+ } );
+ }
+ }
+}
+
+function getSelectedElement( selection ) {
+ const viewElement = selection.getSelectedElement();
+
+ if ( viewElement && viewElement.hasClass( 'ck-mermaid__wrapper' ) ) {
+ return viewElement;
+ }
+
+ return null;
+}
diff --git a/_regroup/ckeditor5-mermaid/src/mermaidui.js b/_regroup/ckeditor5-mermaid/src/mermaidui.js
new file mode 100644
index 000000000..419e46f44
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/mermaidui.js
@@ -0,0 +1,150 @@
+/**
+ * @module mermaid/mermaidui
+ */
+
+import { Plugin } from 'ckeditor5/src/core.js';
+import { ButtonView } from 'ckeditor5/src/ui.js';
+
+import insertMermaidIcon from '../theme/icons/insert.svg';
+import previewModeIcon from '../theme/icons/preview-mode.svg';
+import splitModeIcon from '../theme/icons/split-mode.svg';
+import sourceModeIcon from '../theme/icons/source-mode.svg';
+import infoIcon from '../theme/icons/info.svg';
+
+/* global window, document */
+
+export default class MermaidUI extends Plugin {
+ /**
+ * @inheritDoc
+ */
+ static get pluginName() {
+ return 'MermaidUI';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ init() {
+ this._addButtons();
+ }
+
+ /**
+ * Adds all mermaid-related buttons.
+ *
+ * @private
+ */
+ _addButtons() {
+ const editor = this.editor;
+
+ this._addInsertMermaidButton();
+ this._addMermaidInfoButton();
+ this._createToolbarButton( editor, 'mermaidPreview', 'Preview', previewModeIcon );
+ this._createToolbarButton( editor, 'mermaidSourceView', 'Source view', sourceModeIcon );
+ this._createToolbarButton( editor, 'mermaidSplitView', 'Split view', splitModeIcon );
+ }
+
+ /**
+ * Adds the button for inserting mermaid.
+ *
+ * @private
+ */
+ _addInsertMermaidButton() {
+ const editor = this.editor;
+ const t = editor.t;
+ const view = editor.editing.view;
+
+ editor.ui.componentFactory.add( 'mermaid', locale => {
+ const buttonView = new ButtonView( locale );
+ const command = editor.commands.get( 'insertMermaidCommand' );
+
+ buttonView.set( {
+ label: t( 'Insert Mermaid diagram' ),
+ icon: insertMermaidIcon,
+ tooltip: true
+ } );
+
+ buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
+
+ // Execute the command when the button is clicked.
+ command.listenTo( buttonView, 'execute', () => {
+ const mermaidItem = editor.execute( 'insertMermaidCommand' );
+ const mermaidItemViewElement = editor.editing.mapper.toViewElement( mermaidItem );
+
+ view.scrollToTheSelection();
+ view.focus();
+
+ if ( mermaidItemViewElement ) {
+ const mermaidItemDomElement = view.domConverter.viewToDom( mermaidItemViewElement, document );
+
+ if ( mermaidItemDomElement ) {
+ mermaidItemDomElement.querySelector( '.ck-mermaid__editing-view' ).focus();
+ }
+ }
+ } );
+
+ return buttonView;
+ } );
+ }
+
+ /**
+ * Adds the button linking to the mermaid guide.
+ *
+ * @private
+ */
+ _addMermaidInfoButton() {
+ const editor = this.editor;
+ const t = editor.t;
+
+ editor.ui.componentFactory.add( 'mermaidInfo', locale => {
+ const buttonView = new ButtonView( locale );
+ const link = 'https://ckeditor.com/blog/basic-overview-of-creating-flowcharts-using-mermaid/';
+
+ buttonView.set( {
+ label: t( 'Read more about Mermaid diagram syntax' ),
+ icon: infoIcon,
+ tooltip: true
+ } );
+
+ buttonView.on( 'execute', () => {
+ window.open( link, '_blank', 'noopener' );
+ } );
+
+ return buttonView;
+ } );
+ }
+
+ /**
+ * Adds the mermaid balloon toolbar button.
+ *
+ * @private
+ * @param {module:core/editor/editor~Editor} editor
+ * @param {String} name Name of the button.
+ * @param {String} label Label for the button.
+ * @param {String} icon The button icon.
+ */
+ _createToolbarButton( editor, name, label, icon ) {
+ const t = editor.t;
+
+ editor.ui.componentFactory.add( name, locale => {
+ const buttonView = new ButtonView( locale );
+ const command = editor.commands.get( `${ name }Command` );
+
+ buttonView.set( {
+ label: t( label ),
+ icon,
+ tooltip: true
+ } );
+
+ buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
+
+ // Execute the command when the button is clicked.
+ command.listenTo( buttonView, 'execute', () => {
+ editor.execute( `${ name }Command` );
+ editor.editing.view.scrollToTheSelection();
+ editor.editing.view.focus();
+ } );
+
+ return buttonView;
+ } );
+ }
+}
diff --git a/_regroup/ckeditor5-mermaid/src/utils.js b/_regroup/ckeditor5-mermaid/src/utils.js
new file mode 100644
index 000000000..98ba6f879
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/src/utils.js
@@ -0,0 +1,27 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module mermaid/utils
+ */
+
+/**
+ * Helper function for setting the `isOn` state of buttons.
+ *
+ * @private
+ * @param {module:core/editor/editor~Editor} editor
+ * @param {String} commandName Short name of the command.
+ * @returns {Boolean}
+ */
+export function checkIsOn( editor, commandName ) {
+ const selection = editor.model.document.selection;
+ const mermaidItem = selection.getSelectedElement() || selection.getLastPosition().parent;
+
+ if ( mermaidItem && mermaidItem.is( 'element', 'mermaid' ) && mermaidItem.getAttribute( 'displayMode' ) === commandName ) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/_regroup/ckeditor5-mermaid/tests/commands/insertMermaidCommand.js b/_regroup/ckeditor5-mermaid/tests/commands/insertMermaidCommand.js
new file mode 100644
index 000000000..f210adcd7
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/commands/insertMermaidCommand.js
@@ -0,0 +1,93 @@
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import {
+ _setModelData as setModelData,
+ _getModelData as getModelData
+} from '@ckeditor/ckeditor5-engine';
+
+import InsertMermaidCommand from '../../src/commands/insertMermaidCommand.js';
+import MermaidEditing from '../../src/mermaidediting.js';
+
+/* global document */
+
+describe( 'InsertMermaidCommand', () => {
+ let domElement, editor, model, command;
+
+ beforeEach( async () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ editor = await ClassicEditor.create( domElement, {
+ plugins: [
+ MermaidEditing,
+ Paragraph
+ ]
+ } );
+
+ model = editor.model;
+
+ command = new InsertMermaidCommand( editor );
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ describe( '#isEnabled', () => {
+ describe( 'should be false', () => {
+ it( 'when selection is inside mermaid', () => {
+ setModelData( model,
+ 'foo ' +
+ '[] '
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+
+ it( 'when mermaid is selected', () => {
+ setModelData( model,
+ 'foo ' +
+ '[ ]'
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+ } );
+
+ describe( 'should be true', () => {
+ it( 'when text is selected', () => {
+ setModelData( model,
+ '[foo] ' +
+ ' '
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+
+ it( 'when mermaid is part of the selection', () => {
+ setModelData( model,
+ '[foo ' +
+ ' ' +
+ 'b]az '
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+ } );
+ } );
+
+ describe( 'execute()', () => {
+ it( 'should add sample mermaid', () => {
+ setModelData( model,
+ '[foo] '
+ );
+
+ command.execute();
+
+ expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
+ ' '
+ );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/commands/mermaidPreviewCommand.js b/_regroup/ckeditor5-mermaid/tests/commands/mermaidPreviewCommand.js
new file mode 100644
index 000000000..3de865799
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/commands/mermaidPreviewCommand.js
@@ -0,0 +1,113 @@
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import {
+ _setModelData as setModelData,
+ _getModelData as getModelData
+} from '@ckeditor/ckeditor5-engine';
+
+import MermaidPreviewCommand from '../../src/commands/mermaidPreviewCommand.js';
+import MermaidEditing from '../../src/mermaidediting.js';
+
+/* global document */
+
+describe( 'MermaidPreviewCommand', () => {
+ let domElement, editor, model, command;
+
+ beforeEach( async () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ editor = await ClassicEditor.create( domElement, {
+ plugins: [
+ MermaidEditing,
+ Paragraph
+ ]
+ } );
+
+ model = editor.model;
+
+ command = new MermaidPreviewCommand( editor );
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ describe( '#value', () => {
+ it( 'should be true when mermaid element has displayMode attribute equal to "preview"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( true );
+ } );
+
+ it( 'should be false when mermaid element has displayMode attribute equal to "split"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( false );
+ } );
+
+ it( 'should be false when mermaid element has displayMode attribute equal to "source"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( false );
+ } );
+ } );
+
+ describe( '#isEnabled', () => {
+ describe( 'should be false', () => {
+ it( 'when text is selected', () => {
+ setModelData( model,
+ '[foo] ' +
+ ' '
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+
+ it( 'when mermaid is part of the selection', () => {
+ setModelData( model,
+ '[foo ' +
+ ' ' +
+ 'b]az '
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+ } );
+
+ describe( 'should be true', () => {
+ it( 'when selection is inside mermaid', () => {
+ setModelData( model,
+ 'foo ' +
+ '[] '
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+
+ it( 'when mermaid is selected', () => {
+ setModelData( model,
+ 'foo ' +
+ '[ ]'
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+ } );
+ } );
+
+ describe( 'execute()', () => {
+ it( 'should change displayMode to "preview" for mermaid', () => {
+ setModelData( model,
+ '[ ]'
+ );
+
+ command.execute();
+
+ expect( getModelData( model ) ).to.equal(
+ '[ ]'
+ );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/commands/mermaidSourceViewCommand.js b/_regroup/ckeditor5-mermaid/tests/commands/mermaidSourceViewCommand.js
new file mode 100644
index 000000000..ec32aadb0
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/commands/mermaidSourceViewCommand.js
@@ -0,0 +1,113 @@
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import {
+ _setModelData as setModelData,
+ _getModelData as getModelData
+} from '@ckeditor/ckeditor5-engine';
+
+import MermaidSourceViewCommand from '../../src/commands/mermaidSourceViewCommand.js';
+import MermaidEditing from '../../src/mermaidediting.js';
+
+/* global document */
+
+describe( 'MermaidSourceViewCommand', () => {
+ let domElement, editor, model, command;
+
+ beforeEach( async () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ editor = await ClassicEditor.create( domElement, {
+ plugins: [
+ MermaidEditing,
+ Paragraph
+ ]
+ } );
+
+ model = editor.model;
+
+ command = new MermaidSourceViewCommand( editor );
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ describe( '#value', () => {
+ it( 'should be true when mermaid element has displayMode attribute equal to "preview"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( false );
+ } );
+
+ it( 'should be false when mermaid element has displayMode attribute equal to "split"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( false );
+ } );
+
+ it( 'should be false when mermaid element has displayMode attribute equal to "source"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( true );
+ } );
+ } );
+
+ describe( '#isEnabled', () => {
+ describe( 'should be false', () => {
+ it( 'when text is selected', () => {
+ setModelData( model,
+ '[foo] ' +
+ ' '
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+
+ it( 'when mermaid is part of the selection', () => {
+ setModelData( model,
+ '[foo ' +
+ ' ' +
+ 'b]az '
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+ } );
+
+ describe( 'should be true', () => {
+ it( 'when selection is inside mermaid', () => {
+ setModelData( model,
+ 'foo ' +
+ '[] '
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+
+ it( 'when mermaid is selected', () => {
+ setModelData( model,
+ 'foo ' +
+ '[ ]'
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+ } );
+ } );
+
+ describe( 'execute()', () => {
+ it( 'should add text', () => {
+ setModelData( model,
+ '[ ]'
+ );
+
+ command.execute();
+
+ expect( getModelData( model ) ).to.equal(
+ '[ ]'
+ );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/commands/mermaidSplitViewCommand.js b/_regroup/ckeditor5-mermaid/tests/commands/mermaidSplitViewCommand.js
new file mode 100644
index 000000000..ddc43a32c
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/commands/mermaidSplitViewCommand.js
@@ -0,0 +1,113 @@
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import {
+ _setModelData as setModelData,
+ _getModelData as getModelData
+} from '@ckeditor/ckeditor5-engine';
+
+import MermaidSplitViewCommand from '../../src/commands/mermaidSplitViewCommand.js';
+import MermaidEditing from '../../src/mermaidediting.js';
+
+/* global document */
+
+describe( 'MermaidSplitViewCommand', () => {
+ let domElement, editor, model, command;
+
+ beforeEach( async () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ editor = await ClassicEditor.create( domElement, {
+ plugins: [
+ MermaidEditing,
+ Paragraph
+ ]
+ } );
+
+ model = editor.model;
+
+ command = new MermaidSplitViewCommand( editor );
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ describe( '#value', () => {
+ it( 'should be true when mermaid element has displayMode attribute equal to "preview"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( false );
+ } );
+
+ it( 'should be false when mermaid element has displayMode attribute equal to "split"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( true );
+ } );
+
+ it( 'should be false when mermaid element has source attribute equal to "source"', () => {
+ setModelData( model, ' ' );
+
+ expect( command.value ).to.equal( false );
+ } );
+ } );
+
+ describe( '#isEnabled', () => {
+ describe( 'should be false', () => {
+ it( 'when text is selected', () => {
+ setModelData( model,
+ '[foo] ' +
+ ' '
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+
+ it( 'when mermaid is part of the selection', () => {
+ setModelData( model,
+ '[foo ' +
+ ' ' +
+ 'b]az '
+ );
+
+ expect( command.isEnabled ).to.be.false;
+ } );
+ } );
+
+ describe( 'should be true', () => {
+ it( 'when selection is inside mermaid', () => {
+ setModelData( model,
+ 'foo ' +
+ '[] '
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+
+ it( 'when mermaid is selected', () => {
+ setModelData( model,
+ 'foo ' +
+ '[ ]'
+ );
+
+ expect( command.isEnabled ).to.be.true;
+ } );
+ } );
+ } );
+
+ describe( 'execute()', () => {
+ it( 'should change displayMode to "source" for mermaid', () => {
+ setModelData( model,
+ '[ ]'
+ );
+
+ command.execute();
+
+ expect( getModelData( model ) ).to.equal(
+ '[ ]'
+ );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/index.js b/_regroup/ckeditor5-mermaid/tests/index.js
new file mode 100644
index 000000000..b234b0905
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/index.js
@@ -0,0 +1,32 @@
+import { Mermaid as MermaidDll, icons } from '../src/index.js';
+import Mermaid from '../src/mermaid.js';
+
+import infoIcon from './../theme/icons/info.svg';
+import insertMermaidIcon from './../theme/icons/insert.svg';
+import previewModeIcon from './../theme/icons/preview-mode.svg';
+import splitModeIcon from './../theme/icons/split-mode.svg';
+import sourceModeIcon from './../theme/icons/source-mode.svg';
+
+describe( 'CKEditor5 Mermaid DLL', () => {
+ it( 'exports MermaidWidget', () => {
+ expect( MermaidDll ).to.equal( Mermaid );
+ } );
+
+ describe( 'icons', () => {
+ it( 'exports the "insertMermaidIcon" icon', () => {
+ expect( icons.insertMermaidIcon ).to.equal( insertMermaidIcon );
+ } );
+ it( 'exports the "infoIcon" icon', () => {
+ expect( icons.infoIcon ).to.equal( infoIcon );
+ } );
+ it( 'exports the "previewModeIcon" icon', () => {
+ expect( icons.previewModeIcon ).to.equal( previewModeIcon );
+ } );
+ it( 'exports the "splitModeIcon" icon', () => {
+ expect( icons.splitModeIcon ).to.equal( splitModeIcon );
+ } );
+ it( 'exports the "sourceModeIcon" icon', () => {
+ expect( icons.sourceModeIcon ).to.equal( sourceModeIcon );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/manual/markdown.html b/_regroup/ckeditor5-mermaid/tests/manual/markdown.html
new file mode 100644
index 000000000..2285c9adf
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/manual/markdown.html
@@ -0,0 +1,67 @@
+
+
+
diff --git a/_regroup/ckeditor5-mermaid/tests/manual/markdown.js b/_regroup/ckeditor5-mermaid/tests/manual/markdown.js
new file mode 100644
index 000000000..8f1f26f9e
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/manual/markdown.js
@@ -0,0 +1,67 @@
+/**
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Typing } from '@ckeditor/ckeditor5-typing';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import { Undo } from '@ckeditor/ckeditor5-undo';
+import { Enter } from '@ckeditor/ckeditor5-enter';
+import { Clipboard } from '@ckeditor/ckeditor5-clipboard';
+import { Link } from '@ckeditor/ckeditor5-link';
+import { Bold, Italic } from '@ckeditor/ckeditor5-basic-styles';
+import { Markdown } from '@ckeditor/ckeditor5-markdown-gfm';
+import { CodeBlock } from '@ckeditor/ckeditor5-code-block';
+
+import Mermaid from '../../src/mermaid.js';
+
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ plugins: [
+ Markdown,
+ Typing,
+ Paragraph,
+ Undo,
+ Enter,
+ Clipboard,
+ Link,
+ Bold,
+ Italic,
+ CodeBlock,
+ Mermaid
+ ],
+ toolbar: [ 'bold', 'italic', 'link', 'undo', 'redo', 'codeBlock', 'mermaid' ],
+ codeBlock: {
+ languages: [
+ { language: 'plaintext', label: 'Plain text', class: '' },
+ { language: 'javascript', label: 'JavaScript' },
+ { language: 'python', label: 'Python' },
+ { language: 'mermaid', label: 'Mermaid' }
+ ]
+ }
+
+ } )
+ .then( editor => {
+ window.editor = editor;
+
+ setupMarkdownOutputPreview( editor );
+ } )
+ .catch( err => {
+ console.error( err.stack );
+ } );
+
+function setupMarkdownOutputPreview( editor ) {
+ const outputElement = document.querySelector( '#markdown-output' );
+
+ editor.model.document.on( 'change', () => {
+ outputElement.innerText = editor.getData();
+ } );
+
+ // Set the initial data with delay so hightlight.js doesn't catch them.
+ window.setTimeout( () => {
+ outputElement.innerText = editor.getData();
+ }, 500 );
+}
diff --git a/_regroup/ckeditor5-mermaid/tests/manual/markdown.md b/_regroup/ckeditor5-mermaid/tests/manual/markdown.md
new file mode 100644
index 000000000..abd7e5eec
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/manual/markdown.md
@@ -0,0 +1 @@
+## Mermaid widget
\ No newline at end of file
diff --git a/_regroup/ckeditor5-mermaid/tests/manual/mermaid.html b/_regroup/ckeditor5-mermaid/tests/manual/mermaid.html
new file mode 100644
index 000000000..03fcf802d
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/manual/mermaid.html
@@ -0,0 +1,38 @@
+
+
+
+
Mermaid snippet:
+
+
+
+flowchart TB
+A --> C
+A --> D
+B --> C
+B --> D
+
+
+
+
More complex case:
+
+
+
+sequenceDiagram
+participant Alice
+participant Bob
+Alice->>John: Hello John, how are you?
+loop Healthcheck
+ John->>John: Fight against hypochondria
+end
+Note right of John: Rational thoughts prevail!
+John-->>Alice: Great!
+John->>Bob: How about you?
+Bob-->>John: Jolly good!
+
+
+
diff --git a/_regroup/ckeditor5-mermaid/tests/manual/mermaid.js b/_regroup/ckeditor5-mermaid/tests/manual/mermaid.js
new file mode 100644
index 000000000..de9f7832b
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/manual/mermaid.js
@@ -0,0 +1,50 @@
+/**
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Typing } from '@ckeditor/ckeditor5-typing';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import { Undo } from '@ckeditor/ckeditor5-undo';
+import { Enter } from '@ckeditor/ckeditor5-enter';
+import { Clipboard } from '@ckeditor/ckeditor5-clipboard';
+import { Link } from '@ckeditor/ckeditor5-link';
+import { Bold, Italic } from '@ckeditor/ckeditor5-basic-styles';
+import { CodeBlock } from '@ckeditor/ckeditor5-code-block';
+
+import Mermaid from '../../src/mermaid.js';
+
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ plugins: [
+ Typing,
+ Paragraph,
+ Undo,
+ Enter,
+ Clipboard,
+ Link,
+ Bold,
+ Italic,
+ CodeBlock,
+ Mermaid
+ ],
+ toolbar: [ 'bold', 'italic', 'link', 'undo', 'redo', 'codeBlock', 'mermaid' ],
+ codeBlock: {
+ languages: [
+ { language: 'plaintext', label: 'Plain text', class: '' },
+ { language: 'javascript', label: 'JavaScript' },
+ { language: 'python', label: 'Python' },
+ { language: 'mermaid', label: 'Mermaid' }
+ ]
+ }
+
+ } )
+ .then( editor => {
+ window.editor = editor;
+ } )
+ .catch( err => {
+ console.error( err.stack );
+ } );
diff --git a/_regroup/ckeditor5-mermaid/tests/manual/mermaid.md b/_regroup/ckeditor5-mermaid/tests/manual/mermaid.md
new file mode 100644
index 000000000..abd7e5eec
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/manual/mermaid.md
@@ -0,0 +1 @@
+## Mermaid widget
\ No newline at end of file
diff --git a/_regroup/ckeditor5-mermaid/tests/mermaid.js b/_regroup/ckeditor5-mermaid/tests/mermaid.js
new file mode 100644
index 000000000..7a629848b
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/mermaid.js
@@ -0,0 +1,47 @@
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Essentials } from '@ckeditor/ckeditor5-essentials';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import { Heading } from '@ckeditor/ckeditor5-heading';
+import { _setModelData as setModelData } from '@ckeditor/ckeditor5-engine';
+
+import Mermaid from '../src/mermaid.js';
+
+/* global document */
+
+describe( 'Mermaid', () => {
+ it( 'should be named', () => {
+ expect( Mermaid.pluginName ).to.equal( 'Mermaid' );
+ } );
+
+ describe( 'init()', () => {
+ let domElement, editor;
+
+ beforeEach( async () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ editor = await ClassicEditor.create( domElement, {
+ plugins: [
+ Paragraph,
+ Heading,
+ Essentials,
+ Mermaid
+ ],
+ toolbar: [
+ 'mermaid'
+ ]
+ } );
+
+ setModelData( editor.model, '[] ' );
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ it( 'should add an icon to the toolbar', () => {
+ expect( editor.ui.componentFactory.has( 'Mermaid' ) ).to.equal( true );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/mermaidediting.js b/_regroup/ckeditor5-mermaid/tests/mermaidediting.js
new file mode 100644
index 000000000..44e8daed3
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/mermaidediting.js
@@ -0,0 +1,296 @@
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Essentials } from '@ckeditor/ckeditor5-essentials';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import { Heading } from '@ckeditor/ckeditor5-heading';
+import { CodeBlockEditing } from '@ckeditor/ckeditor5-code-block';
+import {
+ _setModelData as setModelData,
+ _getModelData as getModelData,
+ _getViewData as getViewData
+} from '@ckeditor/ckeditor5-engine';
+import MermaidEditing from '../src/mermaidediting.js';
+
+/* global document */
+
+describe( 'MermaidEditing', () => {
+ it( 'should be named', () => {
+ expect( MermaidEditing.pluginName ).to.equal( 'MermaidEditing' );
+ } );
+
+ describe( 'conversion', () => {
+ let domElement, editor, model;
+
+ beforeEach( async () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ editor = await ClassicEditor.create( domElement, {
+ plugins: [
+ Paragraph,
+ Heading,
+ Essentials,
+ CodeBlockEditing,
+ MermaidEditing
+ ]
+ } );
+
+ model = editor.model;
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ describe( 'conversion', () => {
+ describe( 'upcast', () => {
+ it( 'works correctly', () => {
+ editor.setData(
+ '' +
+ 'flowchart TB\nA --> B\nB --> C
' +
+ ' '
+ );
+
+ expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
+ '' +
+ ' '
+ );
+ } );
+
+ it( 'works correctly when empty', () => {
+ editor.setData(
+ '' +
+ '
' +
+ ' '
+ );
+
+ expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
+ ' '
+ );
+ } );
+ } );
+
+ describe( 'data downcast', () => {
+ it( 'works correctly', () => {
+ // Using editor.setData() instead of setModelData helper because of #11365.
+ editor.setData(
+ '' +
+ 'flowchart TB\nA --> B\nB --> C
' +
+ ' '
+ );
+
+ expect( editor.getData() ).to.equal(
+ '' +
+ 'flowchart TB\nA --> B\nB --> C
' +
+ ' '
+ );
+ } );
+
+ it( 'works correctly when empty ', () => {
+ // Using editor.setData() instead of setModelData helper because of #11365.
+ editor.setData(
+ '' +
+ '
' +
+ ' '
+ );
+
+ expect( editor.getData() ).to.equal(
+ '' +
+ '
' +
+ ' '
+ );
+ } );
+ } );
+
+ describe( 'editing downcast', () => {
+ it( 'works correctly without displayMode attribute', () => {
+ // Using editor.setData() instead of setModelData helper because of #11365.
+ editor.setData(
+ '' +
+ 'flowchart TB\nA --> B\nB --> C
' +
+ ' '
+ );
+
+ expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
+ ''
+ );
+ } );
+
+ it( 'works correctly with displayMode attribute', () => {
+ setModelData( editor.model,
+ ' '
+ );
+
+ expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
+ ''
+ );
+ } );
+
+ it( 'works correctly with empty source', () => {
+ setModelData( editor.model,
+ ' '
+ );
+
+ expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
+ ''
+ );
+ } );
+
+ describe( 'textarea value', () => {
+ let domTextarea = null;
+
+ beforeEach( () => {
+ // Using editor.setData() instead of setModelData helper because of #11365.
+ editor.setData(
+ '' +
+ 'flowchart TB\nA --> B\nB --> C
' +
+ ' '
+ );
+
+ const textareaView = editor.editing.view.document.getRoot().getChild( 0 ).getChild( 1 );
+ domTextarea = editor.editing.view.domConverter.viewToDom( textareaView );
+ } );
+
+ it( 'is properly set during the initial conversion', () => {
+ expect( domTextarea.value ).to.equal( 'flowchart TB\nA --> B\nB --> C' );
+ } );
+
+ it( 'is properly updated after model\'s attribute change', () => {
+ const { model } = editor;
+
+ const mermaidModel = model.document.getRoot().getChild( 0 );
+
+ model.change( writer => {
+ writer.setAttribute( 'source', 'abc', mermaidModel );
+ } );
+
+ expect( domTextarea.value ).to.equal( 'abc' );
+ } );
+
+ it( 'doesn\'t loop if model attribute changes to the same value', () => {
+ const { model } = editor;
+
+ const mermaidModel = model.document.getRoot().getChild( 0 );
+
+ model.change( writer => {
+ writer.setAttribute( 'source', 'flowchart TB\nA --> B\nB --> C', mermaidModel );
+ } );
+
+ expect( domTextarea.value ).to.equal( 'flowchart TB\nA --> B\nB --> C' );
+ } );
+ } );
+
+ describe( 'preview div', () => {
+ let domPreviewContainer, renderMermaidStub;
+
+ beforeEach( () => {
+ // Using editor.setData() instead of setModelData helper because of #11365.
+ editor.setData(
+ '' +
+ 'flowchart TB\nA --> B\nB --> C
' +
+ ' '
+ );
+
+ const previewContainerView = editor.editing.view.document.getRoot().getChild( 0 ).getChild( 2 );
+ domPreviewContainer = editor.editing.view.domConverter.viewToDom( previewContainerView );
+
+ renderMermaidStub = sinon.stub( editor.plugins.get( 'MermaidEditing' ), '_renderMermaid' );
+ } );
+
+ afterEach( () => {
+ renderMermaidStub.restore();
+ } );
+
+ it( 'has proper inner text set during the initial conversion', () => {
+ expect( domPreviewContainer.textContent ).to.equal( 'flowchart TB\nA --> B\nB --> C' );
+ } );
+
+ it( 'has proper inner text set after a model\'s attribute change', () => {
+ const { model } = editor;
+
+ const mermaidModel = model.document.getRoot().getChild( 0 );
+
+ model.change( writer => {
+ writer.setAttribute( 'source', 'abc', mermaidModel );
+ } );
+
+ expect( domPreviewContainer.textContent ).to.equal( 'abc' );
+ } );
+
+ it( 'calls mermaid render function after a model\'s attribute change', () => {
+ const { model } = editor;
+
+ const mermaidModel = model.document.getRoot().getChild( 0 );
+
+ model.change( writer => {
+ writer.setAttribute( 'source', 'abc', mermaidModel );
+ } );
+
+ expect( renderMermaidStub.callCount ).to.equal( 1 );
+ sinon.assert.calledWithExactly( renderMermaidStub, domPreviewContainer );
+ } );
+ } );
+ } );
+
+ it( 'adds a editing pipeline converter that has a precedence over code block', () => {
+ setModelData( editor.model, ' ' );
+
+ const firstViewChild = editor.editing.view.document.getRoot().getChild( 0 );
+
+ expect( firstViewChild.name ).to.equal( 'div' );
+ expect( firstViewChild.hasClass( 'ck-mermaid__wrapper' ), 'has ck-mermaid__wrapper class' ).to.be.true;
+ } );
+
+ it( 'does not convert code blocks other than mermaid language', () => {
+ setModelData( editor.model, 'foo ' );
+
+ const firstViewChild = editor.editing.view.document.getRoot().getChild( 0 );
+
+ expect( firstViewChild.name ).not.to.equal( 'div' );
+ expect( firstViewChild.hasClass( 'ck-mermaid__wrapper' ), 'has ck-mermaid__wrapper class' ).to.be.false;
+ } );
+
+ it( 'adds a preview element', () => {
+ setModelData( editor.model, ' ' );
+
+ const widgetChildren = [ ...editor.editing.view.document.getRoot().getChild( 0 ).getChildren() ];
+ const previewView = widgetChildren.filter( item => item.name === 'div' && item.hasClass( 'ck-mermaid__preview' ) );
+
+ expect( previewView.length ).to.equal( 1 );
+ } );
+
+ it( 'adds an editing element', () => {
+ setModelData( editor.model, ' ' );
+
+ const widgetChildren = [ ...editor.editing.view.document.getRoot().getChild( 0 ).getChildren() ];
+ const previewView = widgetChildren.filter(
+ item => item.name === 'textarea' && item.hasClass( 'ck-mermaid__editing-view' )
+ );
+
+ expect( previewView.length ).to.equal( 1 );
+ } );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/mermaidtoolbar.js b/_regroup/ckeditor5-mermaid/tests/mermaidtoolbar.js
new file mode 100644
index 000000000..2bf0a9550
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/mermaidtoolbar.js
@@ -0,0 +1,154 @@
+import { ClassicEditor as ClassicTestEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { Essentials } from '@ckeditor/ckeditor5-essentials';
+import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
+import { WidgetToolbarRepository } from '@ckeditor/ckeditor5-widget';
+import { _setModelData as setData } from '@ckeditor/ckeditor5-engine';
+
+import Mermaid from '../src/mermaid.js';
+
+/* global document */
+
+describe( 'MermaidToolbar', () => {
+ let editor, domElement, widgetToolbarRepository, balloon, toolbar, model;
+
+ beforeEach( () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ return ClassicTestEditor.create( domElement, {
+ plugins: [ Essentials, Paragraph, Mermaid ],
+ mermaid: {
+ toolbar: [ 'fake_button' ]
+ }
+ } ).then( newEditor => {
+ editor = newEditor;
+ model = newEditor.model;
+ widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+ toolbar = widgetToolbarRepository._toolbarDefinitions.get( 'mermaidToolbar' ).view;
+ balloon = editor.plugins.get( 'ContextualBalloon' );
+ } );
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ describe( 'toolbar', () => {
+ it( 'should be initialized with expected buttons', () => {
+ editor.ui.focusTracker.isFocused = true;
+
+ setData( model, '[] ' );
+
+ expect( toolbar.items ).to.have.length( 5 );
+ expect( toolbar.items.get( 0 ).label ).to.equal( 'Source view' );
+ expect( toolbar.items.get( 1 ).label ).to.equal( 'Split view' );
+ expect( toolbar.items.get( 2 ).label ).to.equal( 'Preview' );
+ expect( toolbar.items.get( 4 ).label ).to.equal( 'Read more about Mermaid diagram syntax' );
+ } );
+ } );
+
+ describe( 'integration with the editor focus', () => {
+ it( 'should show the toolbar when the editor gains focus and the mermaid widget is selected', () => {
+ setData( model,
+ '[ ]'
+ );
+
+ expect( balloon.visibleView ).to.be.null;
+ // @todo: remove me
+ // expect( balloon.visibleView === null, 'balloon.visibleView === null' ).to.be.true;
+
+ editor.ui.focusTracker.isFocused = true;
+ expect( balloon.visibleView ).to.equal( toolbar );
+ // @todo: remove me
+ // expect( balloon.visibleView === toolbar, 'balloon.visibleView === toolbar' ).to.be.true;
+ } );
+
+ it( 'should hide the toolbar when the editor loses focus and the mermaid widget is selected', () => {
+ setData( model,
+ '[ ]'
+ );
+
+ editor.ui.focusTracker.isFocused = true;
+ expect( balloon.visibleView ).to.equal( toolbar );
+
+ editor.ui.focusTracker.isFocused = false;
+ expect( balloon.visibleView ).to.be.null;
+ } );
+ } );
+
+ describe( 'integration with the editor selection', () => {
+ beforeEach( () => {
+ editor.ui.focusTracker.isFocused = true;
+ } );
+
+ it( 'should show the toolbar on ui#update when the mermaid widget is selected', () => {
+ setData( model,
+ '[foo] ' +
+ ' '
+ );
+
+ expect( balloon.visibleView ).to.be.null;
+
+ editor.ui.fire( 'update' );
+
+ expect( balloon.visibleView ).to.be.null;
+
+ model.change( writer => {
+ // Set selection to the [ ]
+ writer.setSelection( model.document.getRoot().getChild( 1 ), 'on' );
+ } );
+
+ expect( balloon.visibleView ).to.equal( toolbar );
+
+ // Make sure successive change does not throw, e.g. attempting
+ // to insert the toolbar twice.
+ editor.ui.fire( 'update' );
+
+ expect( balloon.visibleView ).to.equal( toolbar );
+ } );
+
+ it( 'should hide the toolbar on ui#update if the mermaid widget is de–selected', () => {
+ setData( model,
+ 'foo ' +
+ '[ ]'
+ );
+ expect( balloon.visibleView ).to.equal( toolbar );
+
+ model.change( writer => {
+ // Select the [foo]
+ writer.setSelection( model.document.getRoot().getChild( 0 ), 'in' );
+ } );
+
+ expect( balloon.visibleView ).to.be.null;
+
+ // Make sure successive change does not throw, e.g. attempting
+ // to remove the toolbar twice.
+ editor.ui.fire( 'update' );
+
+ expect( balloon.visibleView ).to.be.null;
+ } );
+
+ it( 'should not hide the toolbar on ui#update when the selection is being moved from one mermaid widget to another', () => {
+ setData( model,
+ '[ ]' +
+ ' '
+ );
+
+ expect( balloon.visibleView ).to.equal( toolbar );
+
+ model.change( writer => {
+ // Set selection to the second
+ writer.setSelection( model.document.selection.getSelectedElement().nextSibling, 'on' );
+ } );
+
+ expect( balloon.visibleView ).to.equal( toolbar );
+
+ // Make sure successive change does not throw, e.g. attempting
+ // to insert the toolbar twice.
+ editor.ui.fire( 'update' );
+
+ expect( balloon.visibleView ).to.equal( toolbar );
+ } );
+ } );
+} );
diff --git a/_regroup/ckeditor5-mermaid/tests/mermaidui.js b/_regroup/ckeditor5-mermaid/tests/mermaidui.js
new file mode 100644
index 000000000..73ef1fd7f
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/tests/mermaidui.js
@@ -0,0 +1,88 @@
+import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
+import { _setModelData as setModelData } from '@ckeditor/ckeditor5-engine';
+
+import Mermaid from '../src/mermaid.js';
+import MermaidUI from '../src/mermaidui.js';
+
+/* global document */
+
+describe( 'MermaidUI', () => {
+ it( 'should be named', () => {
+ expect( MermaidUI.pluginName ).to.equal( 'MermaidUI' );
+ } );
+
+ describe( 'init()', () => {
+ let domElement, editor;
+
+ beforeEach( async () => {
+ domElement = document.createElement( 'div' );
+ document.body.appendChild( domElement );
+
+ editor = await ClassicEditor.create( domElement, {
+ plugins: [
+ Mermaid
+ ]
+ } );
+ } );
+
+ afterEach( () => {
+ domElement.remove();
+ return editor.destroy();
+ } );
+
+ it( 'should register the UI item', () => {
+ expect( editor.ui.componentFactory.has( 'mermaid' ) ).to.equal( true );
+ } );
+
+ it( 'has the base properties', () => {
+ const button = editor.ui.componentFactory.create( 'mermaid' );
+
+ expect( button ).to.have.property( 'label', 'Insert Mermaid diagram' );
+ expect( button ).to.have.property( 'icon' );
+ expect( button ).to.have.property( 'tooltip', true );
+ } );
+
+ describe( 'UI components', () => {
+ for ( const buttonName of [
+ 'mermaidPreview',
+ 'mermaidSourceView',
+ 'mermaidSplitView',
+ 'mermaidInfo'
+ ] ) {
+ it( `should register the ${ buttonName } button`, () => {
+ expect( editor.ui.componentFactory.has( buttonName ) ).to.equal( true );
+ } );
+
+ it( `should add the base properties for ${ buttonName } button`, () => {
+ const button = editor.ui.componentFactory.create( buttonName );
+
+ expect( button ).to.have.property( 'label' );
+ expect( button ).to.have.property( 'icon' );
+ expect( button ).to.have.property( 'tooltip', true );
+ } );
+ }
+ } );
+
+ it( 'should set focus inside textarea of a newly created mermaid', () => {
+ const button = editor.ui.componentFactory.create( 'mermaid' );
+
+ button.fire( 'execute' );
+
+ expect( document.activeElement.tagName ).to.equal( 'TEXTAREA' );
+ } );
+
+ it( 'should not crash if the button is fired inside model.change()', () => {
+ const button = editor.ui.componentFactory.create( 'mermaid' );
+
+ setModelData( editor.model, '[]' );
+
+ editor.model.change( () => {
+ button.fire( 'execute' );
+ } );
+ // As the conversion is to be executed after the model.change(), we don't have access to the fully prepared view and
+ // despite that, we should still successfully add mermaid widget to the editor, not requiring the selection change
+ // to the inside of the nonexisting textarea element.
+ } );
+ } );
+} );
+
diff --git a/_regroup/ckeditor5-mermaid/theme/icons/info.svg b/_regroup/ckeditor5-mermaid/theme/icons/info.svg
new file mode 100644
index 000000000..c9614b2aa
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/theme/icons/info.svg
@@ -0,0 +1 @@
+
diff --git a/_regroup/ckeditor5-mermaid/theme/icons/insert.svg b/_regroup/ckeditor5-mermaid/theme/icons/insert.svg
new file mode 100644
index 000000000..b87e06bf7
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/theme/icons/insert.svg
@@ -0,0 +1 @@
+
diff --git a/_regroup/ckeditor5-mermaid/theme/icons/preview-mode.svg b/_regroup/ckeditor5-mermaid/theme/icons/preview-mode.svg
new file mode 100644
index 000000000..cdccfcaf1
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/theme/icons/preview-mode.svg
@@ -0,0 +1 @@
+
diff --git a/_regroup/ckeditor5-mermaid/theme/icons/source-mode.svg b/_regroup/ckeditor5-mermaid/theme/icons/source-mode.svg
new file mode 100644
index 000000000..be4647c11
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/theme/icons/source-mode.svg
@@ -0,0 +1 @@
+
diff --git a/_regroup/ckeditor5-mermaid/theme/icons/split-mode.svg b/_regroup/ckeditor5-mermaid/theme/icons/split-mode.svg
new file mode 100644
index 000000000..ef07ec185
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/theme/icons/split-mode.svg
@@ -0,0 +1 @@
+
diff --git a/_regroup/ckeditor5-mermaid/theme/mermaid.css b/_regroup/ckeditor5-mermaid/theme/mermaid.css
new file mode 100644
index 000000000..bbd66befe
--- /dev/null
+++ b/_regroup/ckeditor5-mermaid/theme/mermaid.css
@@ -0,0 +1,61 @@
+/* Mermaid wrapper */
+.ck-mermaid__wrapper {
+ display: flex;
+ justify-content: center;
+}
+
+/* Editing
+ Assigned to textarea
+ */
+.ck-mermaid__wrapper .ck-mermaid__editing-view {
+ padding: 16px; /* just like pre element */
+ background-color: hsla(0, 0%, 78%, 0.3); /* just like pre element */
+ min-height: 200px;
+ border: 0;
+ resize: vertical;
+ font-size: 12px; /* just like pre element */
+ outline: 0; /* just like pre element */
+}
+
+/* Preview */
+.ck-mermaid__preview {
+ display: flex;
+ justify-content: center;
+}
+
+/* Preview error state */
+[id^="dmermaid-"] {
+ display: flex;
+ align-items: center;
+ height: 100%;
+ justify-content: center;
+}
+
+[id^="dmermaid-"] svg {
+ display: block;
+ width: 60%;
+ height: auto;
+}
+
+/* Source mode */
+.ck-mermaid__source-mode .ck-mermaid__editing-view {
+ width: 100%;
+}
+
+.ck-mermaid__source-mode .ck-mermaid__preview {
+ display: none;
+}
+
+/* Split mode */
+.ck-mermaid__split-mode .ck-mermaid__editing-view {
+ width: 50%;
+}
+
+.ck-mermaid__split-mode .ck-mermaid__preview {
+ width: 50%;
+}
+
+/* Preview mode */
+.ck-mermaid__preview-mode .ck-mermaid__editing-view {
+ display: none;
+}
\ No newline at end of file