Skip to content

Instantly share code, notes, and snippets.

@kevinchampion
Created June 20, 2014 01:51
Show Gist options
  • Save kevinchampion/4aa89eab9f4cedc7eb5d to your computer and use it in GitHub Desktop.
Save kevinchampion/4aa89eab9f4cedc7eb5d to your computer and use it in GitHub Desktop.
ckeditor angular directive
(function(angular, factory) {
if (typeof define === 'function' && define.amd) {
define(['angular', 'ckeditor'], function(angular) {
return factory(angular);
});
} else {
return factory(angular);
}
}(angular || null, function(angular) {
var app = angular.module('ngCkeditor', []);
var $defer, loaded = false;
app.run(['$q', '$timeout', function($q, $timeout) {
$defer = $q.defer();
if (angular.isUndefined(CKEDITOR)) {
throw new Error('CKEDITOR not found');
}
CKEDITOR.disableAutoInline = true;
function checkLoaded() {
if (CKEDITOR.status == 'loaded') {
loaded = true;
$defer.resolve();
} else {
checkLoaded();
}
}
CKEDITOR.on('loaded', checkLoaded);
$timeout(checkLoaded, 100);
}])
app.directive('ckeditor', ['$timeout', '$q', function ($timeout, $q) {
'use strict';
return {
restrict: 'AC',
require: ['ngModel', '^?form'],
scope: false,
link: function (scope, element, attrs, ctrls) {
var ngModel = ctrls[0];
var form = ctrls[1] || null;
var EMPTY_HTML = '<p></p>',
isTextarea = element[0].tagName.toLowerCase() == 'textarea',
data = [],
isReady = false;
if (!isTextarea) {
element.attr('contenteditable', true);
}
var onLoad = function () {
var options = {
toolbar: 'full',
toolbar_full: [
{ name: 'styles', items: [ 'FontSize', 'Bold', 'Italic', 'Underline', 'TextColor', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink', 'Image', 'HorizontalRule', '-', 'Sourcedialog' ] }
],
disableNativeSpellChecker: false,
width: '100%',
resize_enabled: false,
extraPlugins: 'sourcedialog',
skin: 'diobox',
sharedSpaces: { top: 'cke_container' },
removeDialogTabs: 'link:advanced;link:target;image:advanced',
// The location of an external file browser, that should be launched when "Browse Server" button is pressed.
filebrowserBrowseUrl: "/ckeditor/attachment_files",
// The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Flash dialog.
filebrowserFlashBrowseUrl: "/ckeditor/attachment_files",
// The location of a script that handles file uploads in the Flash dialog.
filebrowserFlashUploadUrl: "/ckeditor/attachment_files",
// The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Link tab of Image dialog.
filebrowserImageBrowseLinkUrl: "/ckeditor/pictures",
// The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Image dialog.
filebrowserImageBrowseUrl: "/ckeditor/pictures",
// The location of a script that handles file uploads in the Image dialog.
filebrowserImageUploadUrl: "/ckeditor/pictures",
// The location of a script that handles file uploads.
filebrowserUploadUrl: "/ckeditor/attachment_files",
// Rails CSRF token
filebrowserParams: function() {
var csrf_token, csrf_param, meta,
metas = document.getElementsByTagName('meta'),
params = new Object();
for ( var i = 0 ; i < metas.length ; i++ ){
meta = metas[i];
switch(meta.name) {
case "csrf-token":
csrf_token = meta.content;
break;
case "csrf-param":
csrf_param = meta.content;
break;
default:
continue;
}
}
if (csrf_param !== undefined && csrf_token !== undefined) {
params[csrf_param] = csrf_token;
}
return params;
},
addQueryString: function( url, params ) {
var queryString = [];
if ( !params ) {
return url;
} else {
for ( var i in params )
queryString.push( i + "=" + encodeURIComponent( params[ i ] ) );
}
return url + ( ( url.indexOf( "?" ) != -1 ) ? "&" : "?" ) + queryString.join( "&" );
}
};
options = angular.extend(options, scope[attrs.ckeditor]);
var instance = (isTextarea) ? CKEDITOR.replace(element[0], options) : CKEDITOR.inline(element[0], options),
configLoaderDef = $q.defer();
element.bind('$destroy', function () {
instance.destroy(
false //If the instance is replacing a DOM element, this parameter indicates whether or not to update the element with the instance contents.
);
});
var setModelData = function(setPristine) {
var data = instance.getData();
if (data == '') {
data = null;
}
$timeout(function () { // for key up event
(setPristine !== true || data != ngModel.$viewValue) && ngModel.$setViewValue(data);
(setPristine === true && form) && form.$setPristine();
}, 0);
}, onUpdateModelData = function(setPristine) {
if (!data.length) { return; }
var item = data.pop() || EMPTY_HTML;
isReady = false;
instance.setData(item, function () {
setModelData(setPristine);
isReady = true;
});
}, showContainer = function() {
$('#cke_container').show();
}, hideContainer = function() {
$('#cke_container').hide();
}
//instance.on('pasteState', setModelData);
instance.on('change', setModelData);
instance.on('blur', function() {
hideContainer();
setModelData();
});
instance.on('focus', showContainer);
//instance.on('key', setModelData); // for source view
instance.on('instanceReady', function() {
instance.setReadOnly(false);
scope.$broadcast("ckeditor.ready");
scope.$apply(function() {
onUpdateModelData(true);
});
instance.document.on("keyup", setModelData);
hideContainer();
});
instance.on('customConfigLoaded', function() {
configLoaderDef.resolve();
});
// Integrate Rails CSRF token into file upload dialogs (link, image, attachment and flash)
instance.on( 'dialogDefinition', function( ev ){
// Take the dialog name and its definition from the event data.
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
var content, upload;
if (CKEDITOR.tools.indexOf(['link', 'image', 'attachment', 'flash'], dialogName) > -1) {
content = (dialogDefinition.getContents('Upload') || dialogDefinition.getContents('upload'));
upload = (content == null ? null : content.get('upload'));
if (upload && upload.filebrowser && upload.filebrowser['params'] === undefined) {
upload.filebrowser['params'] = config.filebrowserParams();
upload.action = config.addQueryString(upload.action, upload.filebrowser['params']);
}
}
});
ngModel.$render = function() {
data.push(ngModel.$viewValue);
if (isReady) {
onUpdateModelData();
}
};
};
if (CKEDITOR.status == 'loaded') {
loaded = true;
}
if (loaded) {
onLoad();
} else {
$defer.promise.then(onLoad);
}
// TODO: Real placeholder.
// TODO: Compile diobox skin.
// TODO: Deal with sanitization.
// TODO: Deal with content actually saving to the model.
// TODO: Better image plugin with upload ability.
}
};
}]);
return app;
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment