mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 21:19:01 +01:00 
			
		
		
		
	Initial commit.
This commit is contained in:
		
						commit
						302a0dc2ef
					
				
							
								
								
									
										22
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					Software License Agreement
 | 
				
			||||||
 | 
					==========================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**CKEditor 5 mermaid feature** (https://ckeditor.com/ckeditor-5/)<br>
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 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.
 | 
				
			||||||
							
								
								
									
										56
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "@ckeditor/ckeditor5-mermaid",
 | 
				
			||||||
 | 
					  "version": "0.0.1",
 | 
				
			||||||
 | 
					  "description": "Mermaid widget for CKEditor 5.",
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "keywords": [
 | 
				
			||||||
 | 
					    "ckeditor",
 | 
				
			||||||
 | 
					    "ckeditor5",
 | 
				
			||||||
 | 
					    "ckeditor 5",
 | 
				
			||||||
 | 
					    "ckeditor5-feature",
 | 
				
			||||||
 | 
					    "ckeditor5-plugin",
 | 
				
			||||||
 | 
					    "ckeditor5-mermaid"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "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": ">=12.0.0",
 | 
				
			||||||
 | 
					    "npm": ">=5.7.1"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "files": [
 | 
				
			||||||
 | 
					    "lang",
 | 
				
			||||||
 | 
					    "src",
 | 
				
			||||||
 | 
					    "theme",
 | 
				
			||||||
 | 
					    "build",
 | 
				
			||||||
 | 
					    "ckeditor5-metadata.json"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "ckeditor5": "^32.0.0",
 | 
				
			||||||
 | 
					    "mermaid": "^8.14.0",
 | 
				
			||||||
 | 
					    "lodash-es": "^4.17.15"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-basic-styles": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-clipboard": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-code-block": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-editor-classic": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-engine": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-enter": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-essentials": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-heading": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-link": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-markdown-gfm": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-paragraph": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-typing": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-undo": "^32.0.0",
 | 
				
			||||||
 | 
					    "@ckeditor/ckeditor5-widget": "^32.0.0",
 | 
				
			||||||
 | 
					    "eslint": "^7.32.0",
 | 
				
			||||||
 | 
					    "eslint-config-ckeditor5": ">=3.1.1"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "dll:build": "webpack"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/commands/insertMermaidCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/commands/insertMermaidCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/insertmermaidcommand
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Command } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							model.change( writer => {
 | 
				
			||||||
 | 
								const item = writer.createElement( 'mermaid', {
 | 
				
			||||||
 | 
									displayMode: 'split',
 | 
				
			||||||
 | 
									source: MOCK_MERMAID_MARKUP
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								model.insertContent( item );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							editor.editing.view.focus();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/commands/mermaidPreviewCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/commands/mermaidPreviewCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/mermaidpreviewcommand
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Command } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { checkIsOn } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 );
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/commands/mermaidSourceViewCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/commands/mermaidSourceViewCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/mermaidsourceviewcommand
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Command } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { checkIsOn } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 );
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/commands/mermaidSplitViewCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/commands/mermaidSplitViewCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/mermaidsplitviewcommand
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Command } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { checkIsOn } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 );
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							@ -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/previewMode.svg';
 | 
				
			||||||
 | 
					import splitModeIcon from './../theme/icons/splitMode.svg';
 | 
				
			||||||
 | 
					import sourceModeIcon from './../theme/icons/sourceMode.svg';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { default as Mermaid } from './mermaid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const icons = {
 | 
				
			||||||
 | 
						infoIcon,
 | 
				
			||||||
 | 
						insertMermaidIcon,
 | 
				
			||||||
 | 
						previewModeIcon,
 | 
				
			||||||
 | 
						splitModeIcon,
 | 
				
			||||||
 | 
						sourceModeIcon
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/mermaid.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/mermaid.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/mermaid
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Plugin } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import MermaidEditing from './mermaidediting';
 | 
				
			||||||
 | 
					import MermaidToolbar from './mermaidtoolbar';
 | 
				
			||||||
 | 
					import MermaidUI from './mermaidui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import '../theme/mermaid.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class Mermaid extends Plugin {
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @inheritDoc
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						static get requires() {
 | 
				
			||||||
 | 
							return [ MermaidEditing, MermaidToolbar, MermaidUI ];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @inheritDoc
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						static get pluginName() {
 | 
				
			||||||
 | 
							return 'Mermaid';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										279
									
								
								src/mermaidediting.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								src/mermaidediting.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,279 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/mermaidediting
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Plugin } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					import { toWidget } from 'ckeditor5/src/widget';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import mermaid from 'mermaid/dist/mermaid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { debounce } from 'lodash-es';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import MermaidPreviewCommand from './commands/mermaidPreviewCommand';
 | 
				
			||||||
 | 
					import MermaidSourceViewCommand from './commands/mermaidSourceViewCommand';
 | 
				
			||||||
 | 
					import MermaidSplitViewCommand from './commands/mermaidSplitViewCommand';
 | 
				
			||||||
 | 
					import InsertMermaidCommand from './commands/insertMermaidCommand';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @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'
 | 
				
			||||||
 | 
							}, writer.createText( data.item.getAttribute( 'source' ) ) );
 | 
				
			||||||
 | 
							const pre = writer.createContainerElement( 'pre', {
 | 
				
			||||||
 | 
								spellcheck: 'false'
 | 
				
			||||||
 | 
							}, 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 );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								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 );
 | 
				
			||||||
 | 
										// console.log( child, domPreviewWrapper );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										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
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						_renderMermaid( domElement ) {
 | 
				
			||||||
 | 
							mermaid.init( undefined, domElement );
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/mermaidtoolbar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/mermaidtoolbar.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/mermaidtoolbar
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Plugin } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					import { WidgetToolbarRepository } from 'ckeditor5/src/widget';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										139
									
								
								src/mermaidui.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/mermaidui.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @module mermaid/mermaidui
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Plugin } from 'ckeditor5/src/core';
 | 
				
			||||||
 | 
					import { ButtonView } from 'ckeditor5/src/ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import insertMermaidIcon from '../theme/icons/insert.svg';
 | 
				
			||||||
 | 
					import previewModeIcon from '../theme/icons/previewMode.svg';
 | 
				
			||||||
 | 
					import splitModeIcon from '../theme/icons/splitMode.svg';
 | 
				
			||||||
 | 
					import sourceModeIcon from '../theme/icons/sourceMode.svg';
 | 
				
			||||||
 | 
					import infoIcon from '../theme/icons/info.svg';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* global window */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							editor.ui.componentFactory.add( 'Mermaid', locale => {
 | 
				
			||||||
 | 
								const buttonView = new ButtonView( locale );
 | 
				
			||||||
 | 
								const command = editor.commands.get( 'insertMermaidCommand' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								buttonView.set( {
 | 
				
			||||||
 | 
									label: t( 'Insert Mermaid' ),
 | 
				
			||||||
 | 
									icon: insertMermaidIcon,
 | 
				
			||||||
 | 
									tooltip: true
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Execute the command when the button is clicked.
 | 
				
			||||||
 | 
								command.listenTo( buttonView, 'execute', () => {
 | 
				
			||||||
 | 
									editor.execute( 'insertMermaidCommand' );
 | 
				
			||||||
 | 
									editor.editing.view.scrollToTheSelection();
 | 
				
			||||||
 | 
									editor.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://mermaid-js.github.io/mermaid/#/flowchart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								buttonView.set( {
 | 
				
			||||||
 | 
									label: t( 'Mermaid info' ),
 | 
				
			||||||
 | 
									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;
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/utils.js
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								tests/commands/insertMermaidCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								tests/commands/insertMermaidCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 | 
				
			||||||
 | 
					import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import InsertMermaidCommand from '../../src/commands/insertMermaidCommand';
 | 
				
			||||||
 | 
					import MermaidEditing from '../../src/mermaidediting';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C">[]</mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							describe( 'should be true', () => {
 | 
				
			||||||
 | 
								it( 'when text is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo]</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is part of the selection', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>' +
 | 
				
			||||||
 | 
										'<paragraph>b]az</paragraph>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( 'execute()', () => {
 | 
				
			||||||
 | 
							it( 'should add sample mermaid', () => {
 | 
				
			||||||
 | 
								setModelData( model,
 | 
				
			||||||
 | 
									'<paragraph>[foo]</paragraph>'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								command.execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
 | 
				
			||||||
 | 
									'<mermaid displayMode="split" source="flowchart TB\nA --> B\nB --> C"></mermaid>'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										110
									
								
								tests/commands/mermaidPreviewCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								tests/commands/mermaidPreviewCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 | 
				
			||||||
 | 
					import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import MermaidPreviewCommand from '../../src/commands/mermaidPreviewCommand';
 | 
				
			||||||
 | 
					import MermaidEditing from '../../src/mermaidediting';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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, '<mermaid displayMode="preview" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( true );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it( 'should be false when mermaid element has displayMode attribute equal to "split"', () => {
 | 
				
			||||||
 | 
								setModelData( model, '<mermaid displayMode="split" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( false );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it( 'should be false when mermaid element has displayMode attribute equal to "source"', () => {
 | 
				
			||||||
 | 
								setModelData( model, '<mermaid displayMode="source" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( false );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( '#isEnabled', () => {
 | 
				
			||||||
 | 
							describe( 'should be false', () => {
 | 
				
			||||||
 | 
								it( 'when text is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo]</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is part of the selection', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>' +
 | 
				
			||||||
 | 
										'<paragraph>b]az</paragraph>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							describe( 'should be true', () => {
 | 
				
			||||||
 | 
								it( 'when selection is inside mermaid', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C">[]</mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( 'execute()', () => {
 | 
				
			||||||
 | 
							it( 'should change displayMode to "preview" for mermaid', () => {
 | 
				
			||||||
 | 
								setModelData( model,
 | 
				
			||||||
 | 
									'[<mermaid displayMode="source" source="foo"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								command.execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( getModelData( model ) ).to.equal(
 | 
				
			||||||
 | 
									'[<mermaid displayMode="preview" source="foo"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										110
									
								
								tests/commands/mermaidSourceViewCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								tests/commands/mermaidSourceViewCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 | 
				
			||||||
 | 
					import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import MermaidSourceViewCommand from '../../src/commands/mermaidSourceViewCommand';
 | 
				
			||||||
 | 
					import MermaidEditing from '../../src/mermaidediting';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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, '<mermaid displayMode="preview" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( false );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it( 'should be false when mermaid element has displayMode attribute equal to "split"', () => {
 | 
				
			||||||
 | 
								setModelData( model, '<mermaid displayMode="split" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( false );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it( 'should be false when mermaid element has displayMode attribute equal to "source"', () => {
 | 
				
			||||||
 | 
								setModelData( model, '<mermaid displayMode="source" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( true );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( '#isEnabled', () => {
 | 
				
			||||||
 | 
							describe( 'should be false', () => {
 | 
				
			||||||
 | 
								it( 'when text is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo]</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is part of the selection', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>' +
 | 
				
			||||||
 | 
										'<paragraph>b]az</paragraph>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							describe( 'should be true', () => {
 | 
				
			||||||
 | 
								it( 'when selection is inside mermaid', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C">[]</mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( 'execute()', () => {
 | 
				
			||||||
 | 
							it( 'should add text', () => {
 | 
				
			||||||
 | 
								setModelData( model,
 | 
				
			||||||
 | 
									'[<mermaid displayMode="preview" source="foo"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								command.execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( getModelData( model ) ).to.equal(
 | 
				
			||||||
 | 
									'[<mermaid displayMode="source" source="foo"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										110
									
								
								tests/commands/mermaidSplitViewCommand.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								tests/commands/mermaidSplitViewCommand.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 | 
				
			||||||
 | 
					import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import MermaidSplitViewCommand from '../../src/commands/mermaidSplitViewCommand';
 | 
				
			||||||
 | 
					import MermaidEditing from '../../src/mermaidediting';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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, '<mermaid displayMode="preview" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( false );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it( 'should be false when mermaid element has displayMode attribute equal to "split"', () => {
 | 
				
			||||||
 | 
								setModelData( model, '<mermaid displayMode="split" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( true );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it( 'should be false when mermaid element has source attribute equal to "source"', () => {
 | 
				
			||||||
 | 
								setModelData( model, '<mermaid displayMode="source" source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( command.value ).to.equal( false );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( '#isEnabled', () => {
 | 
				
			||||||
 | 
							describe( 'should be false', () => {
 | 
				
			||||||
 | 
								it( 'when text is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo]</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is part of the selection', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>[foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>' +
 | 
				
			||||||
 | 
										'<paragraph>b]az</paragraph>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.false;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							describe( 'should be true', () => {
 | 
				
			||||||
 | 
								it( 'when selection is inside mermaid', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'<mermaid source="flowchart TB\nA --> B\nB --> C">[]</mermaid>'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								it( 'when mermaid is selected', () => {
 | 
				
			||||||
 | 
									setModelData( model,
 | 
				
			||||||
 | 
										'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
										'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expect( command.isEnabled ).to.be.true;
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( 'execute()', () => {
 | 
				
			||||||
 | 
							it( 'should change displayMode to "source" for mermaid', () => {
 | 
				
			||||||
 | 
								setModelData( model,
 | 
				
			||||||
 | 
									'[<mermaid displayMode="source" source="foo"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								command.execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( getModelData( model ) ).to.equal(
 | 
				
			||||||
 | 
									'[<mermaid displayMode="split" source="foo"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										32
									
								
								tests/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import { Mermaid as MermaidDll, icons } from '../src';
 | 
				
			||||||
 | 
					import Mermaid from '../src/mermaid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import infoIcon from './../theme/icons/info.svg';
 | 
				
			||||||
 | 
					import insertMermaidIcon from './../theme/icons/insert.svg';
 | 
				
			||||||
 | 
					import previewModeIcon from './../theme/icons/previewMode.svg';
 | 
				
			||||||
 | 
					import splitModeIcon from './../theme/icons/splitMode.svg';
 | 
				
			||||||
 | 
					import sourceModeIcon from './../theme/icons/sourceMode.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 );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										67
									
								
								tests/manual/markdown.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								tests/manual/markdown.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					<style type="text/css">
 | 
				
			||||||
 | 
						#editor, .ck-editor {
 | 
				
			||||||
 | 
							/* Adjust width to the typical width in GH. */
 | 
				
			||||||
 | 
							width: 820px !important;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pre.markdown-output {
 | 
				
			||||||
 | 
							background: hsl(70, 7%, 16%);
 | 
				
			||||||
 | 
							color: hsl(0, 0%, 100%);
 | 
				
			||||||
 | 
							display: block;
 | 
				
			||||||
 | 
							font-size: 1em;
 | 
				
			||||||
 | 
							font-family: Monaco, Menlo, Consolas, "Roboto Mono", "Courier New", "Ubuntu Mono", monospace;
 | 
				
			||||||
 | 
							padding: 1.333em;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.editor-container {
 | 
				
			||||||
 | 
							display:flex;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.output-container {
 | 
				
			||||||
 | 
							padding-left: 20px;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="editor-container">
 | 
				
			||||||
 | 
						<textarea id="editor">
 | 
				
			||||||
 | 
					Mermaid snippet:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					flowchart TB
 | 
				
			||||||
 | 
					A --> C
 | 
				
			||||||
 | 
					A --> D
 | 
				
			||||||
 | 
					B --> C
 | 
				
			||||||
 | 
					B --> D
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					More complex case:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					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 <br/>prevail!
 | 
				
			||||||
 | 
					John-->>Alice: Great!
 | 
				
			||||||
 | 
					John->>Bob: How about you?
 | 
				
			||||||
 | 
					Bob-->>John: Jolly good!
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Javascript snippet:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```Javascript
 | 
				
			||||||
 | 
					var foo = 'bar';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					alert( foo );
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
							</textarea>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<div class="output-container">
 | 
				
			||||||
 | 
								<p>Output:</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								<pre class="markdown-output"><code id="markdown-output"></code></pre>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										68
									
								
								tests/manual/markdown.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								tests/manual/markdown.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @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/src/classiceditor';
 | 
				
			||||||
 | 
					import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 | 
				
			||||||
 | 
					import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 | 
				
			||||||
 | 
					import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 | 
				
			||||||
 | 
					import Enter from '@ckeditor/ckeditor5-enter/src/enter';
 | 
				
			||||||
 | 
					import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
 | 
				
			||||||
 | 
					import Link from '@ckeditor/ckeditor5-link/src/link';
 | 
				
			||||||
 | 
					import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 | 
				
			||||||
 | 
					import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 | 
				
			||||||
 | 
					import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock';
 | 
				
			||||||
 | 
					import Mermaid from '../../src/mermaid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								tests/manual/markdown.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/manual/markdown.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					## Mermaid widget
 | 
				
			||||||
							
								
								
									
										38
									
								
								tests/manual/mermaid.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								tests/manual/mermaid.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					<style type="text/css">
 | 
				
			||||||
 | 
						#editor, .ck-editor {
 | 
				
			||||||
 | 
							/* Adjust width to the typical width in GH. */
 | 
				
			||||||
 | 
							width: 820px !important;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div id="editor">
 | 
				
			||||||
 | 
						<p>Mermaid snippet:</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<pre>
 | 
				
			||||||
 | 
							<code class="mermaid language-mermaid">
 | 
				
			||||||
 | 
					flowchart TB
 | 
				
			||||||
 | 
					A --> C
 | 
				
			||||||
 | 
					A --> D
 | 
				
			||||||
 | 
					B --> C
 | 
				
			||||||
 | 
					B --> D
 | 
				
			||||||
 | 
							</code>
 | 
				
			||||||
 | 
						</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<p>More complex case:</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<pre>
 | 
				
			||||||
 | 
							<code class="mermaid language-mermaid">
 | 
				
			||||||
 | 
					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 <br/>prevail!
 | 
				
			||||||
 | 
					John-->>Alice: Great!
 | 
				
			||||||
 | 
					John->>Bob: How about you?
 | 
				
			||||||
 | 
					Bob-->>John: Jolly good!
 | 
				
			||||||
 | 
							</code>
 | 
				
			||||||
 | 
						</pre>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										51
									
								
								tests/manual/mermaid.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								tests/manual/mermaid.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @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/src/classiceditor';
 | 
				
			||||||
 | 
					import Typing from '@ckeditor/ckeditor5-typing/src/typing';
 | 
				
			||||||
 | 
					import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 | 
				
			||||||
 | 
					import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 | 
				
			||||||
 | 
					import Enter from '@ckeditor/ckeditor5-enter/src/enter';
 | 
				
			||||||
 | 
					import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
 | 
				
			||||||
 | 
					import Link from '@ckeditor/ckeditor5-link/src/link';
 | 
				
			||||||
 | 
					import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 | 
				
			||||||
 | 
					import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock';
 | 
				
			||||||
 | 
					import Mermaid from '../../src/mermaid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
							
								
								
									
										1
									
								
								tests/manual/mermaid.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/manual/mermaid.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					## Mermaid widget
 | 
				
			||||||
							
								
								
									
										47
									
								
								tests/mermaid.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								tests/mermaid.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
 | 
				
			||||||
 | 
					import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 | 
				
			||||||
 | 
					import Heading from '@ckeditor/ckeditor5-heading/src/heading';
 | 
				
			||||||
 | 
					import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					import Mermaid from '../src/mermaid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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, '<paragraph>[]</paragraph>' );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							afterEach( () => {
 | 
				
			||||||
 | 
								domElement.remove();
 | 
				
			||||||
 | 
								return editor.destroy();
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it( 'should add an icon to the toolbar', () => {
 | 
				
			||||||
 | 
								expect( editor.ui.componentFactory.has( 'Mermaid' ) ).to.equal( true );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										293
									
								
								tests/mermaidediting.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								tests/mermaidediting.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,293 @@
 | 
				
			|||||||
 | 
					import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
 | 
				
			||||||
 | 
					import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 | 
				
			||||||
 | 
					import Heading from '@ckeditor/ckeditor5-heading/src/heading';
 | 
				
			||||||
 | 
					import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 | 
				
			||||||
 | 
					import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 | 
				
			||||||
 | 
					import CodeBlockEditing from '@ckeditor/ckeditor5-code-block/src/codeblockediting';
 | 
				
			||||||
 | 
					import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					import MermaidEditing from '../src/mermaidediting';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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(
 | 
				
			||||||
 | 
											'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
				
			||||||
 | 
											'</pre>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
 | 
				
			||||||
 | 
											'<mermaid displayMode="split" source="flowchart TB\nA --> B\nB --> C">' +
 | 
				
			||||||
 | 
											'</mermaid>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									it( 'works correctly when empty', () => {
 | 
				
			||||||
 | 
										editor.setData(
 | 
				
			||||||
 | 
											'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid"></code>' +
 | 
				
			||||||
 | 
											'</pre>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
 | 
				
			||||||
 | 
											'<mermaid displayMode="split" source=""></mermaid>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								describe( 'data downcast', () => {
 | 
				
			||||||
 | 
									it( 'works correctly', () => {
 | 
				
			||||||
 | 
										// Using editor.setData() instead of setModelData helper because of #11365.
 | 
				
			||||||
 | 
										editor.setData(
 | 
				
			||||||
 | 
											'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
				
			||||||
 | 
											'</pre>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expect( editor.getData() ).to.equal(
 | 
				
			||||||
 | 
											'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
				
			||||||
 | 
											'</pre>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									it( 'works correctly when empty ', () => {
 | 
				
			||||||
 | 
										// Using editor.setData() instead of setModelData helper because of #11365.
 | 
				
			||||||
 | 
										editor.setData(
 | 
				
			||||||
 | 
											'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid"></code>' +
 | 
				
			||||||
 | 
											'</pre>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expect( editor.getData() ).to.equal(
 | 
				
			||||||
 | 
											'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid"></code>' +
 | 
				
			||||||
 | 
											'</pre>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								describe( 'editing downcast', () => {
 | 
				
			||||||
 | 
									it( 'works correctly without displayMode attribute', () => {
 | 
				
			||||||
 | 
										// Using editor.setData() instead of setModelData helper because of #11365.
 | 
				
			||||||
 | 
										editor.setData(
 | 
				
			||||||
 | 
											'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
				
			||||||
 | 
											'</pre>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
 | 
				
			||||||
 | 
											'<div class="ck-mermaid__split-mode ck-mermaid__wrapper ck-widget ck-widget_selected' +
 | 
				
			||||||
 | 
												' ck-widget_with-selection-handle" contenteditable="false">' +
 | 
				
			||||||
 | 
												'<div class="ck ck-widget__selection-handle"></div>' +
 | 
				
			||||||
 | 
												// New lines replaced with space, same issue in getViewData as in #11365.
 | 
				
			||||||
 | 
												'<textarea class="ck-mermaid__editing-view" data-cke-ignore-events="true"' +
 | 
				
			||||||
 | 
													' placeholder="Insert mermaid source code"></textarea>' +
 | 
				
			||||||
 | 
												'<div class="ck-mermaid__preview"></div>' +
 | 
				
			||||||
 | 
												'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
 | 
				
			||||||
 | 
											'</div>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									it( 'works correctly with displayMode attribute', () => {
 | 
				
			||||||
 | 
										setModelData( editor.model,
 | 
				
			||||||
 | 
											'<mermaid source="foo" displayMode="preview"></mermaid>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
 | 
				
			||||||
 | 
											'<div class="ck-mermaid__preview-mode ck-mermaid__wrapper ck-widget ck-widget_selected ' +
 | 
				
			||||||
 | 
												'ck-widget_with-selection-handle" contenteditable="false">' +
 | 
				
			||||||
 | 
												'<div class="ck ck-widget__selection-handle"></div>' +
 | 
				
			||||||
 | 
												'<textarea class="ck-mermaid__editing-view" data-cke-ignore-events="true"' +
 | 
				
			||||||
 | 
													' placeholder="Insert mermaid source code"></textarea>' +
 | 
				
			||||||
 | 
												'<div class="ck-mermaid__preview"></div>' +
 | 
				
			||||||
 | 
												'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
 | 
				
			||||||
 | 
											'</div>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									it( 'works correctly with empty source', () => {
 | 
				
			||||||
 | 
										setModelData( editor.model,
 | 
				
			||||||
 | 
											'<mermaid source="" displayMode="preview"></mermaid>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
 | 
				
			||||||
 | 
											'<div class="ck-mermaid__preview-mode ck-mermaid__wrapper ck-widget ck-widget_selected ' +
 | 
				
			||||||
 | 
												'ck-widget_with-selection-handle" contenteditable="false">' +
 | 
				
			||||||
 | 
												'<div class="ck ck-widget__selection-handle"></div>' +
 | 
				
			||||||
 | 
												'<textarea class="ck-mermaid__editing-view" data-cke-ignore-events="true"' +
 | 
				
			||||||
 | 
													' placeholder="Insert mermaid source code"></textarea>' +
 | 
				
			||||||
 | 
												'<div class="ck-mermaid__preview"></div>' +
 | 
				
			||||||
 | 
												'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
 | 
				
			||||||
 | 
											'</div>'
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									describe( 'textarea value', () => {
 | 
				
			||||||
 | 
										let domTextarea = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										beforeEach( () => {
 | 
				
			||||||
 | 
											// Using editor.setData() instead of setModelData helper because of #11365.
 | 
				
			||||||
 | 
											editor.setData(
 | 
				
			||||||
 | 
												'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
				
			||||||
 | 
												'</pre>'
 | 
				
			||||||
 | 
											);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											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(
 | 
				
			||||||
 | 
												'<pre spellcheck="false">' +
 | 
				
			||||||
 | 
												'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
				
			||||||
 | 
												'</pre>'
 | 
				
			||||||
 | 
											);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											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, '<mermaid source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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, '<codeBlock language="javascript">foo</codeBlock>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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, '<mermaid source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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, '<mermaid source="foo"></mermaid>' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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 );
 | 
				
			||||||
 | 
								} );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										152
									
								
								tests/mermaidtoolbar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								tests/mermaidtoolbar.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,152 @@
 | 
				
			|||||||
 | 
					import ClassicTestEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						setData
 | 
				
			||||||
 | 
					} from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 | 
				
			||||||
 | 
					import WidgetToolbarRepository from '@ckeditor/ckeditor5-widget/src/widgettoolbarrepository';
 | 
				
			||||||
 | 
					import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
 | 
				
			||||||
 | 
					import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Mermaid from '../src/mermaid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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', () => {
 | 
				
			||||||
 | 
								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( 'Mermaid info' );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe( 'integration with the editor focus', () => {
 | 
				
			||||||
 | 
							it( 'should show the toolbar when the editor gains focus and the mermaid widget is selected', () => {
 | 
				
			||||||
 | 
								setData( model,
 | 
				
			||||||
 | 
									'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								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,
 | 
				
			||||||
 | 
									'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								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,
 | 
				
			||||||
 | 
									'<paragraph>[foo]</paragraph>' +
 | 
				
			||||||
 | 
									'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( balloon.visibleView ).to.be.null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								editor.ui.fire( 'update' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( balloon.visibleView ).to.be.null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								model.change( writer => {
 | 
				
			||||||
 | 
									// Set selection to the [<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]
 | 
				
			||||||
 | 
									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,
 | 
				
			||||||
 | 
									'<paragraph>foo</paragraph>' +
 | 
				
			||||||
 | 
									'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								expect( balloon.visibleView ).to.equal( toolbar );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								model.change( writer => {
 | 
				
			||||||
 | 
									// Select the  <paragraph>[foo]</paragraph>
 | 
				
			||||||
 | 
									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,
 | 
				
			||||||
 | 
									'[<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>]' +
 | 
				
			||||||
 | 
									'<mermaid source="flowchart TB\nA --> B\nB --> C"></mermaid>'
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expect( balloon.visibleView ).to.equal( toolbar );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								model.change( writer => {
 | 
				
			||||||
 | 
									// Set selection to the second <mermaid></mermaid>
 | 
				
			||||||
 | 
									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 );
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
							
								
								
									
										66
									
								
								tests/mermaidui.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								tests/mermaidui.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Mermaid from '../src/mermaid';
 | 
				
			||||||
 | 
					import MermaidUI from '../src/mermaidui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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' );
 | 
				
			||||||
 | 
								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 );
 | 
				
			||||||
 | 
									} );
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} );
 | 
				
			||||||
 | 
						} );
 | 
				
			||||||
 | 
					} );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								theme/icons/info.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								theme/icons/info.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1.219C4.254 1.219 1.219 4.28 1.219 8A6.78 6.78 0 0 0 8 14.781c3.719 0 6.781-3.035 6.781-6.781 0-3.719-3.062-6.781-6.781-6.781Zm0 12.25A5.45 5.45 0 0 1 2.531 8 5.467 5.467 0 0 1 8 2.531c3.008 0 5.469 2.461 5.469 5.469A5.467 5.467 0 0 1 8 13.469Zm0-9.242c-.656 0-1.148.52-1.148 1.148 0 .656.492 1.148 1.148 1.148.629 0 1.148-.492 1.148-1.148 0-.629-.52-1.148-1.148-1.148Zm1.531 6.945v-.656a.353.353 0 0 0-.328-.329h-.328V7.454a.353.353 0 0 0-.328-.328h-1.75a.332.332 0 0 0-.328.328v.656c0 .192.136.329.328.329h.328v1.75h-.328a.333.333 0 0 0-.328.328v.656c0 .191.136.328.328.328h2.406a.332.332 0 0 0 .328-.328Z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 712 B  | 
							
								
								
									
										1
									
								
								theme/icons/insert.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								theme/icons/insert.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.156 11.51a.549.549 0 0 1-.013.232c-.006.332.002.6.009.828l.005.186c.176-.06.36-.093.553-.093.635 0 1.179.354 1.635.86.364.406.64.508.827.508.186 0 .462-.102.827-.507.456-.507 1-.86 1.634-.86.635 0 1.179.353 1.635.86.19.21.162.526-.061.705a.552.552 0 0 1-.747-.058c-.364-.405-.64-.508-.827-.508-.186 0-.462.103-.827.508-.455.507-1 .86-1.634.86-.635 0-1.179-.353-1.635-.86-.364-.405-.64-.508-.827-.508-.186 0-.462.103-.827.508-.456.507-1 .86-1.634.86-.635 0-1.179-.353-1.635-.86-.364-.405-.64-.508-.827-.508-.186 0-.462.103-.827.508a.552.552 0 0 1-.746.058.48.48 0 0 1-.061-.705c.456-.507 1-.86 1.634-.86.315 0 .607.086.877.236.056-.392.227-.78.492-1.099a.917.917 0 0 1 .111-.169c.11-.203.23-.398.376-.568.416-.486.86-.947 1.305-1.407.4-.414.8-.828 1.178-1.26.12-.252.052-.46-.13-.63-.074-.07-.152-.136-.23-.201-.096-.082-.194-.165-.284-.255C5.43 6.258 5.074 4.94 5.242 3.443a5.939 5.939 0 0 1 .743-2.311.38.38 0 0 1 .04-.096L6.1.973a.109.109 0 0 1 .033-.004c.068 0 .077.056.086.11.004.023.007.046.015.065a.219.219 0 0 1 .004.066l.03.096c.021.075.043.15.07.222.092.268.238.512.43.716.053.055.138.142.205.03.118-.197.212-.117.307-.036l.031.027c.266.215.416.512.56.815a.94.94 0 0 1 .032.077c.03.081.062.165.151.207.027-.028.044-.066.06-.102.028-.064.054-.123.136-.12.11.004.146.094.18.177a2.284 2.284 0 0 1 .215.795.397.397 0 0 1 .029.235c.03.032.036.084.042.136.012.097.023.195.196.162.14-.166.218-.105.246.064.05.27.052.548.006.823.009.163-.027.321-.063.48-.015.067-.03.134-.042.202l-.004.022c-.01.054-.019.103.077.071a.5.5 0 0 1 .183-.147c.214-.198.465-.353.736-.455 0-.003.002-.006.004-.009a.022.022 0 0 1 .017-.009.02.02 0 0 1 .01.002.283.283 0 0 1 .201-.068c.166-.052.284-.061.248.179-.029.193.05.243.245.173.123-.044.246-.093.368-.142.129-.052.257-.103.386-.148.271-.095.548-.133.824-.068.192.045.217.114.068.244-.157.138-.096.196.054.244.126.039.257.04.388.041.08.001.16.002.24.011l.05-.002a.418.418 0 0 1 .236.001.34.34 0 0 1 .222.037c.092.006.183.033.192.132.006.066-.049.09-.103.115-.018.008-.036.016-.051.025a.752.752 0 0 0-.101.089c.12.139.283.147.447.155l.103.007c.111 0 .216-.034.321-.068.073-.023.146-.046.22-.058a.443.443 0 0 0 .122-.05c.08-.04.156-.08.206-.01.066.094-.003.191-.07.285a.964.964 0 0 0-.062.094.069.069 0 0 1-.017.015.023.023 0 0 1-.005.018.024.024 0 0 1-.018.01c-.23.348-.565.57-.919.772l-.007.016h-.018c-.258.199-.54.36-.84.482-.402.235-.834.382-1.276.501a.408.408 0 0 1-.206.042c-.575.154-1.153.175-1.733.15a5.84 5.84 0 0 1-.357-.028c-.344-.034-.688-.068-1.036.073l-.039.026a3.518 3.518 0 0 0-.477.454l-.137.197a.104.104 0 0 1-.055.088c-.189.364-.34.744-.449 1.135l-.091.643v.006Zm7.234-4.592.007.013-.002.002c-.009-.006-.012-.009-.005-.015ZM9.008 5.125l.003.012-.004.001-.002-.002.004.001v-.012Z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 2.8 KiB  | 
							
								
								
									
										1
									
								
								theme/icons/previewMode.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								theme/icons/previewMode.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m8 11.5 3.004-3.004 3.003 3.004-3.003 3.004L8 11.5Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M9.504 2.863v3h3v-3h-3Zm-1 4h5v-5h-5v5Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M11.004 14.504 8 11.5l2.504-2.504V6.07h1v2.926l2.503 2.504-3.003 3.004ZM9.414 11.5l1.59-1.59 1.59 1.59-1.59 1.59-1.59-1.59ZM6.837 4.999h2.625v-1h-2.57a2.5 2.5 0 1 0-2.974 2.814V9h-2v5h5V9h-2V6.813c.934-.19 1.68-.9 1.919-1.814Zm-3.919-.636a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm0 5.637v3h3v-3h-3Z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 590 B  | 
							
								
								
									
										1
									
								
								theme/icons/sourceMode.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								theme/icons/sourceMode.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M11.609 2.2H13.4c.22 0 .4.18.4.4v10.8a.4.4 0 0 1-.4.4h-1.791V15H13.4a1.6 1.6 0 0 0 1.6-1.6V2.6A1.6 1.6 0 0 0 13.4 1h-1.791v1.2ZM4.997 2.2V1H2.6A1.6 1.6 0 0 0 1 2.6v10.8A1.6 1.6 0 0 0 2.6 15h2.397v-1.2H2.6a.4.4 0 0 1-.4-.4V2.6c0-.22.18-.4.4-.4h2.397Z"/><path d="M3.511 4.778a.75.75 0 0 1 .75-.75h3.697a.75.75 0 1 1 0 1.5H4.26a.75.75 0 0 1-.75-.75ZM6.595 7.629a.75.75 0 0 1 .75-.75h3.588a.75.75 0 0 1 0 1.5H7.345a.75.75 0 0 1-.75-.75ZM6.595 10.48a.75.75 0 0 1 .75-.75h1.143a.75.75 0 1 1 0 1.5H7.345a.75.75 0 0 1-.75-.75ZM3.511 7.629a.75.75 0 0 1 .75-.75h.983a.75.75 0 1 1 0 1.5h-.983a.75.75 0 0 1-.75-.75ZM3.511 10.48a.75.75 0 0 1 .75-.75h.983a.75.75 0 1 1 0 1.5h-.983a.75.75 0 0 1-.75-.75Z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 790 B  | 
							
								
								
									
										1
									
								
								theme/icons/splitMode.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								theme/icons/splitMode.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M13.8 3.076H8.588V13.8H13.4a.4.4 0 0 0 .4-.4V3.076Zm-6.412 0H2.2V13.4c0 .22.18.4.4.4h4.788V3.076ZM2.6 1A1.6 1.6 0 0 0 1 2.6v10.8A1.6 1.6 0 0 0 2.6 15h10.8a1.6 1.6 0 0 0 1.6-1.6V2.6A1.6 1.6 0 0 0 13.4 1H2.6Z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 348 B  | 
							
								
								
									
										61
									
								
								theme/mermaid.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								theme/mermaid.css
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user