3 angular.module("motion", ["ngAnimate", "ngAria", "ngMessages", "ngMaterial", "ngResource", "ngSanitize", "ngCsv", "ngEmbed", "ngEmojiPicker", "ui.router", "LocalStorageModule"]).constant("STORAGE_PREFIX", "motion_chat").constant("STORAGE_VISITOR", "visitor").constant("STORAGE_LAYOUT", "layout").directive("ngEnter", function() {
4 return function(e, t, n) {
5 t.bind("keydown keypress", function(t) {
6 13 === t.which && (e.$apply(function() {
8 }), t.preventDefault())
16 function e(e, t, n, i, o, r, s, a, l, c, d, m) {
18 w.visitor && w.visitor.interaction && w.visitor.interaction.id && d.chatInteraction.get({
19 id: w.visitor.interaction.id
20 }).$promise.then(function(e) {
21 delete w.errors.getInteraction, e.closed && (c.remove(m), t.$emit("hide"), t.settings.online ? r.go("app.online") : r.go("app.offline"))
22 }).catch(g("getInteraction"))
26 t.loading = !1, t.settings.showMenu = !0, w.replyMessage = "", y = i(v, 2e3), T = i(p, 1e4), b()
31 w.errors[e] = t.data, b()
36 d.chatWebsite.notify(e).$promise.then(function(e) {
37 delete w.errors.sendMessageError, _.isNil(w.visitor.interaction) && (w.visitor.interaction = {
39 }, c.set(m, w.visitor)), x()
40 }).catch(g("sendMessageError"))
44 w.visitor && w.visitor.interaction && w.visitor.interaction.id && d.chatInteraction.getMessages({
45 id: w.visitor.interaction.id,
46 $gte: $ ? ["updatedAt", $].join() : void 0
47 }).$promise.then(function(e) {
48 if (delete w.errors.getMessagesError, $ = moment().format("YYYY-MM-DD HH:mm:ss"), e.count) {
49 for (var t = 0; t < e.rows.length; t++) w.messages[e.rows[t].id] = e.rows[t], "out" === e.rows[t].direction && h(e.rows[t].id);
52 }).catch(g("getMessagesError"))
56 d.chatMessage.update({
59 }).$promise.catch(function(e) {
65 S.value = "", w.textareaGrow = !1
70 var e = o[0].getElementById("chat-content");
71 e.scrollTop = e.scrollHeight
74 var y, T, $, w = this,
75 S = document.getElementById("emoji-area");
76 w.errors = {}, w.messages = {}, w.visitor = c.get(m) || {}, w.$onInit = u, w.$onDestroy = u, w.reply = function(e) {
77 var t = S.value.replace(/\n$/, "");
78 e && 13 === e.keyCode && e.shiftKey ? w.textareaGrow = !0 : e && 13 !== e.keyCode || ("" !== t ? f(_.merge({
81 }, e.$on("$destroy", function() {
82 i.cancel(y), i.cancel(T), y = null, T = null
83 }), t.$on("$download", function(e, t) {
84 t && t(_.values(w.messages))
87 e.$inject = ["$scope", "$rootScope", "$timeout", "$interval", "$document", "$state", "$http", "$q", "$mdDialog", "localStorageService", "api", "STORAGE_VISITOR"], angular.module("motion").controller("ChatIndexController", e)
92 function e(e, t, n, i, o, r, s, a, l, c) {
94 n.parent.postMessage({
100 u.layout.up = e, s.set(l, u.layout), d(e ? "show" : "hide")
104 return u.visitor.interaction || (u.visitor.interaction = s.get(c).interaction), u.visitor.interaction
107 u.$onInit = function() {
108 u.layout = s.get(l) || {
110 }, u.visitor = s.get(c) || {}, m(!!u.layout.up), t.settings.online ? i.go("app.online") : i.go("app.offline")
111 }, u.toggle = m, u.download = function() {
113 return s.get(c).interaction && a.chatInteraction.getMessages({
115 }).$promise.then(function(t) {
116 e.resolve(_.map(t.rows, function(e) {
118 sender: "in" === e.direction ? "Visitor" + e.ContactId : e.UserId ? "Agent" + e.UserId : "System",
120 createdAt: moment(e.createdAt).format("MM/DD/YYYY HH:mm:ss")
123 }).catch(function(e) {
126 }, u.getHeaderShape = function() {
127 return "rounded" === t.settings.header_shape ? "15px" : "0px"
128 }, u.exit = function(e) {
129 var n = o.confirm().textContent("Are you sure?").ok("Ok").cancel("Cancel");
130 o.show(n).then(function() {
131 if (p()) return a.chatInteraction.update({
134 closedAt: moment().format("YYYY-MM-DD HH:mm:ss")
137 s.remove(c), t.$emit("hide"), i.go("app.online")
138 }).catch(function(e) {
141 }, t.$on("show", function() {
143 }), t.$on("hide", function() {
147 e.$inject = ["$scope", "$rootScope", "$window", "$state", "$mdDialog", "$q", "localStorageService", "api", "STORAGE_LAYOUT", "STORAGE_VISITOR"], angular.module("motion").controller("IndexController", e)
154 baseUrl: e.settings.remote + "/api/"
158 token: e.settings.token
160 return n.chatWebsite = t(n.baseUrl + "chat/websites/:id", i, {
163 url: n.baseUrl + "chat/websites/:id/notify"
167 url: n.baseUrl + "chat/websites/:id/fields"
169 }), n.chatInteraction = t(n.baseUrl + "chat/interactions/:id", i, {
172 url: n.baseUrl + "chat/interactions/:id"
176 url: n.baseUrl + "chat/interactions/:id/messages"
178 }), n.chatMessage = t(n.baseUrl + "chat/messages/:id", i, {
181 url: n.baseUrl + "chat/messages/:id"
185 e.$inject = ["$rootScope", "$resource"], angular.module("motion").factory("api", e)
195 }, _.forIn(n, function(t, n) {
196 "" === t || _.isNil(t) || ("true" === t && (t = !0), "false" === t && (t = !1), e.settings[n] = t)
200 function t(e, t, n, i, o) {
201 n.html5Mode(!0), i.setPrefix(o), e.state("app", {
204 }).state("app.online", {
206 templateUrl: "app/online/index.html",
207 controller: "OnlineIndexController as vm"
208 }).state("app.offline", {
210 templateUrl: "app/offline/index.html",
211 controller: "OfflineIndexController as vm"
212 }).state("app.chat", {
214 templateUrl: "app/chat/index.html",
215 controller: "ChatIndexController as vm"
218 e.$inject = ["$rootScope", "$location"], t.$inject = ["$stateProvider", "$urlRouterProvider", "$locationProvider", "localStorageServiceProvider", "STORAGE_PREFIX"], angular.module("motion").config(t).run(e)
223 function e(e, t, n, i, o, r, s, a, l, c) {
225 d.$onInit = function() {
226 t.settings.showMenu = !1, a.chatWebsite.getFields({
229 }).$promise.then(function(e) {
230 e.count && (d.fields = e.rows)
231 }).catch(function(e) {
234 }, d.submit = function() {
235 r.remove(c), t.$emit("hide"), d.form = {}
236 }, d.toggle = function(e, t) {
237 d.form[e] || (d.form[e] = []);
238 var n = d.form[e].indexOf(t);
239 n > -1 ? d.form[e].splice(n, 1) : d.form[e].push(t)
240 }, d.exists = function(e, t) {
241 return d.form[e] || (d.form[e] = []), d.form[e].indexOf(t) > -1
244 e.$inject = ["$scope", "$rootScope", "$state", "$window", "$timeout", "localStorageService", "$http", "api", "STORAGE_PREFIX", "STORAGE_VISITOR"], angular.module("motion").controller("OfflineIndexController", e)
249 function e(e, t, n, i, o, r, s, a, l, c) {
251 return "Anonymous" + _.random(1, 99999)
255 m.$onInit = function() {
256 r.get(c) && (m.visitor = r.get(c)), m.visitor ? n.go("app.chat") : (m.visitor = {
260 }, t.settings.showMenu = !1, a.chatWebsite.getFields({
263 }).$promise.then(function(e) {
264 e.count ? (p = e.fromKey, m.fields = e.rows) : (r.set(c, m.visitor), n.go("app.chat"))
265 }).catch(function(e) {
268 }, m.submit = function() {
269 for (var e = 0; e < m.fields.length; e++) {
271 i.props && m.form.hasOwnProperty(i.props.title) && (_.isNil(i.variable) || (m.visitor[i.variable] = _.isArray(m.form[i.props.title]) ? m.form[i.props.title].join() : m.form[i.props.title]), _.isNil(i.cmField) || (m.visitor[i.cmField] = _.isArray(m.form[i.props.title]) ? m.form[i.props.title].join() : m.form[i.props.title]), e === p && (_.isNil(t.settings.mapKey) || (m.visitor.mapKey = t.settings.mapKey, m.visitor.from = _.isArray(m.form[i.props.title]) ? m.form[i.props.title].join() : m.form[i.props.title])))
273 r.set(c, m.visitor), t.loading = !0, n.go("app.chat")
274 }, m.toggle = function(e, t) {
275 m.form[e] || (m.form[e] = []);
276 var n = m.form[e].indexOf(t);
277 n > -1 ? m.form[e].splice(n, 1) : m.form[e].push(t)
278 }, m.exists = function(e, t) {
279 return m.form[e] || (m.form[e] = []), m.form[e].indexOf(t) > -1
282 e.$inject = ["$scope", "$rootScope", "$state", "$window", "$timeout", "localStorageService", "$http", "api", "STORAGE_PREFIX", "STORAGE_VISITOR"], angular.module("motion").controller("OnlineIndexController", e)
283 }(), angular.module("motion").run(["$templateCache", function(e) {
285 e.put("app/chat/index.html", '\x3c!-- CHAT CONTENT --\x3e\n<md-content id="chat-content" flex>\n \x3c!-- CHAT MESSAGES --\x3e\n <div layout="row" ng-repeat="(key, message) in vm.messages track by message.id" class="md-padding message-row" ng-class="{\'in\': message.direction === \'in\', \'out\': message.direction === \'out\' && message.UserId, \'auto\': message.direction === \'out\' && !message.UserId}">\n\n <img ng-if="message.direction ===\'out\' && !message.UserId" ng-src="assets/images/avatars/robot0.png" class="avatar" alt="system" />\n <img ng-if="message.direction ===\'out\' && message.UserId" ng-src="assets/images/avatars/agent0.png" class="avatar" alt="agent" />\n <img ng-if="message.direction ===\'in\'" ng-src="assets/images/avatars/customer0.png" class="avatar" alt="customer">\n\n <div class="bubble">\n <div class="time secondary-text">\n <span>\n {{message.direction === \'out\' ? (message.UserId ? \'Agent\' + message.UserId : \'System\') : (vm.visitor.from || \'Visitor\' + message.ContactId)}} - {{message.createdAt | date : \'h:mm\'}}\n </span>\n </div>\n <div ng-class="{\'in\': message.direction === \'in\', \'out\': message.direction ===\'out\' && message.UserId, \'auto\': message.direction ===\'out\' && !message.UserId}">\n <div ng-bind-html="message.body | embed:vm.emojiOptions" class="message"></div>\n </div>\n </div>\n\n </div>\n \x3c!-- CHAT MESSAGES --\x3e\n\n \x3c!-- CHAT ERROR --\x3e\n <div class="chat-error" ng-repeat="error in vm.errors" layout="row" layout-margin>\n <i class="mdi mdi-alert mdi-24px mdi-light"></i>\n <md-tooltip md-direction="top">{{error.message || \'Service temporarily unavailable.\'}}</md-tooltip>\n <span class="chat-error-message">{{error.message || \'Service temporarily unavailable.\'}}</span>\n </div>\n \x3c!-- CHAT ERROR --\x3e\n\n</md-content>\n\x3c!-- / CHAT CONTENT --\x3e\n\n\x3c!-- CHAT FOOTER --\x3e\n<div class="chat-footer flex-noshrink" layout="row" layout-align="center center">\n \x3c!-- REPLY FORM --\x3e\n <form ng-submit="vm.reply()" flex class="reply-form" layout="row" layout-align="start center">\n\n <md-input-container flex md-no-float>\n <textarea id="emoji-area" ng-keyup="vm.reply($event)" md-no-autogrow ng-model="vm.replyMessage" ng-class="{\'grow\': vm.textareaGrow}" placeholder="Type and hit enter to send message" emoji-picker emoji-attachment-location="bottom right" emoji-menu-location="top left"\n emoji-popup-button-classes="mdi mdi-emoticon mdi-24px" emoji-assets-path="assets/images/ng-emoji-picker">\n </textarea>\n </md-input-container>\n\n <md-button class="md-fab md-mini" type="submit" aria-label="Send message" translate translate-attr-aria-label="CHAT.SEND_MESSAGE" ng-style="{\'background-color\': \'{{settings.color_button}}\'}">\n <i class="mdi mdi-send mdi-24px mdi-light"></i>\n </md-button>\n\n </form>\n \x3c!-- / REPLY FORM --\x3e\n</div>\n\x3c!-- / CHAT FOOTER--\x3e\n'), e.put("app/offline/index.html", '\x3c!-- CHAT CONTENT --\x3e\n<md-content id="chat-content" style="height: 444px; padding: 25px;" flex layout-padding ms-scroll>\n <form ng-if="vm.fields.length" name="userForm" layout="column" ng-submit="vm.submit()" novalidate>\n <md-input-container class="md-block" ng-repeat="field in vm.fields | orderBy:\'index\'" ng-switch="field.type">\n \x3c!-- START textinput --\x3e\n <div ng-switch-when="input">\n <label>{{field.props.title}}</label>\n <input name="{{field.props.title}}" ng-model="vm.form[field.props.title]" type="{{field.config.type}}" ng-required="field.config.required">\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END textinput --\x3e\n\n \x3c!-- START textarea --\x3e\n <div ng-switch-when="textarea">\n <label>{{field.props.title}}</label>\n <textarea name="{{field.props.title}}" ng-model="vm.form[field.props.title]" md-maxlength="150" rows="5"></textarea>\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END textarea --\x3e\n\n \x3c!-- START select --\x3e\n <div ng-switch-when="chooseFromList">\n <label>{{field.props.title}}</label>\n <md-select name="{{field.props.title}}"ng-model="vm.form[field.props.title]" placeholder="{{field.config.placeholder}}" ng-required="field.config.required">\n <md-option ng-repeat="option in field.options" ng-value="option.value">{{option.value}}</md-option>\n </md-select>\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END select --\x3e\n\n \x3c!-- START radio --\x3e\n <div ng-switch-when="multipleChoices" layout="column">\n <p md-colors="{color: \'grey\'}">{{field.props.title}}</p>\n <md-radio-group name="{{field.props.title}}" ng-model="vm.form[field.props.title]" ng-required="field.config.required" layout="{{field.config.direction === \'vertical\' ? \'column\' : \'row\'}}">\n <md-radio-button ng-repeat="option in field.options" value="{{option.value}}" class="md-primary">{{option.value}}</md-radio-button>\n </md-radio-group>\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END radio --\x3e\n\n \x3c!-- START checkbox --\x3e\n <div ng-switch-when="checkboxes" layout="column">\n <p md-colors="{color: \'grey\'}">{{field.props.title}}</p>\n <div layout="{{field.config.direction === \'vertical\' ? \'column\' : \'row\'}}">\n <md-checkbox\n aria-label="{{field.props.title}}"\n ng-repeat="option in field.options"\n ng-checked="vm.exists(field.props.title, option.value)"\n ng-disabled="!vm.exists(field.props.title, option.value) && field.config.maxSelections === vm.form[field.props.title].length"\n ng-click="vm.toggle(field.props.title, option.value)"\n flex>\n {{option.value}}\n </md-checkbox>\n </div>\n </div>\n \x3c!-- END checkbox --\x3e\n </md-input-container>\n\n <md-button type="submit" class="md-raised" ng-disabled="userForm.$invalid || userForm.$pristine" ng-style="{\'background-color\': \'{{settings.color_button}}\'}">SEND</md-button>\n </form>\n\n <md-content layout="row" layout-align="center end">\n {{ settings.defaultWhiteLabel ? \'Powered By XCALLY\' : settings.whiteLabel }}\n </md-content>\n\n</md-content>\n\x3c!-- / CHAT CONTENT --\x3e\n'), e.put("app/online/index.html", '\x3c!-- CHAT CONTENT --\x3e\n<md-content id="chat-content" style="height: 444px; padding: 25px;" flex layout-padding ms-scroll>\n <form ng-if="vm.fields.length" name="userForm" layout="column" ng-submit="vm.submit()" novalidate>\n\n <md-input-container class="md-block" ng-repeat="field in vm.fields | orderBy:\'index\'" ng-switch="field.type">\n \x3c!-- START textinput --\x3e\n <div ng-switch-when="input">\n <label>{{field.props.title}}</label>\n <input name="{{field.props.title}}" ng-model="vm.form[field.props.title]" type="{{field.config.type}}" ng-required="field.config.required">\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END textinput --\x3e\n\n \x3c!-- START textarea --\x3e\n <div ng-switch-when="textarea">\n <label>{{field.props.title}}</label>\n <textarea name="{{field.props.title}}" ng-model="vm.form[field.props.title]" md-maxlength="150" rows="5"></textarea>\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END textarea --\x3e\n\n \x3c!-- START select --\x3e\n <div ng-switch-when="chooseFromList">\n <label>{{field.props.title}}</label>\n <md-select name="{{field.props.title}}"ng-model="vm.form[field.props.title]" placeholder="{{field.config.placeholder}}" ng-required="field.config.required">\n <md-option ng-repeat="option in field.options" ng-value="option.value">{{option.value}}</md-option>\n </md-select>\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END select --\x3e\n\n \x3c!-- START radio --\x3e\n <div ng-switch-when="multipleChoices" layout="column">\n <p md-colors="{color: \'grey\'}">{{field.props.title}}</p>\n <md-radio-group name="{{field.props.title}}" ng-model="vm.form[field.props.title]" ng-required="field.config.required" layout="{{field.config.direction === \'vertical\' ? \'column\' : \'row\'}}">\n <md-radio-button ng-repeat="option in field.options" value="{{option.value}}" class="md-primary">{{option.value}}</md-radio-button>\n </md-radio-group>\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END radio --\x3e\n\n \x3c!-- START checkbox --\x3e\n <div ng-switch-when="checkboxes" layout="column">\n <p md-colors="{color: \'grey\'}">{{field.props.title}}</p>\n <div layout="{{field.config.direction === \'vertical\' ? \'column\' : \'row\'}}">\n <md-checkbox\n aria-label="{{field.props.title}}"\n ng-repeat="option in field.options"\n ng-checked="vm.exists(field.props.title, option.value)"\n ng-disabled="!vm.exists(field.props.title, option.value) && field.config.maxSelections === vm.form[field.props.title].length"\n ng-click="vm.toggle(field.props.title, option.value)"\n flex>\n {{option.value}}\n </md-checkbox>\n </div>\n <div class="hint">{{field.props.helpText}}</div>\n </div>\n \x3c!-- END checkbox --\x3e\n </md-input-container>\n\n <md-button type="submit" class="md-raised" ng-disabled="userForm.$invalid || userForm.$pristine" ng-style="{\'background-color\': \'{{settings.color_button}}\'}">{{settings.start_chat_button}}</md-button>\n </form>\n\n <md-content layout="row" layout-align="center end" style="background: transparent;">\n {{ settings.defaultWhiteLabel ? \'Powered By XCALLY\' : settings.whiteLabel }}\n </md-content>\n\n</md-content>\n\x3c!-- / CHAT CONTENT --\x3e\n')