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 @@ + + +
+ + +
+

Output:

+ +
+
+
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( + '
' + + '
' + + // New lines replaced with space, same issue in getViewData as in #11365. + '' + + '
' + + '
' + + '
' + ); + } ); + + 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