1 CKEDITOR.plugins.add('crossreference', {
3 requires : 'dialog,notification',
4 icons : 'crossreference,crossreference-anchor,crossreference-link,crossreference-update',
6 init : function(editor) {
10 var config = getConfig();
11 editor.config.crossreference = config;
15 var anchorAllowedContent = 'a[!cross-reference,cross-anchor,cross-guid,cross-name,cross-number]{*}(cross-reference,cross-anchor)';
16 var anchorRequiredContent = 'a[cross-reference,cross-anchor]';
17 var linkAllowedContent = 'a[!cross-reference,cross-link,cross-guid,cross-name,cross-number]{*}(cross-reference,cross-link)';
18 var linkRequiredContent = 'a[cross-reference,cross-link]';
20 name: 'crossreference-anchor',
21 allowedContent: anchorAllowedContent,
22 requiredContent: anchorRequiredContent
25 name: 'crossreference-link',
26 allowedContent: linkAllowedContent,
27 requiredContent: linkRequiredContent
29 editor.ui.add('crossreference', CKEDITOR.UI_MENUBUTTON, {
30 label : editor.lang.crossreference.name,
37 var selectedElement = null;
39 var selection = editor.getSelection();
41 var element = selection.getStartElement();
43 element = element.getAscendant('a', true);
44 if (element && element.hasAttribute('cross-reference')) {
45 selectedElement = element;
49 var state = getMenuState(selectedElement, true);
56 var updateCmdName = 'update-crossreferences';
57 var anchorDialogCmdName = 'crossreference-anchor-dialog';
58 var linkDialogCmdName = 'crossreference-link-dialog';
60 CKEDITOR.dialog.add(anchorDialogCmdName, this.path + 'dialogs/crossreference-anchor.js');
61 CKEDITOR.dialog.add(linkDialogCmdName, this.path + 'dialogs/crossreference-link.js');
63 editor.addCommand(anchorDialogCmdName, new CKEDITOR.dialogCommand(anchorDialogCmdName, {
64 allowedContent: anchorAllowedContent,
65 requiredContent: anchorRequiredContent
67 editor.addCommand(linkDialogCmdName, new CKEDITOR.dialogCommand(linkDialogCmdName, {
68 allowedContent: linkAllowedContent,
69 requiredContent: linkRequiredContent
74 editor.addCommand(updateCmdName, {
76 contextSensitive: false,
83 exec: function(editor) {
84 editor.setReadOnly(true);
85 var notification = editor.showNotification(editor.lang.crossreference.updatingCrossReferences, 'progress', 0);
90 var processedTypesCount = 0;
91 for (var typeName in config.types) {
97 if (editor.mode == 'source')
98 html = $('<div>' + editor.getData() + '</div>');
100 html = $(editor.editable().$);
102 function finishCommand() {
103 editor.setReadOnly(false);
104 editor.fire('afterCommandExec', {
108 notification.update({
110 message: editor.lang.crossreference.updatedCrossReferences + linksCount,
115 if (typesCount == 0) {
120 for (var typeName in config.types) {
121 config.findAnchors(config, editor, config.types[typeName], function(anchors) {
122 notification.update({
123 progress: (1 / typesCount) * processedTypesCount
125 for (var i = 0; i < anchors.length; i++) {
126 var anchor = anchors[i];
127 var type = config.types[anchor.type];
129 notification.update({
130 progress: (1 / typesCount) * processedTypesCount + (1 / typesCount / anchors.length) * i
133 var aName = type.type + '-' + anchor.guid;
135 var anchorElement = $('a[cross-reference="' + type.type + '"][cross-anchor][cross-guid="' + anchor.guid + '"]', html);
136 if (anchorElement.length > 0) {
137 anchorElement.attr('cross-reference', type.type);
138 anchorElement.attr('cross-anchor', '');
139 anchorElement.attr('cross-guid', anchor.guid);
140 anchorElement.attr('cross-name', anchor.name);
141 anchorElement.attr('cross-number', anchor.number);
142 anchorElement.attr('name', aName);
143 if (!anchorElement.hasClass('cross-reference'))
144 anchorElement.addClass('cross-reference');
145 if (!anchorElement.hasClass('cross-anchor'))
146 anchorElement.addClass('cross-anchor');
148 anchorElement.removeAttr('cross-link');
149 anchorElement.removeClass('cross-link');
151 anchorElement.text(anchor.text);
154 $('a[cross-reference="' + type.type + '"][cross-link][cross-guid="' + anchor.guid + '"]', html).each(function() {
155 var linkElement = $(this);
157 linkElement.attr('cross-reference', type.type);
158 linkElement.attr('cross-link', '');
159 linkElement.attr('cross-guid', anchor.guid);
160 linkElement.attr('cross-name', anchor.name);
161 linkElement.attr('cross-number', anchor.number);
162 linkElement.attr('href', '#' + aName);
164 if (!linkElement.hasClass('cross-reference'))
165 linkElement.addClass('cross-reference');
166 if (!linkElement.hasClass('cross-link'))
167 linkElement.addClass('cross-link');
169 linkElement.removeAttr('cross-anchor');
170 linkElement.removeClass('cross-anchor');
172 var linkText = anchor.text;
173 if (type.linkTextTemplate)
174 linkText = config.formatText(type.linkTextTemplate, anchor);
175 linkElement.text(linkText);
176 linkElement.attr('title', anchor.text.replace(/ /g, ' ').trim());
181 processedTypesCount++;
182 if (processedTypesCount >= typesCount) {
184 if (editor.mode == 'source')
185 editor.setData(html.html());
195 editor.setKeystroke(CKEDITOR.CTRL + CKEDITOR.SHIFT + 65, anchorDialogCmdName);
196 editor.setKeystroke(CKEDITOR.CTRL + CKEDITOR.SHIFT + 76, linkDialogCmdName);
197 editor.setKeystroke(CKEDITOR.CTRL + CKEDITOR.ALT + 85, updateCmdName);
201 editor.on('doubleclick', function(evt) {
202 if (evt.data.element && !evt.data.element.isReadOnly() && evt.data.element.getName() === 'a'
203 && evt.data.element.hasAttribute('cross-reference')) {
204 editor.getSelection().selectElement(evt.data.element);
205 if (evt.data.element.hasAttribute('cross-anchor')) {
206 evt.data.dialog = anchorDialogCmdName;
207 } else if (evt.data.element.hasAttribute('cross-link')) {
208 evt.data.dialog = linkDialogCmdName;
215 var updateMenuItemName = 'updateCrossReferences';
216 var setAnchorMenuItemName = 'setCrossReferenceAnchor';
217 var setLinkMenuItemName = 'setCrossReferenceLink';
219 var getMenuState = function(element, alwaysAllowEditItems) {
221 items[updateMenuItemName] = CKEDITOR.TRISTATE_OFF;
222 if (alwaysAllowEditItems == true) {
223 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_OFF;
224 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_OFF;
226 if (element && element.getName() === 'a' && element.hasAttribute('cross-reference')) {
227 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_OFF;
228 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_OFF;
229 if (element.hasAttribute('cross-anchor')) {
230 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_ON;
231 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_DISABLED;
233 if (element.hasAttribute('cross-link')) {
234 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_DISABLED;
235 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_ON;
240 if (editor.addMenuItem) {
241 editor.addMenuGroup('crossreferenceGroup');
242 editor.addMenuItem(updateMenuItemName, {
243 label : editor.lang.crossreference.updateCrossReferences,
244 command : updateCmdName,
245 icon: 'crossreference-update',
246 group : 'crossreferenceGroup'
248 editor.addMenuItem(setAnchorMenuItemName, {
249 label : editor.lang.crossreference.setCrossReferenceAnchor,
250 command : anchorDialogCmdName,
251 icon: 'crossreference-anchor',
252 group : 'crossreferenceGroup'
254 editor.addMenuItem(setLinkMenuItemName, {
255 label : editor.lang.crossreference.setCrossReferenceLink,
256 command : linkDialogCmdName,
257 icon: 'crossreference-link',
258 group : 'crossreferenceGroup'
261 if (editor.contextMenu) {
262 editor.contextMenu.addListener(function(element, selection) {
263 if (element.getName() === 'a' && element.hasAttribute('cross-reference')) {
264 selection.selectElement(element);
266 var state = getMenuState(element, false);
271 function getConfig() {
272 var defaultConfig = {
273 activeTypes: ['chapter', 'image', 'table', 'reference'],
274 overrideTypes: false,
277 defaultConfig.types.chapter = {
278 name: editor.lang.crossreference.chapter,
279 anchorTextTemplate: '${number}. ${name}.',
280 linkTextTemplate: '${number}',
284 increase: function(number) {
285 var n = parseInt(number);
289 anchorsProvider: 'default',
290 allowCreateAnchors: true,
293 defaultConfig.types.image = {
294 name: editor.lang.crossreference.figure,
295 anchorTextTemplate: editor.lang.crossreference.fig + ' ${number}. ${name}.',
296 linkTextTemplate: '${number}',
300 increase: function(number) {
301 var n = parseInt(number);
305 anchorsProvider: 'default',
306 allowCreateAnchors: true,
309 defaultConfig.types.table = {
310 name: editor.lang.crossreference.table,
311 anchorTextTemplate: editor.lang.crossreference.table + ' ${number}. ${name}.',
312 linkTextTemplate: '${number}',
316 increase: function(number) {
317 var n = parseInt(number);
321 anchorsProvider: 'default',
322 allowCreateAnchors: true,
325 defaultConfig.types.reference = {
326 name: editor.lang.crossreference.reference,
327 anchorTextTemplate: '[${number}] ${name}.',
328 linkTextTemplate: '[${number}]',
332 increase: function(number) {
333 var n = parseInt(number);
337 anchorsProvider: 'default',
338 allowCreateAnchors: true,
342 var config = CKEDITOR.tools.clone(defaultConfig);
343 if (editor.config.crossreference) {
344 config = CKEDITOR.tools.extend(config, editor.config.crossreference, true);
345 if (!config.overrideTypes) {
346 for (var typeName in defaultConfig.types) {
347 var type = defaultConfig.types[typeName];
348 if (!(typeName in config.types))
349 config.types[typeName] = type;
353 for (var typeName in config.types) {
354 var type = config.types[typeName];
355 type.type = typeName;
357 for (var typeName in config.types) {
358 if ($.inArray(typeName, config.activeTypes) == -1) {
359 delete config.types[typeName];
365 config.findAnchors = function(config, editor, type, callback) {
374 if (type.numeration && type.numeration.enabled)
375 number = type.numeration.firstNumber + '';
378 if (editor.mode == 'source')
379 html = $('<div>' + editor.getData() + '</div>');
381 html = $(editor.editable().$);
383 $('a[cross-reference="' + type.type + '"][cross-anchor]', html).each(function() {
384 var element = $(this);
386 type: element.attr('cross-reference'),
387 guid: element.attr('cross-guid'),
388 name: element.attr('cross-name'),
392 anchors.push(anchor);
393 if (type.numeration && type.numeration.enabled)
394 number = type.numeration.increase(number);
397 function postProcessAnchors(anchors) {
398 for(var i = 0; i < anchors.length; i++) {
399 var anchor = anchors[i];
401 if (anchor.type != type.type)
402 throw 'Incompatible type: ' + type.type;
404 var text = anchor.name;
405 if (type.anchorTextTemplate) {
406 text = config.formatText(type.anchorTextTemplate, anchor);
413 if (type.anchorsProvider && type.anchorsProvider !== 'default') {
414 type.anchorsProvider(postProcessAnchors, anchors, type, editor);
416 postProcessAnchors(anchors);
420 config.formatText = function(template, anchor) {
423 for (var propName in anchor) {
424 var propValue = anchor[propName];
425 var regexp = new RegExp('\\$\\{' + propName + '\\}', 'g');
427 text = text.replace(regexp, propValue);
429 text = text.replace(regexp, '');
432 if (anchor.level != null) {
434 for (var i = 0; i < anchor.level; i++)
435 shift += ' ';
437 text = text.replace(/\$\{levelShift\}/g, shift);
440 text = text.replace(/\s+/g, ' ');