Nuevos componentes en CKEditor para gestionar macros y enlaces a páginas de wikis de manera más amigable
This commit is contained in:
parent
cb188df1ae
commit
164ab377d1
15 changed files with 3037 additions and 47 deletions
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,162 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Autocomplete Custom View — CKEditor Sample</title>
|
||||
<script src="../../../ckeditor.js"></script>
|
||||
<script src="utils.js"></script>
|
||||
<link rel="stylesheet" href="../../../samples/css/samples.css">
|
||||
<link href="../skins/moono/autocomplete.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<style>
|
||||
.adjoined-bottom:before {
|
||||
height: 270px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<nav class="navigation-a">
|
||||
<div class="grid-container">
|
||||
<ul class="navigation-a-left grid-width-70">
|
||||
<li><a href="https://ckeditor.com">Project Homepage</a></li>
|
||||
<li><a href="https://github.com/ckeditor/ckeditor-dev/issues">I found a bug</a></li>
|
||||
<li><a href="https://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="https://ckeditor.com/blog/">CKEditor Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="header-a">
|
||||
<div class="grid-container">
|
||||
<h1 class="header-a-logo grid-width-30">
|
||||
<img src="../../../samples/img/logo.svg" onerror="this.src='../../../samples/img/logo.png'; this.onerror=null;" alt="CKEditor Sample">
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="adjoined-top">
|
||||
<div class="grid-container">
|
||||
<div class="content grid-width-100">
|
||||
<h1>Autocomplete Custom View Demo</h1>
|
||||
<p>This sample shows the progress of work on Autocomplete with custom View. Type “ @ ” (at least 2 characters) to start autocompletion.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjoined-bottom">
|
||||
<div class="grid-container">
|
||||
<div class="grid-width-100">
|
||||
<div id="editor">
|
||||
<h1>Apollo 11</h1>
|
||||
<figure class="image easyimage">
|
||||
<img alt="Saturn V carrying Apollo 11" src="../../../samples/img/logo.png">
|
||||
</figure>
|
||||
<p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
|
||||
<figure class="easyimage easyimage-side">
|
||||
<img alt="Saturn V carrying Apollo 11" src="../../image2/samples/assets/image1.jpg">
|
||||
<figcaption>Saturn V carrying Apollo 11</figcaption>
|
||||
</figure>
|
||||
<p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer-a grid-container">
|
||||
<div class="grid-container">
|
||||
<p class="grid-width-100">
|
||||
CKEditor – The text editor for the Internet – <a class="samples" href="https://ckeditor.com/">https://ckeditor.com</a>
|
||||
</p>
|
||||
<p class="grid-width-100" id="copy">
|
||||
Copyright © 2003-2019, <a class="samples" href="https://cksource.com/">CKSource</a> – Frederico Knabben. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
// For simplicity we define the plugin in the sample, but normally
|
||||
// it would be extracted to a separate file.
|
||||
CKEDITOR.plugins.add( 'customautocomplete', {
|
||||
requires: 'autocomplete',
|
||||
|
||||
onLoad: function() {
|
||||
var View = CKEDITOR.plugins.autocomplete.view,
|
||||
Autocomplete = CKEDITOR.plugins.autocomplete;
|
||||
|
||||
function CustomView( editor ) {
|
||||
// Call the parent class constructor.
|
||||
View.call( this, editor );
|
||||
}
|
||||
// Inherit the view methods.
|
||||
CustomView.prototype = CKEDITOR.tools.prototypedCopy( View.prototype );
|
||||
|
||||
// Change the positioning of the panel, so it is stretched
|
||||
// to 100% of the editor container width and is positioned
|
||||
// according to the editor container.
|
||||
CustomView.prototype.updatePosition = function( range ) {
|
||||
var caretRect = this.getViewPosition( range ),
|
||||
container = this.editor.container;
|
||||
|
||||
this.setPosition( {
|
||||
// Position the panel according to the editor container.
|
||||
left: container.$.offsetLeft,
|
||||
top: caretRect.top,
|
||||
bottom: caretRect.bottom
|
||||
} );
|
||||
// Stretch the panel to 100% of the editor container width.
|
||||
this.element.setStyle( 'width', container.getSize( 'width' ) + 'px' );
|
||||
};
|
||||
|
||||
function CustomAutocomplete( editor, textTestCallback, dataCallback ) {
|
||||
// Call the parent class constructor.
|
||||
Autocomplete.call( this, editor, textTestCallback, dataCallback );
|
||||
}
|
||||
// Inherit the autocomplete methods.
|
||||
CustomAutocomplete.prototype = CKEDITOR.tools.prototypedCopy( Autocomplete.prototype );
|
||||
|
||||
CustomAutocomplete.prototype.getView = function() {
|
||||
return new CustomView( this.editor );
|
||||
}
|
||||
|
||||
// Expose the custom autocomplete so it can be used later.
|
||||
CKEDITOR.plugins.customAutocomplete = CustomAutocomplete;
|
||||
}
|
||||
} );
|
||||
|
||||
var editor = CKEDITOR.replace( 'editor', {
|
||||
height: 600,
|
||||
extraPlugins: 'customautocomplete,autocomplete,textmatch,easyimage,sourcearea,toolbar,undo,wysiwygarea,basicstyles',
|
||||
toolbar: [
|
||||
{ name: 'document', items: [ 'Source', 'Undo', 'Redo' ] },
|
||||
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Strike' ] },
|
||||
]
|
||||
} );
|
||||
|
||||
editor.on( 'instanceReady', function() {
|
||||
var prefix = '@',
|
||||
minChars = 2,
|
||||
requireSpaceAfter = true,
|
||||
data = autocompleteUtils.generateData( CKEDITOR.dom.element.prototype, prefix );
|
||||
|
||||
// Use the custom autocomplete class.
|
||||
new CKEDITOR.plugins.customAutocomplete(
|
||||
editor,
|
||||
autocompleteUtils.getTextTestCallback( prefix, minChars, requireSpaceAfter ),
|
||||
autocompleteUtils.getAsyncDataCallback( data )
|
||||
);
|
||||
} );
|
||||
} )();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,172 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Autocomplete Smileys — CKEditor Sample</title>
|
||||
<script src="../../../ckeditor.js"></script>
|
||||
<script src="utils.js"></script>
|
||||
<link rel="stylesheet" href="../../../samples/css/samples.css">
|
||||
<link href="../skins/moono/autocomplete.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<style>
|
||||
.adjoined-bottom:before {
|
||||
height: 270px;
|
||||
}
|
||||
.cke_autocomplete_icon
|
||||
{
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<nav class="navigation-a">
|
||||
<div class="grid-container">
|
||||
<ul class="navigation-a-left grid-width-70">
|
||||
<li><a href="https://ckeditor.com">Project Homepage</a></li>
|
||||
<li><a href="https://github.com/ckeditor/ckeditor-dev/issues">I found a bug</a></li>
|
||||
<li><a href="https://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="https://ckeditor.com/blog/">CKEditor Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="header-a">
|
||||
<div class="grid-container">
|
||||
<h1 class="header-a-logo grid-width-30">
|
||||
<img src="../../../samples/img/logo.svg" onerror="this.src='../../../samples/img/logo.png'; this.onerror=null;" alt="CKEditor Sample">
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="adjoined-top">
|
||||
<div class="grid-container">
|
||||
<div class="content grid-width-100">
|
||||
<h1>Autocomplete Smileys Demo</h1>
|
||||
<p>This sample shows the progress of work on Autocomplete with Smileys integration. Type “ : ” to start smileys autocompletion.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjoined-bottom">
|
||||
<div class="grid-container">
|
||||
<div class="grid-width-100">
|
||||
<div id="editor">
|
||||
<h1>Apollo 11</h1>
|
||||
<figure class="image easyimage">
|
||||
<img alt="Saturn V carrying Apollo 11" src="../../../samples/img/logo.png">
|
||||
</figure>
|
||||
<p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
|
||||
<figure class="easyimage easyimage-side">
|
||||
<img alt="Saturn V carrying Apollo 11" src="../../image2/samples/assets/image1.jpg">
|
||||
<figcaption>Saturn V carrying Apollo 11</figcaption>
|
||||
</figure>
|
||||
<p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer-a grid-container">
|
||||
<div class="grid-container">
|
||||
<p class="grid-width-100">
|
||||
CKEditor – The text editor for the Internet – <a class="samples" href="https://ckeditor.com/">https://ckeditor.com</a>
|
||||
</p>
|
||||
<p class="grid-width-100" id="copy">
|
||||
Copyright © 2003-2019, <a class="samples" href="https://cksource.com/">CKSource</a> – Frederico Knabben. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
// For simplicity we define the plugin in the sample, but normally
|
||||
// it would be extracted to a separate file.
|
||||
CKEDITOR.plugins.add( 'smileyautocomplete', {
|
||||
requires: 'autocomplete,textmatch,smiley',
|
||||
|
||||
onLoad: function() {
|
||||
var that = this,
|
||||
View = CKEDITOR.plugins.autocomplete.view,
|
||||
Autocomplete = CKEDITOR.plugins.autocomplete;
|
||||
|
||||
function SmileyView( editor ) {
|
||||
// Call the parent class constructor.
|
||||
View.call( this, editor );
|
||||
|
||||
this.itemTemplate = new CKEDITOR.template(
|
||||
'<li data-id="{id}"><img src="{src}" alt="{id}" class="cke_autocomplete_icon"> {name}</li>'
|
||||
);
|
||||
}
|
||||
// Inherit the view methods.
|
||||
SmileyView.prototype = CKEDITOR.tools.prototypedCopy( View.prototype );
|
||||
|
||||
function SmileyAutocomplete( editor ) {
|
||||
var data = that.getData( editor );
|
||||
|
||||
// Call the parent class constructor.
|
||||
Autocomplete.call(
|
||||
this, editor,
|
||||
autocompleteUtils.getTextTestCallback( ':', 0, false ),
|
||||
autocompleteUtils.getSyncDataCallback( data )
|
||||
);
|
||||
}
|
||||
// Inherit the autocomplete methods.
|
||||
SmileyAutocomplete.prototype = CKEDITOR.tools.prototypedCopy( Autocomplete.prototype );
|
||||
|
||||
SmileyAutocomplete.prototype.getHtmlToInsert = function( item ) {
|
||||
return '<img src=' + item.src + ' alt="' + item.id + '" />';
|
||||
};
|
||||
|
||||
SmileyAutocomplete.prototype.getView = function() {
|
||||
return new SmileyView( this.editor );
|
||||
}
|
||||
|
||||
// Expose the smiley autocomplete so it can be used later.
|
||||
CKEDITOR.plugins.smileyAutocomplete = SmileyAutocomplete;
|
||||
},
|
||||
|
||||
getData: function( editor ) {
|
||||
var descriptions = editor.config.smiley_descriptions,
|
||||
images = editor.config.smiley_images,
|
||||
path = editor.config.smiley_path,
|
||||
data = [];
|
||||
|
||||
for ( var i = 0; i < descriptions.length; ++i ) {
|
||||
data.push( {
|
||||
id: descriptions[ i ],
|
||||
name: ':' + descriptions[ i ],
|
||||
src: CKEDITOR.tools.htmlEncode( path + images[ i ] )
|
||||
} );
|
||||
}
|
||||
return data;
|
||||
}
|
||||
} );
|
||||
|
||||
var editor = CKEDITOR.replace( 'editor', {
|
||||
height: 600,
|
||||
extraPlugins: 'smileyautocomplete,autocomplete,textmatch,easyimage,sourcearea,toolbar,undo,wysiwygarea,basicstyles',
|
||||
toolbar: [
|
||||
{ name: 'document', items: [ 'Source', 'Undo', 'Redo' ] },
|
||||
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Strike' ] },
|
||||
]
|
||||
} );
|
||||
|
||||
editor.on( 'instanceReady', function() {
|
||||
// Use the smiley autocomplete class.
|
||||
new CKEDITOR.plugins.smileyAutocomplete( editor );
|
||||
} );
|
||||
} )();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
*/
|
||||
|
||||
/* exported autocompleteUtils */
|
||||
|
||||
'use strict';
|
||||
|
||||
var autocompleteUtils = {
|
||||
generateData: function( object, prefix ) {
|
||||
return Object.keys( object ).sort().map( function( prop, index ) {
|
||||
return {
|
||||
id: index,
|
||||
name: prefix + prop
|
||||
};
|
||||
} );
|
||||
},
|
||||
|
||||
getAsyncDataCallback: function( data ) {
|
||||
return function( query, range, callback ) {
|
||||
setTimeout( function() {
|
||||
callback(
|
||||
data.filter( function( item ) {
|
||||
return item.name.indexOf( query ) === 0;
|
||||
} )
|
||||
);
|
||||
}, Math.random() * 500 );
|
||||
};
|
||||
},
|
||||
|
||||
getSyncDataCallback: function( data ) {
|
||||
return function( query, range, callback ) {
|
||||
callback(
|
||||
data.filter( function( item ) {
|
||||
return item.name.indexOf( query ) === 0;
|
||||
} )
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
getTextTestCallback: function( prefix, minChars, requireSpaceAfter ) {
|
||||
var matchPattern = createPattern();
|
||||
|
||||
return function( range ) {
|
||||
if ( !range.collapsed ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CKEDITOR.plugins.textMatch.match( range, matchCallback );
|
||||
};
|
||||
|
||||
function matchCallback( text, offset ) {
|
||||
var left = text.slice( 0, offset ),
|
||||
right = text.slice( offset ),
|
||||
match = left.match( matchPattern );
|
||||
|
||||
if ( !match ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( requireSpaceAfter ) {
|
||||
// Require space (or end of text) after the caret.
|
||||
if ( right && !right.match( /^\s/ ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return { start: match.index, end: offset };
|
||||
}
|
||||
|
||||
function createPattern() {
|
||||
var pattern = prefix + '\\w';
|
||||
|
||||
if ( minChars ) {
|
||||
pattern += '{' + minChars + ',}';
|
||||
} else {
|
||||
pattern += '*';
|
||||
}
|
||||
|
||||
pattern += '$';
|
||||
|
||||
return new RegExp( pattern );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
*/
|
||||
|
||||
.cke_autocomplete_panel
|
||||
{
|
||||
position: absolute;
|
||||
display: none;
|
||||
box-sizing: border-box;
|
||||
width: 200px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
background: #FFF;
|
||||
border: 1px solid #b6b6b6;
|
||||
border-bottom-color: #999;
|
||||
border-radius: 3px;
|
||||
font: 12px Arial, Helvetica, Tahoma, Verdana, Sans-Serif;
|
||||
}
|
||||
.cke_autocomplete_opened
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
.cke_autocomplete_panel > li
|
||||
{
|
||||
padding: 5px;
|
||||
}
|
||||
.cke_autocomplete_panel > li:hover
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
.cke_autocomplete_selected, .cke_autocomplete_panel > li:hover
|
||||
{
|
||||
background-color: #EFF0EF;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
CKEDITOR.dialog.add( 'redmineMacro', function( editor ) {
|
||||
var lang = editor.lang.redmine,
|
||||
generalLabel = editor.lang.common.generalTab,
|
||||
validNameRegex = /^[^\[\]<>]+$/;
|
||||
|
||||
return {
|
||||
title: lang.editMacro,
|
||||
minWidth: 300,
|
||||
minHeight: 80,
|
||||
contents: [
|
||||
{
|
||||
id: 'info',
|
||||
label: generalLabel,
|
||||
title: generalLabel,
|
||||
elements: [
|
||||
{
|
||||
id: 'name',
|
||||
type: 'text',
|
||||
style: 'width: 100%;',
|
||||
label: lang.editMacroProperties,
|
||||
'default': '',
|
||||
required: true,
|
||||
validate: CKEDITOR.dialog.validate.regex( validNameRegex, lang.invalidMacro ),
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.name );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'name', this.getValue() );
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
} );
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
CKEDITOR.dialog.add( 'redmineWikilink', function( editor ) {
|
||||
var lang = editor.lang.redmine,
|
||||
generalLabel = editor.lang.common.generalTab,
|
||||
validNameRegex = /^[^\[\]<>]+$/;
|
||||
|
||||
return {
|
||||
title: lang.wikiPageLink,
|
||||
minWidth: 300,
|
||||
minHeight: 80,
|
||||
contents: [
|
||||
{
|
||||
id: 'info',
|
||||
label: generalLabel,
|
||||
title: generalLabel,
|
||||
elements: [
|
||||
{
|
||||
id: 'name',
|
||||
type: 'text',
|
||||
style: 'width: 100%;',
|
||||
label: lang.wikiPage,
|
||||
'default': '',
|
||||
required: true,
|
||||
validate: CKEDITOR.dialog.validate.regex( validNameRegex, lang.invalidWikiPageLink ),
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.name );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'name', this.getValue() );
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'html',
|
||||
html: '- ' + lang.help_line1 + '<br />'
|
||||
+ '- ' + lang.help_line2 + '<br />'
|
||||
+ '- ' + lang.help_line3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
} );
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 835 B |
32
plugins/redmine_ckeditor/assets/ckeditor-contrib/plugins/redmine/lang/en.js
Executable file
32
plugins/redmine_ckeditor/assets/ckeditor-contrib/plugins/redmine/lang/en.js
Executable file
|
@ -0,0 +1,32 @@
|
|||
|
||||
CKEDITOR.plugins.setLang( 'redmine', 'en', {
|
||||
// Dialogs.
|
||||
wikiPageLink: 'Link to WikiPage',
|
||||
invalidWikiPageLink: 'The link can not be empty and can not contain any of following characters: [, ], <, >',
|
||||
help_line1: 'Also WikiPage#Anchor; or',
|
||||
help_line2: 'WikiPage|TextLink or WikiPage#Anchor|TextLink; or',
|
||||
help_line3: 'link to pages of another project using project:WikiPage.',
|
||||
|
||||
editMacro: 'Edit Macro',
|
||||
editMacroProperties: 'Edit Macro Properties',
|
||||
invalidMacro: 'It can not be empty and can not contain any of following characters: [, ], <, >',
|
||||
|
||||
// Plugin.
|
||||
pathName: 'redmineWidget',
|
||||
|
||||
// Autocomplete WikiLinks.
|
||||
wikiPage: 'WikiPage',
|
||||
anchor: 'Anchor',
|
||||
textLink: 'TextLink',
|
||||
project: 'project',
|
||||
wikipage_linkTo: 'Link to ',
|
||||
|
||||
// Autocomplete Macros.
|
||||
toc: 'Table of contents.',
|
||||
child_pages: 'List of child pages.',
|
||||
child_pages_depth: 'List of child pages with 2 levels.',
|
||||
include_page: 'Page',
|
||||
include: 'Include a wiki page.',
|
||||
include_project: 'project',
|
||||
include_from_project: 'Include a page of a specific project wiki.'
|
||||
});
|
32
plugins/redmine_ckeditor/assets/ckeditor-contrib/plugins/redmine/lang/es.js
Executable file
32
plugins/redmine_ckeditor/assets/ckeditor-contrib/plugins/redmine/lang/es.js
Executable file
|
@ -0,0 +1,32 @@
|
|||
|
||||
CKEDITOR.plugins.setLang( 'redmine', 'es', {
|
||||
// Dialogs.
|
||||
wikiPageLink: 'Enlace a Página del Wiki',
|
||||
invalidWikiPageLink: 'El enlace no puede estar vacío y no puede contener ninguno de los siguientes caracteres: [, ], <, >',
|
||||
help_line1: 'También Página#Referencia; o',
|
||||
help_line2: 'Página|TextoEnlace o Página#Referencia|TextoEnlace; o',
|
||||
help_line3: 'enlazar a páginas de otro proyecto usando proyecto:Página.',
|
||||
|
||||
editMacro: 'Editar Macro',
|
||||
editMacroProperties: 'Editar Propiedades de la Macro',
|
||||
invalidMacro: 'No puede estar vacío y no puede contener ninguno de los siguientes caracteres: [, ], <, >',
|
||||
|
||||
// Plugin.
|
||||
pathName: 'redmineWidget',
|
||||
|
||||
// Autocomplete WikiLinks.
|
||||
wikiPage: 'Página',
|
||||
anchor: 'Referencia',
|
||||
textLink: 'TextoEnlace',
|
||||
project: 'proyecto',
|
||||
wikipage_linkTo: 'Enlace a ',
|
||||
|
||||
// Autocomplete Macros.
|
||||
toc: 'Tabla de contenidos.',
|
||||
child_pages: 'Lista de páginas hijas.',
|
||||
child_pages_depth: 'Lista de páginas hijas con 2 niveles.',
|
||||
include_page: 'Página',
|
||||
include: 'Incluye una página del wiki.',
|
||||
include_project: 'proyecto',
|
||||
include_from_project: 'Incluye una página del wiki de otro proyecto.'
|
||||
});
|
|
@ -1,4 +1,8 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
/*
|
||||
function preservedPattern( i ) {
|
||||
return "____preserved_" + i + "____";
|
||||
}
|
||||
|
@ -7,17 +11,17 @@
|
|||
return function( data ) {
|
||||
var preserved = [];
|
||||
|
||||
// preserve Redmine macro
|
||||
// Preserve Redmine macro.
|
||||
data = data.replace( /\{\{.*?\}\}/g, function( match ) {
|
||||
preserved.push( decodeEntities( match ) );
|
||||
return preservedPattern( preserved.length );
|
||||
});
|
||||
|
||||
// convert
|
||||
// Convert.
|
||||
arguments[0] = data;
|
||||
data = f.apply( this, arguments );
|
||||
|
||||
// restore
|
||||
// Restore.
|
||||
for ( var i = 0; i < preserved.length; i++ ) {
|
||||
data = data.replace( preservedPattern( i + 1 ), preserved[i] );
|
||||
}
|
||||
|
@ -25,7 +29,7 @@
|
|||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
*/
|
||||
var element = document.createElement( 'div' );
|
||||
function decodeEntities( html ) {
|
||||
element.innerHTML = html;
|
||||
|
@ -35,18 +39,276 @@
|
|||
}
|
||||
|
||||
function onText( text, node ) {
|
||||
return (node.parent.name == "a") ?
|
||||
text : text.replace(/(^|\s)https?:\/\/\S*/g, decodeEntities);
|
||||
return ( node.parent.name == "a" ) ? text : text.replace( /(^|\s)https?:\/\/\S*/g, decodeEntities );
|
||||
}
|
||||
|
||||
// Register the plugin in the editor.
|
||||
CKEDITOR.plugins.add( 'redmine', {
|
||||
requires: 'widget,dialog,autocomplete,textmatch',
|
||||
lang: 'en,es', // %REMOVE_LINE_CORE%
|
||||
icons: 'wikipage', // %REMOVE_LINE_CORE%
|
||||
hidpi: true, // %REMOVE_LINE_CORE%
|
||||
|
||||
onLoad: function() {
|
||||
// Register styles for placeholder widget frame.
|
||||
CKEDITOR.addCss( '.cke_placeholder{padding:0 4px;margin:0 2px;background-color:#ff0}' );
|
||||
},
|
||||
|
||||
init: function( editor ) {
|
||||
|
||||
var lang = editor.lang.redmine;
|
||||
|
||||
// Register Wikilink Widget.
|
||||
CKEDITOR.dialog.add( 'redmineWikilink', this.path + 'dialogs/redmineWikilink.js' );
|
||||
editor.widgets.add( 'redmineWikilink', {
|
||||
dialog: 'redmineWikilink',
|
||||
pathName: lang.pathName,
|
||||
template: '<span class="cke_placeholder">[[]]</span>',
|
||||
|
||||
downcast: function() {
|
||||
return new CKEDITOR.htmlParser.text( '[[' + this.data.name + ']]' );
|
||||
},
|
||||
init: function() {
|
||||
this.setData( 'name', this.element.getText().slice( 2, -2 ) );
|
||||
},
|
||||
data: function() {
|
||||
this.element.setText( '[[' + this.data.name + ']]' );
|
||||
},
|
||||
getLabel: function() {
|
||||
return this.editor.lang.widget.label.replace( /%1/, this.data.name + ' ' + this.pathName );
|
||||
}
|
||||
} );
|
||||
editor.ui.addButton && editor.ui.addButton( 'LinkWikiPage', {
|
||||
label: lang.wikiPageLink,
|
||||
command: 'redmineWikilink',
|
||||
toolbar: 'insert,5',
|
||||
icon: 'wikipage'
|
||||
} );
|
||||
|
||||
// Register Macro Widget.
|
||||
CKEDITOR.dialog.add( 'redmineMacro', this.path + 'dialogs/redmineMacro.js' );
|
||||
editor.widgets.add( 'redmineMacro', {
|
||||
dialog: 'redmineMacro',
|
||||
pathName: lang.pathName,
|
||||
template: '<span class="cke_placeholder">{{}}</span>',
|
||||
|
||||
downcast: function() {
|
||||
return new CKEDITOR.htmlParser.text( '{{' + this.data.name + '}}' );
|
||||
},
|
||||
init: function() {
|
||||
this.setData( 'name', this.element.getText().slice( 2, -2 ) );
|
||||
},
|
||||
data: function() {
|
||||
this.element.setText( '{{' + this.data.name + '}}' );
|
||||
},
|
||||
getLabel: function() {
|
||||
return this.editor.lang.widget.label.replace( /%1/, this.data.name + ' ' + this.pathName );
|
||||
}
|
||||
} );
|
||||
|
||||
editor.on( 'instanceReady', function() {
|
||||
var config = {};
|
||||
|
||||
// Called when the user types in the editor or moves the caret.
|
||||
// The range represents the caret position.
|
||||
function textTestCallback( range ) {
|
||||
// You do not want to autocomplete a non-empty selection.
|
||||
if ( !range.collapsed ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use the text match plugin which does the tricky job of performing
|
||||
// a text search in the DOM. The "matchCallback" function should return
|
||||
// a matching fragment of the text.
|
||||
return CKEDITOR.plugins.textMatch.match( range, matchCallback );
|
||||
}
|
||||
|
||||
// Returns the position of the matching text.
|
||||
// It matches a word starting from '{{' or the '#' character
|
||||
// up to the caret position.
|
||||
function matchCallback( text, offset ) {
|
||||
var match;
|
||||
|
||||
// Will look for a '[[' characters followed by a wiki page name.
|
||||
match = text.slice( 0, offset ).match( /\[{2}([A-z]|\])*$/ );
|
||||
if ( match ) {
|
||||
return {
|
||||
start: match.index,
|
||||
end: offset
|
||||
};
|
||||
}
|
||||
|
||||
// Will look for a '{{' characters followed by a macro name.
|
||||
match = text.slice( 0, offset ).match( /\{{2}([A-z]|\})*$/ );
|
||||
if ( match ) {
|
||||
return {
|
||||
start: match.index,
|
||||
end: offset
|
||||
};
|
||||
}
|
||||
|
||||
/* Will look for a '#' character followed by a issue number.
|
||||
match = text.slice( 0, offset ).match( /#\d*$/ );
|
||||
if ( match ) {
|
||||
return {
|
||||
start: match.index,
|
||||
end: offset
|
||||
};
|
||||
} */
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
config.textTestCallback = textTestCallback;
|
||||
|
||||
// WikiPage links sugestions.
|
||||
var itemsWikiPageArray = [
|
||||
{
|
||||
id: 1,
|
||||
title: '[[' + lang.wikiPage + ']]',
|
||||
description: lang.wikipage_linkTo + lang.wikiPage + '.'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '[[' + lang.wikiPage + '#' + lang.anchor + ']]',
|
||||
description: lang.wikipage_linkTo + lang.wikiPage + '#' + lang.anchor + '.'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '[[' + lang.wikiPage + '|' + lang.textLink + ']]',
|
||||
description: lang.wikipage_linkTo + lang.wikiPage + '|' + lang.textLink + '.'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '[[' + lang.project + ':' + lang.wikiPage + ']]',
|
||||
description: lang.wikipage_linkTo + lang.project + ':' + lang.wikiPage + '.'
|
||||
}
|
||||
];
|
||||
// Macro sugestions.
|
||||
var itemsMacroArray = [
|
||||
{
|
||||
id: 1,
|
||||
title: '{{toc}}',
|
||||
description: lang.toc
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '{{child_pages}}',
|
||||
description: lang.child_pages
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '{{child_pages(depth=2)}}',
|
||||
description: lang.child_pages_depth
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '{{include(' + lang.include_page + ')}}',
|
||||
description: lang.include
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '{{include(' + lang.include_project + ':' + lang.include_page + ')}}',
|
||||
description: lang.include_from_project
|
||||
}
|
||||
];
|
||||
|
||||
// Returns (through its callback) the suggestions for the current query.
|
||||
function dataCallback( matchInfo, callback ) {
|
||||
var itemsArray;
|
||||
if (matchInfo.query.substring( 0, 2 ) == '[[' ) {
|
||||
itemsArray = itemsWikiPageArray;
|
||||
}
|
||||
else if (matchInfo.query.substring( 0, 2 ) == '{{' ) {
|
||||
itemsArray = itemsMacroArray;
|
||||
}
|
||||
var suggestions = itemsArray.filter( function( item ) {
|
||||
return item.title.indexOf( matchInfo.query.toLowerCase() ) == 0;
|
||||
});
|
||||
|
||||
callback( suggestions );
|
||||
|
||||
/* Remove the '#' tag.
|
||||
var query = matchInfo.query.substring( 1 );
|
||||
|
||||
// Simple search.
|
||||
// Filter the entire items array so only the items that start
|
||||
// with the query remain.
|
||||
var suggestions = itemsArray.filter( function( item ) {
|
||||
return String( item.id ).indexOf( query ) == 0;
|
||||
} );
|
||||
|
||||
// Note: The callback function can also be executed asynchronously
|
||||
// so dataCallback can do an XHR request or use any other asynchronous API.
|
||||
callback( suggestions ); */
|
||||
}
|
||||
|
||||
config.dataCallback = dataCallback;
|
||||
|
||||
// Define the templates of the autocomplete suggestions dropdown and output text.
|
||||
config.itemTemplate = '<li data-id="{id}"><strong>{title}</strong><br />{description}</li>';
|
||||
config.outputTemplate = "{title}";
|
||||
|
||||
// Attach autocomplete to the editor.
|
||||
new CKEDITOR.plugins.autocomplete( editor, config );
|
||||
} );
|
||||
},
|
||||
|
||||
afterInit: function( editor ) {
|
||||
var processor = editor.dataProcessor;
|
||||
|
||||
processor.toHtml = wrapConversion(processor.toHtml);
|
||||
processor.toDataFormat = wrapConversion(processor.toDataFormat);
|
||||
processor.dataFilter.addRules( {
|
||||
text: function( text, node ) {
|
||||
var dtd = node.parent && CKEDITOR.dtd[ node.parent.name ];
|
||||
|
||||
if ( dtd && !dtd.span )
|
||||
return;
|
||||
|
||||
return text.replace( /\[\[([^\[\]])+\]\]/g, function( match ) {
|
||||
// Creating widget code.
|
||||
var widgetWrapper = null,
|
||||
innerElement = new CKEDITOR.htmlParser.element( 'span', {
|
||||
'class': 'cke_placeholder'
|
||||
} );
|
||||
|
||||
// Adds placeholder identifier as innertext.
|
||||
innerElement.add( new CKEDITOR.htmlParser.text( match ) );
|
||||
widgetWrapper = editor.widgets.wrapElement( innerElement, 'redmineWikilink' );
|
||||
|
||||
// Return outerhtml of widget wrapper so it will be placed as replacement.
|
||||
return widgetWrapper.getOuterHtml();
|
||||
} );
|
||||
}
|
||||
} );
|
||||
processor.dataFilter.addRules( {
|
||||
text: function( text, node ) {
|
||||
var dtd = node.parent && CKEDITOR.dtd[ node.parent.name ];
|
||||
|
||||
if ( dtd && !dtd.span )
|
||||
return;
|
||||
|
||||
return text.replace( /\{\{([^\{\}])+\}\}/g, function( match ) {
|
||||
// Creating widget code.
|
||||
var widgetWrapper = null,
|
||||
innerElement = new CKEDITOR.htmlParser.element( 'span', {
|
||||
'class': 'cke_placeholder'
|
||||
} );
|
||||
|
||||
// Adds placeholder identifier as innertext.
|
||||
innerElement.add( new CKEDITOR.htmlParser.text( match ) );
|
||||
widgetWrapper = editor.widgets.wrapElement( innerElement, 'redmineMacro' );
|
||||
|
||||
// Return outerhtml of widget wrapper so it will be placed as replacement.
|
||||
return widgetWrapper.getOuterHtml();
|
||||
} );
|
||||
}
|
||||
} );
|
||||
processor.htmlFilter.addRules( { text: onText }, 11 );
|
||||
processor.dataFilter.addRules( { text: onText }, 11 );
|
||||
/*
|
||||
processor.toHtml = wrapConversion( processor.toHtml );
|
||||
processor.toDataFormat = wrapConversion( processor.toDataFormat );
|
||||
*/
|
||||
}
|
||||
} );
|
||||
|
||||
|
@ -56,4 +318,5 @@
|
|||
width['default'] = "100%";
|
||||
}
|
||||
} );
|
||||
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
/**
|
||||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
|
||||
CKEDITOR.plugins.add( 'textmatch', {} );
|
||||
|
||||
/**
|
||||
* A global namespace for methods exposed by the [Text Match](https://ckeditor.com/cke4/addon/textmatch) plugin.
|
||||
*
|
||||
* The most important function is {@link #match} which performs a text
|
||||
* search in the DOM.
|
||||
*
|
||||
* @singleton
|
||||
* @class
|
||||
* @since 4.10.0
|
||||
*/
|
||||
CKEDITOR.plugins.textMatch = {};
|
||||
|
||||
/**
|
||||
* Allows to search in the DOM for matching text using a callback which operates on strings instead of text nodes.
|
||||
* Returns {@link CKEDITOR.dom.range} and the matching text.
|
||||
*
|
||||
* ```javascript
|
||||
* var range = editor.getSelection().getRanges()[ 0 ];
|
||||
*
|
||||
* CKEDITOR.plugins.textMatch.match( range, function( text, offset ) {
|
||||
* // Let's assume that text is 'Special thanks to #jo.' and offset is 21.
|
||||
* // The offset "21" means that the caret is between '#jo' and '.'.
|
||||
*
|
||||
* // Get the text before the caret.
|
||||
* var left = text.slice( 0, offset ),
|
||||
* // Will look for a literal '#' character and at least two word characters.
|
||||
* match = left.match( /#\w{2,}$/ );
|
||||
*
|
||||
* if ( !match ) {
|
||||
* return null;
|
||||
* }
|
||||
*
|
||||
* // The matching fragment is the '#jo', which can
|
||||
* // be identified by the following offsets: { start: 18, end: 21 }.
|
||||
* return { start: match.index, end: offset };
|
||||
* } );
|
||||
* ```
|
||||
*
|
||||
* @member CKEDITOR.plugins.textMatch
|
||||
* @param {CKEDITOR.dom.range} range A collapsed range — the position from which the scanning starts.
|
||||
* Usually the caret position.
|
||||
* @param {Function} testCallback A callback executed to check if the text matches.
|
||||
* @param {String} testCallback.text The full text to check.
|
||||
* @param {Number} testCallback.rangeOffset An offset of the `range` in the `text` to be checked.
|
||||
* @param {Object} [testCallback.return] The position of the matching fragment (`null` if nothing matches).
|
||||
* @param {Number} testCallback.return.start The offset of the start of the matching fragment.
|
||||
* @param {Number} testCallback.return.end The offset of the end of the matching fragment.
|
||||
*
|
||||
* @returns {Object/null} An object with information about the matching text or `null`.
|
||||
* @returns {String} return.text The matching text.
|
||||
* The text does not reflect the range offsets. The range could contain additional,
|
||||
* browser-related characters like {@link CKEDITOR.dom.selection#FILLING_CHAR_SEQUENCE}.
|
||||
* @returns {CKEDITOR.dom.range} return.range A range in the DOM for the text that matches.
|
||||
*/
|
||||
CKEDITOR.plugins.textMatch.match = function( range, callback ) {
|
||||
var textAndOffset = CKEDITOR.plugins.textMatch.getTextAndOffset( range ),
|
||||
fillingCharSequence = CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE,
|
||||
fillingSequenceOffset = 0;
|
||||
|
||||
if ( !textAndOffset ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove filling char sequence for clean query (#2038).
|
||||
if ( textAndOffset.text.indexOf( fillingCharSequence ) == 0 ) {
|
||||
fillingSequenceOffset = fillingCharSequence.length;
|
||||
|
||||
textAndOffset.text = textAndOffset.text.replace( fillingCharSequence, '' );
|
||||
textAndOffset.offset -= fillingSequenceOffset;
|
||||
}
|
||||
|
||||
var result = callback( textAndOffset.text, textAndOffset.offset );
|
||||
|
||||
if ( !result ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
range: CKEDITOR.plugins.textMatch.getRangeInText( range, result.start, result.end + fillingSequenceOffset ),
|
||||
text: textAndOffset.text.slice( result.start, result.end )
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a text (as a string) in which the DOM range is located (the function scans for adjacent text nodes)
|
||||
* and the offset of the caret in that text.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* * `{}` is the range position in the text node (it means that the text node is **not** split at that position).
|
||||
* * `[]` is the range position in the element (it means that the text node is split at that position).
|
||||
* * `.` is a separator for text nodes (it means that the text node is split at that position).
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* Input: <p>he[]llo</p>
|
||||
* Result: { text: 'hello', offset: 2 }
|
||||
*
|
||||
* Input: <p>he.llo{}</p>
|
||||
* Result: { text: 'hello', offset: 5 }
|
||||
*
|
||||
* Input: <p>{}he.ll<i>o</i></p>
|
||||
* Result: { text: 'hell', offset: 0 }
|
||||
*
|
||||
* Input: <p>he{}<i>ll</i>o</p>
|
||||
* Result: { text: 'he', offset: 2 }
|
||||
*
|
||||
* Input: <p>he<i>ll</i>o.m{}y.friend</p>
|
||||
* Result: { text: 'omyfriend', offset: 2 }
|
||||
* ```
|
||||
*
|
||||
* @member CKEDITOR.plugins.textMatch
|
||||
* @param {CKEDITOR.dom.range} range
|
||||
* @returns {Object/null}
|
||||
* @returns {String} return.text The text in which the DOM range is located.
|
||||
* @returns {Number} return.offset An offset of the caret.
|
||||
*/
|
||||
CKEDITOR.plugins.textMatch.getTextAndOffset = function( range ) {
|
||||
if ( !range.collapsed ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var text = '', offset = 0,
|
||||
textNodes = CKEDITOR.plugins.textMatch.getAdjacentTextNodes( range ),
|
||||
nodeReached = false,
|
||||
elementIndex,
|
||||
startContainerIsText = ( range.startContainer.type != CKEDITOR.NODE_ELEMENT );
|
||||
|
||||
if ( startContainerIsText ) {
|
||||
// Determining element index in textNodes array.
|
||||
elementIndex = indexOf( textNodes, function( current ) {
|
||||
return range.startContainer.equals( current );
|
||||
} );
|
||||
} else {
|
||||
// Based on range startOffset decreased by first text node index.
|
||||
elementIndex = range.startOffset - ( textNodes[ 0 ] ? textNodes[ 0 ].getIndex() : 0 );
|
||||
}
|
||||
|
||||
var max = textNodes.length;
|
||||
for ( var i = 0; i < max; i += 1 ) {
|
||||
var currentNode = textNodes[ i ];
|
||||
text += currentNode.getText();
|
||||
|
||||
// We want to increase text offset only when startContainer is not reached.
|
||||
if ( !nodeReached ) {
|
||||
if ( startContainerIsText ) {
|
||||
if ( i == elementIndex ) {
|
||||
nodeReached = true;
|
||||
offset += range.startOffset;
|
||||
} else {
|
||||
offset += currentNode.getText().length;
|
||||
}
|
||||
} else {
|
||||
if ( i == elementIndex ) {
|
||||
nodeReached = true;
|
||||
}
|
||||
|
||||
// In below example there are three text nodes in p element and four possible offsets ( 0, 1, 2, 3 )
|
||||
// We are going to increase offset while iteration:
|
||||
// index 0 ==> 0
|
||||
// index 1 ==> 3
|
||||
// index 2 ==> 3 + 3
|
||||
// index 3 ==> 3 + 3 + 2
|
||||
|
||||
// <p> foo bar ba </p>
|
||||
// 0^^^1^^^2^^3
|
||||
if ( i > 0 ) {
|
||||
offset += textNodes[ i - 1 ].getText().length;
|
||||
}
|
||||
|
||||
// If element index at last element we also want to increase offset.
|
||||
if ( max == elementIndex && i + 1 == max ) {
|
||||
offset += currentNode.getText().length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
text: text,
|
||||
offset: offset
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms the `start` and `end` offsets in the text generated by the {@link #getTextAndOffset}
|
||||
* method into a DOM range.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* * `{}` is the range position in the text node (it means that the text node is **not** split at that position).
|
||||
* * `.` is a separator for text nodes (it means that the text node is split at that position).
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* Input: <p>f{}oo.bar</p>, 0, 3
|
||||
* Result: <p>{foo}.bar</p>
|
||||
*
|
||||
* Input: <p>f{}oo.bar</p>, 1, 5
|
||||
* Result: <p>f{oo.ba}r</p>
|
||||
* ```
|
||||
*
|
||||
* @member CKEDITOR.plugins.textMatch
|
||||
* @param {CKEDITOR.dom.range} range
|
||||
* @param {Number} start A start offset.
|
||||
* @param {Number} end An end offset.
|
||||
* @returns {CKEDITOR.dom.range} Transformed range.
|
||||
*/
|
||||
CKEDITOR.plugins.textMatch.getRangeInText = function( range, start, end ) {
|
||||
var resultRange = new CKEDITOR.dom.range( range.root ),
|
||||
elements = CKEDITOR.plugins.textMatch.getAdjacentTextNodes( range ),
|
||||
startData = findElementAtOffset( elements, start ),
|
||||
endData = findElementAtOffset( elements, end );
|
||||
|
||||
resultRange.setStart( startData.element, startData.offset );
|
||||
resultRange.setEnd( endData.element, endData.offset );
|
||||
|
||||
return resultRange;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a collection of adjacent text nodes which are between DOM elements, starting from the given range.
|
||||
* This function works only for collapsed ranges.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* * `{}` is the range position in the text node (it means that the text node is **not** split at that position).
|
||||
* * `.` is a separator for text nodes (it means that the text node is split at that position).
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* Input: <p>he.llo{}</p>
|
||||
* Result: [ 'he', 'llo' ]
|
||||
*
|
||||
* Input: <p>{}he.ll<i>o</i></p>
|
||||
* Result: [ 'he', 'll' ]
|
||||
*
|
||||
* Input: <p>he{}<i>ll</i>o.</p>
|
||||
* Result: [ 'he' ]
|
||||
*
|
||||
* Input: <p>he<i>ll</i>{}o.my.friend</p>
|
||||
* Result: [ 'o', 'my', 'friend' ]
|
||||
* ```
|
||||
*
|
||||
* @member CKEDITOR.plugins.textMatch
|
||||
* @param {CKEDITOR.dom.range} range
|
||||
* @return {CKEDITOR.dom.text[]} An array of text nodes.
|
||||
*/
|
||||
CKEDITOR.plugins.textMatch.getAdjacentTextNodes = function( range ) {
|
||||
if ( !range.collapsed ) {
|
||||
throw new Error( 'Range must be collapsed.' ); // %REMOVE_LINE%
|
||||
// Reachable in prod mode.
|
||||
return []; // jshint ignore:line
|
||||
}
|
||||
|
||||
var collection = [],
|
||||
siblings,
|
||||
elementIndex,
|
||||
node, i;
|
||||
|
||||
if ( range.startContainer.type != CKEDITOR.NODE_ELEMENT ) {
|
||||
siblings = range.startContainer.getParent().getChildren();
|
||||
elementIndex = range.startContainer.getIndex();
|
||||
} else {
|
||||
siblings = range.startContainer.getChildren();
|
||||
elementIndex = range.startOffset;
|
||||
}
|
||||
|
||||
i = elementIndex;
|
||||
while ( node = siblings.getItem( --i ) ) {
|
||||
if ( node.type == CKEDITOR.NODE_TEXT ) {
|
||||
collection.unshift( node );
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i = elementIndex;
|
||||
while ( node = siblings.getItem( i++ ) ) {
|
||||
if ( node.type == CKEDITOR.NODE_TEXT ) {
|
||||
collection.push( node );
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return collection;
|
||||
};
|
||||
|
||||
function findElementAtOffset( elements, offset ) {
|
||||
var max = elements.length,
|
||||
currentOffset = 0;
|
||||
for ( var i = 0; i < max; i += 1 ) {
|
||||
var current = elements[ i ];
|
||||
if ( offset >= currentOffset && currentOffset + current.getText().length >= offset ) {
|
||||
return {
|
||||
element: current,
|
||||
offset: offset - currentOffset
|
||||
};
|
||||
}
|
||||
|
||||
currentOffset += current.getText().length;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function indexOf( arr, checker ) {
|
||||
for ( var i = 0; i < arr.length; i++ ) {
|
||||
if ( checker( arr[ i ] ) ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
} )();
|
|
@ -0,0 +1,295 @@
|
|||
/**
|
||||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
|
||||
CKEDITOR.plugins.add( 'textwatcher', {} );
|
||||
|
||||
/**
|
||||
* API exposed by the [Text Watcher](https://ckeditor.com/cke4/addon/textwatcher) plugin.
|
||||
*
|
||||
* Class implementing the text watcher — a base for features like
|
||||
* autocomplete. It fires the {@link #matched} and {@link #unmatched} events
|
||||
* based on changes in the text and the position of the caret in the editor.
|
||||
*
|
||||
* To check whether the text matches some criteria, the text watcher uses
|
||||
* a callback function which should return the matching text and a {@link CKEDITOR.dom.range}
|
||||
* for that text.
|
||||
*
|
||||
* Since the text watcher works on the DOM where searching for text
|
||||
* is pretty complicated, it is usually recommended to use the {@link CKEDITOR.plugins.textMatch#match}
|
||||
* function.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* function textTestCallback( range ) {
|
||||
* // You do not want to autocomplete a non-empty selection.
|
||||
* if ( !range.collapsed ) {
|
||||
* return null;
|
||||
* }
|
||||
*
|
||||
* // Use the text match plugin which does the tricky job of doing
|
||||
* // a text search in the DOM. The matchCallback function should return
|
||||
* // a matching fragment of the text.
|
||||
* return CKEDITOR.plugins.textMatch.match( range, matchCallback );
|
||||
* }
|
||||
*
|
||||
* function matchCallback( text, offset ) {
|
||||
* // Get the text before the caret.
|
||||
* var left = text.slice( 0, offset ),
|
||||
* // Will look for an '@' character followed by word characters.
|
||||
* match = left.match( /@\w*$/ );
|
||||
*
|
||||
* if ( !match ) {
|
||||
* return null;
|
||||
* }
|
||||
* return { start: match.index, end: offset };
|
||||
* }
|
||||
*
|
||||
* // Initialize the text watcher.
|
||||
* var textWatcher = new CKEDITOR.plugins.textWatcher( editor, textTestCallback );
|
||||
* // Start listening.
|
||||
* textWatcher.attach();
|
||||
*
|
||||
* // Handle text matching.
|
||||
* textWatcher.on( 'matched', function( evt ) {
|
||||
* autocomplete.setQuery( evt.data.text );
|
||||
* } );
|
||||
* ```
|
||||
*
|
||||
* @class CKEDITOR.plugins.textWatcher
|
||||
* @since 4.10.0
|
||||
* @mixins CKEDITOR.event
|
||||
* @constructor Creates the text watcher instance.
|
||||
* @param {CKEDITOR.editor} editor The editor instance to watch in.
|
||||
* @param {Function} callback Callback executed when the text watcher
|
||||
* thinks that something might have changed.
|
||||
* @param {Number} [throttle=0] Throttle interval, see {@link #throttle}.
|
||||
* @param {CKEDITOR.dom.range} callback.range The range representing the caret position.
|
||||
* @param {Object} [callback.return=null] Matching text data (`null` if nothing matches).
|
||||
* @param {String} callback.return.text The matching text.
|
||||
* @param {CKEDITOR.dom.range} callback.return.range A range in the DOM for the text that matches.
|
||||
*/
|
||||
function TextWatcher( editor, callback, throttle ) {
|
||||
/**
|
||||
* The editor instance which the text watcher watches.
|
||||
*
|
||||
* @readonly
|
||||
* @property {CKEDITOR.editor}
|
||||
*/
|
||||
this.editor = editor;
|
||||
|
||||
/**
|
||||
* The last matched text.
|
||||
*
|
||||
* @readonly
|
||||
* @property {String}
|
||||
*/
|
||||
this.lastMatched = null;
|
||||
|
||||
/**
|
||||
* Whether the next check should be ignored. See the {@link #consumeNext} method.
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
this.ignoreNext = false;
|
||||
|
||||
/**
|
||||
* The callback passed to the {@link CKEDITOR.plugins.textWatcher} constructor.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Function}
|
||||
*/
|
||||
this.callback = callback;
|
||||
|
||||
/**
|
||||
* Keys that should be ignored by the {@link #check} method.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number[]}
|
||||
*/
|
||||
this.ignoredKeys = [
|
||||
16, // Shift
|
||||
17, // Ctrl
|
||||
18, // Alt
|
||||
91, // Cmd
|
||||
35, // End
|
||||
36, // Home
|
||||
37, // Left
|
||||
38, // Up
|
||||
39, // Right
|
||||
40, // Down
|
||||
33, // PageUp
|
||||
34 // PageUp
|
||||
];
|
||||
|
||||
/**
|
||||
* Listeners registered by this text watcher.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this._listeners = [];
|
||||
|
||||
/**
|
||||
* Indicates throttle threshold mitigating text checks.
|
||||
*
|
||||
* Higher levels of the throttle threshold will create a delay for text watcher checks
|
||||
* but also improve its performance.
|
||||
*
|
||||
* See the {@link CKEDITOR.tools#throttle throttle} feature for more information.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [throttle=0]
|
||||
*/
|
||||
this.throttle = throttle || 0;
|
||||
|
||||
/**
|
||||
* The {@link CKEDITOR.tools#throttle throttle buffer} used to mitigate text checks.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this._buffer = CKEDITOR.tools.throttle( this.throttle, testTextMatch, this );
|
||||
|
||||
/**
|
||||
* Event fired when the text is no longer matching.
|
||||
*
|
||||
* @event matched
|
||||
* @param {Object} data The value returned by the {@link #callback}.
|
||||
* @param {String} data.text
|
||||
* @param {CKEDITOR.dom.range} data.range
|
||||
*/
|
||||
|
||||
/**
|
||||
* Event fired when the text stops matching.
|
||||
*
|
||||
* @event unmatched
|
||||
*/
|
||||
|
||||
function testTextMatch( selectionRange ) {
|
||||
var matched = this.callback( selectionRange );
|
||||
|
||||
if ( matched ) {
|
||||
if ( matched.text == this.lastMatched ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastMatched = matched.text;
|
||||
this.fire( 'matched', matched );
|
||||
} else if ( this.lastMatched ) {
|
||||
this.unmatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextWatcher.prototype = {
|
||||
/**
|
||||
* Attaches the text watcher to the {@link #editor}.
|
||||
*
|
||||
* @chainable
|
||||
*/
|
||||
attach: function() {
|
||||
var editor = this.editor;
|
||||
|
||||
this._listeners.push( editor.on( 'contentDom', onContentDom, this ) );
|
||||
this._listeners.push( editor.on( 'blur', unmatch, this ) );
|
||||
this._listeners.push( editor.on( 'beforeModeUnload', unmatch, this ) );
|
||||
this._listeners.push( editor.on( 'setData', unmatch, this ) );
|
||||
this._listeners.push( editor.on( 'afterCommandExec', unmatch, this ) );
|
||||
|
||||
// Attach if editor is already initialized.
|
||||
if ( editor.editable() ) {
|
||||
onContentDom.call( this );
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
function onContentDom() {
|
||||
var editable = editor.editable();
|
||||
|
||||
this._listeners.push( editable.attachListener( editable, 'keyup', check, this ) );
|
||||
}
|
||||
|
||||
// CKEditor's event system has a limitation that one function (in this case this.check)
|
||||
// cannot be used as listener for the same event more than once. Hence, wrapper function.
|
||||
function check( evt ) {
|
||||
this.check( evt );
|
||||
}
|
||||
|
||||
function unmatch() {
|
||||
this.unmatch();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers a text check. Fires the {@link #matched} and {@link #unmatched} events.
|
||||
* The {@link #matched} event will not be fired twice in a row for the same text
|
||||
* unless the text watcher is {@link #unmatch reset}.
|
||||
*
|
||||
* @param {CKEDITOR.dom.event/CKEDITOR.eventInfo} [evt]
|
||||
*/
|
||||
check: function( evt ) {
|
||||
if ( this.ignoreNext ) {
|
||||
this.ignoreNext = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore control keys, so they don't trigger the check.
|
||||
if ( evt && evt.name == 'keyup' && ( CKEDITOR.tools.array.indexOf( this.ignoredKeys, evt.data.getKey() ) != -1 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sel = this.editor.getSelection();
|
||||
if ( !sel ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectionRange = sel.getRanges()[ 0 ];
|
||||
if ( !selectionRange ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._buffer.input( selectionRange );
|
||||
},
|
||||
|
||||
/**
|
||||
* Ignores the next {@link #check}.
|
||||
*
|
||||
* @chainable
|
||||
*/
|
||||
consumeNext: function() {
|
||||
this.ignoreNext = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the state and fires the {@link #unmatched} event.
|
||||
*
|
||||
* @chainable
|
||||
*/
|
||||
unmatch: function() {
|
||||
this.lastMatched = null;
|
||||
this.fire( 'unmatched' );
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the text watcher instance. The DOM event listeners will be cleaned up.
|
||||
*/
|
||||
destroy: function() {
|
||||
CKEDITOR.tools.array.forEach( this._listeners, function( obj ) {
|
||||
obj.removeListener();
|
||||
} );
|
||||
this._listeners = [];
|
||||
}
|
||||
};
|
||||
|
||||
CKEDITOR.event.implementOn( TextWatcher.prototype );
|
||||
|
||||
CKEDITOR.plugins.textWatcher = TextWatcher;
|
||||
|
||||
} )();
|
|
@ -695,6 +695,10 @@ div.wiki ul.toc li {
|
|||
min-width: auto;
|
||||
padding-top: 1em;
|
||||
}
|
||||
#preview span.cke_widget_drag_handler_container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************/
|
||||
/* 11. DOCUMENTS */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue