ckeditor/plugins/crossreference/plugin.js
changeset 0 44d330dccc59
child 2 467e24fbc60e
     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(/&nbsp;/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 += '&nbsp;&nbsp;';
   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 +});