refactor(mail(js)): replace ckEditor directive by sgCkeditor component
This refactoring 1. simplifies updating the CKEditor source code; 2. allows many instances of the CKEditor on the same page; 3. fixes the cursor positioning on focus.pull/277/head
parent
9f861bd629
commit
07c06db69d
|
@ -203,11 +203,6 @@ static NSArray *infoKeys = nil;
|
|||
return [[ud mailComposeMessageType] isEqualToString: @"html"];
|
||||
}
|
||||
|
||||
- (NSString *) editorClass
|
||||
{
|
||||
return ([self isHTML]? @"ck-editor" : @"plain-text");
|
||||
}
|
||||
|
||||
- (NSString *) itemPriorityText
|
||||
{
|
||||
return [self labelForKey: [NSString stringWithFormat: @"%@", [item lowercaseString]]];
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, Common.js, Preferences.services.js, Mailer.services.js, Contacts.js, Contacts.services.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js">
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, Common/sgCkeditor.component.js, Common.js, Preferences.services.js, Mailer.services.js, Contacts.js, Contacts.services.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js">
|
||||
<script type="text/javascript">
|
||||
var contactFolders = <var:string value="contactFolders.jsonRepresentation" const:escapeHTML="NO" />;
|
||||
</script>
|
||||
|
|
|
@ -264,18 +264,22 @@
|
|||
|
||||
<!-- MESSAGE CONTENT -->
|
||||
<md-input-container class="md-block sg-mail-editor-content">
|
||||
<textarea name="content" var:class="editorClass"
|
||||
<textarea name="content" class="plain-text"
|
||||
ng-if="editor.composeType == 'text'"
|
||||
rows="9"
|
||||
ck-locale="editor.localeCode"
|
||||
ck-margin="16px"
|
||||
ck-focus="editor.onHTMLFocus($event)"
|
||||
ng-model="editor.message.editable.text"
|
||||
ng-focus="editor.onTextFocus($event)"
|
||||
md-autofocus="::!editor.isNew()"
|
||||
md-no-resize="md-no-resize"
|
||||
md-no-autogrow="md-no-autogrow"
|
||||
sg-autogrow="true"
|
||||
md-detect-hidden="md-detect-hidden" />
|
||||
md-detect-hidden="md-detect-hidden"><!-- plain text editor --></textarea>
|
||||
<sg-ckeditor id="message-content"
|
||||
ng-if="editor.composeType == 'html'"
|
||||
config="editor.ckConfig"
|
||||
on-instance-ready="editor.onHTMLReady($editor)"
|
||||
on-focus="editor.onHTMLFocus($editor)"
|
||||
ng-model="editor.message.editable.text"><!-- HTML editor --></sg-ckeditor>
|
||||
</md-input-container>
|
||||
</md-dialog-content>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, Common.js, Preferences.services.js, Contacts.services.js, Scheduler.services.js, Mailer.js, Mailer.services.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js">
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, Common/sgCkeditor.component.js, Common.js, Preferences.services.js, Contacts.services.js, Scheduler.services.js, Mailer.js, Mailer.services.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js">
|
||||
<script type="text/javascript">
|
||||
var mailAccounts = <var:string value="mailAccounts" const:escapeHTML="NO" />;
|
||||
var userNames = <var:string value="userNames" const:escapeHTML="NO" />;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:popup="YES"
|
||||
const:jsFiles="Common.js, Preferences.services.js, Contacts.services.js, Scheduler.services.js, Mailer.app.popup.js, Mailer.services.js, vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js">
|
||||
const:jsFiles="Common.js, Preferences.services.js, Contacts.services.js, Scheduler.services.js, Mailer.app.popup.js, Mailer.services.js, vendor/ckeditor/ckeditor.js, Common/sgCkeditor.component.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js">
|
||||
|
||||
<main class="layout-fill" ng-controller="navController">
|
||||
<div id="detailView" class="view-detail md-default-theme md-background md-bg md-hue-2"
|
||||
|
|
|
@ -122,19 +122,16 @@
|
|||
<md-input-container class="md-block md-flex"
|
||||
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'text'">
|
||||
<label><var:string label:value="Signature"/></label>
|
||||
<textarea ng-model="$AccountDialogController.account.identities[0].signature"><!-- signature --></textarea>
|
||||
<textarea
|
||||
ng-model="$AccountDialogController.account.identities[0].signature"><!-- plain text editor --></textarea>
|
||||
</md-input-container>
|
||||
<div class="pseudo-input-container"
|
||||
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'html'">
|
||||
<label class="pseudo-input-label"><var:string label:value="Signature"/></label>
|
||||
<textarea class="ck-editor"
|
||||
ck-locale="$AccountDialogController.defaults.LocaleCode"
|
||||
ck-options="{ 'autoGrow_minHeight': 70,
|
||||
'toolbar': [['Bold', 'Italic', '-', 'Link',
|
||||
'Font','FontSize','-','TextColor',
|
||||
'BGColor', 'Source']] }"
|
||||
ck-margin="8px"
|
||||
ng-model="$AccountDialogController.account.identities[0].signature"><!-- signature --></textarea>
|
||||
<sg-ckeditor
|
||||
config="$AccountDialogController.ckConfig"
|
||||
ck-margin="8px"
|
||||
ng-model="$AccountDialogController.account.identities[0].signature"><!-- HTML editor --></sg-ckeditor>
|
||||
</div>
|
||||
|
||||
<md-input-container class="md-block md-input-has-value">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/angular-file-upload.min.js, vendor/ng-sortable.min.js, vendor/qrcode.min.js, Common.js, Preferences.js, Preferences.services.js, Mailer.services.js, Contacts.services.js">
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, Common/sgCkeditor.component.js, vendor/angular-file-upload.min.js, vendor/ng-sortable.min.js, vendor/qrcode.min.js, Common.js, Preferences.js, Preferences.services.js, Mailer.services.js, Contacts.services.js">
|
||||
|
||||
<main layout-fill="layout-fill" ui-view="preferences"
|
||||
ng-controller="navController"><!-- preferences --> </main>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/ng-sortable.min.js, Common.js, Preferences.services.js, Contacts.services.js, Mailer.services.js, vendor/angular-file-upload.min.js, Scheduler.js, Scheduler.services.js, vendor/FileSaver.min.js">
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ng-sortable.min.js, Common/sgCkeditor.component.js, Common.js, Preferences.services.js, Contacts.services.js, Mailer.services.js, vendor/angular-file-upload.min.js, Scheduler.js, Scheduler.services.js, vendor/FileSaver.min.js">
|
||||
<script type="text/javascript">
|
||||
var firstDayOfWeek = <var:string value="firstDayOfWeek"/>;
|
||||
var dayStartHour = <var:string value="dayStartHour"/>;
|
||||
|
|
|
@ -0,0 +1,414 @@
|
|||
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* sgCkeditor - A component for the CKEditor v4
|
||||
* Based on https://github.com/jziggas/ng-ck/.
|
||||
* @memberof SOGo.Common
|
||||
* @example:
|
||||
<sg-ckeditor
|
||||
config="$ctrl.config"
|
||||
on-instance-ready="$ctrl.onEditorReady($editor)"
|
||||
on-focus="$ctrl.onEditorFocus($editor)"
|
||||
ng-model="$ctrl.content"></sg-ckeditor>
|
||||
*/
|
||||
function sgCkeditorConfigProvider() {
|
||||
// Default plugins that have successfully passed through Angular's $sanitize service
|
||||
var defaultConfiguration = {
|
||||
toolbarGroups: [
|
||||
{ name: 'basicstyles', groups: [ 'basicstyles' ] },
|
||||
{ name: 'colors' },
|
||||
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align' ] },
|
||||
{ name: 'links' },
|
||||
{ name: 'insert' },
|
||||
{ name: 'editing', groups: [ 'spellchecker' ] },
|
||||
{ name: 'styles' },
|
||||
{ name: 'mode' }
|
||||
],
|
||||
|
||||
// The default plugins included in the basic setup define some buttons that
|
||||
// are not needed in a basic editor. They are removed here.
|
||||
removeButtons: 'Strike,Subscript,Superscript,BGColor,Anchor,Format,Image',
|
||||
|
||||
// Dialog windows are also simplified.
|
||||
removeDialogTabs: 'link:advanced',
|
||||
|
||||
enterMode: CKEDITOR.ENTER_BR,
|
||||
tabSpaces: 4,
|
||||
// fullPage: true, include header and body
|
||||
allowedContent: true, // don't filter tags
|
||||
entities: false,
|
||||
|
||||
// Configure autogrow
|
||||
// https://ckeditor.com/docs/ckeditor4/latest/guide/dev_autogrow.html
|
||||
autoGrow_onStartup: true,
|
||||
autoGrow_minHeight: 300,
|
||||
autoGrow_bottomSpace: 0,
|
||||
language: 'en',
|
||||
|
||||
// The Upload Image plugin requires a remote URL to be defined even though we won't use it
|
||||
imageUploadUrl: '/SOGo/'
|
||||
};
|
||||
|
||||
var events = [
|
||||
'activeEnterModeChange',
|
||||
'activeFilterChange',
|
||||
'afterCommandExec',
|
||||
'afterInsertHtml',
|
||||
'afterPaste',
|
||||
'afterPasteFromWord',
|
||||
'afterSetData',
|
||||
'afterUndoImage',
|
||||
'ariaEditorHelpLabel',
|
||||
'autogrow',
|
||||
'beforeCommandExec',
|
||||
'beforeDestroy',
|
||||
'beforeGetData',
|
||||
'beforeModeUnload',
|
||||
'beforeSetMode',
|
||||
'beforeUndoImage',
|
||||
'blur',
|
||||
'change',
|
||||
'configLoaded',
|
||||
'contentDirLoaded',
|
||||
'contentDom',
|
||||
'contentDomInvalidated',
|
||||
'contentDomUnload',
|
||||
'customConfigLoaded',
|
||||
'dataFiltered',
|
||||
'dataReady',
|
||||
'destroy',
|
||||
'dialogHide',
|
||||
'dialogShow',
|
||||
'dirChanged',
|
||||
'doubleclick',
|
||||
'dragend',
|
||||
'dragstart',
|
||||
'drop',
|
||||
'elementsPathUpdate',
|
||||
'fileUploadRequest',
|
||||
'fileUploadResponse',
|
||||
'floatingSpaceLayout',
|
||||
'focus',
|
||||
'getData',
|
||||
'getSnapshot',
|
||||
'insertElement',
|
||||
'insertHtml',
|
||||
'insertText',
|
||||
'instanceReady',
|
||||
'key',
|
||||
'langLoaded',
|
||||
'loadSnapshot',
|
||||
'loaded',
|
||||
'lockSnapshot',
|
||||
'maximize',
|
||||
'menuShow',
|
||||
'mode',
|
||||
'notificationHide',
|
||||
'notificationShow',
|
||||
'notificationUpdate',
|
||||
'paste',
|
||||
'pasteFromWord',
|
||||
'pluginsLoaded',
|
||||
'readOnly',
|
||||
'removeFormatCleanup',
|
||||
'required',
|
||||
'resize',
|
||||
'save',
|
||||
'saveSnapshot',
|
||||
'selectionChange',
|
||||
'setData',
|
||||
'stylesSet',
|
||||
'template',
|
||||
'toDataFormat',
|
||||
'toHtml',
|
||||
'unlockSnapshot',
|
||||
'updateSnapshot',
|
||||
'widgetDefinition'
|
||||
];
|
||||
|
||||
var config = angular.copy(defaultConfiguration);
|
||||
|
||||
this.$get = function () {
|
||||
return {
|
||||
config: config,
|
||||
events: events
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var sgCkeditorComponent = {
|
||||
controllerAs: 'vm',
|
||||
require: {
|
||||
ngModelCtrl: 'ngModel'
|
||||
},
|
||||
bindings: {
|
||||
checkTextLength: '<?',
|
||||
config: '<?',
|
||||
maxLength: '<?',
|
||||
minLength: '<?',
|
||||
ckMargin: '@?',
|
||||
onActiveEnterModeChange: '&?',
|
||||
onActiveFilterChange: '&?',
|
||||
onAfterCommandExec: '&?',
|
||||
onAfterInsertHtml: '&?',
|
||||
onAfterPaste: '&?',
|
||||
onAfterPasteFromWord: '&?',
|
||||
onAfterSetData: '&?',
|
||||
onAfterUndoImage: '&?',
|
||||
onAriaEditorHelpLabel: '&?',
|
||||
onAutogrow: '&?',
|
||||
onBeforeCommandExec: '&?',
|
||||
onBeforeDestroy: '&?',
|
||||
onBeforeGetData: '&?',
|
||||
onBeforeModeUnload: '&?',
|
||||
onBeforeSetMode: '&?',
|
||||
onBeforeUndoImage: '&?',
|
||||
onBlur: '&?',
|
||||
onChange: '&?',
|
||||
onConfigLoaded: '&?',
|
||||
onContentChanged: '&?', // Not CKEditor API
|
||||
onContentDirLoaded: '&?',
|
||||
onContentDom: '&?',
|
||||
onContentDomInvalidated: '&?',
|
||||
onContentDomUnload: '&?',
|
||||
onCustomConfigLoaded: '&?',
|
||||
onDataFiltered: '&?',
|
||||
onDataReady: '&?',
|
||||
onDestroy: '&?', // Not sure if this works because of the cleanup done in $onDestroy. Needs testing.
|
||||
onDialogHide: '&?',
|
||||
onDialogShow: '&?',
|
||||
onDirChanged: '&?',
|
||||
onDoubleclick: '&?',
|
||||
onDragend: '&?',
|
||||
onDragstart: '&?',
|
||||
onDrop: '&?',
|
||||
onElementsPathUpdate: '&?',
|
||||
onFileUploadRequest: '&?',
|
||||
onFileUploadResponse: '&?',
|
||||
onFloatingSpaceLayout: '&?',
|
||||
onFocus: '&?',
|
||||
onGetData: '&?',
|
||||
onGetSnapshot: '&?',
|
||||
onInsertElement: '&?',
|
||||
onInsertHtml: '&?',
|
||||
onInsertText: '&?',
|
||||
onInstanceReady: '&?',
|
||||
onKey: '&?',
|
||||
onLangLoaded: '&?',
|
||||
onLoadSnapshot: '&?',
|
||||
onLoaded: '&?',
|
||||
onLockSnapshot: '&?',
|
||||
onMaximize: '&?',
|
||||
onMenuShow: '&?',
|
||||
onMode: '&?',
|
||||
onNotificationHide: '&?',
|
||||
onNotificationShow: '&?',
|
||||
onNotificationUpdate: '&?',
|
||||
onPaste: '&?',
|
||||
onPasteFromWord: '&?',
|
||||
onPluginsLoaded: '&?',
|
||||
onReadOnly: '&?',
|
||||
onRemoveFormatCleanup: '&?',
|
||||
onRequired: '&?',
|
||||
onResize: '&?',
|
||||
onSave: '&?',
|
||||
onSaveSnapshot: '&?',
|
||||
onSelectionChange: '&?',
|
||||
onSetData: '&?',
|
||||
onStylesSet: '&?',
|
||||
onTemplate: '&?',
|
||||
onToDataFormat: '&?',
|
||||
onToHtml: '&?',
|
||||
onUnlockSnapshot: '&?',
|
||||
onUpdateSnapshot: '&?',
|
||||
onWidgetDefinition: '&?',
|
||||
placeholder: '<?',
|
||||
readOnly: '<?',
|
||||
required: '<?'
|
||||
},
|
||||
template: '<textarea ng-attr-placeholder="{{vm.placeholder}}"></textarea>',
|
||||
controller: sgCkeditorController
|
||||
};
|
||||
|
||||
sgCkeditorController.$inject = ['$element', '$scope', '$parse', '$timeout', 'sgCkeditorConfig'];
|
||||
function sgCkeditorController ($element, $scope, $parse, $timeout, sgCkeditorConfig) {
|
||||
var vm = this;
|
||||
var config;
|
||||
var content;
|
||||
var editor;
|
||||
var editorElement;
|
||||
var editorChanged = false;
|
||||
var modelChanged = false;
|
||||
|
||||
this.$onInit = function () {
|
||||
vm.ngModelCtrl.$render = function () {
|
||||
if (editor) {
|
||||
editor.setData(vm.ngModelCtrl.$viewValue, {
|
||||
noSnapshot: true,
|
||||
callback: function () {
|
||||
editor.fire('updateSnapshot')
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
config = vm.config ? angular.merge(sgCkeditorConfig.config, vm.config) : sgCkeditorConfig.config;
|
||||
|
||||
if (config.language) {
|
||||
// Pickup the first matching language supported by SCAYT
|
||||
// See http://docs.ckeditor.com/#!/guide/dev_howtos_scayt
|
||||
config.scayt_sLang = _.find(['en_US', 'en_GB', 'pt_BR', 'da_DK', 'nl_NL', 'en_CA', 'fi_FI', 'fr_FR', 'fr_CA', 'de_DE', 'el_GR', 'it_IT', 'nb_NO', 'pt_PT', 'es_ES', 'sv_SE'], function(sLang) {
|
||||
return sLang.lastIndexOf(config.language, 0) == 0;
|
||||
}) || 'en_US';
|
||||
|
||||
// Disable caching of the language
|
||||
// See https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/126
|
||||
config.scayt_disableOptionsStorage = 'lang';
|
||||
}
|
||||
|
||||
if (vm.ckMargin) {
|
||||
// Set the margin of the iframe editable content
|
||||
CKEDITOR.addCss('.cke_editable { margin-top: ' + vm.ckMargin +
|
||||
'; margin-left: ' + vm.ckMargin +
|
||||
'; margin-right: ' + vm.ckMargin + '; }');
|
||||
}
|
||||
};
|
||||
|
||||
this.$postLink = function () {
|
||||
editorElement = $element[0].children[0];
|
||||
editor = CKEDITOR.replace(editorElement, config);
|
||||
|
||||
editor.on('instanceReady', onInstanceReady);
|
||||
editor.on('pasteState', onEditorChange);
|
||||
editor.on('change', onEditorChange);
|
||||
editor.on('paste', onEditorPaste);
|
||||
editor.on('fileUploadRequest', onEditorFileUploadRequest);
|
||||
|
||||
if (content) {
|
||||
modelChanged = true
|
||||
editor.setData(content, {
|
||||
noSnapshot: true,
|
||||
callback: function () {
|
||||
editor.fire('updateSnapshot')
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.$onChanges = function (changes) {
|
||||
if (
|
||||
changes.ngModel &&
|
||||
changes.ngModel.currentValue !== changes.ngModel.previousValue
|
||||
) {
|
||||
content = changes.ngModel.currentValue;
|
||||
if (editor && !editorChanged) {
|
||||
if (content) {
|
||||
editor.setData(content, {
|
||||
noSnapshot: true,
|
||||
callback: function () {
|
||||
editor.fire('updateSnapshot')
|
||||
}
|
||||
});
|
||||
modelChanged = true;
|
||||
}
|
||||
}
|
||||
editorChanged = false;
|
||||
}
|
||||
if (editor && changes.readOnly) {
|
||||
editor.setReadOnly(changes.readOnly.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
this.$onDestroy = function () {
|
||||
editor.destroy();
|
||||
}
|
||||
|
||||
function onInstanceReady (event) {
|
||||
// Register binded callbacks for all available events
|
||||
_.forEach(_.filter(sgCkeditorConfig.events, function (eventName) {
|
||||
return eventName != 'instanceReady';
|
||||
}), function (eventName) {
|
||||
var callbackName = 'on' + eventName[0].toUpperCase() + eventName.slice(1);
|
||||
if (vm[callbackName]) {
|
||||
editor.on(eventName, function (event) {
|
||||
vm[callbackName]({
|
||||
'$event': event,
|
||||
'$editor': editor
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (vm.onInstanceReady) {
|
||||
vm.onInstanceReady({
|
||||
'$event': event,
|
||||
'$editor': editor
|
||||
});
|
||||
}
|
||||
|
||||
// vm.ngModelCtrl.$render();
|
||||
}
|
||||
|
||||
function onEditorChange () {
|
||||
var html = editor.getData();
|
||||
var text = editor.document.getBody().getText();
|
||||
|
||||
if (text === '\n') {
|
||||
text = '';
|
||||
}
|
||||
|
||||
if (!modelChanged && html !== vm.ngModelCtrl.$viewValue) {
|
||||
editorChanged = true;
|
||||
vm.ngModelCtrl.$setViewValue(html);
|
||||
validate(vm.checkTextLength ? text : html);
|
||||
if (vm.onContentChanged) {
|
||||
vm.onContentChanged({
|
||||
'editor': editor,
|
||||
'html': html,
|
||||
'text': text
|
||||
});
|
||||
}
|
||||
}
|
||||
modelChanged = false;
|
||||
}
|
||||
|
||||
function onEditorPaste (event) {
|
||||
var html;
|
||||
if (event.data.type == 'html') {
|
||||
html = event.data.dataValue;
|
||||
// Remove images to avoid ghost image in Firefox; images will be handled by the Image Upload plugin
|
||||
event.data.dataValue = html.replace(/<img( [^>]*)?>/gi, '');
|
||||
}
|
||||
}
|
||||
|
||||
function onEditorFileUploadRequest (event) {
|
||||
// Intercept the request when an image is pasted, keep an inline base64 version only.
|
||||
var data, img;
|
||||
data = event.data.fileLoader.data;
|
||||
img = editor.document.createElement('img');
|
||||
img.setAttribute('src', data);
|
||||
editor.insertElement(img);
|
||||
event.cancel();
|
||||
}
|
||||
|
||||
function validate (body) {
|
||||
if (vm.maxLength) {
|
||||
vm.ngModelCtrl.$setValidity('maxlength', body.length > vm.maxLength + 1);
|
||||
}
|
||||
if (vm.minLength) {
|
||||
vm.ngModelCtrl.$setValidity('minlength', body.length <= vm.minLength);
|
||||
}
|
||||
if (vm.required) {
|
||||
vm.ngModelCtrl.$setValidity('required', body.length > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
.module('sgCkeditor', [])
|
||||
.provider('sgCkeditorConfig', sgCkeditorConfigProvider)
|
||||
.component('sgCkeditor', sgCkeditorComponent);
|
||||
})();
|
|
@ -4,7 +4,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.ContactsUI', ['ngCookies', 'ui.router', 'angularFileUpload', 'ck', 'SOGo.Common', 'SOGo.PreferencesUI', 'SOGo.MailerUI'])
|
||||
angular.module('SOGo.ContactsUI', ['ngCookies', 'ui.router', 'angularFileUpload', 'sgCkeditor', 'SOGo.Common', 'SOGo.PreferencesUI', 'SOGo.MailerUI'])
|
||||
.config(configure)
|
||||
.run(runBlock);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.MailerUI', ['ngCookies', 'ui.router', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'SOGo.SchedulerUI', 'ngAnimate', 'SOGo.PreferencesUI'])
|
||||
angular.module('SOGo.MailerUI', ['ngCookies', 'ui.router', 'sgCkeditor', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'SOGo.SchedulerUI', 'ngAnimate', 'SOGo.PreferencesUI'])
|
||||
.config(configure)
|
||||
.run(runBlock);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.MailerUI', ['ngCookies', 'ui.router', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'SOGo.SchedulerUI', 'ngAnimate', 'SOGo.PreferencesUI'])
|
||||
angular.module('SOGo.MailerUI', ['ngCookies', 'ui.router', 'sgCkeditor', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'SOGo.SchedulerUI', 'ngAnimate', 'SOGo.PreferencesUI'])
|
||||
.config(configure)
|
||||
.run(runBlock)
|
||||
.controller('MessageEditorControllerPopup', MessageEditorControllerPopup);
|
||||
|
|
|
@ -40,8 +40,12 @@
|
|||
if (Preferences.defaults.SOGoMailAutoSave)
|
||||
// Enable auto-save of draft
|
||||
this.autosave = $timeout(this.autosaveDrafts, Preferences.defaults.SOGoMailAutoSave*1000*60);
|
||||
|
||||
// Set the locale of CKEditor
|
||||
this.localeCode = Preferences.defaults.LocaleCode;
|
||||
this.ckConfig = { language: Preferences.defaults.LocaleCode };
|
||||
|
||||
this.composeType = Preferences.defaults.SOGoMailComposeMessageType;
|
||||
|
||||
this.replyPlacement = Preferences.defaults.SOGoMailReplyPlacement;
|
||||
if (this.message.origin && this.message.origin.action == 'forward') {
|
||||
|
@ -415,14 +419,21 @@
|
|||
}
|
||||
};
|
||||
|
||||
this.onHTMLFocus = function ($event) {
|
||||
var caretAtTop = (this.replyPlacement == 'above');
|
||||
this.onHTMLReady = function ($editor) {
|
||||
if (!this.isNew()) {
|
||||
onCompletePromise().then(function() {
|
||||
$editor.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.onHTMLFocus = function (editor) {
|
||||
if (this.firstFocus) {
|
||||
onCompletePromise().then(function(element) {
|
||||
var selected = $event.editor.getSelection(),
|
||||
var caretAtTop = (vm.replyPlacement == 'above'),
|
||||
selected = editor.getSelection(),
|
||||
selected_ranges = selected.getRanges(),
|
||||
children = $event.editor.document.getBody().getChildren(),
|
||||
children = editor.document.getBody().getChildren(),
|
||||
node;
|
||||
|
||||
if (caretAtTop) {
|
||||
|
|
|
@ -23,6 +23,13 @@
|
|||
vm.cancel = cancel;
|
||||
vm.save = save;
|
||||
vm.hostnameRE = accountId > 0 ? /^(?!(127\.0\.0\.1|localhost(?:\.localdomain)?)$)/ : /./;
|
||||
vm.ckConfig = {
|
||||
'autoGrow_minHeight': 70,
|
||||
'toolbar': [['Bold', 'Italic', '-', 'Link',
|
||||
'Font','FontSize','-','TextColor',
|
||||
'BGColor', 'Source']],
|
||||
language: defaults.LocaleCode
|
||||
};
|
||||
|
||||
if (!vm.account.encryption)
|
||||
vm.account.encryption = "none";
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.PreferencesUI', ['ui.router', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.MailerUI', 'SOGo.ContactsUI', 'SOGo.Authentication', 'as.sortable'])
|
||||
angular.module('SOGo.PreferencesUI', ['ui.router', 'sgCkeditor', 'angularFileUpload', 'SOGo.Common', 'SOGo.MailerUI', 'SOGo.ContactsUI', 'SOGo.Authentication', 'as.sortable'])
|
||||
.config(configure)
|
||||
.run(runBlock);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.SchedulerUI', ['ngCookies', 'ui.router', 'angularFileUpload', 'ck', 'SOGo.Common', 'SOGo.PreferencesUI', 'SOGo.ContactsUI', 'SOGo.MailerUI', 'as.sortable'])
|
||||
angular.module('SOGo.SchedulerUI', ['ngCookies', 'ui.router', 'angularFileUpload', 'sgCkeditor', 'SOGo.Common', 'SOGo.PreferencesUI', 'SOGo.ContactsUI', 'SOGo.MailerUI', 'as.sortable'])
|
||||
.config(configure)
|
||||
.run(runBlock);
|
||||
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* JavaScript for CKEditor module */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
ckEditor.$inject = ['$parse'];
|
||||
function ckEditor($parse) {
|
||||
var calledEarly, loaded;
|
||||
loaded = false;
|
||||
calledEarly = false;
|
||||
|
||||
return {
|
||||
restrict: 'C',
|
||||
require: '?ngModel',
|
||||
compile: function(element, attributes, transclude) {
|
||||
var loadIt, local;
|
||||
|
||||
local = this;
|
||||
loadIt = function() {
|
||||
return calledEarly = true;
|
||||
};
|
||||
|
||||
element.ready(function() {
|
||||
return loadIt();
|
||||
});
|
||||
|
||||
return {
|
||||
post: function($scope, element, attributes, controller) {
|
||||
if (calledEarly) {
|
||||
return local.link($scope, element, attributes, controller);
|
||||
}
|
||||
loadIt = (function($scope, element, attributes, controller) {
|
||||
return function() {
|
||||
local.link($scope, element, attributes, controller);
|
||||
};
|
||||
})($scope, element, attributes, controller);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
link: function($scope, elm, attr, ngModel) {
|
||||
var ck, options = {}, locale, margin;
|
||||
if (!ngModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (calledEarly && !loaded) {
|
||||
return loaded = true;
|
||||
}
|
||||
loaded = false;
|
||||
|
||||
if (attr.ckOptions)
|
||||
options = angular.fromJson(attr.ckOptions.replace(/'/g, "\""));
|
||||
|
||||
if (attr.ckLocale) {
|
||||
locale = $parse(attr.ckLocale)($scope);
|
||||
options.language = locale;
|
||||
|
||||
// Pickup the first matching language supported by SCAYT
|
||||
// See http://docs.ckeditor.com/#!/guide/dev_howtos_scayt
|
||||
options.scayt_sLang = _.find(['en_US', 'en_GB', 'pt_BR', 'da_DK', 'nl_NL', 'en_CA', 'fi_FI', 'fr_FR', 'fr_CA', 'de_DE', 'el_GR', 'it_IT', 'nb_NO', 'pt_PT', 'es_ES', 'sv_SE'], function(sLang) {
|
||||
return sLang.lastIndexOf(locale, 0) == 0;
|
||||
}) || 'en_US';
|
||||
|
||||
// Disable caching of the language
|
||||
// See https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/126
|
||||
options.scayt_disableOptionsStorage = 'lang';
|
||||
}
|
||||
|
||||
if (attr.ckMargin) {
|
||||
// Set the margin of the iframe editable content
|
||||
margin = attr.ckMargin;
|
||||
CKEDITOR.addCss('.cke_editable { margin-top: ' + margin +
|
||||
'; margin-left: ' + margin +
|
||||
'; margin-right: ' + margin + '; }');
|
||||
}
|
||||
|
||||
// The Upload Image plugin requires a remote URL to be defined even though we won't use it
|
||||
options.imageUploadUrl = '/SOGo/';
|
||||
|
||||
ck = CKEDITOR.replace(elm[0], options);
|
||||
|
||||
if (attr.mdAutofocus && $parse(attr.mdAutofocus)($scope)) {
|
||||
// Autofocus is enabled
|
||||
ck.on('instanceReady', function(event) {
|
||||
ck.focus();
|
||||
});
|
||||
ck.on('focus', function(event) {
|
||||
if (attr.ckFocus) {
|
||||
$parse(attr.ckFocus)($scope, {'$event': event});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update the model whenever the content changes
|
||||
ck.on('change', function() {
|
||||
$scope.$apply(function() {
|
||||
ngModel.$setViewValue(ck.getData());
|
||||
});
|
||||
});
|
||||
|
||||
ck.on('paste', function(event) {
|
||||
var html;
|
||||
if (event.data.type == 'html') {
|
||||
html = event.data.dataValue;
|
||||
// Remove images to avoid ghost image in Firefox; images will be handled by the Image Upload plugin
|
||||
event.data.dataValue = html.replace(/<img( [^>]*)?>/gi, '');
|
||||
}
|
||||
});
|
||||
|
||||
// Intercept the request when an image is pasted, keep an inline base64 version only.
|
||||
ck.on('fileUploadRequest', function(event) {
|
||||
var data, img;
|
||||
data = event.data.fileLoader.data;
|
||||
img = ck.document.createElement('img');
|
||||
img.setAttribute('src', data);
|
||||
ck.insertElement(img);
|
||||
event.cancel();
|
||||
});
|
||||
|
||||
ngModel.$render = function(value) {
|
||||
ck.setData(ngModel.$viewValue);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular
|
||||
.module('ck', [])
|
||||
.directive('ckEditor', ckEditor);
|
||||
})();
|
Loading…
Reference in New Issue