new ckeditor

New ckeditor
This commit is contained in:
Francesco Malagrino
2017-03-26 21:10:58 +02:00
parent 87a8e6920e
commit f9caa9759b
4418 changed files with 209667 additions and 2801 deletions

View File

@@ -0,0 +1,52 @@
/* jshint browser: false, node: true */
'use strict';
var config = {
applications: {
ckeditor: {
path: '../../',
files: [
'ckeditor.js'
]
},
codemirror: {
path: '.',
files: [
'js/lib/codemirror/codemirror.js'
]
},
toolbartool: {
path: '.',
files: [
'js/fulltoolbareditor.js',
'js/abstracttoolbarmodifier.js',
'js/toolbarmodifier.js',
'js/toolbartextmodifier.js'
]
}
},
plugins: [
'node_modules/benderjs-mocha',
'node_modules/benderjs-chai'
],
framework: 'mocha',
tests: {
'main': {
applications: [ 'ckeditor', 'codemirror', 'toolbartool' ],
basePath: 'tests/',
paths: [
'**',
'!**/_*/**'
]
}
}
};
module.exports = config;

View File

@@ -0,0 +1,55 @@
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?89024372');
src: url('../font/fontello.eot?89024372#iefix') format('embedded-opentype'),
url('../font/fontello.woff?89024372') format('woff'),
url('../font/fontello.ttf?89024372') format('truetype'),
url('../font/fontello.svg?89024372#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?89024372#fontello') format('svg');
}
}
*/
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-trash:before { content: '\e802'; } /* '' */
.icon-down-big:before { content: '\e800'; } /* '' */
.icon-up-big:before { content: '\e801'; } /* '' */

View File

@@ -0,0 +1,12 @@
Font license info
## Font Awesome
Copyright (C) 2012 by Dave Gandy
Author: Dave Gandy
License: SIL ()
Homepage: http://fortawesome.github.com/Font-Awesome/

View File

@@ -0,0 +1,28 @@
{
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
"css": "trash-empty",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "1c4068ed75209e21af36017df8871802",
"css": "down-big",
"code": 59393,
"src": "fontawesome"
},
{
"uid": "95376bf082bfec6ce06ea1cda7bd7ead",
"css": "up-big",
"code": 59394,
"src": "fontawesome"
}
]
}

Binary file not shown.

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2014 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="trash" unicode="&#xe802;" d="m286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15t6-5h464q2 0 6 5t8 15t4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q22 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
<glyph glyph-name="down-big" unicode="&#xe800;" d="m899 386q0-30-21-50l-363-364q-22-21-51-21q-29 0-50 21l-363 364q-21 20-21 50q0 29 21 51l41 41q22 21 51 21q29 0 50-21l164-164v393q0 29 21 50t51 22h71q29 0 50-22t21-50v-393l164 164q21 21 51 21q29 0 50-21l42-42q21-21 21-50z" horiz-adv-x="928.6" />
<glyph glyph-name="up-big" unicode="&#xe801;" d="m899 308q0-28-21-50l-42-42q-21-21-50-21q-30 0-51 21l-164 164v-393q0-29-20-47t-51-19h-71q-30 0-51 19t-21 47v393l-164-164q-20-21-50-21t-50 21l-42 42q-21 21-21 50q0 30 21 51l363 363q20 21 50 21q30 0 51-21l363-363q21-22 21-51z" horiz-adv-x="928.6" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

View File

