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"];
|
return [[ud mailComposeMessageType] isEqualToString: @"html"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *) editorClass
|
|
||||||
{
|
|
||||||
return ([self isHTML]? @"ck-editor" : @"plain-text");
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *) itemPriorityText
|
- (NSString *) itemPriorityText
|
||||||
{
|
{
|
||||||
return [self labelForKey: [NSString stringWithFormat: @"%@", [item lowercaseString]]];
|
return [self labelForKey: [NSString stringWithFormat: @"%@", [item lowercaseString]]];
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
xmlns:label="OGo:label"
|
xmlns:label="OGo:label"
|
||||||
className="UIxPageFrame"
|
className="UIxPageFrame"
|
||||||
title="title"
|
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">
|
<script type="text/javascript">
|
||||||
var contactFolders = <var:string value="contactFolders.jsonRepresentation" const:escapeHTML="NO" />;
|
var contactFolders = <var:string value="contactFolders.jsonRepresentation" const:escapeHTML="NO" />;
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -264,18 +264,22 @@
|
||||||
|
|
||||||
<!-- MESSAGE CONTENT -->
|
<!-- MESSAGE CONTENT -->
|
||||||
<md-input-container class="md-block sg-mail-editor-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"
|
rows="9"
|
||||||
ck-locale="editor.localeCode"
|
|
||||||
ck-margin="16px"
|
|
||||||
ck-focus="editor.onHTMLFocus($event)"
|
|
||||||
ng-model="editor.message.editable.text"
|
ng-model="editor.message.editable.text"
|
||||||
ng-focus="editor.onTextFocus($event)"
|
ng-focus="editor.onTextFocus($event)"
|
||||||
md-autofocus="::!editor.isNew()"
|
md-autofocus="::!editor.isNew()"
|
||||||
md-no-resize="md-no-resize"
|
md-no-resize="md-no-resize"
|
||||||
md-no-autogrow="md-no-autogrow"
|
md-no-autogrow="md-no-autogrow"
|
||||||
sg-autogrow="true"
|
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-input-container>
|
||||||
</md-dialog-content>
|
</md-dialog-content>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
xmlns:label="OGo:label"
|
xmlns:label="OGo:label"
|
||||||
className="UIxPageFrame"
|
className="UIxPageFrame"
|
||||||
title="title"
|
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">
|
<script type="text/javascript">
|
||||||
var mailAccounts = <var:string value="mailAccounts" const:escapeHTML="NO" />;
|
var mailAccounts = <var:string value="mailAccounts" const:escapeHTML="NO" />;
|
||||||
var userNames = <var:string value="userNames" const:escapeHTML="NO" />;
|
var userNames = <var:string value="userNames" const:escapeHTML="NO" />;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
className="UIxPageFrame"
|
className="UIxPageFrame"
|
||||||
title="title"
|
title="title"
|
||||||
const:popup="YES"
|
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">
|
<main class="layout-fill" ng-controller="navController">
|
||||||
<div id="detailView" class="view-detail md-default-theme md-background md-bg md-hue-2"
|
<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"
|
<md-input-container class="md-block md-flex"
|
||||||
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'text'">
|
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'text'">
|
||||||
<label><var:string label:value="Signature"/></label>
|
<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>
|
</md-input-container>
|
||||||
<div class="pseudo-input-container"
|
<div class="pseudo-input-container"
|
||||||
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'html'">
|
ng-if="$AccountDialogController.defaults.SOGoMailComposeMessageType == 'html'">
|
||||||
<label class="pseudo-input-label"><var:string label:value="Signature"/></label>
|
<label class="pseudo-input-label"><var:string label:value="Signature"/></label>
|
||||||
<textarea class="ck-editor"
|
<sg-ckeditor
|
||||||
ck-locale="$AccountDialogController.defaults.LocaleCode"
|
config="$AccountDialogController.ckConfig"
|
||||||
ck-options="{ 'autoGrow_minHeight': 70,
|
|
||||||
'toolbar': [['Bold', 'Italic', '-', 'Link',
|
|
||||||
'Font','FontSize','-','TextColor',
|
|
||||||
'BGColor', 'Source']] }"
|
|
||||||
ck-margin="8px"
|
ck-margin="8px"
|
||||||
ng-model="$AccountDialogController.account.identities[0].signature"><!-- signature --></textarea>
|
ng-model="$AccountDialogController.account.identities[0].signature"><!-- HTML editor --></sg-ckeditor>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<md-input-container class="md-block md-input-has-value">
|
<md-input-container class="md-block md-input-has-value">
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
xmlns:label="OGo:label"
|
xmlns:label="OGo:label"
|
||||||
className="UIxPageFrame"
|
className="UIxPageFrame"
|
||||||
title="title"
|
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"
|
<main layout-fill="layout-fill" ui-view="preferences"
|
||||||
ng-controller="navController"><!-- preferences --> </main>
|
ng-controller="navController"><!-- preferences --> </main>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
xmlns:label="OGo:label"
|
xmlns:label="OGo:label"
|
||||||
className="UIxPageFrame"
|
className="UIxPageFrame"
|
||||||
title="title"
|
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">
|
<script type="text/javascript">
|
||||||
var firstDayOfWeek = <var:string value="firstDayOfWeek"/>;
|
var firstDayOfWeek = <var:string value="firstDayOfWeek"/>;
|
||||||
var dayStartHour = <var:string value="dayStartHour"/>;
|
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() {
|
(function() {
|
||||||
'use strict';
|
'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)
|
.config(configure)
|
||||||
.run(runBlock);
|
.run(runBlock);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'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)
|
.config(configure)
|
||||||
.run(runBlock);
|
.run(runBlock);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'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)
|
.config(configure)
|
||||||
.run(runBlock)
|
.run(runBlock)
|
||||||
.controller('MessageEditorControllerPopup', MessageEditorControllerPopup);
|
.controller('MessageEditorControllerPopup', MessageEditorControllerPopup);
|
||||||
|
|
|
@ -40,8 +40,12 @@
|
||||||
if (Preferences.defaults.SOGoMailAutoSave)
|
if (Preferences.defaults.SOGoMailAutoSave)
|
||||||
// Enable auto-save of draft
|
// Enable auto-save of draft
|
||||||
this.autosave = $timeout(this.autosaveDrafts, Preferences.defaults.SOGoMailAutoSave*1000*60);
|
this.autosave = $timeout(this.autosaveDrafts, Preferences.defaults.SOGoMailAutoSave*1000*60);
|
||||||
|
|
||||||
// Set the locale of CKEditor
|
// Set the locale of CKEditor
|
||||||
this.localeCode = Preferences.defaults.LocaleCode;
|
this.localeCode = Preferences.defaults.LocaleCode;
|
||||||
|
this.ckConfig = { language: Preferences.defaults.LocaleCode };
|
||||||
|
|
||||||
|
this.composeType = Preferences.defaults.SOGoMailComposeMessageType;
|
||||||
|
|
||||||
this.replyPlacement = Preferences.defaults.SOGoMailReplyPlacement;
|
this.replyPlacement = Preferences.defaults.SOGoMailReplyPlacement;
|
||||||
if (this.message.origin && this.message.origin.action == 'forward') {
|
if (this.message.origin && this.message.origin.action == 'forward') {
|
||||||
|
@ -415,14 +419,21 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onHTMLFocus = function ($event) {
|
this.onHTMLReady = function ($editor) {
|
||||||
var caretAtTop = (this.replyPlacement == 'above');
|
if (!this.isNew()) {
|
||||||
|
onCompletePromise().then(function() {
|
||||||
|
$editor.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onHTMLFocus = function (editor) {
|
||||||
if (this.firstFocus) {
|
if (this.firstFocus) {
|
||||||
onCompletePromise().then(function(element) {
|
onCompletePromise().then(function(element) {
|
||||||
var selected = $event.editor.getSelection(),
|
var caretAtTop = (vm.replyPlacement == 'above'),
|
||||||
|
selected = editor.getSelection(),
|
||||||
selected_ranges = selected.getRanges(),
|
selected_ranges = selected.getRanges(),
|
||||||
children = $event.editor.document.getBody().getChildren(),
|
children = editor.document.getBody().getChildren(),
|
||||||
node;
|
node;
|
||||||
|
|
||||||
if (caretAtTop) {
|
if (caretAtTop) {
|
||||||
|
|
|
@ -23,6 +23,13 @@
|
||||||
vm.cancel = cancel;
|
vm.cancel = cancel;
|
||||||
vm.save = save;
|
vm.save = save;
|
||||||
vm.hostnameRE = accountId > 0 ? /^(?!(127\.0\.0\.1|localhost(?:\.localdomain)?)$)/ : /./;
|
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)
|
if (!vm.account.encryption)
|
||||||
vm.account.encryption = "none";
|
vm.account.encryption = "none";
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'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)
|
.config(configure)
|
||||||
.run(runBlock);
|
.run(runBlock);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'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)
|
.config(configure)
|
||||||
.run(runBlock);
|
.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