Skip to content

Instantly share code, notes, and snippets.

@miracle2k
Created August 18, 2013 13:40
Show Gist options
  • Save miracle2k/6261717 to your computer and use it in GitHub Desktop.
Save miracle2k/6261717 to your computer and use it in GitHub Desktop.
CKEditor 3/4 plugin that adds a new "internal link" option to the Link dialog window. This is an adaption of Henri Medot's Drupal integration plugin (https://drupal.org/project/ckeditor_link) to a custom CMS system. I'm publishing it here because the original code includes no comments, which meant I had to figure out the CKEditor API and what th…
/**
* Add an "CMS internal link" option to the Link plugin.
*
* This was adapted from a similar plugin for Drupal (version 7.x-2.3):
* Written by Henri MEDOT <henri.medot[AT]absyx[DOT]fr>
* http://www.absyx.fr
* https://drupal.org/project/ckeditor_link
*
* Portions of code:
* Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.html or http://ckeditor.com/license
*/
(function($) {
// Get a CKEDITOR.dialog.contentDefinition object by its ID.
var getById = function(array, id, recurse) {
for (var i = 0, item; (item = array[i]); i++) {
if (item.id == id) return item;
if (recurse && item[recurse]) {
var retval = getById(item[recurse], id, recurse);
if (retval) return retval;
}
}
return null;
};
var resetInitValues = function(dialog) {
dialog.foreach(function(contentObj) {
contentObj.setInitValue && contentObj.setInitValue();
});
};
CKEDITOR.plugins.add('aitdlink', {
init: function(editor, pluginPath) {
// Hook us into the creation process of the link dialog
CKEDITOR.on('dialogDefinition', function(e) {
if ((e.editor != editor) || (e.data.name != 'link'))
return;
var definition = e.data.definition;
// When OK is pressed in the dialog. In some cases we need to
// post-process the link we are inserting.
definition.onOk = CKEDITOR.tools.override(definition.onOk, function(original) {
return function() {
var isNewLinkWithText = false;
// Test if there is no selection in the editor (that is, just
// a simple blinking cursor - a "collapsed" range), and we are
// not editing an existing link below the cursor (selectedElement),
// but are inserting a new one.
if ((this.getValueOf('info', 'linkType') == 'aitd') && !this._.selectedElement) {
var ranges = editor.getSelection().getRanges(true);
if ((ranges.length == 1) && ranges[0].collapsed) {
// This means CKEditor will not just add a href to an
// existing piece of text, but will also insert the link
// text itself, which will be the url.
isNewLinkWithText = true;
}
}
// Let the original link dialog insert the link into the text.
// We can't really customize this code, so we need to make our
// changes afterwards
original.call(this);
// If CKEditor must created a new link with "aitd://1234" as
// the text, which is tremendously unuseful, then we will
// replace that text with the title of the page linked to.
if (isNewLinkWithText) {
var value = getPageSpan(dialog).getAttribute('pageTitle');
if (value) {
CKEDITOR.plugins.link.getSelectedLink(editor).setText(value);
}
}
};
});
// Overrides linkType definition.
var infoTab = definition.getContents('info');
// Add a "internal link" option to the link type select box.
var linkTypeSelect = getById(infoTab.elements, 'linkType');
linkTypeSelect.items.unshift(['Interner Link', 'aitd']);
/* Get the DOM element to which we attach our runtime data (page
target of the link) as data attributes. Helper because it is
such a long call.
*/
var getPageSpan = function(dialog) {
var pageIdDisplay = dialog.getContentElement('info', 'aitdPage');
return pageIdDisplay.getElement().$;
};
/* Helper to set the currently selected target page (internal id
and visible link description).
*/
var setTargetPage = function(dialog, page, version) {
var span = getPageSpan(dialog);
if (page) {
span.setAttribute('pageId', page.getId());
if (version)
span.setAttribute('versionId', version.getId());
else
span.removeAttribute('versionId');
span.setAttribute('pageTitle', page.getLinktext());
var label = page.getPathString();
if (version)
label += ' ('+ version.getModified() +')';
jQuery(span).text(label);
}
else {
span.removeAttribute('pageId');
span.removeAttribute('versionId');
jQuery(span).text('');
}
};
// Add the UI that is shown when the user selects our new link type
// option from the select box.
infoTab.elements.push({
type: 'vbox',
id: 'aitdOptions',
children: [{
type: 'button',
label: 'Zielseite auswählen',
onClick: function() {
// Showing a Qooxdoo dialog here is problematic, because
// the CKEditor UI system is working on a completely different
// z-index plane than Qooxdoo; thus, a Qooxdoo window that
// we open here will be behind the CkEditor link dialog (AND
// its blocker).
//
// There are only two options solving this:
// 1) Trying to make CKEditor insert its dialog within
// the Qooxdoo hierarchy. This is probably hard.
// 2) The workaround we are using, which is hiding the
// CKEditor link dialog while we are showing the Qooxdoo
// link chooser.
jQuery('.cke_dialog_background_cover').hide();
var dialog = this.getDialog();
dialog._.element.setStyle('display', 'none');
editor.qooxdooDialog.showBrowsePageDialog(
// init values
getPageSpan(dialog).getAttribute('pageId'),
getPageSpan(dialog).getAttribute('versionId'),
// on page select
function(page, version) {
setTargetPage(dialog, page, version);
},
// on window close
function() {
// Show the CkEditor dialog again once Qooxdoo window is gone.
jQuery('.cke_dialog_background_cover').show();
dialog._.element.setStyle('display', 'block');
});
return true;
}
},
// This label indicates the currently selected page to the user.
// Since I am not aware of a better way "proper" way to attach
// such data to a CKEditor dialog instance, we also use this label
// to store the correctly selected target page as data attributes.
{
type: 'html',
id: 'aitdPage',
html: '<span style="white-space: normal"></span>', // disable nowrap
validate: function() {
var pageId = this.getElement().$.getAttribute('pageId');
if (!pageId) {
alert('Sie müssen eine Zielseite auswählen.');
return false;
}
}
}]
});
// When the user picks a new type in the select box:
// Show or hide our controls.
linkTypeSelect.onChange = CKEDITOR.tools.override(linkTypeSelect.onChange, function(original) {
return function() {
// Run the default logic (handles all other select items)
original.call(this);
// Fetch our UI that we've added to the dialog.
var dialog = this.getDialog();
var ourUIControls = dialog.getContentElement('info', 'aitdOptions')
.getElement().getParent().getParent();
// Handle our own link type option
if (this.getValue() == 'aitd') {
ourUIControls.show();
// Set the visible states for CkEditor's linkTarget and upload
// tabs for our option.
if (editor.config.linkShowTargetTab)
dialog.showPage('target');
var uploadTab = dialog.definition.getContents('upload');
if (uploadTab && !uploadTab.hidden)
dialog.hidePage('upload');
}
else {
ourUIControls.hide();
}
};
});
// When the type select box is initialized with a value. The
// original is a "setValue(data.type)" one-liner, so we can
// replace it in full.
linkTypeSelect.setup = function(data) {
// Make our option the default for all isn't an existing url.
if (!data.type || (data.type == 'url') && !data.url) {
data.type = 'aitd';
// Dialog instance is re-used, so reset selection
setTargetPage(this.getDialog(), null);
}
// If there is a url, but no protocol, that indicates to us we
// are dealing with an internal aitd:// url (the link dialog
// will only parse the protocols it supports, thus the protocol
// is empty, and the full url contained in data.url).
else if (data.url && !data.url.protocol && data.url.url) {
// Initialize dialog as internal link
data.type = 'aitd';
// Store the selected page, show it's title.
var parsed = data.url.url.match(/(?:aitd|internal):\/\/(\d+)(?:#(\d+))?/);
var pageId = parsed[1], versionId = parsed[2];
var page = editor.qooxdooDialog.getPage(pageId);
var version = editor.qooxdooDialog.getPageVersion(page, versionId);
setTargetPage(this.getDialog(), page, version);
// Do not make the "url link type" processing to jump into action
delete data.url;
}
this.setValue(data.type);
};
// When the type select box is supposed to save its value
linkTypeSelect.commit = function(data) {
data.type = this.getValue();
if (data.type == 'aitd') {
// We have a problem here. The the code where the CKEditor
// dialog constructs the link to be inserted cannot reasonably
// be patched by us. That code does a switch() on data.type,
// and will not be able to handle our custom value of 'aitd'.
//
// Our only option is to use the default 'url' type to have
// our link being properly created.
data.type = 'url';
// The "url" link type stores it's values in "data.url.url"
// and "data.url.protocol". However, we cannot set these
// fields directly, because this commit() is running before
// before the commit()s of the respective input fields the
// "url" link type uses - which would override our values.
//
// We thus need to set the text field values directly.
//
// The problem with THAT is that the "protocol" select field
// will not accept invalid values, we cannot set it to aitd://.
// We set it to empty instead, and include aitd:// in the url.
var dialog = this.getDialog();
var link = "aitd://" + getPageSpan(dialog).getAttribute('pageId');
var versionId = getPageSpan(dialog).getAttribute('versionId');
if (versionId)
link += '#' + versionId;
dialog.setValueOf('info', 'protocol', '');
dialog.setValueOf('info', 'url', link);
}
};
});
}
});
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment