Source: service/message.js

/**
 * Show error messages.
 * @namespace messageService
 */

'use strict';

app.factory('messageService', [
  '$sanitize', '$sce', 'constantService',
  function ($sanitize, $sce, constants) {
    var defaults = {
      type: 'blue',
      persist: false,
    };

    var sequence = 0;
    var byBody = {};

    function Message(init) {
      this.id = init.id || 'message-' + sequence++;
      angular.extend(this, defaults, init);

      if (Array.isArray(this.body))
        this.body = (this.more = this.body).shift();
      /* suppress duplicate messages */
      if (this.body in byBody)
        return;

      Message.list[this.id] = this;
      byBody[this.orig = this.body] = this;
    }

    Message.list = {};

    Message.prototype.remove = function () {
      delete Message.list[this.id];
      delete byBody[this.orig];
    };

    Message.prototype.next = function () {
      var more;
      if (Array.isArray(this.more))
        more = this.more.shift();
      else if (typeof this.more === 'string') {
        more = this.more;
        delete this.more;
      }
      if (more)
        this.body = more;
      else
        this.remove();
    };

    Message.add = function (message) {
      return new Message(message);
    };

    /* NB: modifies message */
    Message.addError = function (message) {
      message.type = 'red';

      var body = constants.message('error.prefix') + ' ' + $sce.getTrustedHtml(message.body);

      if (message.report) {
        message.errors = message.report.data;
        message.status = message.report.status;
        if (message.report.config)
          message.url = message.report.config.url;
      }

      if (!message.errors) {
        body += ' ' + constants.message('error.suffix');
      } else if (angular.isString(message.errors)) {
        // try to guess content-type since it's not in report:
        if (message.errors.startsWith('<')) {
          message.fn = function () {
            // escape angular
            window.setTimeout(function() {
              document.open('text/html');
              document.write(message.errors); // jshint ignore:line
              document.close();
            }, 0);
          };
          body += ' <a href="">' + constants.message('error.view') + '</a>';
        } else
          body += "<br>" + $sanitize(message.errors);
      } else if (angular.isObject(message.errors)) {
        var moreBody = '';
        var messageBody = '';

        if (message.statusText)
          messageBody += 'Status:\n' + message.statusText + '\n\n';

        Message.foreachError(message.errors, function(field, error){
          moreBody += '<dl class="comma"><dt>' + (field || 'Reason') + '</dt><dd>' + error.map($sanitize).join('</dd><dd>') + '</dd></dl>';
          messageBody += (field || 'Reason') + ':\n' + error.join('\n') + '\n\n';
        });

        if (messageBody)
          body += ' ' + constants.message('error.report', encodeURIComponent(constants.message('error.mail.subject', message.status || 0, message.url || '')), encodeURIComponent(constants.message('error.mail.body', messageBody))) + moreBody;
        if (message.status == 409)
          body += "<br>" + constants.message('app.reload');
      }

      delete message.report;
      delete message.errors;
      delete message.status;
      delete message.url;
      message.body = $sce.trustAsHtml(body);

      return new Message(message);
    };

    Message.clear = function (owner) {
      _.each(Message.list, function(message){
	if (owner ? message.owner === owner : !message.persist)
          message.remove();
      });
    };

    /* convert the nested (json) structure produced by server errors into a flatter object where keys are dot-delimited and values are always arrays, or calling proc on each key, value pair. */
    Message.foreachError = function (errors, proc) {
      var out = {};
      if (!proc)
        proc = function (key, val) {
          out[key] = val;
        };
      function flatten(obj, prefix) {
        if (typeof obj === 'object' && (!Array.isArray(obj) || obj.some(function (x) { return typeof x === 'object'; })))
          for (var key in obj)
            flatten(obj[key], prefix ? prefix + '.' + key : key);
        else
          proc(prefix || '', Array.isArray(obj) ? obj : [obj]);
      }
      flatten(errors);
      return out;
    };

    return Message;
  }
]);