@@ -0,0 +1,446 @@
<!DOCTYPE html>
<!--
Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.md or http://ckeditor.com/license
-->
<!--[if IE 8]><html class="ie8"><![endif]-->
<!--[if gt IE 8]><html><![endif]-->
<!--[if !IE]><!--><html><!--<![endif]-->
<head>
<meta charset="utf-8">
<title>Toolbar Configurator</title>
<script src="../../ckeditor.js"></script>
<script>
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
CKEDITOR.tools.enableHtml5Elements( document );
</script>
<link rel="stylesheet" href="lib/codemirror/codemirror.css">
<link rel="stylesheet" href="lib/codemirror/show-hint.css">
<link rel="stylesheet" href="lib/codemirror/neo.css">
<link rel="stylesheet" href="css/fontello.css">
<link rel="stylesheet" href="../css/samples.css">
</head>
<body id="toolbar">
<nav class="navigation-a">
<div class="grid-container">
<ul class="navigation-a-left grid-width-70">
<li><a href="http://ckeditor.com">Project Homepage</a></li>
<li><a href="http://dev.ckeditor.com/">I found a bug</a></li>
<li><a href="http://github.com/ckeditor/ckeditor-dev" class="icon-pos-right icon-navigation-a-github">Fork CKEditor on GitHub</a></li>
</ul>
<ul class="navigation-a-right grid-width-30">
<li><a href="http://ckeditor.com/blog-list">CKEditor Blog</a></li>
</ul>
</div>
</nav>
<header class="header-a">
<div class="grid-container">
<h1 class="header-a-logo grid-width-30">
<a href="../index.html"><img src="../img/logo.png" alt="CKEditor Logo"></a>
</h1>
<nav class="navigation-b grid-width-70">
<ul>
<li><a href="../index.html" class="button-a">Start</a></li>
<li><a href="index.html" class="button-a button-a-background">Toolbar configurator</a></li>
</ul>
</nav>
</div>
</header>
<main>
<div class="adjoined-top">
<div class="grid-container">
<div class="content grid-width-100">
<div class="grid-container-nested">
<h1 class="grid-width-60">
Toolbar Configurator
<a href="#help-content" type="button" title="Configurator help" id="help" class="button-a button-a-background button-a-no-text icon-pos-left icon-question-mark">Help</a>
</h1>
<div class="grid-width-40 grid-switch-magic">
<div class="switch">
<span class="balloon-a balloon-a-se">Select configurator type</span>
<input type="radio" name="radio" data-num="1" id="radio-basic" />
<input type="radio" name="radio" data-num="2" id="radio-advanced" />
<label data-for="1" for="radio-basic">Basic</label>
<span class="switch-inner">
<span class="handler"></span>
</span>
<label data-for="2" for="radio-advanced">Advanced</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="adjoined-bottom">
<div class="grid-container">
<div class="grid-width-100">
<div class="editors-container">
<div id="editor-basic"></div>
<div id="editor-advanced"></div>
</div>
</div>
</div>
</div>
<div class="grid-container configurator">
<div class="content grid-width-100">
<div class="configurator">
<div>
<div id="toolbarModifierWrapper"></div>
</div>
</div>
</div>
</div>
<div id="help-content">
<div class="grid-container">
<div class="grid-width-100">
<h2>What Am I Doing Here?</h2>
<div class="grid-container grid-container-nested">
<div class="basic">
<div class="grid-width-50">
<p>Arrange <a href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-toolbarGroups">toolbar groups</a>, toggle <a href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-removeButtons">button visibility</a> according to your needs and get your toolbar configuration.</p>
<p>You can replace the content of the <a href="../../config.js"><code>config.js</code></a> file with the generated configuration. If you already set some configuration options you will need to merge both configurations.</p>
</div>
<div class="grid-width-50">
<p>Read more about different ways of <a href="http://docs.ckeditor.com/#!/guide/dev_configuration">setting configuration</a> and do not forget about <strong>clearing browser cache</strong>.</p>
<p>Arranging toolbar groups is the recommended way of configuring the toolbar, but if you need more freedom you can use the <a href="#advanced">advanced configurator</a>.</p>
</div>
</div>
<div class="advanced" style="display: none;">
<div class="grid-width-50">
<p>With this code editor you can edit your <a href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-toolbar">toolbar configuration</a> live.</p>
<p>You can replace the content of the <a href="../../config.js"><code>config.js</code></a> file with the generated configuration. If you already set some configuration options you will need to merge both configurations.</p>
</div>
<div class="grid-width-50">
<p>Read more about different ways of <a href="http://docs.ckeditor.com/#!/guide/dev_configuration">setting configuration</a> and do not forget about <strong>clearing browser cache</strong>.</p>
</div>
</div>
</div>
<p class="grid-container grid-container-nested">
<button type="button" class="help-content-close grid-width-100 button-a button-a-background">Got it. Let's play!</button>
</p>
</div>
</div>
</div>
</main>
<footer class="footer-a grid-container">
<p class="grid-width-100">
CKEditor &ndash; The text editor for the Internet &ndash; <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
</p>
<p class="grid-width-100" id="copy">
Copyright &copy; 2003-2017, <a class="samples" href="http://cksource.com/">CKSource</a> &ndash; Frederico Knabben. All rights reserved.
</p>
</footer>
<script src="lib/codemirror/codemirror.js"></script>
<script src="lib/codemirror/javascript.js"></script>
<script src="lib/codemirror/show-hint.js"></script>
<script src="js/fulltoolbareditor.js"></script>
<script src="js/abstracttoolbarmodifier.js"></script>
<script src="js/toolbarmodifier.js"></script>
<script src="js/toolbartextmodifier.js"></script>
<script src="../js/sf.js"></script>
<script>
( function() {
'use strict';
var mode = ( window.location.hash.substr( 1 ) === 'advanced' ) ? 'advanced' : 'basic',
configuratorSection = CKEDITOR.document.findOne( 'main > .grid-container.configurator' ),
basicInstruction = CKEDITOR.document.findOne( '#help-content .basic' ),
advancedInstruction = CKEDITOR.document.findOne( '#help-content .advanced' ),
// Configurator mode switcher.
modeSwitchBasic = CKEDITOR.document.getById( 'radio-basic' ),
modeSwitchAdvanced = CKEDITOR.document.getById( 'radio-advanced' );
// Initial setup
function updateSwitcher() {
if ( mode === 'advanced' ) {
modeSwitchAdvanced.$.checked = true;
} else {
modeSwitchBasic.$.checked = true;
}
}
updateSwitcher();
CKEDITOR.document.getWindow().on( 'hashchange', function( e ) {
var hash = window.location.hash.substr( 1 );
if ( !( hash === 'advanced' || hash === 'basic' ) ) {
return;
}
mode = hash;
onToolbarsDone( mode );
} );
CKEDITOR.document.getWindow().on( 'resize', function() {
updateToolbar( ( mode === 'basic' ? toolbarModifier : toolbarTextModifier )[ 'editorInstance' ] );
} );
function onRefresh( modifier ) {
modifier = modifier || this;
if ( mode === 'basic' && modifier instanceof ToolbarConfigurator.ToolbarTextModifier ) {
return;
}
// CodeMirror container becomes visible, so we need to refresh and to avoid rendering problems.
if ( mode === 'advanced' && modifier instanceof ToolbarConfigurator.ToolbarTextModifier ) {
modifier.codeContainer.refresh();
}
updateToolbar( modifier.editorInstance );
}
function updateToolbar( editor ) {
var editorContainer = editor.container;
// Not always editor is loaded.
if ( !editorContainer ) {
return;
}
var displayStyle = editorContainer.getStyle( 'display' );
editorContainer.setStyle( 'display', 'block' );
var newHeight = editorContainer.getSize( 'height' );
var newMarginTop = parseInt( editorContainer.getComputedStyle( 'margin-top' ), 10 );
newMarginTop = ( isNaN( newMarginTop ) ? 0 : Number( newMarginTop ) );
var newMarginBottom = parseInt( editorContainer.getComputedStyle( 'margin-bottom' ), 10 );
newMarginBottom = ( isNaN( newMarginBottom ) ? 0 : Number( newMarginBottom ) );
var result = newHeight + newMarginTop + newMarginBottom;
editorContainer.setStyle( 'display', displayStyle );
editor.container.getAscendant( 'div' ).setStyle( 'height', result + 'px' );
}
var toolbarModifier = new ToolbarConfigurator.ToolbarModifier( 'editor-basic' );
var done = 0;
toolbarModifier.init( onToolbarInit );
toolbarModifier.onRefresh = onRefresh;
CKEDITOR.document.getById( 'toolbarModifierWrapper' ).append( toolbarModifier.mainContainer );
var toolbarTextModifier = new ToolbarConfigurator.ToolbarTextModifier( 'editor-advanced' );
toolbarTextModifier.init( onToolbarInit );
toolbarTextModifier.onRefresh = onRefresh;
function onToolbarInit() {
if ( ++done === 2 ) {
onToolbarsDone();
positionSticky.watch( CKEDITOR.document.findOne( '.toolbar' ), function() {
return mode === 'advanced';
} );
}
}
function onToolbarsDone() {
if ( mode === 'basic' ) {
toggleModeBasic( false );
} else {
toggleModeAdvanced( false );
}
updateSwitcher();
setTimeout( function() {
CKEDITOR.document.findOne( '.editors-container' ).addClass( 'active' );
CKEDITOR.document.findOne( '#toolbarModifierWrapper' ).addClass( 'active' );
}, 200 );
}
CKEDITOR.document.getById( 'toolbarModifierWrapper' ).append( toolbarTextModifier.mainContainer );
function toogleModeSwitch( onElement, offElement, onModifier, offModifier ) {
onElement.addClass( 'fancy-button-active' );
offElement.removeClass( 'fancy-button-active' );
onModifier.showUI();
offModifier.hideUI();
}
function toggleModeBasic( callOnRefresh ) {
callOnRefresh = ( callOnRefresh !== false );
mode = 'basic';
window.location.hash = '#basic';
toogleModeSwitch( modeSwitchBasic, modeSwitchAdvanced, toolbarModifier, toolbarTextModifier );
configuratorSection.removeClass( 'freed-width' );
basicInstruction.show();
advancedInstruction.hide();
callOnRefresh && onRefresh( toolbarModifier );
}
function toggleModeAdvanced( callOnRefresh ) {
callOnRefresh = ( callOnRefresh !== false );
mode = 'advanced';
window.location.hash = '#advanced';
toogleModeSwitch( modeSwitchAdvanced, modeSwitchBasic, toolbarTextModifier, toolbarModifier );
configuratorSection.addClass( 'freed-width' );
advancedInstruction.show();
basicInstruction.hide();
callOnRefresh && onRefresh( toolbarTextModifier );
}
modeSwitchBasic.on( 'click', toggleModeBasic );
modeSwitchAdvanced.on( 'click', toggleModeAdvanced );
//
// Position:sticky for the toolbar.
//
// Will make elements behave like they were styled with position:sticky.
var positionSticky = {
// Store object: {
// element: CKEDITOR.dom.element, // Element which will float.
// placeholder: CKEDITOR.dom.element, // Placeholder which is place to prevent page bounce.
// isFixed: boolean // Whether element float now.
// }
watched: [],
active: [],
staticContainer: null,
init: function() {
var element = CKEDITOR.dom.element.createFromHtml(
'<div class="staticContainer">' +
'<div class="grid-container" >' +
'<div class="grid-width-100">' +
'<div class="inner"></div>' +
'</div>' +
'</div>' +
'</div>' );
this.staticContainer = element.findOne( '.inner' );
CKEDITOR.document.getBody().append( element );
},
watch: function( element, preventFunc ) {
this.watched.push( {
element: element,
placeholder: new CKEDITOR.dom.element( 'div' ),
isFixed: false,
preventFunc: preventFunc
} );
},
checkAll: function() {
for ( var i = 0; i < this.watched.length; i++ ) {
this.check( this.watched[ i ] );
}
},
check: function( element ) {
var isFixed = element.isFixed;
var shouldBeFixed = this.shouldBeFixed( element );
// Nothing to be done.
if ( isFixed === shouldBeFixed ) {
return;
}
var placeholder = element.placeholder;
if ( isFixed ) {
// Unfixing.
element.element.insertBefore( placeholder );
placeholder.remove();
element.element.removeStyle( 'margin' );
this.active.splice( CKEDITOR.tools.indexOf( this.active, element ), 1 );
} else {
// Fixing.
placeholder.setStyle( 'width', element.element.getSize( 'width' ) + 'px' );
placeholder.setStyle( 'height', element.element.getSize( 'height' ) + 'px' );
placeholder.setStyle( 'margin-bottom', element.element.getComputedStyle( 'margin-bottom' ) );
placeholder.setStyle( 'display', element.element.getComputedStyle( 'display' ) );
placeholder.insertAfter( element.element );
this.staticContainer.append( element.element );
this.active.push( element );
}
element.isFixed = !element.isFixed;
},
shouldBeFixed: function( element ) {
if ( element.preventFunc && element.preventFunc() ) {
return false;
}
// If element is already fixed we are checking it's placeholder.
var related = ( element.isFixed ? element.placeholder : element.element ),
clientRect = related.$.getBoundingClientRect(),
staticHeight = this.staticContainer.getSize('height' ),
elemHeight = element.element.getSize( 'height' );
if ( element.isFixed ) {
return ( clientRect.top + elemHeight < staticHeight );
} else {
return ( clientRect.top < staticHeight );
}
}
};
positionSticky.init();
CKEDITOR.document.getWindow().on( 'scroll',
new CKEDITOR.tools.eventsBuffer( 100, positionSticky.checkAll, positionSticky ).input
);
// Make the toolbar sticky.
positionSticky.watch( CKEDITOR.document.findOne( '.editors-container' ) );
// Help button and help-content.
( function() {
var helpButton = CKEDITOR.document.getById( 'help' ),
helpContent = CKEDITOR.document.getById( 'help-content' );
// Don't show help button on IE8 because it's unsupported by Pico Modal.
if ( CKEDITOR.env.ie && CKEDITOR.env.version == 8 ) {
helpButton.hide();
} else {
// Display help modal when the button is clicked.
helpButton.on( 'click', function( evt ) {
SF.modal( {
// Clone modal content from DOM.
content: helpContent.getHtml(),
afterCreate: function( modal ) {
// Enable modal content button to close the modal.
new CKEDITOR.dom.element( modal.modalElem() ).findOne( '.help-content-close' ).once( 'click', modal.close );
}
} ).show();
} );
}
} )();
} )();
</script>
</body>
</html>

View File

