1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/ckeditor/plugins/crossreference/plugin.js Thu Dec 15 18:10:20 2016 +0300
1.3 @@ -0,0 +1,439 @@
1.4 +CKEDITOR.plugins.add('crossreference', {
1.5 + lang : [ 'en', 'ru' ],
1.6 + requires : 'dialog,notification',
1.7 + icons : 'crossreference',
1.8 + hidpi : true,
1.9 + init : function(editor) {
1.10 +
1.11 + // config
1.12 +
1.13 + var config = getConfig();
1.14 + editor.config.crossreference = config;
1.15 +
1.16 + // plugin
1.17 +
1.18 + var anchorAllowedContent = 'a[!cross-reference,cross-anchor,cross-guid,cross-name,cross-number]{*}(cross-reference,cross-anchor)';
1.19 + var anchorRequiredContent = 'a[cross-reference,cross-anchor]';
1.20 + var linkAllowedContent = 'a[!cross-reference,cross-link,cross-guid,cross-name,cross-number]{*}(cross-reference,cross-link)';
1.21 + var linkRequiredContent = 'a[cross-reference,cross-link]';
1.22 + editor.addFeature({
1.23 + name: 'crossreference-anchor',
1.24 + allowedContent: anchorAllowedContent,
1.25 + requiredContent: anchorRequiredContent
1.26 + });
1.27 + editor.addFeature({
1.28 + name: 'crossreference-link',
1.29 + allowedContent: linkAllowedContent,
1.30 + requiredContent: linkRequiredContent
1.31 + });
1.32 + editor.ui.add('crossreference', CKEDITOR.UI_MENUBUTTON, {
1.33 + label : editor.lang.crossreference.name,
1.34 + modes: {
1.35 + wysiwyg: 1,
1.36 + source: 1
1.37 + },
1.38 + toolbar : 'insert',
1.39 + onMenu: function() {
1.40 + var selectedElement = null;
1.41 +
1.42 + var selection = editor.getSelection();
1.43 + if (selection) {
1.44 + var element = selection.getStartElement();
1.45 + if (element)
1.46 + element = element.getAscendant('a', true);
1.47 + if (element && element.hasAttribute('cross-reference')) {
1.48 + selectedElement = element;
1.49 + }
1.50 + }
1.51 +
1.52 + var state = getMenuState(selectedElement, true);
1.53 + return state;
1.54 + }
1.55 + });
1.56 +
1.57 + // dialogs
1.58 +
1.59 + var updateCmdName = 'update-crossreferences';
1.60 + var anchorDialogCmdName = 'crossreference-anchor-dialog';
1.61 + var linkDialogCmdName = 'crossreference-link-dialog';
1.62 +
1.63 + CKEDITOR.dialog.add(anchorDialogCmdName, this.path + 'dialogs/crossreference-anchor.js');
1.64 + CKEDITOR.dialog.add(linkDialogCmdName, this.path + 'dialogs/crossreference-link.js');
1.65 +
1.66 + editor.addCommand(anchorDialogCmdName, new CKEDITOR.dialogCommand(anchorDialogCmdName, {
1.67 + allowedContent: anchorAllowedContent,
1.68 + requiredContent: anchorRequiredContent
1.69 + }));
1.70 + editor.addCommand(linkDialogCmdName, new CKEDITOR.dialogCommand(linkDialogCmdName, {
1.71 + allowedContent: linkAllowedContent,
1.72 + requiredContent: linkRequiredContent
1.73 + }));
1.74 +
1.75 + // commands
1.76 +
1.77 + editor.addCommand(updateCmdName, {
1.78 + async: true,
1.79 + contextSensitive: false,
1.80 + editorFocus: false,
1.81 + modes: {
1.82 + wysiwyg: 1,
1.83 + source: 1
1.84 + },
1.85 + readOnly: true,
1.86 + exec: function(editor) {
1.87 + editor.setReadOnly(true);
1.88 + var notification = editor.showNotification(editor.lang.crossreference.updatingCrossReferences, 'progress', 0);
1.89 +
1.90 + var cmd = this;
1.91 +
1.92 + var typesCount = 0;
1.93 + var processedTypesCount = 0;
1.94 + for (var typeName in config.types) {
1.95 + typesCount++;
1.96 + }
1.97 + var linksCount = 0;
1.98 +
1.99 + var html = null;
1.100 + if (editor.mode == 'source')
1.101 + html = $('<div>' + editor.getData() + '</div>');
1.102 + else
1.103 + html = $(editor.editable().$);
1.104 +
1.105 + function finishCommand() {
1.106 + editor.setReadOnly(false);
1.107 + editor.fire('afterCommandExec', {
1.108 + name: updateCmdName,
1.109 + command: cmd
1.110 + });
1.111 + notification.update({
1.112 + type: 'success',
1.113 + message: editor.lang.crossreference.updatedCrossReferences + linksCount,
1.114 + important: true
1.115 + });
1.116 + }
1.117 +
1.118 + if (typesCount == 0) {
1.119 + finishCommand();
1.120 + return;
1.121 + }
1.122 +
1.123 + for (var typeName in config.types) {
1.124 + config.findAnchors(config, editor, config.types[typeName], function(anchors) {
1.125 + notification.update({
1.126 + progress: (1 / typesCount) * processedTypesCount
1.127 + });
1.128 + for (var i = 0; i < anchors.length; i++) {
1.129 + var anchor = anchors[i];
1.130 + var type = config.types[anchor.type];
1.131 +
1.132 + notification.update({
1.133 + progress: (1 / typesCount) * processedTypesCount + (1 / typesCount / anchors.length) * i
1.134 + });
1.135 +
1.136 + var aName = type.type + '-' + anchor.guid;
1.137 +
1.138 + var anchorElement = $('a[cross-reference="' + type.type + '"][cross-anchor][cross-guid="' + anchor.guid + '"]', html);
1.139 + if (anchorElement.length > 0) {
1.140 + anchorElement.attr('cross-reference', type.type);
1.141 + anchorElement.attr('cross-anchor', '');
1.142 + anchorElement.attr('cross-guid', anchor.guid);
1.143 + anchorElement.attr('cross-name', anchor.name);
1.144 + anchorElement.attr('cross-number', anchor.number);
1.145 + anchorElement.attr('name', aName);
1.146 + if (!anchorElement.hasClass('cross-reference'))
1.147 + anchorElement.addClass('cross-reference');
1.148 + if (!anchorElement.hasClass('cross-anchor'))
1.149 + anchorElement.addClass('cross-anchor');
1.150 +
1.151 + anchorElement.removeAttr('cross-link');
1.152 + anchorElement.removeClass('cross-link');
1.153 +
1.154 + anchorElement.text(anchor.text);
1.155 + }
1.156 +
1.157 + $('a[cross-reference="' + type.type + '"][cross-link][cross-guid="' + anchor.guid + '"]', html).each(function() {
1.158 + var linkElement = $(this);
1.159 +
1.160 + linkElement.attr('cross-reference', type.type);
1.161 + linkElement.attr('cross-link', '');
1.162 + linkElement.attr('cross-guid', anchor.guid);
1.163 + linkElement.attr('cross-name', anchor.name);
1.164 + linkElement.attr('cross-number', anchor.number);
1.165 + linkElement.attr('href', '#' + aName);
1.166 +
1.167 + if (!linkElement.hasClass('cross-reference'))
1.168 + linkElement.addClass('cross-reference');
1.169 + if (!linkElement.hasClass('cross-link'))
1.170 + linkElement.addClass('cross-link');
1.171 +
1.172 + linkElement.removeAttr('cross-anchor');
1.173 + linkElement.removeClass('cross-anchor');
1.174 +
1.175 + var linkText = anchor.text;
1.176 + if (type.linkTextTemplate)
1.177 + linkText = config.formatText(type.linkTextTemplate, anchor);
1.178 + linkElement.text(linkText);
1.179 + linkElement.attr('title', anchor.text.replace(/ /g, ' ').trim());
1.180 +
1.181 + linksCount++;
1.182 + });
1.183 + }
1.184 + processedTypesCount++;
1.185 + if (processedTypesCount >= typesCount) {
1.186 + // done
1.187 + if (editor.mode == 'source')
1.188 + editor.setData(html.html());
1.189 + finishCommand();
1.190 + }
1.191 + });
1.192 + }
1.193 + }
1.194 + });
1.195 + editor.on('doubleclick', function(evt) {
1.196 + if (evt.data.element && !evt.data.element.isReadOnly() && evt.data.element.getName() === 'a'
1.197 + && evt.data.element.hasAttribute('cross-reference')) {
1.198 + editor.getSelection().selectElement(evt.data.element);
1.199 + if (evt.data.element.hasAttribute('cross-anchor')) {
1.200 + evt.data.dialog = anchorDialogCmdName;
1.201 + } else if (evt.data.element.hasAttribute('cross-link')) {
1.202 + evt.data.dialog = linkDialogCmdName;
1.203 + }
1.204 + }
1.205 + });
1.206 +
1.207 + // menu
1.208 +
1.209 + var updateMenuItemName = 'updateCrossReferences';
1.210 + var setAnchorMenuItemName = 'setCrossReferenceAnchor';
1.211 + var setLinkMenuItemName = 'setCrossReferenceLink';
1.212 +
1.213 + var getMenuState = function(element, alwaysAllowEditItems) {
1.214 + var items = {};
1.215 + items[updateMenuItemName] = CKEDITOR.TRISTATE_OFF;
1.216 + if (alwaysAllowEditItems == true) {
1.217 + items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_OFF;
1.218 + items[setLinkMenuItemName] = CKEDITOR.TRISTATE_OFF;
1.219 + }
1.220 + if (element && element.getName() === 'a' && element.hasAttribute('cross-reference')) {
1.221 + items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_OFF;
1.222 + items[setLinkMenuItemName] = CKEDITOR.TRISTATE_OFF;
1.223 + if (element.hasAttribute('cross-anchor')) {
1.224 + items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_ON;
1.225 + items[setLinkMenuItemName] = CKEDITOR.TRISTATE_DISABLED;
1.226 + }
1.227 + if (element.hasAttribute('cross-link')) {
1.228 + items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_DISABLED;
1.229 + items[setLinkMenuItemName] = CKEDITOR.TRISTATE_ON;
1.230 + }
1.231 + }
1.232 + return items;
1.233 + }
1.234 + if (editor.addMenuItem) {
1.235 + editor.addMenuGroup('crossreferenceGroup');
1.236 + editor.addMenuItem(updateMenuItemName, {
1.237 + label : editor.lang.crossreference.updateCrossReferences,
1.238 + command : updateCmdName,
1.239 + icon: this.path + 'icons/update.png',
1.240 + group : 'crossreferenceGroup'
1.241 + });
1.242 + editor.addMenuItem(setAnchorMenuItemName, {
1.243 + label : editor.lang.crossreference.setCrossReferenceAnchor,
1.244 + command : anchorDialogCmdName,
1.245 + icon: this.path + 'icons/anchor.png',
1.246 + group : 'crossreferenceGroup'
1.247 + });
1.248 + editor.addMenuItem(setLinkMenuItemName, {
1.249 + label : editor.lang.crossreference.setCrossReferenceLink,
1.250 + command : linkDialogCmdName,
1.251 + icon: this.path + 'icons/link.png',
1.252 + group : 'crossreferenceGroup'
1.253 + });
1.254 + }
1.255 + if (editor.contextMenu) {
1.256 + editor.contextMenu.addListener(function(element, selection) {
1.257 + if (element.getName() === 'a' && element.hasAttribute('cross-reference')) {
1.258 + selection.selectElement(element);
1.259 + }
1.260 + var state = getMenuState(element, false);
1.261 + return state;
1.262 + });
1.263 + }
1.264 +
1.265 + function getConfig() {
1.266 + var defaultConfig = {
1.267 + activeTypes: ['chapter', 'image', 'table', 'reference'],
1.268 + overrideTypes: false,
1.269 + types: {}
1.270 + };
1.271 + defaultConfig.types.chapter = {
1.272 + name: editor.lang.crossreference.chapter,
1.273 + anchorTextTemplate: '${number}. ${name}.',
1.274 + linkTextTemplate: '${number}',
1.275 + numeration: {
1.276 + enabled: true,
1.277 + firstNumber: '1',
1.278 + increase: function(number) {
1.279 + var n = parseInt(number);
1.280 + return ++n;
1.281 + }
1.282 + },
1.283 + anchorsProvider: 'default',
1.284 + allowCreateAnchors: true,
1.285 + groupAnchors: false
1.286 + };
1.287 + defaultConfig.types.image = {
1.288 + name: editor.lang.crossreference.figure,
1.289 + anchorTextTemplate: editor.lang.crossreference.fig + ' ${number}. ${name}.',
1.290 + linkTextTemplate: '${number}',
1.291 + numeration: {
1.292 + enabled: true,
1.293 + firstNumber: '1',
1.294 + increase: function(number) {
1.295 + var n = parseInt(number);
1.296 + return ++n;
1.297 + }
1.298 + },
1.299 + anchorsProvider: 'default',
1.300 + allowCreateAnchors: true,
1.301 + groupAnchors: false
1.302 + };
1.303 + defaultConfig.types.table = {
1.304 + name: editor.lang.crossreference.table,
1.305 + anchorTextTemplate: editor.lang.crossreference.table + ' ${number}. ${name}.',
1.306 + linkTextTemplate: '${number}',
1.307 + numeration: {
1.308 + enabled: true,
1.309 + firstNumber: '1',
1.310 + increase: function(number) {
1.311 + var n = parseInt(number);
1.312 + return ++n;
1.313 + }
1.314 + },
1.315 + anchorsProvider: 'default',
1.316 + allowCreateAnchors: true,
1.317 + groupAnchors: false
1.318 + };
1.319 + defaultConfig.types.reference = {
1.320 + name: editor.lang.crossreference.reference,
1.321 + anchorTextTemplate: '[${number}] ${name}.',
1.322 + linkTextTemplate: '[${number}]',
1.323 + numeration: {
1.324 + enabled: true,
1.325 + firstNumber: '1',
1.326 + increase: function(number) {
1.327 + var n = parseInt(number);
1.328 + return ++n;
1.329 + }
1.330 + },
1.331 + anchorsProvider: 'default',
1.332 + allowCreateAnchors: true,
1.333 + groupAnchors: false
1.334 + };
1.335 +
1.336 + var config = CKEDITOR.tools.clone(defaultConfig);
1.337 + if (editor.config.crossreference) {
1.338 + config = CKEDITOR.tools.extend(config, editor.config.crossreference, true);
1.339 + if (!config.overrideTypes) {
1.340 + for (var typeName in defaultConfig.types) {
1.341 + var type = defaultConfig.types[typeName];
1.342 + if (!(typeName in config.types))
1.343 + config.types[typeName] = type;
1.344 + }
1.345 + }
1.346 + }
1.347 + for (var typeName in config.types) {
1.348 + var type = config.types[typeName];
1.349 + type.type = typeName;
1.350 + }
1.351 + for (var typeName in config.types) {
1.352 + if ($.inArray(typeName, config.activeTypes) == -1) {
1.353 + delete config.types[typeName];
1.354 + }
1.355 + }
1.356 +
1.357 + // shared methods
1.358 +
1.359 + config.findAnchors = function(config, editor, type, callback) {
1.360 + var anchors = [];
1.361 +
1.362 + if (type == null) {
1.363 + callback(anchors);
1.364 + return;
1.365 + }
1.366 +
1.367 + var number = null;
1.368 + if (type.numeration && type.numeration.enabled)
1.369 + number = type.numeration.firstNumber + '';
1.370 +
1.371 + var html = null;
1.372 + if (editor.mode == 'source')
1.373 + html = $('<div>' + editor.getData() + '</div>');
1.374 + else
1.375 + html = $(editor.editable().$);
1.376 +
1.377 + $('a[cross-reference="' + type.type + '"][cross-anchor]', html).each(function() {
1.378 + var element = $(this);
1.379 + var anchor = {
1.380 + type: element.attr('cross-reference'),
1.381 + guid: element.attr('cross-guid'),
1.382 + name: element.attr('cross-name'),
1.383 + number: number,
1.384 + text: element.text()
1.385 + }
1.386 + anchors.push(anchor);
1.387 + if (type.numeration && type.numeration.enabled)
1.388 + number = type.numeration.increase(number);
1.389 + });
1.390 +
1.391 + function postProcessAnchors(anchors) {
1.392 + for(var i = 0; i < anchors.length; i++) {
1.393 + var anchor = anchors[i];
1.394 +
1.395 + if (anchor.type != type.type)
1.396 + throw 'Incompatible type: ' + type.type;
1.397 +
1.398 + var text = anchor.name;
1.399 + if (type.anchorTextTemplate) {
1.400 + text = config.formatText(type.anchorTextTemplate, anchor);
1.401 + }
1.402 + anchor.text = text;
1.403 + }
1.404 + callback(anchors);
1.405 + }
1.406 +
1.407 + if (type.anchorsProvider && type.anchorsProvider !== 'default') {
1.408 + type.anchorsProvider(postProcessAnchors, anchors, type, editor);
1.409 + } else {
1.410 + postProcessAnchors(anchors);
1.411 + }
1.412 + };
1.413 +
1.414 + config.formatText = function(template, anchor) {
1.415 + var text = template;
1.416 +
1.417 + for (var propName in anchor) {
1.418 + var propValue = anchor[propName];
1.419 + var regexp = new RegExp('\\$\\{' + propName + '\\}', 'g');
1.420 + if (propValue)
1.421 + text = text.replace(regexp, propValue);
1.422 + else
1.423 + text = text.replace(regexp, '');
1.424 + }
1.425 +
1.426 + if (anchor.level != null) {
1.427 + var shift = '';
1.428 + for (var i = 0; i < anchor.level; i++)
1.429 + shift += ' ';
1.430 +
1.431 + text = text.replace(/\$\{levelShift\}/g, shift);
1.432 + }
1.433 +
1.434 + text = text.trim();
1.435 +
1.436 + return text;
1.437 + }
1.438 +
1.439 + return config;
1.440 + }
1.441 + }
1.442 +});