new ckeditor
New ckeditor
This commit is contained in:
52
ckeditor/samples/toolbarconfigurator/bender.js
Normal file
52
ckeditor/samples/toolbarconfigurator/bender.js
Normal 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;
|
||||
55
ckeditor/samples/toolbarconfigurator/css/fontello.css
vendored
Normal file
55
ckeditor/samples/toolbarconfigurator/css/fontello.css
vendored
Normal 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'; } /* '' */
|
||||
12
ckeditor/samples/toolbarconfigurator/font/LICENSE.txt
Normal file
12
ckeditor/samples/toolbarconfigurator/font/LICENSE.txt
Normal 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/
|
||||
|
||||
|
||||
28
ckeditor/samples/toolbarconfigurator/font/config.json
Normal file
28
ckeditor/samples/toolbarconfigurator/font/config.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
ckeditor/samples/toolbarconfigurator/font/fontello.eot
Normal file
BIN
ckeditor/samples/toolbarconfigurator/font/fontello.eot
Normal file
Binary file not shown.
14
ckeditor/samples/toolbarconfigurator/font/fontello.svg
Normal file
14
ckeditor/samples/toolbarconfigurator/font/fontello.svg
Normal 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="" 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="" 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="" 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 |
BIN
ckeditor/samples/toolbarconfigurator/font/fontello.ttf
Normal file
BIN
ckeditor/samples/toolbarconfigurator/font/fontello.ttf
Normal file
Binary file not shown.
BIN
ckeditor/samples/toolbarconfigurator/font/fontello.woff
Normal file
BIN
ckeditor/samples/toolbarconfigurator/font/fontello.woff
Normal file
Binary file not shown.
446
ckeditor/samples/toolbarconfigurator/index.html
Normal file
446
ckeditor/samples/toolbarconfigurator/index.html
Normal 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 – The text editor for the Internet – <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
|
||||
</p>
|
||||
<p class="grid-width-100" id="copy">
|
||||
Copyright © 2003-2017, <a class="samples" href="http://cksource.com/">CKSource</a> – 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>
|
||||
@@ -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';
|
||||
};
|
||||
} )();
|
||||
365
ckeditor/samples/toolbarconfigurator/js/fulltoolbareditor.js
Normal file
365
ckeditor/samples/toolbarconfigurator/js/fulltoolbareditor.js
Normal 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;
|
||||
};
|
||||
|
||||
} )();
|
||||
1366
ckeditor/samples/toolbarconfigurator/js/toolbarmodifier.js
Normal file
1366
ckeditor/samples/toolbarconfigurator/js/toolbarmodifier.js
Normal file
File diff suppressed because it is too large
Load Diff
623
ckeditor/samples/toolbarconfigurator/js/toolbartextmodifier.js
Normal file
623
ckeditor/samples/toolbarconfigurator/js/toolbartextmodifier.js
Normal 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;
|
||||
} )();
|
||||
38
ckeditor/samples/toolbarconfigurator/less/base.less
Normal file
38
ckeditor/samples/toolbarconfigurator/less/base.less
Normal 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";
|
||||
}
|
||||
508
ckeditor/samples/toolbarconfigurator/less/toolbarmodifier.less
Normal file
508
ckeditor/samples/toolbarconfigurator/less/toolbarmodifier.less
Normal 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;
|
||||
}
|
||||
}
|
||||
19
ckeditor/samples/toolbarconfigurator/lib/codemirror/LICENSE
Normal file
19
ckeditor/samples/toolbarconfigurator/lib/codemirror/LICENSE
Normal 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.
|
||||
@@ -0,0 +1,12 @@
|
||||
# CodeMirror
|
||||
[](https://travis-ci.org/codemirror/CodeMirror)
|
||||
[](https://www.npmjs.org/package/codemirror)
|
||||
[Funding status: ](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)
|
||||
@@ -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; }
|
||||
8738
ckeditor/samples/toolbarconfigurator/lib/codemirror/codemirror.js
Normal file
8738
ckeditor/samples/toolbarconfigurator/lib/codemirror/codemirror.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 });
|
||||
|
||||
});
|
||||
36
ckeditor/samples/toolbarconfigurator/lib/codemirror/neo.css
Normal file
36
ckeditor/samples/toolbarconfigurator/lib/codemirror/neo.css
Normal 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; }
|
||||
@@ -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;
|
||||
}
|
||||
392
ckeditor/samples/toolbarconfigurator/lib/codemirror/show-hint.js
Normal file
392
ckeditor/samples/toolbarconfigurator/lib/codemirror/show-hint.js
Normal 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);
|
||||
});
|
||||
12
ckeditor/samples/toolbarconfigurator/package.json
Normal file
12
ckeditor/samples/toolbarconfigurator/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
9
ckeditor/samples/toolbarconfigurator/tests/one.js
Normal file
9
ckeditor/samples/toolbarconfigurator/tests/one.js
Normal 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' );
|
||||
} );
|
||||
} );
|
||||
Reference in New Issue
Block a user