@@ -0,0 +1,566 @@
/* global ToolbarConfigurator */
'use strict';
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
if ( typeof Object.create != 'function' ) {
( function() {
var F = function() {};
Object.create = function( o ) {
if ( arguments.length > 1 ) {
throw Error( 'Second argument not supported' );
}
if ( o === null ) {
throw Error( 'Cannot set a null [[Prototype]]' );
}
if ( typeof o != 'object' ) {
throw TypeError( 'Argument must be an object' );
}
F.prototype = o;
return new F();
};
} )();
}
// Copy of the divarea plugin (with some enhancements), so we always have some editable mode, regardless of the build's config.
CKEDITOR.plugins.add( 'toolbarconfiguratorarea', {
// Use afterInit to override wysiwygarea's mode. May still fail to override divarea, but divarea is nice.
afterInit: function( editor ) {
editor.addMode( 'wysiwyg', function( callback ) {
var editingBlock = CKEDITOR.dom.element.createFromHtml( '<div class="cke_wysiwyg_div cke_reset" hidefocus="true"></div>' );
var contentSpace = editor.ui.space( 'contents' );
contentSpace.append( editingBlock );
editingBlock = editor.editable( editingBlock );
editingBlock.detach = CKEDITOR.tools.override( editingBlock.detach,
function( org ) {
return function() {
org.apply( this, arguments );
this.remove();
};
} );
editor.setData( editor.getData( 1 ), callback );
editor.fire( 'contentDom' );
} );
// Additions to the divarea.
// Speed up data processing.
editor.dataProcessor.toHtml = function( html ) {
return html;
};
editor.dataProcessor.toDataFormat = function( html ) {
return html;
};
// End of the additions.
}
} );
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
if ( !Object.keys ) {
Object.keys = ( function() {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !( { toString: null } ).propertyIsEnumerable( 'toString' ),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function( obj ) {
if ( typeof obj !== 'object' && ( typeof obj !== 'function' || obj === null ) )
throw new TypeError( 'Object.keys called on non-object' );
var result = [], prop, i;
for ( prop in obj ) {
if ( hasOwnProperty.call( obj, prop ) )
result.push( prop );
}
if ( hasDontEnumBug ) {
for ( i = 0; i < dontEnumsLength; i++ ) {
if ( hasOwnProperty.call( obj, dontEnums[ i ] ) )
result.push( dontEnums[ i ] );
}
}
return result;
};
}() );
}
( function() {
/**
* @class ToolbarConfigurator.AbstractToolbarModifier
* @param {String} editorId An id of modified editor
* @constructor
*/
function AbstractToolbarModifier( editorId, cfg ) {
this.cfg = cfg || {};
this.hidden = false;
this.editorId = editorId;
this.fullToolbarEditor = new ToolbarConfigurator.FullToolbarEditor();
this.mainContainer = null;
this.originalConfig = null;
this.actualConfig = null;
this.waitForReady = false;
this.isEditableVisible = false;
this.toolbarContainer = null;
this.toolbarButtons = [];
}
// Expose the class.
ToolbarConfigurator.AbstractToolbarModifier = AbstractToolbarModifier;
/**
* @param {String} config
*/
AbstractToolbarModifier.prototype.setConfig = function( config ) {
this._onInit( undefined, config, true );
};
/**
* @param {Function} [callback]
*/
AbstractToolbarModifier.prototype.init = function( callback ) {
var that = this;
this.mainContainer = new CKEDITOR.dom.element( 'div' );
if ( this.fullToolbarEditor.editorInstance !== null ) {
throw 'Only one instance of ToolbarModifier is allowed';
}
if ( !this.editorInstance ) {
// Do not refresh yet, let's wait for the full toolbar editor (see below).
this._createEditor( false );
}
this.editorInstance.once( 'loaded', function() {
that.fullToolbarEditor.init( function() {
that._onInit( callback );
if ( typeof that.onRefresh == 'function' ) {
that.onRefresh();
}
}, that.editorInstance.config );
} );
return this.mainContainer;
};
/**
* Called editor initialization finished.
*
* @param {Function} callback
* @param {String} [actualConfig]
* @private
*/
AbstractToolbarModifier.prototype._onInit = function( callback, actualConfig ) {
this.originalConfig = this.editorInstance.config;
if ( !actualConfig ) {
this.actualConfig = JSON.parse( JSON.stringify( this.originalConfig ) );
} else {
this.actualConfig = JSON.parse( actualConfig );
}
if ( !this.actualConfig.toolbarGroups && !this.actualConfig.toolbar ) {
this.actualConfig.toolbarGroups = getDefaultToolbarGroups( this.editorInstance );
}
if ( typeof callback === 'function' )
callback( this.mainContainer );
// Here we are going to keep only `name` and `groups` data from editor `toolbar` property.
function getDefaultToolbarGroups( editor ) {
var toolbarGroups = editor.toolbar,
copy = [];
var max = toolbarGroups.length;
for ( var i = 0; i < max; i++ ) {
var group = toolbarGroups[ i ];
if ( typeof group == 'string' ) {
copy.push( group ); // separator
} else {
copy.push( {
name: group.name,
groups: group.groups ? group.groups.slice() : []
} );
}
}
return copy;
}
};
/**
* Creates DOM structure of tool.
*
* @returns {CKEDITOR.dom.element}
* @private
*/
AbstractToolbarModifier.prototype._createModifier = function() {
this.mainContainer.addClass( 'unselectable' );
if ( this.modifyContainer ) {
this.modifyContainer.remove();
}
this.modifyContainer = new CKEDITOR.dom.element( 'div' );
this.modifyContainer.addClass( 'toolbarModifier' );
this.mainContainer.append( this.modifyContainer );
return this.mainContainer;
};
/**
* Find editable area in CKEditor instance DOM container
*
* @returns {CKEDITOR.dom.element}
*/
AbstractToolbarModifier.prototype.getEditableArea = function() {
var selector = ( '#' + this.editorInstance.id + '_contents' );
return this.editorInstance.container.findOne( selector );
};
/**
* Hide editable area in modified editor by sets its height to 0.
*
* @private
*/
AbstractToolbarModifier.prototype._hideEditable = function() {
var area = this.getEditableArea();
this.isEditableVisible = false;
this.lastEditableAreaHeight = area.getStyle( 'height' );
area.setStyle( 'height', '0' );
};
/**
* Show editable area in modified editor.
*
* @private
*/
AbstractToolbarModifier.prototype._showEditable = function() {
this.isEditableVisible = true;
this.getEditableArea().setStyle( 'height', this.lastEditableAreaHeight || 'auto' );
};
/**
* Toggle editable area visibility.
*
* @private
*/
AbstractToolbarModifier.prototype._toggleEditable = function() {
if ( this.isEditableVisible )
this._hideEditable();
else
this._showEditable();
};
/**
* Usually called when configuration changes.
*
* @private
*/
AbstractToolbarModifier.prototype._refreshEditor = function() {
var that = this,
status = this.editorInstance.status;
// Wait for ready only once.
if ( this.waitForReady )
return;
// Not ready.
if ( status == 'unloaded' || status == 'loaded' ) {
this.waitForReady = true;
this.editorInstance.once( 'instanceReady', function() {
refresh();
}, this );
// Ready or destroyed.
} else {
refresh();
}
function refresh() {
that.editorInstance.destroy();
that._createEditor( true, that.getActualConfig() );
that.waitForReady = false;
}
};
/**
* Creates editor that can be used to present the toolbar configuration.
*
* @private
*/
AbstractToolbarModifier.prototype._createEditor = function( doRefresh, configOverrides ) {
var that = this;
this.editorInstance = CKEDITOR.replace( this.editorId );
this.editorInstance.on( 'configLoaded', function() {
var config = that.editorInstance.config;
if ( configOverrides ) {
CKEDITOR.tools.extend( config, configOverrides, true );
}
AbstractToolbarModifier.extendPluginsConfig( config );
} );
// Prevent creating any other space than the top one.
this.editorInstance.on( 'uiSpace', function( evt ) {
if ( evt.data.space != 'top' ) {
evt.stop();
}
}, null, null, -999 );
this.editorInstance.once( 'loaded', function() {
var btns = that.editorInstance.ui.instances;
for ( var i in btns ) {
if ( btns[ i ] ) {
btns[ i ].click = empty;
btns[ i ].onClick = empty;
}
}
if ( !that.isEditableVisible ) {
that._hideEditable();
}
if ( that.currentActive && that.currentActive.name ) {
that._highlightGroup( that.currentActive.name );
}
if ( that.hidden ) {
that.hideUI();
} else {
that.showUI();
}
if ( doRefresh && ( typeof that.onRefresh === 'function' ) ) {
that.onRefresh();
}
} );
function empty() {}
};
/**
* Always returns copy of config.
*
* @returns {Object}
*/
AbstractToolbarModifier.prototype.getActualConfig = function() {
return JSON.parse( JSON.stringify( this.actualConfig ) );
};
/**
* Creates toolbar in tool.
*
* @private
*/
AbstractToolbarModifier.prototype._createToolbar = function() {
if ( !this.toolbarButtons.length ) {
return;
}
this.toolbarContainer = new CKEDITOR.dom.element( 'div' );
this.toolbarContainer.addClass( 'toolbar' );
var max = this.toolbarButtons.length;
for ( var i = 0; i < max; i += 1 ) {
this._createToolbarBtn( this.toolbarButtons[ i ] );
}
};
/**
* Create toolbar button and add it to toolbar container
*
* @param {Object} cfg
* @returns {CKEDITOR.dom.element}
* @private
*/
AbstractToolbarModifier.prototype._createToolbarBtn = function( cfg ) {
var btnText = ( typeof cfg.text === 'string' ? cfg.text : cfg.text.inactive ),
btn = ToolbarConfigurator.FullToolbarEditor.createButton( btnText, cfg.cssClass );
this.toolbarContainer.append( btn );
btn.data( 'group', cfg.group );
btn.addClass( cfg.position );
btn.on( 'click', function() {
cfg.clickCallback.call( this, btn, cfg );
}, this );
return btn;
};
/**
* @private
* @param {Object} config
*/
AbstractToolbarModifier.prototype._fixGroups = function( config ) {
var groups = config.toolbarGroups || [];
var max = groups.length;
for ( var i = 0; i < max; i += 1 ) {
var currentGroup = groups[ i ];
// separator, in config, is in raw format
// need to make it more sophisticated to keep unique id
// for each one
if ( currentGroup == '/' ) {
currentGroup = groups[ i ] = {};
currentGroup.type = 'separator';
currentGroup.name = ( 'separator' + CKEDITOR.tools.getNextNumber() );
continue;
}
// sometimes subgroups are not set (basic package), so need to
// create them artifically
currentGroup.groups = currentGroup.groups || [];
// when there is no subgroup with same name like its parent name
// then it have to be added artificially
// in order to maintain consistency between user interface and config
if ( CKEDITOR.tools.indexOf( currentGroup.groups, currentGroup.name ) == -1 ) {
this.editorInstance.ui.addToolbarGroup( currentGroup.name, currentGroup.groups[ currentGroup.groups.length - 1 ], currentGroup.name );
currentGroup.groups.push( currentGroup.name );
}
this._fixSubgroups( currentGroup );
}
};
/**
* Transform subgroup string to object literal
* with keys: {String} name and {Number} totalBtns
* Please note: this method modify Object provided in first argument
*
* input:
* [
* { groups: [ 'nameOne', 'nameTwo' ] }
* ]
*
* output:
* [
* { groups: [ { name: 'nameOne', totalBtns: 3 }, { name: 'nameTwo', totalBtns: 5 } ] }
* ]
*
* @param {Object} group
* @private
*/
AbstractToolbarModifier.prototype._fixSubgroups = function( group ) {
var subGroups = group.groups;
var max = subGroups.length;
for ( var i = 0; i < max; i += 1 ) {
var subgroupName = subGroups[ i ];
subGroups[ i ] = {
name: subgroupName,
totalBtns: ToolbarConfigurator.ToolbarModifier.getTotalSubGroupButtonsNumber( subgroupName, this.fullToolbarEditor )
};
}
};
/**
* Same as JSON.stringify method but returned string is in one line
*
* @param {Object} json
* @param {Object} opts
* @param {Boolean} opts.addSpaces
* @param {Boolean} opts.noQuotesOnKey
* @param {Boolean} opts.singleQuotes
* @returns {Object}
*/
AbstractToolbarModifier.stringifyJSONintoOneLine = function( json, opts ) {
opts = opts || {};
var stringJSON = JSON.stringify( json, null, '' );
// IE8 make new line characters
stringJSON = stringJSON.replace( /\n/g, '' );
if ( opts.addSpaces ) {
stringJSON = stringJSON.replace( /(\{|:|,|\[|\])/g, function( sentence ) {
return sentence + ' ';
} );
stringJSON = stringJSON.replace( /(\])/g, function( sentence ) {
return ' ' + sentence;
} );
}
if ( opts.noQuotesOnKey ) {
stringJSON = stringJSON.replace( /"(\w*)":/g, function( sentence, word ) {
return word + ':';
} );
}
if ( opts.singleQuotes ) {
stringJSON = stringJSON.replace( /\"/g, '\'' );
}
return stringJSON;
};
/**
* Hide toolbar configurator
*/
AbstractToolbarModifier.prototype.hideUI = function() {
this.hidden = true;
this.mainContainer.hide();
if ( this.editorInstance.container ) {
this.editorInstance.container.hide();
}
};
/**
* Show toolbar configurator
*/
AbstractToolbarModifier.prototype.showUI = function() {
this.hidden = false;
this.mainContainer.show();
if ( this.editorInstance.container ) {
this.editorInstance.container.show();
}
};
/**
* Extends plugins setttings in the specified config with settings useful for
* the toolbar configurator.
*
* @static
*/
AbstractToolbarModifier.extendPluginsConfig = function( config ) {
var extraPlugins = config.extraPlugins;
// Enable the special, lightweight area to replace wysiwygarea.
config.extraPlugins = ( extraPlugins ? extraPlugins + ',' : '' ) + 'toolbarconfiguratorarea';
};
} )();

View File

@@ -0,0 +1,365 @@
/* exported ToolbarConfigurator */
/* global ToolbarConfigurator */
'use strict';
window.ToolbarConfigurator = {};
( function() {
/**
* @class ToolbarConfigurator.FullToolbarEditor
* @constructor
*/
function FullToolbarEditor() {
this.instanceid = 'fte' + CKEDITOR.tools.getNextId();
this.textarea = new CKEDITOR.dom.element( 'textarea' );
this.textarea.setAttributes( {
id: this.instanceid,
name: this.instanceid,
contentEditable: true
} );
this.buttons = null;
this.editorInstance = null;
}
// Expose the class.
ToolbarConfigurator.FullToolbarEditor = FullToolbarEditor;
/**
* @param {Function} callback
* @param {Object} cfg
*/
FullToolbarEditor.prototype.init = function( callback ) {
var that = this;
document.body.appendChild( this.textarea.$ );
CKEDITOR.replace( this.instanceid );
this.editorInstance = CKEDITOR.instances[ this.instanceid ];
this.editorInstance.once( 'configLoaded', function( e ) {
var cfg = e.editor.config;
// We want all the buttons.
delete cfg.removeButtons;
delete cfg.toolbarGroups;
delete cfg.toolbar;
ToolbarConfigurator.AbstractToolbarModifier.extendPluginsConfig( cfg );
e.editor.once( 'loaded', function() {
that.buttons = FullToolbarEditor.toolbarToButtons( that.editorInstance.toolbar );
that.buttonsByGroup = FullToolbarEditor.groupButtons( that.buttons );
that.buttonNamesByGroup = that.groupButtonNamesByGroup( that.buttons );
e.editor.container.hide();
if ( typeof callback === 'function' )
callback( that.buttons );
} );
} );
};
/**
* Group array of button names by their group parents.
*
* @param {Array} buttons
* @returns {Object}
*/
FullToolbarEditor.prototype.groupButtonNamesByGroup = function( buttons ) {
var that = this,
groups = FullToolbarEditor.groupButtons( buttons );
for ( var groupName in groups ) {
var currGroup = groups[ groupName ];
groups[ groupName ] = FullToolbarEditor.map( currGroup, function( button ) {
return that.getCamelCasedButtonName( button.name );
} );
}
return groups;
};
/**
* Returns group literal.
*
* @param {String} name
* @returns {Object}
*/
FullToolbarEditor.prototype.getGroupByName = function( name ) {
var groups = this.editorInstance.config.toolbarGroups || this.getFullToolbarGroupsConfig();
var max = groups.length;
for ( var i = 0; i < max; i += 1 ) {
if ( groups[ i ].name === name )
return groups[ i ];
}
return null;
};
/**
* @param {String} name
* @returns {String | null}
*/
FullToolbarEditor.prototype.getCamelCasedButtonName = function( name ) {
var items = this.editorInstance.ui.items;
for ( var key in items ) {
if ( items[ key ].name == name )
return key;
}
return null;
};
/**
* Returns full toolbarGroups config value which is used when
* there is no toolbarGroups field in config.
*
* @param {Boolean} [pickSeparators=false]
* @returns {Array}
*/
FullToolbarEditor.prototype.getFullToolbarGroupsConfig = function( pickSeparators ) {
pickSeparators = ( pickSeparators === true ? true : false );
var result = [],
toolbarGroups = this.editorInstance.toolbar;
var max = toolbarGroups.length;
for ( var i = 0; i < max; i += 1 ) {
var currentGroup = toolbarGroups[ i ],
copiedGroup = {};
if ( typeof currentGroup.name != 'string' ) {
// this is not a group
if ( pickSeparators ) {
result.push( '/' );
}
continue;
}
copiedGroup.name = currentGroup.name;
if ( currentGroup.groups )
copiedGroup.groups = Array.prototype.slice.call( currentGroup.groups );
result.push( copiedGroup );
}
return result;
};
/**
* Filters array items based on checker provided in second argument.
* Returns new array.
*
* @param {Array} arr
* @param {Function} checker
* @returns {Array}
*/
FullToolbarEditor.filter = function( arr, checker ) {
var max = ( arr && arr.length ? arr.length : 0 ),
result = [];
for ( var i = 0; i < max; i += 1 ) {
if ( checker( arr[ i ] ) )
result.push( arr[ i ] );
}
return result;
};
/**
* Simplified http://underscorejs.org/#map functionality
*
* @param {Array | Object} enumerable
* @param {Function} modifier
* @returns {Array | Object}
*/
FullToolbarEditor.map = function( enumerable, modifier ) {
var result;
if ( CKEDITOR.tools.isArray( enumerable ) ) {
result = [];
var max = enumerable.length;
for ( var i = 0; i < max; i += 1 )
result.push( modifier( enumerable[ i ] ) );
} else {
result = {};
for ( var key in enumerable )
result[ key ] = modifier( enumerable[ key ] );
}
return result;
};
/**
* Group buttons by their parent names.
*
* @static
* @param {Array} buttons
* @returns {Object} The object (`name => group`) representing CKEDITOR.ui.button or CKEDITOR.ui.richCombo
*/
FullToolbarEditor.groupButtons = function( buttons ) {
var groups = {};
var max = buttons.length;
for ( var i = 0; i < max; i += 1 ) {
var currBtn = buttons[ i ],
currBtnGroupName = currBtn.toolbar.split( ',' )[ 0 ];
groups[ currBtnGroupName ] = groups[ currBtnGroupName ] || [];
groups[ currBtnGroupName ].push( currBtn );
}
return groups;
};
/**
* Pick all buttons from toolbar.
*
* @static
* @param {Array} groups
* @returns {Array}
*/
FullToolbarEditor.toolbarToButtons = function( groups ) {
var buttons = [];
var max = groups.length;
for ( var i = 0; i < max; i += 1 ) {
var currentGroup = groups[ i ];
if ( typeof currentGroup == 'object' )
buttons = buttons.concat( FullToolbarEditor.groupToButtons( groups[ i ] ) );
}
return buttons;
};
/**
* Creates HTML button representation for view.
*
* @static
* @param {CKEDITOR.ui.button | CKEDITOR.ui.richCombo} button
* @returns {CKEDITOR.dom.element}
*/
FullToolbarEditor.createToolbarButton = function( button ) {
var $button = new CKEDITOR.dom.element( 'a' ),
icon = FullToolbarEditor.createIcon( button.name, button.icon, button.command );
$button.setStyle( 'float', 'none' );
$button.addClass( 'cke_' + ( CKEDITOR.lang.dir == 'rtl' ? 'rtl' : 'ltr' ) );
if ( button instanceof CKEDITOR.ui.button ) {
$button.addClass( 'cke_button' );
$button.addClass( 'cke_toolgroup' );
$button.append( icon );
} else if ( CKEDITOR.ui.richCombo && button instanceof CKEDITOR.ui.richCombo ) {
var comboLabel = new CKEDITOR.dom.element( 'span' ),
comboOpen = new CKEDITOR.dom.element( 'span' ),
comboArrow = new CKEDITOR.dom.element( 'span' );
$button.addClass( 'cke_combo_button' );
comboLabel.addClass( 'cke_combo_text' );
comboLabel.addClass( 'cke_combo_inlinelabel' );
comboLabel.setText( button.label );
comboOpen.addClass( 'cke_combo_open' );
comboArrow.addClass( 'cke_combo_arrow' );
comboOpen.append( comboArrow );
$button.append( comboLabel );
$button.append( comboOpen );
}
return $button;
};
/**
* Create and return icon element.
*
* @param {String} name
* @param {String} icon
* @param {String} command
* @static
* @returns {CKEDITOR.dom.element}
*/
FullToolbarEditor.createIcon = function( name, icon, command ) {
var iconStyle = CKEDITOR.skin.getIconStyle( name, ( CKEDITOR.lang.dir == 'rtl' ) );
// We don't know exactly how to get icon style. Especially for extra plugins,
// Which definition may vary.
iconStyle = iconStyle || CKEDITOR.skin.getIconStyle( icon, ( CKEDITOR.lang.dir == 'rtl' ) );
iconStyle = iconStyle || CKEDITOR.skin.getIconStyle( command, ( CKEDITOR.lang.dir == 'rtl' ) );
var iconElement = new CKEDITOR.dom.element( 'span' );
iconElement.addClass( 'cke_button_icon' );
iconElement.addClass( 'cke_button__' + name + '_icon' );
iconElement.setAttribute( 'style', iconStyle );
iconElement.setStyle( 'float', 'none' );
return iconElement;
};
/**
* Create and return button element
*
* @param {String} text
* @param {String} cssClasses
* @returns {CKEDITOR.dom.element}
*/
FullToolbarEditor.createButton = function( text, cssClasses ) {
var $button = new CKEDITOR.dom.element( 'button' );
$button.addClass( 'button-a' );
$button.setAttribute( 'type', 'button' );
if ( typeof cssClasses == 'string' ) {
cssClasses = cssClasses.split( ' ' );
var i = cssClasses.length;
while ( i-- ) {
$button.addClass( cssClasses[ i ] );
}
}
$button.setHtml( text );
return $button;
};
/**
* @static
* @param {Object} group
* @returns {Array} representing HTML buttons for view
*/
FullToolbarEditor.groupToButtons = function( group ) {
var buttons = [],
items = group.items;
var max = items ? items.length : 0;
for ( var i = 0; i < max; i += 1 ) {
var item = items[ i ];
if ( item instanceof CKEDITOR.ui.button || CKEDITOR.ui.richCombo && item instanceof CKEDITOR.ui.richCombo ) {
item.$ = FullToolbarEditor.createToolbarButton( item );
buttons.push( item );
}
}
return buttons;
};
} )();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,623 @@
/* global CodeMirror, ToolbarConfigurator */
'use strict';
( function() {
var AbstractToolbarModifier = ToolbarConfigurator.AbstractToolbarModifier,
FullToolbarEditor = ToolbarConfigurator.FullToolbarEditor;
/**
* @class ToolbarConfigurator.ToolbarTextModifier
* @param {String} editorId An id of modified editor
* @extends AbstractToolbarModifier
* @constructor
*/
function ToolbarTextModifier( editorId ) {
AbstractToolbarModifier.call( this, editorId );
this.codeContainer = null;
this.hintContainer = null;
}
// Expose the class.
ToolbarConfigurator.ToolbarTextModifier = ToolbarTextModifier;
ToolbarTextModifier.prototype = Object.create( AbstractToolbarModifier.prototype );
/**
* @param {Function} callback
* @param {String} [config]
* @private
*/
ToolbarTextModifier.prototype._onInit = function( callback, config ) {
AbstractToolbarModifier.prototype._onInit.call( this, undefined, config );
this._createModifier( config ? this.actualConfig : undefined );
if ( typeof callback === 'function' )
callback( this.mainContainer );
};
/**
* Creates HTML main container of modifier.
*
* @param {String} cfg
* @returns {CKEDITOR.dom.element}
* @private
*/
ToolbarTextModifier.prototype._createModifier = function( cfg ) {
var that = this;
this._createToolbar();
if ( this.toolbarContainer ) {
this.mainContainer.append( this.toolbarContainer );
}
AbstractToolbarModifier.prototype._createModifier.call( this );
this._setupActualConfig( cfg );
var toolbarCfg = this.actualConfig.toolbar,
cfgValue;
if ( CKEDITOR.tools.isArray( toolbarCfg ) ) {
var stringifiedToolbar = '[\n\t\t' + FullToolbarEditor.map( toolbarCfg, function( json ) {
return AbstractToolbarModifier.stringifyJSONintoOneLine( json, {
addSpaces: true,
noQuotesOnKey: true,
singleQuotes: true
} );
} ).join( ',\n\t\t' ) + '\n\t]';
cfgValue = '\tconfig.toolbar = ' + stringifiedToolbar + ';';
} else {
cfgValue = 'config.toolbar = [];';
}
cfgValue = [
'CKEDITOR.editorConfig = function( config ) {\n',
cfgValue,
'\n};'
].join( '' );
function hint( cm ) {
var data = setupData( cm );
if ( data.charsBetween === null ) {
return;
}
var unused = that.getUnusedButtonsArray( that.actualConfig.toolbar, true, data.charsBetween ),
to = cm.getCursor(),
from = CodeMirror.Pos( to.line, ( to.ch - ( data.charsBetween.length ) ) ),
token = cm.getTokenAt( to ),
prevToken = cm.getTokenAt( { line: to.line, ch: token.start } );
// determine that we are at beginning of group,
// so first key is "name"
if ( prevToken.string === '{' )
unused = [ 'name' ];
// preventing close with special character and move cursor forward
// when no autocomplete
if ( unused.length === 0 )
return;
return new HintData( from, to, unused );
}
function HintData( from, to, list ) {
this.from = from;
this.to = to;
this.list = list;
this._handlers = [];
}
function setupData( cm, character ) {
var result = {};
result.cur = cm.getCursor();
result.tok = cm.getTokenAt( result.cur );
result[ 'char' ] = character || result.tok.string.charAt( result.tok.string.length - 1 );
// Getting string between begin of line and cursor.
var curLineTillCur = cm.getRange( CodeMirror.Pos( result.cur.line, 0 ), result.cur );
// Reverse string.
var currLineTillCurReversed = curLineTillCur.split( '' ).reverse().join( '' );
// Removing proper string definitions :
// FROM:
// R' ,'odeR' ,'odnU' [ :smeti{
// ^^^^^^ ^^^^^^
// TO:
// R' , [ :smeti{
currLineTillCurReversed = currLineTillCurReversed.replace( /(['|"]\w*['|"])/g, '' );
// Matching letters till ' or " character and end string char.
// R' , [ :smeti{
// ^
result.charsBetween = currLineTillCurReversed.match( /(^\w*)(['|"])/ );
if ( result.charsBetween ) {
result.endChar = result.charsBetween[ 2 ];
// And reverse string (bring to original state).
result.charsBetween = result.charsBetween[ 1 ].split( '' ).reverse().join( '' );
}
return result;
}
function complete( cm ) {
setTimeout( function() {
if ( !cm.state.completionActive ) {
CodeMirror.showHint( cm, hint, {
hintsClass: 'toolbar-modifier',
completeSingle: false
} );
}
}, 100 );
return CodeMirror.Pass;
}
var codeMirrorWrapper = new CKEDITOR.dom.element( 'div' );
codeMirrorWrapper.addClass( 'codemirror-wrapper' );
this.modifyContainer.append( codeMirrorWrapper );
this.codeContainer = CodeMirror( codeMirrorWrapper.$, {
mode: { name: 'javascript', json: true },
// For some reason (most likely CM's bug) gutter breaks CM's height.
// Refreshing CM does not help.
lineNumbers: false,
lineWrapping: true,
// Trick to make CM autogrow. http://codemirror.net/demo/resize.html
viewportMargin: Infinity,
value: cfgValue,
smartIndent: false,
indentWithTabs: true,
indentUnit: 4,
tabSize: 4,
theme: 'neo',
extraKeys: {
'Left': complete,
'Right': complete,
"'''": complete,
"'\"'": complete,
Backspace: complete,
Delete: complete,
'Shift-Tab': 'indentLess'
}
} );
this.codeContainer.on( 'endCompletion', function( cm, completionData ) {
var data = setupData( cm );
// preventing close with special character and move cursor forward
// when no autocomplete
if ( completionData === undefined )
return;
cm.replaceSelection( data.endChar );
} );
this.codeContainer.on( 'change', function() {
var value = that.codeContainer.getValue();
value = that._evaluateValue( value );
if ( value !== null ) {
that.actualConfig.toolbar = ( value.toolbar ? value.toolbar : that.actualConfig.toolbar );
that._fillHintByUnusedElements();
that._refreshEditor();
that.mainContainer.removeClass( 'invalid' );
} else {
that.mainContainer.addClass( 'invalid' );
}
} );
this.hintContainer = new CKEDITOR.dom.element( 'div' );
this.hintContainer.addClass( 'toolbarModifier-hints' );
this._fillHintByUnusedElements();
this.hintContainer.insertBefore( codeMirrorWrapper );
};
/**
* Create DOM string and set to hint container,
* show proper information when no unused element left.
*
* @private
*/
ToolbarTextModifier.prototype._fillHintByUnusedElements = function() {
var unused = this.getUnusedButtonsArray( this.actualConfig.toolbar, true );
unused = this.groupButtonNamesByGroup( unused );
var unusedElements = FullToolbarEditor.map( unused, function( elem ) {
var buttonsList = FullToolbarEditor.map( elem.buttons, function( buttonName ) {
return '<code>' + buttonName + '</code> ';
} ).join( '' );
return [
'<dt>',
'<code>', elem.name, '</code>',
'</dt>',
'<dd>',
buttonsList,
'</dd>'
].join( '' );
} ).join( ' ' );
var listHeader = [
'<dt class="list-header">Toolbar group</dt>',
'<dd class="list-header">Unused items</dd>'
].join( '' );
var header = '<h3>Unused toolbar items</h3>';
if ( !unused.length ) {
listHeader = '<p>All items are in use.</p>';
}
this.codeContainer.refresh();
this.hintContainer.setHtml( header + '<dl>' + listHeader + unusedElements + '</dl>' );
};
/**
* @param {String} buttonName
* @returns {String}
*/
ToolbarTextModifier.prototype.getToolbarGroupByButtonName = function( buttonName ) {
var buttonNames = this.fullToolbarEditor.buttonNamesByGroup;
for ( var groupName in buttonNames ) {
var buttons = buttonNames[ groupName ];
var i = buttons.length;
while ( i-- ) {
if ( buttonName === buttons[ i ] ) {
return groupName;
}
}
}
return null;
};
/**
* Filter all available toolbar elements by array of elements provided in first argument.
* Returns elements which are not used.
*
* @param {Object} toolbar
* @param {Boolean} [sorted=false]
* @param {String} prefix
* @returns {Array}
*/
ToolbarTextModifier.prototype.getUnusedButtonsArray = function( toolbar, sorted, prefix ) {
sorted = ( sorted === true ? true : false );
var providedElements = ToolbarTextModifier.mapToolbarCfgToElementsList( toolbar ),
allElements = Object.keys( this.fullToolbarEditor.editorInstance.ui.items );
// get rid of "-" elements
allElements = FullToolbarEditor.filter( allElements, function( elem ) {
var isSeparator = ( elem === '-' ),
matchPrefix = ( prefix === undefined || elem.toLowerCase().indexOf( prefix.toLowerCase() ) === 0 );
return !isSeparator && matchPrefix;
} );
var elementsNotUsed = FullToolbarEditor.filter( allElements, function( elem ) {
return CKEDITOR.tools.indexOf( providedElements, elem ) == -1;
} );
if ( sorted )
elementsNotUsed.sort();
return elementsNotUsed;
};
/**
*
* @param {Array} buttons
* @returns {Array}
*/
ToolbarTextModifier.prototype.groupButtonNamesByGroup = function( buttons ) {
var result = [],
groupedBtns = JSON.parse( JSON.stringify( this.fullToolbarEditor.buttonNamesByGroup ) );
for ( var groupName in groupedBtns ) {
var currGroup = groupedBtns[ groupName ];
currGroup = FullToolbarEditor.filter( currGroup, function( btnName ) {
return CKEDITOR.tools.indexOf( buttons, btnName ) !== -1;
} );
if ( currGroup.length ) {
result.push( {
name: groupName,
buttons: currGroup
} );
}
}
return result;
};
/**
* Map toolbar config value to flat items list.
*
* input:
* [
* { name: "basicstyles", items: ["Bold", "Italic"] },
* { name: "advancedstyles", items: ["Bold", "Outdent", "Indent"] }
* ]
*
* output:
* ["Bold", "Italic", "Outdent", "Indent"]
*
* @param {Object} toolbar
* @returns {Array}
*/
ToolbarTextModifier.mapToolbarCfgToElementsList = function( toolbar ) {
var elements = [];
var max = toolbar.length;
for ( var i = 0; i < max; i += 1 ) {
if ( !toolbar[ i ] || typeof toolbar[ i ] === 'string' )
continue;
elements = elements.concat( FullToolbarEditor.filter( toolbar[ i ].items, checker ) );
}
function checker( elem ) {
return elem !== '-';
}
return elements;
};
/**
* @param {String} cfg
* @private
*/
ToolbarTextModifier.prototype._setupActualConfig = function( cfg ) {
cfg = cfg || this.editorInstance.config;
// if toolbar already exists in config, there is nothing to do
if ( CKEDITOR.tools.isArray( cfg.toolbar ) )
return;
// if toolbar group not present, we need to pick them from full toolbar instance
if ( !cfg.toolbarGroups )
cfg.toolbarGroups = this.fullToolbarEditor.getFullToolbarGroupsConfig( true );
this._fixGroups( cfg );
cfg.toolbar = this._mapToolbarGroupsToToolbar( cfg.toolbarGroups, this.actualConfig.removeButtons );
this.actualConfig.toolbar = cfg.toolbar;
this.actualConfig.removeButtons = '';
};
/**
* **Please note:** This method modify element provided in first argument.
*
* @param {Array} toolbarGroups
* @returns {Array}
* @private
*/
ToolbarTextModifier.prototype._mapToolbarGroupsToToolbar = function( toolbarGroups, removedBtns ) {
removedBtns = removedBtns || this.editorInstance.config.removedBtns;
removedBtns = typeof removedBtns == 'string' ? removedBtns.split( ',' ) : [];
// from the end, because array indexes may change
var i = toolbarGroups.length;
while ( i-- ) {
var mappedSubgroup = this._mapToolbarSubgroup( toolbarGroups[ i ], removedBtns );
if ( toolbarGroups[ i ].type === 'separator' ) {
toolbarGroups[ i ] = '/';
continue;
}
// don't want empty groups
if ( CKEDITOR.tools.isArray( mappedSubgroup ) && mappedSubgroup.length === 0 ) {
toolbarGroups.splice( i, 1 );
continue;
}
if ( typeof mappedSubgroup == 'string' )
toolbarGroups[ i ] = mappedSubgroup;
else {
toolbarGroups[ i ] = {
name: toolbarGroups[ i ].name,
items: mappedSubgroup
};
}
}
return toolbarGroups;
};
/**
*
* @param {String|Object} group
* @param {Array} removedBtns
* @returns {Array}
* @private
*/
ToolbarTextModifier.prototype._mapToolbarSubgroup = function( group, removedBtns ) {
var totalBtns = 0;
if ( typeof group == 'string' )
return group;
var max = group.groups ? group.groups.length : 0,
result = [];
for ( var i = 0; i < max; i += 1 ) {
var currSubgroup = group.groups[ i ];
var buttons = this.fullToolbarEditor.buttonsByGroup[ typeof currSubgroup === 'string' ? currSubgroup : currSubgroup.name ] || [];
buttons = this._mapButtonsToButtonsNames( buttons, removedBtns );
var currTotalBtns = buttons.length;
totalBtns += currTotalBtns;
result = result.concat( buttons );
if ( currTotalBtns )
result.push( '-' );
}
if ( result[ result.length - 1 ] == '-' )
result.pop();
return result;
};
/**
*
* @param {Array} buttons
* @param {Array} removedBtns
* @returns {Array}
* @private
*/
ToolbarTextModifier.prototype._mapButtonsToButtonsNames = function( buttons, removedBtns ) {
var i = buttons.length;
while ( i-- ) {
var currBtn = buttons[ i ],
camelCasedName;
if ( typeof currBtn === 'string' ) {
camelCasedName = currBtn;
} else {
camelCasedName = this.fullToolbarEditor.getCamelCasedButtonName( currBtn.name );
}
if ( CKEDITOR.tools.indexOf( removedBtns, camelCasedName ) !== -1 ) {
buttons.splice( i, 1 );
continue;
}
buttons[ i ] = camelCasedName;
}
return buttons;
};
/**
* @param {String} val
* @returns {Object}
* @private
*/
ToolbarTextModifier.prototype._evaluateValue = function( val ) {
var parsed;
try {
var config = {};
( function() {
var CKEDITOR = Function( 'var CKEDITOR = {}; ' + val + '; return CKEDITOR;' )();
CKEDITOR.editorConfig( config );
parsed = config;
} )();
// CKEditor does not handle empty arrays in configuration files
// on IE8
var i = parsed.toolbar.length;
while ( i-- )
if ( !parsed.toolbar[ i ] ) parsed.toolbar.splice( i, 1 );
} catch ( e ) {
parsed = null;
}
return parsed;
};
/**
* @param {Array} toolbar
* @returns {{toolbarGroups: Array, removeButtons: string}}
*/
ToolbarTextModifier.prototype.mapToolbarToToolbarGroups = function( toolbar ) {
var usedGroups = {},
removeButtons = [],
toolbarGroups = [];
var max = toolbar.length;
for ( var i = 0; i < max; i++ ) {
if ( toolbar[ i ] === '/' ) {
toolbarGroups.push( '/' );
continue;
}
var items = toolbar[ i ].items;
var toolbarGroup = {};
toolbarGroup.name = toolbar[ i ].name;
toolbarGroup.groups = [];
var max2 = items.length;
for ( var j = 0; j < max2; j++ ) {
var item = items[ j ];
if ( item === '-' ) {
continue;
}
var groupName = this.getToolbarGroupByButtonName( item );
var groupIndex = toolbarGroup.groups.indexOf( groupName );
if ( groupIndex === -1 ) {
toolbarGroup.groups.push( groupName );
}
usedGroups[ groupName ] = usedGroups[ groupName ] || {};
var buttons = ( usedGroups[ groupName ].buttons = usedGroups[ groupName ].buttons || {} );
buttons[ item ] = buttons[ item ] || { used: 0, origin: toolbarGroup.name };
buttons[ item ].used++;
}
toolbarGroups.push( toolbarGroup );
}
// Handling removed buttons
removeButtons = prepareRemovedButtons( usedGroups, this.fullToolbarEditor.buttonNamesByGroup );
function prepareRemovedButtons( usedGroups, buttonNames ) {
var removed = [];
for ( var groupName in usedGroups ) {
var group = usedGroups[ groupName ];
var allButtonsInGroup = buttonNames[ groupName ].slice();
removed = removed.concat( removeStuffFromArray( allButtonsInGroup, Object.keys( group.buttons ) ) );
}
return removed;
}
function removeStuffFromArray( array, stuff ) {
array = array.slice();
var i = stuff.length;
while ( i-- ) {
var atIndex = array.indexOf( stuff[ i ] );
if ( atIndex !== -1 ) {
array.splice( atIndex, 1 );
}
}
return array;
}
return { toolbarGroups: toolbarGroups, removeButtons: removeButtons.join( ',' ) };
};
return ToolbarTextModifier;
} )();

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
// For licensing, see LICENSE.html or http://cksource.com/ckeditor/license
@base-font-size: 16px;
@base-line-height: 24px;
@base-line-ratio: 1.8;
@sample-font-stack: Arial, Helvetica, sans-serif;
@sample-font-stack-monospace: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
@sample-font-maven: "Maven Pro";
@sample-font-indie: "Indie Flower";
@sample-text-color: #575757;
@sample-link-color: #cf5d4e;
@sample-link-color-hover: lighten( @sample-link-color, -10% );
@sample-box-background-color: #f5f5f5;
@sample-box-border-color: #ddd;
@sample-top-navigation-background: #454545;
// Standard gaps
@sample-standard-vgap: 1.2em;
@sample-standard-hgap: 1.5em;
// Generic font-size/line-height mixin.
.font-size( @remSize ) {
@pxSize: round( @remSize * @base-font-size, 2 );
@remHeight: round( @remSize * @base-line-ratio, 2 );
@pxHeight: round( @pxSize * @base-line-ratio, 2 );
font-size: ~"@{pxSize}";
font-size: ~"@{remSize}rem";
line-height: ~"@{pxHeight}";
line-height: ~"@{remHeight}rem";
}

View File

@@ -0,0 +1,508 @@
// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
// For licensing, see LICENSE.html or http://cksource.com/ckeditor/license
@import "base.less";
@modifier-group-hover-color: #fffbe3;
@modifier-group-active-color: #f0fafb;
@modifier-active-toolbar-color: darken( @modifier-group-hover-color, 10% );
@modifier-toolbar-border-color: #ccc;
@modifier-toolbar-group-border-color: #ddd;
@modifier-toolbar-group-vpadding: 2px;
@modifier-toolbar-hgap: 5px;
@modifier-toolbar-button-color: #e7e7e7;
#toolbar .cke_toolbar {
pointer-events: none;
.user-select( none );
cursor: default;
}
// Dim all but active toolbars if some is active.
.some-toolbar-active .cke_toolbar {
.opacity( .5 );
}
.cke_toolbar.active {
position: relative;
// Active toolbar is always highlighted.
.opacity( 1 );
&:after {
content: '';
display: block;
position: absolute;
top: 0;
right: 6px;
bottom: 5px;
left: 0;
.border-radius( 5px );
.box-shadow( 0px 0px 15px 3px @modifier-active-toolbar-color );
}
.cke_toolgroup {
.box-shadow( none );
border-color: darken( @modifier-active-toolbar-color, 40% );
}
.cke_combo,
.cke_toolgroup {
position: relative;
z-index: 2;
}
.cke_combo_button {
.box-shadow( none );
}
}
.unselectable {
.user-select( none );
}
.toolbar {
padding: 5px 0;
margin-bottom: 2 * @sample-standard-vgap;
overflow: hidden;
background: #fff;
button.button-a {
&.cke_button {
cursor: pointer;
display: inline-block;
padding: 4px 6px;
outline: 0;
border: 1px solid #a6a6a6;
}
&.hidden {
display: none;
}
&.left {
float: left;
margin-right: 8px;
}
&.right {
float: right;
margin-left: 8px;
}
.highlight {
color: #ffefc1;
}
}
}
// Styles applied when configurator is hidden and code is being displayed (and vice-versa).
.configContainer.hidden,
.toolbarModifier.hidden,
.toolbarModifier-hints.hidden {
display: none;
}
.toolbarModifier :focus,
.toolbar button:focus,
.configContainer textarea.configCode:focus {
outline: none;
}
div.toolbarModifier {
padding: 0;
overflow: hidden;
width: 100%;
position: relative;
display: table;
border-collapse: collapse;
::-moz-focus-inner {
border: 0;
}
.empty {
display: none;
}
&.empty-visible .empty {
display: table-row;
.opacity( 0.6 );
}
// Give empty toolbar groups height similar to height of non empty groups.
// Non empty groups are stretched by contained toolbar buttons.
.empty > p {
line-height: 31px;
}
// List of toolbars.
& > ul {
padding: 0;
margin: 0;
border-top: 1px solid @modifier-toolbar-border-color;
width: 100%;
&[data-type="table-header"] {
display: table-header-group;
}
&[data-type="table-body"] {
display: table-row-group;
}
// Override global margins and paddings.
p {
padding: 0;
margin: 0;
}
// A single toolbar.
& > li {
display: table-row;
&[data-type="header"] {
font-weight: bold;
user-select: none;
cursor: default;
}
&[data-type="group"],
&[data-type="separator"] {
border-bottom: 1px solid @modifier-toolbar-border-color;
}
&[data-type="subgroup"] {
border-top: 1px solid #eee;
&:first-child {
border-top: none;
}
}
&[data-type="group"].active,
&[data-type="group"]:hover,
&[data-type="separator"].active,
&[data-type="separator"]:hover {
overflow: hidden;
z-index: 2;
}
&[data-type="group"].active,
&[data-type="separator"].active,
&[data-type="group"].active:hover,
&[data-type="separator"].active:hover {
background: @modifier-group-active-color;
}
&[data-type="group"]:hover,
&[data-type="separator"]:hover {
background: @modifier-group-hover-color;
}
&[data-type="separator"] {
&:after {
content: '';
width: 100%;
}
background: #f5f5f5;
& > p {
padding: @modifier-toolbar-group-vpadding @modifier-toolbar-hgap;
}
}
& > p, & > ul {
display: table-cell;
vertical-align: middle;
}
// Note: this also controls the list of toolbar groups.
p {
padding-left: @modifier-toolbar-hgap;
min-width: 200px;
span {
white-space: nowrap;
cursor: default;
button {
font-size: 12.666px;
margin-right: 5px;
cursor: pointer;
background: #fff;
.border-radius( 5px );
border: 1px solid #bbb;
padding: 0 7px;
line-height: 12px;
height: 20px;
&:not(.disabled) {
&:hover,
&:focus {
color: #fff;
background-color: @sample-top-navigation-background;
border-color: transparent;
}
}
&.move.disabled {
cursor: default;
.opacity( 0.2 );
}
}
}
}
// List of toolbar groups.
ul {
border-collapse: collapse;
padding: 0;
width: 100%;
// A single toolbar group.
li {
display: table-row;
list-style-type: none;
// Resets slightly increased lists' lh which is bigger than button's height
// so it stretches columns.
line-height: 1;
&[data-type="subgroup"] {
border-top: 1px solid @modifier-toolbar-group-border-color;
&:first-child {
border-top: 0;
}
[data-type="button"] {
.border-radius( 3px );
padding: 0 2px;
&:focus {
background: rgba(0, 0, 0, 0.04);
}
input {
vertical-align: middle;
}
}
}
& > p, & > ul {
display: table-cell;
vertical-align: middle;
}
// List of buttons in a group.
ul {
padding: 0;
// A single button in a group.
li {
padding: 0;
display: inline-block;
cursor: pointer;
margin: @modifier-toolbar-group-vpadding 5px @modifier-toolbar-group-vpadding 0;
// Enforce styles to save space.
.cke_combo_text {
cursor: pointer;
white-space: nowrap;
}
.cke_toolgroup,
.cke_combo_button {
cursor: pointer;
margin: 0;
vertical-align: middle;
border: 1px solid #ddd;
.font-size( .713 );
}
}
}
}
}
}
}
& > .codemirror-wrapper {
overflow-y: auto;
}
// Advanced configurator: list of unused elements.
&-hints {
float: right;
width: 350px;
min-width: 150px;
overflow-y: auto;
margin-left: @sample-standard-hgap;
h3 {
.font-size( 1.13 );
padding: .3*@sample-standard-vgap @sample-standard-hgap;
background: @sample-box-background-color;
border-bottom: 1px solid @sample-box-border-color;
margin-top: 0;
margin-bottom: @sample-standard-vgap;
}
dl {
//margin-top: 0;
margin-bottom: @sample-standard-vgap;
overflow: hidden;
.list-header {
font-weight: bold;
border: 0;
padding-bottom: .5*@sample-standard-vgap;
}
& > p {
text-align: center;
}
dt {
float: left;
width: 9em;
clear: both;
text-align: right;
border-top: 1px solid @sample-box-border-color;
padding-left: @sample-standard-hgap;
padding-right: .1em;
.box-sizing( border-box );
code {
background: none;
border: none;
vertical-align: middle;
}
}
dd {
margin-left: 10em;
clear: right;
padding-right: @sample-standard-hgap;
code {
line-height: 2.2em;
}
&:after {
content: '\00a0';
display: block;
clear: left;
float: right;
height: 0;
width: 0;
}
}
}
}
}
.toolbarModifier-hints,
.configContainer textarea.configCode,
.CodeMirror {
.border-radius( 3px );
border: 1px solid #ccc;
.font-size( .813 );
}
.configContainer textarea.configCode,
.CodeMirror pre,
.CodeMirror-linenumber {
.font-size( .813 );
font-family: @sample-font-stack-monospace;
}
.CodeMirror pre {
border: none;
padding: 0;
margin: 0;
}
.configContainer textarea.configCode {
.box-sizing( border-box );
color: @sample-text-color;
padding: 10px;
width: 100%;
min-height: 500px;
margin: 0;
resize: none;
outline: none;
-moz-tab-size: 4;
tab-size: 4;
white-space: pre;
word-wrap: normal;
overflow: auto;
}
.CodeMirror-hints.toolbar-modifier {
padding: 0;
color: @sample-text-color;
.CodeMirror-hint-active {
color: @sample-text-color;
background: @modifier-group-active-color;
}
.font-size( .875 );
font-family: @sample-font-stack-monospace;
& > li:hover {
background: @modifier-group-hover-color;
}
}
/* Text modifier */
#toolbarModifierWrapper {
margin-bottom: @sample-standard-vgap;
.invalid .CodeMirror {
background: #fff8f8;
border-color: red;
}
.CodeMirror {
// Autogrow. http://codemirror.net/demo/resize.html
height: auto;
// Complementory with std's CodeMirror-lines vertical padding.
// Not needed when we use lines number, but we can't due to a bug in CM.
padding: 0 @sample-standard-vgap/2;
}
}
.staticContainer {
position: fixed;
top: 0;
width: 100%;
z-index: 10;
> .grid-container {
max-width: 1044px + 2 * @grid-gutter-width;
.inner {
background: #fff;
.toolbar {
margin-bottom: 0;
}
}
}
}
// Help button to display information about configurator.
#help {
position: relative;
top: -15px;
left: -5px;
&-content {
display: none;
}
}

View File

@@ -0,0 +1,19 @@
Copyright (C) 2014 by Marijn Haverbeke <marijnh@gmail.com> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,12 @@
# CodeMirror
[![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror)
[![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror)
[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png?again)](https://marijnhaverbeke.nl/fund/)
CodeMirror is a JavaScript component that provides a code editor in
the browser. When a mode is available for the language you are coding
in, it will color your code, and optionally help with indentation.
The project page is http://codemirror.net
The manual is at http://codemirror.net/doc/manual.html
The contributing guidelines are in [CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)

View File

@@ -0,0 +1,325 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror div.CodeMirror-cursor {
border-left: 1px solid black;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
}
.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
@-moz-keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
@-webkit-keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
@keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
/* Can style cursor different in overwrite (non-insert) mode */
div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actuall scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
height: 100%;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor {
position: absolute;
border-right: none;
width: 0;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror ::selection { background: #d7d4f0; }
.CodeMirror ::-moz-selection { background: #d7d4f0; }
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,701 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// TODO actually recognize syntax of TypeScript constructs
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("interface"),
"extends": kw("extends"),
"constructor": kw("constructor"),
// scope modifiers
"public": kw("public"),
"private": kw("private"),
"protected": kw("protected"),
"static": kw("static"),
// types
"string": type, "number": type, "bool": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|~^]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (state.lastType == "operator" || state.lastType == "keyword c" ||
state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
readRegexp(stream);
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) break;
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/]/.test(ch)) {
return;
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
function inList(list) {
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false;
}
var state = cx.state;
if (state.context) {
cx.marked = "def";
if (inList(state.localVars)) return;
state.localVars = {name: varname, next: state.localVars};
} else {
if (inList(state.globalVars)) return;
if (parserConfig.globalVars)
state.globalVars = {name: varname, next: state.globalVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), expression, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("form"), afterExport, poplex);
if (type == "import") return cont(pushlex("form"), afterImport, poplex);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
return expressionInner(type, false);
}
function expressionNoComma(type) {
return expressionInner(type, true);
}
function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") { return pass(quasi, maybeop); }
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeexpressionNoComma(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expressionNoComma);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(what, proceed);
}
if (type == end) return cont();
return cont(expect(end));
}
return function(type) {
if (type == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type) {
if (isTS && type == ":") return cont(typedef);
}
function typedef(type) {
if (type == "variable"){cx.marked = "variable-3"; return cont();}
}
function vardef() {
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (type == "variable") { register(value); return cont(); }
if (type == "[") return contCommasep(pattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
return cont(expect(":"), pattern, maybeAssign);
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type) {
if (type == "spread") return cont(funarg);
return pass(pattern, maybetype);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "extends") return cont(expression, classNameAfter);
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") {
if (value == "static") {
cx.marked = "keyword";
return cont(classBody);
}
cx.marked = "property";
if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
return cont(functiondef, classBody);
}
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (type == ";") return cont(classBody);
if (type == "}") return cont();
}
function classGetterSetter(type) {
if (type != "variable") return pass();
cx.marked = "property";
return cont();
}
function afterModule(type, value) {
if (type == "string") return cont(statement);
if (type == "variable") { register(value); return cont(maybeFrom); }
}
function afterExport(_type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
return pass(statement);
}
function afterImport(type) {
if (type == "string") return cont();
return pass(importSpec, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(expressionNoComma, maybeArrayComprehension);
}
function maybeArrayComprehension(type) {
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]"));
}
function comprehension(type) {
if (type == "for") return cont(forspec, comprehension);
if (type == "if") return cont(expression, comprehension);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});

View File

@@ -0,0 +1,36 @@
/* neo theme for codemirror */
/* Color scheme */
.cm-s-neo.CodeMirror {
background-color:#ffffff;
color:#2e383c;
line-height:1.4375;
}
.cm-s-neo .cm-comment {color:#75787b}
.cm-s-neo .cm-keyword, .cm-s-neo .cm-property {color:#1d75b3}
.cm-s-neo .cm-atom,.cm-s-neo .cm-number {color:#75438a}
.cm-s-neo .cm-node,.cm-s-neo .cm-tag {color:#9c3328}
.cm-s-neo .cm-string {color:#b35e14}
.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier {color:#047d65}
/* Editor styling */
.cm-s-neo pre {
padding:0;
}
.cm-s-neo .CodeMirror-gutters {
border:none;
border-right:10px solid transparent;
background-color:transparent;
}
.cm-s-neo .CodeMirror-linenumber {
padding:0;
color:#e0e2e5;
}
.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; }
.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; }

View File

@@ -0,0 +1,38 @@
.CodeMirror-hints {
position: absolute;
z-index: 10;
overflow: hidden;
list-style: none;
margin: 0;
padding: 2px;
-webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
-moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
box-shadow: 2px 3px 5px rgba(0,0,0,.2);
border-radius: 3px;
border: 1px solid silver;
background: white;
font-size: 90%;
font-family: monospace;
max-height: 20em;
overflow-y: auto;
}
.CodeMirror-hint {
margin: 0;
padding: 0 4px;
border-radius: 2px;
max-width: 19em;
overflow: hidden;
white-space: pre;
color: black;
cursor: pointer;
}
li.CodeMirror-hint-active {
background: #08f;
color: white;
}

View File

@@ -0,0 +1,392 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
// This is the old interface, kept around for now to stay
// backwards-compatible.
CodeMirror.showHint = function(cm, getHints, options) {
if (!getHints) return cm.showHint(options);
if (options && options.async) getHints.async = true;
var newOpts = {hint: getHints};
if (options) for (var prop in options) newOpts[prop] = options[prop];
return cm.showHint(newOpts);
};
CodeMirror.defineExtension("showHint", function(options) {
// We want a single cursor position.
if (this.listSelections().length > 1 || this.somethingSelected()) return;
if (this.state.completionActive) this.state.completionActive.close();
var completion = this.state.completionActive = new Completion(this, options);
if (!completion.options.hint) return;
CodeMirror.signal(this, "startCompletion", this);
completion.update();
});
function Completion(cm, options) {
this.cm = cm;
this.options = this.buildOptions(options);
this.widget = null;
this.debounce = 0;
this.tick = 0;
this.startPos = this.cm.getCursor();
this.startLen = this.cm.getLine(this.startPos.line).length;
var self = this;
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
}
var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
return setTimeout(fn, 1000/60);
};
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
Completion.prototype = {
close: function() {
if (!this.active()) return;
this.cm.state.completionActive = null;
this.tick = null;
this.cm.off("cursorActivity", this.activityFunc);
if (this.widget) this.widget.close();
CodeMirror.signal(this.cm, "endCompletion", this.cm);
},
active: function() {
return this.cm.state.completionActive == this;
},
pick: function(data, i) {
var completion = data.list[i];
if (completion.hint) completion.hint(this.cm, data, completion);
else this.cm.replaceRange(getText(completion), completion.from || data.from,
completion.to || data.to, "complete");
CodeMirror.signal(data, "pick", completion);
this.close();
},
showHints: function(data) {
if (!data || !data.list.length || !this.active()) return this.close();
if (this.options.completeSingle && data.list.length == 1)
this.pick(data, 0);
else
this.showWidget(data);
},
cursorActivity: function() {
if (this.debounce) {
cancelAnimationFrame(this.debounce);
this.debounce = 0;
}
var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
(pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
this.close();
} else {
var self = this;
this.debounce = requestAnimationFrame(function() {self.update();});
if (this.widget) this.widget.disable();
}
},
update: function() {
if (this.tick == null) return;
if (this.data) CodeMirror.signal(this.data, "update");
if (!this.options.hint.async) {
this.finishUpdate(this.options.hint(this.cm, this.options), myTick);
} else {
var myTick = ++this.tick, self = this;
this.options.hint(this.cm, function(data) {
if (self.tick == myTick) self.finishUpdate(data);
}, this.options);
}
},
finishUpdate: function(data) {
this.data = data;
var picked = this.widget && this.widget.picked;
if (this.widget) this.widget.close();
if (data && data.list.length) {
if (picked && data.list.length == 1) this.pick(data, 0);
else this.widget = new Widget(this, data);
}
},
showWidget: function(data) {
this.data = data;
this.widget = new Widget(this, data);
CodeMirror.signal(data, "shown");
},
buildOptions: function(options) {
var editor = this.cm.options.hintOptions;
var out = {};
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
if (editor) for (var prop in editor)
if (editor[prop] !== undefined) out[prop] = editor[prop];
if (options) for (var prop in options)
if (options[prop] !== undefined) out[prop] = options[prop];
return out;
}
};
function getText(completion) {
if (typeof completion == "string") return completion;
else return completion.text;
}
function buildKeyMap(completion, handle) {
var baseMap = {
Up: function() {handle.moveFocus(-1);},
Down: function() {handle.moveFocus(1);},
PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
Home: function() {handle.setFocus(0);},
End: function() {handle.setFocus(handle.length - 1);},
Enter: handle.pick,
Tab: handle.pick,
Esc: handle.close
};
var custom = completion.options.customKeys;
var ourMap = custom ? {} : baseMap;
function addBinding(key, val) {
var bound;
if (typeof val != "string")
bound = function(cm) { return val(cm, handle); };
// This mechanism is deprecated
else if (baseMap.hasOwnProperty(val))
bound = baseMap[val];
else
bound = val;
ourMap[key] = bound;
}
if (custom)
for (var key in custom) if (custom.hasOwnProperty(key))
addBinding(key, custom[key]);
var extra = completion.options.extraKeys;
if (extra)
for (var key in extra) if (extra.hasOwnProperty(key))
addBinding(key, extra[key]);
return ourMap;
}
function getHintElement(hintsElement, el) {
while (el && el != hintsElement) {
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
el = el.parentNode;
}
}
function Widget(completion, data) {
this.completion = completion;
this.data = data;
this.picked = false;
var widget = this, cm = completion.cm;
var hints = this.hints = document.createElement("ul");
hints.className = "CodeMirror-hints";
this.selectedHint = data.selectedHint || 0;
var completions = data.list;
for (var i = 0; i < completions.length; ++i) {
var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
if (cur.className != null) className = cur.className + " " + className;
elt.className = className;
if (cur.render) cur.render(elt, data, cur);
else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
elt.hintId = i;
}
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
var left = pos.left, top = pos.bottom, below = true;
hints.style.left = left + "px";
hints.style.top = top + "px";
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
(completion.options.container || document.body).appendChild(hints);
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
if (overlapY > 0) {
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor
hints.style.top = (top = pos.top - height) + "px";
below = false;
} else if (height > winH) {
hints.style.height = (winH - 5) + "px";
hints.style.top = (top = pos.bottom - box.top) + "px";
var cursor = cm.getCursor();
if (data.from.ch != cursor.ch) {
pos = cm.cursorCoords(cursor);
hints.style.left = (left = pos.left) + "px";
box = hints.getBoundingClientRect();
}
}
}
var overlapX = box.right - winW;
if (overlapX > 0) {
if (box.right - box.left > winW) {
hints.style.width = (winW - 5) + "px";
overlapX -= (box.right - box.left) - winW;
}
hints.style.left = (left = pos.left - overlapX) + "px";
}
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
setFocus: function(n) { widget.changeActive(n); },
menuSize: function() { return widget.screenAmount(); },
length: completions.length,
close: function() { completion.close(); },
pick: function() { widget.pick(); },
data: data
}));
if (completion.options.closeOnUnfocus) {
var closingOnBlur;
cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
}
var startScroll = cm.getScrollInfo();
cm.on("scroll", this.onScroll = function() {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
var newTop = top + startScroll.top - curScroll.top;
var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
if (!below) point += hints.offsetHeight;
if (point <= editor.top || point >= editor.bottom) return completion.close();
hints.style.top = newTop + "px";
hints.style.left = (left + startScroll.left - curScroll.left) + "px";
});
CodeMirror.on(hints, "dblclick", function(e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
});
CodeMirror.on(hints, "click", function(e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
if (completion.options.completeOnSingleClick) widget.pick();
}
});
CodeMirror.on(hints, "mousedown", function() {
setTimeout(function(){cm.focus();}, 20);
});
CodeMirror.signal(data, "select", completions[0], hints.firstChild);
return true;
}
Widget.prototype = {
close: function() {
if (this.completion.widget != this) return;
this.completion.widget = null;
this.hints.parentNode.removeChild(this.hints);
this.completion.cm.removeKeyMap(this.keyMap);
var cm = this.completion.cm;
if (this.completion.options.closeOnUnfocus) {
cm.off("blur", this.onBlur);
cm.off("focus", this.onFocus);
}
cm.off("scroll", this.onScroll);
},
disable: function() {
this.completion.cm.removeKeyMap(this.keyMap);
var widget = this;
this.keyMap = {Enter: function() { widget.picked = true; }};
this.completion.cm.addKeyMap(this.keyMap);
},
pick: function() {
this.completion.pick(this.data, this.selectedHint);
},
changeActive: function(i, avoidWrap) {
if (i >= this.data.list.length)
i = avoidWrap ? this.data.list.length - 1 : 0;
else if (i < 0)
i = avoidWrap ? 0 : this.data.list.length - 1;
if (this.selectedHint == i) return;
var node = this.hints.childNodes[this.selectedHint];
node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
node = this.hints.childNodes[this.selectedHint = i];
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
if (node.offsetTop < this.hints.scrollTop)
this.hints.scrollTop = node.offsetTop - 3;
else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
},
screenAmount: function() {
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
}
};
CodeMirror.registerHelper("hint", "auto", function(cm, options) {
var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
if (helpers.length) {
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, options);
if (cur && cur.list.length) return cur;
}
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
if (words) return CodeMirror.hint.fromList(cm, {words: words});
} else if (CodeMirror.hint.anyword) {
return CodeMirror.hint.anyword(cm, options);
}
});
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
var found = [];
for (var i = 0; i < options.words.length; i++) {
var word = options.words[i];
if (word.slice(0, token.string.length) == token.string)
found.push(word);
}
if (found.length) return {
list: found,
from: CodeMirror.Pos(cur.line, token.start),
to: CodeMirror.Pos(cur.line, token.end)
};
});
CodeMirror.commands.autocomplete = CodeMirror.showHint;
var defaultOptions = {
hint: CodeMirror.hint.auto,
completeSingle: true,
alignWithWord: true,
closeCharacters: /[\s()\[\]{};:>,]/,
closeOnUnfocus: true,
completeOnSingleClick: false,
container: null,
customKeys: null,
extraKeys: null
};
CodeMirror.defineOption("hintOptions", null);
});

View File

@@ -0,0 +1,12 @@
{
"name": "ckeditor-toolbarconfigurator",
"version": "0.0.0",
"description": "",
"author": "CKSource (http://cksource.com/)",
"license": "For licensing, see LICENSE.md or http://ckeditor.com/license.",
"devDependencies": {
"benderjs": "~0.2.1",
"benderjs-chai": "~0.2.0",
"benderjs-mocha": "~0.1.2"
}
}

View File

@@ -0,0 +1,9 @@
/* global describe, it, expect, ToolbarConfigurator */
describe( 'Full toolbar configurator', function() {
var FTE = ToolbarConfigurator.FullToolbarEditor;
it( 'exists', function() {
expect( FTE ).to.be.a( 'function' );
} );
} );