1210 lines
36 KiB
JavaScript
1210 lines
36 KiB
JavaScript
/*
|
|
* Axelor Business Solutions
|
|
*
|
|
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
(function() {
|
|
|
|
"use strict";
|
|
|
|
var ui = angular.module('axelor.ui');
|
|
|
|
ui.factory('MessageService', ['$q', '$timeout', 'DataSource', 'TagService', function($q, $timeout, DataSource, TagService) {
|
|
|
|
var dsFlags = DataSource.create('com.axelor.mail.db.MailFlags');
|
|
var dsMessage = DataSource.create('com.axelor.mail.db.MailMessage');
|
|
|
|
var pollResult = {};
|
|
|
|
TagService.listen(function (data) {
|
|
pollResult = data.mail || {};
|
|
});
|
|
|
|
/**
|
|
* Get the followers of the given record.
|
|
*
|
|
*/
|
|
function getFollowers(relatedModel, relatedId) {
|
|
var ds = DataSource.create(relatedModel);
|
|
return ds.followers(relatedId);
|
|
}
|
|
|
|
/**
|
|
* Get messages with the given options.
|
|
*
|
|
* The options can have following values:
|
|
*
|
|
* - relatedId = related record id
|
|
* - relatedModel = related model
|
|
* - folder = only from given folder
|
|
*
|
|
* @param {Object} options - search options
|
|
* @return {Promise}
|
|
*/
|
|
function getMessages(options) {
|
|
var opts = _.extend({}, options);
|
|
return dsMessage.messages(opts);
|
|
}
|
|
|
|
/**
|
|
* Get replies of the given message.
|
|
*
|
|
*/
|
|
function getReplies(parent) {
|
|
var deferred = $q.defer();
|
|
var opts = {
|
|
parent: parent.id
|
|
};
|
|
|
|
var promise = dsMessage.messages(opts)
|
|
.error(deferred.reject)
|
|
.success(function (res) {
|
|
var records = res.data || [];
|
|
deferred.resolve(records);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Toggle flags on the given message.
|
|
*
|
|
* Flag state can be:
|
|
*
|
|
* 1 = mark read
|
|
* 2 = mark starred
|
|
* 3 = mark archived
|
|
*
|
|
* Negative value unmarks the flag.
|
|
*
|
|
* @param {object|array} message - the message
|
|
* @param {number} flagState - flag state
|
|
*/
|
|
function flagMessage(message, flagState) {
|
|
var messages = _.isArray(message) ? message : [message];
|
|
var ref = {};
|
|
var all = _.map(messages, function (message) {
|
|
var flags = _.extend({}, message.$flags);
|
|
if (flagState === 1) flags.isRead = true;
|
|
if (flagState === -1) flags.isRead = false;
|
|
if (flagState === 2) flags.isStarred = true;
|
|
if (flagState === -2) flags.isStarred = false;
|
|
if (flagState === 3) flags.isArchived = true;
|
|
if (flagState === -3) flags.isArchived = false;
|
|
|
|
flags.message = _.pick(message, 'id');
|
|
flags.user = {
|
|
id: axelor.config['user.id']
|
|
};
|
|
|
|
ref[""+message.id] = message;
|
|
|
|
return flags;
|
|
});
|
|
|
|
var promise = dsFlags.saveAll(all);
|
|
promise.success(function (records) {
|
|
_.each(records, function (rec) {
|
|
var msg = ref[""+rec.message.id];
|
|
if (msg) {
|
|
msg.$flags = _.pick(rec, ['id', 'version', 'isStarred', 'isRead', 'isArchived']);
|
|
}
|
|
});
|
|
// force unread check
|
|
if (flagState === 1 || flagState == -1) {
|
|
TagService.find();
|
|
}
|
|
});
|
|
|
|
return promise;
|
|
}
|
|
|
|
function removeMessage(message) {
|
|
var promise = dsMessage.remove(message);
|
|
promise.then(function () {
|
|
TagService.find(); // force unread check
|
|
});
|
|
return promise;
|
|
}
|
|
|
|
return {
|
|
getFollowers: getFollowers,
|
|
getMessages: getMessages,
|
|
getReplies: getReplies,
|
|
flagMessage: flagMessage,
|
|
removeMessage: removeMessage,
|
|
unreadCount: function () {
|
|
return pollResult.unread;
|
|
}
|
|
};
|
|
}]);
|
|
|
|
ui.directive('uiMailMessage', function () {
|
|
return {
|
|
require: '^uiMailMessages',
|
|
replace: true,
|
|
link: function (scope, element, attrs) {
|
|
|
|
var message = scope.message;
|
|
var body = message.body || "{}";
|
|
if (body.indexOf("{") === 0) {
|
|
body = angular.fromJson(body);
|
|
body.tags = _.map(body.tags, function (tag) {
|
|
if (tag.style) {
|
|
tag.css = 'label-' + tag.style;
|
|
}
|
|
return tag;
|
|
});
|
|
scope.body = body;
|
|
} else {
|
|
scope.body = null;
|
|
}
|
|
|
|
function findField(name) {
|
|
if (scope.field && scope.field.target) {
|
|
return ((scope.field.viewer||{}).fields||{})[name]
|
|
|| ((scope.field.editor||{}).fields||{})[name];
|
|
}
|
|
return (scope.viewItems || scope.fields || {})[name];
|
|
}
|
|
|
|
function format(item, value) {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
var field = findField(item.name);
|
|
if(field && ["many-to-many", "one-to-many", "many-to-one"].indexOf(field.type) === -1) {
|
|
var formatter = ui.formatters[field.type];
|
|
if (formatter) {
|
|
return formatter(field, value);
|
|
}
|
|
}
|
|
if (value === 'True') return _t('True');
|
|
if (value === 'False') return _t('False');
|
|
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(value)) {
|
|
return moment(value).format("DD/MM/YYYY HH:mm");
|
|
}
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
return moment(value).format("DD/MM/YYYY");
|
|
}
|
|
if (value === '0E-10') value = '0.000000000000';
|
|
return value;
|
|
}
|
|
|
|
if (body && body.tracks) {
|
|
_.each(body.tracks, function (item) {
|
|
item.displayValue = item.displayValue || format(item, item.value);
|
|
item.oldDisplayValue = item.oldDisplayValue || format(item, item.oldValue);
|
|
if (item.oldDisplayValue !== undefined) {
|
|
item.displayValue = item.oldDisplayValue + " » " + item.displayValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
message.$title = (body||{}).title || message.subject;
|
|
if (!message.$avatar) {
|
|
var a = scope.$userName(message) || "";
|
|
message.$a = a[0] || "?";
|
|
}
|
|
|
|
scope.showFull = !message.summary;
|
|
scope.onShowFull = function () {
|
|
scope.showFull = true;
|
|
};
|
|
|
|
setTimeout(function () {
|
|
element.addClass('fadeIn');
|
|
// dim avatar background color
|
|
var avatar = element.find('.avatar[class*=bg-]');
|
|
if (avatar.length) {
|
|
var color = avatar.css('background-color');
|
|
var bg = d3.hsl(color);
|
|
bg.l += 0.1;
|
|
bg.s -= 0.1;
|
|
avatar.css('background-color', bg.toString());
|
|
}
|
|
});
|
|
},
|
|
template: "" +
|
|
"<div class='fade'>" +
|
|
"<a href='' class='pull-left avatar' ng-class='userColor(message)' title='{{::$userName(message)}}' ng-click='showAuthor(message)'>" +
|
|
"<img ng-if='message.$avatar' ng-src='{{::message.$avatar}}' width='32px'>" +
|
|
"<span ng-if='message.$a'>{{::message.$a}}</span>" +
|
|
"</a>" +
|
|
"<div class='mail-message'>" +
|
|
"<span class='arrow left'></span>" +
|
|
"<span class='star left' ng-show='message.$thread && (message.$isNew || !message.$flags.isRead)'><i class='fa fa-asterisk'></i></span>" +
|
|
"<div class='mail-message-icons'>" +
|
|
"<span ng-if='::message.$thread'>" +
|
|
"<i class='fa fa-reply' ng-show='::message.$thread' ng-click='onReply(message)'></i> " +
|
|
"</span>" +
|
|
"<div class='btn-group'>" +
|
|
"<a href='javascript:' class='btn btn-link dropdown-toggle' data-toggle='dropdown'>" +
|
|
"<i class='fa fa-caret-down'></i>" +
|
|
"</a>" +
|
|
"<ul class='dropdown-menu pull-right'>" +
|
|
"<li>" +
|
|
"<a href='javascript:' ng-show='!message.$flags.isRead' ng-click='onFlag(message, 1)' x-translate>Mark as read</a>" +
|
|
"<a href='javascript:' ng-show='message.$flags.isRead' ng-click='onFlag(message, -1)' x-translate>Mark as unread</a>" +
|
|
"</li>" +
|
|
"<li>" +
|
|
"<a href='javascript:' ng-show='!message.$flags.isStarred' ng-click='onFlag(message, 2)' x-translate>Mark as important</a>" +
|
|
"<a href='javascript:' ng-show='message.$flags.isStarred' ng-click='onFlag(message, -2)' x-translate>Mark as not important</a>" +
|
|
"</li>" +
|
|
"<li ng-if='message.$thread' ng-show='::!message.parent'>" +
|
|
"<a href='javascript:' ng-show='::!message.$flags.isArchived' ng-click='onFlag(message, 3)' x-translate>Move to archive</a>" +
|
|
"<a href='javascript:' ng-show='::message.$flags.isArchived' ng-click='onFlag(message, -3)' x-translate>Move to inbox</a>" +
|
|
"</li>" +
|
|
"<li>" +
|
|
"<a href='javascript:' ng-show='::message.$canDelete' ng-click='onRemove(message)' x-translate>Delete</a>" +
|
|
"</li>" +
|
|
"</ul>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"<div class='mail-message-header' ng-if='::message.$name || message.$title'>" +
|
|
"<span class='subject' ng-if='::message.$thread'>" +
|
|
"<a ng-if='message.relatedId && message.$name' href='#ds/form::{{::message.relatedModel}}/edit/{{::message.relatedId}}'>{{::message.$name}}</a>" +
|
|
"<span ng-if='::!message.relatedId && message.$name'>{{::message.$name}}</span>" +
|
|
"<span ng-if='::message.$name && message.$title'> - </span>" +
|
|
"<span ng-if='::message.$title'>" +
|
|
"<a ng-if='::message.relatedId' href='#ds/form::{{::message.relatedModel}}/edit/{{::message.relatedId}}'>{{:: _t(message.$title) }}</a>" +
|
|
"<span ng-if='::!message.relatedId'>{{:: _t(message.$title) }}</span>" +
|
|
"</span>" +
|
|
"</span>" +
|
|
"<span class='subject' ng-if='::!message.$thread'>" +
|
|
"<span ng-if='message.relatedId && message.$name'>{{::message.$name}}</span>" +
|
|
"<span ng-if='::!message.relatedId && message.$name'>{{::message.$name}}</span>" +
|
|
"<span ng-if='::message.$name'> - </span>" +
|
|
"<span ng-if='::message.$title'>{{:: _t(message.$title) }}</span>" +
|
|
"</span>" +
|
|
"<span class='track-tags' ng-if='::body.tags.length'>" +
|
|
"<span class='label' ng-class='::item.css' ng-repeat='item in ::body.tags'>{{:: _t(item.title) }}</span>" +
|
|
"</span>" +
|
|
"</div>" +
|
|
"<div class='mail-message-body'>" +
|
|
"<ul class='track-fields' ng-if='::body'>" +
|
|
"<li ng-repeat='item in ::body.tracks'>" +
|
|
"<strong>{{:: _t(item.title) }}</strong> : <span ng-bind-html='::item.displayValue'></span>" +
|
|
"</li>" +
|
|
"</ul>" +
|
|
"<div class='track-content' ng-if='::body.content'>" +
|
|
"<div ui-bind-template x-text='body.content'></div>" +
|
|
"</div>" +
|
|
"<div ng-if='!body'>" +
|
|
"<div ng-if='message.summary && !showFull' ui-bind-template x-text='message.summary'></div>" +
|
|
"<div ng-if='!message.summary || showFull' ui-bind-template x-text='message.body'></div>" +
|
|
"<a ng-if='!showFull' href='' ng-click='onShowFull()' class='show-full'><i class='fa fa-ellipsis-h'></i></a>" +
|
|
"</div>" +
|
|
"<div class='mail-message-files' ng-if='::body.files'>" +
|
|
"<div ui-mail-files x-removable='false' x-files='body.files' class='inline'></div>" +
|
|
"</div>" +
|
|
"<div class='mail-message-files' ng-show='::message.$files.length'>" +
|
|
"<div ui-mail-files x-removable='false' x-files='message.$files' class='inline'></div>" +
|
|
"</div>" +
|
|
"<div class='mail-message-footer'>" +
|
|
"<span ng-if='message.$numReplies' class='pull-right'>" +
|
|
"<a href='' ng-click='onReplies(message)'>{{formatNumReplies(message)}}</a>" +
|
|
"</span>" +
|
|
"<span>" +
|
|
"<a href='' ng-click='showAuthor(message)'>{{::$userName(message)}}</a> " +
|
|
"<span ng-bind-html='formatEvent(message)'></span>" +
|
|
"</span>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"<div ui-mail-composer ng-if='::message.$thread'></div>" +
|
|
"</div>"
|
|
};
|
|
});
|
|
|
|
ui.formWidget('uiMailMessages', {
|
|
scope: true,
|
|
controller: ['$scope', 'MessageService', function ($scope, MessageService) {
|
|
|
|
var userColors = {};
|
|
var usedColors = [];
|
|
var colorNames = [
|
|
'blue',
|
|
'green',
|
|
'red',
|
|
'orange',
|
|
'yellow',
|
|
'olive',
|
|
'teal',
|
|
'violet',
|
|
'purple',
|
|
'pink',
|
|
'brown'
|
|
];
|
|
|
|
$scope.userColor = function (message) {
|
|
var user = message.$author;
|
|
if (!user) return null;
|
|
if (userColors[user.code]) {
|
|
return userColors[user.code];
|
|
}
|
|
if (usedColors.length === colorNames.length) {
|
|
usedColors = [];
|
|
}
|
|
var color = _.find(colorNames, function (n) {
|
|
return usedColors.indexOf(n) === -1;
|
|
});
|
|
usedColors.push(color);
|
|
return userColors[user.code] = 'bg-' + color;
|
|
};
|
|
|
|
function updateReadCount(messages) {
|
|
var unread = [];
|
|
_.each(messages, function (message) {
|
|
if (!(message.$flags||{}).isRead) {
|
|
unread.push(message);
|
|
message.$isNew = true;
|
|
}
|
|
_.each(message.$children, function (item) {
|
|
if (!(item.$flags||{}).isRead) {
|
|
unread.push(item);
|
|
}
|
|
});
|
|
});
|
|
if (unread.length) {
|
|
MessageService.flagMessage(unread, 1);
|
|
}
|
|
}
|
|
|
|
$scope.onFlag = function (message, flagState) {
|
|
MessageService.flagMessage(message, flagState).success(function (res) {
|
|
if (flagState !== 1 && flagState !== -1) {
|
|
$scope.onLoadMessages();
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.onRemove = function(message) {
|
|
var index = $scope.messages.indexOf(message);
|
|
MessageService.removeMessage(message).success(function (res) {
|
|
if (message.$afterDelete) {
|
|
message.$afterDelete();
|
|
} else if (index > -1) {
|
|
$scope.messages.splice(index, 1);
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.onReply = function (message) {
|
|
message.$reply = true;
|
|
$scope.$broadcast("on:message-add");
|
|
};
|
|
|
|
$scope.onReplies = function (message) {
|
|
MessageService.getReplies(message)
|
|
.then(function (data) {
|
|
_.each(data, function (item, i) {
|
|
item.$afterReply = item.$afterDelete = function () {
|
|
$scope.onReplies(message);
|
|
};
|
|
});
|
|
message.$numReplies = data.length;
|
|
message.$children = data;
|
|
updateReadCount(data);
|
|
});
|
|
};
|
|
|
|
$scope.formatEvent = function (message) {
|
|
if (message.$eventLine) {
|
|
return message.$eventLine;
|
|
}
|
|
var line = message.$eventText
|
|
+ " - "
|
|
+ "<a href='javascript:void(0)' title='" + moment(message.$eventTime).format('DD/MM/YYYY HH:mm') + "'>"
|
|
+ moment(message.$eventTime).fromNow()
|
|
+ "</a>";
|
|
message.$eventLine = line;
|
|
return line;
|
|
};
|
|
|
|
$scope.formatNumReplies = function (message) {
|
|
var children = (message.$children||[]).length || 0;
|
|
var total = message.$numReplies || 0;
|
|
return _t('replies ({0} of {1})', children, total);
|
|
};
|
|
|
|
var folder = $scope.folder;
|
|
|
|
$scope.messages = [];
|
|
$scope.hasMore = false;
|
|
$scope.hasMessages = true;
|
|
|
|
$scope.animation = {};
|
|
|
|
$scope.onLoadMessages = function (offset) {
|
|
|
|
var record = $scope.record || {};
|
|
var limit = ($scope.field || {}).limit || 10;
|
|
var params = {
|
|
limit: limit,
|
|
offset: offset,
|
|
folder: folder
|
|
};
|
|
|
|
if (record.id > 0) {
|
|
params.relatedId = record.id;
|
|
params.relatedModel = $scope._model;
|
|
}
|
|
|
|
if (!offset && folder) {
|
|
$scope.animation = {
|
|
"fadeDim": true
|
|
};
|
|
}
|
|
|
|
return MessageService.getMessages(params).success(function (res) {
|
|
var found = res.data;
|
|
var count = res.total;
|
|
|
|
if (!offset) {
|
|
$scope.hasMore = false;
|
|
$scope.messages.length = 0;
|
|
}
|
|
|
|
Array.prototype.push.apply($scope.messages, found);
|
|
$scope.hasMore = $scope.messages.length < count;
|
|
|
|
$scope.waitForActions(function () {
|
|
if (folder) {
|
|
$scope.record.__empty = !count;
|
|
updateReadCount(found);
|
|
}
|
|
}, 100);
|
|
|
|
$scope.animation = {
|
|
'fade': true,
|
|
'fadeIn': true
|
|
};
|
|
});
|
|
};
|
|
|
|
$scope.onLoadMore = function () {
|
|
var offset = $scope.messages.length;
|
|
$scope.onLoadMessages(offset);
|
|
};
|
|
|
|
$scope.$on("on:new", function (e) {
|
|
if (folder) {
|
|
$scope.onLoadMessages();
|
|
}
|
|
});
|
|
|
|
$scope.$watch("record", function mailMessageWatch(record, old) {
|
|
if (record === old) { return; }
|
|
if (record && record.id) {
|
|
$scope.onLoadMessages();
|
|
}
|
|
});
|
|
|
|
$scope.$on("on:load-messages", function (e, record) {
|
|
if ($scope.record === record) {
|
|
$scope.onLoadMessages();
|
|
}
|
|
});
|
|
|
|
$scope.$on("on:nav-click", function (e, tab) {
|
|
var action = ($scope.tab||{}).action;
|
|
if (action !== tab.action) { return ; }
|
|
if (folder) {
|
|
$scope.onLoadMessages();
|
|
}
|
|
});
|
|
}],
|
|
template_readonly: null,
|
|
template_editable: null,
|
|
template:
|
|
"<div class='mail-messages panel panel-default span9' ng-class='animation'>" +
|
|
"<div class='panel-body'>" +
|
|
"<div class='mail-composer' ui-mail-composer></div>" +
|
|
"<div class='mail-thread'>" +
|
|
"<span ng-repeat='message in messages'>" +
|
|
"<div ui-mail-message></div>" +
|
|
"<div ng-repeat='message in message.$children' class='mail-message-indent' ui-mail-message></div>" +
|
|
"</span>" +
|
|
"<div ng-show='hasMore' class='mail-thread-more'>" +
|
|
"<a class='btn btn-load-more' href='' role='button' ng-click='onLoadMore()'>" +
|
|
"<span x-translate>load more</span>" +
|
|
"<i class='fa fa-arrow-right fa-fw'></i>" +
|
|
"</a>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</div>"
|
|
});
|
|
|
|
ui.directive('uiMailUploader', ["$compile", function ($compile) {
|
|
|
|
return function (scope, element, attrs) {
|
|
|
|
scope.onSelect = function (items) {
|
|
for (var i = 0; i < items.length; i++) {
|
|
var file = items[i];
|
|
var fileId = file.metaFile ? file.metaFile.id : file['metaFile.id'];
|
|
if (file.isDirectory || !fileId) continue;
|
|
if (_.findWhere(scope.files, {id: fileId})) continue;
|
|
scope.files.push({
|
|
id: fileId,
|
|
fileName: file.fileName
|
|
});
|
|
}
|
|
};
|
|
|
|
scope.onUpload = function () {
|
|
var popup = $compile('<div ui-dms-popup x-on-select="onSelect"></div>')(scope);
|
|
popup.isolateScope().showPopup();
|
|
};
|
|
};
|
|
}]);
|
|
|
|
ui.directive('uiMailFiles', [function () {
|
|
return {
|
|
scope: {
|
|
files: "=",
|
|
removable: "@"
|
|
},
|
|
link: function (scope, element, attrs) {
|
|
|
|
scope.onRemoveFile = function (file) {
|
|
var files = scope.files || [];
|
|
var i = files.indexOf(file);
|
|
if (i > -1) {
|
|
files.splice(i, 1);
|
|
}
|
|
};
|
|
|
|
scope.onDownload = function (file) {
|
|
var url = "ws/rest/com.axelor.meta.db.MetaFile/" + file.id + "/content/download";
|
|
ui.download(url, file.fileName);
|
|
};
|
|
|
|
scope.fileIcon = function (file) {
|
|
return file.fileIcon || 'fa-paperclip';
|
|
};
|
|
},
|
|
replace: true,
|
|
template:
|
|
"<ul class='mail-files'>" +
|
|
"<li ng-repeat='file in files'>" +
|
|
"<i class='fa fa-close' ng-if='removable != \"false\"' ng-click='onRemoveFile(file)'></i>" +
|
|
"<i class='fa fa-colored' ng-class='fileIcon(file)' ng-if='removable == \"false\"'></i>" +
|
|
"<a href='' ng-click='onDownload(file)'>{{file.fileName}}</a>" +
|
|
"</li>" +
|
|
"</ul>"
|
|
};
|
|
}]);
|
|
|
|
ui.directive('uiMailEditor', ["$compile", function ($compile) {
|
|
|
|
return function (scope, element, attrs) {
|
|
|
|
var title = scope.$eval(attrs.emailTitle) || _t("Email");
|
|
var sendTitle = scope.$eval(attrs.sendTitle) || _t("Send");
|
|
|
|
scope._viewParams = {
|
|
model: "com.axelor.mail.db.MailMessage",
|
|
type: "form",
|
|
fields: {
|
|
},
|
|
views: [{
|
|
type: "form",
|
|
title: title,
|
|
css: "mail-editor",
|
|
items: [{
|
|
type: "panel",
|
|
showFrame: false,
|
|
itemSpan: 12,
|
|
items: [{
|
|
name: "recipients",
|
|
widget: "mail-select",
|
|
showTitle: false,
|
|
placeholder: _t("Recipients")
|
|
}, {
|
|
name: "subject",
|
|
showTitle: false,
|
|
placeholder: _t("Subject")
|
|
}, {
|
|
name: "body",
|
|
showTitle: false,
|
|
widget: "html",
|
|
height: 250
|
|
}, {
|
|
name: "files",
|
|
showTitle: false,
|
|
widget: "mail-file-list"
|
|
}]
|
|
}]
|
|
}]
|
|
};
|
|
|
|
var popup = null;
|
|
|
|
scope.$on("$destroy", function () {
|
|
if (popup) {
|
|
popup.$destroy();
|
|
popup = null;
|
|
}
|
|
});
|
|
|
|
scope.select = function (record) {
|
|
|
|
};
|
|
|
|
scope.isEditable = function () {
|
|
return true;
|
|
};
|
|
|
|
scope.onEditEmail = function (record, onSelect) {
|
|
|
|
if (popup === null) {
|
|
popup = scope.$new();
|
|
popup.fields = {};
|
|
|
|
var editor = $compile('<div ui-editor-popup x-buttons="buttons"></div>')(popup);
|
|
var buttons = editor.parent().find(".ui-dialog-buttonpane");
|
|
|
|
buttons.find(".button-ok").text(sendTitle);
|
|
|
|
var attach = $('<button type="button" class="btn"><i class="fa fa-paperclip"></i></button>')
|
|
.click(function() {
|
|
var uploader = $compile('<div ui-dms-popup x-on-select="onSelectFiles"></div>')(popup);
|
|
uploader.isolateScope().showPopup();
|
|
uploader.isolateScope().$applyAsync();
|
|
});
|
|
|
|
$('<div class="pull-left ui-dialog-buttonset" style="float: left;"></div>')
|
|
.append(attach)
|
|
.appendTo(buttons);
|
|
|
|
popup = editor.isolateScope();
|
|
popup.onSelectFiles = function (items) {
|
|
var rec = popup.record || {};
|
|
var all = rec.files || [];
|
|
for (var i = 0; i < items.length; i++) {
|
|
var file = items[i];
|
|
var fileId = file.metaFile ? file.metaFile.id : file['metaFile.id'];
|
|
if (file.isDirectory || !fileId) continue;
|
|
if (_.findWhere(all, {id: fileId})) continue;
|
|
all.push({
|
|
id: fileId,
|
|
fileName: file.fileName
|
|
});
|
|
}
|
|
rec.files = all;
|
|
popup.record = rec;
|
|
};
|
|
}
|
|
|
|
popup.show(record, onSelect);
|
|
};
|
|
};
|
|
}]);
|
|
|
|
ui.formWidget('uiMailFileList', {
|
|
|
|
link: function () {
|
|
|
|
},
|
|
template_readonly: null,
|
|
template_editable: null,
|
|
template: "<div>" +
|
|
"<div ui-mail-files x-files='record.files' class='inline'></div>" +
|
|
"</div>"
|
|
});
|
|
|
|
ui.formInput('uiMailSelect', 'MultiSelect', {
|
|
metaWidget: false,
|
|
|
|
init: function (scope) {
|
|
this._super.apply(this, arguments);
|
|
scope.isReadonly = function () { return false; };
|
|
},
|
|
|
|
link_editable: function (scope, element, attrs, model) {
|
|
this._super.apply(this, arguments);
|
|
|
|
var field = scope.field;
|
|
var selectionMap = {};
|
|
|
|
scope.formatItem = function(value) {
|
|
if (!value) { return ""; }
|
|
var item = selectionMap[value];
|
|
if (item) {
|
|
return item.label || value;
|
|
}
|
|
return item;
|
|
};
|
|
|
|
var handleSelect = scope.handleSelect;
|
|
scope.handleSelect = function(e, ui) {
|
|
var item = ui.item;
|
|
selectionMap[item.value] = item;
|
|
// store selection map in record to find display names
|
|
if (scope.record) {
|
|
scope.record["$" + field.name + "Map"] = selectionMap;
|
|
}
|
|
return handleSelect.apply(scope, arguments);
|
|
};
|
|
|
|
scope.loadSelection = function(request, response) {
|
|
|
|
var $http = scope._dataSource._http;
|
|
var data = {
|
|
search: request.term,
|
|
selected: _.pluck(scope.getSelection(), "value")
|
|
};
|
|
|
|
if (_.isEmpty(data.selected)) {
|
|
selectionMap = {};
|
|
}
|
|
|
|
$http.post("ws/search/emails", { data: data }).then(function (resp) {
|
|
var res = resp.data;
|
|
var items = _.map(res.data, function (item) {
|
|
return {
|
|
label: item.personal || item.address,
|
|
value: item.address
|
|
};
|
|
});
|
|
response(items);
|
|
}, function (err) {
|
|
response([]);
|
|
});
|
|
};
|
|
}
|
|
});
|
|
|
|
ui.formWidget('uiMailComposer', {
|
|
scope: true,
|
|
require: '^uiMailMessages',
|
|
controller: ['$scope', 'DataSource', function ($scope, DataSource) {
|
|
|
|
function dataSource() {
|
|
var msg = $scope.message;
|
|
if (msg && msg.relatedModel) {
|
|
return DataSource.create(msg.relatedModel);
|
|
}
|
|
return $scope._dataSource;
|
|
}
|
|
|
|
function dataRecord() {
|
|
var msg = $scope.message;
|
|
if (msg && msg.relatedModel) {
|
|
return {
|
|
id: msg.relatedId
|
|
};
|
|
}
|
|
return $scope.record;
|
|
}
|
|
|
|
$scope.post = null;
|
|
$scope.files = [];
|
|
|
|
$scope.canShow = function () {
|
|
var rec = $scope.record || {};
|
|
var msg = $scope.message || {};
|
|
return rec.id > 0 || msg.$reply;
|
|
};
|
|
|
|
$scope.canPost = function () {
|
|
return $scope.post;
|
|
};
|
|
|
|
$scope.onPost = function () {
|
|
var text = $scope.post.replace(/\n/g, '<br>');
|
|
var files = $scope.files;
|
|
return $scope.sendEmail({
|
|
body: text,
|
|
files: files
|
|
});
|
|
};
|
|
|
|
$scope.sendEmail = function (email) {
|
|
|
|
var ds = dataSource();
|
|
var record = dataRecord();
|
|
var parent = _.pick($scope.message || {}, 'id');
|
|
|
|
if (!record.id) {
|
|
return;
|
|
}
|
|
|
|
var text = email.body || email.post;
|
|
var files = _.pluck(email.files, "id");
|
|
var recipients = email.recipients;
|
|
|
|
return ds.messagePost(record.id, text, {
|
|
type: 'comment',
|
|
parent: parent.id && parent,
|
|
files: files,
|
|
subject: email.subject,
|
|
recipients: recipients
|
|
}).success(function (res) {
|
|
var message = _.first(res.data);
|
|
var messages = $scope.$parent.messages || [];
|
|
|
|
$scope.post = null;
|
|
$scope.files = [];
|
|
|
|
if (parent.id && $scope.message) {
|
|
$scope.message.$reply = false;
|
|
if ($scope.message.$afterReply) {
|
|
return $scope.message.$afterReply();
|
|
}
|
|
return $scope.onReplies($scope.message);
|
|
} else {
|
|
messages.unshift(message);
|
|
}
|
|
|
|
$scope.$broadcast('on:message-add');
|
|
});
|
|
};
|
|
|
|
}],
|
|
link: function (scope, element, attrs) {
|
|
|
|
var textarea = element.find('textarea');
|
|
|
|
textarea.on('blur', function () {
|
|
if (scope.post || !scope.message) return;
|
|
scope.message.$reply = false;
|
|
scope.$applyAsync();
|
|
});
|
|
|
|
scope.$on("on:edit", function (e) {
|
|
textarea.val('');
|
|
});
|
|
|
|
scope.$on('on:message-add', function () {
|
|
if (scope.canShow()) {
|
|
scope.post = null;
|
|
setTimeout(function () {
|
|
textarea.focus();
|
|
});
|
|
}
|
|
});
|
|
|
|
function findName() {
|
|
var record = scope.record || {};
|
|
var fields = scope.fields || {};
|
|
var name = _.findWhere(fields, {nameColumn: true});
|
|
return record[name] || record.name || record.code || "";
|
|
}
|
|
|
|
scope.showEditor = function () {
|
|
var record = {
|
|
body: scope.post,
|
|
files: scope.files
|
|
};
|
|
scope.onEditEmail(record, function (record) {
|
|
var map = record.$recipientsMap || {};
|
|
var recipients = record.recipients || "";
|
|
|
|
record.recipients = _.map(recipients.split(","), function (item) {
|
|
var email = item.trim();
|
|
return _.extend({
|
|
address: email,
|
|
personal: (map[email]||{}).label || email
|
|
});
|
|
});
|
|
|
|
scope.sendEmail(record);
|
|
});
|
|
};
|
|
},
|
|
template: "" +
|
|
"<div class='mail-composer' ng-show='canShow()'>" +
|
|
"<textarea rows='1' ng-model='post' ui-textarea-auto-size class='span12' placeholder='{{\"Write your comment here\" | t}}'></textarea>" +
|
|
"<div class='mail-composer-files' ng-show='files.length'>" +
|
|
"<div ui-mail-files x-files='files'></div>" +
|
|
"</div>" +
|
|
"<div class='mail-composer-buttons' ng-show='canPost()'>" +
|
|
"<button type='button' class='btn btn-primary' ng-click='onPost()' x-translate>Post</button>" +
|
|
"<span class='btn btn-default' ui-mail-uploader ng-click='onUpload()'>" +
|
|
"<i class='fa fa-paperclip'></i>" +
|
|
"</span>" +
|
|
"<span class='btn btn-default' ui-mail-editor ng-click='showEditor()'>" +
|
|
"<i class='fa fa-pencil'></i>" +
|
|
"</span>" +
|
|
"</div>" +
|
|
"</div>"
|
|
});
|
|
|
|
ui.formWidget('uiMailFollowers', {
|
|
|
|
scope: true,
|
|
controller: ['$scope', 'ViewService', 'MessageService', function ($scope, ViewService, MessageService) {
|
|
|
|
var ds = $scope._dataSource;
|
|
|
|
$scope.emailTitle = _t("Add followers");
|
|
$scope.sendTitle = _t("Add");
|
|
|
|
$scope.following = false;
|
|
$scope.followers = [];
|
|
|
|
$scope.updateStatus = function() {
|
|
var followers = $scope.followers || [];
|
|
var found = _.find(followers, function (item) {
|
|
return item.$author && item.$author.code === axelor.config["user.login"];
|
|
});
|
|
$scope.following = !!found;
|
|
};
|
|
|
|
function sendEmail(email) {
|
|
|
|
var ds = $scope._dataSource;
|
|
var record = $scope.record || {};
|
|
var promise = ds.messageFollow(record.id, {
|
|
email: email
|
|
});
|
|
|
|
promise.success(function (res) {
|
|
$scope.followers = res.data || [];
|
|
$scope.updateStatus();
|
|
});
|
|
}
|
|
|
|
function findName() {
|
|
var record = $scope.record || {};
|
|
var fields = $scope.fields || {};
|
|
var name = _.findWhere(fields, {nameColumn: true});
|
|
return record[name] || record.name || record.code || "";
|
|
}
|
|
|
|
$scope.onAddFollowers = function () {
|
|
var record = {
|
|
subject: _t("Follow: {0}", findName())
|
|
};
|
|
$scope.onEditEmail(record, function (record) {
|
|
|
|
var map = record.$recipientsMap || {};
|
|
var recipients = record.recipients || "";
|
|
|
|
record.recipients = _.map(recipients.split(","), function (item) {
|
|
var email = item.trim();
|
|
return _.extend({
|
|
address: email,
|
|
personal: (map[email]||{}).label || email
|
|
});
|
|
});
|
|
sendEmail(record);
|
|
});
|
|
};
|
|
|
|
$scope.onFollow = function () {
|
|
|
|
var record = $scope.record || {};
|
|
var promise = ds.messageFollow(record.id);
|
|
|
|
promise.success(function (res) {
|
|
$scope.followers = res.data || [];
|
|
$scope.updateStatus();
|
|
});
|
|
};
|
|
|
|
$scope.onUnfollow = function (follower) {
|
|
axelor.dialogs.confirm(_t('Are you sure to unfollow this document?'),
|
|
function (confirmed) {
|
|
if (confirmed) {
|
|
doUnfollow(follower);
|
|
}
|
|
});
|
|
};
|
|
|
|
function doUnfollow(follower) {
|
|
var followerId = (follower||{}).id;
|
|
var record = $scope.record || {};
|
|
var promise = ds.messageUnfollow(record.id, {
|
|
records: _.compact([followerId])
|
|
});
|
|
|
|
promise.success(function (res) {
|
|
$scope.followers = res.data || [];
|
|
$scope.updateStatus();
|
|
});
|
|
}
|
|
|
|
function doLoadFollowers(record) {
|
|
MessageService.getFollowers($scope._model, record.id)
|
|
.success(function (res) {
|
|
$scope.followers = res.data;
|
|
$scope.updateStatus();
|
|
});
|
|
}
|
|
|
|
$scope.$watch("record", function mailFollowerWatch(record) {
|
|
if (record && record.id) {
|
|
doLoadFollowers(record);
|
|
}
|
|
});
|
|
}],
|
|
link: function (scope, element, attrs) {
|
|
|
|
},
|
|
template_readonly: null,
|
|
template_editable: null,
|
|
template:
|
|
"<div class='mail-followers panel panel-default span3'>" +
|
|
"<div class='panel-header'>" +
|
|
"<div class='panel-title' x-translate>Followers</div>" +
|
|
"<div class='panel-icons'>" +
|
|
"<i class='fa fa-star-o' ng-click='onFollow()' ng-show='!following'></i>" +
|
|
"<i class='fa fa-star' ng-click='onUnfollow()' ng-show='following'></i>" +
|
|
"<i class='fa fa-plus' ui-mail-editor x-email-title='emailTitle' x-send-title='sendTitle' ng-click='onAddFollowers()'></i>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"<div class='panel-body'>" +
|
|
"<ul class='links'>" +
|
|
"<li ng-repeat='follower in followers'>" +
|
|
"<i class='fa fa-remove' ng-click='onUnfollow(follower)'></i>" +
|
|
"<a href='' ng-click='showAuthor(follower)'>{{$userName(follower)}}</a> " +
|
|
"</li>" +
|
|
"</ul>" +
|
|
"</div>" +
|
|
"</div>"
|
|
});
|
|
|
|
ui.formWidget('PanelMail', {
|
|
|
|
scope: true,
|
|
controller: ['$scope', '$timeout', 'NavService', 'ViewService', 'MessageService', function ($scope, $timeout, NavService, ViewService, MessageService) {
|
|
|
|
$scope.getDomain = function () {
|
|
return {};
|
|
};
|
|
|
|
$scope.showAuthor = function (message) {
|
|
var msg = message || {};
|
|
var act = msg.$authorAction;
|
|
var author = msg.$author;
|
|
var model = msg.$authorModel;
|
|
if (!author || !author.id) {
|
|
return;
|
|
}
|
|
|
|
function openUserForm(name) {
|
|
NavService.openTab({
|
|
title: _t('User'),
|
|
action: act || _.uniqueId('$act'),
|
|
model: model,
|
|
viewType: "form",
|
|
views: [{ type: "form", name: name }]
|
|
}, {
|
|
mode: "edit",
|
|
state: author.id
|
|
});
|
|
};
|
|
|
|
ViewService.getMetaDef(model, {
|
|
type: 'form',
|
|
name: 'user-info-form'
|
|
}).then(function (meta) {
|
|
openUserForm('user-info-form');
|
|
},function () {
|
|
openUserForm('user-form');
|
|
});
|
|
};
|
|
|
|
$scope.$userName = function (message) {
|
|
var msg = message || {};
|
|
var author = msg.$author || msg.$from;
|
|
if (!author) {
|
|
return null;
|
|
}
|
|
var key = axelor.config["user.nameField"] || "name";
|
|
return author.personal || author[key] || author.name || author.fullName || author.displayName;
|
|
};
|
|
|
|
var folder;
|
|
var tab = $scope.tab || {};
|
|
|
|
if (tab.action === "mail.inbox") folder = "inbox";
|
|
if (tab.action === "mail.unread") folder = "unread";
|
|
if (tab.action === "mail.important") folder = "important";
|
|
if (tab.action === "mail.archive") folder = "archive";
|
|
|
|
$scope.folder = folder;
|
|
}],
|
|
link: function (scope, element, attrs) {
|
|
var field = scope.field;
|
|
if (field.items && field.items.length === 1) {
|
|
setTimeout(function () {
|
|
element.children('.mail-messages').removeClass('span9').addClass('span12');
|
|
element.children('.mail-followers').removeClass('span3').addClass('span12');
|
|
});
|
|
}
|
|
},
|
|
transclude: true,
|
|
template_readonly: null,
|
|
template_editable: null,
|
|
template: "<div class='form-mail row-fluid' ng-show='record.id > 0 || folder' ui-transclude></div>"
|
|
});
|
|
|
|
ui.controller("TeamListCtrl", TeamListCtrl);
|
|
TeamListCtrl.$inject = ['$scope', '$element'];
|
|
function TeamListCtrl($scope, $element) {
|
|
ui.GridViewCtrl.call(this, $scope, $element);
|
|
|
|
$scope.getUrl = function (record) {
|
|
if (!record || !record.id) return null;
|
|
return "ws/rest/com.axelor.team.db.Team/" + record.id + "/image/download?image=true&v=" + record.version;
|
|
};
|
|
|
|
$scope.onEdit = function(record) {
|
|
$scope.switchTo('form', function (formScope) {
|
|
if (formScope.canEdit()) {
|
|
formScope.edit(record);
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.onFollow = function (record) {
|
|
|
|
var ds = $scope._dataSource;
|
|
var promise = ds.messageFollow(record.id);
|
|
|
|
promise.success(function (res) {
|
|
record.$following = true;
|
|
});
|
|
};
|
|
|
|
$scope.onUnfollow = function (record) {
|
|
|
|
axelor.dialogs.confirm(_t('Are you sure to unfollow this group?'),
|
|
function (confirmed) {
|
|
if (confirmed) {
|
|
doUnfollow(record);
|
|
}
|
|
});
|
|
};
|
|
|
|
function doUnfollow(record) {
|
|
|
|
var ds = $scope._dataSource;
|
|
var promise = ds.messageUnfollow(record.id);
|
|
|
|
promise.success(function (res) {
|
|
record.$following = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
})();
|