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());
192 editor.on('doubleclick', function(evt) {
193 if (evt.data.element && !evt.data.element.isReadOnly() && evt.data.element.getName() === 'a'
194 && evt.data.element.hasAttribute('cross-reference')) {
195 editor.getSelection().selectElement(evt.data.element);
196 if (evt.data.element.hasAttribute('cross-anchor')) {
197 evt.data.dialog = anchorDialogCmdName;
198 } else if (evt.data.element.hasAttribute('cross-link')) {
199 evt.data.dialog = linkDialogCmdName;
206 var updateMenuItemName = 'updateCrossReferences';
207 var setAnchorMenuItemName = 'setCrossReferenceAnchor';
208 var setLinkMenuItemName = 'setCrossReferenceLink';
210 var getMenuState = function(element, alwaysAllowEditItems) {
212 items[updateMenuItemName] = CKEDITOR.TRISTATE_OFF;
213 if (alwaysAllowEditItems == true) {
214 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_OFF;
215 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_OFF;
217 if (element && element.getName() === 'a' && element.hasAttribute('cross-reference')) {
218 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_OFF;
219 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_OFF;
220 if (element.hasAttribute('cross-anchor')) {
221 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_ON;
222 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_DISABLED;
224 if (element.hasAttribute('cross-link')) {
225 items[setAnchorMenuItemName] = CKEDITOR.TRISTATE_DISABLED;
226 items[setLinkMenuItemName] = CKEDITOR.TRISTATE_ON;
231 if (editor.addMenuItem) {
232 editor.addMenuGroup('crossreferenceGroup');
233 editor.addMenuItem(updateMenuItemName, {
234 label : editor.lang.crossreference.updateCrossReferences,
235 command : updateCmdName,
236 icon: 'crossreference-update',
237 group : 'crossreferenceGroup'
239 editor.addMenuItem(setAnchorMenuItemName, {
240 label : editor.lang.crossreference.setCrossReferenceAnchor,
241 command : anchorDialogCmdName,
242 icon: 'crossreference-anchor',
243 group : 'crossreferenceGroup'
245 editor.addMenuItem(setLinkMenuItemName, {
246 label : editor.lang.crossreference.setCrossReferenceLink,
247 command : linkDialogCmdName,
248 icon: 'crossreference-link',
249 group : 'crossreferenceGroup'
252 if (editor.contextMenu) {
253 editor.contextMenu.addListener(function(element, selection) {
254 if (element.getName() === 'a' && element.hasAttribute('cross-reference')) {
255 selection.selectElement(element);
257 var state = getMenuState(element, false);
262 function getConfig() {
263 var defaultConfig = {
264 activeTypes: ['chapter', 'image', 'table', 'reference'],
265 overrideTypes: false,
268 defaultConfig.types.chapter = {
269 name: editor.lang.crossreference.chapter,
270 anchorTextTemplate: '${number}. ${name}.',
271 linkTextTemplate: '${number}',
275 increase: function(number) {
276 var n = parseInt(number);
280 anchorsProvider: 'default',
281 allowCreateAnchors: true,
284 defaultConfig.types.image = {
285 name: editor.lang.crossreference.figure,
286 anchorTextTemplate: editor.lang.crossreference.fig + ' ${number}. ${name}.',
287 linkTextTemplate: '${number}',
291 increase: function(number) {
292 var n = parseInt(number);
296 anchorsProvider: 'default',
297 allowCreateAnchors: true,
300 defaultConfig.types.table = {
301 name: editor.lang.crossreference.table,
302 anchorTextTemplate: editor.lang.crossreference.table + ' ${number}. ${name}.',
303 linkTextTemplate: '${number}',
307 increase: function(number) {
308 var n = parseInt(number);
312 anchorsProvider: 'default',
313 allowCreateAnchors: true,
316 defaultConfig.types.reference = {
317 name: editor.lang.crossreference.reference,
318 anchorTextTemplate: '[${number}] ${name}.',
319 linkTextTemplate: '[${number}]',
323 increase: function(number) {
324 var n = parseInt(number);
328 anchorsProvider: 'default',
329 allowCreateAnchors: true,
333 var config = CKEDITOR.tools.clone(defaultConfig);
334 if (editor.config.crossreference) {
335 config = CKEDITOR.tools.extend(config, editor.config.crossreference, true);
336 if (!config.overrideTypes) {
337 for (var typeName in defaultConfig.types) {
338 var type = defaultConfig.types[typeName];
339 if (!(typeName in config.types))
340 config.types[typeName] = type;
344 for (var typeName in config.types) {
345 var type = config.types[typeName];
346 type.type = typeName;
348 for (var typeName in config.types) {
349 if ($.inArray(typeName, config.activeTypes) == -1) {
350 delete config.types[typeName];
356 config.findAnchors = function(config, editor, type, callback) {
365 if (type.numeration && type.numeration.enabled)
366 number = type.numeration.firstNumber + '';
369 if (editor.mode == 'source')
370 html = $('<div>' + editor.getData() + '</div>');
372 html = $(editor.editable().$);
374 $('a[cross-reference="' + type.type + '"][cross-anchor]', html).each(function() {
375 var element = $(this);
377 type: element.attr('cross-reference'),
378 guid: element.attr('cross-guid'),
379 name: element.attr('cross-name'),
383 anchors.push(anchor);
384 if (type.numeration && type.numeration.enabled)
385 number = type.numeration.increase(number);
388 function postProcessAnchors(anchors) {
389 for(var i = 0; i < anchors.length; i++) {
390 var anchor = anchors[i];
392 if (anchor.type != type.type)
393 throw 'Incompatible type: ' + type.type;
395 var text = anchor.name;
396 if (type.anchorTextTemplate) {
397 text = config.formatText(type.anchorTextTemplate, anchor);
404 if (type.anchorsProvider && type.anchorsProvider !== 'default') {
405 type.anchorsProvider(postProcessAnchors, anchors, type, editor);
407 postProcessAnchors(anchors);
411 config.formatText = function(template, anchor) {
414 for (var propName in anchor) {
415 var propValue = anchor[propName];
416 var regexp = new RegExp('\\$\\{' + propName + '\\}', 'g');
418 text = text.replace(regexp, propValue);
420 text = text.replace(regexp, '');
423 if (anchor.level != null) {
425 for (var i = 0; i < anchor.level; i++)
426 shift += ' ';
428 text = text.replace(/\$\{levelShift\}/g, shift);
431 text = text.replace(/\s+/g, ' ');