(function() {
    'use strict';

    const globals = typeof window === 'undefined' ? global : window;
    if (typeof globals.require === 'function') return;

    let modules = {},
        cache = {},
        aliases = {},
        has = {}.hasOwnProperty;

    const unalias = function(alias, loaderPath) {
        const _cmp = 'components/';
        let start = 0;
        if (loaderPath) {
            if (loaderPath.startsWith(_cmp)) {
                start = _cmp.length;
            }
            if (loaderPath.indexOf('/', start) > 0) {
                loaderPath = loaderPath.substring(
                    start,
                    loaderPath.indexOf('/', start)
                );
            }
        }
        const result =
      aliases[alias + '/index.js'] ||
      aliases[loaderPath + '/deps/' + alias + '/index.js'];
        if (result) {
            return _cmp + result.substring(0, result.length - '.js'.length);
        }
        return alias;
    };

    const expand = function(root, name) {
        const _reg = /^\.\.?(\/|$)/;
        let results = [],
            parts = (_reg.test(name) ? root + '/' + name : name).split('/');
        for (let part of parts) {
            if (part === '..') {
                results.pop();
            } else if (part !== '.' && part !== '') {
                results.push(part);
            }
        }
        return results.join('/');
    };

    const dirname = function(path) {
        return path
            .split('/')
            .slice(0, -1)
            .join('/');
    };

    const localRequire = function(path) {
        return function(name) {
            let absolute = expand(dirname(path), name);
            return globals.require(absolute, path);
        };
    };

    const initModule = function(name, definition) {
        let module = { id: name, exports: {} };
        cache[name] = module;
        definition(module.exports, localRequire(name), module);
        return module.exports;
    };

    const require = function(name, loaderPath) {
        if (loaderPath === undefined) loaderPath = '/';
        let path = unalias(name, loaderPath);

        if (path in cache) return cache[path].exports;
        if (path in modules) return initModule(path, modules[path]);

        let dirIndex = expand(path, './index');
        if (dirIndex in cache) return cache[dirIndex].exports;
        if (dirIndex in modules) return initModule(dirIndex, modules[dirIndex]);

        throw new Error(
            'Cannot find module "' + name + '" from ' + '"' + loaderPath + '"'
        );
    };

    require.alias = function(from, to) {
        aliases[to] = from;
    };

    require.register = require.define = function(bundle, fn) {
        if (typeof bundle === 'object') {
            for (let key in bundle) {
                if (has.call(bundle, key)) {
                    modules[key] = bundle[key];
                }
            }
        } else {
            modules[bundle] = fn;
        }
    };

    require.list = function() {
        let result = [];
        for (let item in modules) {
            if (has.call(modules, item)) {
                result.push(item);
            }
        }
        return result;
    };

    require._cache = cache;
    globals.require = require;
})();
require.register("home", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');

const logo = () => {
  return {
    view() {
      return m('.logo', [
        m('img', {
          src: 'images/retroshare.svg',
          alt: 'retroshare_icon',
        }),
        m('.retroshareText', [
          m('.retrotext', [m('span', 'RETRO'), 'SHARE']),
          m('b', 'secure communication for everyone'),
        ]),
      ]);
    },
  };
};

const webhelpConfirm = () => {
  return {
    view: () => [
      m('h3', 'Confirmation'),
      m('hr'),
      m('p', 'Do you want this link to be handled by your system?'),
      m('p', 'https://retrosharedocs.readthedocs.io/en/latest/'),
      m('p', 'Make sure this link has not been forged to drag you to a malicious website.'),
      m(
        'button',
        {
          onclick: () => {
            window.open('https://retrosharedocs.readthedocs.io/en/latest/');
          },
        },
        'Ok'
      ),
    ],
  };
};

const webhelp = () => {
  return {
    view() {
      return m(
        '.webhelp',
        {
          onclick: () => {
            widget.popupMessage(m(webhelpConfirm));
          },
        },
        [m('i.fas.fa-globe-europe'), m('p', 'Open Web Help')]
      );
    },
  };
};

const ConfirmCopied = () => {
  return {
    view: () => [
      m('h3', 'Copied to Clipboard'),
      m('hr'),
      m('p[style="margin: 12px 0 4px"]', 'Your Retroshare ID has been copied to Clipboard.'),
      m(
        'p[style="margin: 4px 0 12px"]',
        'Now, you can paste and send it to your friend via email or some other way.'
      ),
      m('button', {}, 'Ok'),
    ],
  };
};

const retroshareId = () => {
  return {
    view(v) {
      return m('.retroshareID', [
        m(
          'textarea[readonly].textArea',
          {
            id: 'retroId',
            placeholder: 'certificate',
            onclick: () => {
              document.getElementById('retroId').select();
            },
          },
          v.attrs.ownCert.substring(31)
        ),
        m('i.fas.fa-copy', {
          onclick: () => {
            document.getElementById('retroId').select();
            document.execCommand('copy');
            widget.popupMessage(m(ConfirmCopied));
          },
        }),
        m('i.fas.fa-share-alt'),
      ]);
    },
  };
};

function invalidCertPrompt() {
  widget.popupMessage([m('h3', 'Error'), m('hr'), m('p', 'Not a valid Retroshare certificate.')]);
}

function confirmAddPrompt(details, cert, long) {
  widget.popupMessage([
    m('i.fas.fa-user-plus'),
    m('h3', 'Make friend'),
    m('p', 'Details about your friend'),
    m('hr'),
    m('ul', [
      m('li', 'Name: ' + details.name),
      m('li', 'Location: ' + details.location + '(' + details.id + ')'),
      m('li', details.isHiddenNode ? details.hiddenNodeAddress : details.extAddr),
    ]),

    long
      ? m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsPeers/loadCertificateFromString', { cert });
              if (res.body.retval) {
                widget.popupMessage([
                  m('h3', 'Successful'),
                  m('hr'),
                  m('p', 'Successfully added friend.'),
                ]);
              } else {
                widget.popupMessage([
                  m('h3', 'Error'),
                  m('hr'),
                  m('p', 'An error occoured during adding. Friend not added.'),
                ]);
              }
            },
          },
          'Finish'
        )
      : m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsPeers/addSslOnlyFriend', {
                sslId: details.id,
                pgpId: details.gpg_id,
              });
              if (res.body.retval) {
                widget.popupMessage([
                  m('h3', 'Successful'),
                  m('hr'),
                  m('p', 'Successfully added friend.'),
                ]);
              } else {
                widget.popupMessage([
                  m('h3', 'Error'),
                  m('hr'),
                  m('p', 'An error occoured during adding. Friend not added.'),
                ]);
              }
            },
          },
          'Finish'
        ),
  ]);
}

async function addFriendFromCert(cert) {
  const res = await rs.rsJsonApiRequest('/rsPeers/parseShortInvite', { invite: cert });

  if (res.body.retval) {
    console.log(res.body);
    confirmAddPrompt(res.body.details, cert, false);
  } else {
    rs.rsJsonApiRequest('/rsPeers/loadDetailsFromStringCert', { cert }, (data) => {
      if (!data.retval) {
        invalidCertPrompt();
        return null;
      }
      confirmAddPrompt(data.certDetails, cert, true);
    });
  }
}

const AddFriend = () => {
  let certificate = '';

  function loadFileContents(fileListObj) {
    const file = fileListObj[0];
    if (file.type.indexOf('text') !== 0 || file.size === 0) {
      // TODO handle incorrect file
      return null;
    }
    const reader = new FileReader();
    reader.onload = (e) => {
      certificate = e.target.result;
      m.redraw();
    };
    reader.readAsText(file);
  }

  return {
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Add friend'),
        m('h5', 'Did you recieve a certificate from a friend?'),
        m('hr'),
        m(
          '.cert-drop-zone',
          {
            isDragged: false,
            ondragenter: () => (vnode.state.isDragged = true),
            ondragexit: () => (vnode.state.isDragged = false),

            // Styling element when file is dragged
            style: { border: vnode.state.isDragged && '5px solid #3ba4d7' },

            ondragover: (e) => e.preventDefault(),
            ondrop: (e) => {
              vnode.state.isDragged = false;
              e.preventDefault();
              loadFileContents(e.target.files || e.dataTransfer.files);
            },
          },

          [
            m(
              'p[style="margin: 16px 0 4px"]',
              'You can directly upload or Drag and drop the file below'
            ),
            m('input[type=file][name=certificate]', {
              onchange: (e) => {
                // Note: this one is for the 'browse' button
                loadFileContents(e.target.files || e.dataTransfer.files);
              },
            }),
            m('p[style="width: 100%; text-align: center; margin: 5px 0;"]', 'OR'),
            m(
              'textarea[rows=5][placeholder="Paste the certificate here"][style="width: 100%; display: block; resize: vertical;"]',
              {
                oninput: (e) => (certificate = e.target.value),
                value: certificate,
              }
            ),
            m(
              'button[style="margin-top: 10px;"]',
              {
                onclick: () => addFriendFromCert(certificate),
              },
              'Add'
            ),
          ]
        ),
      ]),
  };
};

const Certificate = () => {
  let ownCert = '';
  function loadOwnCert() {
    rs.rsJsonApiRequest(
      '/rsPeers/GetShortInvite',
      { formatRadix: true },
      (data) => (ownCert = data.invite)
    );
  }

  return {
    oninit() {
      // Load long cert by default
      loadOwnCert();
    },

    view() {
      return m('.homepage ', [
        m(logo),
        m('.certificate', [
          m('.certificate__heading', [
            m('h1', 'Welcome to Web Interface of Retroshare!'),
            'Retroshare is an Open Source Cross-platform,',
            m('br'),
            'Private and Secure Decentralized Communication Platform.',
          ]),
          m('.certificate__content', [
            m('.rsId', [
              m('p', 'This is your Retroshare ID. Copy and share with your friends!'),
              m(retroshareId, { ownCert }),
            ]),
            m('.add-friend', [
              m('h6', 'Did you receive a Retroshare ID from your friend ?'),
              m(
                'button',
                {
                  onclick: () => {
                    widget.popupMessage(m(AddFriend));
                  },
                },
                'Add Friend'
              ),
            ]),
            m('.webhelp-container', [m('h6', 'Do you need help with Retoshare ?'), m(webhelp)]),
          ]),
        ]),
      ]);
    },
  };
};

const Layout = () => {
  return {
    view: () => m(Certificate),
  };
};

module.exports = Layout;
 
}); 
require.register("login", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const displayErrorMessage = function (message) {
  m.render(document.getElementById('error'), message);
};

const verifyLogin = async function (uname, passwd, url, displayAuthError = true) {
  const loginHeader = {
    Authorization: `Basic ${btoa(`${uname}:${passwd}`)}`,
  };
  if (!url.trim()) {
    displayErrorMessage('Server-url is missing, please enter json-api url');
    return;
  }
  rs.setKeys('', '', url, false);
  rs.logon(
    loginHeader,
    displayAuthError ? displayErrorMessage : () => {},
    displayErrorMessage,
    () => {
      rs.setKeys(uname, passwd, url);
      m.route.set('/home');
    }
  );
};

function loginComponent() {
  const urlParams = new URLSearchParams(window.location.search);
  let uname = urlParams.get('Username') || 'webui';
  let passwd = urlParams.get('Password') || '';
  let url =
    urlParams.get('Url') || window.location.protocol === 'file:'
      ? 'http://127.0.0.1:9092'
      : window.location.protocol +
        '//' +
        window.location.host +
        window.location.pathname.replace('/index.html', '');
  let withOptions = false;

  const logo = () =>
    m('img.logo[width=30%]', { src: 'images/retroshare.svg', alt: 'retroshare_icon' });

  const inputName = () =>
    m('input', {
      id: 'username',
      type: 'text',
      value: uname,
      placeholder: 'Username',
      onchange: (e) => (uname = e.target.value),
    });
  const buttonLogin = () =>
    m(
      'button[type=submit].submit-btn#loginBtn',
      {
        onclick: (ev) => {
          ev.preventDefault();
          verifyLogin(uname, passwd, url);
        },
      },
      'Login'
    );

  const inputPassword = () =>
    m('input[autofocus]', {
      id: 'password',
      type: 'password',
      placeholder: 'Password',
      oncreate: (e) => e.dom.focus(),
      onchange: (e) => (passwd = e.target.value),
    });

  const inputUrl = () =>
    m('input', {
      id: 'url',
      type: 'text',
      placeholder: 'Url',
      value: url,
      oninput: (e) => (url = e.target.value),
    });

  const linkOptions = (action) =>
    m('a', { onclick: () => (withOptions = !withOptions) }, `${action} options`);

  const textError = () => m('p.error[id=error]');
  return {
    oninit: () => verifyLogin(uname, passwd, url, false),
    view: () => {
      return m(
        'form.login-page',
        m(
          '.login-container',
          withOptions
            ? [
                logo(),
                m('.extra', [m('label', 'Username:'), m('br'), inputName()]),
                m('.extra', [m('label', 'Password:'), m('br'), inputPassword()]),
                m('.extra', [m('label', 'Url:'), m('br'), inputUrl()]),
                linkOptions('hide'),
                buttonLogin(),
                textError(),
              ]
            : [logo(), inputPassword(), linkOptions('show'), buttonLogin(), textError()]
        )
      );
    },
  };
}

module.exports = loginComponent;
 
}); 
require.register("main", function(exports, require, module) { 
const m = require('mithril');

const login = require('login');
const home = require('home');
const network = require('network/network');
const people = require('people/people_resolver');
const chat = require('chat/chat');
const mail = require('mail/mail_resolver');
const files = require('files/files_resolver');
const channels = require('channels/channels');
const forums = require('forums/forums');
const boards = require('boards/boards');
const config = require('config/config_resolver');

const navIcon = {
  home: m('i.fas.fa-home.sidenav-icon'),
  network: m('i.fas.fa-share-alt.sidenav-icon'),
  people: m('i.fas.fa-users.sidenav-icon'),
  chat: m('i.fas.fa-comments.sidenav-icon'),
  mail: m('i.fas.fa-envelope.sidenav-icon'),
  files: m('i.fas.fa-folder-open.sidenav-icon'),
  channels: m('i.fas.fa-tv.sidenav-icon'),
  forums: m('i.fas.fa-bullhorn.sidenav-icon'),
  boards: m('i.fas.fa-globe.sidenav-icon'),
  config: m('i.fas.fa-cogs.sidenav-icon'),
};

const navbar = () => {
  let isCollapsed = true;
  return {
    view: (vnode) =>
      m(
        'nav.nav-menu',
        {
          class: isCollapsed ? 'collapsed' : '',
        },
        [
          m('.nav-menu__logo', [
            m('img', {
              src: 'images/retroshare.svg',
              alt: 'retroshare_icon',
            }),
            m('h5', 'Retroshare'),
          ]),
          m('.nav-menu__box', [
            Object.keys(vnode.attrs.links).map((linkName, i) => {
              const active = m.route.get().split('/')[1] === linkName;
              return m(
                m.route.Link,
                {
                  href: vnode.attrs.links[linkName],
                  class: 'item' + (active ? ' item-selected' : ''),
                },
                [navIcon[linkName], m('p', linkName)]
              );
            }),
            m(
              'button.toggle-nav',
              {
                onclick: () => (isCollapsed = !isCollapsed),
              },
              m('i.fas.fa-angle-double-left')
            ),
          ]),
        ]
      ),
  };
};

const Layout = () => {
  return {
    view: (vnode) =>
      m('.content', [
        m(navbar, {
          links: {
            home: '/home',
            network: '/network',
            people: '/people/OwnIdentity',
            chat: '/chat',
            mail: '/mail/inbox',
            files: '/files/files',
            channels: '/channels/MyChannels',
            forums: '/forums/MyForums',
            boards: '/boards/MyBoards',
            config: '/config/network',
          },
        }),
        m('.tab-content', vnode.children),
      ]),
  };
};

m.route(document.getElementById('main'), '/', {
  '/': {
    render: () => m(login),
  },
  '/home': {
    render: () => m(Layout, m(home)),
  },
  '/network': {
    render: () => m(Layout, m(network)),
  },

  '/people/:tab': {
    render: (v) => m(Layout, m(people, v.attrs)),
  },
  '/chat/:lobby/:subaction': {
    render: (v) => m(Layout, m(chat, v.attrs)),
  },
  '/chat/:lobby': {
    render: (v) => m(Layout, m(chat, v.attrs)),
  },
  '/chat': {
    render: () => m(Layout, m(chat)),
  },
  '/mail/:tab': {
    render: (v) => m(Layout, m(mail, v.attrs)),
  },
  '/mail/:tab/:msgId': {
    render: (v) => m(Layout, m(mail, v.attrs)),
  },
  '/files/:tab': {
    render: (v) => m(Layout, m(files, v.attrs)),
  },
  '/files/:tab/:resultId': {
    render: (v) => m(Layout, m(files, v.attrs)),
  },
  '/channels/:tab': {
    render: (v) => m(Layout, m(channels, v.attrs)),
  },
  '/channels/:tab/:mGroupId': {
    render: (v) => m(Layout, m(channels, v.attrs)),
  },
  '/channels/:tab/:mGroupId/:mMsgId': {
    render: (v) => m(Layout, m(channels, v.attrs)),
  },
  '/forums/:tab': {
    render: (v) => m(Layout, m(forums, v.attrs)),
  },
  '/forums/:tab/:mGroupId': {
    render: (v) => m(Layout, m(forums, v.attrs)),
  },

  '/forums/:tab/:mGroupId/:mMsgId': {
    render: (v) => m(Layout, m(forums, v.attrs)),
  },
  '/boards/:tab': {
    render: (v) => m(Layout, m(boards, v.attrs)),
  },
  '/boards/:tab/:mGroupId': {
    render: (v) => m(Layout, m(boards, v.attrs)),
  },
  '/boards/:tab/:mGroupId/:mMsgId': {
    render: (v) => m(Layout, m(boards, v.attrs)),
  },
  '/config/:tab': {
    render: (v) => m(Layout, m(config, v.attrs)),
  },
});
 
}); 
require.register("mithril", function(exports, require, module) { 
(function () {
  'use strict';
  function Vnode(tag, key, attrs0, children0, text, dom) {
    return {
      tag: tag,
      key: key,
      attrs: attrs0,
      children: children0,
      text: text,
      dom: dom,
      domSize: undefined,
      state: undefined,
      events: undefined,
      instance: undefined,
    };
  }
  Vnode.normalize = function (node) {
    if (Array.isArray(node))
      return Vnode('[', undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined);
    if (node == null || typeof node === 'boolean') return null;
    if (typeof node === 'object') return node;
    return Vnode('#', undefined, undefined, String(node), undefined, undefined);
  };
  Vnode.normalizeChildren = function (input) {
    var children0 = [];
    if (input.length) {
      var isKeyed = input[0] != null && input[0].key != null;
      // Note: this is a *very* perf-sensitive check.
      // Fun fact: merging the loop like this is somehow faster than splitting
      // it, noticeably so.
      for (var i = 1; i < input.length; i++) {
        if ((input[i] != null && input[i].key != null) !== isKeyed) {
          throw new TypeError('Vnodes must either always have keys or never have keys!');
        }
      }
      for (var i = 0; i < input.length; i++) {
        children0[i] = Vnode.normalize(input[i]);
      }
    }
    return children0;
  };
  // Call via `hyperscriptVnode0.apply(startOffset, arguments)`
  //
  // The reason I do it this way, forwarding the arguments and passing the start
  // offset in `this`, is so I don't have to create a temporary array in a
  // performance-critical path.
  //
  // In native ES6, I'd instead add a final `...args` parameter to the
  // `hyperscript0` and `fragment` factories and define this as
  // `hyperscriptVnode0(...args)`, since modern engines do optimize that away. But
  // ES5 (what Mithril requires thanks to IE support) doesn't give me that luxury,
  // and engines aren't nearly intelligent enough to do either of these:
  //
  // 1. Elide the allocation for `[].slice.call(arguments, 1)` when it's passed to
  //    another function only to be indexed.
  // 2. Elide an `arguments` allocation when it's passed to any function other
  //    than `Function.prototype.apply` or `Reflect.apply`.
  //
  // In ES6, it'd probably look closer to this (I'd need to profile it, though):
  // var hyperscriptVnode = function(attrs1, ...children1) {
  //     if (attrs1 == null || typeof attrs1 === "object" && attrs1.tag == null && !Array.isArray(attrs1)) {
  //         if (children1.length === 1 && Array.isArray(children1[0])) children1 = children1[0]
  //     } else {
  //         children1 = children1.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children1]
  //         attrs1 = undefined
  //     }
  //
  //     if (attrs1 == null) attrs1 = {}
  //     return Vnode("", attrs1.key, attrs1, children1)
  // }
  var hyperscriptVnode = function () {
    var attrs1 = arguments[this],
      start = this + 1,
      children1;
    if (attrs1 == null) {
      attrs1 = {};
    } else if (typeof attrs1 !== 'object' || attrs1.tag != null || Array.isArray(attrs1)) {
      attrs1 = {};
      start = this;
    }
    if (arguments.length === start + 1) {
      children1 = arguments[start];
      if (!Array.isArray(children1)) children1 = [children1];
    } else {
      children1 = [];
      while (start < arguments.length) children1.push(arguments[start++]);
    }
    return Vnode('', attrs1.key, attrs1, children1);
  };
  var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g;
  var selectorCache = {};
  var hasOwn = {}.hasOwnProperty;
  function isEmpty(object) {
    for (var key in object) if (hasOwn.call(object, key)) return false;
    return true;
  }
  function compileSelector(selector) {
    var match,
      tag = 'div',
      classes = [],
      attrs = {};
    while ((match = selectorParser.exec(selector))) {
      var type = match[1],
        value = match[2];
      if (type === '' && value !== '') tag = value;
      else if (type === '#') attrs.id = value;
      else if (type === '.') classes.push(value);
      else if (match[3][0] === '[') {
        var attrValue = match[6];
        if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, '$1').replace(/\\\\/g, '\\');
        if (match[4] === 'class') classes.push(attrValue);
        else attrs[match[4]] = attrValue === '' ? attrValue : attrValue || true;
      }
    }
    if (classes.length > 0) attrs.className = classes.join(' ');
    return (selectorCache[selector] = { tag: tag, attrs: attrs });
  }
  function execSelector(state, vnode) {
    var attrs = vnode.attrs;
    var children = Vnode.normalizeChildren(vnode.children);
    var hasClass = hasOwn.call(attrs, 'class');
    var className = hasClass ? attrs.class : attrs.className;
    vnode.tag = state.tag;
    vnode.attrs = null;
    vnode.children = undefined;
    if (!isEmpty(state.attrs) && !isEmpty(attrs)) {
      var newAttrs = {};
      for (var key in attrs) {
        if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key];
      }
      attrs = newAttrs;
    }
    for (var key in state.attrs) {
      if (hasOwn.call(state.attrs, key) && key !== 'className' && !hasOwn.call(attrs, key)) {
        attrs[key] = state.attrs[key];
      }
    }
    if (className != null || state.attrs.className != null)
      attrs.className =
        className != null
          ? state.attrs.className != null
            ? String(state.attrs.className) + ' ' + String(className)
            : className
          : state.attrs.className != null
          ? state.attrs.className
          : null;
    if (hasClass) attrs.class = null;
    for (var key in attrs) {
      if (hasOwn.call(attrs, key) && key !== 'key') {
        vnode.attrs = attrs;
        break;
      }
    }
    if (
      Array.isArray(children) &&
      children.length === 1 &&
      children[0] != null &&
      children[0].tag === '#'
    ) {
      vnode.text = children[0].children;
    } else {
      vnode.children = children;
    }
    return vnode;
  }
  function hyperscript(selector) {
    if (
      selector == null ||
      (typeof selector !== 'string' &&
        typeof selector !== 'function' &&
        typeof selector.view !== 'function')
    ) {
      throw Error('The selector must be either a string or a component.');
    }
    var vnode = hyperscriptVnode.apply(1, arguments);
    if (typeof selector === 'string') {
      vnode.children = Vnode.normalizeChildren(vnode.children);
      if (selector !== '[')
        return execSelector(selectorCache[selector] || compileSelector(selector), vnode);
    }
    vnode.tag = selector;
    return vnode;
  }
  hyperscript.trust = function (html) {
    if (html == null) html = '';
    return Vnode('<', undefined, undefined, html, undefined, undefined);
  };
  hyperscript.fragment = function () {
    var vnode2 = hyperscriptVnode.apply(0, arguments);
    vnode2.tag = '[';
    vnode2.children = Vnode.normalizeChildren(vnode2.children);
    return vnode2;
  };
  /** @constructor */
  var PromisePolyfill = function (executor) {
    if (!(this instanceof PromisePolyfill)) throw new Error('Promise must be called with `new`');
    if (typeof executor !== 'function') throw new TypeError('executor must be a function');
    var self = this,
      resolvers = [],
      rejectors = [],
      resolveCurrent = handler(resolvers, true),
      rejectCurrent = handler(rejectors, false);
    var instance = (self._instance = { resolvers: resolvers, rejectors: rejectors });
    var callAsync = typeof setImmediate === 'function' ? setImmediate : setTimeout;
    function handler(list, shouldAbsorb) {
      return function execute(value) {
        var then;
        try {
          if (
            shouldAbsorb &&
            value != null &&
            (typeof value === 'object' || typeof value === 'function') &&
            typeof (then = value.then) === 'function'
          ) {
            if (value === self) throw new TypeError("Promise can't be resolved w/ itself");
            executeOnce(then.bind(value));
          } else {
            callAsync(function () {
              if (!shouldAbsorb && list.length === 0)
                console.error('Possible unhandled promise rejection:', value);
              for (var i = 0; i < list.length; i++) list[i](value);
              (resolvers.length = 0), (rejectors.length = 0);
              instance.state = shouldAbsorb;
              instance.retry = function () {
                execute(value);
              };
            });
          }
        } catch (e) {
          rejectCurrent(e);
        }
      };
    }
    function executeOnce(then) {
      var runs = 0;
      function run(fn) {
        return function (value) {
          if (runs++ > 0) return;
          fn(value);
        };
      }
      var onerror = run(rejectCurrent);
      try {
        then(run(resolveCurrent), onerror);
      } catch (e) {
        onerror(e);
      }
    }
    executeOnce(executor);
  };
  PromisePolyfill.prototype.then = function (onFulfilled, onRejection) {
    var self = this,
      instance = self._instance;
    function handle(callback, list, next, state) {
      list.push(function (value) {
        if (typeof callback !== 'function') next(value);
        else
          try {
            resolveNext(callback(value));
          } catch (e) {
            if (rejectNext) rejectNext(e);
          }
      });
      if (typeof instance.retry === 'function' && state === instance.state) instance.retry();
    }
    var resolveNext, rejectNext;
    var promise = new PromisePolyfill(function (resolve, reject) {
      (resolveNext = resolve), (rejectNext = reject);
    });
    handle(onFulfilled, instance.resolvers, resolveNext, true),
      handle(onRejection, instance.rejectors, rejectNext, false);
    return promise;
  };
  PromisePolyfill.prototype.catch = function (onRejection) {
    return this.then(null, onRejection);
  };
  PromisePolyfill.prototype.finally = function (callback) {
    return this.then(
      function (value) {
        return PromisePolyfill.resolve(callback()).then(function () {
          return value;
        });
      },
      function (reason) {
        return PromisePolyfill.resolve(callback()).then(function () {
          return PromisePolyfill.reject(reason);
        });
      }
    );
  };
  PromisePolyfill.resolve = function (value) {
    if (value instanceof PromisePolyfill) return value;
    return new PromisePolyfill(function (resolve) {
      resolve(value);
    });
  };
  PromisePolyfill.reject = function (value) {
    return new PromisePolyfill(function (resolve, reject) {
      reject(value);
    });
  };
  PromisePolyfill.all = function (list) {
    return new PromisePolyfill(function (resolve, reject) {
      var total = list.length,
        count = 0,
        values = [];
      if (list.length === 0) resolve([]);
      else
        for (var i = 0; i < list.length; i++) {
          (function (i) {
            function consume(value) {
              count++;
              values[i] = value;
              if (count === total) resolve(values);
            }
            if (
              list[i] != null &&
              (typeof list[i] === 'object' || typeof list[i] === 'function') &&
              typeof list[i].then === 'function'
            ) {
              list[i].then(consume, reject);
            } else consume(list[i]);
          })(i);
        }
    });
  };
  PromisePolyfill.race = function (list) {
    return new PromisePolyfill(function (resolve, reject) {
      for (var i = 0; i < list.length; i++) {
        list[i].then(resolve, reject);
      }
    });
  };
  if (typeof window !== 'undefined') {
    if (typeof window.Promise === 'undefined') {
      window.Promise = PromisePolyfill;
    } else if (!window.Promise.prototype.finally) {
      window.Promise.prototype.finally = PromisePolyfill.prototype.finally;
    }
    var PromisePolyfill = window.Promise;
  } else if (typeof global !== 'undefined') {
    if (typeof global.Promise === 'undefined') {
      global.Promise = PromisePolyfill;
    } else if (!global.Promise.prototype.finally) {
      global.Promise.prototype.finally = PromisePolyfill.prototype.finally;
    }
    var PromisePolyfill = global.Promise;
  } else {
  }
  var _12 = function ($window) {
    var $doc = $window && $window.document;
    var currentRedraw;
    var nameSpace = {
      svg: 'http://www.w3.org/2000/svg',
      math: 'http://www.w3.org/1998/Math/MathML',
    };
    function getNameSpace(vnode3) {
      return (vnode3.attrs && vnode3.attrs.xmlns) || nameSpace[vnode3.tag];
    }
    //sanity check to discourage people from doing `vnode3.state = ...`
    function checkState(vnode3, original) {
      if (vnode3.state !== original) throw new Error('`vnode.state` must not be modified');
    }
    //Note: the hook is passed as the `this` argument to allow proxying the
    //arguments without requiring a full array allocation to do so. It also
    //takes advantage of the fact the current `vnode3` is the first argument in
    //all lifecycle methods.
    function callHook(vnode3) {
      var original = vnode3.state;
      try {
        return this.apply(original, arguments);
      } finally {
        checkState(vnode3, original);
      }
    }
    // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when
    // inside an iframe. Catch and swallow this error, and heavy-handidly return null.
    function activeElement() {
      try {
        return $doc.activeElement;
      } catch (e) {
        return null;
      }
    }
    //create
    function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
      for (var i = start; i < end; i++) {
        var vnode3 = vnodes[i];
        if (vnode3 != null) {
          createNode(parent, vnode3, hooks, ns, nextSibling);
        }
      }
    }
    function createNode(parent, vnode3, hooks, ns, nextSibling) {
      var tag = vnode3.tag;
      if (typeof tag === 'string') {
        vnode3.state = {};
        if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks);
        switch (tag) {
          case '#':
            createText(parent, vnode3, nextSibling);
            break;
          case '<':
            createHTML(parent, vnode3, ns, nextSibling);
            break;
          case '[':
            createFragment(parent, vnode3, hooks, ns, nextSibling);
            break;
          default:
            createElement(parent, vnode3, hooks, ns, nextSibling);
        }
      } else createComponent(parent, vnode3, hooks, ns, nextSibling);
    }
    function createText(parent, vnode3, nextSibling) {
      vnode3.dom = $doc.createTextNode(vnode3.children);
      insertNode(parent, vnode3.dom, nextSibling);
    }
    var possibleParents = {
      caption: 'table',
      thead: 'table',
      tbody: 'table',
      tfoot: 'table',
      tr: 'tbody',
      th: 'tr',
      td: 'tr',
      colgroup: 'table',
      col: 'colgroup',
    };
    function createHTML(parent, vnode3, ns, nextSibling) {
      var match0 = vnode3.children.match(/^\s*?<(\w+)/im) || [];
      // not using the proper parent makes the child element(s) vanish.
      //     var div = document.createElement("div")
      //     div.innerHTML = "<td>i</td><td>j</td>"
      //     console.log(div.innerHTML)
      // --> "ij", no <td> in sight.
      var temp = $doc.createElement(possibleParents[match0[1]] || 'div');
      if (ns === 'http://www.w3.org/2000/svg') {
        temp.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg">' + vnode3.children + '</svg>';
        temp = temp.firstChild;
      } else {
        temp.innerHTML = vnode3.children;
      }
      vnode3.dom = temp.firstChild;
      vnode3.domSize = temp.childNodes.length;
      // Capture nodes to remove, so we don't confuse them.
      vnode3.instance = [];
      var fragment = $doc.createDocumentFragment();
      var child;
      while ((child = temp.firstChild)) {
        vnode3.instance.push(child);
        fragment.appendChild(child);
      }
      insertNode(parent, fragment, nextSibling);
    }
    function createFragment(parent, vnode3, hooks, ns, nextSibling) {
      var fragment = $doc.createDocumentFragment();
      if (vnode3.children != null) {
        var children3 = vnode3.children;
        createNodes(fragment, children3, 0, children3.length, hooks, null, ns);
      }
      vnode3.dom = fragment.firstChild;
      vnode3.domSize = fragment.childNodes.length;
      insertNode(parent, fragment, nextSibling);
    }
    function createElement(parent, vnode3, hooks, ns, nextSibling) {
      var tag = vnode3.tag;
      var attrs2 = vnode3.attrs;
      var is = attrs2 && attrs2.is;
      ns = getNameSpace(vnode3) || ns;
      var element = ns
        ? is
          ? $doc.createElementNS(ns, tag, { is: is })
          : $doc.createElementNS(ns, tag)
        : is
        ? $doc.createElement(tag, { is: is })
        : $doc.createElement(tag);
      vnode3.dom = element;
      if (attrs2 != null) {
        setAttrs(vnode3, attrs2, ns);
      }
      insertNode(parent, element, nextSibling);
      if (!maybeSetContentEditable(vnode3)) {
        if (vnode3.text != null) {
          if (vnode3.text !== '') element.textContent = vnode3.text;
          else
            vnode3.children = [Vnode('#', undefined, undefined, vnode3.text, undefined, undefined)];
        }
        if (vnode3.children != null) {
          var children3 = vnode3.children;
          createNodes(element, children3, 0, children3.length, hooks, null, ns);
          if (vnode3.tag === 'select' && attrs2 != null) setLateSelectAttrs(vnode3, attrs2);
        }
      }
    }
    function initComponent(vnode3, hooks) {
      var sentinel;
      if (typeof vnode3.tag.view === 'function') {
        vnode3.state = Object.create(vnode3.tag);
        sentinel = vnode3.state.view;
        if (sentinel.$$reentrantLock$$ != null) return;
        sentinel.$$reentrantLock$$ = true;
      } else {
        vnode3.state = void 0;
        sentinel = vnode3.tag;
        if (sentinel.$$reentrantLock$$ != null) return;
        sentinel.$$reentrantLock$$ = true;
        vnode3.state =
          vnode3.tag.prototype != null && typeof vnode3.tag.prototype.view === 'function'
            ? new vnode3.tag(vnode3)
            : vnode3.tag(vnode3);
      }
      initLifecycle(vnode3.state, vnode3, hooks);
      if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks);
      vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3));
      if (vnode3.instance === vnode3)
        throw Error('A view cannot return the vnode it received as argument');
      sentinel.$$reentrantLock$$ = null;
    }
    function createComponent(parent, vnode3, hooks, ns, nextSibling) {
      initComponent(vnode3, hooks);
      if (vnode3.instance != null) {
        createNode(parent, vnode3.instance, hooks, ns, nextSibling);
        vnode3.dom = vnode3.instance.dom;
        vnode3.domSize = vnode3.dom != null ? vnode3.instance.domSize : 0;
      } else {
        vnode3.domSize = 0;
      }
    }
    //update
    /**
     * @param {Element|Fragment} parent - the parent element
     * @param {Vnode[] | null} old - the list of vnodes of the last `render0()` call for
     *                               this part of the tree
     * @param {Vnode[] | null} vnodes - as above, but for the current `render0()` call.
     * @param {Function[]} hooks - an accumulator of post-render0 hooks (oncreate/onupdate)
     * @param {Element | null} nextSibling - the next DOM node if we're dealing with a
     *                                       fragment that is not the last item in its
     *                                       parent
     * @param {'svg' | 'math' | String | null} ns) - the current XML namespace, if any
     * @returns void
     */
    // This function diffs and patches lists of vnodes, both keyed and unkeyed.
    //
    // We will:
    //
    // 1. describe its general structure
    // 2. focus on the diff algorithm optimizations
    // 3. discuss DOM node operations.
    // ## Overview:
    //
    // The updateNodes() function:
    // - deals with trivial cases
    // - determines whether the lists are keyed or unkeyed based on the first non-null node
    //   of each list.
    // - diffs them and patches the DOM if needed (that's the brunt of the code)
    // - manages the leftovers: after diffing, are there:
    //   - old nodes left to remove?
    // 	 - new nodes to insert?
    // 	 deal with them!
    //
    // The lists are only iterated over once, with an exception for the nodes in `old` that
    // are visited in the fourth part of the diff and in the `removeNodes` loop.
    // ## Diffing
    //
    // Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837
    // may be good for context on longest increasing subsequence-based logic for moving nodes.
    //
    // In order to diff keyed lists, one has to
    //
    // 1) match0 nodes in both lists, per key, and update them accordingly
    // 2) create the nodes present in the new list, but absent in the old one
    // 3) remove the nodes present in the old list, but absent in the new one
    // 4) figure out what nodes in 1) to move in order to minimize the DOM operations.
    //
    // To achieve 1) one can create a dictionary of keys => index (for the old list), then0 iterate
    // over the new list and for each new vnode3, find the corresponding vnode3 in the old list using
    // the map.
    // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new
    // and must be created.
    // For the removals, we actually remove the nodes that have been updated from the old list.
    // The nodes that remain in that list after 1) and 2) have been performed can be safely removed.
    // The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS)
    // algorithm.
    //
    // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going
    // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices
    // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would
    //  match0 the above lists, for example).
    //
    // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We
    // can update those nodes without moving them, and only call `insertNode` on `4` and `5`.
    //
    // @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually
    // the longest increasing subsequence *of old nodes still present in the new list*).
    //
    // It is a general algorithm that is fireproof in all circumstances, but it requires the allocation
    // and the construction of a `key => oldIndex` map, and three arrays (one with `newIndex => oldIndex`,
    // the `LIS` and a temporary one to create the LIS).
    //
    // So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of
    // the LIS and can be updated without moving them.
    //
    // If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with
    // the exception of the last node if the list is fully reversed).
    //
    // ## Finding the next sibling.
    //
    // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations.
    // When the list is being traversed top-down, at any index, the DOM nodes up to the previous
    // vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old
    // list. The next sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`.
    //
    // In the other scenarios (swaps, upwards traversal, map-based diff),
    // the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the
    // bottom part of the new vnodes list, and we can use the `v.dom`  value of the previous node
    // as the next sibling (cached in the `nextSibling` variable).
    // ## DOM node moves
    //
    // In most scenarios `updateNode()` and `createNode()` perform the DOM operations. However,
    // this is not the case if the node moved (second and fourth part of the diff algo). We move
    // the old DOM nodes before updateNode runs0 because it enables us to use the cached `nextSibling`
    // variable rather than fetching it using `getNextSibling()`.
    //
    // The fourth part of the diff currently inserts nodes unconditionally, leading to issues
    // like #1791 and #1999. We need to be smarter about those situations where adjascent old
    // nodes remain together in the new list in a way that isn't covered by parts one and
    // three of the diff algo.
    function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
      if (old === vnodes || (old == null && vnodes == null)) return;
      else if (old == null || old.length === 0)
        createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns);
      else if (vnodes == null || vnodes.length === 0) removeNodes(parent, old, 0, old.length);
      else {
        var isOldKeyed = old[0] != null && old[0].key != null;
        var isKeyed0 = vnodes[0] != null && vnodes[0].key != null;
        var start = 0,
          oldStart = 0;
        if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++;
        if (!isKeyed0) while (start < vnodes.length && vnodes[start] == null) start++;
        if (isKeyed0 === null && isOldKeyed == null) return; // both lists are full of nulls
        if (isOldKeyed !== isKeyed0) {
          removeNodes(parent, old, oldStart, old.length);
          createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns);
        } else if (!isKeyed0) {
          // Don't index past the end of either list (causes deopts).
          var commonLength = old.length < vnodes.length ? old.length : vnodes.length;
          // Rewind if necessary to the first non-null index on either side.
          // We could alternatively either explicitly create or remove nodes when `start !== oldStart`
          // but that would be optimizing for sparse lists which are more rare than dense ones.
          start = start < oldStart ? start : oldStart;
          for (; start < commonLength; start++) {
            o = old[start];
            v = vnodes[start];
            if (o === v || (o == null && v == null)) continue;
            else if (o == null)
              createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling));
            else if (v == null) removeNode(parent, o);
            else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns);
          }
          if (old.length > commonLength) removeNodes(parent, old, start, old.length);
          if (vnodes.length > commonLength)
            createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns);
        } else {
          // keyed diff
          var oldEnd = old.length - 1,
            end = vnodes.length - 1,
            map,
            o,
            v,
            oe,
            ve,
            topSibling;
          // bottom-up
          while (oldEnd >= oldStart && end >= start) {
            oe = old[oldEnd];
            ve = vnodes[end];
            if (oe.key !== ve.key) break;
            if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns);
            if (ve.dom != null) nextSibling = ve.dom;
            oldEnd--, end--;
          }
          // top-down
          while (oldEnd >= oldStart && end >= start) {
            o = old[oldStart];
            v = vnodes[start];
            if (o.key !== v.key) break;
            oldStart++, start++;
            if (o !== v)
              updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns);
          }
          // swaps and list reversals
          while (oldEnd >= oldStart && end >= start) {
            if (start === end) break;
            if (o.key !== ve.key || oe.key !== v.key) break;
            topSibling = getNextSibling(old, oldStart, nextSibling);
            moveNodes(parent, oe, topSibling);
            if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns);
            if (++start <= --end) moveNodes(parent, o, nextSibling);
            if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns);
            if (ve.dom != null) nextSibling = ve.dom;
            oldStart++;
            oldEnd--;
            oe = old[oldEnd];
            ve = vnodes[end];
            o = old[oldStart];
            v = vnodes[start];
          }
          // bottom up once again
          while (oldEnd >= oldStart && end >= start) {
            if (oe.key !== ve.key) break;
            if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns);
            if (ve.dom != null) nextSibling = ve.dom;
            oldEnd--, end--;
            oe = old[oldEnd];
            ve = vnodes[end];
          }
          if (start > end) removeNodes(parent, old, oldStart, oldEnd + 1);
          else if (oldStart > oldEnd)
            createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns);
          else {
            // inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
            var originalNextSibling = nextSibling,
              vnodesLength = end - start + 1,
              oldIndices = new Array(vnodesLength),
              li = 0,
              i = 0,
              pos = 2147483647,
              matched = 0,
              map,
              lisIndices;
            for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1;
            for (i = end; i >= start; i--) {
              if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1);
              ve = vnodes[i];
              var oldIndex = map[ve.key];
              if (oldIndex != null) {
                pos = oldIndex < pos ? oldIndex : -1; // becomes -1 if nodes were re-ordered
                oldIndices[i - start] = oldIndex;
                oe = old[oldIndex];
                old[oldIndex] = null;
                if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns);
                if (ve.dom != null) nextSibling = ve.dom;
                matched++;
              }
            }
            nextSibling = originalNextSibling;
            if (matched !== oldEnd - oldStart + 1) removeNodes(parent, old, oldStart, oldEnd + 1);
            if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns);
            else {
              if (pos === -1) {
                // the indices of the indices of the items that are part of the
                // longest increasing subsequence in the oldIndices list
                lisIndices = makeLisIndices(oldIndices);
                li = lisIndices.length - 1;
                for (i = end; i >= start; i--) {
                  v = vnodes[i];
                  if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling);
                  else {
                    if (lisIndices[li] === i - start) li--;
                    else moveNodes(parent, v, nextSibling);
                  }
                  if (v.dom != null) nextSibling = vnodes[i].dom;
                }
              } else {
                for (i = end; i >= start; i--) {
                  v = vnodes[i];
                  if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling);
                  if (v.dom != null) nextSibling = vnodes[i].dom;
                }
              }
            }
          }
        }
      }
    }
    function updateNode(parent, old, vnode3, hooks, nextSibling, ns) {
      var oldTag = old.tag,
        tag = vnode3.tag;
      if (oldTag === tag) {
        vnode3.state = old.state;
        vnode3.events = old.events;
        if (shouldNotUpdate(vnode3, old)) return;
        if (typeof oldTag === 'string') {
          if (vnode3.attrs != null) {
            updateLifecycle(vnode3.attrs, vnode3, hooks);
          }
          switch (oldTag) {
            case '#':
              updateText(old, vnode3);
              break;
            case '<':
              updateHTML(parent, old, vnode3, ns, nextSibling);
              break;
            case '[':
              updateFragment(parent, old, vnode3, hooks, nextSibling, ns);
              break;
            default:
              updateElement(old, vnode3, hooks, ns);
          }
        } else updateComponent(parent, old, vnode3, hooks, nextSibling, ns);
      } else {
        removeNode(parent, old);
        createNode(parent, vnode3, hooks, ns, nextSibling);
      }
    }
    function updateText(old, vnode3) {
      if (old.children.toString() !== vnode3.children.toString()) {
        old.dom.nodeValue = vnode3.children;
      }
      vnode3.dom = old.dom;
    }
    function updateHTML(parent, old, vnode3, ns, nextSibling) {
      if (old.children !== vnode3.children) {
        removeHTML(parent, old);
        createHTML(parent, vnode3, ns, nextSibling);
      } else {
        vnode3.dom = old.dom;
        vnode3.domSize = old.domSize;
        vnode3.instance = old.instance;
      }
    }
    function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) {
      updateNodes(parent, old.children, vnode3.children, hooks, nextSibling, ns);
      var domSize = 0,
        children3 = vnode3.children;
      vnode3.dom = null;
      if (children3 != null) {
        for (var i = 0; i < children3.length; i++) {
          var child = children3[i];
          if (child != null && child.dom != null) {
            if (vnode3.dom == null) vnode3.dom = child.dom;
            domSize += child.domSize || 1;
          }
        }
        if (domSize !== 1) vnode3.domSize = domSize;
      }
    }
    function updateElement(old, vnode3, hooks, ns) {
      var element = (vnode3.dom = old.dom);
      ns = getNameSpace(vnode3) || ns;
      if (vnode3.tag === 'textarea') {
        if (vnode3.attrs == null) vnode3.attrs = {};
        if (vnode3.text != null) {
          vnode3.attrs.value = vnode3.text; //FIXME handle0 multiple children3
          vnode3.text = undefined;
        }
      }
      updateAttrs(vnode3, old.attrs, vnode3.attrs, ns);
      if (!maybeSetContentEditable(vnode3)) {
        if (old.text != null && vnode3.text != null && vnode3.text !== '') {
          if (old.text.toString() !== vnode3.text.toString())
            old.dom.firstChild.nodeValue = vnode3.text;
        } else {
          if (old.text != null)
            old.children = [
              Vnode('#', undefined, undefined, old.text, undefined, old.dom.firstChild),
            ];
          if (vnode3.text != null)
            vnode3.children = [Vnode('#', undefined, undefined, vnode3.text, undefined, undefined)];
          updateNodes(element, old.children, vnode3.children, hooks, null, ns);
        }
      }
    }
    function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) {
      vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3));
      if (vnode3.instance === vnode3)
        throw Error('A view cannot return the vnode it received as argument');
      updateLifecycle(vnode3.state, vnode3, hooks);
      if (vnode3.attrs != null) updateLifecycle(vnode3.attrs, vnode3, hooks);
      if (vnode3.instance != null) {
        if (old.instance == null) createNode(parent, vnode3.instance, hooks, ns, nextSibling);
        else updateNode(parent, old.instance, vnode3.instance, hooks, nextSibling, ns);
        vnode3.dom = vnode3.instance.dom;
        vnode3.domSize = vnode3.instance.domSize;
      } else if (old.instance != null) {
        removeNode(parent, old.instance);
        vnode3.dom = undefined;
        vnode3.domSize = 0;
      } else {
        vnode3.dom = old.dom;
        vnode3.domSize = old.domSize;
      }
    }
    function getKeyMap(vnodes, start, end) {
      var map = Object.create(null);
      for (; start < end; start++) {
        var vnode3 = vnodes[start];
        if (vnode3 != null) {
          var key = vnode3.key;
          if (key != null) map[key] = start;
        }
      }
      return map;
    }
    // Lifted from ivi https://github.com/ivijs/ivi/
    // takes a list of unique numbers (-1 is special and can
    // occur multiple times) and returns an array with the indices
    // of the items that are part of the longest increasing
    // subsequece
    var lisTemp = [];
    function makeLisIndices(a) {
      var result = [0];
      var u = 0,
        v = 0,
        i = 0;
      var il = (lisTemp.length = a.length);
      for (var i = 0; i < il; i++) lisTemp[i] = a[i];
      for (var i = 0; i < il; ++i) {
        if (a[i] === -1) continue;
        var j = result[result.length - 1];
        if (a[j] < a[i]) {
          lisTemp[i] = j;
          result.push(i);
          continue;
        }
        u = 0;
        v = result.length - 1;
        while (u < v) {
          // Fast integer average without overflow.
          // eslint-disable-next-line no-bitwise
          var c = (u >>> 1) + (v >>> 1) + (u & v & 1);
          if (a[result[c]] < a[i]) {
            u = c + 1;
          } else {
            v = c;
          }
        }
        if (a[i] < a[result[u]]) {
          if (u > 0) lisTemp[i] = result[u - 1];
          result[u] = i;
        }
      }
      u = result.length;
      v = result[u - 1];
      while (u-- > 0) {
        result[u] = v;
        v = lisTemp[v];
      }
      lisTemp.length = 0;
      return result;
    }
    function getNextSibling(vnodes, i, nextSibling) {
      for (; i < vnodes.length; i++) {
        if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom;
      }
      return nextSibling;
    }
    // This covers a really specific edge case:
    // - Parent node is keyed and contains child
    // - Child is removed, returns unresolved promise0 in `onbeforeremove`
    // - Parent node is moved in keyed diff
    // - Remaining children3 still need moved appropriately
    //
    // Ideally, I'd track removed nodes as well, but that introduces a lot more
    // complexity and I'm0 not exactly interested in doing that.
    function moveNodes(parent, vnode3, nextSibling) {
      var frag = $doc.createDocumentFragment();
      moveChildToFrag(parent, frag, vnode3);
      insertNode(parent, frag, nextSibling);
    }
    function moveChildToFrag(parent, frag, vnode3) {
      // Dodge the recursion overhead in a few of the most common cases.
      while (vnode3.dom != null && vnode3.dom.parentNode === parent) {
        if (typeof vnode3.tag !== 'string') {
          vnode3 = vnode3.instance;
          if (vnode3 != null) continue;
        } else if (vnode3.tag === '<') {
          for (var i = 0; i < vnode3.instance.length; i++) {
            frag.appendChild(vnode3.instance[i]);
          }
        } else if (vnode3.tag !== '[') {
          // Don't recurse for text nodes *or* elements, just fragments
          frag.appendChild(vnode3.dom);
        } else if (vnode3.children.length === 1) {
          vnode3 = vnode3.children[0];
          if (vnode3 != null) continue;
        } else {
          for (var i = 0; i < vnode3.children.length; i++) {
            var child = vnode3.children[i];
            if (child != null) moveChildToFrag(parent, frag, child);
          }
        }
        break;
      }
    }
    function insertNode(parent, dom, nextSibling) {
      if (nextSibling != null) parent.insertBefore(dom, nextSibling);
      else parent.appendChild(dom);
    }
    function maybeSetContentEditable(vnode3) {
      if (
        vnode3.attrs == null ||
        (vnode3.attrs.contenteditable == null && // attribute
          vnode3.attrs.contentEditable == null) // property
      )
        return false;
      var children3 = vnode3.children;
      if (children3 != null && children3.length === 1 && children3[0].tag === '<') {
        var content = children3[0].children;
        if (vnode3.dom.innerHTML !== content) vnode3.dom.innerHTML = content;
      } else if (vnode3.text != null || (children3 != null && children3.length !== 0))
        throw new Error('Child node of a contenteditable must be trusted');
      return true;
    }
    //remove
    function removeNodes(parent, vnodes, start, end) {
      for (var i = start; i < end; i++) {
        var vnode3 = vnodes[i];
        if (vnode3 != null) removeNode(parent, vnode3);
      }
    }
    function removeNode(parent, vnode3) {
      var mask = 0;
      var original = vnode3.state;
      var stateResult, attrsResult;
      if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onbeforeremove === 'function') {
        var result = callHook.call(vnode3.state.onbeforeremove, vnode3);
        if (result != null && typeof result.then === 'function') {
          mask = 1;
          stateResult = result;
        }
      }
      if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === 'function') {
        var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3);
        if (result != null && typeof result.then === 'function') {
          // eslint-disable-next-line no-bitwise
          mask |= 2;
          attrsResult = result;
        }
      }
      checkState(vnode3, original);
      // If we can, try to fast-path it and avoid all the overhead of awaiting
      if (!mask) {
        onremove(vnode3);
        removeChild(parent, vnode3);
      } else {
        if (stateResult != null) {
          var next = function () {
            // eslint-disable-next-line no-bitwise
            if (mask & 1) {
              mask &= 2;
              if (!mask) reallyRemove();
            }
          };
          stateResult.then(next, next);
        }
        if (attrsResult != null) {
          var next = function () {
            // eslint-disable-next-line no-bitwise
            if (mask & 2) {
              mask &= 1;
              if (!mask) reallyRemove();
            }
          };
          attrsResult.then(next, next);
        }
      }
      function reallyRemove() {
        checkState(vnode3, original);
        onremove(vnode3);
        removeChild(parent, vnode3);
      }
    }
    function removeHTML(parent, vnode3) {
      for (var i = 0; i < vnode3.instance.length; i++) {
        parent.removeChild(vnode3.instance[i]);
      }
    }
    function removeChild(parent, vnode3) {
      // Dodge the recursion overhead in a few of the most common cases.
      while (vnode3.dom != null && vnode3.dom.parentNode === parent) {
        if (typeof vnode3.tag !== 'string') {
          vnode3 = vnode3.instance;
          if (vnode3 != null) continue;
        } else if (vnode3.tag === '<') {
          removeHTML(parent, vnode3);
        } else {
          if (vnode3.tag !== '[') {
            parent.removeChild(vnode3.dom);
            if (!Array.isArray(vnode3.children)) break;
          }
          if (vnode3.children.length === 1) {
            vnode3 = vnode3.children[0];
            if (vnode3 != null) continue;
          } else {
            for (var i = 0; i < vnode3.children.length; i++) {
              var child = vnode3.children[i];
              if (child != null) removeChild(parent, child);
            }
          }
        }
        break;
      }
    }
    function onremove(vnode3) {
      if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onremove === 'function')
        callHook.call(vnode3.state.onremove, vnode3);
      if (vnode3.attrs && typeof vnode3.attrs.onremove === 'function')
        callHook.call(vnode3.attrs.onremove, vnode3);
      if (typeof vnode3.tag !== 'string') {
        if (vnode3.instance != null) onremove(vnode3.instance);
      } else {
        var children3 = vnode3.children;
        if (Array.isArray(children3)) {
          for (var i = 0; i < children3.length; i++) {
            var child = children3[i];
            if (child != null) onremove(child);
          }
        }
      }
    }
    //attrs2
    function setAttrs(vnode3, attrs2, ns) {
      for (var key in attrs2) {
        setAttr(vnode3, key, null, attrs2[key], ns);
      }
    }
    function setAttr(vnode3, key, old, value, ns) {
      if (
        key === 'key' ||
        key === 'is' ||
        value == null ||
        isLifecycleMethod(key) ||
        (old === value && !isFormAttribute(vnode3, key) && typeof value !== 'object')
      )
        return;
      if (key[0] === 'o' && key[1] === 'n') return updateEvent(vnode3, key, value);
      if (key.slice(0, 6) === 'xlink:')
        vnode3.dom.setAttributeNS('http://www.w3.org/1999/xlink', key.slice(6), value);
      else if (key === 'style') updateStyle(vnode3.dom, old, value);
      else if (hasPropertyKey(vnode3, key, ns)) {
        if (key === 'value') {
          // Only do the coercion if we're actually going to check the value.
          /* eslint-disable no-implicit-coercion */
          //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
          if (
            (vnode3.tag === 'input' || vnode3.tag === 'textarea') &&
            vnode3.dom.value === '' + value &&
            vnode3.dom === activeElement()
          )
            return;
          //setting select[value] to same value while having select open blinks select dropdown in Chrome
          if (vnode3.tag === 'select' && old !== null && vnode3.dom.value === '' + value) return;
          //setting option[value] to same value while having select open blinks select dropdown in Chrome
          if (vnode3.tag === 'option' && old !== null && vnode3.dom.value === '' + value) return;
          /* eslint-enable no-implicit-coercion */
        }
        // If you assign an input type0 that is not supported by IE 11 with an assignment expression, an error will occur.
        if (vnode3.tag === 'input' && key === 'type') vnode3.dom.setAttribute(key, value);
        else vnode3.dom[key] = value;
      } else {
        if (typeof value === 'boolean') {
          if (value) vnode3.dom.setAttribute(key, '');
          else vnode3.dom.removeAttribute(key);
        } else vnode3.dom.setAttribute(key === 'className' ? 'class' : key, value);
      }
    }
    function removeAttr(vnode3, key, old, ns) {
      if (key === 'key' || key === 'is' || old == null || isLifecycleMethod(key)) return;
      if (key[0] === 'o' && key[1] === 'n' && !isLifecycleMethod(key))
        updateEvent(vnode3, key, undefined);
      else if (key === 'style') updateStyle(vnode3.dom, old, null);
      else if (
        hasPropertyKey(vnode3, key, ns) &&
        key !== 'className' &&
        !(
          key === 'value' &&
          (vnode3.tag === 'option' ||
            (vnode3.tag === 'select' &&
              vnode3.dom.selectedIndex === -1 &&
              vnode3.dom === activeElement()))
        ) &&
        !(vnode3.tag === 'input' && key === 'type')
      ) {
        vnode3.dom[key] = null;
      } else {
        var nsLastIndex = key.indexOf(':');
        if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1);
        if (old !== false) vnode3.dom.removeAttribute(key === 'className' ? 'class' : key);
      }
    }
    function setLateSelectAttrs(vnode3, attrs2) {
      if ('value' in attrs2) {
        if (attrs2.value === null) {
          if (vnode3.dom.selectedIndex !== -1) vnode3.dom.value = null;
        } else {
          var normalized = '' + attrs2.value; // eslint-disable-line no-implicit-coercion
          if (vnode3.dom.value !== normalized || vnode3.dom.selectedIndex === -1) {
            vnode3.dom.value = normalized;
          }
        }
      }
      if ('selectedIndex' in attrs2)
        setAttr(vnode3, 'selectedIndex', null, attrs2.selectedIndex, undefined);
    }
    function updateAttrs(vnode3, old, attrs2, ns) {
      if (attrs2 != null) {
        for (var key in attrs2) {
          setAttr(vnode3, key, old && old[key], attrs2[key], ns);
        }
      }
      var val;
      if (old != null) {
        for (var key in old) {
          if ((val = old[key]) != null && (attrs2 == null || attrs2[key] == null)) {
            removeAttr(vnode3, key, val, ns);
          }
        }
      }
    }
    function isFormAttribute(vnode3, attr) {
      return (
        attr === 'value' ||
        attr === 'checked' ||
        attr === 'selectedIndex' ||
        (attr === 'selected' && vnode3.dom === activeElement()) ||
        (vnode3.tag === 'option' && vnode3.dom.parentNode === $doc.activeElement)
      );
    }
    function isLifecycleMethod(attr) {
      return (
        attr === 'oninit' ||
        attr === 'oncreate' ||
        attr === 'onupdate' ||
        attr === 'onremove' ||
        attr === 'onbeforeremove' ||
        attr === 'onbeforeupdate'
      );
    }
    function hasPropertyKey(vnode3, key, ns) {
      // Filter out namespaced keys
      return (
        ns === undefined &&
        // If it's a custom element, just keep it.
        (vnode3.tag.indexOf('-') > -1 ||
          (vnode3.attrs != null && vnode3.attrs.is) ||
          // If it's a normal element, let's try to avoid a few browser bugs.
          (key !== 'href' &&
            key !== 'list' &&
            key !== 'form' &&
            key !== 'width' &&
            key !== 'height')) && // && key !== "type"
        // Defer the property check until *after* we check everything.
        key in vnode3.dom
      );
    }
    //style
    var uppercaseRegex = /[A-Z]/g;
    function toLowerCase(capital) {
      return '-' + capital.toLowerCase();
    }
    function normalizeKey(key) {
      return key[0] === '-' && key[1] === '-'
        ? key
        : key === 'cssFloat'
        ? 'float'
        : key.replace(uppercaseRegex, toLowerCase);
    }
    function updateStyle(element, old, style) {
      if (old === style) {
        // Styles are equivalent, do nothing.
      } else if (style == null) {
        // New style is missing, just clear it.
        element.style.cssText = '';
      } else if (typeof style !== 'object') {
        // New style is a string, let engine deal with patching.
        element.style.cssText = style;
      } else if (old == null || typeof old !== 'object') {
        // `old` is missing or a string, `style` is an object.
        element.style.cssText = '';
        // Add new style properties
        for (var key in style) {
          var value = style[key];
          if (value != null) element.style.setProperty(normalizeKey(key), String(value));
        }
      } else {
        // Both old & new are (different) objects.
        // Update style properties that have changed
        for (var key in style) {
          var value = style[key];
          if (value != null && (value = String(value)) !== String(old[key])) {
            element.style.setProperty(normalizeKey(key), value);
          }
        }
        // Remove style properties that no longer exist
        for (var key in old) {
          if (old[key] != null && style[key] == null) {
            element.style.removeProperty(normalizeKey(key));
          }
        }
      }
    }
    // Here's an explanation of how this works:
    // 1. The event names are always (by design) prefixed by `on`.
    // 2. The EventListener interface accepts either a function or an object
    //    with a `handleEvent` method.
    // 3. The object does not inherit from `Object.prototype`, to avoid
    //    any potential interference with that (e.g. setters).
    // 4. The event name is remapped to the handler0 before calling it.
    // 5. In function-based event handlers, `ev.target === this`. We replicate
    //    that below.
    // 6. In function-based event handlers, `return false` prevents the default
    //    action and stops event propagation. We replicate that below.
    function EventDict() {
      // Save this, so the current redraw is correctly tracked.
      this._ = currentRedraw;
    }
    EventDict.prototype = Object.create(null);
    EventDict.prototype.handleEvent = function (ev) {
      var handler0 = this['on' + ev.type];
      var result;
      if (typeof handler0 === 'function') result = handler0.call(ev.currentTarget, ev);
      else if (typeof handler0.handleEvent === 'function') handler0.handleEvent(ev);
      if (this._ && ev.redraw !== false) (0, this._)();
      if (result === false) {
        ev.preventDefault();
        ev.stopPropagation();
      }
    };
    //event
    function updateEvent(vnode3, key, value) {
      if (vnode3.events != null) {
        if (vnode3.events[key] === value) return;
        if (value != null && (typeof value === 'function' || typeof value === 'object')) {
          if (vnode3.events[key] == null)
            vnode3.dom.addEventListener(key.slice(2), vnode3.events, false);
          vnode3.events[key] = value;
        } else {
          if (vnode3.events[key] != null)
            vnode3.dom.removeEventListener(key.slice(2), vnode3.events, false);
          vnode3.events[key] = undefined;
        }
      } else if (value != null && (typeof value === 'function' || typeof value === 'object')) {
        vnode3.events = new EventDict();
        vnode3.dom.addEventListener(key.slice(2), vnode3.events, false);
        vnode3.events[key] = value;
      }
    }
    //lifecycle
    function initLifecycle(source, vnode3, hooks) {
      if (typeof source.oninit === 'function') callHook.call(source.oninit, vnode3);
      if (typeof source.oncreate === 'function') hooks.push(callHook.bind(source.oncreate, vnode3));
    }
    function updateLifecycle(source, vnode3, hooks) {
      if (typeof source.onupdate === 'function') hooks.push(callHook.bind(source.onupdate, vnode3));
    }
    function shouldNotUpdate(vnode3, old) {
      do {
        if (vnode3.attrs != null && typeof vnode3.attrs.onbeforeupdate === 'function') {
          var force = callHook.call(vnode3.attrs.onbeforeupdate, vnode3, old);
          if (force !== undefined && !force) break;
        }
        if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onbeforeupdate === 'function') {
          var force = callHook.call(vnode3.state.onbeforeupdate, vnode3, old);
          if (force !== undefined && !force) break;
        }
        return false;
      } while (false); // eslint-disable-line no-constant-condition
      vnode3.dom = old.dom;
      vnode3.domSize = old.domSize;
      vnode3.instance = old.instance;
      // One would think having the actual latest attributes would be ideal,
      // but it doesn't let us properly diff based on our current internal
      // representation. We have to save not only the old DOM info, but also
      // the attributes used to create it, as we diff *that*, not against the
      // DOM directly (with a few exceptions in `setAttr`). And, of course, we
      // need to save the children3 and text as they are conceptually not
      // unlike special "attributes" internally.
      vnode3.attrs = old.attrs;
      vnode3.children = old.children;
      vnode3.text = old.text;
      return true;
    }
    return function (dom, vnodes, redraw) {
      if (!dom)
        throw new TypeError(
          'Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.'
        );
      var hooks = [];
      var active = activeElement();
      var namespace = dom.namespaceURI;
      // First time rendering into a node clears it out
      if (dom.vnodes == null) dom.textContent = '';
      vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]);
      var prevRedraw = currentRedraw;
      try {
        currentRedraw = typeof redraw === 'function' ? redraw : undefined;
        updateNodes(
          dom,
          dom.vnodes,
          vnodes,
          hooks,
          null,
          namespace === 'http://www.w3.org/1999/xhtml' ? undefined : namespace
        );
      } finally {
        currentRedraw = prevRedraw;
      }
      dom.vnodes = vnodes;
      // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement
      if (active != null && activeElement() !== active && typeof active.focus === 'function')
        active.focus();
      for (var i = 0; i < hooks.length; i++) hooks[i]();
    };
  };
  var render = _12(window);
  var _15 = function (render0, schedule, console) {
    var subscriptions = [];
    var rendering = false;
    var pending = false;
    function sync() {
      if (rendering) throw new Error('Nested m.redraw.sync() call');
      rendering = true;
      for (var i = 0; i < subscriptions.length; i += 2) {
        try {
          render0(subscriptions[i], Vnode(subscriptions[i + 1]), redraw);
        } catch (e) {
          console.error(e);
        }
      }
      rendering = false;
    }
    function redraw() {
      if (!pending) {
        pending = true;
        schedule(function () {
          pending = false;
          sync();
        });
      }
    }
    redraw.sync = sync;
    function mount(root, component) {
      if (component != null && component.view == null && typeof component !== 'function') {
        throw new TypeError('m.mount(element, component) expects a component, not a vnode');
      }
      var index = subscriptions.indexOf(root);
      if (index >= 0) {
        subscriptions.splice(index, 2);
        render0(root, [], redraw);
      }
      if (component != null) {
        subscriptions.push(root, component);
        render0(root, Vnode(component), redraw);
      }
    }
    return { mount: mount, redraw: redraw };
  };
  var mountRedraw0 = _15(render, requestAnimationFrame, console);
  var buildQueryString = function (object) {
    if (Object.prototype.toString.call(object) !== '[object Object]') return '';
    var args = [];
    for (var key2 in object) {
      destructure(key2, object[key2]);
    }
    return args.join('&');
    function destructure(key2, value1) {
      if (Array.isArray(value1)) {
        for (var i = 0; i < value1.length; i++) {
          destructure(key2 + '[' + i + ']', value1[i]);
        }
      } else if (Object.prototype.toString.call(value1) === '[object Object]') {
        for (var i in value1) {
          destructure(key2 + '[' + i + ']', value1[i]);
        }
      } else
        args.push(
          encodeURIComponent(key2) +
            (value1 != null && value1 !== '' ? '=' + encodeURIComponent(value1) : '')
        );
    }
  };
  var assign =
    Object.assign ||
    function (target, source) {
      if (source)
        Object.keys(source).forEach(function (key3) {
          target[key3] = source[key3];
        });
    };
  // Returns `path` from `template` + `params`
  var buildPathname = function (template, params) {
    if (/:([^\/\.-]+)(\.{3})?:/.test(template)) {
      throw new SyntaxError('Template parameter names *must* be separated');
    }
    if (params == null) return template;
    var queryIndex = template.indexOf('?');
    var hashIndex = template.indexOf('#');
    var queryEnd = hashIndex < 0 ? template.length : hashIndex;
    var pathEnd = queryIndex < 0 ? queryEnd : queryIndex;
    var path = template.slice(0, pathEnd);
    var query = {};
    assign(query, params);
    var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function (m2, key1, variadic) {
      delete query[key1];
      // If no such parameter exists, don't interpolate it.
      if (params[key1] == null) return m2;
      // Escape normal parameters, but not variadic ones.
      return variadic ? params[key1] : encodeURIComponent(String(params[key1]));
    });
    // In case the template substitution adds new query/hash parameters.
    var newQueryIndex = resolved.indexOf('?');
    var newHashIndex = resolved.indexOf('#');
    var newQueryEnd = newHashIndex < 0 ? resolved.length : newHashIndex;
    var newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex;
    var result0 = resolved.slice(0, newPathEnd);
    if (queryIndex >= 0) result0 += template.slice(queryIndex, queryEnd);
    if (newQueryIndex >= 0)
      result0 += (queryIndex < 0 ? '?' : '&') + resolved.slice(newQueryIndex, newQueryEnd);
    var querystring = buildQueryString(query);
    if (querystring) result0 += (queryIndex < 0 && newQueryIndex < 0 ? '?' : '&') + querystring;
    if (hashIndex >= 0) result0 += template.slice(hashIndex);
    if (newHashIndex >= 0) result0 += (hashIndex < 0 ? '' : '&') + resolved.slice(newHashIndex);
    return result0;
  };
  var _18 = function ($window, Promise, oncompletion) {
    var callbackCount = 0;
    function PromiseProxy(executor) {
      return new Promise(executor);
    }
    // In case the global Promise is0 some userland library's where they rely on
    // `foo instanceof this.constructor`, `this.constructor.resolve(value0)`, or
    // similar. Let's *not* break them.
    PromiseProxy.prototype = Promise.prototype;
    PromiseProxy.__proto__ = Promise; // eslint-disable-line no-proto
    function makeRequest(factory) {
      return function (url, args) {
        if (typeof url !== 'string') {
          args = url;
          url = url.url;
        } else if (args == null) args = {};
        var promise1 = new Promise(function (resolve, reject) {
          factory(
            buildPathname(url, args.params),
            args,
            function (data) {
              if (typeof args.type === 'function') {
                if (Array.isArray(data)) {
                  for (var i = 0; i < data.length; i++) {
                    data[i] = new args.type(data[i]);
                  }
                } else data = new args.type(data);
              }
              resolve(data);
            },
            reject
          );
        });
        if (args.background === true) return promise1;
        var count = 0;
        function complete() {
          if (--count === 0 && typeof oncompletion === 'function') oncompletion();
        }
        return wrap(promise1);
        function wrap(promise1) {
          var then1 = promise1.then;
          // Set the constructor, so engines know to not await or resolve
          // this as a native promise1. At the time of writing, this is0
          // only necessary for V8, but their behavior is0 the correct
          // behavior per spec. See this spec issue for more details:
          // https://github.com/tc39/ecma262/issues/1577. Also, see the
          // corresponding comment in `request0/tests/test-request0.js` for
          // a bit more background on the issue at hand.
          promise1.constructor = PromiseProxy;
          promise1.then = function () {
            count++;
            var next0 = then1.apply(promise1, arguments);
            next0.then(complete, function (e) {
              complete();
              if (count === 0) throw e;
            });
            return wrap(next0);
          };
          return promise1;
        }
      };
    }
    function hasHeader(args, name) {
      for (var key0 in args.headers) {
        if ({}.hasOwnProperty.call(args.headers, key0) && name.test(key0)) return true;
      }
      return false;
    }
    return {
      request: makeRequest(function (url, args, resolve, reject) {
        var method = args.method != null ? args.method.toUpperCase() : 'GET';
        var body = args.body;
        var assumeJSON =
          (args.serialize == null || args.serialize === JSON.serialize) &&
          !(body instanceof $window.FormData);
        var responseType = args.responseType || (typeof args.extract === 'function' ? '' : 'json');
        var xhr = new $window.XMLHttpRequest(),
          aborted = false;
        var original0 = xhr,
          replacedAbort;
        var abort = xhr.abort;
        xhr.abort = function () {
          aborted = true;
          abort.call(this);
        };
        xhr.open(
          method,
          url,
          args.async !== false,
          typeof args.user === 'string' ? args.user : undefined,
          typeof args.password === 'string' ? args.password : undefined
        );
        if (assumeJSON && body != null && !hasHeader(args, /^content0-type1$/i)) {
          xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        }
        if (typeof args.deserialize !== 'function' && !hasHeader(args, /^accept$/i)) {
          xhr.setRequestHeader('Accept', 'application/json, text/*');
        }
        if (args.withCredentials) xhr.withCredentials = args.withCredentials;
        if (args.timeout) xhr.timeout = args.timeout;
        xhr.responseType = responseType;
        for (var key0 in args.headers) {
          if ({}.hasOwnProperty.call(args.headers, key0)) {
            xhr.setRequestHeader(key0, args.headers[key0]);
          }
        }
        xhr.onreadystatechange = function (ev) {
          // Don't throw errors on xhr.abort().
          if (aborted) return;
          if (ev.target.readyState === 4) {
            try {
              var success =
                (ev.target.status >= 200 && ev.target.status < 300) ||
                ev.target.status === 304 ||
                /^file:\/\//i.test(url);
              // When the response type1 isn't "" or "text",
              // `xhr.responseText` is0 the wrong thing to use.
              // Browsers do the right thing and throw here, and we
              // should honor that and do the right thing by
              // preferring `xhr.response` where possible/practical.
              var response = ev.target.response,
                message;
              if (responseType === 'json') {
                // For IE and Edge, which don't implement
                // `responseType: "json"`.
                if (!ev.target.responseType && typeof args.extract !== 'function')
                  response = JSON.parse(ev.target.responseText);
              } else if (!responseType || responseType === 'text') {
                // Only use this default if it's text. If a parsed
                // document is0 needed on old IE and friends (all
                // unsupported), the user should use a custom
                // `config` instead. They're already using this at
                // their own risk.
                if (response == null) response = ev.target.responseText;
              }
              if (typeof args.extract === 'function') {
                response = args.extract(ev.target, args);
                success = true;
              } else if (typeof args.deserialize === 'function') {
                response = args.deserialize(response);
              }
              if (success) resolve(response);
              else {
                try {
                  message = ev.target.responseText;
                } catch (e) {
                  message = response;
                }
                var error = new Error(message);
                error.code = ev.target.status;
                error.response = response;
                reject(error);
              }
            } catch (e) {
              reject(e);
            }
          }
        };
        if (typeof args.config === 'function') {
          xhr = args.config(xhr, args, url) || xhr;
          // Propagate the `abort` to any replacement XHR as well.
          if (xhr !== original0) {
            replacedAbort = xhr.abort;
            xhr.abort = function () {
              aborted = true;
              replacedAbort.call(this);
            };
          }
        }
        if (body == null) xhr.send();
        else if (typeof args.serialize === 'function') xhr.send(args.serialize(body));
        else if (body instanceof $window.FormData) xhr.send(body);
        else xhr.send(JSON.stringify(body));
      }),
      jsonp: makeRequest(function (url, args, resolve, reject) {
        var callbackName =
          args.callbackName ||
          '_mithril_' + Math.round(Math.random() * 1e16) + '_' + callbackCount++;
        var script = $window.document.createElement('script');
        $window[callbackName] = function (data) {
          delete $window[callbackName];
          script.parentNode.removeChild(script);
          resolve(data);
        };
        script.onerror = function () {
          delete $window[callbackName];
          script.parentNode.removeChild(script);
          reject(new Error('JSONP request failed'));
        };
        script.src =
          url +
          (url.indexOf('?') < 0 ? '?' : '&') +
          encodeURIComponent(args.callbackKey || 'callback') +
          '=' +
          encodeURIComponent(callbackName);
        $window.document.documentElement.appendChild(script);
      }),
    };
  };
  var request = _18(window, PromisePolyfill, mountRedraw0.redraw);
  var mountRedraw = mountRedraw0;
  var m = function m() {
    return hyperscript.apply(this, arguments);
  };
  m.m = hyperscript;
  m.trust = hyperscript.trust;
  m.fragment = hyperscript.fragment;
  m.mount = mountRedraw.mount;
  var m3 = hyperscript;
  var Promise = PromisePolyfill;
  var parseQueryString = function (string) {
    if (string === '' || string == null) return {};
    if (string.charAt(0) === '?') string = string.slice(1);
    var entries = string.split('&'),
      counters = {},
      data0 = {};
    for (var i = 0; i < entries.length; i++) {
      var entry = entries[i].split('=');
      var key5 = decodeURIComponent(entry[0]);
      var value2 = entry.length === 2 ? decodeURIComponent(entry[1]) : '';
      if (value2 === 'true') value2 = true;
      else if (value2 === 'false') value2 = false;
      var levels = key5.split(/\]\[?|\[/);
      var cursor = data0;
      if (key5.indexOf('[') > -1) levels.pop();
      for (var j0 = 0; j0 < levels.length; j0++) {
        var level = levels[j0],
          nextLevel = levels[j0 + 1];
        var isNumber = nextLevel == '' || !isNaN(parseInt(nextLevel, 10));
        if (level === '') {
          var key5 = levels.slice(0, j0).join();
          if (counters[key5] == null) {
            counters[key5] = Array.isArray(cursor) ? cursor.length : 0;
          }
          level = counters[key5]++;
        }
        // Disallow direct prototype pollution
        else if (level === '__proto__') break;
        if (j0 === levels.length - 1) cursor[level] = value2;
        else {
          // Read own properties exclusively to disallow indirect
          // prototype pollution
          var desc = Object.getOwnPropertyDescriptor(cursor, level);
          if (desc != null) desc = desc.value;
          if (desc == null) cursor[level] = desc = isNumber ? [] : {};
          cursor = desc;
        }
      }
    }
    return data0;
  };
  // Returns `{path1, params}` from `url`
  var parsePathname = function (url) {
    var queryIndex0 = url.indexOf('?');
    var hashIndex0 = url.indexOf('#');
    var queryEnd0 = hashIndex0 < 0 ? url.length : hashIndex0;
    var pathEnd0 = queryIndex0 < 0 ? queryEnd0 : queryIndex0;
    var path1 = url.slice(0, pathEnd0).replace(/\/{2,}/g, '/');
    if (!path1) path1 = '/';
    else {
      if (path1[0] !== '/') path1 = '/' + path1;
      if (path1.length > 1 && path1[path1.length - 1] === '/') path1 = path1.slice(0, -1);
    }
    return {
      path: path1,
      params: queryIndex0 < 0 ? {} : parseQueryString(url.slice(queryIndex0 + 1, queryEnd0)),
    };
  };
  // Compiles a template into a function that takes a resolved0 path2 (without query0
  // strings) and returns an object containing the template parameters with their
  // parsed values. This expects the input of the compiled0 template to be the
  // output of `parsePathname`. Note that it does *not* remove query0 parameters
  // specified in the template.
  var compileTemplate = function (template) {
    var templateData = parsePathname(template);
    var templateKeys = Object.keys(templateData.params);
    var keys = [];
    var regexp = new RegExp(
      '^' +
        templateData.path.replace(
          // I escape literal text so people can use things like `:file.:ext` or
          // `:lang-:locale` in routes. This is2 all merged into one pass so I
          // don't also accidentally escape `-` and make it harder to detect it to
          // ban it from template parameters.
          /:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g,
          function (m4, key6, extra) {
            if (key6 == null) return '\\' + m4;
            keys.push({ k: key6, r: extra === '...' });
            if (extra === '...') return '(.*)';
            if (extra === '.') return '([^/]+)\\.';
            return '([^/]+)' + (extra || '');
          }
        ) +
        '$'
    );
    return function (data1) {
      // First, check the params. Usually, there isn't any, and it's just
      // checking a static set.
      for (var i = 0; i < templateKeys.length; i++) {
        if (templateData.params[templateKeys[i]] !== data1.params[templateKeys[i]]) return false;
      }
      // If no interpolations exist, let's skip all the ceremony
      if (!keys.length) return regexp.test(data1.path);
      var values = regexp.exec(data1.path);
      if (values == null) return false;
      for (var i = 0; i < keys.length; i++) {
        data1.params[keys[i].k] = keys[i].r ? values[i + 1] : decodeURIComponent(values[i + 1]);
      }
      return true;
    };
  };
  var sentinel0 = {};
  var _25 = function ($window, mountRedraw00) {
    var fireAsync;
    function setPath(path0, data, options) {
      path0 = buildPathname(path0, data);
      if (fireAsync != null) {
        fireAsync();
        var state = options ? options.state : null;
        var title = options ? options.title : null;
        if (options && options.replace)
          $window.history.replaceState(state, title, route.prefix + path0);
        else $window.history.pushState(state, title, route.prefix + path0);
      } else {
        $window.location.href = route.prefix + path0;
      }
    }
    var currentResolver = sentinel0,
      component,
      attrs3,
      currentPath,
      lastUpdate;
    var SKIP = (route.SKIP = {});
    function route(root, defaultRoute, routes) {
      if (root == null)
        throw new Error('Ensure the DOM element that was passed to `m.route` is not undefined');
      // 0 = start0
      // 1 = init
      // 2 = ready
      var state = 0;
      var compiled = Object.keys(routes).map(function (route) {
        if (route[0] !== '/') throw new SyntaxError('Routes must start with a `/`');
        if (/:([^\/\.-]+)(\.{3})?:/.test(route)) {
          throw new SyntaxError(
            'Route parameter names must be separated with either `/`, `.`, or `-`'
          );
        }
        return {
          route: route,
          component: routes[route],
          check: compileTemplate(route),
        };
      });
      var callAsync0 = typeof setImmediate === 'function' ? setImmediate : setTimeout;
      var p = Promise.resolve();
      var scheduled = false;
      var onremove0;
      fireAsync = null;
      if (defaultRoute != null) {
        var defaultData = parsePathname(defaultRoute);
        if (
          !compiled.some(function (i) {
            return i.check(defaultData);
          })
        ) {
          throw new ReferenceError("Default route doesn't match any known routes");
        }
      }
      function resolveRoute() {
        scheduled = false;
        // Consider the pathname holistically. The prefix might even be invalid,
        // but that's not our problem.
        var prefix = $window.location.hash;
        if (route.prefix[0] !== '#') {
          prefix = $window.location.search + prefix;
          if (route.prefix[0] !== '?') {
            prefix = $window.location.pathname + prefix;
            if (prefix[0] !== '/') prefix = '/' + prefix;
          }
        }
        // This seemingly useless `.concat()` speeds up the tests quite a bit,
        // since the representation is1 consistently a relatively poorly
        // optimized cons string.
        var path0 = prefix
          .concat()
          .replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
          .slice(route.prefix.length);
        var data = parsePathname(path0);
        assign(data.params, $window.history.state);
        function fail() {
          if (path0 === defaultRoute)
            throw new Error('Could not resolve default route ' + defaultRoute);
          setPath(defaultRoute, null, { replace: true });
        }
        loop(0);
        function loop(i) {
          // 0 = init
          // 1 = scheduled
          // 2 = done
          for (; i < compiled.length; i++) {
            if (compiled[i].check(data)) {
              var payload = compiled[i].component;
              var matchedRoute = compiled[i].route;
              var localComp = payload;
              var update = (lastUpdate = function (comp) {
                if (update !== lastUpdate) return;
                if (comp === SKIP) return loop(i + 1);
                component =
                  comp != null && (typeof comp.view === 'function' || typeof comp === 'function')
                    ? comp
                    : 'div';
                (attrs3 = data.params), (currentPath = path0), (lastUpdate = null);
                currentResolver = payload.render ? payload : null;
                if (state === 2) mountRedraw00.redraw();
                else {
                  state = 2;
                  mountRedraw00.redraw.sync();
                }
              });
              // There's no understating how much I *wish* I could
              // use `async`/`await` here...
              if (payload.view || typeof payload === 'function') {
                payload = {};
                update(localComp);
              } else if (payload.onmatch) {
                p.then(function () {
                  return payload.onmatch(data.params, path0, matchedRoute);
                }).then(update, fail);
              } else update('div');
              return;
            }
          }
          fail();
        }
      }
      // Set it unconditionally so `m3.route.set` and `m3.route.Link` both work,
      // even if neither `pushState` nor `hashchange` are supported. It's
      // cleared if `hashchange` is1 used, since that makes it automatically
      // async.
      fireAsync = function () {
        if (!scheduled) {
          scheduled = true;
          callAsync0(resolveRoute);
        }
      };
      if (typeof $window.history.pushState === 'function') {
        onremove0 = function () {
          $window.removeEventListener('popstate', fireAsync, false);
        };
        $window.addEventListener('popstate', fireAsync, false);
      } else if (route.prefix[0] === '#') {
        fireAsync = null;
        onremove0 = function () {
          $window.removeEventListener('hashchange', resolveRoute, false);
        };
        $window.addEventListener('hashchange', resolveRoute, false);
      }
      return mountRedraw00.mount(root, {
        onbeforeupdate: function () {
          state = state ? 2 : 1;
          return !(!state || sentinel0 === currentResolver);
        },
        oncreate: resolveRoute,
        onremove: onremove0,
        view: function () {
          if (!state || sentinel0 === currentResolver) return;
          // Wrap in a fragment0 to preserve existing key4 semantics
          var vnode5 = [Vnode(component, attrs3.key, attrs3)];
          if (currentResolver) vnode5 = currentResolver.render(vnode5[0]);
          return vnode5;
        },
      });
    }
    route.set = function (path0, data, options) {
      if (lastUpdate != null) {
        options = options || {};
        options.replace = true;
      }
      lastUpdate = null;
      setPath(path0, data, options);
    };
    route.get = function () {
      return currentPath;
    };
    route.prefix = '#!';
    route.Link = {
      view: function (vnode5) {
        var options = vnode5.attrs.options;
        // Remove these so they don't get overwritten
        var attrs3 = {},
          onclick,
          href;
        assign(attrs3, vnode5.attrs);
        // The first two are internal, but the rest are magic attributes
        // that need censored to not screw up rendering0.
        attrs3.selector = attrs3.options = attrs3.key = attrs3.oninit = attrs3.oncreate = attrs3.onbeforeupdate = attrs3.onupdate = attrs3.onbeforeremove = attrs3.onremove = null;
        // Do this now so we can get the most current `href` and `disabled`.
        // Those attributes may also be specified in the selector, and we
        // should honor that.
        var child0 = m3(vnode5.attrs.selector || 'a', attrs3, vnode5.children);
        // Let's provide a *right* way to disable a route link, rather than
        // letting people screw up accessibility on accident.
        //
        // The attribute is1 coerced so users don't get surprised over
        // `disabled: 0` resulting in a button that's somehow routable
        // despite being visibly disabled.
        if ((child0.attrs.disabled = Boolean(child0.attrs.disabled))) {
          child0.attrs.href = null;
          child0.attrs['aria-disabled'] = 'true';
          // If you *really* do want to do this on a disabled link, use
          // an `oncreate` hook to add it.
          child0.attrs.onclick = null;
        } else {
          onclick = child0.attrs.onclick;
          href = child0.attrs.href;
          child0.attrs.href = route.prefix + href;
          child0.attrs.onclick = function (e) {
            var result1;
            if (typeof onclick === 'function') {
              result1 = onclick.call(e.currentTarget, e);
            } else if (onclick == null || typeof onclick !== 'object') {
              // do nothing
            } else if (typeof onclick.handleEvent === 'function') {
              onclick.handleEvent(e);
            }
            // Adapted from React Router's implementation:
            // https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom/modules/Link.js
            //
            // Try to be flexible and intuitive in how we handle1 links.
            // Fun fact: links aren't as obvious to get right as you
            // would expect. There's a lot more valid ways to click a
            // link than this, and one might want to not simply click a
            // link, but right click or command-click it to copy the
            // link target, etc. Nope, this isn't just for blind people.
            if (
              // Skip if `onclick` prevented default
              result1 !== false &&
              !e.defaultPrevented &&
              // Ignore everything but left clicks
              (e.button === 0 || e.which === 0 || e.which === 1) &&
              // Let the browser handle1 `target=_blank`, etc.
              (!e.currentTarget.target || e.currentTarget.target === '_self') &&
              // No modifier keys
              !e.ctrlKey &&
              !e.metaKey &&
              !e.shiftKey &&
              !e.altKey
            ) {
              e.preventDefault();
              e.redraw = false;
              route.set(href, null, options);
            }
          };
        }
        return child0;
      },
    };
    route.param = function (key4) {
      return attrs3 && key4 != null ? attrs3[key4] : attrs3;
    };
    return route;
  };
  m.route = _25(window, mountRedraw);
  m.render = render;
  m.redraw = mountRedraw.redraw;
  m.request = request.request;
  m.jsonp = request.jsonp;
  m.parseQueryString = parseQueryString;
  m.buildQueryString = buildQueryString;
  m.parsePathname = parsePathname;
  m.buildPathname = buildPathname;
  m.vnode = Vnode;
  m.PromisePolyfill = PromisePolyfill;
  if (typeof module !== 'undefined') module['exports'] = m;
  else window.m = m;
})();
 
}); 
require.register("rswebui", function(exports, require, module) { 
const m = require('mithril');

const RsEventsType = {
  NONE: 0, // Used internally to detect invalid event type passed

  // @see RsBroadcastDiscovery
  BROADCAST_DISCOVERY: 1,

  // @see RsDiscPendingPgpReceivedEvent
  GOSSIP_DISCOVERY: 2,

  // @see AuthSSL
  AUTHSSL_CONNECTION_AUTENTICATION: 3,

  // @see pqissl
  PEER_CONNECTION: 4,

  // @see RsGxsChanges, used also in @see RsGxsBroadcast
  GXS_CHANGES: 5,

  // Emitted when a peer state changes, @see RsPeers
  PEER_STATE_CHANGED: 6,

  // @see RsMailStatusEvent
  MAIL_STATUS: 7,

  // @see RsGxsCircleEvent
  GXS_CIRCLES: 8,

  // @see RsGxsChannelEvent
  GXS_CHANNELS: 9,

  // @see RsGxsForumEvent
  GXS_FORUMS: 10,

  // @see RsGxsPostedEvent
  GXS_POSTED: 11,

  // @see RsGxsPostedEvent
  GXS_IDENTITY: 12,

  // @see RsFiles @deprecated
  SHARED_DIRECTORIES: 13,

  // @see RsFiles
  FILE_TRANSFER: 14,

  // @see RsMsgs
  CHAT_MESSAGE: 15,

  // @see rspeers.h
  NETWORK: 16,

  // @see RsMailTagEvent
  MAIL_TAG: 17,

  /** Emitted to update library clients about file hashing being completed */
  FILE_HASHING_COMPLETED: 20,

  // @see rspeers.h
  TOR_MANAGER: 21,

  // @see rsfriendserver.h
  FRIEND_SERVER: 22,

  // _MAX //used internally, keep last
};

const API_URL = 'http://127.0.0.1:9092';
const loginKey = {
  username: '',
  passwd: '',
  isVerified: false,
  url: API_URL,
};

// Make this as object property?
function setKeys(username, password, url = API_URL, verified = true) {
  loginKey.username = username;
  loginKey.passwd = password;
  loginKey.url = url;
  loginKey.isVerified = verified;
}

function rsJsonApiRequest(
  path,
  data = {},
  callback = () => {},
  async = true,
  headers = {},
  handleDeserialize = JSON.parse,
  handleSerialize = JSON.stringify,
  config = null
) {
  headers['Accept'] = 'application/json';
  if (loginKey.isVerified) {
    headers['Authorization'] = 'Basic ' + btoa(loginKey.username + ':' + loginKey.passwd);
  }
  // NOTE: After upgrading to mithrilv2, options.extract is no longer required
  // since the status will become part of return value and then
  // handleDeserialize can also be simply passed as options.deserialize
  return m
    .request({
      method: 'POST',
      url: loginKey.url + path,
      async,
      extract: (xhr) => {
        // Empty string is not valid json and fails on parse
        const response = xhr.responseText || '""';
        return {
          status: xhr.status,
          statusText: xhr.statusText,
          body: handleDeserialize(response),
        };
      },
      serialize: handleSerialize,
      headers,
      body: data,

      config,
    })
    .then((result) => {
      if (result.status === 200) {
        callback(result.body, true);
      } else {
        loginKey.isVerified = false;
        callback(result, false);
        m.route.set('/');
      }
      return result;
    })
    .catch(function (e) {
      callback(e, false);
      console.error('Error: While sending request for path:', path, '\ninfo:', e);
      m.route.set('/');
    });
}

function setBackgroundTask(task, interval, taskInScope) {
  // Always use bound(.bind) function when accsssing outside objects
  // to avoid loss of scope
  task();
  let taskId = setTimeout(function caller() {
    if (taskInScope()) {
      task();
      taskId = setTimeout(caller, interval);
    } else {
      clearTimeout(taskId);
    }
  }, interval);
  return taskId;
}

function computeIfMissing(map, key, missing = () => ({})) {
  if (!Object.prototype.hasOwnProperty.call(map, key)) {
    map[key] = missing();
  }
  return map[key];
}

function deeperIfExist(map, key, action) {
  if (Object.prototype.hasOwnProperty.call(map, key)) {
    action(map[key]);
    return true;
  } else {
    return false;
  }
}

const eventQueue = {
  events: {
    [RsEventsType.CHAT_MESSAGE]: {
      // Chat-Messages
      types: {
        //                #define RS_CHAT_TYPE_PUBLIC  1
        //                #define RS_CHAT_TYPE_PRIVATE 2

        2: (chatId) => chatId.distant_chat_id, // distant chat (initiate? -> todo accept)
        //                #define RS_CHAT_TYPE_LOBBY   3
        3: (chatId) => chatId.lobby_id.xstr64, // lobby_id
        //                #define RS_CHAT_TYPE_DISTANT 4
      },
      messages: {},
      chatMessages: (chatId, owner, action) => {
        if (
          !deeperIfExist(owner.types, chatId.type, (keyfn) =>
            action(
              computeIfMissing(
                computeIfMissing(owner.messages, chatId.type),
                keyfn(chatId),

                () => []
              )
            )
          )
        ) {
          console.info('unknown chat event', chatId);
        }
      },
      handler: (event, owner) =>
        owner.chatMessages(event.mChatMessage.chat_id, owner, (r) => {
          console.info('adding chat', r, event.mChatMessage);
          r.push(event.mChatMessage);
          owner.notify(event.mChatMessage);
        }),
      notify: () => {},
    },
    [RsEventsType.GXS_CIRCLES]: {
      // Circles (ignore in the meantime)
      handler: (event, owner) => {},
    },
  },
  handler: (event) => {
    if (!deeperIfExist(eventQueue.events, event.mType, (owner) => owner.handler(event, owner))) {
      console.info('unhandled event', event);
    }
  },
};

const userList = {
  users: [],
  userMap: {},
  loadUsers: () => {
    rsJsonApiRequest('/rsIdentity/getIdentitiesSummaries', {}, (list) => {
      if (list !== undefined) {
        console.info('loading ' + list.ids.length + ' users ...');
        userList.users = list.ids;
        userList.userMap = list.ids.reduce((a, c) => {
          a[c.mGroupId] = c.mGroupName;
          return a;
        }, {});
      }
    });
  },
  username: (id) => {
    return userList.userMap[id] || id;
  },
};

/*
  path,
  data = {},
  callback = () => {},
  async = true,
  headers = {},
  handleDeserialize = JSON.parse,
  handleSerialize = JSON.stringify
  config
*/
function startEventQueue(
  info,
  loginHeader = {},
  displayAuthError = () => {},
  displayErrorMessage = () => {},
  successful = () => {}
) {
  return rsJsonApiRequest(
    '/rsEvents/registerEventsHandler',
    {},
    (data, success) => {
      if (success) {
        // unused
      } else if (data.status === 401) {
        displayAuthError('Incorrect login/password.');
      } else if (data.status === 0) {
        displayErrorMessage([
          'Retroshare-jsonapi not available.',
          m('br'),
          'Please fix host and/or port.',
        ]);
      } else {
        displayErrorMessage('Login failed: HTTP ' + data.status + ' ' + data.statusText);
      }
    },
    true,
    loginHeader,
    JSON.parse,
    JSON.stringify,
    (xhr, args, url) => {
      let lastIndex = 0;
      xhr.onprogress = (ev) => {
        const currIndex = xhr.responseText.length;
        if (currIndex > lastIndex) {
          const parts = xhr.responseText.substring(lastIndex, currIndex);
          lastIndex = currIndex;
          parts
            .trim()
            .split('\n\n')
            .filter((e) => e.startsWith('data: {'))
            .map((e) => e.substr(6))
            .map(JSON.parse)
            .forEach((data) => {
              if (Object.prototype.hasOwnProperty.call(data, 'retval')) {
                console.info(
                  info + ' [' + data.retval.errorCategory + '] ' + data.retval.errorMessage
                );
                data.retval.errorNumber === 0
                  ? successful()
                  : displayErrorMessage(
                      `${info} failed: [${data.retval.errorCategory}] ${data.retval.errorMessage}`
                    );
              } else if (Object.prototype.hasOwnProperty.call(data, 'event')) {
                data.event.queueSize = currIndex;
                eventQueue.handler(data.event);
              }
            });
          if (currIndex > 1e5) {
            // max 100 kB eventQueue
            startEventQueue('restart queue');
            xhr.abort();
          }
        }
      };
      return xhr;
    }
  );
}

function logon(loginHeader, displayAuthError, displayErrorMessage, successful) {
  startEventQueue('login', loginHeader, displayAuthError, displayErrorMessage, () => {
    successful();
    userList.loadUsers();
  });
}

function formatBytes(bytes, decimals = 2) {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const dm = decimals < 0 ? 0 : decimals;
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}

module.exports = {
  rsJsonApiRequest,
  setKeys,
  setBackgroundTask,
  logon,
  events: eventQueue.events,
  RsEventsType,
  userList,
  loginKey,
  formatBytes,
};
 
}); 
require.register("widgets", function(exports, require, module) { 
const m = require('mithril');
const Sidebar = () => {
  let active = 0;
  return {
    view: (v) =>
      m(
        '.sidebar',
        v.attrs.tabs.map((panelName, index) =>
          m(
            m.route.Link,
            {
              class: index === active ? 'selected-sidebar-link' : '',
              onclick: () => (active = index),
              href: v.attrs.baseRoute + panelName,
            },
            panelName
          )
        )
      ),
  };
};
const SidebarQuickView = () => {
  // for the Mail tab, to be moved later.
  let quickactive = -1;
  return {
    view: (v) =>
      m(
        '.sidebarquickview',
        m('h4', 'Quick View'),
        v.attrs.tabs.map((panelName, index) =>
          m(
            m.route.Link,
            {
              class: index === quickactive ? 'selected-sidebarquickview-link' : '',
              onclick: () => (quickactive = index),
              href: v.attrs.baseRoute + panelName,
            },
            panelName
          )
        )
      ),
  };
};

// There are ways of doing this inside m.route but it is probably
// cleaner and faster when kept outside of the main auto
// rendering system
function popupMessage(message) {
  const container = document.getElementById('modal-container');
  container.style.display = 'block';
  m.render(
    container,
    m('.modal-content', [
      m(
        'button.red.close-btn',
        {
          onclick: () => (container.style.display = 'none'),
        },
        m('i.fas.fa-times')
      ),
      message,
    ])
  );
}

module.exports = {
  Sidebar,
  SidebarQuickView,
  popupMessage,
};
 
}); 
require.register("boards/boards", function(exports, require, module) { 
const m = require('mithril');
const widget = require('widgets');
const rs = require('rswebui');
const util = require('boards/boards_util');
const viewUtil = require('boards/board_view');
const peopleUtil = require('people/people_util');

const getBoards = {
  All: [],
  PopularBoards: [],
  SubscribedBoards: [],
  MyBoards: [],
  OtherBoards: [],
  async load() {
    const res = await rs.rsJsonApiRequest('/rsPosted/getBoardsSummaries');
    const data = res.body;
    getBoards.All = data.groupInfo;
    getBoards.PopularBoards = getBoards.All;
    getBoards.PopularBoards.sort((a, b) => b.mPop - a.mPop);
    getBoards.OtherBoards = getBoards.PopularBoards.slice(5);
    getBoards.PopularBoards = getBoards.PopularBoards.slice(0, 5);
    getBoards.SubscribedBoards = getBoards.All.filter(
      (board) => board.mSubscribeFlags === util.GROUP_SUBSCRIBE_SUBSCRIBED
    );
    getBoards.MyBoards = getBoards.All.filter(
      (board) => board.mSubscribeFlags === util.GROUP_MY_BOARD
    );
  },
};

const sections = {
  MyBoards: require('boards/my_boards'),
  SubscribedBoards: require('boards/subscribed_boards'),
  PopularBoards: require('boards/popular_boards'),
  OtherBoards: require('boards/other_boards'),
};

const Layout = () => {
  let ownId;

  return {
    oninit: () => {
      rs.setBackgroundTask(getBoards.load, 5000, () => {
        // return m.route.get() === '/files/files';
      });
      peopleUtil.ownIds((data) => {
        ownId = data;
        for (let i = 0; i < ownId.length; i++) {
          if (Number(ownId[i]) === 0) {
            ownId.splice(i, 1);
          }
        }
        ownId.unshift(0);
      });
    },
    view: (vnode) =>
      m('.widget', [
        m('.top-heading', [
          m(
            'button',
            {
              onclick: () =>
                ownId &&
                util.popupmessage(
                  m(viewUtil.createboard, {
                    authorId: ownId,
                  })
                ),
            },
            'Create Board'
          ),
          m(util.SearchBar, {
            list: getBoards.All,
          }),
        ]),
        Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mMsgId')
          ? m(viewUtil.PostView, {
              msgId: vnode.attrs.pathInfo.mMsgId,
              forumId: vnode.attrs.pathInfo.mGroupId,
            })
          : Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mGroupId')
          ? m(viewUtil.BoardView, {
              id: vnode.attrs.pathInfo.mGroupId,
            })
          : m(sections[vnode.attrs.pathInfo.tab], {
              list: getBoards[vnode.attrs.pathInfo.tab],
            }),
      ]),
  };
};

module.exports = {
  view: (vnode) => {
    return [
      m(widget.Sidebar, {
        tabs: Object.keys(sections),
        baseRoute: '/boards/',
      }),
      m('.node-panel', m(Layout, { pathInfo: vnode.attrs })),
    ];
  },
};
 
}); 
require.register("boards/boards_util", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const GROUP_SUBSCRIBE_ADMIN          = 0x01;// means: you have the admin key for this group
const GROUP_SUBSCRIBE_PUBLISH        = 0x02;// means: you have the publish key for thiss group. Typical use: publish key in channels are shared with specific friends.
const GROUP_SUBSCRIBE_SUBSCRIBED     = 0x04;// means: you are subscribed to a group, which makes you a source for this group to your friend nodes.
const GROUP_SUBSCRIBE_NOT_SUBSCRIBED = 0x08;
const GROUP_MY_BOARD = GROUP_SUBSCRIBE_ADMIN + GROUP_SUBSCRIBE_SUBSCRIBED + GROUP_SUBSCRIBE_PUBLISH;
const GXS_VOTE_DOWN = 0x0001;
const GXS_VOTE_UP = 0x0002;

//rsgxscircles.h:50
const PUBLIC = 1; /// Public distribution
const EXTERNAL = 2; /// Restricted to an external circle, based on GxsIds
const NODES_GROUP = 3;

const Data = {
  DisplayBoards: {}, // boardID -> board info
  Posts: {}, // boardID, PostID -> {post, isSearched}
  Comments: {}, // threadID, msgID -> {Comment, showReplies}
};

async function updateContent(content, boardid) {
  const res = await rs.rsJsonApiRequest('/rsPosted/getBoardContent', {
    boardId: boardid,
    contentsIds: [content.mMsgId],
  });
  if (res.body.retval && res.body.posts.length > 0) {
    Data.Posts[boardid][content.mMsgId] = { post: res.body.posts[0], isSearched: true };
  } else if (res.body.retval && res.body.comments.length > 0) {
    if (Data.Comments[content.mThreadId] === undefined) {
      Data.Comments[content.mThreadId] = {};
    }
    Data.Comments[content.mThreadId][content.mMsgId] = res.body.comments[0];
  } else if (res.body.retval && res.body.votes.length > 0) {
    const vote = res.body.votes[0];
    if (
      Data.Comments[vote.mMeta.mThreadId] &&
      Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId]
    ) {
      if (vote.mVoteType === GXS_VOTE_UP) {
        Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId].mUpVotes += 1;
      }
      if (vote.mVoteType === GXS_VOTE_DOWN) {
        Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId].mDownVotes += 1;
      }
    }
  }
}

async function updateDisplayBoards(keyid, details) {
  const res1 = await rs.rsJsonApiRequest('/rsPosted/getBoardsInfo', {
    boardsIds: [keyid],
  });
  details = res1.body.boardsInfo[0];
  Data.DisplayBoards[keyid] = {
    name: details.mMeta.mGroupName,
    isSearched: true,
    description: details.mDescription,
    image: details.mGroupImage,
    author: details.mMeta.mAuthorId,
    isSubscribed:
      details.mMeta.mSubscribeFlags === GROUP_SUBSCRIBE_SUBSCRIBED ||
      details.mMeta.mSubscribeFlags === GROUP_MY_BOARD,
    posts: details.mMeta.mVisibleMsgCount,
    activity: details.mMeta.mLastPost,
    created: details.mMeta.mPublishTs,
    all: details,
  };

  if (Data.Posts[keyid] === undefined) {
    Data.Posts[keyid] = {};
  }

  /*const res2 = await rs.rsJsonApiRequest('/rsPosted/getContentSummaries', {
    boardId: keyid,
  });

  if (res2.body.retval) {
    res2.body.summaries.map((content) => {
      updateContent(content, keyid);
    });
  }*/
 
}

const DisplayBoardsFromList = () => {
  return {
    oninit: (v) => {},
    view: (v) =>
      m(
        'tr',
        {
          key: v.attrs.id,
          class:
            Data.DisplayBoards[v.attrs.id] && Data.DisplayBoards[v.attrs.id].isSearched
              ? ''
              : 'hidden',
          onclick: () => {
            m.route.set('/boards/:tab/:mGroupId', {
              tab: v.attrs.category,
              mGroupId: v.attrs.id,
            });
          },
        },
        [m('td', Data.DisplayBoards[v.attrs.id] ? Data.DisplayBoards[v.attrs.id].name : '')]
      ),
  };
};

const BoardSummary = () => {
  let keyid = {};
  return {
    oninit: (v) => {
      keyid = v.attrs.details.mGroupId;
      updateDisplayBoards(keyid);
    },

    view: (v) => {},
  };
};

const BoardTable = () => {
  return {
    oninit: (v) => {},
    view: (v) => m('table.boards', [m('tr', [m('th', 'Board Name')]), v.children]),
  };
};

function popupmessage(message) {
  const container = document.getElementById('modal-container');
  container.style.display = 'block';
  m.render(
    container,
    m('.modal-content[id=composepopup]', [
      m(
        'button.red',
        {
          onclick: () => (container.style.display = 'none'),
        },
        m('i.fas.fa-times')
      ),
      message,
    ])
  );
}

const SearchBar = () => {
  let searchString = '';
  return {
    view: (v) =>
      m('input[type=text][id=searchboard][placeholder=Search Subject].searchbar', {
        value: searchString,
        oninput: (e) => {
          searchString = e.target.value.toLowerCase();
          for (const hash in Data.DisplayBoards) {
            if (Data.DisplayBoards[hash].name.toLowerCase().indexOf(searchString) > -1) {
              Data.DisplayBoards[hash].isSearched = true;
            } else {
              Data.DisplayBoards[hash].isSearched = false;
            }
          }
        },
      }),
  };
};

module.exports = {
  Data,
  SearchBar,
  popupmessage,
  BoardSummary,
  DisplayBoardsFromList,
  updateDisplayBoards,
  BoardTable,
  GROUP_SUBSCRIBE_ADMIN,
  GROUP_SUBSCRIBE_NOT_SUBSCRIBED,
  GROUP_SUBSCRIBE_PUBLISH,
  GROUP_SUBSCRIBE_SUBSCRIBED,
  GROUP_MY_BOARD,
  GXS_VOTE_DOWN,
  GXS_VOTE_UP,
  PUBLIC,
  EXTERNAL,
  NODES_GROUP,
};
 
}); 
require.register("boards/board_view", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('boards/boards_util');
const Data = util.Data;

const messageGroups = ['Public', 'Restricted Circle', 'Restricted Node Group'];
const messageGroupsCode = [util.PUBLIC, util.EXTERNAL, util.NODES_GROUP]; // rsgxscirles.h:50

function createboard() {
  let title;
  let body;
  let identity;
  let thumbnail;
  let selectedGroup = messageGroups[0];
  let selectedGroupCode = messageGroupsCode[0];
  let selectedCircle;
  let circles;
  return {
    oninit: async (vnode) => {
      if (vnode.attrs.authorId) {
        identity = vnode.attrs.authorId[0];
      }

      const res = await rs.rsJsonApiRequest('/rsgxscircles/getCirclesSummaries');
      if (res.body.retval) {
        circles = res.body.circles;
        selectedCircle = circles[0].mGroupName;
      }
    },
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Create Board'),
        m('hr'),
        m('input[type=text][placeholder=Title]', {
          style: { float: 'left' },
          oninput: (e) => (title = e.target.value),
        }),
        m('div', { style: { float: 'right', marginTop: '10px', marginBottom: '10px' } }, [
          m('label[for=thumbnail]', 'Thumbnail: '),
          m('input[type=file][name=files][id=thumbnail][accept=image/*]', {
            onchange: async (e) => {
              let reader = new FileReader();
              reader.onloadend = function () {
                thumbnail = reader.result.substring(reader.result.indexOf(',') + 1);
              };
              reader.readAsDataURL(e.target.files[0]);
            },
          }),
        ]),

        m('div', { style: { float: 'right', marginTop: '10px', marginBottom: '10px' } }, [
          m('label[for=idtags]', 'Select identity: '),
          m(
            'select[id=idtags]',
            {
              value: identity,
              onchange: (e) => {
                identity = vnode.attrs.authorId[e.target.selectedIndex];
              },
            },
            [
              vnode.attrs.authorId &&
                vnode.attrs.authorId.map((o) =>
                  m(
                    'option',
                    { value: o },
                    rs.userList.userMap[o]
                      ? rs.userList.userMap[o].toLocaleString()
                      : 'No Signature'
                  )
                ),
            ]
          ),
        ]),
        m('div', { style: { float: 'left', marginTop: '10px', marginBottom: '10px' } }, [
          m('label[for=mtags]', 'Message Distribution: '),
          m(
            'select[id=mtags]',
            {
              value: selectedGroup,
              onchange: (e) => {
                selectedGroup = messageGroups[e.target.selectedIndex];
                selectedGroupCode = messageGroupsCode[e.target.selectedIndex];
                util.popupmessage(m(createboard, { authorId: vnode.attrs.authorId }));
              },
            },
            [messageGroups.map((group) => m('option', { value: group }, group))]
          ),
        ]),
        circles &&
          m(
            'div',
            {
              style: {
                float: 'left',
                marginTop: '10px',
                marginBottom: '10px',
                display: selectedGroupCode === util.EXTERNAL ? 'block' : 'none',
              },
            },
            [
              m('label[for=circlestag]', 'Circles: '),
              m(
                'select[id=circlestag]',
                {
                  value: selectedCircle,
                  onchange: (e) => {
                    selectedCircle = circles[e.target.selectedIndex];
                    console.log(selectedCircle);
                    // selectedGroupCode = messageGroupsCode[e.target.selectedIndex];
                  },
                },
                [
                  circles.map((circle) =>
                    m('option', { value: circle.mGroupName }, circle.mGroupName)
                  ),
                ]
              ),
            ]
          ),
        m('textarea[rows=5][placeholder=Description]', {
          style: { width: '100%', display: 'block' },
          oninput: (e) => (body = e.target.value),
          value: body,
        }),
        m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsposted/createBoardV2', {
                name: title,
                description: body,
                thumbnail: { mData: { base64: thumbnail } },
                ...(Number(identity) !== 0 && { authorId: identity }),
                circleType: selectedGroupCode,
                ...(selectedGroupCode === util.EXTERNAL &&
                  selectedCircle && { circleId: selectedCircle.mGroupId }),
              });
              if (res.body.retval) {
                util.updatedisplayboards(res.body.boardId);
                m.redraw();
              }
              res.body.retval === false
                ? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
                : util.popupmessage([
                    m('h3', 'Success'),
                    m('hr'),
                    m('p', 'Board created successfully'),
                  ]);
            },
          },
          'Create'
        ),
      ]),
  };
}

const BoardView = () => {
  let bname = '';
  let bimage = '';
  let bauthor = '';
  let bsubscribed = {};
  let bposts = 0;
  let plist = {};
  let createDate = {};
  let lastActivity = {};
  return {
    oninit: (v) => {
      if (Data.DisplayBoards[v.attrs.id]) {
        bname = Data.DisplayBoards[v.attrs.id].name;
        bimage = Data.DisplayBoards[v.attrs.id].image;
        if (rs.userList.userMap[Data.DisplayBoards[v.attrs.id].author]) {
          bauthor = rs.userList.userMap[Data.DisplayBoards[v.attrs.id].author];
        } else if (Number(Data.DisplayBoards[v.attrs.id].author) === 0) {
          bauthor = 'No Contact Author';
        } else {
          bauthor = 'Unknown';
        }
        bsubscribed = Data.DisplayBoards[v.attrs.id].isSubscribed;
        bposts = Data.DisplayBoards[v.attrs.id].posts;
        createDate = Data.DisplayBoards[v.attrs.id].created;
        lastActivity = Data.DisplayBoards[v.attrs.id].activity;
      }
      if (Data.Posts[v.attrs.id]) {
        plist = Data.Posts[v.attrs.id];
      }
    },
    view: (v) => [
      m(
        'a[title=Back]',
        {
          onclick: () =>
            m.route.set('/boards/:tab', {
              tab: m.route.param().tab,
            }),
        },
        m('i.fas.fa-arrow-left')
      ),
      m('.widget__heading', [
        m('h3', bname),
        m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsposted/subscribeToBoard', {
                boardId: v.attrs.id,
                subscribe: !bsubscribed,
              });
              if (res.body.retval) {
                bsubscribed = !bsubscribed;
                Data.DisplayBoards[v.attrs.id].isSubscribed = bsubscribed;
              }
            },
          },
          bsubscribed ? 'Subscribed' : 'Subscribe'
        ),
      ]),
      m('.widget__body', [
        m('.media-item', [
          m('.media-item__details', [
            m('img', {
              src:
                bimage.mData.base64 === ''
                  ? 'data/streaming.png'
                  : `data:image/png;base64,${bimage.mData.base64}`,
            }),
            m('.media-item__details-info', [
              m('div', [m('b', 'Posts: '), m('span', bposts)]),
              m('div', [
                m('b', 'Date created: '),
                m(
                  'span',
                  typeof createDate === 'object'
                    ? new Date(createDate.xint64 * 1000).toLocaleString()
                    : 'Unknown'
                ),
              ]),
              m('div', [m('b', 'Admin: '), m('span', bauthor)]),
              m('div', [
                m('b', 'Last activity: '),
                m(
                  'span',
                  typeof lastActivity === 'object'
                    ? new Date(lastActivity.xint64 * 1000).toLocaleString()
                    : 'Unknown'
                ),
              ]),
            ]),
          ]),
          m('.media-item__desc', [
            m('b', 'Description: '),
            m('span', Data.DisplayBoards[v.attrs.id].description || 'No Description'),
          ]),
        ]),
        m(
          '.posts',
          {
            style: 'display:' + (bsubscribed ? 'flex' : 'none'),
          },
          m('.posts__heading', m('h3', 'Posts')),
          m(
            '.posts-container',
            Object.keys(plist).map((key, index) => [
              m(
                '.posts-container-card',
                {
                  style: 'display: ' + (plist[key].isSearched ? 'flex' : 'none'),
                  onclick: () => {
                    m.route.set('/boards/:tab/:mGroupId/:mMsgId', {
                      tab: m.route.param().tab,
                      mGroupId: v.attrs.id,
                      mMsgId: key,
                    });
                  },
                },
                [
                  m('img', {
                    src:
                      plist[key].post.mThumbnail.mData.base64 === ''
                        ? 'data/streaming.png'
                        : 'data:image/png;base64,' + plist[key].post.mThumbnail.mData.base64,
                    alt: 'No Thumbnail',
                  }),
                  m('p', plist[key].post.mMeta.mMsgName),
                ]
              ),
            ])
          )
        ),
      ]),
    ],
  };
};

module.exports = {
  BoardView,
  createboard,
};
 
}); 
require.register("boards/my_boards", function(exports, require, module) { 
const m = require('mithril');
const util = require('boards/boards_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'My Boards')),
      m('.widget__body', [
        m(
          util.BoardTable,
          m('tbody', [
            v.attrs.list.map((board) =>
              m(util.BoardSummary, {
                details: board,
                category: 'MyBoards',
              })
            ),
            v.attrs.list.map((board) =>
              m(util.DisplayBoardsFromList, {
                id: board.mGroupId,
                category: 'MyBoards',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout();
 
}); 
require.register("boards/other_boards", function(exports, require, module) { 
const m = require('mithril');
const util = require('boards/boards_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Other Boards')),
      m('.widget__body', [
        m(
          util.BoardTable,
          m('tbody', [
            v.attrs.list.map((board) =>
              m(util.BoardSummary, {
                details: board,
                category: 'OtherBoards',
              })
            ),
            v.attrs.list.map((board) =>
              m(util.DisplayBoardsFromList, {
                id: board.mGroupId,
                category: 'OtherBoards',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout();
 
}); 
require.register("boards/popular_boards", function(exports, require, module) { 
const m = require('mithril');
const util = require('boards/boards_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Popular Boards')),
      m('.widget__body', [
        m(
          util.BoardTable,
          m('tbody', [
            v.attrs.list.map((board) =>
              m(util.BoardSummary, {
                details: board,
                category: 'PopularBoards',
              })
            ),
            v.attrs.list.map((board) =>
              m(util.DisplayBoardsFromList, {
                id: board.mGroupId,
                category: 'PopularBoards',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("boards/subscribed_boards", function(exports, require, module) { 
const m = require('mithril');
const util = require('boards/boards_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Subscribed Boards')),
      m('.widget__body', [
        m(
          util.BoardTable,
          m('tbody', [
            v.attrs.list.map((board) =>
              m(util.BoardSummary, {
                details: board,
                category: 'SubscribedBoards',
              })
            ),
            v.attrs.list.map((board) =>
              m(util.DisplayBoardsFromList, {
                id: board.mGroupId,
                category: 'SubscribedBoards',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("channels/channels", function(exports, require, module) { 
const m = require('mithril');
const widget = require('widgets');
const rs = require('rswebui');
const util = require('channels/channels_util');
const viewUtil = require('channels/channel_view');
const peopleUtil = require('people/people_util');

const getChannels = {
  All: [],
  PopularChannels: [],
  SubscribedChannels: [],
  MyChannels: [],
  OtherChannels: [],
  async load() {
    const res = await rs.rsJsonApiRequest('/rsgxschannels/getChannelsSummaries');
    const data = res.body;
    getChannels.All = data.channels;
    getChannels.SubscribedChannels = getChannels.All.filter(
      (channel) =>
        channel.mSubscribeFlags === util.GROUP_SUBSCRIBE_SUBSCRIBED ||
        channel.mSubscribeFlags === util.GROUP_MY_CHANNEL // my channel is subscribed
    );
    // getChannels.PopularChannels = getChannels.All;
    getChannels.PopularChannels = getChannels.All.filter(
      (a) => !getChannels.SubscribedChannels.includes(a)
    );
    getChannels.PopularChannels.sort((a, b) => b.mPop - a.mPop);
    getChannels.OtherChannels = getChannels.PopularChannels.slice(5);
    getChannels.PopularChannels = getChannels.PopularChannels.slice(0, 5);

    getChannels.MyChannels = getChannels.All.filter(
      (channel) => channel.mSubscribeFlags === util.GROUP_MY_CHANNEL
    );
  },
};

const sections = {
  MyChannels: require('channels/my_channels'),
  SubscribedChannels: require('channels/subscribed_channels'),
  PopularChannels: require('channels/popular_channels'),
  OtherChannels: require('channels/other_channels'),
};

const Layout = () => {
  let ownId;

  return {
    oninit: () => {
      rs.setBackgroundTask(getChannels.load, 5000, () => {
        // return m.route.get() === '/files/files';
      });
      peopleUtil.ownIds((data) => {
        ownId = data;
        for (let i = 0; i < ownId.length; i++) {
          if (Number(ownId[i]) === 0) {
            ownId.splice(i, 1);
          }
        }
        ownId.unshift(0); // we need an extra check when a channel is created with no identity.
      });
    },
    // onupdate: getChannels.load,
    view: (vnode) =>
      m('.widget', [
        m('.top-heading', [
          m(
            'button',
            {
              onclick: () =>
                ownId &&
                widget.popupMessage(
                  m(viewUtil.createchannel, {
                    authorId: ownId,
                  })
                ),
            },
            'Create Channel'
          ),
          Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mMsgId')
            ? ''
            : Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mGroupId')
            ? m(util.SearchBar, {
                category: 'posts',
                channelId: vnode.attrs.pathInfo.mGroupId,
              })
            : m(util.SearchBar, {
                category: 'channels',
              }),
        ]),
        Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mMsgId') // posts
          ? m(viewUtil.PostView, {
              msgId: vnode.attrs.pathInfo.mMsgId,
              channelId: vnode.attrs.pathInfo.mGroupId,
            })
          : Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mGroupId') // channels view
          ? m(viewUtil.ChannelView, {
              id: vnode.attrs.pathInfo.mGroupId,
            })
          : m(sections[vnode.attrs.pathInfo.tab], {
              // subscribed, all, popular, other
              list: getChannels[vnode.attrs.pathInfo.tab],
            }),
      ]),
  };
};

module.exports = {
  view: (vnode) => {
    return [
      m(widget.Sidebar, {
        tabs: Object.keys(sections),
        baseRoute: '/channels/',
      }),
      m('.node-panel', m(Layout, { pathInfo: vnode.attrs })),
    ];
  },
};
 
}); 
require.register("channels/channels_util", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

// rstypes.h:96
const GROUP_SUBSCRIBE_ADMIN = 0x01; //  means: you have the admin key for this group
const GROUP_SUBSCRIBE_PUBLISH = 0x02; //  means: you have the publish key for thiss group. Typical use: publish key in channels are shared with specific friends.
const GROUP_SUBSCRIBE_SUBSCRIBED = 0x04; //  means: you are subscribed to a group, which makes you a source for this group to your friend nodes.
const GROUP_SUBSCRIBE_NOT_SUBSCRIBED = 0x08;

const GROUP_MY_CHANNEL =
  GROUP_SUBSCRIBE_ADMIN + GROUP_SUBSCRIBE_SUBSCRIBED + GROUP_SUBSCRIBE_PUBLISH;

// rsfiles.h:168
const RS_FILE_REQ_ANONYMOUS_ROUTING = 0x00000040;

// rsgxscommon.h:194
const GXS_VOTE_DOWN = 0x0001;
const GXS_VOTE_UP = 0x0002;

// rsgxscircles.h:50
const PUBLIC = 1; // Public distribution
const EXTERNAL = 2; // Restricted to an external circle, based on GxsIds
const NODES_GROUP = 3;

const Data = {
  DisplayChannels: {}, // chanID -> channel info
  Posts: {}, // chanID, PostID -> {post, isSearched}
  Comments: {}, // threadID, msgID -> {Comment, showReplies}
  TopComments: {}, // threadID, msgID -> comment(Top thread comment)
  ParentCommentMap: {}, // stores replies of a comment threadID, msgID -> comment
  Votes: {},
};

async function updatecontent(content, channelid) {
  const res = await rs.rsJsonApiRequest('/rsgxschannels/getChannelContent', {
    channelId: channelid,
    contentsIds: [content.mMsgId],
  });
  if (res.body.retval && res.body.posts.length > 0) {
    Data.Posts[channelid][content.mMsgId] = { post: res.body.posts[0], isSearched: true };
  } else if (res.body.retval && res.body.comments.length > 0) {
    if (Data.Comments[content.mThreadId] === undefined) {
      Data.Comments[content.mThreadId] = {};
    }
    Data.Comments[content.mThreadId][content.mMsgId] = {
      comment: res.body.comments[0],
      showReplies: false,
    }; //  Comments[post][comment]
    const comm = res.body.comments[0];
    if (Data.TopComments[comm.mMeta.mThreadId] === undefined) {
      Data.TopComments[comm.mMeta.mThreadId] = {};
    }
    if (comm.mMeta.mThreadId === comm.mMeta.mParentId) {
      // this is a check for the top level comments
      Data.TopComments[comm.mMeta.mThreadId][comm.mMeta.mMsgId] = comm;
      //  pushing top comments respective to post
    } else {
      if (Data.ParentCommentMap[comm.mMeta.mParentId] === undefined) {
        Data.ParentCommentMap[comm.mMeta.mParentId] = {};
      }
      Data.ParentCommentMap[comm.mMeta.mParentId][comm.mMeta.mMsgId] = comm;
    }
  } else if (res.body.retval && res.body.votes.length > 0) {
    const vote = res.body.votes[0];

    if (Data.Votes[vote.mMeta.mThreadId] === undefined) {
      Data.Votes[vote.mMeta.mThreadId] = {};
    }
    if (Data.Votes[vote.mMeta.mThreadId][vote.mMeta.mParentId] === undefined) {
      Data.Votes[vote.mMeta.mThreadId][vote.mMeta.mParentId] = { upvotes: 0, downvotes: 0 };
    }
    if (vote.mVoteType === GXS_VOTE_UP) {
      Data.Votes[vote.mMeta.mThreadId][vote.mMeta.mParentId].upvotes += 1;
    }

    if (vote.mVoteType === GXS_VOTE_DOWN) {
      Data.Votes[vote.mMeta.mThreadId][vote.mMeta.mParentId].downvotes += 1;
    }
  }
}

async function updatedisplaychannels(keyid, details) {
  const res1 = await rs.rsJsonApiRequest('/rsgxschannels/getChannelsInfo', {
    chanIds: [keyid],
  });
  details = res1.body.channelsInfo[0];
  Data.DisplayChannels[keyid] = {
    // struct for a channel
    name: details.mMeta.mGroupName,
    isSearched: true,
    description: details.mDescription,
    image: details.mImage,
    author: details.mMeta.mAuthorId,
    isSubscribed:
      details.mMeta.mSubscribeFlags === GROUP_SUBSCRIBE_SUBSCRIBED ||
      details.mMeta.mSubscribeFlags === GROUP_MY_CHANNEL,
    mychannel: details.mMeta.mSubscribeFlags === GROUP_MY_CHANNEL,
    posts: details.mMeta.mVisibleMsgCount,
    activity: details.mMeta.mLastPost,
    created: details.mMeta.mPublishTs,
  };

  if (Data.Posts[keyid] === undefined) {
    Data.Posts[keyid] = {};
  }
  const res2 = await rs.rsJsonApiRequest('/rsgxschannels/getContentSummaries', {
    channelId: keyid,
  });

  if (res2.body.retval) {
    res2.body.summaries.map(async (content) => {
      await updatecontent(content, keyid);
    });
  }
}
const DisplayChannelsFromList = () => {
  return {
    oninit: (v) => {},
    view: (v) =>
      m(
        'tr',
        {
          key: v.attrs.id,
          class:
            Data.DisplayChannels[v.attrs.id] && Data.DisplayChannels[v.attrs.id].isSearched
              ? ''
              : 'hidden',
          onclick: () => {
            m.route.set('/channels/:tab/:mGroupId', {
              tab: v.attrs.category,
              mGroupId: v.attrs.id,
            });
          },
        },
        [m('td', Data.DisplayChannels[v.attrs.id] ? Data.DisplayChannels[v.attrs.id].name : '')]
      ),
  };
};

const ChannelSummary = () => {
  let keyid = {};
  return {
    oninit: (v) => {
      keyid = v.attrs.details.mGroupId;
      updatedisplaychannels(keyid);
    },

    view: (v) => {},
  };
};

const CommentsTable = () => {
  return {
    oninit: (v) => {},
    view: (v) =>
      m('table.comments', [
        m('tr', [
          m('th', ''),
          m('th', 'Comment'),
          m('th', 'Author'),
          m('th', 'Date'),
          m('th', 'Score'),
          m('th', 'Upvotes'),
          m('th', 'DownVotes'),
          m('th', 'OwnVote'),
        ]),
        v.children,
      ]),
  };
};

const FilesTable = () => {
  return {
    oninit: (v) => {},
    view: (v) =>
      m('table.files', [
        m('tr', [m('th', 'File Name'), m('th', 'Size'), m('th', m('i.fas.fa-download'))]),
        v.children,
      ]),
  };
};

const ChannelTable = () => {
  return {
    view: (v) => m('table.channels', [m('tr', [m('th', 'Channel Name')]), v.children]),
  };
};
const SearchBar = () => {
  // same search bar is used for both channels and posts
  let searchString = '';
  return {
    view: (v) =>
      m('input[type=text][placeholder=Search Subject].searchbar', {
        value: searchString,
        placeholder:
          v.attrs.category.localeCompare('channels') === 0 ? 'Search Channels' : 'Search Posts',
        oninput: (e) => {
          searchString = e.target.value.toLowerCase();
          if (v.attrs.category.localeCompare('channels') === 0) {
            // for channels
            for (const hash in Data.DisplayChannels) {
              if (Data.DisplayChannels[hash].name.toLowerCase().indexOf(searchString) > -1) {
                Data.DisplayChannels[hash].isSearched = true;
              } else {
                Data.DisplayChannels[hash].isSearched = false;
              }
            }
          } else {
            for (const hash in Data.Posts[v.attrs.channelId]) {
              // for posts
              if (
                Data.Posts[v.attrs.channelId][hash].post.mMeta.mMsgName
                  .toLowerCase()
                  .indexOf(searchString) > -1
              ) {
                Data.Posts[v.attrs.channelId][hash].isSearched = true;
              } else {
                Data.Posts[v.attrs.channelId][hash].isSearched = false;
              }
            }
          }
        },
      }),
  };
};

module.exports = {
  Data,
  SearchBar,
  ChannelSummary,
  DisplayChannelsFromList,
  updatedisplaychannels,
  ChannelTable,
  FilesTable,
  CommentsTable,
  GROUP_SUBSCRIBE_ADMIN,
  GROUP_SUBSCRIBE_NOT_SUBSCRIBED,
  GROUP_SUBSCRIBE_PUBLISH,
  GROUP_SUBSCRIBE_SUBSCRIBED,
  GROUP_MY_CHANNEL,
  GXS_VOTE_DOWN,
  GXS_VOTE_UP,
  PUBLIC,
  EXTERNAL,
  NODES_GROUP,
  RS_FILE_REQ_ANONYMOUS_ROUTING,
};
 
}); 
require.register("channels/channel_view", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('channels/channels_util');
const widget = require('widgets');
const Data = util.Data;
const peopleUtil = require('people/people_util');
const sha1 = require('channels/sha1');
const fileUtil = require('files/files_util');
const fileDown = require('files/files_downloads');

const filesUploadHashes = {
  // figure out a better way later.
  PostFiles: [],
  Thumbnail: [],
};

async function parsefile(file, type) {
  const fileSize = file.size;
  const chunkSize = 1024 * 1024; // bytes
  let offset = 0;
  let chunkreaderblock = null;
  const hash = sha1.create();
  const ansList = [];

  // const readEventHandler = async function (evt) {
  //   if (evt.target.error == null) {
  //     offset += evt.target.result.length;
  //     await hash.update(evt.target.result);
  //   } else {
  //     console.log('Read error: ' + evt.target.error);
  //     return;
  //   }
  //   if (offset >= fileSize) {
  //     const ans = await hash.hex();
  //     console.log(ans);
  //     ansList.push(ans);
  //     if (type.localeCompare('multiple') === 0) {
  //       filesUploadHashes.PostFiles.push(ans);
  //     } else {
  //       filesUploadHashes.Thumbnail.push(ans);
  //     }
  //     return;
  //   }

  //   // of to the next chunk
  //   await chunkreaderblock(offset, chunkSize, file);
  //   return ansList;
  // };

  chunkreaderblock = async function (_offset, length, _file) {
    // const reader = new FileReader();
    const blob = await _file.slice(_offset, length + _offset);
    const data = await blob.text();
    offset += data.length;
    await hash.update(data);
    if (offset >= fileSize) {
      const ans = await hash.hex();
      // console.log(ans);
      // ansList.push(ans);
      if (type.localeCompare('multiple') === 0) {
        filesUploadHashes.PostFiles.push(ans);
      } else {
        filesUploadHashes.Thumbnail.push(ans);
      }
      return;
    }

    // of to the next chunk
    await chunkreaderblock(offset, chunkSize, file);
  };

  // read with the first block
  await chunkreaderblock(offset, chunkSize, file);
  return ansList;
}
const messageGroups = ['Public', 'Restricted Circle', 'Restricted Node Group'];
const messageGroupsCode = [util.PUBLIC, util.EXTERNAL, util.NODES_GROUP]; // rsgxscirles.h:50

function createchannel() {
  let title;
  let body;
  let identity;
  let thumbnail;
  let selectedGroup = messageGroups[0];
  let selectedGroupCode = messageGroupsCode[0];
  let selectedCircle;
  let circles;
  return {
    oninit: async (vnode) => {
      if (vnode.attrs.authorId) {
        identity = vnode.attrs.authorId[0];
      }

      const res = await rs.rsJsonApiRequest('/rsgxscircles/getCirclesSummaries');
      if (res.body.retval) {
        circles = res.body.circles;
        selectedCircle = circles[0].mGroupName;
      }
    },
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Create Channel'),
        m('hr'),
        m('input[type=text][placeholder=Title]', {
          style: { float: 'left' },
          oninput: (e) => (title = e.target.value),
        }),
        m('div', { style: { float: 'right', marginTop: '10px', marginBottom: '10px' } }, [
          m('label[for=thumbnail]', 'Thumbnail: '),
          m('input[type=file][name=files][id=thumbnail][accept=image/*]', {
            onchange: async (e) => {
              let reader = new FileReader();
              reader.onloadend = function () {
                thumbnail = reader.result.substring(reader.result.indexOf(',') + 1);
              };
              reader.readAsDataURL(e.target.files[0]);
            },
          }),
        ]),

        m('div', { style: { float: 'right', marginTop: '10px', marginBottom: '10px' } }, [
          m('label[for=idtags]', 'Select identity: '),
          m(
            'select[id=idtags]',
            {
              value: identity,
              onchange: (e) => {
                identity = vnode.attrs.authorId[e.target.selectedIndex];
              },
            },
            [
              vnode.attrs.authorId &&
                vnode.attrs.authorId.map((o) =>
                  m(
                    'option',
                    { value: o },
                    rs.userList.userMap[o]
                      ? rs.userList.userMap[o].toLocaleString() + ' (' + o.slice(0, 8) + '...)'
                      : 'No Signature'
                  )
                ),
            ]
          ),
        ]),
        m('div', { style: { float: 'left', marginTop: '10px', marginBottom: '10px' } }, [
          m('label[for=mtags]', 'Message Distribution: '),
          m(
            'select[id=mtags]',
            {
              value: selectedGroup,
              onchange: (e) => {
                selectedGroup = messageGroups[e.target.selectedIndex];
                selectedGroupCode = messageGroupsCode[e.target.selectedIndex];
                widget.popupMessage(m(createchannel, { authorId: vnode.attrs.authorId }));
              },
            },
            [messageGroups.map((group) => m('option', { value: group }, group))]
          ),
        ]),
        circles &&
          m(
            'div',
            {
              style: {
                float: 'left',
                marginTop: '10px',
                marginBottom: '10px',
                display: selectedGroupCode === util.EXTERNAL ? 'block' : 'none',
              },
            },
            [
              m('label[for=circlestag]', 'Circles: '),
              m(
                'select[id=circlestag]',
                {
                  value: selectedCircle,
                  onchange: (e) => {
                    selectedCircle = circles[e.target.selectedIndex];
                    // selectedGroupCode = messageGroupsCode[e.target.selectedIndex];
                  },
                },
                [
                  circles.map((circle) =>
                    m('option', { value: circle.mGroupName }, circle.mGroupName)
                  ),
                ]
              ),
            ]
          ),
        m('textarea[rows=5][placeholder=Description]', {
          style: { width: '100%', display: 'block' },
          oninput: (e) => (body = e.target.value),
          value: body,
        }),
        m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsgxschannels/createChannelV2', {
                name: title,
                description: body,
                thumbnail: { mData: { base64: thumbnail } },
                ...(Number(identity) !== 0 && { authorId: identity }), // checks if some identity has to be assigned
                circleType: selectedGroupCode,
                ...(selectedGroupCode === util.EXTERNAL &&
                  selectedCircle && { circleId: selectedCircle.mGroupId }), // checks if the selectedGroup code is EXTERNAL
              });
              if (res.body.retval) {
                util.updatedisplaychannels(res.body.channelId);
                m.redraw();
              }
              res.body.retval === false
                ? widget.popupMessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
                : widget.popupMessage([
                    m('h3', 'Success'),
                    m('hr'),
                    m('p', 'Channel created successfully'),
                  ]);
            },
          },
          'Create'
        ),
      ]),
  };
}

const AddPost = () => {
  let content = '';
  let ptitle = '';
  let pthumbnail = [];
  let pfiles = [];
  let uploadFiles = true;
  return {
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Add Post'),
        m('hr'),
        m('label[for=thumbnail]', 'Thumbnail: '),
        m('input[type=file][name=files][id=thumbnail][accept=image/*]', {
          onchange: async (e) => {
            let reader = new FileReader();
            reader.onloadend = function () {
              pthumbnail = reader.result.substring(reader.result.indexOf(',') + 1);
            };
            reader.readAsDataURL(e.target.files[0]); // converts into base64 string
          },
        }),
        m('label[for=browse]', 'Attachments: '),
        m('input[type=file][name=files][id=browse][multiple=multiple]', {
          // attachments option wrong hash, not working
          onchange: async (e) => {
            uploadFiles = false;
            filesUploadHashes.PostFiles = [];
            pfiles = [];
            for (let i = 0; i < e.target.files.length; i++) {
              await parsefile(e.target.files[i], 'multiple');
            }
            // console.log(filesUploadHashes.PostFiles, filesUploadHashes.PostFiles.length);

            if (filesUploadHashes.PostFiles.length === e.target.files.length) {
              for (let i = 0; i < e.target.files.length; i++) {
                pfiles.push({
                  name: e.target.files[i].name,
                  size: e.target.files[i].size,
                  hash: filesUploadHashes.PostFiles[i],
                });
              }
              uploadFiles = true;
            }
          },
        }),
        m('input[type=text][placeholder=Title]', {
          oninput: (e) => (ptitle = e.target.value),
        }),
        m('textarea[rows=5]', {
          style: { width: '90%', display: 'block' },
          oninput: (e) => (content = e.target.value),
          value: content,
        }),
        m(
          'button',
          {
            onclick: async () => {
              if (uploadFiles) {
                // console.log(vnode.attrs.chanId, ptitle, content, pfiles, pthumbnail);
                const res = await rs.rsJsonApiRequest('/rsgxschannels/createPostV2', {
                  channelId: vnode.attrs.chanId,
                  title: ptitle,
                  mBody: content,
                  files: pfiles, // does not work for now
                  thumbnail: { mData: { base64: pthumbnail } },
                });
                res.body.retval === false
                  ? widget.popupMessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
                  : widget.popupMessage([
                      m('h3', 'Success'),
                      m('hr'),
                      m('p', 'Post added successfully'),
                    ]);
                util.updatedisplaychannels(vnode.attrs.chanId);
                m.redraw();
              }
            },
          },
          'Add'
        ),
      ]),
  };
};

const ChannelView = () => {
  let cname = '';
  let cimage = '';
  let cauthor = '';
  let csubscribed = {};
  let mychannel = false;
  let cposts = 0;
  let plist = {};
  let createDate = {};
  let lastActivity = {};
  return {
    oninit: (v) => {
      if (Data.DisplayChannels[v.attrs.id]) {
        cname = Data.DisplayChannels[v.attrs.id].name;
        cimage = Data.DisplayChannels[v.attrs.id].image;
        if (rs.userList.userMap[Data.DisplayChannels[v.attrs.id].author]) {
          cauthor = rs.userList.userMap[Data.DisplayChannels[v.attrs.id].author];
        } else if (Number(Data.DisplayChannels[v.attrs.id].author) === 0) {
          cauthor = 'No Contact Author';
        } else {
          cauthor = 'Unknown';
        }
        csubscribed = Data.DisplayChannels[v.attrs.id].isSubscribed;
        mychannel = Data.DisplayChannels[v.attrs.id].mychannel;
        cposts = Data.DisplayChannels[v.attrs.id].posts;
        createDate = Data.DisplayChannels[v.attrs.id].created;
        lastActivity = Data.DisplayChannels[v.attrs.id].activity;
      }
      if (Data.Posts[v.attrs.id]) {
        plist = Data.Posts[v.attrs.id];
      }
    },
    view: (v) => [
      m(
        'a[title=Back]',
        {
          onclick: () =>
            m.route.set('/channels/:tab', {
              tab: m.route.param().tab,
            }),
        },
        m('i.fas.fa-arrow-left')
      ),
      m('.widget__heading', [
        m('h3', cname),
        m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsgxschannels/subscribeToChannel', {
                channelId: v.attrs.id,
                subscribe: !csubscribed,
              });
              if (res.body.retval) {
                csubscribed = !csubscribed;
                Data.DisplayChannels[v.attrs.id].isSubscribed = csubscribed;
              }
            },
          },
          csubscribed ? 'Subscribed' : 'Subscribe'
        ),
      ]),
      m('.widget__body', [
        m('.media-item', [
          m('.media-item__details', [
            m('img', {
              src:
                cimage.mData.base64 === ''
                  ? 'data/streaming.png'
                  : `data:image/png;base64,${cimage.mData.base64}`,
            }),
            m('.media-item__details-info', [
              m('div', [m('b', 'Posts: '), m('span', cposts)]),
              m('div', [
                m('b', 'Date created: '),
                m(
                  'span',
                  typeof createDate === 'object'
                    ? new Date(createDate.xint64 * 1000).toLocaleString()
                    : 'Unknown'
                ),
              ]),
              m('div', [m('b', 'Admin: '), m('span', cauthor)]),
              m('div', [
                m('b', 'Last activity: '),
                m(
                  'span',
                  typeof lastActivity === 'object'
                    ? new Date(lastActivity.xint64 * 1000).toLocaleString()
                    : 'Unknown'
                ),
              ]),
            ]),
          ]),
          m('.media-item__desc', [
            m('b', 'Description: '),
            m('span', Data.DisplayChannels[v.attrs.id].description || 'No Description'),
          ]),
        ]),
        m(
          '.posts',
          {
            style: 'display: ' + (csubscribed ? 'flex' : 'none'),
          },
          [
            m('.posts__heading', [
              m('h3', 'Posts'),
              mychannel &&
                m(
                  'button',
                  { onclick: () => widget.popupMessage(m(AddPost, { chanId: v.attrs.id })) },
                  ['Add Post', m('i.fas.fa-edit')]
                ),
            ]),
            m(
              '.posts-container',
              Object.keys(plist).map((key, index) => [
                m(
                  '.posts-container-card',
                  {
                    style: 'display: ' + (plist[key].isSearched ? 'flex' : 'none'), // for search
                    onclick: () => {
                      m.route.set('/channels/:tab/:mGroupId/:mMsgId', {
                        tab: m.route.param().tab,
                        mGroupId: v.attrs.id,
                        mMsgId: key,
                      });
                    },
                  },
                  [
                    m('img', {
                      src:
                        plist[key].post.mThumbnail.mData.base64 === ''
                          ? 'data/streaming.png'
                          : 'data:image/png;base64,' + plist[key].post.mThumbnail.mData.base64,
                      alt: 'No Thumbnail',
                    }),
                    m('p', plist[key].post.mMeta.mMsgName),
                  ]
                ),
              ])
            ),
          ]
        ),
      ]),
    ],
  };
};

async function addvote(voteType, vchannelId, vpostId, vauthorId, vcommentId) {
  const res = await rs.rsJsonApiRequest('/rsgxschannels/voteForComment', {
    channelId: vchannelId,
    postId: vpostId,
    authorId: vauthorId,
    commentId: vcommentId,
    vote: voteType,
  });
  if (res.body.retval) {
    util.updatedisplaychannels(vchannelId);
    m.redraw();
  }
}

const AddComment = () => {
  let inputComment = '';
  let identity;
  return {
    oninit: (vnode) => {
      if (vnode.attrs.authorId) {
        identity = vnode.attrs.authorId[0];
      }
    },
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Add Comment'),
        m('label[for=tags]', 'Select identity'),
        m(
          'select[id=idtags]',
          {
            value: identity,
            onchange: (e) => {
              identity = vnode.attrs.authorId[e.target.selectedIndex];
            },
          },
          [
            vnode.attrs.authorId &&
              vnode.attrs.authorId.map((o) =>
                m(
                  'option',
                  { value: o },
                  rs.userList.userMap[o].toLocaleString() + ' (' + o.slice(0, 8) + '...)'
                )
              ),
          ]
        ),
        m('hr'),
        (vnode.attrs.parent_comment !== '') > 0
          ? [m('h5', 'Reply to comment: '), m('p', vnode.attrs.parent_comment)] // if it is add reply option
          : '',
        m('textarea[rows=5]', {
          style: { width: '90%', display: 'block' },
          oninput: (e) => (inputComment = e.target.value),
          value: inputComment,
        }),
        m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsgxschannels/createCommentV2', {
                channelId: vnode.attrs.channelId,
                threadId: vnode.attrs.threadId,
                comment: inputComment,
                authorId: identity,
                parentId: vnode.attrs.parentId,
              });

              res.body.retval === false
                ? widget.popupMessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
                : widget.popupMessage([
                    m('h3', 'Success'),
                    m('hr'),
                    m('p', 'Comment added successfully'),
                  ]);
              util.updatedisplaychannels(vnode.attrs.channelId);
              m.redraw();
            },
          },
          'Add'
        ),
      ]),
  };
};
function displaycomment() {
  // recursive function to display comments
  return {
    oninit: (v) => {},
    view: ({ attrs: { commentStruct, identity, replyDepth, voteIdentity } }) => {
      const comment = commentStruct.comment;
      let cUpVotes = 0;
      let cDownVotes = 0;
      let parMap = {};
      if (Data.ParentCommentMap[comment.mMeta.mMsgId]) {
        parMap = Data.ParentCommentMap[comment.mMeta.mMsgId];
      }
      if (
        Data.Votes[comment.mMeta.mThreadId] &&
        Data.Votes[comment.mMeta.mThreadId][comment.mMeta.mMsgId]
      ) {
        cUpVotes = Data.Votes[comment.mMeta.mThreadId][comment.mMeta.mMsgId].upvotes;
        cDownVotes = Data.Votes[comment.mMeta.mThreadId][comment.mMeta.mMsgId].downvotes;
      }
      return [
        m('tr', [
          Object.keys(parMap).length // if it has replies
            ? m(
                'td',
                m('i.fas.fa-angle-right', {
                  class: 'fa-rotate-' + (commentStruct.showReplies ? '90' : '0'),
                  style: 'cursor:pointer',
                  onclick: () => {
                    commentStruct.showReplies = !commentStruct.showReplies;
                  },
                })
              )
            : m('td', ''),

          m(
            'td',
            {
              style: {
                position: 'relative',
                '--replyDepth': replyDepth,
                left: 'calc(30px*var(--replyDepth))', // shifts the reply by 30px
              },
            },
            [
              comment.mComment,
              m('options', { style: 'display:block' }, [
                m(
                  'button',
                  {
                    style: 'font-size:15px',
                    onclick: () =>
                      widget.popupMessage(
                        m(AddComment, {
                          parent_comment: comment.mComment,
                          channelId: comment.mMeta.mGroupId,
                          authorId: identity,
                          threadId: comment.mMeta.mThreadId,
                          parentId: comment.mMeta.mMsgId,
                        })
                      ),
                  },
                  'Reply'
                ),
                voteIdentity &&
                  m(
                    'button',
                    {
                      style: 'font-size:15px',
                      onclick: () =>
                        addvote(
                          util.GXS_VOTE_UP,
                          comment.mMeta.mGroupId,
                          comment.mMeta.mThreadId,
                          voteIdentity,
                          comment.mMeta.mMsgId
                        ),
                    },
                    m('i.fas.fa-thumbs-up')
                  ),
                voteIdentity &&
                  m(
                    'button',
                    {
                      style: 'font-size:15px',
                      onclick: () =>
                        addvote(
                          util.GXS_VOTE_DOWN,
                          comment.mMeta.mGroupId,
                          comment.mMeta.mThreadId,
                          voteIdentity,
                          comment.mMeta.mMsgId
                        ),
                    },
                    m('i.fas.fa-thumbs-down')
                  ),
              ]),
            ]
          ),

          m('td', rs.userList.userMap[comment.mMeta.mAuthorId]),
          m(
            'td',
            typeof comment.mMeta.mPublishTs === 'object'
              ? new Date(comment.mMeta.mPublishTs.xint64 * 1000).toLocaleString()
              : 'undefined'
          ),
          m('td', comment.mScore),
          m('td', cUpVotes),
          m('td', cDownVotes),
        ]),
        commentStruct.showReplies && // recursive calls for the replies
          // parMap.map((value) =>
          Object.keys(parMap).map((key, index) =>
            m(displaycomment, {
              commentStruct: Data.Comments[parMap[key].mMeta.mThreadId][parMap[key].mMeta.mMsgId],
              voteIdentity,
              identity,
              replyDepth: replyDepth + 1, // for the css
            })
          ),
      ];
    },
  };
}

const PostView = () => {
  let post = {};
  let topComments = {};
  const filesInfo = {};
  let voteIdentity;
  let ownId;
  return {
    oninit: async (v) => {
      if (Data.Posts[v.attrs.channelId] && Data.Posts[v.attrs.channelId][v.attrs.msgId]) {
        post = Data.Posts[v.attrs.channelId][v.attrs.msgId].post;
      }
      if (Data.TopComments[v.attrs.msgId]) {
        topComments = Data.TopComments[v.attrs.msgId]; // get all the top level parent comments
      }
      if (post) {
        post.mFiles.map(async (file) => {
          const res = await rs.rsJsonApiRequest('/rsfiles/alreadyHaveFile', {
            // checks if the file is already there with the user
            hash: file.mHash,
          });
          filesInfo[file.mHash] = res.body;
        });
      }
      await peopleUtil.ownIds((data) => {
        ownId = data;
        for (let i = 0; i < ownId.length; i++) {
          if (Number(ownId[i]) === 0) {
            ownId.splice(i, 1); // workaround for id '0'
          }
        }
        voteIdentity = ownId[0];
      });
      fileDown.Downloads.loadStatus(); // for retrieving downloading files.
    },
    view: (v) => [
      m(
        'a[title=Back]',
        {
          onclick: () =>
            m.route.set('/channels/:tab/:mGroupId', {
              tab: m.route.param().tab,
              mGroupId: m.route.param().mGroupId,
            }),
        },
        m('i.fas.fa-arrow-left')
      ),
      m('.widget__heading', m('h3', post.mMeta.mMsgName)),
      m('.widget__body', [
        m('p', { style: { whiteSpace: 'normal' } }, m.trust(post.mMsg)),
        m('.file-section', [
          m('h3', 'Files(' + post.mAttachmentCount + ')'),
          m(
            util.FilesTable,
            m(
              'tbody',
              post.mFiles.map((file) =>
                m('tr', [
                  m('td', file.mName),
                  m('td', rs.formatBytes(file.mSize.xint64)),
                  m(
                    'button',
                    {
                      style: { fontSize: '0.9em' },
                      onclick: async () =>
                        widget.popupMessage([
                          m('p', 'Start Download?'),
                          m(
                            'button',
                            {
                              onclick: async () => {
                                if (filesInfo[file.mHash] && !filesInfo[file.mHash].retval) {
                                  const res = await rs.rsJsonApiRequest('/rsFiles/FileRequest', {
                                    fileName: file.mName,
                                    hash: file.mHash,
                                    flags: util.RS_FILE_REQ_ANONYMOUS_ROUTING,
                                    size: {
                                      xstr64: file.mSize.xstr64,
                                    },
                                  });
                                  res.body.retval === false
                                    ? widget.popupMessage([
                                        m('h3', 'Error'),
                                        m('hr'),
                                        m('p', res.body.errorMessage),
                                      ])
                                    : widget.popupMessage([
                                        m('h3', 'Success'),
                                        m('hr'),
                                        m('p', 'Download Started'),
                                      ]);
                                  m.redraw();
                                }
                              },
                            },
                            'Start Download'
                          ),
                        ]),
                    },
                    filesInfo[file.mHash]
                      ? filesInfo[file.mHash].retval
                        ? 'Open File'
                        : ['Download', m('i.fas.fa-download')]
                      : 'Please Wait...'
                  ),
                  fileDown.list[file.mHash] && // using the file from files_util to display download.
                    m(fileUtil.File, {
                      info: fileDown.list[file.mHash],
                      direction: 'down',
                      transferred: fileDown.list[file.mHash].transfered.xint64,
                      parts: [],
                    }),
                ])
              )
            )
          ),
        ]),
        m('.comments-section', [
          m('h3', 'Comments'),
          m('.comments-section__menu', [
            m(
              'button',
              {
                onclick: () => {
                  widget.popupMessage(
                    m(AddComment, {
                      parent_comment: '',
                      channelId: v.attrs.channelId,
                      authorId: ownId,
                      threadId: v.attrs.msgId,
                      parentId: v.attrs.msgId,
                    })
                  );
                },
              },
              'Add Comment'
            ),
            m('.comments-section__menu-id', [
              m('label[for=idtags', 'Voter ID: '),
              m(
                'select[id=idtags]',
                {
                  value: voteIdentity,
                  onchange: (e) => {
                    voteIdentity = ownId[e.target.selectedIndex];
                  },
                },
                [
                  ownId &&
                    ownId.map((o) =>
                      m(
                        'option',
                        { value: o },
                        `${rs.userList.userMap[o].toLocaleString()} (${o.slice(0, 8)}...)`
                      )
                    ),
                ]
              ),
            ]),
          ]),
        ]),
        m(
          util.CommentsTable,
          m(
            'tbody',
            Object.keys(topComments).map((key, index) =>
              Data.Comments[topComments[key].mMeta.mThreadId] &&
              Data.Comments[topComments[key].mMeta.mThreadId][topComments[key].mMeta.mMsgId]
                ? m(displaycomment, {
                    // calls the recursive function for all the parents.
                    identity: ownId,
                    voteIdentity,
                    commentStruct:
                      Data.Comments[topComments[key].mMeta.mThreadId][
                        topComments[key].mMeta.mMsgId
                      ],
                    replyDepth: 0,
                  })
                : ''
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = {
  ChannelView,
  PostView,
  createchannel,
};
 
}); 
require.register("channels/my_channels", function(exports, require, module) { 
const m = require('mithril');
const util = require('channels/channels_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'My Channels')),
      m('.widget__body', [
        m(
          util.ChannelTable,
          m('tbody', [
            v.attrs.list.map((channel) =>
              m(util.ChannelSummary, {
                details: channel,
                category: 'MyChannels',
              })
            ),
            v.attrs.list.map((channel) =>
              m(util.DisplayChannelsFromList, {
                id: channel.mGroupId,
                category: 'MyChannels',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("channels/other_channels", function(exports, require, module) { 
const m = require('mithril');
const util = require('channels/channels_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Other Channels')),
      m('.widget__body', [
        m(
          util.ChannelTable,
          m('tbody', [
            v.attrs.list.map((channel) =>
              m(util.ChannelSummary, {
                details: channel,
                category: 'OtherChannels',
              })
            ),
            v.attrs.list.map((channel) =>
              m(util.DisplayChannelsFromList, {
                id: channel.mGroupId,
                category: 'OtherChannels',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("channels/popular_channels", function(exports, require, module) { 
const m = require('mithril');
const util = require('channels/channels_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Popular Channels')),
      m('.widget__body', [
        m(
          util.ChannelTable,
          m('tbody', [
            v.attrs.list.map((channel) =>
              m(util.ChannelSummary, {
                details: channel,
                category: 'PopularChannels',
              })
            ),
            v.attrs.list.map((channel) =>
              m(util.DisplayChannelsFromList, {
                id: channel.mGroupId,
                category: 'PopularChannels',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("channels/sha1", function(exports, require, module) { 
/*
 * [js-sha1]{@link https://github.com/emn178/js-sha1}
 *
 * @version 0.6.0
 * @author Chen, Yi-Cyuan [emn178@gmail.com]
 * @copyright Chen, Yi-Cyuan 2014-2017
 * @license MIT
 */
/* jslint bitwise: true */
(function() {
    'use strict';

    let root = typeof window === 'object' ? window : {};
    const NODE_JS = !root.JS_SHA1_NO_NODE_JS && typeof process === 'object';
    if (NODE_JS) {
      root = global;
    }
    const COMMON_JS = !root.JS_SHA1_NO_COMMON_JS && typeof module === 'object' && module.exports;
    // const AMD = typeof define === 'function' && define.amd;
    const HEX_CHARS = '0123456789abcdef'.split('');
    const EXTRA = [-2147483648, 8388608, 32768, 128];
    const SHIFT = [24, 16, 8, 0];
    const OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer'];

    const blocks = [];
    function Sha1(sharedMemory) {
      if (sharedMemory) {
        blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
        blocks[4] = blocks[5] = blocks[6] = blocks[7] =
        blocks[8] = blocks[9] = blocks[10] = blocks[11] =
        blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
        this.blocks = blocks;
      } else {
        this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
      }

      this.h0 = 0x67452301;
      this.h1 = 0xEFCDAB89;
      this.h2 = 0x98BADCFE;
      this.h3 = 0x10325476;
      this.h4 = 0xC3D2E1F0;

      this.block = this.start = this.bytes = this.hBytes = 0;
      this.finalized = this.hashed = false;
      this.first = true;
    }
    const createOutputMethod = function (outputType) {
      return function (message) {
        return new Sha1(true).update(message)[outputType]();
      };
    };
    const nodeWrap = function (method) {
      const crypto = eval('require(\'crypto\')');
      const Buffer = eval('require(\'buffer\').Buffer');
      const nodeMethod = function (message) {
        if (typeof message === 'string') {
          return crypto.createHash('sha1').update(message, 'utf8').digest('hex');
        } else if (message.constructor === ArrayBuffer) {
          message = new Uint8Array(message);
        } else if (message.length === undefined) {
          return method(message);
        }
        return crypto.createHash('sha1').update(new Buffer(message)).digest('hex');
      };
      return nodeMethod;
    };
    const createMethod = function () {
      let method = createOutputMethod('hex');
      if (NODE_JS) {
        method = nodeWrap(method);
      }
      method.create = function () {
        return new Sha1();
      };
      method.update = function (message) {
        return method.create().update(message);
      };
      for (let i = 0; i < OUTPUT_TYPES.length; ++i) {
        const type = OUTPUT_TYPES[i];
        method[type] = createOutputMethod(type);
      }
      return method;
    };


    Sha1.prototype.update = function (message) {
      if (this.finalized) {
        return;
      }
      const notString = typeof(message) !== 'string';
      if (notString && message.constructor === root.ArrayBuffer) {
        message = new Uint8Array(message);
      }
      let code, index = 0, i;
      const length = message.length || 0, blocks = this.blocks;

      while (index < length) {
        if (this.hashed) {
          this.hashed = false;
          blocks[0] = this.block;
          blocks[16] = blocks[1] = blocks[2] = blocks[3] =
          blocks[4] = blocks[5] = blocks[6] = blocks[7] =
          blocks[8] = blocks[9] = blocks[10] = blocks[11] =
          blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
        }

        if(notString) {
          for (i = this.start; index < length && i < 64; ++index) {
            blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
          }
        } else {
          for (i = this.start; index < length && i < 64; ++index) {
            code = message.charCodeAt(index);
            if (code < 0x80) {
              blocks[i >> 2] |= code << SHIFT[i++ & 3];
            } else if (code < 0x800) {
              blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
              blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
            } else if (code < 0xd800 || code >= 0xe000) {
              blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
              blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
              blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
            } else {
              code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
              blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
              blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
              blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
              blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
            }
          }
        }

        this.lastByteIndex = i;
        this.bytes += i - this.start;
        if (i >= 64) {
          this.block = blocks[16];
          this.start = i - 64;
          this.hash();
          this.hashed = true;
        } else {
          this.start = i;
        }
      }
      if (this.bytes > 4294967295) {
        this.hBytes += this.bytes / 4294967296 << 0;
        this.bytes = this.bytes % 4294967296;
      }
      return this;
    };

    Sha1.prototype.finalize = function () {
      if (this.finalized) {
        return;
      }
      this.finalized = true;
      const blocks = this.blocks, i = this.lastByteIndex;
      blocks[16] = this.block;
      blocks[i >> 2] |= EXTRA[i & 3];
      this.block = blocks[16];
      if (i >= 56) {
        if (!this.hashed) {
          this.hash();
        }
        blocks[0] = this.block;
        blocks[16] = blocks[1] = blocks[2] = blocks[3] =
        blocks[4] = blocks[5] = blocks[6] = blocks[7] =
        blocks[8] = blocks[9] = blocks[10] = blocks[11] =
        blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
      }
      blocks[14] = this.hBytes << 3 | this.bytes >>> 29;
      blocks[15] = this.bytes << 3;
      this.hash();
    };

    Sha1.prototype.hash = function () {
      let a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4;
      let f, j, t;
      const blocks = this.blocks;

      for(j = 16; j < 80; ++j) {
        t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
        blocks[j] =  (t << 1) | (t >>> 31);
      }

      for(j = 0; j < 20; j += 5) {
        f = (b & c) | ((~b) & d);
        t = (a << 5) | (a >>> 27);
        e = t + f + e + 1518500249 + blocks[j] << 0;
        b = (b << 30) | (b >>> 2);

        f = (a & b) | ((~a) & c);
        t = (e << 5) | (e >>> 27);
        d = t + f + d + 1518500249 + blocks[j + 1] << 0;
        a = (a << 30) | (a >>> 2);

        f = (e & a) | ((~e) & b);
        t = (d << 5) | (d >>> 27);
        c = t + f + c + 1518500249 + blocks[j + 2] << 0;
        e = (e << 30) | (e >>> 2);

        f = (d & e) | ((~d) & a);
        t = (c << 5) | (c >>> 27);
        b = t + f + b + 1518500249 + blocks[j + 3] << 0;
        d = (d << 30) | (d >>> 2);

        f = (c & d) | ((~c) & e);
        t = (b << 5) | (b >>> 27);
        a = t + f + a + 1518500249 + blocks[j + 4] << 0;
        c = (c << 30) | (c >>> 2);
      }

      for(; j < 40; j += 5) {
        f = b ^ c ^ d;
        t = (a << 5) | (a >>> 27);
        e = t + f + e + 1859775393 + blocks[j] << 0;
        b = (b << 30) | (b >>> 2);

        f = a ^ b ^ c;
        t = (e << 5) | (e >>> 27);
        d = t + f + d + 1859775393 + blocks[j + 1] << 0;
        a = (a << 30) | (a >>> 2);

        f = e ^ a ^ b;
        t = (d << 5) | (d >>> 27);
        c = t + f + c + 1859775393 + blocks[j + 2] << 0;
        e = (e << 30) | (e >>> 2);

        f = d ^ e ^ a;
        t = (c << 5) | (c >>> 27);
        b = t + f + b + 1859775393 + blocks[j + 3] << 0;
        d = (d << 30) | (d >>> 2);

        f = c ^ d ^ e;
        t = (b << 5) | (b >>> 27);
        a = t + f + a + 1859775393 + blocks[j + 4] << 0;
        c = (c << 30) | (c >>> 2);
      }

      for(; j < 60; j += 5) {
        f = (b & c) | (b & d) | (c & d);
        t = (a << 5) | (a >>> 27);
        e = t + f + e - 1894007588 + blocks[j] << 0;
        b = (b << 30) | (b >>> 2);

        f = (a & b) | (a & c) | (b & c);
        t = (e << 5) | (e >>> 27);
        d = t + f + d - 1894007588 + blocks[j + 1] << 0;
        a = (a << 30) | (a >>> 2);

        f = (e & a) | (e & b) | (a & b);
        t = (d << 5) | (d >>> 27);
        c = t + f + c - 1894007588 + blocks[j + 2] << 0;
        e = (e << 30) | (e >>> 2);

        f = (d & e) | (d & a) | (e & a);
        t = (c << 5) | (c >>> 27);
        b = t + f + b - 1894007588 + blocks[j + 3] << 0;
        d = (d << 30) | (d >>> 2);

        f = (c & d) | (c & e) | (d & e);
        t = (b << 5) | (b >>> 27);
        a = t + f + a - 1894007588 + blocks[j + 4] << 0;
        c = (c << 30) | (c >>> 2);
      }

      for(; j < 80; j += 5) {
        f = b ^ c ^ d;
        t = (a << 5) | (a >>> 27);
        e = t + f + e - 899497514 + blocks[j] << 0;
        b = (b << 30) | (b >>> 2);

        f = a ^ b ^ c;
        t = (e << 5) | (e >>> 27);
        d = t + f + d - 899497514 + blocks[j + 1] << 0;
        a = (a << 30) | (a >>> 2);

        f = e ^ a ^ b;
        t = (d << 5) | (d >>> 27);
        c = t + f + c - 899497514 + blocks[j + 2] << 0;
        e = (e << 30) | (e >>> 2);

        f = d ^ e ^ a;
        t = (c << 5) | (c >>> 27);
        b = t + f + b - 899497514 + blocks[j + 3] << 0;
        d = (d << 30) | (d >>> 2);

        f = c ^ d ^ e;
        t = (b << 5) | (b >>> 27);
        a = t + f + a - 899497514 + blocks[j + 4] << 0;
        c = (c << 30) | (c >>> 2);
      }

      this.h0 = this.h0 + a << 0;
      this.h1 = this.h1 + b << 0;
      this.h2 = this.h2 + c << 0;
      this.h3 = this.h3 + d << 0;
      this.h4 = this.h4 + e << 0;
    };

    Sha1.prototype.hex = function () {
      this.finalize();

      const h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;

      return HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] +
             HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] +
             HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] +
             HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +
             HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] +
             HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] +
             HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] +
             HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +
             HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] +
             HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] +
             HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] +
             HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +
             HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] +
             HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] +
             HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] +
             HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +
             HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] +
             HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] +
             HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] +
             HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F];
    };

    Sha1.prototype.toString = Sha1.prototype.hex;

    Sha1.prototype.digest = function () {
      this.finalize();

      const h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;

      return [
        (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF,
        (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF,
        (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF,
        (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF,
        (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF
      ];
    };

    Sha1.prototype.array = Sha1.prototype.digest;

    Sha1.prototype.arrayBuffer = function () {
      this.finalize();

      const buffer = new ArrayBuffer(20);
      const dataView = new DataView(buffer);
      dataView.setUint32(0, this.h0);
      dataView.setUint32(4, this.h1);
      dataView.setUint32(8, this.h2);
      dataView.setUint32(12, this.h3);
      dataView.setUint32(16, this.h4);
      return buffer;
    };

    const exports = createMethod();

    if (COMMON_JS) {
      module.exports = exports;
    } else {
      root.sha1 = exports;
      // if (AMD) {
      //   define(function () {
      //     return exports;
      //   });
      // }
    }
  })();
 
}); 
require.register("channels/subscribed_channels", function(exports, require, module) { 
const m = require('mithril');
const util = require('channels/channels_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Subscribed Channels')),
      m('.widget__body', [
        m(
          util.ChannelTable,
          m('tbody', [
            v.attrs.list.map((channel) =>
              m(util.ChannelSummary, {
                details: channel,
                category: 'SubscribedChannels',
              })
            ),
            v.attrs.list.map((channel) =>
              m(util.DisplayChannelsFromList, {
                id: channel.mGroupId,
                category: 'SubscribedChannels',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("chat/chat", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const peopleUtil = require('people/people_util');

// **************** utility functions ********************

function loadLobbyDetails(id, apply) {
  rs.rsJsonApiRequest(
    '/rsMsgs/getChatLobbyInfo',
    {
      id,
    },
    (detail) => {
      if (detail.retval) {
        apply(detail.info);
      }
    },
    true,
    {},
    undefined,
    // Custom serializer NOTE:
    // Since id represents 64-bit int(see deserializer note below)
    // Instead of using JSON.stringify, this function directly
    // creates a json string manually.
    () => '{"id":' + id + '}'
  );
}

function sortLobbies(lobbies) {
  if (lobbies !== undefined) {
    const list = [...lobbies];
    list.sort((a, b) => a.lobby_name.localeCompare(b.lobby_name));
    return list;
  }
  // return lobbies; // fallback on reload page in browser, keep undefiend
}

// ***************************** models ***********************************

const ChatRoomsModel = {
  allRooms: [],
  knownSubscrIds: [], // to exclude subscribed from public rooms (subscribedRooms filled to late)
  subscribedRooms: {},
  loadPublicRooms() {
    // TODO: this doesn't preserve id of rooms,
    // use regex on response to extract ids.
    rs.rsJsonApiRequest(
      '/rsMsgs/getListOfNearbyChatLobbies',
      {},
      (data) => (ChatRoomsModel.allRooms = sortLobbies(data.public_lobbies))
    );
  },
  loadSubscribedRooms(after = null) {
    // ChatRoomsModel.subscribedRooms = {};
    rs.rsJsonApiRequest(
      '/rsMsgs/getChatLobbyList',
      {},
      // JS uses double precision numbers of 64 bit. It is equivalent
      // to 53 bits of precision. All large precision ints will
      // get truncated to an approximation.
      // This API uses Cpp-style 64 bits for `id`.
      // So we use the string-value 'xstr64' instead
      (data) => {
        const ids = data.cl_list.map((lid) => lid.xstr64);
        ChatRoomsModel.knownSubscrIds = ids;
        const rooms = {};
        ids.map((id) =>
          loadLobbyDetails(id, (info) => {
            rooms[id] = info;
            if (Object.keys(rooms).length === ids.length) {
              // apply rooms to subscribedRooms only after reading all room-details, so sorting all or nothing
              ChatRoomsModel.subscribedRooms = rooms;
            }
          })
        );
        if (after != null) {
          after();
        }
      }
    );
  },
  subscribed(info) {
    return this.knownSubscrIds.includes(info.lobby_id.xstr64);
  },
};

/**
 * Message displays a single Chat-Message<br>
 * currently removes formatting and in consequence inline links
 * msg: Message to Display
 */
const Message = () => {
  let msg = null; // message to display
  let text = ''; // extracted text to display
  let datetime = ''; // date time to display
  let username = ''; // username to display (later may be linked)
  return {
    oninit: (vnode) => {
      console.info('chat Message', vnode);
      msg = vnode.attrs;
      datetime = new Date(msg.sendTime * 1000).toLocaleTimeString();
      username = rs.userList.username(msg.lobby_peer_gxs_id);
      text = msg.msg
        .replaceAll('<br/>', '\n')
        .replace(new RegExp('<style[^<]*</style>|<[^>]*>', 'gm'), '');
      console.info('chat Text', text);
    },
    view: () =>
      m(
        '.message',
        m('span.datetime', datetime),
        m('span.username', username),
        m('span.messagetext', text)
      ),
  };
};

const ChatLobbyModel = {
  currentLobby: {
    lobby_name: '...',
  },
  lobby_user: '...',
  isSubscribed: false,
  messages: [],
  users: [],
  setupAction: (lobbyId, nick) => {},
  setIdentity(lobbyId, nick) {
    rs.rsJsonApiRequest(
      '/rsMsgs/setIdentityForChatLobby',
      {},
      () => m.route.set('/chat/:lobby_id', { lobbyId }),
      true,
      {},
      JSON.parse,
      () => '{"lobby_id":' + lobbyId + ',"nick":"' + nick + '"}'
    );
  },
  enterPublicLobby(lobbyId, nick) {
    console.info('joinVisibleChatLobby', nick, '@', lobbyId);
    rs.rsJsonApiRequest(
      '/rsMsgs/joinVisibleChatLobby',
      {},
      () => {
        loadLobbyDetails(lobbyId, (info) => {
          ChatRoomsModel.subscribedRooms[lobbyId] = info;
          ChatRoomsModel.loadSubscribedRooms(() => {
            m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 });
          });
        });
      },
      true,
      {},
      JSON.parse,
      () => '{"lobby_id":' + lobbyId + ',"own_id":"' + nick + '"}'
    );
  },
  unsubscribeChatLobby(lobbyId, follow) {
    console.info('unsubscribe lobby', lobbyId);
    rs.rsJsonApiRequest(
      '/rsMsgs/unsubscribeChatLobby',
      {},
      () => ChatRoomsModel.loadSubscribedRooms(follow),
      true,
      {},
      JSON.parse,
      () => '{"lobby_id":' + lobbyId + '}'
    );
  },
  chatId(action) {
    return { type: 3, lobby_id: { xstr64: m.route.param('lobby') } };
  },
  loadLobby(currentlobbyid) {
    loadLobbyDetails(currentlobbyid, (detail) => {
      this.setupAction = this.setIdentity;
      this.currentLobby = detail;
      this.isSubscribed = true;
      this.lobby_user = rs.userList.username(detail.gxs_id) || '???';
      const lobbyid = currentlobbyid;
      // apply existing messages to current lobby view
      rs.events[15].chatMessages(
        this.chatId(),
        rs.events[15],
        (l) => (this.messages = l.map((msg) => m(Message, msg)))
      );
      // register for chatEvents for future messages
      rs.events[15].notify = (chatMessage) => {
        if (chatMessage.chat_id.type === 3 && chatMessage.chat_id.lobby_id.xstr64 === lobbyid) {
          this.messages.push(m(Message, chatMessage));
          m.redraw();
        }
      };
      // lookup for chat-user names (only snapshot, we don't get notified about changes of participants)
      const names = detail.gxs_ids.reduce((a, u) => a.concat(rs.userList.username(u.key)), []);
      names.sort((a, b) => a.localeCompare(b));
      this.users = [];
      names.forEach((name) => (this.users = this.users.concat([m('.user', name)])));
      return this.users;
    });
  },
  loadPublicLobby(currentlobbyid) {
    console.info('loadPublicLobby ChatRoomsModel:', ChatRoomsModel);
    this.setupAction = this.enterPublicLobby;
    this.isSubscribed = false;
    ChatRoomsModel.allRooms.forEach((it) => {
      if (it.lobby_id.xstr64 === currentlobbyid) {
        this.currentLobby = it;
        this.lobby_user = '???';
        this.lobbyid = currentlobbyid;
      }
    });
    this.users = [];
  },
  sendMessage(msg, onsuccess) {
    rs.rsJsonApiRequest(
      '/rsmsgs/sendChat',
      {},
      () => {
        // adding own message to log
        rs.events[15].handler(
          {
            mChatMessage: {
              chat_id: this.chatId(),
              msg,
              sendTime: new Date().getTime() / 1000,
              lobby_peer_gxs_id: this.currentLobby.gxs_id,
            },
          },
          rs.events[15]
        );
        onsuccess();
      },
      true,
      {},
      undefined,
      () =>
        '{"id":{"type": 3,"lobby_id":' +
        m.route.param('lobby') +
        '}, "msg":' +
        JSON.stringify(msg) +
        '}'
    );
  },
  selected(info, selName, defaultName) {
    const currid = (ChatLobbyModel.currentLobby.lobby_id || { xstr64: m.route.param('lobby') })
      .xstr64;
    return (info.lobby_id.xstr64 === currid ? selName : '') + defaultName;
  },
  switchToEvent(info) {
    return () => {
      ChatLobbyModel.currentLobby = info;
      m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 });
      ChatLobbyModel.loadLobby(info.lobby_id.xstr64); // update
    };
  },
  setupEvent(info) {
    return () => {
      m.route.set('/chat/:lobby/setup', { lobby: info.lobby_id.xstr64 });
      ChatLobbyModel.loadPublicLobby(info.lobby_id.xstr64); // update
    };
  },
};

// ************************* views ****************************

const Lobby = () => {
  let info = {};
  let tagname = '';
  let onclick = (e) => {};
  let lobbytagname = '';
  return {
    oninit: (v) => {
      info = v.attrs.info;
      tagname = v.attrs.tagname;
      onclick = v.attrs.onclick || ((e) => {});
      lobbytagname = v.attrs.lobbytagname || 'mainname';
    },
    view: (v) => {
      return m(
        ChatLobbyModel.selected(info, '.selected-lobby', tagname),
        {
          key: info.lobby_id.xstr64,

          onclick,
        },
        [
          m('h5', { class: lobbytagname }, info.lobby_name === '' ? '<unnamed>' : info.lobby_name),
          m('.topic', info.lobby_topic),
        ]
      );
    },
  };
};

const LobbyList = {
  view(vnode) {
    const tagname = vnode.attrs.tagname;
    const lobbytagname = vnode.attrs.lobbytagname;
    const onclick = vnode.attrs.onclick || (() => null);
    return [
      vnode.attrs.rooms.map((info) =>
        m(Lobby, {
          info,
          tagname,
          lobbytagname,
          onclick: onclick(info),
        })
      ),
    ];
  },
};

const SubscribedLeftLobbies = {
  view() {
    return [
      m('h5.lefttitle', 'subscribed:'),
      m(LobbyList, {
        rooms: sortLobbies(Object.values(ChatRoomsModel.subscribedRooms)),
        tagname: '.leftlobby.subscribed',
        lobbytagname: 'leftname',
        onclick: ChatLobbyModel.switchToEvent,
      }),
    ];
  },
};

const SubscribedLobbies = {
  view() {
    return m('.widget', [
      m('.widget__heading', m('h3', 'Subscribed chat rooms')),
      m('.widget__body', [
        m(LobbyList, {
          rooms: sortLobbies(Object.values(ChatRoomsModel.subscribedRooms)),
          tagname: '.lobby.subscribed',
          onclick: ChatLobbyModel.switchToEvent,
        }),
      ]),
    ]);
  },
};

const PublicLeftLobbies = {
  view() {
    return [
      m('h5.lefttitle', 'public:'),
      m(LobbyList, {
        rooms: Object.values(ChatRoomsModel.allRooms).filter(
          (info) => !ChatRoomsModel.subscribed(info)
        ),
        tagname: '.leftlobby.public',
        lobbytagname: 'leftname',
        onclick: ChatLobbyModel.setupEvent,
      }),
    ];
  },
};

const PublicLobbies = () => {
  return m('.widget', [
    m('.widget__heading', m('h3', 'Public chat rooms')),
    m('.widget__body', [
      m(LobbyList, {
        rooms: ChatRoomsModel.allRooms.filter((info) => !ChatRoomsModel.subscribed(info)),
        tagname: '.lobby.public',
        onclick: ChatLobbyModel.setupEvent,
      }),
    ]),
  ]);
};

const LobbyName = () => {
  return m(
    'h3.lobbyName',
    ChatLobbyModel.isSubscribed
      ? [m('span.chatusername', ChatLobbyModel.lobby_user), m('span.chatatchar', '@')]
      : [],
    m('span.chatlobbyname', ChatLobbyModel.currentLobby.lobby_name),
    m.route.param('subaction') !== 'setup'
      ? [
          m('i.fas.fa-cog.setupicon', {
            title: 'configure lobby',
            onclick: () =>
              m.route.set(
                '/chat/:lobby/:subaction',
                {
                  lobby: m.route.param('lobby'),
                  subaction: 'setup',
                },
                { replace: true }
              ),
          }),
        ]
      : [],
    ChatLobbyModel.isSubscribed
      ? [
          m('i.fas.fa-sign-out-alt.leaveicon', {
            title: 'leaving lobby',
            onclick: () =>
              ChatLobbyModel.unsubscribeChatLobby(m.route.param('lobby'), () => {
                m.route.set('/chat', null, { replace: true });
              }),
          }),
        ]
      : []
  );
};

// ***************************** Page Layouts ******************************

const Layout = () => {
  return {
    view: () => m('.node-panel', [m(SubscribedLobbies), PublicLobbies()]),
  };
};

const LayoutSingle = () => {
  return {
    oninit: () => ChatLobbyModel.loadLobby(m.route.param('lobby')),
    view: (vnode) =>
      m('.node-panel', [
        LobbyName(),
        m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
        m('.messages', ChatLobbyModel.messages),
        m('.rightbar', ChatLobbyModel.users),
        m(
          '.chatMessage',
          {},
          m('textarea.chatMsg', {
            placeholder: 'enter new message and press return to send',
            onkeydown: (e) => {
              if (e.code === 'Enter') {
                const msg = e.target.value;
                e.target.value = ' sending ... ';
                ChatLobbyModel.sendMessage(msg, () => (e.target.value = ''));
                return false;
              }
            },
          })
        ),
      ]),
  };
};

const LayoutSetup = () => {
  let ownIds = [];
  return {
    oninit: () => peopleUtil.ownIds((data) => (ownIds = data)),
    view: (vnode) =>
      m('.node-panel', [
        LobbyName(),
        m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
        m('.setup', [
          m('h5.selectidentity', 'Select identity to use'),
          ownIds.map((nick) =>
            m(
              '.identity' +
                (ChatLobbyModel.currentLobby.gxs_id === nick ? '.selectedidentity' : ''),
              {
                onclick: () => ChatLobbyModel.setupAction(m.route.param('lobby'), nick),
              },
              rs.userList.username(nick)
            )
          ),
        ]),
      ]),
  };
};

/*
    /rsMsgs/initiateDistantChatConnexion
	 * @param[in] to_pid RsGxsId to start the connection
	 * @param[in] from_pid owned RsGxsId who start the connection
	 * @param[out] pid distant chat id
	 * @param[out] error_code if the connection can't be stablished
	 * @param[in] notify notify remote that the connection is stablished
*/
const LayoutCreateDistant = () => {
  let ownIds = [];
  return {
    oninit: () => peopleUtil.ownIds((data) => (ownIds = data)),
    view: (vnode) =>
      m('.node-panel', [
        m('.createDistantChat', [
          'choose identitiy to chat with ',
          rs.userList.username(m.route.param('lobby')),
          ownIds.map((id) =>
            m(
              '.identity',
              {
                onclick: () =>
                  rs.rsJsonApiRequest(
                    '/rsMsgs/initiateDistantChatConnexion',
                    {
                      to_pid: m.route.param('lobby'),
                      from_pid: id,
                      notify: true,
                    },
                    (result) => {
                      console.info('initiateDistantChatConnexion', result);
                      m.route.set('/chat/:lobbyid', { lobbyid: result.pid });
                    }
                  ),
              },
              rs.userList.username(id)
            )
          ),
        ]),
      ]),
  };
};

module.exports = {
  oninit: () => {
    ChatRoomsModel.loadSubscribedRooms();
    ChatRoomsModel.loadPublicRooms();
  },
  view: (vnode) => {
    if (m.route.param('lobby') === undefined) {
      return m(Layout);
    } else if (m.route.param('subaction') === 'setup') {
      return m(LayoutSetup);
    } else if (m.route.param('subaction') === 'createdistantchat') {
      return m(LayoutCreateDistant);
    } else {
      return m(LayoutSingle);
    }
  },
};
 
}); 
require.register("config/config_files", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('config/config_util');

const SharedDirectories = () => {
  let directories = [];
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsFiles/getSharedDirectories', {}, (data) => (directories = data.dirs));
    },
    view: () =>
      m('.widget__body-box', [
        m('.widget__heading', m('h3', 'Shared Directories')),
        directories.map((dir) =>
          m('input[type=text].stretched', {
            value: dir.filename,
          })
        ),
      ]),
  };
};

const DownloadDirectory = () => {
  let dlDir = '';
  const setDir = () => {
    rs.rsJsonApiRequest('rsFiles/setDownloadDirectory', {
      path: dlDir,
    });
  };
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsFiles/getDownloadDirectory', {}, (data) => (dlDir = data.retval));
    },
    view: () =>
      m('.widget__body-box', [
        m('.widget__heading', m('h3', 'Downloads Directory')),
        m('input[type=text].stretched#dl-dir-input', {
          oninput: (e) => (dlDir = e.target.value),
          value: dlDir,
          onchange: setDir,
        }),
      ]),
  };
};

const PartialsDirectory = () => {
  let partialsDir = '';
  const setDir = () => {
    // const path = document.getElementById('partial-dir-input').value; // unused?

    rs.rsJsonApiRequest('rsFiles/setPartialsDirectory', {
      path: partialsDir,
    });
  };
  return {
    oninit: () =>
      rs.rsJsonApiRequest(
        '/rsFiles/getPartialsDirectory',
        {},
        (data) => (partialsDir = data.retval)
      ),
    view: () =>
      m('.widget__body-box', [
        m('.widget__heading', m('h3', 'Partials Directory')),
        m('input[type=text].stretched#partial-dir-input', {
          oninput: (e) => (partialsDir = e.target.value),
          value: partialsDir,
          onchange: setDir,
        }),
      ]),
  };
};

const TransferOptions = () => {
  let queueSize = undefined;
  let maxUploadSlots = undefined;
  let strategy = undefined;
  let diskLimit = undefined;
  let encryptionPolicy = undefined;
  let directDLPerm = undefined;
  const setMaxSimultaneousDownloads = () =>
    rs.rsJsonApiRequest('/rsFiles/setQueueSize', {
      s: parseInt(queueSize),
    });
  const setMaxUploadSlots = () =>
    rs.rsJsonApiRequest('/rsFiles/setMaxUploadSlotsPerFriend', {
      n: parseInt(maxUploadSlots),
    });
  const setChunkStrat = () =>
    rs.rsJsonApiRequest('/rsFiles/setDefaultChunkStrategy', {
      strategy: parseInt(strategy),
    });
  const setFreeLimit = () =>
    rs.rsJsonApiRequest('/rsFiles/setFreeDiskSpaceLimit', {
      minimumFreeMB: parseInt(diskLimit),
    });
  const setDefaultEncryption = () => {
    rs.rsJsonApiRequest('/rsFiles/setDefaultEncryptionPolicy', {
      policy: parseInt(encryptionPolicy),
    });
  };
  const setDirectDLPerm = () => {
    rs.rsJsonApiRequest('/rsFiles/setFilePermDirectDL', {
      perm: parseInt(directDLPerm),
    });
  };
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsFiles/getQueueSize').then((res) => (queueSize = res.body.retval));
      rs.rsJsonApiRequest('/rsFiles/defaultChunkStrategy', {}, (data) => (strategy = data.retval));
      rs.rsJsonApiRequest('/rsFiles/getMaxUploadSlotsPerFriend').then(
        (res) => (maxUploadSlots = res.body.retval)
      );
      rs.rsJsonApiRequest('/rsFiles/freeDiskSpaceLimit', {}, (data) => (diskLimit = data.retval));
      rs.rsJsonApiRequest('/rsFiles/defaultEncryptionPolicy').then(
        (res) => (encryptionPolicy = res.body.retval)
      );
      rs.rsJsonApiRequest('/rsFiles/filePermDirectDL').then(
        (res) => (directDLPerm = res.body.retval)
      );
    },
    view: () =>
      m('.widget__body-box', [
        m('.widget__heading', m('h3', 'Transfer options')),
        m('.grid-2col', [
          m('p', 'Maximum simultaneous downloads:'),
          m('input[type=number]', {
            value: queueSize,
            oninput: (e) => (queueSize = e.target.value),
            onchange: setMaxSimultaneousDownloads,
          }),
          m('p', 'Default chunk strategy:'),
          m(
            'select[name=strategy]',
            {
              value: strategy,
              oninput: (e) => (strategy = e.target.value),
              onchange: setChunkStrat,
            },
            ['Streaming', 'Random', 'Progressive'].map((val, i) =>
              m('option[value=' + i + ']', val)
            )
          ),
          m('p', 'Maximum uploads per friend:'),
          m('input[type=number]', {
            value: maxUploadSlots,
            oninput: (e) => (maxUploadSlots = e.target.value),
            onchange: setMaxUploadSlots,
          }),
          m('p', 'Safety disk space limit(MB):'),
          m('input[type=number]', {
            value: diskLimit,
            oninput: (e) => (diskLimit = e.target.value),
            onchange: setFreeLimit,
          }),
          m('p', 'End-to-end encryption:'),
          m(
            'select',
            {
              value: encryptionPolicy,
              oninput: (e) => (encryptionPolicy = e.target.value),
              onchange: setDefaultEncryption,
            },
            [
              m(
                'option',
                {
                  value: util.RS_FILE_CTRL_ENCRYPTION_POLICY_STRICT,
                },
                'Enforced'
              ),
              m(
                'option',
                {
                  value: util.RS_FILE_CTRL_ENCRYPTION_POLICY_PERMISSIVE,
                },
                'Accepted'
              ),
            ]
          ),
          m('p', 'Allow Direct Download:'),
          m(
            'select',
            {
              value: directDLPerm,
              oninput: (e) => (directDLPerm = e.target.value),
              onchange: setDirectDLPerm,
            },
            [
              m(
                'option',
                {
                  value: util.RS_FILE_PERM_DIRECT_DL_YES,
                },
                'Yes'
              ),
              m(
                'option',
                {
                  value: util.RS_FILE_PERM_DIRECT_DL_NO,
                },
                'No'
              ),
              m(
                'option',
                {
                  value: util.RS_FILE_PERM_DIRECT_DL_PER_USER,
                },
                'Per User'
              ),
            ]
          ),
        ]),
      ]),
  };
};

const Layout = () => {
  return {
    view: () =>
      m('.widget', [
        m('.widget__heading', m('h3', 'Files Configuration')),
        m('.widget__body.config-files', [
          m(SharedDirectories),
          m(DownloadDirectory),
          m(PartialsDirectory),
          m(TransferOptions),
        ]),
      ]),
  };
};

module.exports = Layout; 
}); 
require.register("config/config_mail", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');
const util = require('config/config_util');

const msgTagObj = {
  tagId: 100,
  tagName: '',
  tagColor: '',
};
let tagArr = [];

async function handleSubmit(tagId) {
  const modalContainer = document.getElementById('modal-container');
  msgTagObj.tagId = typeof tagId === 'number' ? tagId : util.getRandomId(tagArr);
  let tagNameAlreadyExists = false;
  tagArr.forEach((item) => {
    if (item.value.first === msgTagObj.tagName) tagNameAlreadyExists = true;
  });
  if (tagNameAlreadyExists) {
    alert('Tag Name Already Exists');
  } else {
    rs.rsJsonApiRequest('/rsMsgs/setMessageTagType', {
      tagId: msgTagObj.tagId,
      text: msgTagObj.tagName,
      rgb_color: parseInt(msgTagObj.tagColor.substring(1), 16),
    });
    modalContainer.style.display = 'none';
    rs.rsJsonApiRequest('/rsMsgs/getMessageTagTypes').then((res) => (tagArr = res.body.tags.types));
  }
}

const MessageTagForm = () => {
  return {
    view: (v) => {
      const isCreateForm = v.attrs.tagItem === undefined;
      return m(
        'form.mail-tags-form',
        {
          onsubmit: isCreateForm ? handleSubmit : () => handleSubmit(v.attrs.tagItem.key),
        },
        [
          m('h3', isCreateForm ? 'Create New Tag Type' : 'Edit Tag Type'),
          m('hr'),
          m('.input-field', [
            m('label[for=tagName]', 'Enter Tag Name'),
            m('input[type=text][id=tagName][placeholder="enter tag name"]', {
              value: msgTagObj.tagName,
              oninput: (e) => (msgTagObj.tagName = e.target.value),
            }),
          ]),
          m('.input-field', [
            m('label[for=tagColor]', 'Choose Tag Color'),
            m('input[type=color][id=tagColor]', {
              value: msgTagObj.tagColor,
              oninput: (e) => (msgTagObj.tagColor = e.target.value),
            }),
          ]),
          // v.attrs.tagItem !== undefined && m('p', v.attrs.tagItem.value.first),
          m('button[type=submit]', 'Submit'),
        ]
      );
    },
  };
};

const Mail = () => {
  let distantMessagingPermissionFlag = 0;
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsMsgs/getMessageTagTypes').then(
        (res) => (tagArr = res.body.tags.types)
      );
      rs.rsJsonApiRequest('/rsMsgs/getDistantMessagingPermissionFlags').then(
        (res) => (distantMessagingPermissionFlag = res.body.retval)
      );
    },
    view: () =>
      m('.widget.mail', [
        m('.widget__heading', m('h3', 'Mail Configuration')),
        m('.widget__body', [
          m('.permission-flag', [
            m('p', 'Accept encrypted distant messages from: '),
            m(
              'select',
              {
                value: distantMessagingPermissionFlag,
                oninput: (e) => (distantMessagingPermissionFlag = e.target.value),
                onchange: () => {
                  rs.rsJsonApiRequest('/rsMsgs/setDistantMessagingPermissionFlags', {
                    flags: parseInt(distantMessagingPermissionFlag),
                  });
                },
              },
              [
                m(
                  'option',
                  {
                    value: util.RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_NONE,
                  },
                  'Everybody'
                ),
                m(
                  'option',
                  {
                    value: util.RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_NON_CONTACTS,
                  },
                  'Contacts'
                ),
                m(
                  'option',
                  {
                    value: util.RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_EVERYBODY,
                  },
                  'Nobody'
                ),
              ]
            ),
          ]),
          m('.widget__heading', [
            m('h3', 'Mail Tags'),
            m(
              'button',
              {
                onclick: () => {
                  // set form fields to default values
                  msgTagObj.tagName = '';
                  msgTagObj.tagColor = '';
                  widget.popupMessage(m(MessageTagForm));
                },
              },
              'Create New Tag'
            ),
          ]),
          m(
            '.mail-tags',
            tagArr.length === 0
              ? m('h4', 'No Message Tags')
              : m(
                  '.mail-tags__container',
                  tagArr.map((tag) =>
                    m('.tag-item', { key: tag.key }, [
                      m('.tag-item__color', {
                        style: {
                          backgroundColor: `#${tag.value.second.toString(16).padStart(6, '0')}`,
                        },
                      }),
                      m('p.tag-item__name', tag.value.first),
                      m('.tag-item__modify', [
                        m(
                          'button',
                          {
                            onclick: () => {
                              msgTagObj.tagName = tag.value.first;
                              msgTagObj.tagColor = `#${tag.value.second
                                .toString(16)
                                .padStart(6, '0')}`;
                              widget.popupMessage(m(MessageTagForm, { tagItem: tag }));
                            },
                          },
                          m('i.fas.fa-pen')
                        ),
                        m(
                          'button.red',
                          {
                            onclick: () => {
                              rs.rsJsonApiRequest('/rsMsgs/removeMessageTagType', {
                                tagId: tag.key,
                              }).then((res) => {
                                if (res.body.retval)
                                  tagArr = tagArr.filter((item) => item.key !== tag.key);
                              });
                            },
                          },
                          m('i.fas.fa-trash')
                        ),
                      ]),
                    ])
                  )
                )
          ),
        ]),
      ]),
  };
};

module.exports = Mail;
 
}); 
require.register("config/config_network", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const util = require('config/config_util'); // for future use

const SetNwMode = () => {
  const networkModes = [
    'Public: DHT & Discovery',
    'Private: Discovery only',
    'Inverted: DHT only',
    'Dark Net: None',
  ];

  let vsDisc = 0;
  let vsDht = 0;
  let selectedMode;
  let sslId = '';
  let details = {};

  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsAccounts/getCurrentAccountId').then((res) => {
        if (res.body.retval) {
          sslId = res.body.id;
          rs.rsJsonApiRequest('/rsPeers/getPeerDetails', {
            sslId,
          }).then((res) => {
            if (res.body.retval) {
              details = res.body.det;
              if (
                details.vs_dht === util.RS_VS_DHT_FULL &&
                details.vs_disc === util.RS_VS_DISC_FULL
              ) {
                selectedMode = networkModes[0];
              } else if (
                details.vs_dht === util.RS_VS_DHT_OFF &&
                details.vs_disc === util.RS_VS_DISC_FULL
              ) {
                selectedMode = networkModes[1];
              } else if (
                details.vs_dht === util.RS_VS_DHT_FULL &&
                details.vs_disc === util.RS_VS_DISC_OFF
              ) {
                selectedMode = networkModes[2];
              } else if (
                details.vs_dht === util.RS_VS_DHT_OFF &&
                details.vs_disc === util.RS_VS_DISC_OFF
              ) {
                selectedMode = networkModes[3];
              }
            }
          });
        }
      });
    },
    view: () => {
      return [
        m('p', 'Network mode:'),
        m(
          'select',
          {
            value: selectedMode,
            onchange: (e) => {
              selectedMode = networkModes[e.target.selectedIndex];
              if (e.target.selectedIndex === 0) {
                // Public: DHT & Discovery
                vsDisc = util.RS_VS_DISC_FULL;
                vsDht = util.RS_VS_DHT_FULL;
              } else if (e.target.selectedIndex === 1) {
                // Private: Discovery only
                vsDisc = util.RS_VS_DISC_FULL;
                vsDht = util.RS_VS_DHT_OFF;
              } else if (e.target.selectedIndex === 2) {
                // Inverted: DHT only
                vsDisc = util.RS_VS_DISC_OFF;
                vsDht = util.RS_VS_DHT_FULL;
              } else if (e.target.selectedIndex === 3) {
                // Dark Net: None
                vsDisc = util.RS_VS_DISC_OFF;
                vsDht = util.RS_VS_DHT_OFF;
              }
              if (
                details &&
                (vsDht !== details.vs_dht || vsDisc !== details.vs_disc) &&
                sslId !== undefined
              ) {
                rs.rsJsonApiRequest('/rsPeers/setVisState', {
                  sslId,
                  vsDisc,
                  vsDht,
                });
              }
            },
          },
          [networkModes.map((o) => m('option', { value: o }, o))]
        ),
      ];
    },
  };
};

const SetNAT = () => {
  let sslId;
  let netMode;

  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsAccounts/getCurrentAccountId').then((res) => {
        if (res.body.retval) {
          sslId = res.body.id;
          rs.rsJsonApiRequest('/rsPeers/getPeerDetails', {
            sslId,
          }).then((res) => {
            if (res.body.retval) {
              netMode = res.body.det.netMode;
            }
          });
        }
      });
    },
    view: () => [
      m('p', 'NAT:'),
      m(
        'select',
        {
          value: netMode,
          oninput: (e) => (netMode = e.target.value),
          onchange: () => {
            rs.rsJsonApiRequest('/rsPeers/setNetworkMode', {
              sslId,
              netMode,
            });
          },
        },
        [
          m('option', { value: util.RS_NETMODE_UPNP }, 'Automatic (UPnP)'),
          m('option', { value: util.RS_NETMODE_UDP }, 'FireWalled'),
          m('option', { value: util.RS_NETMODE_EXT }, 'Manually Forwarded Port'),
        ]
      ),
    ],
  };
};

const SetLimits = () => {
  let dlim = undefined;
  let ulim = undefined;
  const setMaxRates = () =>
    rs.rsJsonApiRequest('/rsConfig/SetMaxDataRates', {
      downKb: dlim,
      upKb: ulim,
    });
  return {
    oninit: () =>
      rs.rsJsonApiRequest('/rsConfig/GetMaxDataRates', {}, (data) => {
        dlim = data.inKb;
        ulim = data.outKb;
      }),
    view: () => [
      m(
        'p',
        util.tooltip(
          'The download limit covers the whole application. ' +
            'However, in some situations, such as when transfering ' +
            'many files at once, the estimated bandwidth becomes ' +
            'unreliable and the total value reported by Retroshare ' +
            'might exceed that limit.'
        ),
        'Download limit(KB/s):'
      ),
      m('input[type=number][name=download]', {
        value: dlim,
        oninput: (e) => (dlim = Number(e.target.value)),
        onchange: setMaxRates,
      }),
      m(
        'p',
        util.tooltip(
          'The upload limit covers the entire software. ' +
            'Too small an upload limit may eventually block ' +
            'low priority services(forums, channels). ' +
            'A minimum recommended value is 50KB/s.'
        ),
        'Upload limit(KB/s):'
      ),
      m('input[type=number][name=upload]', {
        value: ulim,
        oninput: (e) => (ulim = Number(e.target.value)),
        onchange: setMaxRates,
      }),
    ],
  };
};

const SetOpMode = () => {
  let opmode = undefined;
  const setmode = () =>
    rs.rsJsonApiRequest('/rsconfig/SetOperatingMode', {
      opMode: Number(opmode),
    });
  return {
    oninit: () =>
      rs.rsJsonApiRequest('/rsConfig/getOperatingMode', {}, (data) => (opmode = data.retval)),
    view: () => [
      m(
        'p',
        'Operating mode:',
        util.tooltip(
          `No Anon D/L: Switches off file forwarding\n
          Gaming Mode: 25% standard traffic and TODO: Reduced popups\n
          Low traffic: 10% standard traffic and TODO: pause all file transfers\n`
        )
      ),
      m(
        'select',
        {
          oninput: (e) => (opmode = e.target.value),
          value: opmode,
          onchange: setmode,
        },
        ['Normal', 'No Anon D/L', 'Gaming', 'Low traffic'].map((val, i) =>
          m(`option[value=${i + 1}]`, val)
        )
      ),
    ],
  };
};

const displayLocalIPAddress = () => {
  return {
    view: ({ attrs: { details } }) =>
      details && [m('p', 'Local Address: '), m('p', details.localAddr)],
  };
};
const displayExternalIPAddress = () => {
  return {
    view: ({ attrs: { details } }) =>
      details && [m('p', 'External Address: '), m('p', details.extAddr)],
  };
};

const displayIPAddresses = () => {
  return {
    view: ({ attrs: { details } }) =>
      details && [
        m('p', 'External Address: '),
        m(
          'ul.external-address',
          details.ipAddressList.map((ip) => m('li', ip))
        ),
      ],
  };
};

const SetDynamicDNS = () => {
  let addr = '';
  let sslId = '';
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsAccounts/getCurrentAccountId').then((res) => {
        if (res.body.retval) {
          sslId = res.body.id;
          rs.rsJsonApiRequest('/rsPeers/getPeerDetails', {
            sslId,
          }).then((res) => {
            if (res.body.retval) {
              addr = res.body.det.dyndns;
            }
          });
        }
      });
    },
    view: () => [
      m('p', 'Set Dynamic DNS:'),
      m('input[type=text]', {
        value: addr,
        oninput: (e) => (addr = e.target.value),
        onchange: () => {
          rs.rsJsonApiRequest('/rsPeers/setDynDNS', {
            sslId,
            addr,
          });
        },
      }),
    ],
  };
};

const SetSocksProxy = () => {
  const socksProxyObj = {
    tor: {},
    i2p: {},
  };
  const fetchOutgoing = () => {
    Object.keys(socksProxyObj).forEach((proxyItem) => {
      fetch(`http://${socksProxyObj[proxyItem].addr}:${socksProxyObj[proxyItem].port}`)
        .then(() => {
          socksProxyObj[proxyItem].outgoing = true;
          m.redraw();
        })
        .catch(() => {
          socksProxyObj[proxyItem].outgoing = false;
        });
    });
  };
  const handleProxyChange = (proxyItem) => {
    rs.rsJsonApiRequest('/rsPeers/setProxyServer', {
      type: util[`RS_HIDDEN_TYPE_${proxyItem.toUpperCase()}`],
      addr: socksProxyObj[proxyItem].addr,
      port: socksProxyObj[proxyItem].port,
    }).then(fetchOutgoing);
  };
  return {
    oninit: () => {
      Object.keys(socksProxyObj).forEach((proxyItem) => {
        rs.rsJsonApiRequest('/rsPeers/getProxyServer', {
          type: util[`RS_HIDDEN_TYPE_${proxyItem.toUpperCase()}`],
        })
          .then((res) => {
            if (res.body.retval) {
              socksProxyObj[proxyItem] = res.body;
            }
          })
          .then(fetchOutgoing);
      });
    },
    view: () =>
      m('.proxy-server', [
        m(
          'p',
          'Configure your TOR and I2P SOCKS proxy here. It will allow you to also connect to hidden nodes.'
        ),
        Object.keys(socksProxyObj).map((proxyItem) => {
          return m(`.proxy-server__${proxyItem}`, [
            m('h6', `${proxyItem.toUpperCase()} Socks Proxy: `),
            m('input[type=text]', {
              value: socksProxyObj[proxyItem].addr,
              oninput: (e) => (socksProxyObj[proxyItem].addr = e.target.value),
              onchange: () => handleProxyChange(proxyItem),
            }),
            m('input[type=number]', {
              value: socksProxyObj[proxyItem].port,
              oninput: (e) => (socksProxyObj[proxyItem].port = parseInt(e.target.value)),
              onchange: () => handleProxyChange(proxyItem),
            }),
            socksProxyObj[proxyItem].outgoing !== undefined &&
              m('.proxy-outgoing', [
                m('.proxy-outgoing__status', {
                  style: {
                    backgroundColor: socksProxyObj[proxyItem].outgoing ? '#00dd44' : '#808080',
                  },
                }),
                m(
                  'p',
                  `${proxyItem.toUpperCase()} outgoing ${
                    socksProxyObj[proxyItem].outgoing ? 'on' : 'off'
                  }`
                ),
              ]),
          ]);
        }),
      ]),
  };
};

const Component = () => {
  let details;
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsAccounts/getCurrentAccountId').then((res) => {
        if (res.body.retval) {
          rs.rsJsonApiRequest('/rsPeers/getPeerDetails', {
            sslId: res.body.id,
          }).then((res) => {
            if (res.body.retval) {
              details = res.body.det;
            }
          });
        }
      });
    },
    view: () =>
      m('.widget', [
        m('.widget__heading', m('h3', 'Network Configuration')),
        m('.widget__body', [
          m('.grid-2col', [
            m(SetNwMode),
            m(SetNAT),
            m(displayLocalIPAddress, { details }),
            m(displayExternalIPAddress, { details }),
            m(SetDynamicDNS),
            m(SetLimits),
            m(SetOpMode),
            m(displayIPAddresses, { details }),
          ]),
          m('.widget__heading', m('h3', 'Hidden Service Configuration')),
          m('.widget__body', [m('.grid-2col', [m(SetSocksProxy)])]),
        ]),
      ]),
  };
};

module.exports = Component;
 
}); 
require.register("config/config_node", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const Node = () => {
  const nodeInfo = {
    setData(data) {
      Object.assign(nodeInfo, data.status);
    },
  };
  return {
    oninit() {
      rs.rsJsonApiRequest('/rsConfig/getConfigNetStatus', {}, nodeInfo.setData);
    },
    view() {
      return [
        m('.widget', [
          m('.widget__heading', m('h3', 'Public Information')),
          m('.widget__body', [
            m('ul', [
              m('li', 'Name: ' + nodeInfo.ownName),
              m('li', 'Location ID: ' + nodeInfo.ownId),
              m('li', 'Firewall: ' + nodeInfo.firewalled),
              m('li', 'Port Forwarding: ' + nodeInfo.forwardPort),
              m('li', 'DHT: ' + nodeInfo.DHTActive),
              m('li', 'uPnP: ' + nodeInfo.uPnPActive),
              m('li', 'Local Address: ' + nodeInfo.localAddr + '  Port: ' + nodeInfo.localPort),
            ]),
          ]),
        ]),
      ];
    },
  };
};

module.exports = Node;
 
}); 
require.register("config/config_people", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const Reputation = () => {
  let addFriendIdAsContacts = undefined;
  let usePositiveDefault = undefined;
  let deleteBannedAfter = undefined;
  let negativeThreshold = undefined;
  let positiveThreshold = undefined;

  return {
    oninit: (vnode) => {
      rs.rsJsonApiRequest(
        '/rsIdentity/autoAddFriendIdsAsContact',
        {},
        (data) => (addFriendIdAsContacts = data.retval)
      );
      rs.rsJsonApiRequest(
        '/rsreputations/autoPositiveOpinionForContacts',
        {},
        (data) => (usePositiveDefault = data.retval)
      );
      rs.rsJsonApiRequest(
        '/rsIdentity/deleteBannedNodesThreshold',
        {},
        (data) => (deleteBannedAfter = data.retval)
      );
      rs.rsJsonApiRequest(
        '/rsreputations/thresholdForRemotelyPositiveReputation',
        {},
        (data) => (positiveThreshold = data.retval)
      );
      rs.rsJsonApiRequest(
        '/rsreputations/thresholdForRemotelyNegativeReputation',
        {},
        (data) => (negativeThreshold = data.retval)
      );
    },
    view: (vnode) =>
      m('.widget', [
        m('.widget__heading', m('h3', 'Reputation')),
        m('.widget__body', [
          m('.grid-2col', [
            m('p', 'Use "positive" as the default opinion for contacts(instead of neutral):'),
            m('input[type=checkbox]', {
              checked: usePositiveDefault,
              oninput: (e) => {
                usePositiveDefault = e.target.checked;
                rs.rsJsonApiRequest(
                  '/rsreputations/setAutoPositiveOpinionForContacts',
                  {
                    b: usePositiveDefault,
                  },
                  () => {}
                );
              },
            }),
            m('p', 'Automatically add identities owned by friend nodes to my contacts:'),
            m('input[type=checkbox]', {
              checked: addFriendIdAsContacts,
              oninput: (e) => {
                addFriendIdAsContacts = e.target.checked;
                rs.rsJsonApiRequest(
                  '/rsIdentity/setAutoAddFriendIdsAsContact',
                  {
                    enabled: addFriendIdAsContacts,
                  },
                  () => {}
                );
              },
            }),
            m('p', 'Difference in votes (+/-) to rate an ID positively:'),
            m('input[type=number]', {
              oninput: (e) => (positiveThreshold = e.target.value),
              value: positiveThreshold,
              onchange: () =>
                rs.rsJsonApiRequest(
                  '/rsreputations/setThresholdForRemotelyPositiveReputation',
                  {
                    thresh: positiveThreshold,
                  },
                  () => {}
                ),
            }),
            m('p', 'Difference in votes (+/-) to rate an ID negatively:'),
            m('input[type=number]', {
              oninput: (e) => (negativeThreshold = e.target.value),
              value: negativeThreshold,
              onchange: () =>
                rs.rsJsonApiRequest(
                  '/rsreputations/setThresholdForRemotelyNegativeReputation',
                  {
                    thresh: negativeThreshold,
                  },
                  () => {}
                ),
            }),
            m('p', 'Delete banned identities after(in days, 0 means indefinitely):'),
            m('input[type=number]', {
              oninput: (e) => (deleteBannedAfter = e.target.value),
              value: deleteBannedAfter,
              onchange: () =>
                rs.rsJsonApiRequest(
                  '/rsIdentity/setDeleteBannedNodesThreshold',
                  {
                    days: deleteBannedAfter,
                  },
                  () => {}
                ),
            }),
          ]),
        ]),
      ]),
  };
};

const Layout = () => {
  return {
    view: (vnode) => [m(Reputation)],
  };
};

module.exports = Layout;
 
}); 
require.register("config/config_resolver", function(exports, require, module) { 
const m = require('mithril');
const widget = require('widgets');

const sections = {
  network: require('config/config_network'),
  node: require('config/config_node'),
  services: require('config/config_services'),
  files: require('config/config_files'),
  people: require('config/config_people'),
  mail: require('config/config_mail'),
};

const Layout = {
  view: (vnode) => [
    m(widget.Sidebar, {
      tabs: Object.keys(sections),
      baseRoute: '/config/',
    }),
    m('.node-panel', vnode.children),
  ],
};

module.exports = {
  view: (vnode) => {
    const tab = vnode.attrs.tab;
    return m(Layout, m(sections[tab]));
  },
};
 
}); 
require.register("config/config_services", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const servicesInfo = {
  list: [],

  setData(data) {
    servicesInfo.list = data.info.mServiceList;
  },
};

const Service = () => {
  let defaultAllowed = undefined;
  return {
    oninit: (v) =>
      rs.rsJsonApiRequest(
        '/rsServiceControl/getServicePermissions',
        {
          serviceId: v.attrs.data.key,
        },
        (retval) => (defaultAllowed = retval.permissions.mDefaultAllowed)
      ),
    view: (v) =>
      m(
        'tr',
        {
          key: v.attrs.data.key,
        },
        [
          m('td', v.attrs.data.value.mServiceName),
          m('td', v.attrs.data.value.mServiceType),
          m('td', v.attrs.data.value.mVersionMajor + '.' + v.attrs.data.value.mVersionMinor),
          m(
            'td',
            m('input[type=checkbox]', {
              checked: defaultAllowed,
              oninput: (e) => {
                defaultAllowed = e.target.checked;
                rs.rsJsonApiRequest('/rsServiceControl/updateServicePermissions', {
                  serviceId: v.attrs.data.key,
                  permissions: {
                    mDefaultAllowed: defaultAllowed,
                  },
                });
              },
            })
          ),
        ]
      ),
  };
};

const MyServices = {
  oninit() {
    rs.rsJsonApiRequest('/rsServiceControl/getOwnServices', {}, servicesInfo.setData);
  },
  view() {
    return m('.widget', [
      m('.widget__heading', m('h3', 'My Services')),
      m('.widget__body', [
        m('table', [
          m('tr', [
            m('th', 'Name'),
            m('th', 'ID'),
            m('th', 'Version'),
            m('th', 'Allow by default'),
          ]),
          servicesInfo.list.map((data) =>
            m(Service, {
              data,
            })
          ),
        ]),
      ]),
    ]);
  },
};

module.exports = {
  view: () => {
    return m(MyServices);
  },
};
 
}); 
require.register("config/config_util", function(exports, require, module) { 
const m = require('mithril');

/* Visibility parameter for discovery */
const RS_VS_DISC_OFF = 0x0000;
const RS_VS_DISC_MINIMAL = 0x0001;
const RS_VS_DISC_FULL = 0x0002;

const RS_VS_DHT_OFF = 0x0000;
const RS_VS_DHT_PASSIVE = 0x0001;
const RS_VS_DHT_FULL = 0x0002;

const MAX_TAG_ID_VAL = 1000000;
const MIN_TAG_ID_VAL = 100;

// Distant Messaging Permission Flags to define who we accept to talk to.
// Each flag *removes* some people.
const RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_NONE = 0;
const RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_NON_CONTACTS = 1;
const RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_EVERYBODY = 2;

// Hidden Service Configuration Type
const RS_HIDDEN_TYPE_NONE = 0;
const RS_HIDDEN_TYPE_UNKNOWN = 1;
const RS_HIDDEN_TYPE_TOR = 2;
const RS_HIDDEN_TYPE_I2P = 4;

// NAT Net Mode
const RS_NETMODE_UDP = 1;
const RS_NETMODE_UPNP = 2;
const RS_NETMODE_EXT = 3;

// Default Encryption Policy
const RS_FILE_CTRL_ENCRYPTION_POLICY_STRICT = 1;
const RS_FILE_CTRL_ENCRYPTION_POLICY_PERMISSIVE = 2;

// Direct Download Permission
const RS_FILE_PERM_DIRECT_DL_YES = 1;
const RS_FILE_PERM_DIRECT_DL_NO = 2;
const RS_FILE_PERM_DIRECT_DL_PER_USER = 3;

function getRandomId(tagArr) {
  const random = Math.floor(Math.random() * (MAX_TAG_ID_VAL - MIN_TAG_ID_VAL) + MIN_TAG_ID_VAL);
  tagArr.forEach((tag) => {
    if (tag.key === random) {
      return getRandomId(tagArr);
    }
  });
  return random;
}

function tooltip(text) {
  return m('.tooltip', [m('i.fas.fa-info-circle'), m('.tooltiptext', text)]);
}

module.exports = {
  getRandomId,
  tooltip,
  RS_VS_DHT_FULL,
  RS_VS_DHT_OFF,
  RS_VS_DISC_FULL,
  RS_VS_DHT_PASSIVE,
  RS_VS_DISC_OFF,
  RS_VS_DISC_MINIMAL,
  RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_NONE,
  RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_NON_CONTACTS,
  RS_DISTANT_MESSAGING_PERMISSION_FLAG_FILTER_EVERYBODY,
  RS_HIDDEN_TYPE_NONE,
  RS_HIDDEN_TYPE_UNKNOWN,
  RS_HIDDEN_TYPE_TOR,
  RS_HIDDEN_TYPE_I2P,
  RS_NETMODE_UDP,
  RS_NETMODE_UPNP,
  RS_NETMODE_EXT,
  RS_FILE_CTRL_ENCRYPTION_POLICY_STRICT,
  RS_FILE_CTRL_ENCRYPTION_POLICY_PERMISSIVE,
  RS_FILE_PERM_DIRECT_DL_YES,
  RS_FILE_PERM_DIRECT_DL_NO,
  RS_FILE_PERM_DIRECT_DL_PER_USER,
};
 
}); 
require.register("files/files_downloads", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('files/files_util');
const widget = require('widgets');

const Downloads = {
  strategies: {},
  statusMap: {},
  hashes: [],
  chunksMap: {},

  loadStrategy() {
    rs.rsJsonApiRequest('/rsFiles/FileDownloads', {}, (d) =>
      d.hashs.map((hash) => {
        rs.rsJsonApiRequest('/rsFiles/getChunkStrategy', { hash }).then((res) => {
          if (res.body.retval) Downloads.strategies[hash] = res.body.s;
        });
      })
    );
  },

  async loadHashes() {
    await rs
      .rsJsonApiRequest('/rsFiles/FileDownloads', {}, (d) => (Downloads.hashes = d.hashs))
      .then(() => {
        Downloads.hashes.forEach((hash) => {
          rs.rsJsonApiRequest('/rsFiles/FileDownloadChunksDetails', {
            hash,
          }).then((res) => (this.chunksMap[hash] = res.body.info));
        });
      });
  },

  async loadStatus() {
    await Downloads.loadHashes();
    const fileKeys = Object.keys(Downloads.statusMap);
    if (Downloads.hashes !== undefined && Downloads.hashes.length !== fileKeys.length) {
      if (Downloads.hashes.length > fileKeys.length) {
        // New file added
        const newHashes = util.compareArrays(Downloads.hashes, fileKeys);
        for (const hash of newHashes) {
          Downloads.updateFileDetail(hash, true);
        }
      } else {
        // Existing file removed
        const oldHashes = util.compareArrays(fileKeys, Downloads.hashes);
        for (const hash of oldHashes) {
          delete Downloads.statusMap[hash];
        }
      }
    }
    for (const hash in Downloads.statusMap) {
      Downloads.updateFileDetail(hash);
    }
  },
  resetSearch() {
    for (const hash in Downloads.statusMap) {
      Downloads.statusMap[hash].isSearched = true;
    }
  },
  updateFileDetail(hash, isNew = false) {
    rs.rsJsonApiRequest(
      '/rsFiles/FileDetails',
      {
        hash,
        hintflags: 16, // RS_FILE_HINTS_DOWNLOAD
      },
      (fileStat) => {
        if (!fileStat.retval) {
          console.error('Error: Unknown hash in Downloads: ', hash);
          return;
        }
        fileStat.info.isSearched = isNew ? true : Downloads.statusMap[hash].isSearched;
        Downloads.statusMap[hash] = fileStat.info;
      }
    );
  },
};

function InvalidFileMessage() {
  widget.popupMessage([
    m('i.fas.fa-file-medical'),
    m('h3', 'Add new file'),
    m('hr'),
    m('p', 'Error: could not add file'),
  ]);
}

function addFile(url) {
  // valid url format: retroshare://file?name=...&size=...&hash=...
  if (!url.startsWith('retroshare://')) {
    InvalidFileMessage();
    return;
  }
  const details = m.parseQueryString(url.split('?')[1]);
  if (
    !Object.prototype.hasOwnProperty.call(details, 'name') ||
    !Object.prototype.hasOwnProperty.call(details, 'size') ||
    !Object.prototype.hasOwnProperty.call(details, 'hash')
  ) {
    InvalidFileMessage();
    return;
  }
  rs.rsJsonApiRequest(
    '/rsFiles/FileRequest',
    {
      fileName: details.name,
      hash: details.hash,
      flags: util.RS_FILE_REQ_ANONYMOUS_ROUTING,
      size: {
        xstr64: details.size,
      },
    },
    (status) => {
      widget.popupMessage([
        m('i.fas.fa-file-medical'),
        m('h3', 'Add new file'),
        m('hr'),
        m('p', 'Successfully added file!'),
      ]);
    }
  );
}

const NewFileDialog = () => {
  let url = '';
  return {
    view: () => [
      m('i.fas.fa-file-medical'),
      m('h3', 'Add new file'),
      m('hr'),
      m('p', 'Enter the file link:'),
      m('input[type=text][name=fileurl]', {
        onchange: (e) => (url = e.target.value),
      }),
      m('button', { onclick: () => addFile(url) }, 'Add'),
    ],
  };
};

const Component = () => {
  function clearFileCompleted() {
    rs.rsJsonApiRequest('/rsFiles/FileClearCompleted');
  }
  return {
    oninit: () => {
      Downloads.loadStrategy();
      rs.setBackgroundTask(Downloads.loadStatus, 1000, () => m.route.get() === '/files/files');
      Downloads.resetSearch();
    },
    view: () => [
      m('.widget__body-heading', [
        m('h3', `Downloads (${Downloads.hashes ? Downloads.hashes.length : 0} files)`),
        m('.action', [
          m('button', { onclick: () => widget.popupMessage(m(NewFileDialog)) }, 'Add new file'),
          m('button', { onclick: clearFileCompleted }, 'Clear completed'),
        ]),
      ]),
      m('.widget__body-content', [
        Downloads.statusMap &&
          Object.keys(Downloads.statusMap).map((hash) =>
            m(util.File, {
              info: Downloads.statusMap[hash],
              strategy: Downloads.strategies[hash],
              direction: 'down',
              transferred: Downloads.statusMap[hash].transfered.xint64,
              chunksInfo: Downloads.chunksMap[hash],
            })
          ),
      ]),
    ],
  };
};

module.exports = {
  Component,
  Downloads,
  list: Downloads.statusMap,
};
 
}); 
require.register("files/files_manager", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');
const futil = require('files/files_util');
const cutil = require('config/config_util');

const shareManagerInfo = `
  This is a list of shared folders. You can add and remove folders using the buttons at the bottom.
  e.g- You can click on Edit button and then modify any field. When you add a new folder, initially
  all files in that folder are shared. You can separately share flags for each shared directory.
`;

const accessTooltipText = [
  'Manage Control Access for Directories, The three options are for the following purpose.',
  m('i.fas.fa-search'),
  ' Directory can be searched anonymously, ',
  m('i.fas.fa-download'),
  ' Directory can be accessed anonymously, ',
  m('i.fas.fa-eye'),
  ' Directory can be browsed by designated friends',
];

const addNewDirInfo = `For Security reasons, Browsers don't allow to read directories so Please
  copy and paste the absolute path of the directory which you want to share.
`;

let sharedDirArr = [];
let isEditDisabled = true;

function loadSharedDirectories() {
  rs.rsJsonApiRequest('/rsFiles/getSharedDirectories').then((res) => {
    if (res.body.retval) sharedDirArr = res.body.dirs;
  });
}

// Update Shared Directories when there is a corresponding event
rs.events[rs.RsEventsType.SHARED_DIRECTORIES] = {
  handler: (event) => {
    console.log('Shared Directories Event: ', event);
    loadSharedDirectories();
  },
};

const AddSharedDirForm = () => {
  let newDirPath = '';

  function addNewSharedDirectory() {
    // check if newDirPath already exists
    const sharedDirArrExists = sharedDirArr.find((item) => item.filename === newDirPath);
    if (sharedDirArrExists) {
      alert('The path you entered already exists.');
      return;
    }
    const newSharedDir = {
      dir: {
        filename: newDirPath,
        virtualname: '',
        shareflags: futil.DIR_FLAGS_ANONYMOUS_SEARCH | futil.DIR_FLAGS_ANONYMOUS_DOWNLOAD,
        parent_groups: [],
      },
    };
    rs.rsJsonApiRequest('/rsFiles/addSharedDirectory', { ...newSharedDir }).then((res) => {
      if (res.body.retval) {
        loadSharedDirectories();
      }
      widget.popupMessage(
        m('.widget', [
          m('.widget__heading', m('h3', 'Add Shared Directory')),
          m(
            '.widget__body',
            m(
              'p',
              res.body.retval
                ? 'Successfully Added Directory to Shared List'
                : 'Error in Adding Directory to Shared List'
            )
          ),
        ])
      );
    });
  }

  return {
    view: () =>
      m('.widget', [
        m('.widget__heading', m('h3', 'Add New Directory')),
        m('form.widget__body.share-manager__form', { onsubmit: addNewSharedDirectory }, [
          m('blockquote.info', addNewDirInfo),
          m('.share-manager__form_input', [
            m('label', 'Enter absolute directory path :'),
            m('input[type=text]', {
              value: newDirPath,
              oninput: (e) => (newDirPath = e.target.value),
            }),
          ]),
          m('button[type=submit]', 'Add Directory'),
        ]),
      ]),
  };
};

const ManageVisibility = () => {
  function handleSubmit() {
    m.redraw();
    const mContainer = document.getElementById('modal-container');
    mContainer.style.display = 'none';
  }
  return {
    view: (v) => {
      const { parentGroups } = v.attrs;
      return m('.widget', [
        m('.widget__heading', m('h3', 'Manage Visibility')),
        m('form.widget__body', { onsubmit: handleSubmit }, [
          Object.keys(futil.RsNodeGroupId).map((groupId) =>
            m('div.manage-visibility', [
              m(`label[for=${futil.RsNodeGroupId[groupId]}]`, futil.RsNodeGroupId[groupId]),
              m(`input[type=checkbox][id=${futil.RsNodeGroupId[groupId]}]`, {
                // if parentGroups is empty it means All friends nodes have Visibility
                checked: parentGroups.includes(groupId),
                onclick: () => {
                  if (parentGroups.includes(groupId)) {
                    const groupItemIndex = parentGroups.indexOf(groupId);
                    parentGroups.splice(groupItemIndex, 1);
                  } else {
                    parentGroups.push(groupId);
                  }
                },
              }),
            ])
          ),
          m('button[type=submit]', 'OK'),
        ]),
      ]);
    },
  };
};

const ShareDirTable = () => {
  return {
    oninit: futil.loadRsNodeGroupId,
    view: () => {
      return m('table.share-manager__table', [
        m(
          'thead.share-manager__table_heading',
          m('tr', [
            m('td', 'Shared Directories'),
            m('td', 'Visible Name'),
            m('td', 'Access', cutil.tooltip(accessTooltipText)),
            m('td', 'Visibility'),
          ])
        ),
        m(
          'tbody.share-manager__table_body',
          sharedDirArr.length &&
            sharedDirArr.map((sharedDirItem, index) => {
              const {
                filename,
                virtualname,
                shareflags,
                parent_groups: parentGroups,
              } = sharedDirItem;
              const sharedFlags = futil.calcIndividualFlags(shareflags);
              return m('tr', [
                m(
                  'td',
                  m('input[type=text]', {
                    value: filename,
                    disabled: isEditDisabled,
                    oninput: (e) => {
                      sharedDirArr[index].filename = e.target.value;
                    },
                  })
                ),
                m(
                  'td',
                  m('input[type=text]', {
                    value: virtualname,
                    disabled: isEditDisabled,
                    oninput: (e) => {
                      sharedDirArr[index].virtualname = e.target.value;
                    },
                  })
                ),
                m(
                  'td.share-flags',
                  Object.keys(sharedFlags).map((flag) => {
                    return [
                      m(`input.share-flags-check[type=checkbox][id=${flag}]`, {
                        checked: sharedFlags[flag],
                        disabled: isEditDisabled,
                      }),
                      m(
                        `label.share-flags-label[for=${flag}]`,
                        {
                          onclick: () => {
                            if (isEditDisabled) return;
                            sharedFlags[flag] = !sharedFlags[flag];
                            sharedDirArr[index].shareflags = futil.calcShareFlagsValue(sharedFlags);
                          },
                          style: isEditDisabled && { color: '#7D7D7D' },
                        },
                        m(
                          // check the flag type then if its value is true then only render the icon
                          flag === 'isAnonymousSearch'
                            ? sharedFlags[flag]
                              ? 'i.fas.fa-search'
                              : 'span'
                            : flag === 'isAnonymousDownload'
                            ? sharedFlags[flag]
                              ? 'i.fas.fa-download'
                              : 'span'
                            : sharedFlags[flag]
                            ? 'i.fas.fa-eye'
                            : 'span'
                        )
                      ),
                    ];
                  })
                ),
                m(
                  'td',
                  {
                    // since this is not an input element, manually change color
                    style: { color: isEditDisabled ? '#6D6D6D' : 'black' },
                    onclick: () =>
                      !isEditDisabled && widget.popupMessage(m(ManageVisibility, { parentGroups })),
                  },
                  parentGroups.length === 0
                    ? 'All Friend nodes'
                    : parentGroups.map((groupFlag) => futil.RsNodeGroupId[groupFlag]).join(', ')
                ),
              ]);
            })
        ),
      ]);
    },
  };
};

const ShareManager = () => {
  function setNewSharedDirectories() {
    rs.rsJsonApiRequest('/rsFiles/setSharedDirectories', {
      dirs: sharedDirArr,
    });
  }
  return {
    oninit: loadSharedDirectories,
    view: () => {
      return m('.widget', [
        m('.widget__heading', m('h3', 'ShareManager')),
        m('form.widget__body.share-manager', { onsubmit: setNewSharedDirectories }, [
          m('blockquote.info', shareManagerInfo),
          m(ShareDirTable),
          m('.share-manager__actions', [
            m('button', { onclick: () => widget.popupMessage(m(AddSharedDirForm)) }, 'Add New'),
            m(
              'button',
              { onclick: () => (isEditDisabled = !isEditDisabled) },
              isEditDisabled ? 'Edit' : 'Apply and Close'
            ),
          ]),
        ]),
      ]);
    },
  };
};

module.exports = ShareManager;
 
}); 
require.register("files/files_proxy", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const futil = require('files/files_util');

const fileProxyObj = futil.createProxy({}, () => {
  m.redraw();
});

rs.events[rs.RsEventsType.FILE_TRANSFER] = {
  handler: (event) => {
    console.log('search results : ', event);

    // if request item doesn't already exists in Object then create new item
    if (!Object.prototype.hasOwnProperty.call(fileProxyObj, event.mRequestId)) {
      fileProxyObj[event.mRequestId] = [];
    }

    fileProxyObj[event.mRequestId].push(...event.mResults);
  },
};

module.exports = {
  fileProxyObj,
};
 
}); 
require.register("files/files_resolver", function(exports, require, module) { 
const m = require('mithril');

const widget = require('widgets');

const downloads = require('files/files_downloads');
const uploads = require('files/files_uploads');
const util = require('files/files_util');
const search = require('files/files_search');
const myfile = require('files/my_files');
const friendfile = require('files/friends_files');

const MyFiles = () => {
  return {
    view: () => [
      m('.widget__heading', [
        m('h3', 'File Transfers'),
        m(util.SearchBar, {
          list: Object.assign({}, downloads.list, uploads.list),
        }),
      ]),
      m('.widget__body', [m(downloads.Component), m(uploads.Component)]),
    ],
  };
};

const sections = {
  files: MyFiles,
  search,
  MyFiles: myfile,
  FriendsFiles: friendfile,
};

const Layout = {
  view: (vnode) => [
    m(widget.Sidebar, {
      tabs: Object.keys(sections),
      baseRoute: '/files/',
    }),
    m('.node-panel', m('.widget', vnode.children)),
  ],
};

module.exports = {
  view: (vnode) => {
    const tab = vnode.attrs.tab;
    return m(Layout, m(sections[tab]));
  },
};
 
}); 
require.register("files/files_search", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');
const futil = require('files/files_util');
const fproxy = require('files/files_proxy');

let matchString = '';
let currentItem = 0;
const reqObj = {};

function handleSubmit() {
  rs.rsJsonApiRequest('/rsFiles/turtleSearch', { matchString })
    .then((res) => {
      // Add prefix to obj keys so that javascript doesn't sort them
      reqObj['_' + res.body.retval] = matchString;
      currentItem = '_' + res.body.retval;
    })
    .catch((error) => console.log(error));
}

const SearchBar = () => {
  return {
    view: () =>
      m('form.search-form', { onsubmit: handleSubmit }, [
        m('input[type=text][placeholder=search keyword]', {
          value: matchString,
          oninput: (e) => (matchString = e.target.value),
        }),
        m('button[type=submit]', m('i.fas.fa-search')),
      ]),
  };
};

const Layout = () => {
  let active = 0;
  function handleFileDownload(item) {
    rs.rsJsonApiRequest('/rsFiles/FileRequest', {
      fileName: item.fName,
      hash: item.fHash,
      flags: futil.RS_FILE_REQ_ANONYMOUS_ROUTING,
      size: {
        xstr64: item.fSize.xstr64,
      },
    })
      .then((res) => {
        widget.popupMessage(
          m('.widget', [
            m('.widget__heading', m('h3', m('i.fas.fa-file-medical'), ' File Download')),
            m(
              '.widget__body',
              m('p', `File is ${res.body.retval ? 'getting' : 'already'} downloaded.`)
            ),
          ])
        );
      })
      .catch((error) => {
        console.log('error in sending download request: ', error);
      });
  }
  return {
    view: () => [
      m('.widget__heading', [m('h3', 'Search'), m(SearchBar)]),
      m('.widget__body', [
        m('div.file-search-container', [
          m('div.file-search-container__keywords', [
            m('h5.bold', 'Keywords'),
            Object.keys(reqObj).length !== 0 &&
              m(
                'div.keywords-container',
                Object.keys(reqObj)
                  .reverse()
                  .map((item, index) => {
                    return m(
                      m.route.Link,
                      {
                        class: active === index ? 'selected' : '',
                        onclick: () => {
                          active = index;
                          currentItem = item;
                        },
                        href: `/files/search/${item}`,
                      },
                      reqObj[item]
                    );
                  })
              ),
          ]),
          m('div.file-search-container__results', [
            Object.keys(fproxy.fileProxyObj).length === 0
              ? m('h5.bold', 'Results')
              : m('table.results-container', [
                  m(
                    'thead.results-header',
                    m('tr', [
                      m('th', 'Name'),
                      m('th', 'Size'),
                      m('th', 'Hash'),
                      m('th', 'Download'),
                    ])
                  ),
                  m(
                    'tbody.results',
                    fproxy.fileProxyObj[currentItem.slice(1)]
                      ? fproxy.fileProxyObj[currentItem.slice(1)].map((item) =>
                          m('tr', [
                            m('td.results__name', [m('i.fas.fa-file'), m('span', item.fName)]),
                            m('td.results__size', rs.formatBytes(item.fSize.xint64)),
                            m('td.results__hash', item.fHash),
                            m(
                              'td.results__download',
                              m('button', { onclick: () => handleFileDownload(item) }, 'Download')
                            ),
                          ])
                        )
                      : 'No Results.'
                  ),
                ]),
          ]),
        ]),
      ]),
    ],
  };
};

module.exports = {
  view: () => m(Layout),
};
 
}); 
require.register("files/files_uploads", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('files/files_util');

const Uploads = {
  statusMap: {},
  hashes: [],

  loadHashes() {
    rs.rsJsonApiRequest('/rsFiles/FileUploads', {}, (d) => (Uploads.hashes = d.hashs));
  },

  loadStatus() {
    Uploads.loadHashes();
    const fileKeys = Object.keys(Uploads.statusMap);
    if (Uploads.hashes.length !== fileKeys.length) {
      // New file added
      if (Uploads.hashes.length > fileKeys.length) {
        const newHashes = util.compareArrays(Uploads.hashes, fileKeys);
        for (const hash of newHashes) {
          Uploads.updateFileDetail(hash, true);
        }
      }
      // Existing file removed
      else {
        const oldHashes = util.compareArrays(fileKeys, Uploads.hashes);
        for (const hash of oldHashes) {
          delete Uploads.statusMap[hash];
        }
      }
    }
    for (const hash in Uploads.statusMap) {
      Uploads.updateFileDetail(hash);
    }
  },
  updateFileDetail(hash, isNew = false) {
    rs.rsJsonApiRequest(
      '/rsFiles/FileDetails',
      {
        hash,
        hintflags: 32, // RS_FILE_HINTS_UPLOAD
      },
      (fileStat) => {
        if (!fileStat.retval) {
          console.error('Error: Unknown hash in Uploads: ', hash);
          return;
        }
        fileStat.info.isSearched = isNew ? true : Uploads.statusMap[hash].isSearched;
        Uploads.statusMap[hash] = fileStat.info;
      }
    );
  },
};

function averageOf(peers) {
  return peers.reduce((s, e) => s + e.transfered.xint64, 0) / peers.length;
}

const Component = () => {
  return {
    oninit: () =>
      rs.setBackgroundTask(Uploads.loadStatus, 1000, () => {
        return m.route.get() === '/files/files';
      }),
    view: () =>
      Uploads.hashes.length > 0
        ? m('.widget', [
            m('h3', 'Uploads (' + Uploads.hashes.length + ' files)'),
            m('hr'),
            Object.keys(Uploads.statusMap).map((hash) =>
              m(util.File, {
                info: Uploads.statusMap[hash],
                direction: 'up',
                transferred: averageOf(Uploads.statusMap[hash].peers),
                parts: Uploads.statusMap[hash].peers.reduce(
                  (a, e) => [...a, e.transfered.xint64],
                  []
                ),
              })
            ),
          ])
        : [],
  };
};

module.exports = {
  Component,
  list: Uploads.statusMap,
};
 
}); 
require.register("files/files_util", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');

const RS_FILE_CTRL_PAUSE = 0x00000100;
const RS_FILE_CTRL_START = 0x00000200;
const RS_FILE_CTRL_FORCE_CHECK = 0x00000400;

const FT_STATE_FAILED = 0x0000;
const FT_STATE_OKAY = 0x0001;
const FT_STATE_WAITING = 0x0002;
const FT_STATE_DOWNLOADING = 0x0003;
const FT_STATE_COMPLETE = 0x0004;
const FT_STATE_QUEUED = 0x0005;
const FT_STATE_PAUSED = 0x0006;
const FT_STATE_CHECKING_HASH = 0x0007;

const RS_FILE_REQ_ANONYMOUS_ROUTING = 0x00000040;
const RS_FILE_HINTS_REMOTE = 0x00000008;
const RS_FILE_HINTS_LOCAL = 0x00000004;

// Flags for directory sharing permissions.
const DIR_FLAGS_ANONYMOUS_SEARCH = 0x0800;
const DIR_FLAGS_ANONYMOUS_DOWNLOAD = 0x0080;
const DIR_FLAGS_BROWSABLE = 0x0400;

/* eslint-disable no-unused-vars */

// Access Permission calculated by performing OR operation on the above three flags.
const DIR_FLAGS_PERMISSIONS_MASK =
  DIR_FLAGS_ANONYMOUS_SEARCH | DIR_FLAGS_ANONYMOUS_DOWNLOAD | DIR_FLAGS_BROWSABLE;

/* eslint-enable no-unused-vars */

// parent_groups visibility
const RsNodeGroupId = {
  '00000000000000000000000000000001': 'Friends',
  '00000000000000000000000000000002': 'Family',
  '00000000000000000000000000000003': 'Co-Workers',
  '00000000000000000000000000000004': 'Other Contacts',
  '00000000000000000000000000000005': 'Favorites',
};

function loadRsNodeGroupId() {
  rs.rsJsonApiRequest('/rsPeers/getGroupInfoList').then((res) => {
    const { groupInfoList } = res.body;
    groupInfoList.forEach((groupItem) => {
      if (!Object.prototype.hasOwnProperty.call(RsNodeGroupId, groupItem.id)) {
        RsNodeGroupId[groupItem.id] = groupItem.name;
      }
    });
  });
}

function calcIndividualFlags(shareFlagsVal) {
  const isAnonymousSearch = (shareFlagsVal & DIR_FLAGS_ANONYMOUS_SEARCH) !== 0;
  const isAnonymousDownload = (shareFlagsVal & DIR_FLAGS_ANONYMOUS_DOWNLOAD) !== 0;
  const isBrowsable = (shareFlagsVal & DIR_FLAGS_BROWSABLE) !== 0;
  return {
    isAnonymousSearch,
    isAnonymousDownload,
    isBrowsable,
  };
}

function calcShareFlagsValue(shareFlagsObj) {
  // calculate shareFlagsVal by performing OR operation on the Flags that have true value
  const shareFlagsVal =
    (shareFlagsObj.isAnonymousSearch && DIR_FLAGS_ANONYMOUS_SEARCH) |
    (shareFlagsObj.isAnonymousDownload && DIR_FLAGS_ANONYMOUS_DOWNLOAD) |
    (shareFlagsObj.isBrowsable && DIR_FLAGS_BROWSABLE);
  return shareFlagsVal;
}

const createArrayProxy = (arr, onChange) => {
  return new Proxy(arr, {
    set: (target, property, value, reciever) => {
      const success = Reflect.set(target, property, value, reciever);
      if (success && onChange) {
        onChange();
      }
      return success;
    },
  });
};

const createProxy = (obj, onChange) => {
  return new Proxy(obj, {
    get: (target, property, reciever) => {
      const value = Reflect.get(target, property, reciever);
      return typeof value === 'object' && value !== null
        ? Array.isArray(value)
          ? createArrayProxy(value, onChange)
          : createProxy(value, onChange)
        : value;
    },
    set: (target, property, value, reciever) => {
      const success = Reflect.set(target, property, value, reciever);
      if (success && onChange) {
        onChange();
      }
      return success;
    },
  });
};

function calcRemainingTime(bytes, rate) {
  if (rate <= 0 || bytes < 1) {
    return '--';
  } else {
    let secs = bytes / rate / 1024;
    if (secs < 60) {
      return secs.toFixed() + 's';
    }
    let mins = secs / 60;
    secs = secs - Math.floor(mins) * 60;
    if (mins < 60) {
      return mins.toFixed() + 'm ' + secs.toFixed() + 's';
    }
    let hours = mins / 60;
    mins = mins - Math.floor(hours) * 60;
    if (hours < 24) {
      return hours.toFixed() + 'h ' + mins.toFixed() + 'm';
    }
    const days = hours / 24;
    hours = hours - Math.floor(days) * 24;
    return days.toFixed() + 'd ' + hours.toFixed() + 'h';
  }
}

function fileAction(hash, action) {
  const jsonParams = {
    hash,
    flags: 0,
  };
  switch (action) {
    case 'pause':
      jsonParams.flags = RS_FILE_CTRL_PAUSE;
      break;

    case 'resume':
      jsonParams.flags = RS_FILE_CTRL_START;
      break;

    case 'force_check':
      jsonParams.flags = RS_FILE_CTRL_FORCE_CHECK;
      break;

    default:
      console.error('Unknown action in Downloads.control()');
      return;
  }
  rs.rsJsonApiRequest('/rsFiles/FileControl', jsonParams);
}

const ProgressBar = () => {
  return {
    view: (v) =>
      m('.progress-bar-chunks', [
        v.attrs.chunksInfo.chunks.map((item) => m(`span.chunk[data-chunkVal=${item}]`)),
        m('span.progress-bar-chunks__percent', v.attrs.rate.toPrecision(3) + '%'),
      ]),
  };
};

const File = () => {
  let chunkStrat;
  const chunkStrats = {
    // rstypes.h :: 366
    0: 'Streaming', // CHUNK_STRATEGY_STREAMING
    1: 'Random', // CHUNK_STRATEGY_RANDOM
    2: 'Progressive', // CHUNK_STRATEGY_PROGRESSIVE
  };
  function fileCancel(hash) {
    rs.rsJsonApiRequest('/rsFiles/FileCancel', { hash }).then((res) =>
      widget.popupMessage(m('p', `Download Cancel ${res ? 'Successful' : 'Failed'}`))
    );
  }
  function cancelFileDownload(hash) {
    widget.popupMessage([
      m('p', 'Are you sure you want to cancel download?'),
      m('button', { onclick: () => fileCancel(hash) }, 'Cancel'),
    ]);
  }
  function actionButton(file, action) {
    return m(
      'button',
      { title: action, onclick: () => fileAction(file.hash, action) },
      m(`i.fas.fa-${action === 'resume' ? 'play' : action}`)
    );
  }

  return {
    oninit: async (v) => {
      chunkStrat = await v.attrs.strategy;
    },
    view: (v) => {
      const { info, direction, transferred, chunksInfo } = v.attrs;
      function changeChunkStrategy(e) {
        chunkStrat = e.target.selectedIndex;
        rs.rsJsonApiRequest('/rsFiles/setChunkStrategy', {
          hash: info.hash,
          newStrategy: chunkStrat,
        });
      }
      return m('.file-view', { style: { display: info.isSearched ? 'block' : 'none' } }, [
        m('.file-view__heading', [
          m('h6', info.fname),
          chunkStrat !== undefined &&
            direction === 'down' && [
              m('.file-view__heading-chunk', [
                m('label[for=chunkTag]', 'Set Chunk Strategy: '),
                m('select[id=chunkTag]', { value: chunkStrat, onchange: changeChunkStrategy }, [
                  Object.keys(chunkStrats).map((strat) =>
                    m('option', { value: strat }, chunkStrats[strat])
                  ),
                ]),
              ]),
            ],
        ]),
        m('.file-view__body', [
          m(
            '.file-view__body-progress',
            direction === 'down' &&
              m(ProgressBar, { rate: (transferred / info.size.xint64) * 100, chunksInfo })
          ),
          m('.file-view__body-details', [
            m('.file-view__body-details-stat', [
              m('span', { title: 'downloaded size' }, [
                m('i.fas.fa-download'),
                rs.formatBytes(transferred),
              ]),
              m('span', { title: 'total size' }, [
                m('i.fas.fa-file'),
                rs.formatBytes(info.size.xint64),
              ]),
              m('span', { title: 'speed' }, [
                m(`i.fas.fa-arrow-circle-${direction}`),
                `${rs.formatBytes(info.tfRate * 1024)}/s`,
              ]),
              direction === 'down' &&
                m('span', { title: 'time remaining' }, [
                  m('i.fas.fa-clock'),
                  calcRemainingTime(info.size.xint64 - transferred, info.tfRate),
                ]),
              m('span', { title: 'peers' }, [m('i.fas.fa-users'), info.peers.length]),
            ]),
            m(
              '.file-view__body-details-action',
              info.downloadStatus !== FT_STATE_COMPLETE && [
                actionButton(info, info.downloadStatus === FT_STATE_PAUSED ? 'resume' : 'pause'),
                m(
                  'button.red',
                  { title: 'cancel', onclick: () => cancelFileDownload(info.hash) },
                  m('i.fas.fa-times')
                ),
              ]
            ),
          ]),
        ]),
      ]);
    },
  };
};

const SearchBar = () => {
  let searchString = '';
  return {
    view: (v) =>
      m('input[type=text][placeholder=Search].searchbar', {
        value: searchString,
        oninput: (e) => {
          searchString = e.target.value.toLowerCase();
          for (const hash in v.attrs.list) {
            v.attrs.list[hash].isSearched =
              v.attrs.list[hash].fname.toLowerCase().indexOf(searchString) > -1;
          }
        },
      }),
  };
};

function compareArrays(big, small) {
  // Use filter on bigger array
  // Pass `new Set(array_to_compare)` as second param to filter
  // Source: https://stackoverflow.com/a/40538072/7683374
  return big.filter(function (val) {
    return !this.has(val);
  }, new Set(small));
}

const MyFilesTable = () => {
  return {
    view: (v) =>
      m('table.myfiles', [
        m('tr', [m('th', ''), m('th', 'My Directories'), m('th', 'Size')]),
        v.children,
      ]),
  };
};

const FriendsFilesTable = () => {
  return {
    view: (v) =>
      m('table.friendsfiles', [
        m('tr', [
          m('th', ''),
          m('th', 'Friends Directories'),
          m('th', 'Size'),
          m('th', m('i.fas.fa-download')),
        ]),
        v.children,
      ]),
  };
};

module.exports = {
  RS_FILE_CTRL_PAUSE,
  RS_FILE_CTRL_START,
  RS_FILE_CTRL_FORCE_CHECK,
  FT_STATE_FAILED,
  FT_STATE_OKAY,
  FT_STATE_WAITING,
  FT_STATE_DOWNLOADING,
  FT_STATE_COMPLETE,
  FT_STATE_QUEUED,
  FT_STATE_PAUSED,
  FT_STATE_CHECKING_HASH,
  RS_FILE_REQ_ANONYMOUS_ROUTING,
  RS_FILE_HINTS_REMOTE,
  RS_FILE_HINTS_LOCAL,
  DIR_FLAGS_ANONYMOUS_SEARCH,
  DIR_FLAGS_ANONYMOUS_DOWNLOAD,
  DIR_FLAGS_BROWSABLE,
  RsNodeGroupId,
  loadRsNodeGroupId,
  File,
  SearchBar,
  compareArrays,
  MyFilesTable,
  FriendsFilesTable,
  createProxy,
  calcIndividualFlags,
  calcShareFlagsValue,
};
 
}); 
require.register("files/friends_files", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('files/files_util');
const widget = require('widgets');
const fileDown = require('files/files_downloads');

function displayfiles() {
  const childrenList = []; // stores children details
  let loaded = false; // checks whether we have loaded the children details or not.
  let parStruct; // stores current struct(details, showChild)
  let isFile = false;
  let haveFile = false;
  let isId = false;
  let nameOfId;
  return {
    oninit: async (v) => {
      if (v.attrs.par_directory) {
        parStruct = v.attrs.par_directory;
        if (Number(parStruct.details.hash) !== 0) {
          isFile = true;
          const res = await rs.rsJsonApiRequest('/rsfiles/alreadyHaveFile', {
            // checks if the file is already there with the user
            hash: parStruct.details.hash,
          });
          haveFile = res.body.retval;
        }
      }
      if (v.attrs.replyDepth === 1 && parStruct) {
        isId = true;
        const res = await rs.rsJsonApiRequest('/rsPeers/getPeerDetails', {
          sslId: parStruct.details.name,
        });
        if (res.body.retval) {
          nameOfId = res.body.det.name;
        }
      }
    },
    view: (v) => [
      m('tr', [
        parStruct && Object.keys(parStruct.details.children).length
          ? m(
              'td',
              m('i.fas.fa-angle-right', {
                class: 'fa-rotate-' + (parStruct.showChild ? '90' : '0'),
                style: 'margin-top:12px',
                onclick: () => {
                  if (!loaded) {
                    // if it is not already retrieved.
                    parStruct.details.children.map(async (child) => {
                      const res = await rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {
                        handle: child.handle.xint64,
                        flags: util.RS_FILE_HINTS_REMOTE,
                      });
                      childrenList.push(res.body.details);
                      loaded = true;
                    });
                  }
                  parStruct.showChild = !parStruct.showChild;
                },
              })
            )
          : m('td', ''),
        m(
          'td',
          {
            style: {
              position: 'relative',
              '--replyDepth': v.attrs.replyDepth,
              left: `calc(30px*${v.attrs.replyDepth})`,
            },
          },
          isId
            ? nameOfId + ' (' + parStruct.details.name.slice(0, 8) + '...)'
            : parStruct.details.name
        ),
        m('td', rs.formatBytes(parStruct.details.size.xint64)),
        isFile &&
          m(
            'td',
            // using the file from files_util to display download.
            fileDown.list[parStruct.details.hash]
              ? m(util.File, {
                  info: fileDown.list[parStruct.details.hash],
                  direction: 'down',
                  transferred: fileDown.list[parStruct.details.hash].transfered.xint64,
                  parts: [],
                })
              : m(
                  'button',
                  {
                    style: { fontSize: '0.9em' },
                    onclick: async () => {
                      widget.popupMessage([
                        m('p', 'Start Download?'),
                        m(
                          'button',
                          {
                            onclick: async () => {
                              if (!haveFile) {
                                const res = await rs.rsJsonApiRequest('/rsFiles/FileRequest', {
                                  fileName: parStruct.details.name,
                                  hash: parStruct.details.hash,
                                  flags: util.RS_FILE_REQ_ANONYMOUS_ROUTING,
                                  size: {
                                    xstr64: parStruct.details.size.xstr64,
                                  },
                                });
                                res.body.retval === false
                                  ? widget.popupMessage([
                                      m('h3', 'Error'),
                                      m('hr'),
                                      m('p', res.body.errorMessage),
                                    ])
                                  : widget.popupMessage([
                                      m('h3', 'Success'),
                                      m('hr'),
                                      m('p', 'Download Started'),
                                    ]);
                                m.redraw();
                              }
                            },
                          },
                          'Start Download'
                        ),
                      ]);
                    },
                  },

                  haveFile ? 'Open File' : ['Download', m('i.fas.fa-download')]
                )
          ),
      ]),
      parStruct.showChild && // recursive call to show children
        childrenList.map((child) =>
          m(displayfiles, {
            par_directory: { details: child, showChild: false },
            replyDepth: v.attrs.replyDepth + 1,
          })
        ),
    ],
  };
}

const Layout = () => {
  //  let root_handle;
  let parent;
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {
        flags: util.RS_FILE_HINTS_REMOTE,
      }).then((res) => (parent = res));
    },
    view: () => [
      m('.widget__heading', [m('h3', 'Friends Files')]),
      m('.widget__body', [
        m(
          util.FriendsFilesTable,
          m(
            'tbody',
            parent && // root
              m(displayfiles, {
                par_directory: { details: parent.body.details, showChild: false },
                replyDepth: 0,
              })
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("files/my_files", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('files/files_util');
const manager = require('files/files_manager');

const DisplayFiles = () => {
  const childrenList = []; // stores children details
  let loaded = false; // checks whether we have loaded the children details or not.
  let parStruct; // stores current struct(details, showChild)
  return {
    oninit: (v) => {
      if (v.attrs.par_directory) {
        parStruct = v.attrs.par_directory;
      }
    },
    view: (v) => [
      m('tr', [
        parStruct && Object.keys(parStruct.details.children).length
          ? m(
              'td',
              m('i.fas.fa-angle-right', {
                class: `fa-rotate-${parStruct.showChild ? '90' : '0'}`,
                style: 'margin-top: 0.5rem',
                onclick: () => {
                  if (!loaded) {
                    // if it is not already retrieved
                    parStruct.details.children.map(async (child) => {
                      const res = await rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {
                        handle: child.handle.xint64,
                        flags: util.RS_FILE_HINTS_LOCAL,
                      });
                      childrenList.push(res.body.details);
                      loaded = true;
                    });
                  }
                  parStruct.showChild = !parStruct.showChild;
                },
              })
            )
          : m('td', ''),
        m(
          'td',
          {
            style: {
              position: 'relative',
              '--replyDepth': v.attrs.replyDepth,
              left: `calc(1.5rem*${v.attrs.replyDepth})`,
            },
          },
          parStruct.details.name
        ),
        m('td', rs.formatBytes(parStruct.details.size.xint64)),
      ]),
      parStruct.showChild &&
        childrenList.map((child) =>
          m(DisplayFiles, {
            // recursive call
            par_directory: { details: child, showChild: false },
            replyDepth: v.attrs.replyDepth + 1,
          })
        ),
    ],
  };
};

const Layout = () => {
  //  let root_handle;
  let parent;
  let showShareManager = false;
  return {
    oninit: () => {
      rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {}).then((res) => (parent = res));
    },
    view: () => [
      m('.widget__heading', [
        m('h3', 'My Files'),
        m('button', { onclick: () => (showShareManager = true) }, 'Configure shared directories'),
      ]),
      m('.widget__body', [
        m(
          util.MyFilesTable,
          m(
            'tbody',
            parent &&
              m(DisplayFiles, {
                par_directory: { details: parent.body.details, showChild: false },
                replyDepth: 0,
              })
          )
        ),
        m(
          '.shareManagerPopupOverlay#shareManagerPopup',
          { style: { display: showShareManager ? 'block' : 'none' } },
          m(
            '.shareManagerPopup',
            m(manager),
            m(
              'button.red.close-btn',
              { onclick: () => (showShareManager = false) },
              m('i.fas.fa-times')
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("forums/forums", function(exports, require, module) { 
const m = require('mithril');
const widget = require('widgets');
const rs = require('rswebui');
const util = require('forums/forums_util');
const viewUtil = require('forums/forum_view');
const peopleUtil = require('people/people_util');

const getForums = {
  All: [],
  PopularForums: [],
  SubscribedForums: [],
  MyForums: [],
  async load() {
    const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumsSummaries');
    getForums.All = res.body.forums;
    getForums.PopularForums = getForums.All;
    getForums.SubscribedForums = getForums.All.filter(
      (forum) =>
        forum.mSubscribeFlags === util.GROUP_SUBSCRIBE_SUBSCRIBED ||
        forum.mSubscribeFlags === util.GROUP_MY_FORUM
    );
    getForums.MyForums = getForums.All.filter(
      (forum) => forum.mSubscribeFlags === util.GROUP_MY_FORUM
    );
  },
};
const sections = {
  MyForums: require('forums/my_forums'),
  SubscribedForums: require('forums/subscribed_forums'),
  PopularForums: require('forums/popular_forums'),
  OtherForums: require('forums/other_forums'),
};

const Layout = () => {
  let ownId;

  return {
    oninit: () => {
      rs.setBackgroundTask(getForums.load, 5000, () => {
        // return m.route.get() === '/files/files';
      });
      peopleUtil.ownIds((data) => {
        ownId = data;
        for (let i = 0; i < ownId.length; i++) {
          if (Number(ownId[i]) === 0) {
            ownId.splice(i, 1);
          }
        }
        ownId.unshift(0);
      });
    },
    view: (vnode) =>
      m('.widget', [
        m('.top-heading', [
          m(
            'button',
            {
              onclick: () =>
                ownId &&
                util.popupmessage(
                  m(viewUtil.createforum, {
                    authorId: ownId,
                  })
                ),
            },
            'Create Forum'
          ),
          m(util.SearchBar, {
            list: getForums.All,
          }),
        ]),
        Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mMsgId') // thread's view
          ? m(viewUtil.ThreadView, {
              msgId: vnode.attrs.pathInfo.mMsgId,
              forumId: vnode.attrs.pathInfo.mGroupId,
            })
          : Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mGroupId') // Forum's view
          ? m(viewUtil.ForumView, {
              id: vnode.attrs.pathInfo.mGroupId,
            })
          : m(sections[vnode.attrs.pathInfo.tab], {
              list: getForums[vnode.attrs.pathInfo.tab],
            }),
      ]),
  };
};

module.exports = {
  view: (vnode) => {
    return [
      m(widget.Sidebar, {
        tabs: Object.keys(sections),
        baseRoute: '/forums/',
      }),
      m('.node-panel', m(Layout, { pathInfo: vnode.attrs })),
    ];
  },
};
 
}); 
require.register("forums/forums_util", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const GROUP_SUBSCRIBE_ADMIN = 0x01; // means: you have the admin key for this group
const GROUP_SUBSCRIBE_PUBLISH = 0x02; // means: you have the publish key for thiss group. Typical use: publish key in forums are shared with specific friends.
const GROUP_SUBSCRIBE_SUBSCRIBED = 0x04; // means: you are subscribed to a group, which makes you a source for this group to your friend nodes.
const GROUP_SUBSCRIBE_NOT_SUBSCRIBED = 0x08;
const GROUP_MY_FORUM = GROUP_SUBSCRIBE_ADMIN + GROUP_SUBSCRIBE_SUBSCRIBED + GROUP_SUBSCRIBE_PUBLISH;

const THREAD_UNREAD = 0x00000003;

const Data = {
  DisplayForums: {},
  Threads: {},
  ParentThreads: {},
  ParentThreadMap: {},
};

async function updatedisplayforums(keyid, details = {}) {
  const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumsInfo', {
    forumIds: [keyid], // keyid: Forumid
  });
  details = res.body.forumsInfo[0];
  Data.DisplayForums[keyid] = {
    // struct for a forum
    name: details.mMeta.mGroupName,
    author: details.mMeta.mAuthorId,
    isSearched: true,
    description: details.mDescription,
    isSubscribed:
      details.mMeta.mSubscribeFlags === GROUP_SUBSCRIBE_SUBSCRIBED ||
      details.mMeta.mSubscribeFlags === GROUP_MY_FORUM,
    activity: details.mMeta.mLastPost,
    created: details.mMeta.mPublishTs,
  };
  if (Data.Threads[keyid] === undefined) {
    Data.Threads[keyid] = {};
  }
  const res2 = await rs.rsJsonApiRequest('/rsgxsforums/getForumMsgMetaData', {
    forumId: keyid,
  });
  if (res2.body.retval) {
    res2.body.msgMetas.map(async (thread) => {
      const res3 = await rs.rsJsonApiRequest('/rsgxsforums/getForumContent', {
        forumId: keyid,
        msgsIds: [thread.mMsgId],
      });

      if (
        res3.body.retval &&
        (Data.Threads[keyid][thread.mOrigMsgId] === undefined ||
          Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta.mPublishTs.xint64 <
            thread.mPublishTs.xint64)
        // here we get the latest edited thread for each thread by comparing the publish time
      ) {
        Data.Threads[keyid][thread.mOrigMsgId] = { thread: res3.body.msgs[0], showReplies: false };
        if (
          Data.Threads[keyid][thread.mOrigMsgId] &&
          Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta.mMsgStatus === THREAD_UNREAD
        ) {
          let parent = Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta.mParentId;
          while (Data.Threads[keyid][parent]) {
            // to mark all parent threads of an inread thread
            Data.Threads[keyid][parent].thread.mMeta.mMsgStatus = THREAD_UNREAD;
            parent = Data.Threads[keyid][parent].thread.mMeta.mParentId;
          }
        }

        if (Data.ParentThreads[keyid] === undefined) {
          Data.ParentThreads[keyid] = {};
        }
        if (thread.mThreadId === thread.mParentId) {
          // top level thread.
          Data.ParentThreads[keyid][thread.mOrigMsgId] =
            Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta;
        } else {
          if (Data.ParentThreadMap[thread.mParentId] === undefined) {
            Data.ParentThreadMap[thread.mParentId] = {};
          }
          Data.ParentThreadMap[thread.mParentId][thread.mOrigMsgId] = thread;
        }
      }
    });
  }
}

const DisplayForumsFromList = () => {
  return {
    view: (v) =>
      m(
        'tr',
        {
          key: v.attrs.id,
          class:
            Data.DisplayForums[v.attrs.id] && Data.DisplayForums[v.attrs.id].isSearched
              ? ''
              : 'hidden',
          onclick: () => {
            m.route.set('/forums/:tab/:mGroupId', {
              tab: v.attrs.category,
              mGroupId: v.attrs.id,
            });
          },
        },
        [m('td', Data.DisplayForums[v.attrs.id] ? Data.DisplayForums[v.attrs.id].name : '')]
      ),
  };
};

const ForumSummary = () => {
  let keyid = {};
  return {
    oninit: (v) => {
      keyid = v.attrs.details.mGroupId;
      updatedisplayforums(keyid);
    },
    view: (v) => {},
  };
};

const ForumTable = () => {
  return {
    view: (v) => m('table.forums', [m('tr', [m('th', 'Forum Name')]), v.children]),
  };
};
const ThreadsTable = () => {
  return {
    oninit: (v) => {},
    view: (v) =>
      m('table.threads', [
        m('tr', [m('th', 'Comment'), m('th', 'Date'), m('th', 'Author')]),
        v.children,
      ]),
  };
};
const ThreadsReplyTable = () => {
  return {
    oninit: (v) => {},
    view: (v) =>
      m('table.threadreply', [
        m('tr', [
          m('th', ''),
          m('th', 'Comment'),
          m('th', 'Unread'),
          m('th', 'Author'),
          m('th', 'Date'),
        ]),
        v.children,
      ]),
  };
};

const SearchBar = () => {
  let searchString = '';
  return {
    view: (v) =>
      m('input[type=text][id=searchforum][placeholder=Search Subject].searchbar', {
        value: searchString,
        oninput: (e) => {
          searchString = e.target.value.toLowerCase();
          for (const hash in Data.DisplayForums) {
            if (Data.DisplayForums[hash].name.toLowerCase().indexOf(searchString) > -1) {
              Data.DisplayForums[hash].isSearched = true;
            } else {
              Data.DisplayForums[hash].isSearched = false;
            }
          }
        },
      }),
  };
};
function popupmessage(message) {
  const container = document.getElementById('modal-container');
  container.style.display = 'block';
  m.render(
    container,
    m('.modal-content', [
      m(
        'button.red',
        {
          onclick: () => (container.style.display = 'none'),
        },
        m('i.fas.fa-times')
      ),
      message,
    ])
  );
}

module.exports = {
  Data,
  SearchBar,
  ForumSummary,
  DisplayForumsFromList,
  ForumTable,
  ThreadsTable,
  ThreadsReplyTable,
  popupmessage,
  updatedisplayforums,
  GROUP_SUBSCRIBE_ADMIN,
  GROUP_SUBSCRIBE_NOT_SUBSCRIBED,
  GROUP_SUBSCRIBE_PUBLISH,
  GROUP_SUBSCRIBE_SUBSCRIBED,
  GROUP_MY_FORUM,
  THREAD_UNREAD,
};
 
}); 
require.register("forums/forum_view", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('forums/forums_util');
const peopleUtil = require('people/people_util');
const { updatedisplayforums } = require('./forums_util');

function createforum() {
  let title;
  let body;
  let identity;
  return {
    oninit: (vnode) => {
      if (vnode.attrs.authorId) {
        identity = vnode.attrs.authorId[0];
      }
    },
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Create Forum'),
        m('hr'),
        m('input[type=text][placeholder=Title]', {
          oninput: (e) => (title = e.target.value),
        }),
        m('label[for=tags]', 'Select identity'),
        m(
          'select[id=idtags]',
          {
            value: identity,
            onchange: (e) => {
              identity = vnode.attrs.authorId[e.target.selectedIndex];
            },
          },
          [
            vnode.attrs.authorId &&
              vnode.attrs.authorId.map((o) =>
                m(
                  'option',
                  { value: o },
                  rs.userList.userMap[o]
                    ? rs.userList.userMap[o].toLocaleString() + ' (' + o.slice(0, 8) + '...)'
                    : 'No Signature'
                )
              ),
          ]
        ),
        m('textarea[rows=5][placeholder=Description]', {
          style: { width: '90%', display: 'block' },
          oninput: (e) => (body = e.target.value),
          value: body,
        }),
        m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsgxsforums/createForumV2', {
                name: title,
                description: body,
                ...(Number(identity) !== 0 && { authorId: identity }), // if id == '0', authorId is left empty
              });
              if (res.body.retval) {
                util.updatedisplayforums(res.body.forumId);
                m.redraw();
              }
              res.body.retval === false
                ? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
                : util.popupmessage([
                    m('h3', 'Success'),
                    m('hr'),
                    m('p', 'Forum created successfully'),
                  ]);
            },
          },
          'Create'
        ),
      ]),
  };
}
const EditThread = () => {
  let title = '';
  let body = '';
  return {
    oninit: (vnode) => {
      title = vnode.attrs.current_title;
      body = vnode.attrs.current_body;
    },
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Edit Thread'),
        m('hr'),

        m(
          'iddisplay',
          {
            style: { display: 'block ruby' }, // same line block ruby
          },
          [
            'Identity: ',
            m('h5[id=authid]', rs.userList.userMap[vnode.attrs.authorId].toLocaleString()),
          ]
        ),
        m(
          'titledisplay',
          {
            style: { display: 'block ruby' },
          },
          [
            'Title: ',
            m('input[type=text][placeholder=Title]', {
              value: vnode.attrs.current_title,
              oninput: (e) => (title = e.target.value),
            }),
          ]
        ),
        m('textarea[rows=5]', {
          style: { width: '90%', display: 'block' },
          oninput: (e) => (body = e.target.value),
          value: vnode.attrs.current_body,
        }),
        m(
          'button',
          {
            onclick: async () => {
              const res = await rs.rsJsonApiRequest('/rsgxsforums/createPost', {
                forumId: vnode.attrs.forumId,
                mBody: body,
                title,
                authorId: vnode.attrs.authorId,
                parentId: vnode.attrs.current_parent,
                origPostId: vnode.attrs.current_msgid,
              });
              res.body.retval === false
                ? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
                : util.popupmessage([
                    m('h3', 'Success'),
                    m('hr'),
                    m('p', 'Thread edited successfully'),
                  ]);
              util.updatedisplayforums(vnode.attrs.forumId);
              m.redraw();
            },
          },
          'Add'
        ),
      ]),
  };
};
const AddThread = () => {
  let title = '';
  let body = '';
  let identity;
  return {
    oninit: (vnode) => {
      if (vnode.attrs.authorId) {
        identity = vnode.attrs.authorId[0];
      }
    },
    view: (vnode) =>
      m('.widget', [
        m('h3', 'Add Thread'),
        m('hr'),
        (vnode.attrs.parent_thread !== '') > 0
          ? [m('h5', 'Reply to thread: '), m('p', vnode.attrs.parent_thread)]
          : '',
        m('input[type=text][placeholder=Title]', {
          oninput: (e) => (title = e.target.value),
        }),
        m('label[for=tags]', 'Select identity'),
        m(
          'select[id=idtags]',
          {
            value: identity,
            onchange: (e) => {
              identity = vnode.attrs.authorId[e.target.selectedIndex];
            },
          },
          [
            vnode.attrs.authorId &&
              vnode.attrs.authorId.map((o) =>
                m(
                  'option',
                  { value: o },
                  rs.userList.userMap[o].toLocaleString() + ' (' + o.slice(0, 8) + '...)'
                )
              ),
          ]
        ),
        m('textarea[rows=5]', {
          style: { width: '90%', display: 'block' },
          oninput: (e) => (body = e.target.value),
          value: body,
        }),
        m(
          'button',
          {
            onclick: async () => {
              const res =
                (vnode.attrs.parent_thread !== '') > 0 // is it a reply or a new thread
                  ? await rs.rsJsonApiRequest('/rsgxsforums/createPost', {
                      forumId: vnode.attrs.forumId,
                      mBody: body,
                      title,
                      authorId: identity,
                      parentId: vnode.attrs.parentId,
                    })
                  : await rs.rsJsonApiRequest('/rsgxsforums/createPost', {
                      forumId: vnode.attrs.forumId,
                      mBody: body,
                      title,
                      authorId: identity,
                    });

              res.body.retval === false
                ? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
                : util.popupmessage([
                    m('h3', 'Success'),
                    m('hr'),
                    m('p', 'Thread added successfully'),
                  ]);
              util.updatedisplayforums(vnode.attrs.forumId);
              m.redraw();
            },
          },
          'Add'
        ),
      ]),
  };
};
function displaythread() {
  // recursive function to display all the threads
  let groupmessagepair;
  let unread;
  let editpermission = false;
  return {
    view: (v) => {
      const thread = v.attrs.threadStruct.thread;
      groupmessagepair = { first: thread.mMeta.mGroupId, second: thread.mMeta.mOrigMsgId };
      let parMap = [];
      if (util.Data.ParentThreadMap[thread.mMeta.mOrigMsgId]) {
        parMap = util.Data.ParentThreadMap[thread.mMeta.mOrigMsgId];
      }
      unread = thread.mMeta.mMsgStatus === util.THREAD_UNREAD;
      v.attrs.identity &&
        v.attrs.identity.map((val) => {
          if (val.localeCompare(thread.mMeta.mAuthorId) === 0) {
            // if the author of the thread matches one of our own ids
            editpermission = true;
          }
        });
      return [
        m(
          'tr',
          {
            style: unread ? { fontWeight: 'bold' } : '',
          },
          [
            Object.keys(parMap).length // if this thread has some replies
              ? m(
                  'td',
                  m('i.fas.fa-angle-right', {
                    class: 'fa-rotate-' + (v.attrs.threadStruct.showReplies ? '90' : '0'),
                    style: 'margin-top:12px',
                    onclick: () => {
                      v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies;
                    },
                  })
                )
              : m('td', ''),

            m(
              'td',
              {
                style: {
                  position: 'relative',
                  '--replyDepth': v.attrs.replyDepth,
                  left: 'calc(30px*var(--replyDepth))', // shifts reply by 30 px
                },
                onclick: async () => {
                  v.attrs.changeThread(thread.mMeta.mOrigMsgId);
                  if (unread) {
                    const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
                      messageId: groupmessagepair,
                      read: true,
                    });
                    if (res.body.retval) {
                      updatedisplayforums(thread.mMeta.mGroupId);
                      m.redraw();
                    }
                  }
                },
                ondblclick: () =>
                  (v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies),
              },
              [
                thread.mMeta.mMsgName,
                m('options', { style: 'display:block' }, [
                  m(
                    'button',
                    {
                      style: 'font-size:15px',
                      onclick: () =>
                        util.popupmessage(
                          m(AddThread, {
                            parent_thread: thread.mMeta.mMsgName,
                            forumId: thread.mMeta.mGroupId,
                            authorId: v.attrs.identity,
                            parentId: thread.mMeta.mMsgId,
                          })
                        ),
                    },
                    'Reply'
                  ),
                  editpermission &&
                    m(
                      'button',
                      {
                        style: 'font-size:15px',
                        onclick: () =>
                          util.popupmessage(
                            m(EditThread, {
                              current_thread: thread.mMeta.mMsgName,
                              forumId: thread.mMeta.mGroupId,
                              current_title: thread.mMeta.mMsgName,
                              current_body: thread.mMsg,
                              authorId: thread.mMeta.mAuthorId,
                              current_parent: thread.mMeta.mParentId,
                              current_msgid: thread.mMeta.mOrigMsgId,
                            })
                          ),
                      },
                      'Edit'
                    ),
                ]),
              ]
            ),
            m(
              'td',
              m(
                'button',
                {
                  style: { fontSize: '15px' },
                  onclick: async () => {
                    if (!unread) {
                      const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
                        messageId: groupmessagepair,
                        read: false,
                      });

                      if (res.body.retval) {
                        updatedisplayforums(thread.mMeta.mGroupId);
                        m.redraw();
                      }
                    }
                  },
                },
                'Mark Unread'
              )
            ),
            m('td', rs.userList.userMap[thread.mMeta.mAuthorId]),
            m(
              'td',
              typeof thread.mMeta.mPublishTs === 'object'
                ? new Date(thread.mMeta.mPublishTs.xint64 * 1000).toLocaleString()
                : 'undefined'
            ),
          ]
        ),
        v.attrs.threadStruct.showReplies &&
          Object.keys(parMap).map((key, index) =>
            m(displaythread, {
              // recursive call to all replies
              threadStruct: util.Data.Threads[parMap[key].mGroupId][parMap[key].mOrigMsgId],
              replyDepth: v.attrs.replyDepth + 1,
              identity: v.attrs.identity,
              changeThread: v.attrs.changeThread,
            })
          ),
      ];
    },
  };
}

const ThreadView = () => {
  let thread = {};
  let ownId;
  return {
    showThread: '',
    oninit: async (v) => {
      if (
        util.Data.ParentThreads[v.attrs.forumId] &&
        util.Data.ParentThreads[v.attrs.forumId][v.attrs.msgId]
      ) {
        thread = util.Data.ParentThreads[v.attrs.forumId][v.attrs.msgId];
      }
      peopleUtil.ownIds((data) => {
        ownId = data;
        for (let i = 0; i < ownId.length; i++) {
          if (Number(ownId[i]) === 0) {
            ownId.splice(i, 1);
          }
        }
      });
    },
    view: (v) =>
      m('.widget', { key: v.attrs.msgId }, [
        m(
          'a[title=Back]',
          {
            onclick: () =>
              m.route.set('/forums/:tab/:mGroupId', {
                tab: m.route.param().tab,
                mGroupId: m.route.param().mGroupId,
              }),
          },
          m('i.fas.fa-arrow-left')
        ),
        m('h3', thread.mMsgName),
        m('hr'),
        m(
          util.ThreadsReplyTable,
          m(
            'tbody',
            util.Data.Threads[v.attrs.forumId] &&
              util.Data.Threads[v.attrs.forumId][v.attrs.msgId] &&
              m(displaythread, {
                threadStruct: util.Data.Threads[v.attrs.forumId][v.attrs.msgId],
                replyDepth: 0,
                identity: ownId,
                changeThread(newThread) {
                  v.state.showThread = newThread;
                  // For displaying the messages of the threads. We pass this into the recursive function displaythreads()
                },
              })
          )
        ),
        m('hr'),
        v.state.showThread && [
          m('h4', 'Messages'),
          util.Data.Threads[v.attrs.forumId] &&
            util.Data.Threads[v.attrs.forumId][v.state.showThread] &&
            m('p', m.trust(util.Data.Threads[v.attrs.forumId][v.state.showThread].thread.mMsg)),
          // m.trust is to render html content directly.
        ],
      ]),
  };
};

const ForumView = () => {
  let fname = '';
  let fauthor = '';
  let fsubscribed = {};
  let createDate = {};
  let lastActivity = {};
  let topThreads = {};
  let ownId = '';
  return {
    oninit: (v) => {
      if (util.Data.DisplayForums[v.attrs.id]) {
        fname = util.Data.DisplayForums[v.attrs.id].name;
        fsubscribed = util.Data.DisplayForums[v.attrs.id].isSubscribed;
        createDate = util.Data.DisplayForums[v.attrs.id].created;
        lastActivity = util.Data.DisplayForums[v.attrs.id].activity;
        if (rs.userList.userMap[util.Data.DisplayForums[v.attrs.id].author]) {
          fauthor = rs.userList.userMap[util.Data.DisplayForums[v.attrs.id].author];
        } else if (Number(util.Data.DisplayForums[v.attrs.id].author) === 0) {
          fauthor = 'No Contact Author';
        } else {
          fauthor = 'Unknown';
        }
      }
      if (util.Data.ParentThreads[v.attrs.id]) {
        topThreads = util.Data.ParentThreads[v.attrs.id];
      }
      peopleUtil.ownIds((data) => {
        ownId = data;
        for (let i = 0; i < ownId.length; i++) {
          if (Number(ownId[i]) === 0) {
            ownId.splice(i, 1);
          }
        }
      });
    },
    view: (v) => [
      m(
        'a[title=Back]',
        {
          onclick: () =>
            m.route.set('/forums/:tab', {
              tab: m.route.param().tab,
            }),
        },
        m('i.fas.fa-arrow-left')
      ),

      m('h3', fname),
      m(
        'button',
        {
          onclick: async () => {
            const res = await rs.rsJsonApiRequest('/rsgxsforums/subscribeToForum', {
              forumId: v.attrs.id,
              subscribe: !fsubscribed,
            });
            if (res.body.retval) {
              fsubscribed = !fsubscribed;
              util.Data.DisplayForums[v.attrs.id].isSubscribed = fsubscribed;
            }
          },
        },
        fsubscribed ? 'Subscribed' : 'Subscribe'
      ),
      m('[id=forumdetails]', [
        m(
          'p',
          m('b', 'Date created: '),
          typeof createDate === 'object'
            ? new Date(createDate.xint64 * 1000).toLocaleString()
            : 'undefined'
        ),
        m('p', m('b', 'Admin: '), fauthor),
        m(
          'p',
          m('b', 'Last activity: '),
          typeof lastActivity === 'object'
            ? new Date(lastActivity.xint64 * 1000).toLocaleString()
            : 'undefined'
        ),
      ]),
      m('hr'),
      m('forumdesc', m('b', 'Description: '), util.Data.DisplayForums[v.attrs.id].description),
      m('hr'),
      m(
        'threaddetails',
        {
          style: 'display:' + (fsubscribed ? 'block' : 'none'),
        },
        m('h3', 'Threads'),
        m(
          'button',
          {
            onclick: () => {
              util.popupmessage(
                m(AddThread, {
                  parent_thread: '',
                  forumId: v.attrs.id,
                  authorId: ownId,
                  parentId: '',
                })
              );
            },
          },
          ['New Thread', m('i.fas.fa-pencil-alt')]
        ),
        m('hr'),
        m(
          util.ThreadsTable,
          m(
            'tbody',
            Object.keys(topThreads).map((key, index) =>
              m(
                'tr',
                {
                  style:
                    topThreads[key].mMsgStatus === util.THREAD_UNREAD ? { fontWeight: 'bold' } : '',
                  onclick: () => {
                    m.route.set('/forums/:tab/:mGroupId/:mMsgId', {
                      tab: m.route.param().tab,
                      mGroupId: v.attrs.id,
                      mMsgId: topThreads[key].mOrigMsgId,
                    });
                  },
                },
                [
                  m('td', topThreads[key].mMsgName),
                  m(
                    'td',
                    typeof topThreads[key].mPublishTs === 'object'
                      ? new Date(topThreads[key].mPublishTs.xint64 * 1000).toLocaleString()
                      : 'undefined'
                  ),
                  m(
                    'td',
                    rs.userList.userMap[topThreads[key].mAuthorId]
                      ? rs.userList.userMap[topThreads[key].mAuthorId]
                      : 'Unknown'
                  ),
                ]
              )
            )
          )
        )
      ),
    ],
  };
};

module.exports = {
  ForumView,
  ThreadView,
  createforum,
};
 
}); 
require.register("forums/my_forums", function(exports, require, module) { 
const m = require('mithril');
const util = require('forums/forums_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'My Forums')),
      m('.widget__body', [
        m(
          util.ForumTable,
          m('tbody', [
            v.attrs.list.map((forum) =>
              m(util.ForumSummary, {
                details: forum,
                category: 'MyForums',
              })
            ),
            v.attrs.list.map((forum) =>
              m(util.DisplayForumsFromList, {
                id: forum.mGroupId,
                category: 'MyForums',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("forums/other_forums", function(exports, require, module) { 
const m = require('mithril');

const Layout = () => {
  return {
    view: () => [m('.widget__heading', m('h3', 'Other Forums'))],
  };
};

module.exports = Layout();
 
}); 
require.register("forums/popular_forums", function(exports, require, module) { 
const m = require('mithril');
const util = require('forums/forums_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Popular Forums')),
      m('.widget__body', [
        m(
          util.ForumTable,
          m('tbody', [
            v.attrs.list.map((forum) =>
              m(util.ForumSummary, {
                details: forum,
                category: 'PopularForums',
              })
            ),
            v.attrs.list.map((forum) =>
              m(util.DisplayForumsFromList, {
                id: forum.mGroupId,
                category: 'PopularForums',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("forums/subscribed_forums", function(exports, require, module) { 
const m = require('mithril');
const util = require('forums/forums_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Subscribed Forums')),
      m('.widget__body', [
        m(
          util.ForumTable,
          m('tbody', [
            v.attrs.list.map((forum) =>
              m(util.ForumSummary, {
                details: forum,
                category: 'SubscribedForums',
              })
            ),
            v.attrs.list.map((forum) =>
              m(util.DisplayForumsFromList, {
                id: forum.mGroupId,
                category: 'SubscribedForums',
              })
            ),
          ])
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_attachment", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('mail/mail_util');

const Layout = () => {
  const files = [];
  let viewChanged = false;

  return {
    oninit: (v) => {
      v.attrs.list.forEach(async (element) => {
        const res = await rs.rsJsonApiRequest('/rsMsgs/getMessage', {
          msgId: element.msgId,
        });
        if (res.body.retval) {
          res.body.msg.files.forEach((element) => {
            files.push({ ...element, from: res.body.msg.from, ts: res.body.msg.ts });
          });
        }
      });
    },
    view: (v) => [
      m('.widget__heading', [
        m('h3', 'Attachments'),
        m('.view-toggle', [
          m(
            '.mail-view',
            {
              onclick: () => (viewChanged = false),
              style: { backgroundColor: viewChanged ? '#fff' : '#019DFF' },
            },
            m('i.fas.fa-mail-bulk')
          ),
          m(
            '.attachment-view',
            {
              onclick: () => (viewChanged = true),
              style: { backgroundColor: viewChanged ? '#019DFF' : '#fff' },
            },
            m('i.fas.fa-file')
          ),
        ]),
      ]),
      m('.widget__body', [
        viewChanged
          ? m(util.AttachmentSection, {
              files,
            })
          : m(
              util.Table,
              m(
                'tbody',
                v.attrs.list.map((msg) =>
                  m(util.MessageSummary, {
                    details: msg,
                    category: 'attachment',
                  })
                )
              )
            ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_compose", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');
const peopleUtil = require('people/people_util');

const Layout = () => {
  const Data = {
    allUsers: [],
    ownId: [],
    subject: '',
    identity: null,
    recipients: {
      to: {
        inputVal: '',
        inputList: [],
        sendList: [],
      },
      cc: {
        inputVal: '',
        inputList: [],
        sendList: [],
      },
      bcc: {
        inputVal: '',
        inputList: [],
        sendList: [],
      },
    },
  };
  async function loadMailUserDetails(msgType, senderId, recipientList) {
    Data.allUsers = await peopleUtil.sortUsers(rs.userList.users);
    if (msgType === 'reply') {
      Data.allUsers.forEach(async (user) => {
        if (user.mGroupId === (await senderId)) Data.recipients.to.sendList.push(user);
      });
    }
    await peopleUtil.ownIds(async (data) => {
      Data.ownId = await data;
      for (let i = 0; i < Data.ownId.length; i++) {
        if (Number(Data.ownId[i]) === 0) {
          Data.ownId.splice(i, 1); // workaround for id '0'
        }
      }
      if (msgType === 'reply') {
        Data.identity = Data.ownId.filter((id) =>
          Object.prototype.hasOwnProperty.call(recipientList, id)
        )[0];
      }
    });
  }
  async function loadDetails(attrs) {
    const { msgType, senderId, recipientList } = await attrs;
    await loadMailUserDetails(msgType, senderId, recipientList);

    Object.keys(Data.recipients).forEach((item) => {
      Data.recipients[item].inputList = Data.allUsers;
    });

    if (msgType === 'compose') {
      Data.identity = Data.ownId[0];
    }

    if (msgType === 'reply') {
      const { subject, replyMessage, timeStamp } = await attrs;
      const tmb = document.querySelector('#composerMailBody');
      const time = timeStamp.toLocaleTimeString('UTC', { hour: '2-digit', minute: '2-digit' });
      const dateLong = timeStamp.toLocaleDateString('UTC', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
      });
      const replyMessageHeader = `
        -----Original Message-----
        <br>
        <b>From: </b>
        <a href="retroshare://message?id=${senderId}">${rs.userList.userMap[senderId]}</a>
        <br>
        <b>To: </b>
        ${Object.keys(recipientList).map(
          (recip) => `
          <a href="retroshare://message?id=${recip}">
            ${rs.userList.userMap[recipientList[recip]._addr_string] || 'Unknown'},
          </a>
        `
        )}
        <br>
        <br>
        <b>Sent: </b>
        <span>${dateLong} ${time}</span>
        <br>
        <b>Subject: </b>
        <span>${subject}</span>
        <br>
        <br>
        <span>
          On ${timeStamp.toLocaleDateString()} ${time},
          <a href="retroshare://message?id=${senderId}">${rs.userList.userMap[senderId]}</a>
          wrote:
        </span>
      `;
      tmb.innerHTML = `
        <br>
        <br>
        <div>
          ${replyMessageHeader}
          <div class="original-message" style="margin-left: 20px;">
            ${replyMessage}
          </div>
        </div>
      `;
      Data.subject = subject.substring(0, 4) === 'Re: ' ? subject : `Re: ${subject}`;
    }
  }
  return {
    oninit: async (v) => await loadDetails(v.attrs),
    view: (v) => {
      // get recipientType from the function call to handle events for all recipient types
      function handleInput(e, recipientType) {
        Data.recipients[recipientType].inputVal = e.target.value;
        Data.recipients[recipientType].inputList = Data.allUsers.filter((item) =>
          item.mGroupName.toLowerCase().includes(e.target.value.toLowerCase())
        );
      }
      function handleClick(item, recipientType) {
        Data.recipients[recipientType].sendList.push(item);
        // reset current input values after a sender is selected
        Data.recipients[recipientType].inputVal = '';
        Data.recipients[recipientType].inputList = Data.allUsers;
      }
      function removeSelectedItem(recipient, recipientType) {
        Data.recipients[recipientType].sendList = Data.recipients[recipientType].sendList.filter(
          (item) => item.mGroupId !== recipient.mGroupId
        );
      }
      function sendMail() {
        const to = Data.recipients.to.sendList.map((toItem) => toItem.mGroupId);
        const cc = Data.recipients.cc.sendList.map((ccItem) => ccItem.mGroupId);
        const bcc = Data.recipients.bcc.sendList.map((bccItem) => bccItem.mGroupId);
        const { identity: from, subject } = Data;
        const mailBodyElement = document.querySelector('#composerMailBody');
        const mailBody = `<div>${mailBodyElement.innerHTML}</div>`;
        rs.rsJsonApiRequest('/rsMsgs/sendMail', { from, subject, mailBody, to, cc, bcc }).then(
          (res) => {
            if (res.body.retval) {
              Object.keys(Data.recipients).forEach((recipientType) => {
                Data.recipients[recipientType].sendList = [];
              });
              Data.subject = '';
              mailBodyElement.innerHTML = '';
              v.attrs.setShowCompose(false);
            }
            const success = res.body.retval === 1;
            widget.popupMessage(
              m('.widget', [
                m('.widget__heading', m('h3', success ? 'Success' : 'Error')),
                m('.widget__body', m('p', success ? 'Mail sent successfully' : res.body.errorMsg)),
              ])
            );
          }
        );
      }
      return m('.widget', [
        m('.widget__heading', m('h3', 'Compose a mail')),
        m('.widget__body.compose-mail', [
          m('.compose-mail__from', [
            m('label[for=idtags].bold', 'From: '),
            m(
              'select[id=idtags]',
              {
                value: Data.identity,
                onchange: (e) => {
                  Data.identity = Data.ownId[e.target.selectedIndex];
                },
              },
              Data.ownId &&
                Data.ownId.map((id) =>
                  m(
                    'option',
                    { value: id },
                    rs.userList.userMap[id]
                      ? rs.userList.userMap[id].toLocaleString() + ' (' + id.slice(0, 12) + '...)'
                      : 'No Signature'
                  )
                )
            ),
          ]),
          m('.compose-mail__recipients', [
            m('.compose-mail__recipients__container', [
              m('label.bold', 'To: '),
              m('.recipients', [
                Data.recipients.to.sendList.length > 0 &&
                  Data.recipients.to.sendList.map((recipient) =>
                    m('.recipients__selected', [
                      m('span', recipient.mGroupName),
                      m('i.fas.fa-times', {
                        onclick: () => removeSelectedItem(recipient, 'to'),
                      }),
                    ])
                  ),
                m('.recipients__input', [
                  m('input[type=text].recipients__input-field', {
                    value: Data.recipients.to.inputVal,
                    oninput: (e) => handleInput(e, 'to'),
                  }),
                  m('ul.recipients__input-list[autocomplete=off]', [
                    Data.recipients.to.inputList.length > 0
                      ? Data.recipients.to.inputList.map((item) =>
                          m('li', { onclick: () => handleClick(item, 'to') }, item.mGroupName)
                        )
                      : m('li', 'No Item'),
                  ]),
                ]),
              ]),
            ]),
            ['cc', 'bcc'].map((recipientType) =>
              m('.compose-mail__recipients__container', [
                m('label.bold', `${recipientType}: `),
                m('.recipients', [
                  Data.recipients[recipientType].sendList.length > 0 &&
                    Data.recipients[recipientType].sendList.map((recipient) =>
                      m('.recipients__selected', [
                        m('span', recipient.mGroupName),
                        m('i.fas.fa-times', {
                          onclick: () => removeSelectedItem(recipient, recipientType),
                        }),
                      ])
                    ),
                  m('.recipients__input', [
                    m('input[type=text].recipients__input-field', {
                      value: Data.recipients[recipientType].inputVal,
                      oninput: (e) => handleInput(e, recipientType),
                    }),
                    m('ul.recipients__input-list[autocomplete=off]', [
                      Data.recipients[recipientType].inputList.length > 0
                        ? Data.recipients[recipientType].inputList.map((item) =>
                            m(
                              'li',
                              { onclick: () => handleClick(item, recipientType) },
                              item.mGroupName
                            )
                          )
                        : m('li', 'No Item'),
                    ]),
                  ]),
                ]),
              ])
            ),
          ]),
          m('input.compose-mail__subject[type=text][placeholder=Subject]', {
            value: Data.subject,
            oninput: (e) => (Data.subject = e.target.value),
          }),
          m('.compose-mail__message', [
            m('.compose-mail__message-body[placeholder=Message][contenteditable]#composerMailBody'),
          ]),
          m('button.compose-mail__send-btn', { onclick: sendMail }, [
            m('span', 'Send Mail'),
            m('i.fas.fa-paper-plane'),
          ]),
        ]),
      ]);
    },
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_draftbox", function(exports, require, module) { 
const m = require('mithril');

const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Draft')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'drafts',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_important", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Important')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'important',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_inbox", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Inbox')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'inbox',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_later", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Later')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'later',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_outbox", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Outbox')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'outbox',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_personal", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Personal')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'personal',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_resolver", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('mail/mail_util');
const compose = require('mail/mail_compose');

const Messages = {
  all: [],
  inbox: [],
  sent: [],
  outbox: [],
  drafts: [],
  trash: [],
  starred: [],
  system: [],
  spam: [],
  attachment: [],
  important: [],
  work: [],
  personal: [],
  todo: [],
  later: [],
  load() {
    rs.rsJsonApiRequest('/rsMsgs/getMessageSummaries', { box: util.BOX_ALL }, (data) => {
      Messages.all = data.msgList;
      Messages.inbox = Messages.all.filter(
        (msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_INBOX
      );
      Messages.sent = Messages.all.filter(
        (msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_SENTBOX
      );
      Messages.outbox = Messages.all.filter(
        (msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_OUTBOX
      );
      Messages.drafts = Messages.all.filter(
        (msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_DRAFTBOX
      );
      Messages.trash = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_TRASH);
      Messages.starred = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_STAR);
      Messages.system = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_SYSTEM);
      Messages.spam = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_SPAM);

      Messages.attachment = Messages.all.filter((msg) => msg.count);

      // Messages.important = Messages.all.filter(
      //   (msg) => msg.msgflags & util.RS_MSGTAGTYPE_IMPORTANT
      // );
      // Messages.work = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_WORK);
      // Messages.personal = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_PERSONAL);
      // Messages.todo = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_TODO);
      // Messages.later = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_LATER);
    });
  },
};

const sections = {
  inbox: require('mail/mail_inbox'),
  outbox: require('mail/mail_outbox'),
  drafts: require('mail/mail_draftbox'),
  sent: require('mail/mail_sentbox'),
  trash: require('mail/mail_trashbox'),
};
const sectionsquickview = {
  starred: require('mail/mail_starred'),
  system: require('mail/mail_system'),
  spam: require('mail/mail_spam'),
  attachment: require('mail/mail_attachment'),
  important: require('mail/mail_important'),
  work: require('mail/mail_work'),
  todo: require('mail/mail_todo'),
  later: require('mail/mail_later'),
  personal: require('mail/mail_personal'),
};
const tagselect = {
  showval: 'Tags',
  opts: ['Tags', 'Important', 'Work', 'Personal'],
};
const Layout = () => {
  let showCompose = false;
  // setFunction like react to show/hide popup
  function setShowCompose(bool) {
    showCompose = bool;
  }
  return {
    oninit: () => Messages.load(),
    view: (vnode) => {
      const sectionsSize = {
        inbox: Messages.inbox.length,
        outbox: Messages.outbox.length,
        drafts: Messages.drafts.length,
        sent: Messages.sent.length,
        trash: Messages.trash.length,
      };
      const sectionsQuickviewSize = {
        starred: Messages.starred.length,
        system: Messages.system.length,
        spam: Messages.spam.length,
        attachment: Messages.attachment.length,
        important: Messages.important.length,
        work: Messages.work.length,
        todo: Messages.todo.length,
        later: Messages.later.length,
        personal: Messages.personal.length,
      };

      return [
        m('.side-bar', [
          m('button.mail-compose-btn', { onclick: () => setShowCompose(true) }, 'Compose'),
          m(util.Sidebar, {
            tabs: Object.keys(sections),
            size: sectionsSize,
            baseRoute: '/mail/',
          }),
          m(util.SidebarQuickView, {
            tabs: Object.keys(sectionsquickview),
            size: sectionsQuickviewSize,
            baseRoute: '/mail/',
          }),
        ]),
        m(
          '.node-panel',
          m('.widget', [
            m.route.get().split('/').length < 4 &&
              m('.top-heading', [
                m(
                  'select.mail-tag',
                  {
                    value: tagselect.showval,
                    onchange: (e) => (tagselect.showval = tagselect.opts[e.target.selectedIndex]),
                  },
                  [tagselect.opts.map((opt) => m('option', { value: opt }, opt.toLocaleString()))]
                ),
                m(util.SearchBar, { list: {} }),
              ]),
            vnode.children,
          ])
        ),
        m(
          '.composePopupOverlay#mailComposerPopup',
          { style: { display: showCompose ? 'block' : 'none' } },
          m(
            '.composePopup',
            m(compose, { msgType: 'compose', setShowCompose }),
            m('button.red.close-btn', { onclick: () => setShowCompose(false) }, m('i.fas.fa-times'))
          )
        ),
      ];
    },
  };
};

module.exports = {
  view: ({ attrs, attrs: { tab, msgId } }) => {
    // TODO: utilize multiple routing params
    if (Object.prototype.hasOwnProperty.call(attrs, 'msgId')) {
      return m(Layout, m(util.MessageView, { msgId }));
    }
    return m(
      Layout,
      m(sections[tab] || sectionsquickview[tab], {
        list: Messages[tab].sort((msgA, msgB) => {
          const msgADate = new Date(msgA.ts.xint64 * 1000);
          const msgBDate = new Date(msgB.ts.xint64 * 1000);
          return msgADate < msgBDate;
        }),
      })
    );
  },
};
 
}); 
require.register("mail/mail_sentbox", function(exports, require, module) { 
const m = require('mithril');

const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Sent')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'sent',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_spam", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Spam')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'spam',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_starred", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Starred')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'starred',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_system", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'System')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'system',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_todo", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Todo')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'todo',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_trashbox", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Trash')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'trash',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("mail/mail_util", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const util = require('files/files_util');
const widget = require('widgets');
const peopleUtil = require('people/people_util');
const compose = require('mail/mail_compose');

// rsmsgs.h
const RS_MSG_BOXMASK = 0x000f;

const RS_MSG_INBOX = 0x00;
const RS_MSG_SENTBOX = 0x01;
const RS_MSG_OUTBOX = 0x03;
const RS_MSG_DRAFTBOX = 0x05;
const RS_MSG_TRASH = 0x000020;
const RS_MSG_NEW = 0x10;
const RS_MSG_UNREAD_BY_USER = 0x40;
const RS_MSG_STAR = 0x200;
const RS_MSG_SPAM = 0x040000;

const RS_MSGTAGTYPE_IMPORTANT = 1;
const RS_MSGTAGTYPE_WORK = 2;
const RS_MSGTAGTYPE_PERSONAL = 3;
const RS_MSGTAGTYPE_TODO = 4;
const RS_MSGTAGTYPE_LATER = 5;
const RS_MSG_USER_REQUEST = 0x000400;
const RS_MSG_FRIEND_RECOMMENDATION = 0x000800;
const RS_MSG_PUBLISH_KEY = 0x020000;
const RS_MSG_SYSTEM = RS_MSG_USER_REQUEST | RS_MSG_FRIEND_RECOMMENDATION | RS_MSG_PUBLISH_KEY;

const MSG_ADDRESS_MODE_TO = 0x01;
const MSG_ADDRESS_MODE_CC = 0x02;
const MSG_ADDRESS_MODE_BCC = 0x03;

const BOX_ALL = 0x06;

// Utility functions
const humanReadableSize = (fileSize) => {
  return fileSize / 1024 > 1024
    ? fileSize / 1024 / 1024 > 1024
      ? (fileSize / 1024 / 1024 / 1024).toFixed(2) + ' GB'
      : (fileSize / 1024 / 1024).toFixed(2) + ' MB'
    : (fileSize / 1024).toFixed(2) + ' KB';
};

// Layouts
const MessageSummary = () => {
  let details = {};
  let files;
  let isStarred = false;
  let msgStatus = '';
  let fromUserInfo;
  function starMessage(e) {
    isStarred = !isStarred;
    rs.rsJsonApiRequest('/rsMsgs/MessageStar', { msgId: details.msgId, mark: isStarred });
    // Stop event bubbling, both functions for supporting IE & FF
    e.stopImmediatePropagation();
    e.preventDefault();
  }
  return {
    oninit: (v) => {
      rs.rsJsonApiRequest('/rsMsgs/getMessage', {
        msgId: v.attrs.details.msgId,
      })
        .then((res) => {
          if (res.body.retval) {
            details = res.body.msg;
            files = details.files;
            isStarred = (details.msgflags & 0xf00) === RS_MSG_STAR;
            const flag = details.msgflags & 0xf0;
            msgStatus = flag === RS_MSG_NEW || flag === RS_MSG_UNREAD_BY_USER ? 'unread' : 'read';
          }
        })
        .then(() => {
          if (details?.from?._addr_string) {
            rs.rsJsonApiRequest(
              '/rsIdentity/getIdDetails',
              { id: details.from._addr_string },
              (data) => (fromUserInfo = data.details)
            );
          }
        });
    },
    view: (v) =>
      m(
        'tr.msgbody',
        {
          key: details.msgId,
          class: msgStatus,
          onclick: () =>
            m.route.set('/mail/:tab/:msgId', { tab: v.attrs.category, msgId: details.msgId }),
        },
        [
          m(
            'td',
            m(`input.star-check[type=checkbox][id=msg-${details.msgId}]`, { checked: isStarred }),
            // Use label with  [for] to manipulate hidden checkbox
            m(
              `label.star-check[for=msg-${details.msgId}]`,
              {
                onclick: starMessage,
                class: (details.msgflags & 0xf00) === RS_MSG_STAR ? 'starred' : 'unstarred',
              },
              m('i.fas.fa-star')
            )
          ),
          files && m('td', files.length),
          m('td', details.title),
          m(
            'td',
            fromUserInfo && Number(fromUserInfo.mId) !== 0 ? fromUserInfo.mNickname : '[Unknown]'
          ),
          m('td', new Date(details.ts * 1000).toLocaleString()),
        ]
      ),
  };
};

const AttachmentSection = () => {
  function handleAttachmentDownload(item) {
    const { fname: fileName, hash, size: xstr64 } = item;
    const flags = util.RS_FILE_REQ_ANONYMOUS_ROUTING;
    rs.rsJsonApiRequest(
      '/rsFiles/FileRequest',
      { fileName, hash, flags, size: { xstr64 } },
      (status) =>
        widget.popupMessage([
          m('i.fas.fa-file-medical'),
          m('h3', `File is ${status.retval ? 'being' : 'already'} downloaded!`),
        ])
    ).catch((error) => console.log('error: ', error));
  }
  return {
    view: (v) =>
      m('table.attachment-container', [
        m('tr.attachment-header', [
          m('th', 'File Name'),
          m('th', 'From'),
          m('th', 'Size'),
          m('th', 'Date'),
          m('th', 'Download'),
        ]),
        m(
          'tbody',
          v.attrs.files.map((file) =>
            m('tr.attachment', [
              m('td.attachment__name', [m('i.fas.fa-file'), m('span', file.fname)]),
              m('td.attachment__from', rs.userList.userMap[file.from._addr_string] || '[Unknown]'),
              m('td.attachment__size', humanReadableSize(file.size.xint64)),
              m('td.attachment__date', new Date(file.ts * 1000).toLocaleString()),
              m('td', m('button', { onclick: () => handleAttachmentDownload(file) }, 'Download')),
            ])
          )
        ),
      ]),
  };
};

const MessageView = () => {
  let showCompose = false;
  // setFunction like react to show/hide popup
  function setShowCompose(bool) {
    showCompose = bool;
  }
  const MailData = {
    msgId: '',
    message: '',
    subject: '',
    sender: {},
    recipients: [],
    toList: {},
    ccList: {},
    bccList: {},
    timeStamp: '',
    files: [],
  };
  function deleteMail() {
    rs.rsJsonApiRequest('/rsMsgs/MessageToTrash', { msgId: MailData.msgId, bTrash: true });
    rs.rsJsonApiRequest('/rsMsgs/MessageDelete', { msgId: MailData.msgId }).then((res) => {
      widget.popupMessage(
        m('.widget', [
          m('.widget__heading', m('h3', res.body.retval ? 'Success' : 'Error')),
          m('.widget__body', m('p', res.body.retval ? 'Mail Deleted.' : 'Error in Deleting.')),
        ])
      );
      m.route.set('/mail/:tab', { tab: m.route.param().tab });
    });
  }
  function confirmMailDelete() {
    widget.popupMessage([
      m('p', 'Are you sure you want to delete this mail?'),
      m('button', { onclick: deleteMail }, 'Delete'),
    ]);
  }

  return {
    oninit: async (v) => {
      const res = await rs.rsJsonApiRequest('/rsMsgs/getMessage', {
        msgId: v.attrs.msgId,
      });
      if (res.body.retval) {
        const msgDetails = await res.body.msg;
        msgDetails.files.forEach((element) =>
          MailData.files.push({ ...element, from: msgDetails.from, ts: msgDetails.ts })
        );
        // regex to detect html tags, better regex?  /<[a-z][\s\S]*>/gi
        MailData.message = /<\/*[a-z][^>]+?>/gi.test(msgDetails.msg)
          ? msgDetails.msg
          : `<p style="white-space: pre">${msgDetails.msg}</p>`;
        document.querySelector('#msgView').innerHTML = MailData.message;
        MailData.msgId = msgDetails.msgId;
        MailData.sender = msgDetails.from;
        MailData.subject = msgDetails.title;
        MailData.timeStamp = msgDetails.ts;
        MailData.recipients = msgDetails.destinations;
        MailData?.recipients?.forEach((destDetail) => {
          const { _addr_string: addrString, _mode: mode } = destDetail; // destructuring + renaming
          if (mode === MSG_ADDRESS_MODE_TO && !MailData.toList[addrString]) {
            MailData.toList[addrString] = destDetail;
          } else if (mode === MSG_ADDRESS_MODE_CC && !MailData.ccList[addrString]) {
            MailData.ccList[addrString] = destDetail;
          } else if (mode === MSG_ADDRESS_MODE_BCC && !MailData.bccList[addrString]) {
            MailData.bccList[addrString] = destDetail;
          }
        });
        rs.rsJsonApiRequest(
          '/rsIdentity/getIdDetails',
          { id: MailData?.sender?._addr_string },
          (data) => (MailData.avatar = data?.details?.mAvatar)
        );
      }
    },
    view: () =>
      m(
        '.msg-view',
        [
          m('.msg-view-nav', [
            m(
              'a[title=Back]',
              { onclick: () => m.route.set('/mail/:tab', { tab: m.route.param().tab }) },
              m('i.fas.fa-arrow-left')
            ),
            m('.msg-view-nav__action', [
              m('button', { onclick: () => setShowCompose(true) }, 'Reply'),
              m('button', 'Reply All'),
              m('button', 'Forward'),
              m('button', { onclick: confirmMailDelete }, 'Delete'),
            ]),
          ]),
          m('.msg-view__header', [
            m('h3', MailData.subject),
            m('.msg-details', [
              MailData.sender &&
                m(peopleUtil.UserAvatar, {
                  avatar: MailData.avatar,
                  firstLetter: rs.userList.userMap[MailData.sender._addr_string]
                    ? rs.userList.userMap[MailData.sender._addr_string].slice(0, 1).toUpperCase()
                    : '',
                }),
              m('.msg-details__info', [
                MailData.sender &&
                  m('.msg-details__info-item', [
                    m('b', 'From: '),
                    rs.userList.userMap[MailData.sender._addr_string] || 'Unknown',
                  ]),
                m('.msg-details__info-item', [
                  m('b', 'To: '),
                  MailData.toList && Object.keys(MailData.toList).length > 0
                    ? [
                        m('#truncate.truncated-view', [
                          Object.keys(MailData.toList).map((key, index) =>
                            m('span', { key: index }, `${rs.userList.userMap[key] || 'Unknown'}, `)
                          ),
                        ]),
                        m(
                          'button.toggle-truncate',
                          {
                            style: {
                              display: Object.keys(MailData.toList).length > 10 ? 'block' : 'none',
                            },
                            onclick: () => {
                              document
                                .querySelector('#truncate')
                                .classList.toggle('truncated-view');
                            },
                          },
                          '...'
                        ),
                      ]
                    : m('span', 'Unknown'),
                ]),
                MailData.ccList &&
                  Object.keys(MailData.ccList).length > 0 &&
                  m('.msg-details__info-item', [
                    m('b', 'Cc: '),
                    Object.keys(MailData.ccList).map((key, index) =>
                      m('p', { key: index }, `${rs.userList.userMap[key]}, `)
                    ),
                  ]),
                MailData.bccList &&
                  Object.keys(MailData.bccList).length > 0 &&
                  m('.msg-details__info-item', [
                    m('b', 'Bcc: '),
                    Object.keys(MailData.bccList).map((key, index) =>
                      m('p', { key: index }, `${rs.userList.userMap[key]}, `)
                    ),
                  ]),
              ]),
            ]),
          ]),
          m('.msg-view__body', m('#msgView')),
          MailData.files.length > 0 &&
            m('.msg-view__attachment', [
              m('h3', 'Attachments'),
              m('.msg-view__attachment-items', m(AttachmentSection, { files: MailData.files })),
            ]),
        ],
        m(
          '.composePopupOverlay#mailComposerPopup',
          { style: { display: showCompose ? 'block' : 'none' } },
          m(
            '.composePopup',
            MailData.sender._addr_string
              ? m(compose, {
                  msgType: 'reply',
                  senderId: MailData.sender._addr_string,
                  recipientList: MailData.toList,
                  subject: MailData.subject,
                  replyMessage: MailData.message,
                  timeStamp: new Date(MailData.timeStamp * 1000),
                  setShowCompose,
                })
              : m('.widget', m('.widget__heading', m('h3', 'Sender is not known'))),
            m('button.red.close-btn', { onclick: () => setShowCompose(false) }, m('i.fas.fa-times'))
          )
        )
      ),
  };
};

const Table = () => {
  return {
    view: (v) =>
      m('table.mails', [
        m('tr', [
          m('th[title=starred]', m('i.fas.fa-star')),
          m('th[title=attachments]', m('i.fas.fa-paperclip')),
          m('th', 'Subject'),
          m('th', 'From'),
          m('th', 'Date'),
        ]),
        v.children,
      ]),
  };
};

const SearchBar = () => {
  let searchString = '';
  return {
    view: ({ attrs: { list } }) =>
      m('input[type=text][placeholder=Search Subject].searchbar', {
        value: searchString,
        oninput: (e) => {
          searchString = e.target.value.toLowerCase();
          for (const hash in list) {
            list[hash].isSearched = list[hash].fname.toLowerCase().indexOf(searchString) > -1;
          }
        },
      }),
  };
};

const activeSideLink = {
  sideactive: 0,
  quicksideactive: -1,
};

const Sidebar = () => {
  return {
    view: ({ attrs: { tabs, baseRoute, size } }) =>
      m(
        '.sidebar',
        tabs.map((panelName, index) =>
          m(
            m.route.Link,
            {
              class: index === activeSideLink.sideactive ? 'selected-sidebar-link' : '',
              onclick: () => {
                activeSideLink.sideactive = index;
                activeSideLink.quicksideactive = -1;
              },
              href: baseRoute + panelName,
            },
            size[panelName] > 0 ? `${panelName} (${size[panelName]})` : panelName
          )
        )
      ),
  };
};

const SidebarQuickView = () => {
  // for the Mail tab, to be moved later.
  return {
    view: ({ attrs: { tabs, baseRoute, size } }) =>
      m(
        '.sidebarquickview',
        m('h6.bold', 'Quick View'),
        tabs.map((panelName, index) =>
          m(
            m.route.Link,
            {
              class:
                index === activeSideLink.quicksideactive ? 'selected-sidebarquickview-link' : '',
              onclick: () => {
                activeSideLink.quicksideactive = index;
                activeSideLink.sideactive = -1;
              },
              href: baseRoute + panelName,
            },
            size[panelName] > 0 ? `${panelName} (${size[panelName]})` : panelName
          )
        )
      ),
  };
};

module.exports = {
  MessageSummary,
  MessageView,
  AttachmentSection,
  Table,
  SearchBar,
  Sidebar,
  SidebarQuickView,
  RS_MSG_BOXMASK,
  RS_MSG_INBOX,
  RS_MSG_SENTBOX,
  RS_MSG_OUTBOX,
  RS_MSG_DRAFTBOX,
  RS_MSG_NEW,
  RS_MSG_UNREAD_BY_USER,
  RS_MSG_STAR,
  RS_MSG_TRASH,
  RS_MSG_SYSTEM,
  RS_MSG_SPAM,
  RS_MSGTAGTYPE_IMPORTANT,
  RS_MSGTAGTYPE_LATER,
  RS_MSGTAGTYPE_PERSONAL,
  RS_MSGTAGTYPE_TODO,
  RS_MSGTAGTYPE_WORK,
  BOX_ALL,
};
 
}); 
require.register("mail/mail_work", function(exports, require, module) { 
const m = require('mithril');
const util = require('mail/mail_util');

const Layout = () => {
  return {
    view: (v) => [
      m('.widget__heading', m('h3', 'Work')),
      m('.widget__body', [
        m(
          util.Table,
          m(
            'tbody',
            v.attrs.list.map((msg) =>
              m(util.MessageSummary, {
                details: msg,
                category: 'work',
              })
            )
          )
        ),
      ]),
    ],
  };
};

module.exports = Layout;
 
}); 
require.register("network/network", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');
const Data = require('network/network_data');

const ConfirmRemove = () => {
  return {
    view: (vnode) => [
      m('h3', 'Remove Friend'),
      m('hr'),
      m('p', 'Are you sure you want to end connections with this node?'),
      m(
        'button',
        {
          onclick: () => {
            rs.rsJsonApiRequest('/rsPeers/removeFriend', {
              pgpId: vnode.attrs.gpg_id,
            });
            m.redraw();
          },
        },
        'Confirm'
      ),
    ],
  };
};

const Locations = () => {
  return {
    view: (v) => [
      m('h4', 'Locations'),
      v.attrs.locations.map((loc) =>
        m('.location', [
          m('i.fas.fa-user-tag', { style: 'margin-top:3px' }),
          m('span', { style: 'margin-top:1px' }, loc.name),
          m('p', 'ID :'),
          m('p', loc.id),
          m('p', 'Last contacted :'),
          m('p', new Date(loc.lastSeen * 1000).toDateString()),
          m('p', 'Online :'),
          m('i.fas', {
            class: loc.isOnline ? 'fa-check-circle' : 'fa-times-circle',
          }),
          m(
            'button.red',
            {
              onclick: () =>
                widget.popupMessage(
                  m(ConfirmRemove, {
                    gpg: loc.gpg_id,
                  })
                ),
            },
            'Remove node'
          ),
        ])
      ),
    ],
  };
};

const Friend = () => {
  return {
    isExpanded: false,

    view: (vnode) =>
      m(
        '.friend',
        {
          key: vnode.attrs.id,
          class: Data.gpgDetails[vnode.attrs.id].isSearched ? '' : 'hidden',
        },
        [
          m('i.fas.fa-angle-right', {
            class: 'fa-rotate-' + (vnode.state.isExpanded ? '90' : '0'),
            style: 'margin-top:12px',
            onclick: () => (vnode.state.isExpanded = !vnode.state.isExpanded),
          }),
          m('.brief-info', { class: Data.gpgDetails[vnode.attrs.id].isOnline ? 'online' : '' }, [
            m('i.fas.fa-2x.fa-user-circle'),
            m('span', Data.gpgDetails[vnode.attrs.id].name),
          ]),
          m(
            '.details',
            {
              style: 'display:' + (vnode.state.isExpanded ? 'block' : 'none'),
            },
            [
              m(Locations, {
                locations: Data.gpgDetails[vnode.attrs.id].locations,
              }),
            ]
          ),
        ]
      ),
  };
};

const SearchBar = () => {
  let searchString = '';
  return {
    view: () =>
      m('input.searchbar', {
        type: 'text',
        placeholder: 'search',
        value: searchString,
        oninput: (e) => {
          searchString = e.target.value.toLowerCase();
          for (const id in Data.gpgDetails) {
            if (Data.gpgDetails[id].name.toLowerCase().indexOf(searchString) > -1) {
              Data.gpgDetails[id].isSearched = true;
            } else {
              Data.gpgDetails[id].isSearched = false;
            }
          }
        },
      }),
  };
};

const FriendsList = () => {
  return {
    oninit: () => {
      Data.refreshGpgDetails();
    },
    view: () =>
      m('.widget', [
        m('.widget__heading', [m('h3', 'Friend nodes'), m(SearchBar)]),
        m('.widget__body', [
          Object.entries(Data.gpgDetails)
            .sort((a, b) => {
              return a[1].isOnline === b[1].isOnline ? 0 : a[1].isOnline ? -1 : 1;
            })
            .map((item) => {
              const id = item[0];
              return m(Friend, { id });
            }),
        ]),
      ]),
  };
};

const Layout = () => {
  return {
    view: () => m('.node-panel', m(FriendsList)),
  };
};

module.exports = Layout;
 
}); 
require.register("network/network_data", function(exports, require, module) { 
const rs = require('rswebui');

async function refreshIds() {
  let sslIds = [];
  await rs.rsJsonApiRequest('/rsPeers/getFriendList', {}, (data) => (sslIds = data.sslIds));
  return sslIds;
}

async function loadSslDetails() {
  const sslDetails = [];
  const sslIds = await refreshIds();
  await Promise.all(
    sslIds.map((sslId) =>
      rs.rsJsonApiRequest('/rsPeers/getPeerDetails', { sslId }, (data) => sslDetails.push(data.det))
    )
  );
  return sslDetails;
}

const Data = {
  gpgDetails: {},
};
Data.refreshGpgDetails = async function () {
  const details = {};
  const sslDetails = await loadSslDetails();
  await Promise.all(
    sslDetails.map((data) => {
      let isOnline = false;
      return rs
        .rsJsonApiRequest(
          '/rsPeers/isOnline',
          { sslId: data.id },
          (stat) => (isOnline = stat.retval)
        )
        .then(() => {
          const loc = {
            name: data.location,
            id: data.id,
            lastSeen: data.lastConnect,
            isOnline,
            gpg_id: data.gpg_id,
          };

          if (details[data.gpg_id] === undefined) {
            details[data.gpg_id] = {
              name: data.name,
              isSearched: true,
              isOnline,
              locations: [loc],
            };
          } else {
            details[data.gpg_id].locations.push(loc);
          }
          details[data.gpg_id].isOnline = details[data.gpg_id].isOnline || isOnline;
        });
    })
  );
  Data.gpgDetails = details;
};
module.exports = Data;
 
}); 
require.register("people/people", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');

const peopleUtil = require('people/people_util');

const AllContacts = () => {
  const list = peopleUtil.sortUsers(rs.userList.users);
  return {
    view: () => {
      return m('.widget', [
        m('.widget__heading', [
          m('h3', 'Contacts', m('span.counter', list.length)),
          m(peopleUtil.SearchBar),
        ]),
        m('.widget__body', [list.map((id) => m(peopleUtil.regularcontactInfo, { id }))]),
      ]);
    },
  };
};

module.exports = {
  view: () => {
    return m(AllContacts);
  },
};
 
}); 
require.register("people/people_ownids", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');
const peopleUtil = require('people/people_util');

const SignedIdentiy = () => {
  let passphase = '';

  return {
    view: (v) => [
      m('i.fas.fa-user-edit'),
      m('h3', 'Enter your passpharse'),
      m('hr'),

      m('input[type=password][placeholder=Passpharse]', {
        style: 'margin-top:50px;width:80%',
        oninput: (e) => {
          passphase = e.target.value;
        },
      }),
      m(
        'button',
        {
          style: 'margin-top:160px;',
          onclick: () => {
            rs.rsJsonApiRequest('/rsIdentity/getOwnSignedIds', {}, (owns) => {
              console.log(owns.ids[0]);
              console.log(v.attrs.name);

              owns.ids.length > 0
                ? rs.rsJsonApiRequest(
                    '/rsIdentity/createIdentity',
                    {
                      id: owns.ids[0],
                      name: v.attrs.name,
                      pseudonimous: false,
                      pgpPassword: passphase,
                    },
                    (data) => {
                      const message = data.retval
                        ? 'Successfully created identity.'
                        : 'An error occured while creating identity.';
                      console.log(message);
                      widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]);
                    }
                  )
                : widget.popupMessage([
                    m('h3', 'Create new Identity'),
                    m('hr'),
                    'An error occured while creating identity.',
                  ]);
            });
          },
        },
        'Enter'
      ),
    ],
  };
};
const CreateIdentity = () => {
  // TODO: set user avatar
  let name = '',
    pseudonimous = false;
  return {
    view: (v) => [
      m('i.fas.fa-user-plus'),
      m('h3', 'Create new Identity'),
      m('hr'),
      m('input[type=text][placeholder=Name]', {
        value: name,
        oninput: (e) => (name = e.target.value),
      }),
      m(
        'div',
        {
          style: 'display:inline; margin-left:5px;',
        },
        [
          'Type:',
          m(
            'select',
            {
              value: pseudonimous,
              style: 'border:1px solid black',
              oninput: (e) => {
                pseudonimous = e.target.value === 'true';
                console.log(pseudonimous);
              },
            },
            [
              m('option[value=false][selected]', 'Linked to your Profile'),
              m('option[value=true]', 'Pseudonymous'),
            ]
          ),
        ]
      ),
      m('br'),

      m(
        'p',
        'You can have one or more identities. ' +
          'They are used when you chat in lobbies, ' +
          'forums and channel comments. ' +
          'They act as the destination for distant chat and ' +
          'the Retroshare distant mail system.'
      ),
      m(
        'button',
        {
          onclick: () => {
            !pseudonimous
              ? widget.popupMessage(m(SignedIdentiy, { name }))
              : rs.rsJsonApiRequest(
                  '/rsIdentity/createIdentity',
                  {
                    name,
                    pseudonimous,
                  },
                  (data) => {
                    const message = data.retval
                      ? 'Successfully created identity.'
                      : 'An error occured while creating identity.';
                    widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]);
                  }
                );
          },
        },
        'Create'
      ),
    ],
  };
};

const SignedEditIdentity = () => {
  let passphase = '';
  return {
    view: (v) => [
      m('i.fas.fa-user-edit'),
      m('h3', 'Enter your passpharse'),
      m('hr'),

      m('input[type=password][placeholder=Passpharse]', {
        style: 'margin-top:50px;width:80%',
        oninput: (e) => {
          passphase = e.target.value;
        },
      }),
      m(
        'button',
        {
          style: 'margin-top:160px;',
          onclick: () =>
            rs.rsJsonApiRequest(
              '/rsIdentity/updateIdentity',
              {
                id: v.attrs.details.mId,
                name: v.attrs.name,
                pseudonimous: false,
                pgpPassword: passphase,
              },
              (data) => {
                const message = data.retval
                  ? 'Successfully created identity.'
                  : 'An error occured while creating identity.';
                widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]);
              }
            ),
        },
        'Enter'
      ),
    ],
  };
};

const EditIdentity = () => {
  let name = '';
  return {
    view: (v) => [
      m('i.fas.fa-user-edit'),
      m('h3', 'Edit Identity'),
      m('hr'),
      m('input[type=text][placeholder=Name]', {
        value: name,
        oninput: (e) => {
          name = e.target.value;
        },
      }),
      m('canvas'),
      m(
        'button',
        {
          onclick: () => {
            !peopleUtil.checksudo(v.attrs.details.mPgpId)
              ? widget.popupMessage([
                  m(SignedEditIdentity, {
                    name,
                    details: v.attrs.details,
                  }),
                ])
              : rs.rsJsonApiRequest(
                  '/rsIdentity/updateIdentity',
                  {
                    id: v.attrs.details.mId,

                    name,

                    // avatar: v.attrs.details.mAvatar.mData.base64,
                    pseudonimous: true,
                  },
                  (data) => {
                    const message = data.retval
                      ? 'Successfully Updated identity.'
                      : 'An error occured while updating  identity.';
                    widget.popupMessage([m('h3', 'Update Identity'), m('hr'), message]);
                  }
                );
          },
        },
        'Save'
      ),
    ],
  };
};

const DeleteIdentity = () => {
  return {
    view: (v) => [
      m('i.fas.fa-user-times'),
      m('h3', 'Delete Identity: ' + v.attrs.name),
      m('hr'),
      m('p', 'Are you sure you want to delete this Identity? It cannot be restore'),
      m(
        'button',
        {
          onclick: () =>
            rs.rsJsonApiRequest(
              '/rsIdentity/deleteIdentity',
              {
                id: v.attrs.id,
              },
              () => {
                widget.popupMessage([
                  m('i.fas.fa-user-edit'),
                  m('h3', 'Delete Identity: ' + v.attrs.name),
                  m('hr'),
                  m('p', 'Identity Deleted successfuly.'),
                ]);
              }
            ),
        },
        'Confirm'
      ),
    ],
  };
};

const Identity = () => {
  let details = {};

  return {
    oninit: (v) =>
      rs.rsJsonApiRequest(
        '/rsIdentity/getIdDetails',
        {
          id: v.attrs.id,
        },
        (data) => {
          details = data.details;
        }
      ),
    view: (v) =>
      m(
        '.identity',
        {
          key: details.mId,
        },
        [
          m('h4', details.mNickname),
          details.mNickname &&
            m(peopleUtil.UserAvatar, {
              avatar: details.mAvatar,
              firstLetter: details.mNickname.slice(0, 1).toUpperCase(),
            }),
          m('.details', [
            m('p', 'ID:'),
            m('p', details.mId),
            m('p', 'Type:'),
            m('p', details.mFlags === 14 ? 'Signed ID' : 'Anonymous ID'),
            m('p', 'Owner node ID:'),
            m('p', details.mPgpId),
            m('p', 'Created on:'),
            m(
              'p',
              typeof details.mPublishTS === 'object'
                ? new Date(details.mPublishTS.xint64 * 1000).toLocaleString()
                : 'undefiend'
            ),
            m('p', 'Last used:'),
            m(
              'p',
              typeof details.mLastUsageTS === 'object'
                ? new Date(details.mLastUsageTS.xint64 * 1000).toLocaleDateString()
                : 'undefiend'
            ),
          ]),
          m(
            'button',
            {
              onclick: () =>
                widget.popupMessage(
                  m(EditIdentity, {
                    details,
                  })
                ),
            },
            'Edit'
          ),
          m(
            'button.red',
            {
              onclick: () =>
                widget.popupMessage(
                  m(DeleteIdentity, {
                    id: details.mId,
                    name: details.mNickname,
                  })
                ),
            },
            'Delete'
          ),
        ]
      ),
  };
};

const Layout = () => {
  let ownIds = [];
  return {
    oninit: () => peopleUtil.ownIds((data) => (ownIds = data)),
    view: () =>
      m('.widget', [
        m('.widget__heading', [
          m('h3', 'Own Identities', m('span.counter', ownIds.length)),
          m(
            'button',
            {
              onclick: () => widget.popupMessage(m(CreateIdentity)),
            },
            'New Identity'
          ),
        ]),
        m('.widget__body', [ownIds.map((id) => m(Identity, { id }))]),
      ]),
  };
};

module.exports = Layout;
 
}); 
require.register("people/people_own_contacts", function(exports, require, module) { 
const m = require('mithril');
const rs = require('rswebui');
const peopleUtil = require('people/people_util');

const MyContacts = () => {
  const list = peopleUtil.contactlist(rs.userList.users);
  return {
    view: () => {
      return m('.widget', [
        m('.widget__heading', [
          m('h3', 'MyContacts', m('span.counter', list.length)),
          m(peopleUtil.SearchBar),
        ]),
        m('.widget__body', [list.map((id) => m(peopleUtil.regularcontactInfo, { id }))]),
      ]);
    },
  };
};

module.exports = {
  view: () => {
    return m(MyContacts);
  },
};
 
}); 
require.register("people/people_resolver", function(exports, require, module) { 
const m = require('mithril');
const widget = require('widgets');

const sections = {
  OwnIdentity: require('people/people_ownids'),
  MyContacts: require('people/people_own_contacts'),
  All: require('people/people'),
};

const Layout = {
  view: (vnode) => [
    m(widget.Sidebar, {
      tabs: Object.keys(sections),
      baseRoute: '/people/',
    }),
    m('.node-panel .', vnode.children),
  ],
};

module.exports = {
  view: (vnode) => {
    const tab = vnode.attrs.tab;
    return m(Layout, m(sections[tab]));
  },
};
 
}); 
require.register("people/people_util", function(exports, require, module) { 
const rs = require('rswebui');
const m = require('mithril');

function checksudo(id) {
  return id === '0000000000000000';
}

const UserAvatar = () => ({
  view: (v) => {
    const imageURI = v.attrs.avatar;
    return imageURI === undefined || imageURI.mData.base64 === ''
      ? m(
          'div.defaultAvatar',
          {
            // image isn't getting loaded
            // ? m('img.defaultAvatar', {
            //   src: '../data/user.png'
            // })
          },
          m('p', v.attrs.firstLetter)
        )
      : m('img.avatar', {
          src: 'data:image/png;base64,' + imageURI.mData.base64,
        });
  },
});

function contactlist(list) {
  const result = [];
  if (list !== undefined) {
    list.map((id) => {
      id.isSearched = true;
      rs.rsJsonApiRequest('/rsIdentity/isARegularContact', { id: id.mGroupId }, (data) => {
        if (data.retval) result.push(id);
        console.log(data);
      });
    });
  }
  return result;
}

function sortUsers(list) {
  if (list !== undefined) {
    const result = [];
    list.map((id) => {
      id.isSearched = true;
      result.push(id);
    });

    result.sort((a, b) => a.mGroupName.localeCompare(b.mGroupName));
    return result;
  }
  return list;
}

function sortIds(list) {
  if (list !== undefined) {
    const result = [...list];

    result.sort((a, b) => rs.userList.username(a).localeCompare(rs.userList.username(b)));
    return result;
  }
  return list;
}

async function ownIds(consumer = () => {}, onlySigned = false) {
  await rs.rsJsonApiRequest('/rsIdentity/getOwnSignedIds', {}, (owns) => {
    if (onlySigned) {
      consumer(sortIds(owns.ids));
    } else {
      rs.rsJsonApiRequest('/rsIdentity/getOwnPseudonimousIds', {}, (pseudo) => {
        if (pseudo.ids) consumer(sortIds(pseudo.ids.concat(owns.ids)));
      });
    }
  });
}
const SearchBar = () => {
  let searchString = '';

  return {
    view: () =>
      m('input.searchbar', {
        type: 'text',
        placeholder: 'search',
        value: searchString,
        oninput: (e) => {
          searchString = e.target.value.toLowerCase();

          rs.userList.users.map((id) => {
            if (id.mGroupName.toLowerCase().indexOf(searchString) > -1) {
              id.isSearched = true;
            } else {
              id.isSearched = false;
            }
          });
        },
      }),
  };
};

const regularcontactInfo = () => {
  let details = {};

  return {
    oninit: (v) =>
      rs.rsJsonApiRequest(
        '/rsIdentity/getIdDetails',
        {
          id: v.attrs.id.mGroupId,
        },
        (data) => {
          details = data.details;
        }
      ),
    view: (v) =>
      m(
        '.identity',
        {
          key: details.mId,
          style: 'display:' + (v.attrs.id.isSearched ? 'block' : 'none'),
        },
        [
          m('h4', details.mNickname),
          details.mNickname &&
            m(UserAvatar, {
              avatar: details.mAvatar,
              firstLetter: details.mNickname.slice(0, 1).toUpperCase(),
            }),
          m('.details', [
            m('p', 'ID:'),
            m('p', details.mId),
            m('p', 'Type:'),
            m('p', details.mFlags === 14 ? 'Signed ID' : 'Anonymous ID'),
            m('p', 'Owner node ID:'),
            m('p', details.mPgpId),
            m('p', 'Created on:'),
            m(
              'p',
              typeof details.mPublishTS === 'object'
                ? new Date(details.mPublishTS.xint64 * 1000).toLocaleString()
                : 'undefiend'
            ),
            m('p', 'Last used:'),
            m(
              'p',
              typeof details.mLastUsageTS === 'object'
                ? new Date(details.mLastUsageTS.xint64 * 1000).toLocaleDateString()
                : 'undefiend'
            ),
          ]),
          m(
            'button',
            {
              onclick: () =>
                m.route.set('/chat/:userid/createdistantchat', {
                  userid: v.attrs.id.mGroupId,
                }),
            },
            'Chat'
          ),
          m('button.red', {}, 'Mail'),
        ]
      ),
  };
};

module.exports = {
  sortUsers,
  sortIds,
  ownIds,
  checksudo,
  UserAvatar,
  contactlist,
  SearchBar,
  regularcontactInfo,
};
 
}); 
