(function() {
  'use strict';

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

  var modules = {};
  var cache = {};
  var aliases = {};
  var has = ({}).hasOwnProperty;

  var endsWith = function(str, suffix) {
    return str.indexOf(suffix, str.length - suffix.length) !== -1;
  };

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

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

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

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

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

  var require = function(name, loaderPath) {
    var path = expand(name, '.');
    if (loaderPath == null) loaderPath = '/';
    path = unalias(name, loaderPath);

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

    var dirIndex = expand(path, './index');
    if (has.call(cache, dirIndex)) return cache[dirIndex].exports;
    if (has.call(modules, dirIndex)) 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 (var key in bundle) {
        if (has.call(bundle, key)) {
          modules[key] = bundle[key];
        }
      }
    } else {
      modules[bundle] = fn;
    }
  };

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

  require.brunch = true;
  require._cache = cache;
  globals.require = require;
})();

require.register("accountselect", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

function cancel(){
    rs.memory("control/locations").curraccount=null;
    m.redraw();
}

function selAccount(account){
    rs.memory("control/locations").curraccount=account;
    m.redraw();
    rs.request("control/login", {id: account.id}, function(){
        console.log("login sent");
    });
}

function curraccount() {
    var mem;
    mem = rs.memory("control/locations");
    if (mem.curraccount === undefined) {
        return null;
    }
    return mem.curraccount;
}

module.exports = {view: function(){
    var accounts = rs("control/locations");
    if(accounts === undefined || accounts == null){
        return m("div", "accounts: waiting_server");
    }
    if (curraccount() == null) {
        return m("div", [
    	    m("h2","login:"),
            m("hr"),
    	    accounts.map(function(account){
    	    	return [
    	    	    m("div.btn2", {
    	    	        onclick: function(){
    	    	            selAccount(account)
    	    	        }
    	    	    },
    	    	    account.location + " (" + account.name + ")"),
    	    	    m("br")
    	    	]
            })
        ]);
    } else {
//        rs.untoken("control/password");
        return m("div", [
            m("div", [
                "logging in ...",
                m("br"),
                "(waiting for password-request)",
            ]),
            /*
            m("hr"),
            m(".btn2", {
                onclick: function() {
                    cancel();
                }
            },"Cancel " + curraccount().name + " login "),
            */
            ]);
    }
}
};

});
require.register("adddownloads", function(exports, require, module) {
var m = require("mithril");
var rs = require("retroshare");

var me = {
    toParse: [], // links to parse ( = pasted content)
    toConfirm: [], // links to confirm
    toAdd: [], // links to add
    toResult: [], // Result to show
    index: 0,
    view: function(){
        return m("div", {
            style: {
                height:"100%",
                boxSizing: "border-box",
                paddingBottom: "130px",
            }
        },[
            m("h2","add downloads"),
            m("hr"),
            this.toParse.length
            ? step2()
            : this.toConfirm.length
            ? step3()
            : this.toAdd.length
            ? step4()
            : this.toResult.length
            ? step5()
            : step1()
            ,
        ]);
    },
    parseOne: function(){
        if (me.index == null) {
            return null;
        }
        var startindex = me.index;
        while (me.toParse.length  > me.index && me.index - startindex < 10) {
            var src = me.toParse[me.index].split("?",2);
            console.log(src);
            if (src[0] == "retroshare://file" && src.length == 2) {
                var target = {action: "begin"};
                var errText = "Error: link missing name and/or hash"
                src[1].split("&").map(function(parm){
                    var pos = parm.indexOf("=");
                    if (pos >0){
                        if (parm.substr(0,pos) == "name") {
                            var sname=decodeURIComponent(parm.substr(pos+1))
                            if (sname.match("[\\\\/]")) {
                                errText="name contains illegal char "
                                    + sname.match("[\\\\/]");
                            } else {
                                target.name=sname;
                            }
                        } else if (parm.substr(0,pos) == "size") {
                            target.size=parseFloat(parm.substr(pos+1));
                        } else if (parm.substr(0,pos) == "hash") {
                            target.hash=parm.substr(pos+1);
                        }
                    }
                });
                if (target['name'] && target['hash']){
                    me.toConfirm.push({
                        text: target.name,
                        target: target,
                        confirmed: true,
                    });
                } else {
                    me.toConfirm.push({
                        text:errText,
                    });
                }
            } else {
                me.toConfirm.push({ text: "Error: no Retroshare-file link"})
            }
            me.index++;
        }
        if (me.toParse.length > me.index) {
            window.setTimeout("require(\"adddownloads\").parseOne()",1);
        } else {
            me.toParse = [];
            console.log(me.toConfirm.length);
        }
        refresh();
    },
    addOne: function(){
        if (me.index == null) {
            cancel();
        } else if (me.index >= me.toAdd.length) {
            me.toResult=me.toAdd;
            me.toAdd=[];
            refresh();
        } else {
            console.log([
                me.toAdd[me.index].action,
                me.toAdd[me.index].name,
                me.toAdd[me.index].size,
                me.toAdd[me.index].hash,
            ]);
            refresh();
            rs.request("transfers/control_download", me.toAdd[me.index],
                function(data,statetoken){
                    if (me.index != null) {
                        me.toAdd[me.index].ok=true;
                        me.index++;
                        me.addOne();
                    }
                }, {
                    onfail: function(value){
                        me.toAdd[me.index].ok=false;
                        me.toAdd[me.index].debug_msg=value;
                        me.index++;
                        me.addOne();
                    },
                    onmismatch: function(response){
                        me.toAdd[me.index].ok=false;
                        me.toAdd[me.index].debug_msg=response.debug_msg;
                        me.index++;
                        me.addOne();
                    },
                }
            );
        }
    }
};

function cancel() {
    me.toAdd=[];
    me.toConfirm=[];
    me.toParse=[];
    me.toResult=[];
    me.index=null;
    refresh();
}

function parseDownloads(){
    me.toParse = document.getElementById("txtInput").value.replace("\r\n","\n").split("\n");
    var pos;
    while ((pos=me.toParse.indexOf(""))>=0) {
        me.toParse.splice(pos,1);
    }
    var parser = document.createElement('a');
    me.toConfirm = [];
    me.index = 0;
    if (me.toParse.length  > me.index){
        window.setTimeout("require(\"adddownloads\").parseOne()",1);
    }
}

function addDownloads(){
    me.toConfirm.map(function(item){
        if (item.confirmed) {
            item.debug_msg="";
            me.toAdd.push(item.target);
        }
    });
    me.toConfirm=[];
    if (me.toAdd.length > 0){
        me.index=0;
        window.setTimeout("require(\"adddownloads\").addOne()",1);
    } else {
        cancel();
    }
    refresh();
}

function refresh(){
    m.startComputation();
    m.endComputation();
}

function cancelBtn(){
    return m("div.btn2", {
        style:{
            textAlign: "center",
            color: "red",
            borderColor: "red",
        },
        onclick:cancel,
    },"cancel");
}

// paste links
function step1(){
    m.initControl = "txtInput";
    return [
        m("h3","step 1 / 5: paste retroshare-links:"),
        m("textarea[id=txtInput]", {
            style: {
                height:"100%",
            },
            onkeydown: function(event){
                if (event.keyCode == 13){
                    parseDownloads();
                }
            }
        }),
        m("div.btn2", {
            style:{
                textAlign:"center",
            },
            onclick:parseDownloads,
        },"add downloads")
    ]
}

// parsing links
function step2(){
    return [
        m("h3","step 2 / 5: parsing input ..."),
        m("p",
            "parsing " + (me.index)  + " / " + me.toParse.length),
        cancelBtn(),
    ]
}

// parsing confirm
function step3(){
    return [
        m("h3","step 3 / 5: confirm-links:"),
        m("ul",
            me.toConfirm.map(function(item){
                return m("li", {
                    style:{
                        color: item.confirmed
                            ? "lime"
                            : "red"
                    },
                }, item.text);
            })
        ),
        m("div.btn2", {
            style:{
                textAlign:"center",
            },
            onclick:addDownloads,
        },"add green listed downloads"),
        cancelBtn(),
    ]
}

// adding links
function step4(){
    return [
        m("h3","step 4 / 5: adding downloads:"),
        m("p",
            "adding " + (me.index)  + " / " + me.toParse.length),
        m("ul",
            me.toAdd.map(function(item){
                return m("li", {
                    style:{
                        color: item.ok === undefined
                            ? "white"
                            : item.ok == null
                            ? "grey"
                            : item.ok
                            ? "lime"
                            : "red"
                    },
                }, (item.debug_msg ? item.debug_msg + ": " : "") + item.name
                    + " " + item.size + " " + item.hash);
            })
        ),
        cancelBtn(),
    ]
}

// show result
function step5(){
    return [
        m("h3","step 5 / 5: Result:"),
        m("p",
            "verarbeitet: " + me.toResult.length),
        m("ul",
            me.toResult.map(function(item){
                return m("li", {
                    style:{
                        color: item.ok === undefined
                            ? "white"
                            : item.ok == null
                            ? "grey"
                            : item.ok
                            ? "lime"
                            : "red"
                    },
                }, (item.debug_msg ? item.debug_msg + ": " : "") + item.name);
            })
        ),
        m("div.btn2", {
            style:{
                textAlign: "center",
            },
            onclick: cancel,
        },"ok"),
    ]
}



module.exports = me;

});
require.register("addidentity", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

function createidentity(){
    var data = {
        name: document.getElementById("txtname").value,
        pgp_linked: false,
        //document.getElementById("chklinked").checked,
    };
    m.route("/waiting");
    rs.request("identity/create_identity",data, function(){
        m.route("/identities");
    })
}

module.exports = {view: function(){
    m.initControl = "txtname";
    return m("div",
        m("h2","create identity"),
        m("hr"),
        m("h3","name"),
        m("input", {
            type: "text",
            id: "txtname",
            /*
            onkeydown: function(event){
                if (event.keyCode == 13){
                    setPasswd(this.value);
                    sendPassword(needpasswd);
                }
            }
            */
        }),
        /*
        m("b","linked with pgp-id: "),
        m("input", {
            type: "checkbox",
            id: "chklinked",
            style: {
                fontweight:"bold",
                width: "0%",
            }
        }),
        */
        m("p"," "),
        m("input.btn2", {
            onclick: createidentity,
            type: "button",
            value: "create new identity",
        })
    )
}}

});
require.register("addpeer", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

var newkey = "";
var remote = "";

module.exports = {
    view: function(){
        var key = m.route.param("radix");
        var pgp = m.route.param("pgp_id");
        var peer_id =m.route.param("peer_id");

        if (key===undefined && pgp === undefined) {

            var owncert = rs("peers/self/certificate");
            if (owncert === undefined ) {
                owncert = {cert_string:"< waiting for server ... >"}
            }
            return m("div", [
                m("h2","add new friend (Step 1/3)"),
                m("p","Your own key, give it to your friends"),
                m("pre", owncert.cert_string),
                m("p","paste your friends key below"),
			    m("textarea", {
                    ref:"cert",
			        cols:"70",
			        rows:"16",
			        onchange: m.withAttr("value", function(value){newkey=value;})
			    }),
			    m("br"),
			    m("input.btn2",{
			        type:"button",
			        value:"read",
			        onclick: function (){
			            m.route("/addpeer",{radix:newkey})
			        },
			    })
            ]);
        } else if (pgp === undefined) {
            rs.request("peers/examine_cert",{cert_string:key},
                function(data,responsetoken){
                    data.radix=key;
                    m.route("/addpeer", data);
                }
            );
            return m("div", [
                m("h2","add new friend (Step 2/3)"),
                m("div", "analyse cert, please wait for server ...")
                // { data: null, debug_msg: "failed to load certificate ", returncode: "fail" }
            ]);
        } else {
            var result = {
                cert_string:key ,
                flags:{
                    allow_direct_download:false,
                    allow_push:false,
					// set to false, until the webinterface supports managment of the blacklist/whitelist
					require_whitelist: false,
                }
            };
            return m("div",[
                m("h2","add new friend (Step 3/3)"),
                m("p","Do you want to add "
                    + m.route.param("name")
                    + " (" + m.route.param("location") + ")"
                    + " to your friendslist?"),
                m("input.checkbox[type=checkbox]", {
                    onchange: m.withAttr("checked", function(checked){
                        result.flags.allow_direct_download=checked;
                    })
                }), "Allow direct downloads from this node",
                m("br"),
                m("input.checkbox[type=checkbox]", {
                    onchange: m.withAttr("checked", function(checked){
                        result.flags.allow_push=checked;
                    })
                }), "Auto download recommended files from this node",
                m("div.btn2",{
                    onclick: function(){
                        m.route("/waiting");
                        rs.request("peers",result,function(data, responsetoken){
                            m.route("/peers");
                        })
                    }
                },"add to friendslist")

            ])
        }
    }
};

/*

    return "peers/self/certificate"



		RS.request({path: "peers/examine_cert", data: {cert_string: cert_string}}, this.examine_cert_callback);
		this.setState({page:"waiting", cert_string: cert_string});


		RS.request(
		{
			path: "peers",
			data: {
				cert_string: this.state.cert_string,
				flags:{
					allow_direct_download: this.refs.cb_direct_dl.getDOMNode().checked,
					allow_push: this.refs.cb_push.getDOMNode().checked,
					// set to false, until the webinterface supports managment of the blacklist/whitelist
					require_whitelist: false,
				}
			}
		});


	render: function(){
		if(this.state.page === "start")
			return(
				<div>
					<p>Your own key, give it to your friends</p>
					<OwnCert/>
					<p>paste your friends key below</p>
			        <textarea ref="cert" cols="70" rows="16"></textarea><br/>
			        <input
			          type="button"
			          value="read key"
			          onClick={this.add_friend_handler}
			        />
				</div>
			);
		if(this.state.page === "waiting")
			return(
				<div>
			        waiting for response from server...
				</div>
			);
		if(this.state.page === "peer")
			return(
				<div>
			        <p>Do you want to add {this.state.data.name} to your friendslist?</p>
			        <input className="checkbox" type="checkbox" ref="cb_direct_dl"/> Allow direct downloads from this node<br/>
			        <input className="checkbox" type="checkbox" ref="cb_push"/> Auto download recommended files from this node<br/>
			        <div onClick={this.final_add_handler} className="btn2">add to friendslist</div>
				</div>
			);
	},

	*/

});
require.register("chat", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

var msg = null;
var particips = [];

function dspmsg(from, when, text){
    return m(".chat.msg.container",[
        m(".chat.msg.from", from),
        m(".chat.msg.when", when),
        m(".chat.msg.text", text),
    ]);
}

function lobbies(){
    return [
        rs.list("chat/lobbies",function(lobby){
            return m("div.btn",{
                title: "topic: " + lobby.topic + "\n"
                    + "subscribed: " + lobby.subscribed,
                style: {
                    backgroundColor: lobby.subscribed ? 'blue' : 'darkred',
                },
                onclick: function(){
                    m.route("/chat?lobby=" + lobby.chat_id);
                }
            },
            lobby.name + (
                lobby.unread_msg_count > 0
                ? ("(" + lobby.unread_msg_count + ")")
                : "")
            );
        },
            rs.sort.bool("is_broadcast",
                rs.sort.bool("subscribed",
                    rs.sort("name")))
        ),
        m("br"),
        m("h3","peers:"),
        rs.list("peers",function(peer){
            return peer.locations.map(function(loc){
                if (loc.location == "") {
                    return [];
                };
                return m("div.btn",{
                    style: {
                        backgroundColor: loc.is_online ? 'blue' : 'darkred',
                    },
                    onclick: function(){
                        m.route("/chat?lobby=" + loc.chat_id);
                    }
                },
                peer.name + " / " + loc.location  + (
                    loc.unread_msgs > 0
                    ? ("(" + loc.unread_msgs + ")")
                    : "")
                );
            })
        })
    ];
}

function getLobbyDetails(lobbyid){
    var lobs = rs("chat/lobbies");
    if (lobs === undefined) {
        return null;
    };
    for(var i = 0, l = lobs.length; i < l; ++i) {
        if (lobs[i].chat_id == lobbyid) {
            return lobs[i];
        }
    }
    var peers = rs("peers");
    if (peers === undefined) {
        return null;
    };
    for(var i = 0, l = peers.length; i < l; ++i) {
        var peer = peers[i];
        for(var i1 = 0, l1 = peer.locations.length; i1 < l1; ++i1) {
            if (peer.locations[i1].chat_id == lobbyid) {
                return peer.locations[i1];
            }
        }
    }
    return null;
}

function sendmsg(msgid){
    var txtmsg = document.getElementById("txtNewMsg");
	rs.request("chat/send_message", {
		chat_id: msgid,
		msg: txtmsg.value
	});
    txtmsg.value="";
}

function lobby(lobbyid){
    var msgs;
    var lobdt = getLobbyDetails(lobbyid);
    var info = rs("chat/info/" + lobbyid);
    if (lobdt == null || info === undefined) {
        return m("div","waiting ...");
    }

    var mem = rs.memory("chat/info/" + lobbyid);
    if (mem.msg === undefined) {
        mem.msg = [];
    };

    var reqData = {};
    if (mem.lastKnownMsg != undefined) {
        reqData.begin_after = mem.lastKnownMsg;
    }

    rs.request("chat/messages/" + lobbyid, reqData, function (data) {
        if (data.length > 0 ) {
            mem.msg = mem.msg.concat(data);
            if (mem.msg.length > 0) {
                mem.lastKnownMsg = mem.msg[mem.msg.length -1].id;
            }
            rs.request("chat/mark_chat_as_read/" + lobbyid,{}, null,
                {allow: "ok|not_set"});
        } else {
            mem.msg = [];
        }
    }, {
        onmismatch: function (){},
        log:function(){} //no logging (pulling)
    });

    var intro = [
            m("h2",lobdt.name),
            m("p",lobdt.topic ? lobdt.topic: lobdt.location),
            m("hr")
    ]
    if (lobdt.subscribed != undefined && !lobdt.subscribed) {
        return [
            intro,
            m("b","select subscribe identity:"),
            m("p"),
            rs.list("identity/own", function(item){
                return m("div.btn2",{
                    onclick:function(){
                        console.log("subscribe - id:" + lobdt.id +", "
                            + "gxs_id:" + item.gxs_id)
			            rs.request("chat/subscribe_lobby",{
			                id:lobdt.id,
			                gxs_id:item.gxs_id
			            })
                    }
                },"subscribe as " + item.name);
            }),
        ];
    } else {
        msg = m(".chat.bottom",[
            m("div","enter new message:"),
            m("input",{
                id:"txtNewMsg",
                onkeydown: function(event){
                    if (event.keyCode == 13){
                        sendmsg(lobbyid);
                    }
                }
            }),
            m("div.btn2", {
                style: {textAlign:"center"},
                onclick: function(){
                    sendmsg(lobbyid);
                }
            },"submit")
        ]);
    }
    if (lobdt.subscribed != undefined
        && lobdt.subscribed
        && !lobdt.is_broadcast
        ) {
        //set participants
        particips = [
            m("div.btn", {
                style: {
                    "text-align":"center"
                },
                onclick: function (){
                    rs.request("chat/unsubscribe_lobby",{
                        id:lobdt.id,
                    });
                    m.route("/chat");
                }
            },"unsubscribe"),
            m("div.btn", {
                style: {
                    "text-align":"center"
                },
                onclick: function (){
                    rs.request("chat/clear_lobby",{
                        id:lobdt.id,
                    });
                    m.route("/chat?lobby=" + lobbyid);
                }
            },"clear"),
            m("h3","participants:"),
            rs.list(
                "chat/lobby_participants/" + lobbyid,
                function(item) {
                    return m("div",item.identity.name);
                },
                function (a,b){
                    return rs.stringSort(a.identity.name,b.identity.name);
                }
            )
        ]
    } else {
        if (lobdt.subscribed != undefined
            && lobdt.subscribed
            && lobdt.is_broadcast
            ) {
            //set participants
            particips = [
                m("div.btn", {
                    style: {
                        "text-align":"center"
                    },
                    onclick: function (){
                        rs.request("chat/clear_lobby",{
                            lobbyid,
                        });
                        m.route("/chat?lobby=" + lobbyid);
                    }
                },"clear"),
            ]
        }
    }
    return [
        intro,
        mem.msg.map(function(item){
            var d = new Date(new Number(item.send_time)*1000);
            return dspmsg(
                item.author_name,
                d.toLocaleDateString() + " " + d.toLocaleTimeString(),
                item.msg
            );
        })
    ];
}

module.exports = {
    frame: function(content, right){
        return m("div", {
            style: {
                "height": "100%",
                "box-sizing": "border-box",
                "padding-bottom": "170px",
            }
        },[
            m(".chat.container", [
                m(".chat.header", [
                    m(
                        "h2",
                        {style:{margin:"0px"}},
                        "chat"
                    )
                ]),
                m(".chat.left", [
                    m("div.chat.header[style=position:relative]","lobbies:"),
                    m("br"),
                    lobbies(),
                ]),
                m(".chat.right", right),
                m(".chat.middle", content),
                m(".chat.clear", ""),
            ]),
            msg != null
            ? msg
            : [],
        ]);
    },
    view: function(){
        var lobbyid = m.route.param("lobby");
        msg = null;
        if (lobbyid != undefined ) {
            particips = [];
            return this.frame(
                lobby(lobbyid),
                particips
            );
        };
        return this.frame(
            m(
                "div",
                {style: {margin:"10px"}},
                "please select lobby"
            ),
            m("div","")
        );
    }
}

});
require.register("createlogin", function(exports, require, module) {
var m = require("mithril");
var rs = require("retroshare");

var locationName = "";
var password ="";
var ssl_name = "";
var newName = "";

function listprofiles(){
    var locations = rs("control/locations");
    var knownProfileIds = [];
    var result = [];
    if(locations === undefined || locations == null){
        return m("div", "profiles: waiting_server");
    }
    locations.map(function(location) {
        if (knownProfileIds.indexOf(location.pgp_id)<0){
            knownProfileIds.push(location.pgp_id);
            result.push(m(
                "div.btn2",{
                    onclick: function(){
                        m.route("/createlogin",{
                            id: location.pgp_id,
                            name: location.name,
                        })
                    }
                },
                location.name
            ));
        }
    });

    return result;
}

function setLocationName(location) {
    locationName = location;
}

function setPasswd(passwd) {
    password = passwd;
}

function setSslName(ssl) {
    ssl_name = ssl;
}

function setNewName(name) {
    newName = name;
}

function checkpasswd(){
    var status = "";
    var color = "red";
    var lbl = document.getElementById("lblpwdinfo");
    var passwd2 = document.getElementById("txtpasswd2").value;
    if (passwd2 == password && passwd2 != "") {
        color = "lime";
        status = "password ok";
    } else if (passwd2 == "") {
        color = "yellow";
        status = "password required";
    } else {
        color = "red";
        status = "passwords don't match";
    }
    lbl.textContent = status;
    lbl.style.color=color;
}


function createLocation() {
    var profile = m.route.param("id");
    var profname = m.route.param("name");

    var loc ={
        ssl_name: document.getElementById("txtlocation").value,
        pgp_password: password,
    };
    if (profile != undefined) {
        loc.pgp_id= profile;
    } else {
        loc.pgp_name = newName;
    };
    rs.request("control/create_location",loc,function(data){
        m.route("/accountselect", {});
    });
    m.route("/createlogin",{state:wait});
}

function certDrop(event)
{
	console.log("onDrop()");
	console.log(event.dataTransfer.files);
	event.preventDefault();

	var reader = new FileReader();

	var widget = this;

	reader.onload = function(evt) {
		console.log("onDrop(): file loaded");
		rs.request(
		    "control/import_pgp",{
		        key_string:evt.target.result,
		    }, importCallback);
	};
	reader.readAsText(event.dataTransfer.files[0]);
	m.route("/createlogin",{state:"waiting"});
}

function importCallback(resp)
{
	console.log("importCallback()" + resp);
	m.route("/createlogin",{
	    id:resp.pgp_id,
	    name:"",
	});
}


module.exports = {
    view: function(){
        var profile = m.route.param("id");
        var state = m.route.param("state");
        var profname = m.route.param("name");
        var hidden = m.route.param("hidden");
        if (state == "wait"){
            return m("div","waiting ...");
        } if (state == "newid"){
            m.initControl = "txtnewname";
            return m("div",[
                m("h2","create login - Step 2 / 2: create location"),
                m("h3","- for new profile "),
                m("hr"),
                m("h2","PGP-profile name:"),
                m("input",{
                    id: "txtnewname",
                    type:"text",
                    onchange:m.withAttr("value", setNewName),
                    onkeydown: function(event){
                        if (event.keyCode == 13){
                            document.getElementById("txtpasswd").focus();
                        }
                    },
                }),
                m("h2","enter password:"),
                m("input", {
                    id: "txtpasswd",
                    type:"password",
                    onchange: m.withAttr("value",setPasswd),
                    onkeydown: function(event){
                        if (event.keyCode == 13){
                            setPasswd(this.value);
                            document.getElementById("txtpasswd2").focus();
                        };
                        checkpasswd;
                    }
                }),
                m("h2", "re-enter password:"),
                m("input", {
                    id: "txtpasswd2",
                    type:"password",
                    onfocus: checkpasswd,
                    onchange: checkpasswd,
                    onkeyup: function(event){
                        if (event.keyCode == 13){
                            document.getElementById("txtlocation").focus();
                        }
                        checkpasswd();
                    }
                }),
                m("h3",{
                    id: "lblpwdinfo",
                    style:"color:yellow",
                }, "password required"),
                m("h2","location name:"),
                m("input",{
                    id: "txtlocation",
                    type:"text",
                    onchange:m.withAttr("value", setLocationName),
                    onkeydown: function(event){
                        if (event.keyCode == 13){
                            setSslName(this.value);
                            createLocation();
                        }
                    },
                }),
                m("br"),
                m("input",{
                    type: "button",
                    onclick: createLocation,
                    value: "create location",
                }),
            ]);
        } else if (profile != undefined) {
            m.initControl = "txtpasswd";
            return m("div",[
                m("h2","create login - Step 2 / 2: create location"),
                m("h3","- for " + profname + " (" +profile + ")"),
                m("hr"),
                m("h2","enter password:"),
                m("input", {
                    id: "txtpasswd",
                    type:"password",
                    onchange: m.withAttr("value",setPasswd),
                    onkeydown: function(event){
                        if (event.keyCode == 13){
                            setPasswd(this.value);
                            document.getElementById("txtlocation").focus();
                        }
                    }
                }),
                m("h2","location name:"),
                m("input",{
                    id: "txtlocation",
                    type:"text",
                    onchange:m.withAttr("value", setLocationName),
                    onkeydown: function(event){
                        if (event.keyCode == 13){
                            setSslName(this.value);
                            createLocation();
                        }
                    },
                }),
                m("br"),
                m("input",{
                    type: "button",
                    onclick: createLocation,
                    value: "create location",
                }),
            ]);
        } else {
            return m("div",[
                m("h2","create login - Step 1 / 2: select profile(PGP-ID)"),
                m("hr"),
                m("div.btn2",{
                    onclick: function(){
                        m.route("/createlogin", {state: "newid"});
                    },
                } ,"<create new profile>"),
                m("div.btn2",{
                	ondragover:function(event){
                	    /*important: block default event*/
                	    event.preventDefault();
                	},
					ondrop: certDrop,
                } ,"<import profile (drag and drop a profile here)>"),
                listprofiles()
            ]);
        };

    }
}

});
require.register("downloads", function(exports, require, module) {
var m = require("mithril");
var rs = require("retroshare");

function makeFriendlyUnit(bytes)
{
	if(bytes < 1e3)
		return bytes.toFixed(1) + "B";
	if(bytes < 1e6)
		return (bytes/1e3).toFixed(1) + "kB";
	if(bytes < 1e9)
		return (bytes/1e6).toFixed(1) + "MB";
	if(bytes < 1e12)
		return (bytes/1e9).toFixed(1) + "GB";
	return (bytes/1e12).toFixed(1) + "TB";
}

function progressBar(file){
    return m("div[style=border:5px solid lime;"
        + 'border-radius:3mm;'
        + 'padding:2mm;'
        + 'height:5mm'
	+ "]", [
	m("div[style="
	    + 'background-color:lime;'
	    + 'height:100%;'
	    + 'width:' + (file.transfered  /  file.size * 100)+'%'
	    + ']'
	,"")
	]);
};

function cntrlBtn(file, act) {
    return(
        m("div.btn",{
            onclick: function(){
                rs.request("transfers/control_download",{action: act, id: file.id});
            }
        },
        act)
    )
}


module.exports = {
    view: function(){
        var paths = rs("transfers/downloads");
        var filestreamer_url = "/fstream/";
        if (paths === undefined) {
            return m("div", "Downloads ... please wait ...");
        }
        return m("div", [
            m("h2","Downloads (" + paths.length +")"),
            m("div.btn2", {
                onclick: function(){
                    m.route("/downloads/add");
                }
            }, "add retrohare downloads"),
            m("hr"),
            m('table', [
                m("tr",[
                    m("th","name"),
                    m("th","size"),
                    m("th","progress"),
                    m("th","transfer rate"),
                    m("th","status"),
                    m("th","progress"),
                    m("th","action")
                ]),
            	paths.map(function (file){
            	    var ctrlBtn = m("div","");
                    var progress = file.transfered  /  file.size * 100;
            	    return m("tr",[
            	        m("td",[
            	            m("a.filelink",
            	                {
                        	        target: "blank",
                        	        href: filestreamer_url + file.hash + "/" + encodeURIComponent(file.name)
                	            },
            	                file.name
            	            )
            	        ]),
            	        m("td", makeFriendlyUnit(file.size)),
            	        m("td", progress.toPrecision(3) + "%"),
            	        m("td", makeFriendlyUnit(file.transfer_rate*1e3)+"/s"),
            	        m("td", file.download_status),
            	        m("td", progressBar(file)),
            	        m("td", [
            	            cntrlBtn(file, file.download_status==="paused"?"start":"pause"),
            	            cntrlBtn(file, "cancel")]
            	        )
            	    ])
                })
	    ])
        ]);
    }
};

});
require.register("forums", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

module.exports = {view: function(){
    return m("div",[
        m("h2","forums"),
        m("p","(work in progress, currently only listing)"),
        m("hr"),
        /*
        m("div.btn2", {
            onclick: function (){
                m.route("/addforum");
            }
        },"< create new forum >"),
        */
        m("ul",
            rs.list("forums",
                function(item){
                    return m("li",[
                        m("h2",item.name),
                        m("div",{style:{margin:"10px"}},
                            [
                                item.description != ""
                                ? [
                                    m("span", "Description: "
                                     + item.description),
                                    m("br")]
                                : [],
                                m("span","messages visible: "
                                 + item.visible_msg_count),
                            ]
                        ),
                        /*
                        item.subscribed
                            ? [
                                m(
                                    "span.btn2",
                                    {style:{padding:"0px"}},
                                    "unsubscribe"
                                ),
                                " ",
                                m(
                                    "span.btn2",
                                    {style:{padding:"0px", margin:"10px"}},
                                    "enter"
                                ),
                                m("hr", {style: {color:"silver"}}),
                            ]
                            : [
                                m(
                                    "span.btn2",
                                    {style:{padding:"0px", margin:"10px"}},
                                    "subscribe"
                                ),
                            ]
                        */
                    ]);
                },
                rs.sort("name")
            )
        )
    ]);
}}

});
require.register("home", function(exports, require, module) {
var m = require("mithril");

module.exports = {view: function(){
        return m("div","RetroShare - WebClient - Welcome");
    }
};

});
require.register("identities", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

module.exports = {view: function(){
    return m("div",[
        m("h2","identities"),
        m("hr"),
        m("div.btn2", {
            onclick: function (){
                m.route("/addidentity");
            }
        },"< create new identity >"),
        m("ul",
            rs.list("identity/own", function(item){
                return m("li",[m("h2",item.name)]);
            },
            rs.sort("name"))
        )
    ]);
}}

});
require.register("main", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");
var menu =require("menu");
var currentpasswd = null;


function setPasswd(password) {
    currentpasswd = password
}

function sendPassword(data) {
    console.log("sending pwd for " + data.key_name + "...");
    rs.request("control/password", {password: currentpasswd}, function(){
        m.redraw();
    });
}

function Page(menu){
    this.menu = menu;
    this.module = (menu.module != undefined) ? menu.module : menu.name;
    this.path = menu.path != undefined ? menu.path : ("/" + menu.name);

    var runst = menu.runstate;
    var content = require(this.module);
    var mm = require("menu");

    this.view = function(){
        var runstate = rs("control/runstate");
        var needpasswd = rs("control/password");
        //console.log("runstate: " + (runstate === undefined ? runstate : runstate.runstate));
        if(runstate === undefined){
            return m("h2", "waiting_server ... ");
        } else if (runstate.runstate == null){
            // try clean reboot ...
            rs.clearCache();
            rs("control/runstate"); //reboot detector
            console.log("i'm down");
            return m("h2", "server down");
        } else if (needpasswd != undefined && needpasswd.want_password === true){
            m.initControl = "txtpasswd";
            return m("div",[
                m("h2","password required"),
                m("h3",needpasswd.key_name),
                m("input",{
                    id: "txtpasswd",
                    type:"password",
                    onchange:m.withAttr("value", setPasswd),
                    onkeydown: function(event){
                        if (event.keyCode == 13){
                            setPasswd(this.value);
                            sendPassword(needpasswd);
                        }
                    }
                }),
                m("br"),
                m("input[type=button][value=send password]",{
                    onclick: function(){
                        sendPassword(needpasswd);
                    }
                }),
            ]);
        } else {
            if (runstate.runstate.match("waiting_init|waiting_startup")) {
                return m("h2","server starting ...")
            } else if(runstate.runstate.match("waiting_account_select|running_ok.*")) {
                if (runst === undefined || runstate.runstate.match(runst)) {
                    return m("div", {
                        style: {
                            height: "100%",
                            "box-sizing": "border-box",
                            "padding-bottom": "40px"
                        }
                    }, [
                        m("div", mm.view()),
        	            m("hr"),
                        m("div", {
                            style: {
                                height: "100%",
                                "box-sizing": "border-box",
                                "padding-bottom":"40px"
                            }
                        }, content)
                    ]);
                } else {
                    // funktion currently not available
                    m.route("/");
                    return m("div", [
                        m("div", mm.view()),
        	            m("hr"),
                        m("div", require("home").view())
                    ]);
                };
            } else {
                return m("div", "unknown runstate: " + runstate.runstate);
            }
        }
    }
};


module.exports = {
    init:function(main){
        console.log("start init ...");
        var menudef = require("menudef");
        var maps = {};
        var m = require("mithril");

        menudef.nodes.map(function(menu){
            if (menu.action === undefined) {
                var p = new Page(menu)
                console.log("adding route " + menu.name + " for " + p.path + " with module " + p.module);
                maps[p.path] = p;
            }
        });
        m.route.mode = "hash";
        m.route(main,"/",maps);
        console.log("init done.");
    }
};


});
require.register("menu", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");
var mnodes = require("menudef");

function goback(){
    rs.content=null;
    m.redraw();
}

function buildmenu(menu, tagname, runstate, ignore){
    if (
        (menu.runstate === undefined
            || runstate.match("^(" + menu.runstate + ")$")!=null)
        && (!menu.name.match(ignore))
        && (menu.path === undefined || menu.path.match(":")==null)
        && (menu.show === undefined || menu.show)
    )  {
        var name = menu.name;
        if (menu.counter != undefined) {
            name += menu.counter();
        }
        if (menu.action === undefined) {
            return m(tagname , {
                onclick: function(){
                    m.route(
                        menu.path != undefined ? menu.path : "/" + menu.name
                    )
                }
            }, name);
        } else {
            return m(tagname, {onclick: function(){menu.action(m)}}, name);
        }
    }
}

module.exports = {view: function(){
    var runstate = rs("control/runstate");
    if (runstate === undefined
        || runstate.runstate === undefined
        || runstate.runstate == null)
    	return m("div.nav","menu: waiting for server ...");
    if (m.route() != "/")
    return m("span",[
            m("span"," | "),
        	mnodes.nodes.map(function(menu){
        	    var item = buildmenu(menu,"span.menu", runstate.runstate, "-");
        	    if (item != null){
        	        return [
            	        item,
            	        m("span"," | ")
            	    ]
            	}
            })
        ]);
    return m("div", [
    	m("h2","home"),
    	m("hr"),
    	mnodes.nodes.map(function(menu){
    	    return buildmenu(menu,"div.btn2", runstate.runstate, "home");
        })
    ]);
}
};

});
require.register("menudef", function(exports, require, module) {
var rs=require("retroshare");
module.exports = {  nodes: [
	{
		name: "home",
		path: "/"
	},
	{
		name: "login",
		module: "accountselect",
		runstate: "waiting_account_select",
		counter: rs.counting("control/locations"),
	},
	{
	    name: "create login",
	    path: "/createlogin",
	    module: "createlogin",
	    runstate: "waiting_account_select",
	},
	{
		name: "peers",
		runstate: "running_ok.*",
		counter: rs.counting("peers", function(data){
		    var onlinecount = 0;
		    data.map(function(peer) {
		        var is_online = false;
		        peer.locations.map(function (location){
		            if (location.is_online) {
		                is_online=true;
		            }
		        });
		        if (is_online) {
		            onlinecount +=1;
		        }
		    });
		    return onlinecount + "/" + data.length;
		})
	},
	{
	    name: "addpeer",
	    runstate: "running_ok.*",
	    show: false,
	},
	{
	    name: "identities",
	    runstate: "running_ok.*",
	    counter: rs.counting("identity/own"),
	},
	{
	    name: "addidentity",
	    runstate: "running_ok.*",
	    show: false,
	},
	{
	    name:"searchresult",
	    path: "/search/:id",
	    runstate: "running_ok.*",
	},
	{
	    name: "search",
	    runstate: "running_ok.*",
	},
	{
		name: "downloads",
		runstate: "running_ok.*",
		counter: rs.counting("transfers/downloads")
	},
	{
		name: "adddownloads",
		runstate: "running_ok.*",
		path: "/downloads/add",
		show: false,
	},
	{
	    name: "forums",
	    runstate: "running_ok.*",
	},
	{
		name: "chat",
		runstate: "running_ok.*",
		counter: rs.counting2({
	        "peers": function(peer) {
	            var sum = 0;
	            peer.locations.map(function (loc) {
	                sum += parseInt(loc.unread_msgs);
	            });
	            return sum;
	        },
	        "chat/lobbies": function(lobby) {
	            return lobby.unread_msg_count;
	        }
		})
	},
	{
	    name:"settings",
	    runstate: "running_ok.*",
	},
	{
	    name:"servicecontrol",
	    runstate: "running_ok.*",
	    path:"/settings/servicecontrol",
	    show: false,
	},
	{
		name: "shutdown",
		runstate: "running_ok|waiting_account_select",
		action: function(m){
			rs.request("control/shutdown",null,function(){
				rs("control/runstate").runstate=null;
				rs.forceUpdate("control/runstate");
				m.redraw();
			});
		}
	},
	{
	    name: "waiting",
	    show: false,
	},
]
}

});
require.register("mithril", function(exports, require, module) {
;(function (global, factory) { // eslint-disable-line
	"use strict"
	/* eslint-disable no-undef */
	var m = factory(global)
	if (typeof module === "object" && module != null && module.exports) {
		module.exports = m
	} else if (typeof define === "function" && define.amd) {
		define(function () { return m })
	} else {
		global.m = m
	}
	/* eslint-enable no-undef */
})(typeof window !== "undefined" ? window : {}, function (global, undefined) { // eslint-disable-line
	"use strict"

	m.version = function () {
		return "v0.2.3"
	}

	var hasOwn = {}.hasOwnProperty
	var type = {}.toString

	function isFunction(object) {
		return typeof object === "function"
	}

	function isObject(object) {
		return type.call(object) === "[object Object]"
	}

	function isString(object) {
		return type.call(object) === "[object String]"
	}

	var isArray = Array.isArray || function (object) {
		return type.call(object) === "[object Array]"
	}

	function noop() {}

	var voidElements = {
		AREA: 1,
		BASE: 1,
		BR: 1,
		COL: 1,
		COMMAND: 1,
		EMBED: 1,
		HR: 1,
		IMG: 1,
		INPUT: 1,
		KEYGEN: 1,
		LINK: 1,
		META: 1,
		PARAM: 1,
		SOURCE: 1,
		TRACK: 1,
		WBR: 1
	}

	// caching commonly used variables
	var $document, $location, $requestAnimationFrame, $cancelAnimationFrame

	// self invoking function needed because of the way mocks work
	function initialize(mock) {
		$document = mock.document
		$location = mock.location
		$cancelAnimationFrame = mock.cancelAnimationFrame || mock.clearTimeout
		$requestAnimationFrame = mock.requestAnimationFrame || mock.setTimeout
	}

	// testing API
	m.deps = function (mock) {
		initialize(global = mock || window)
		return global
	}

	m.deps(global)

	/**
	 * @typedef {String} Tag
	 * A string that looks like -> div.classname#id[param=one][param2=two]
	 * Which describes a DOM node
	 */

	function parseTagAttrs(cell, tag) {
		var classes = []
		var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g
		var match

		while ((match = parser.exec(tag))) {
			if (match[1] === "" && match[2]) {
				cell.tag = match[2]
			} else if (match[1] === "#") {
				cell.attrs.id = match[2]
			} else if (match[1] === ".") {
				classes.push(match[2])
			} else if (match[3][0] === "[") {
				var pair = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/.exec(match[3])
				cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" : true)
			}
		}

		return classes
	}

	function getVirtualChildren(args, hasAttrs) {
		var children = hasAttrs ? args.slice(1) : args

		if (children.length === 1 && isArray(children[0])) {
			return children[0]
		} else {
			return children
		}
	}

	function assignAttrs(target, attrs, classes) {
		var classAttr = "class" in attrs ? "class" : "className"

		for (var attrName in attrs) {
			if (hasOwn.call(attrs, attrName)) {
				if (attrName === classAttr &&
						attrs[attrName] != null &&
						attrs[attrName] !== "") {
					classes.push(attrs[attrName])
					// create key in correct iteration order
					target[attrName] = ""
				} else {
					target[attrName] = attrs[attrName]
				}
			}
		}

		if (classes.length) target[classAttr] = classes.join(" ")
	}

	/**
	 *
	 * @param {Tag} The DOM node tag
	 * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs
	 * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array,
	 *                      or splat (optional)
	 */
	function m(tag, pairs) {
		var args = [].slice.call(arguments, 1)

		if (isObject(tag)) return parameterize(tag, args)

		if (!isString(tag)) {
			throw new Error("selector in m(selector, attrs, children) should " +
				"be a string")
		}

		var hasAttrs = pairs != null && isObject(pairs) &&
			!("tag" in pairs || "view" in pairs || "subtree" in pairs)

		var attrs = hasAttrs ? pairs : {}
		var cell = {
			tag: "div",
			attrs: {},
			children: getVirtualChildren(args, hasAttrs)
		}

		assignAttrs(cell.attrs, attrs, parseTagAttrs(cell, tag))
		return cell
	}

	function forEach(list, f) {
		for (var i = 0; i < list.length && !f(list[i], i++);) {
			// function called in condition
		}
	}

	function forKeys(list, f) {
		forEach(list, function (attrs, i) {
			return (attrs = attrs && attrs.attrs) &&
				attrs.key != null &&
				f(attrs, i)
		})
	}
	// This function was causing deopts in Chrome.
	function dataToString(data) {
		// data.toString() might throw or return null if data is the return
		// value of Console.log in some versions of Firefox (behavior depends on
		// version)
		try {
			if (data != null && data.toString() != null) return data
		} catch (e) {
			// silently ignore errors
		}
		return ""
	}

	// This function was causing deopts in Chrome.
	function injectTextNode(parentElement, first, index, data) {
		try {
			insertNode(parentElement, first, index)
			first.nodeValue = data
		} catch (e) {
			// IE erroneously throws error when appending an empty text node
			// after a null
		}
	}

	function flatten(list) {
		// recursively flatten array
		for (var i = 0; i < list.length; i++) {
			if (isArray(list[i])) {
				list = list.concat.apply([], list)
				// check current index again and flatten until there are no more
				// nested arrays at that index
				i--
			}
		}
		return list
	}

	function insertNode(parentElement, node, index) {
		parentElement.insertBefore(node,
			parentElement.childNodes[index] || null)
	}

	var DELETION = 1
	var INSERTION = 2
	var MOVE = 3

	function handleKeysDiffer(data, existing, cached, parentElement) {
		forKeys(data, function (key, i) {
			existing[key = key.key] = existing[key] ? {
				action: MOVE,
				index: i,
				from: existing[key].index,
				element: cached.nodes[existing[key].index] ||
					$document.createElement("div")
			} : {action: INSERTION, index: i}
		})

		var actions = []
		for (var prop in existing) if (hasOwn.call(existing, prop)) {
			actions.push(existing[prop])
		}

		var changes = actions.sort(sortChanges)
		var newCached = new Array(cached.length)

		newCached.nodes = cached.nodes.slice()

		forEach(changes, function (change) {
			var index = change.index
			if (change.action === DELETION) {
				clear(cached[index].nodes, cached[index])
				newCached.splice(index, 1)
			}
			if (change.action === INSERTION) {
				var dummy = $document.createElement("div")
				dummy.key = data[index].attrs.key
				insertNode(parentElement, dummy, index)
				newCached.splice(index, 0, {
					attrs: {key: data[index].attrs.key},
					nodes: [dummy]
				})
				newCached.nodes[index] = dummy
			}

			if (change.action === MOVE) {
				var changeElement = change.element
				var maybeChanged = parentElement.childNodes[index]
				if (maybeChanged !== changeElement && changeElement !== null) {
					parentElement.insertBefore(changeElement,
						maybeChanged || null)
				}
				newCached[index] = cached[change.from]
				newCached.nodes[index] = changeElement
			}
		})

		return newCached
	}

	function diffKeys(data, cached, existing, parentElement) {
		var keysDiffer = data.length !== cached.length

		if (!keysDiffer) {
			forKeys(data, function (attrs, i) {
				var cachedCell = cached[i]
				return keysDiffer = cachedCell &&
					cachedCell.attrs &&
					cachedCell.attrs.key !== attrs.key
			})
		}

		if (keysDiffer) {
			return handleKeysDiffer(data, existing, cached, parentElement)
		} else {
			return cached
		}
	}

	function diffArray(data, cached, nodes) {
		// diff the array itself

		// update the list of DOM nodes by collecting the nodes from each item
		forEach(data, function (_, i) {
			if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes)
		})
		// remove items from the end of the array if the new array is shorter
		// than the old one. if errors ever happen here, the issue is most
		// likely a bug in the construction of the `cached` data structure
		// somewhere earlier in the program
		forEach(cached.nodes, function (node, i) {
			if (node.parentNode != null && nodes.indexOf(node) < 0) {
				clear([node], [cached[i]])
			}
		})

		if (data.length < cached.length) cached.length = data.length
		cached.nodes = nodes
	}

	function buildArrayKeys(data) {
		var guid = 0
		forKeys(data, function () {
			forEach(data, function (attrs) {
				if ((attrs = attrs && attrs.attrs) && attrs.key == null) {
					attrs.key = "__mithril__" + guid++
				}
			})
			return 1
		})
	}

	function isDifferentEnough(data, cached, dataAttrKeys) {
		if (data.tag !== cached.tag) return true

		if (dataAttrKeys.sort().join() !==
				Object.keys(cached.attrs).sort().join()) {
			return true
		}

		if (data.attrs.id !== cached.attrs.id) {
			return true
		}

		if (data.attrs.key !== cached.attrs.key) {
			return true
		}

		if (m.redraw.strategy() === "all") {
			return !cached.configContext || cached.configContext.retain !== true
		}

		if (m.redraw.strategy() === "diff") {
			return cached.configContext && cached.configContext.retain === false
		}

		return false
	}

	function maybeRecreateObject(data, cached, dataAttrKeys) {
		// if an element is different enough from the one in cache, recreate it
		if (isDifferentEnough(data, cached, dataAttrKeys)) {
			if (cached.nodes.length) clear(cached.nodes)

			if (cached.configContext &&
					isFunction(cached.configContext.onunload)) {
				cached.configContext.onunload()
			}

			if (cached.controllers) {
				forEach(cached.controllers, function (controller) {
					if (controller.onunload) controller.onunload({preventDefault: noop});
				});
			}
		}
	}

	function getObjectNamespace(data, namespace) {
		if (data.attrs.xmlns) return data.attrs.xmlns
		if (data.tag === "svg") return "http://www.w3.org/2000/svg"
		if (data.tag === "math") return "http://www.w3.org/1998/Math/MathML"
		return namespace
	}

	var pendingRequests = 0
	m.startComputation = function () { pendingRequests++ }
	m.endComputation = function () {
		if (pendingRequests > 1) {
			pendingRequests--
		} else {
			pendingRequests = 0
			m.redraw()
		}
	}

	function unloadCachedControllers(cached, views, controllers) {
		if (controllers.length) {
			cached.views = views
			cached.controllers = controllers
			forEach(controllers, function (controller) {
				if (controller.onunload && controller.onunload.$old) {
					controller.onunload = controller.onunload.$old
				}

				if (pendingRequests && controller.onunload) {
					var onunload = controller.onunload
					controller.onunload = noop
					controller.onunload.$old = onunload
				}
			})
		}
	}

	function scheduleConfigsToBeCalled(configs, data, node, isNew, cached) {
		// schedule configs to be called. They are called after `build` finishes
		// running
		if (isFunction(data.attrs.config)) {
			var context = cached.configContext = cached.configContext || {}

			// bind
			configs.push(function () {
				return data.attrs.config.call(data, node, !isNew, context,
					cached)
			})
		}
	}

	function buildUpdatedNode(
		cached,
		data,
		editable,
		hasKeys,
		namespace,
		views,
		configs,
		controllers
	) {
		var node = cached.nodes[0]

		if (hasKeys) {
			setAttributes(node, data.tag, data.attrs, cached.attrs, namespace)
		}

		cached.children = build(
			node,
			data.tag,
			undefined,
			undefined,
			data.children,
			cached.children,
			false,
			0,
			data.attrs.contenteditable ? node : editable,
			namespace,
			configs
		)

		cached.nodes.intact = true

		if (controllers.length) {
			cached.views = views
			cached.controllers = controllers
		}

		return node
	}

	function handleNonexistentNodes(data, parentElement, index) {
		var nodes
		if (data.$trusted) {
			nodes = injectHTML(parentElement, index, data)
		} else {
			nodes = [$document.createTextNode(data)]
			if (!(parentElement.nodeName in voidElements)) {
				insertNode(parentElement, nodes[0], index)
			}
		}

		var cached

		if (typeof data === "string" ||
				typeof data === "number" ||
				typeof data === "boolean") {
			cached = new data.constructor(data)
		} else {
			cached = data
		}

		cached.nodes = nodes
		return cached
	}

	function reattachNodes(
		data,
		cached,
		parentElement,
		editable,
		index,
		parentTag
	) {
		var nodes = cached.nodes
		if (!editable || editable !== $document.activeElement) {
			if (data.$trusted) {
				clear(nodes, cached)
				nodes = injectHTML(parentElement, index, data)
			} else if (parentTag === "textarea") {
				// <textarea> uses `value` instead of `nodeValue`.
				parentElement.value = data
			} else if (editable) {
				// contenteditable nodes use `innerHTML` instead of `nodeValue`.
				editable.innerHTML = data
			} else {
				// was a trusted string
				if (nodes[0].nodeType === 1 || nodes.length > 1 ||
						(nodes[0].nodeValue.trim &&
							!nodes[0].nodeValue.trim())) {
					clear(cached.nodes, cached)
					nodes = [$document.createTextNode(data)]
				}

				injectTextNode(parentElement, nodes[0], index, data)
			}
		}
		cached = new data.constructor(data)
		cached.nodes = nodes
		return cached
	}

	function handleTextNode(
		cached,
		data,
		index,
		parentElement,
		shouldReattach,
		editable,
		parentTag
	) {
		if (!cached.nodes.length) {
			return handleNonexistentNodes(data, parentElement, index)
		} else if (cached.valueOf() !== data.valueOf() || shouldReattach) {
			return reattachNodes(data, cached, parentElement, editable, index,
				parentTag)
		} else {
			return (cached.nodes.intact = true, cached)
		}
	}

	function getSubArrayCount(item) {
		if (item.$trusted) {
			// fix offset of next element if item was a trusted string w/ more
			// than one html element
			// the first clause in the regexp matches elements
			// the second clause (after the pipe) matches text nodes
			var match = item.match(/<[^\/]|\>\s*[^<]/g)
			if (match != null) return match.length
		} else if (isArray(item)) {
			return item.length
		}
		return 1
	}

	function buildArray(
		data,
		cached,
		parentElement,
		index,
		parentTag,
		shouldReattach,
		editable,
		namespace,
		configs
	) {
		data = flatten(data)
		var nodes = []
		var intact = cached.length === data.length
		var subArrayCount = 0

		// keys algorithm: sort elements without recreating them if keys are
		// present
		//
		// 1) create a map of all existing keys, and mark all for deletion
		// 2) add new keys to map and mark them for addition
		// 3) if key exists in new list, change action from deletion to a move
		// 4) for each key, handle its corresponding action as marked in
		//    previous steps

		var existing = {}
		var shouldMaintainIdentities = false

		forKeys(cached, function (attrs, i) {
			shouldMaintainIdentities = true
			existing[cached[i].attrs.key] = {action: DELETION, index: i}
		})

		buildArrayKeys(data)
		if (shouldMaintainIdentities) {
			cached = diffKeys(data, cached, existing, parentElement)
		}
		// end key algorithm

		var cacheCount = 0
		// faster explicitly written
		for (var i = 0, len = data.length; i < len; i++) {
			// diff each item in the array
			var item = build(
				parentElement,
				parentTag,
				cached,
				index,
				data[i],
				cached[cacheCount],
				shouldReattach,
				index + subArrayCount || subArrayCount,
				editable,
				namespace,
				configs)

			if (item !== undefined) {
				intact = intact && item.nodes.intact
				subArrayCount += getSubArrayCount(item)
				cached[cacheCount++] = item
			}
		}

		if (!intact) diffArray(data, cached, nodes)
		return cached
	}

	function makeCache(data, cached, index, parentIndex, parentCache) {
		if (cached != null) {
			if (type.call(cached) === type.call(data)) return cached

			if (parentCache && parentCache.nodes) {
				var offset = index - parentIndex
				var end = offset + (isArray(data) ? data : cached.nodes).length
				clear(
					parentCache.nodes.slice(offset, end),
					parentCache.slice(offset, end))
			} else if (cached.nodes) {
				clear(cached.nodes, cached)
			}
		}

		cached = new data.constructor()
		// if constructor creates a virtual dom element, use a blank object as
		// the base cached node instead of copying the virtual el (#277)
		if (cached.tag) cached = {}
		cached.nodes = []
		return cached
	}

	function constructNode(data, namespace) {
		if (data.attrs.is) {
			if (namespace == null) {
				return $document.createElement(data.tag, data.attrs.is)
			} else {
				return $document.createElementNS(namespace, data.tag,
					data.attrs.is)
			}
		} else if (namespace == null) {
			return $document.createElement(data.tag)
		} else {
			return $document.createElementNS(namespace, data.tag)
		}
	}

	function constructAttrs(data, node, namespace, hasKeys) {
		if (hasKeys) {
			return setAttributes(node, data.tag, data.attrs, {}, namespace)
		} else {
			return data.attrs
		}
	}

	function constructChildren(
		data,
		node,
		cached,
		editable,
		namespace,
		configs
	) {
		if (data.children != null && data.children.length > 0) {
			return build(
				node,
				data.tag,
				undefined,
				undefined,
				data.children,
				cached.children,
				true,
				0,
				data.attrs.contenteditable ? node : editable,
				namespace,
				configs)
		} else {
			return data.children
		}
	}

	function reconstructCached(
		data,
		attrs,
		children,
		node,
		namespace,
		views,
		controllers
	) {
		var cached = {
			tag: data.tag,
			attrs: attrs,
			children: children,
			nodes: [node]
		}

		unloadCachedControllers(cached, views, controllers)

		if (cached.children && !cached.children.nodes) {
			cached.children.nodes = []
		}

		// edge case: setting value on <select> doesn't work before children
		// exist, so set it again after children have been created
		if (data.tag === "select" && "value" in data.attrs) {
			setAttributes(node, data.tag, {value: data.attrs.value}, {},
				namespace)
		}

		return cached
	}

	function getController(views, view, cachedControllers, controller) {
		var controllerIndex

		if (m.redraw.strategy() === "diff" && views) {
			controllerIndex = views.indexOf(view)
		} else {
			controllerIndex = -1
		}

		if (controllerIndex > -1) {
			return cachedControllers[controllerIndex]
		} else if (isFunction(controller)) {
			return new controller()
		} else {
			return {}
		}
	}

	var unloaders = []

	function updateLists(views, controllers, view, controller) {
		if (controller.onunload != null && unloaders.map(function(u) {return u.handler}).indexOf(controller.onunload) < 0) {
			unloaders.push({
				controller: controller,
				handler: controller.onunload
			})
		}

		views.push(view)
		controllers.push(controller)
	}

	var forcing = false
	function checkView(data, view, cached, cachedControllers, controllers, views) {
		var controller = getController(cached.views, view, cachedControllers, data.controller)
		var key = data && data.attrs && data.attrs.key
		data = pendingRequests === 0 || forcing || cachedControllers && cachedControllers.indexOf(controller) > -1 ? data.view(controller) : {tag: "placeholder"}
		if (data.subtree === "retain") return data;
		data.attrs = data.attrs || {}
		data.attrs.key = key
		updateLists(views, controllers, view, controller)
		return data
	}

	function markViews(data, cached, views, controllers) {
		var cachedControllers = cached && cached.controllers

		while (data.view != null) {
			data = checkView(
				data,
				data.view.$original || data.view,
				cached,
				cachedControllers,
				controllers,
				views)
		}

		return data
	}

	function buildObject( // eslint-disable-line max-statements
		data,
		cached,
		editable,
		parentElement,
		index,
		shouldReattach,
		namespace,
		configs
	) {
		var views = []
		var controllers = []

		data = markViews(data, cached, views, controllers)

		if (data.subtree === "retain") return cached

		if (!data.tag && controllers.length) {
			throw new Error("Component template must return a virtual " +
				"element, not an array, string, etc.")
		}

		data.attrs = data.attrs || {}
		cached.attrs = cached.attrs || {}

		var dataAttrKeys = Object.keys(data.attrs)
		var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0)

		maybeRecreateObject(data, cached, dataAttrKeys)

		if (!isString(data.tag)) return

		var isNew = cached.nodes.length === 0

		namespace = getObjectNamespace(data, namespace)

		var node
		if (isNew) {
			node = constructNode(data, namespace)
			// set attributes first, then create children
			var attrs = constructAttrs(data, node, namespace, hasKeys)

			var children = constructChildren(data, node, cached, editable,
				namespace, configs)

			cached = reconstructCached(
				data,
				attrs,
				children,
				node,
				namespace,
				views,
				controllers)
		} else {
			node = buildUpdatedNode(
				cached,
				data,
				editable,
				hasKeys,
				namespace,
				views,
				configs,
				controllers)
		}

		if (isNew || shouldReattach === true && node != null) {
			insertNode(parentElement, node, index)
		}

		// The configs are called after `build` finishes running
		scheduleConfigsToBeCalled(configs, data, node, isNew, cached)

		return cached
	}

	function build(
		parentElement,
		parentTag,
		parentCache,
		parentIndex,
		data,
		cached,
		shouldReattach,
		index,
		editable,
		namespace,
		configs
	) {
		/*
		 * `build` is a recursive function that manages creation/diffing/removal
		 * of DOM elements based on comparison between `data` and `cached` the
		 * diff algorithm can be summarized as this:
		 *
		 * 1 - compare `data` and `cached`
		 * 2 - if they are different, copy `data` to `cached` and update the DOM
		 *     based on what the difference is
		 * 3 - recursively apply this algorithm for every array and for the
		 *     children of every virtual element
		 *
		 * The `cached` data structure is essentially the same as the previous
		 * redraw's `data` data structure, with a few additions:
		 * - `cached` always has a property called `nodes`, which is a list of
		 *    DOM elements that correspond to the data represented by the
		 *    respective virtual element
		 * - in order to support attaching `nodes` as a property of `cached`,
		 *    `cached` is *always* a non-primitive object, i.e. if the data was
		 *    a string, then cached is a String instance. If data was `null` or
		 *    `undefined`, cached is `new String("")`
		 * - `cached also has a `configContext` property, which is the state
		 *    storage object exposed by config(element, isInitialized, context)
		 * - when `cached` is an Object, it represents a virtual element; when
		 *    it's an Array, it represents a list of elements; when it's a
		 *    String, Number or Boolean, it represents a text node
		 *
		 * `parentElement` is a DOM element used for W3C DOM API calls
		 * `parentTag` is only used for handling a corner case for textarea
		 * values
		 * `parentCache` is used to remove nodes in some multi-node cases
		 * `parentIndex` and `index` are used to figure out the offset of nodes.
		 * They're artifacts from before arrays started being flattened and are
		 * likely refactorable
		 * `data` and `cached` are, respectively, the new and old nodes being
		 * diffed
		 * `shouldReattach` is a flag indicating whether a parent node was
		 * recreated (if so, and if this node is reused, then this node must
		 * reattach itself to the new parent)
		 * `editable` is a flag that indicates whether an ancestor is
		 * contenteditable
		 * `namespace` indicates the closest HTML namespace as it cascades down
		 * from an ancestor
		 * `configs` is a list of config functions to run after the topmost
		 * `build` call finishes running
		 *
		 * there's logic that relies on the assumption that null and undefined
		 * data are equivalent to empty strings
		 * - this prevents lifecycle surprises from procedural helpers that mix
		 *   implicit and explicit return statements (e.g.
		 *   function foo() {if (cond) return m("div")}
		 * - it simplifies diffing code
		 */
		data = dataToString(data)
		if (data.subtree === "retain") return cached
		cached = makeCache(data, cached, index, parentIndex, parentCache)

		if (isArray(data)) {
			return buildArray(
				data,
				cached,
				parentElement,
				index,
				parentTag,
				shouldReattach,
				editable,
				namespace,
				configs)
		} else if (data != null && isObject(data)) {
			return buildObject(
				data,
				cached,
				editable,
				parentElement,
				index,
				shouldReattach,
				namespace,
				configs)
		} else if (!isFunction(data)) {
			return handleTextNode(
				cached,
				data,
				index,
				parentElement,
				shouldReattach,
				editable,
				parentTag)
		} else {
			return cached
		}
	}

	function sortChanges(a, b) {
		return a.action - b.action || a.index - b.index
	}

	function copyStyleAttrs(node, dataAttr, cachedAttr) {
		for (var rule in dataAttr) if (hasOwn.call(dataAttr, rule)) {
			if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) {
				node.style[rule] = dataAttr[rule]
			}
		}

		for (rule in cachedAttr) if (hasOwn.call(cachedAttr, rule)) {
			if (!hasOwn.call(dataAttr, rule)) node.style[rule] = ""
		}
	}

	var shouldUseSetAttribute = {
		list: 1,
		style: 1,
		form: 1,
		type: 1,
		width: 1,
		height: 1
	}

	function setSingleAttr(
		node,
		attrName,
		dataAttr,
		cachedAttr,
		tag,
		namespace
	) {
		if (attrName === "config" || attrName === "key") {
			// `config` isn't a real attribute, so ignore it
			return true
		} else if (isFunction(dataAttr) && attrName.slice(0, 2) === "on") {
			// hook event handlers to the auto-redrawing system
			node[attrName] = autoredraw(dataAttr, node)
		} else if (attrName === "style" && dataAttr != null &&
				isObject(dataAttr)) {
			// handle `style: {...}`
			copyStyleAttrs(node, dataAttr, cachedAttr)
		} else if (namespace != null) {
			// handle SVG
			if (attrName === "href") {
				node.setAttributeNS("http://www.w3.org/1999/xlink",
					"href", dataAttr)
			} else {
				node.setAttribute(
					attrName === "className" ? "class" : attrName,
					dataAttr)
			}
		} else if (attrName in node && !shouldUseSetAttribute[attrName]) {
			// handle cases that are properties (but ignore cases where we
			// should use setAttribute instead)
			//
			// - list and form are typically used as strings, but are DOM
			//   element references in js
			//
			// - when using CSS selectors (e.g. `m("[style='']")`), style is
			//   used as a string, but it's an object in js
			//
			// #348 don't set the value if not needed - otherwise, cursor
			// placement breaks in Chrome
			try {
				if (tag !== "input" || node[attrName] !== dataAttr) {
					node[attrName] = dataAttr
				}
			} catch (e) {
				node.setAttribute(attrName, dataAttr)
			}
		}
		else node.setAttribute(attrName, dataAttr)
	}

	function trySetAttr(
		node,
		attrName,
		dataAttr,
		cachedAttr,
		cachedAttrs,
		tag,
		namespace
	) {
		if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) {
			cachedAttrs[attrName] = dataAttr
			try {
				return setSingleAttr(
					node,
					attrName,
					dataAttr,
					cachedAttr,
					tag,
					namespace)
			} catch (e) {
				// swallow IE's invalid argument errors to mimic HTML's
				// fallback-to-doing-nothing-on-invalid-attributes behavior
				if (e.message.indexOf("Invalid argument") < 0) throw e
			}
		} else if (attrName === "value" && tag === "input" &&
				node.value !== dataAttr) {
			// #348 dataAttr may not be a string, so use loose comparison
			node.value = dataAttr
		}
	}

	function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
		for (var attrName in dataAttrs) if (hasOwn.call(dataAttrs, attrName)) {
			if (trySetAttr(
					node,
					attrName,
					dataAttrs[attrName],
					cachedAttrs[attrName],
					cachedAttrs,
					tag,
					namespace)) {
				continue
			}
		}
		return cachedAttrs
	}

	function clear(nodes, cached) {
		for (var i = nodes.length - 1; i > -1; i--) {
			if (nodes[i] && nodes[i].parentNode) {
				try {
					nodes[i].parentNode.removeChild(nodes[i])
				} catch (e) {
					/* eslint-disable max-len */
					// ignore if this fails due to order of events (see
					// http://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node)
					/* eslint-enable max-len */
				}
				cached = [].concat(cached)
				if (cached[i]) unload(cached[i])
			}
		}
		// release memory if nodes is an array. This check should fail if nodes
		// is a NodeList (see loop above)
		if (nodes.length) {
			nodes.length = 0
		}
	}

	function unload(cached) {
		if (cached.configContext && isFunction(cached.configContext.onunload)) {
			cached.configContext.onunload()
			cached.configContext.onunload = null
		}
		if (cached.controllers) {
			forEach(cached.controllers, function (controller) {
				if (isFunction(controller.onunload)) {
					controller.onunload({preventDefault: noop})
				}
			})
		}
		if (cached.children) {
			if (isArray(cached.children)) forEach(cached.children, unload)
			else if (cached.children.tag) unload(cached.children)
		}
	}

	function appendTextFragment(parentElement, data) {
		try {
			parentElement.appendChild(
				$document.createRange().createContextualFragment(data))
		} catch (e) {
			parentElement.insertAdjacentHTML("beforeend", data)
		}
	}

	function injectHTML(parentElement, index, data) {
		var nextSibling = parentElement.childNodes[index]
		if (nextSibling) {
			var isElement = nextSibling.nodeType !== 1
			var placeholder = $document.createElement("span")
			if (isElement) {
				parentElement.insertBefore(placeholder, nextSibling || null)
				placeholder.insertAdjacentHTML("beforebegin", data)
				parentElement.removeChild(placeholder)
			} else {
				nextSibling.insertAdjacentHTML("beforebegin", data)
			}
		} else {
			appendTextFragment(parentElement, data)
		}

		var nodes = []

		while (parentElement.childNodes[index] !== nextSibling) {
			nodes.push(parentElement.childNodes[index])
			index++
		}

		return nodes
	}

	function autoredraw(callback, object) {
		return function (e) {
			e = e || event
			m.redraw.strategy("diff")
			m.startComputation()
			try {
				return callback.call(object, e)
			} finally {
				endFirstComputation()
			}
		}
	}

	var html
	var documentNode = {
		appendChild: function (node) {
			if (html === undefined) html = $document.createElement("html")
			if ($document.documentElement &&
					$document.documentElement !== node) {
				$document.replaceChild(node, $document.documentElement)
			} else {
				$document.appendChild(node)
			}

			this.childNodes = $document.childNodes
		},

		insertBefore: function (node) {
			this.appendChild(node)
		},

		childNodes: []
	}

	var nodeCache = []
	var cellCache = {}

	m.render = function (root, cell, forceRecreation) {
		if (!root) {
			throw new Error("Ensure the DOM element being passed to " +
				"m.route/m.mount/m.render is not undefined.")
		}
		var configs = []
		var id = getCellCacheKey(root)
		var isDocumentRoot = root === $document
		var node

		if (isDocumentRoot || root === $document.documentElement) {
			node = documentNode
		} else {
			node = root
		}

		if (isDocumentRoot && cell.tag !== "html") {
			cell = {tag: "html", attrs: {}, children: cell}
		}

		if (cellCache[id] === undefined) clear(node.childNodes)
		if (forceRecreation === true) reset(root)

		cellCache[id] = build(
			node,
			null,
			undefined,
			undefined,
			cell,
			cellCache[id],
			false,
			0,
			null,
			undefined,
			configs)

		forEach(configs, function (config) { config() })
	}

	function getCellCacheKey(element) {
		var index = nodeCache.indexOf(element)
		return index < 0 ? nodeCache.push(element) - 1 : index
	}

	m.trust = function (value) {
		value = new String(value) // eslint-disable-line no-new-wrappers
		value.$trusted = true
		return value
	}

	function gettersetter(store) {
		function prop() {
			if (arguments.length) store = arguments[0]
			return store
		}

		prop.toJSON = function () {
			return store
		}

		return prop
	}

	m.prop = function (store) {
		if ((store != null && isObject(store) || isFunction(store)) &&
				isFunction(store.then)) {
			return propify(store)
		}

		return gettersetter(store)
	}

	var roots = []
	var components = []
	var controllers = []
	var lastRedrawId = null
	var lastRedrawCallTime = 0
	var computePreRedrawHook = null
	var computePostRedrawHook = null
	var topComponent
	var FRAME_BUDGET = 16 // 60 frames per second = 1 call per 16 ms

	function parameterize(component, args) {
		function controller() {
			/* eslint-disable no-invalid-this */
			return (component.controller || noop).apply(this, args) || this
			/* eslint-enable no-invalid-this */
		}

		if (component.controller) {
			controller.prototype = component.controller.prototype
		}

		function view(ctrl) {
			var currentArgs = [ctrl].concat(args)
			for (var i = 1; i < arguments.length; i++) {
				currentArgs.push(arguments[i])
			}

			return component.view.apply(component, currentArgs)
		}

		view.$original = component.view
		var output = {controller: controller, view: view}
		if (args[0] && args[0].key != null) output.attrs = {key: args[0].key}
		return output
	}

	m.component = function (component) {
		var args = [].slice.call(arguments, 1)

		return parameterize(component, args)
	}

	function checkPrevented(component, root, index, isPrevented) {
		if (!isPrevented) {
			m.redraw.strategy("all")
			m.startComputation()
			roots[index] = root
			var currentComponent

			if (component) {
				currentComponent = topComponent = component
			} else {
				currentComponent = topComponent = component = {controller: noop}
			}

			var controller = new (component.controller || noop)()

			// controllers may call m.mount recursively (via m.route redirects,
			// for example)
			// this conditional ensures only the last recursive m.mount call is
			// applied
			if (currentComponent === topComponent) {
				controllers[index] = controller
				components[index] = component
			}
			endFirstComputation()
			if (component === null) {
				removeRootElement(root, index)
			}
			return controllers[index]
		} else if (component == null) {
			removeRootElement(root, index)
		}
	}

	m.mount = m.module = function (root, component) {
		if (!root) {
			throw new Error("Please ensure the DOM element exists before " +
				"rendering a template into it.")
		}

		var index = roots.indexOf(root)
		if (index < 0) index = roots.length

		var isPrevented = false
		var event = {
			preventDefault: function () {
				isPrevented = true
				computePreRedrawHook = computePostRedrawHook = null
			}
		}

		forEach(unloaders, function (unloader) {
			unloader.handler.call(unloader.controller, event)
			unloader.controller.onunload = null
		})

		if (isPrevented) {
			forEach(unloaders, function (unloader) {
				unloader.controller.onunload = unloader.handler
			})
		} else {
			unloaders = []
		}

		if (controllers[index] && isFunction(controllers[index].onunload)) {
			controllers[index].onunload(event)
		}

		return checkPrevented(component, root, index, isPrevented)
	}

	function removeRootElement(root, index) {
		roots.splice(index, 1)
		controllers.splice(index, 1)
		components.splice(index, 1)
		reset(root)
		nodeCache.splice(getCellCacheKey(root), 1)
	}

	var redrawing = false
	m.redraw = function (force) {
		if (redrawing) return
		redrawing = true
		if (force) forcing = true

		try {
			// lastRedrawId is a positive number if a second redraw is requested
			// before the next animation frame
			// lastRedrawID is null if it's the first redraw and not an event
			// handler
			if (lastRedrawId && !force) {
				// when setTimeout: only reschedule redraw if time between now
				// and previous redraw is bigger than a frame, otherwise keep
				// currently scheduled timeout
				// when rAF: always reschedule redraw
				if ($requestAnimationFrame === global.requestAnimationFrame ||
						new Date() - lastRedrawCallTime > FRAME_BUDGET) {
					if (lastRedrawId > 0) $cancelAnimationFrame(lastRedrawId)
					lastRedrawId = $requestAnimationFrame(redraw, FRAME_BUDGET)
				}
			} else {
				redraw()
				lastRedrawId = $requestAnimationFrame(function () {
					lastRedrawId = null
				}, FRAME_BUDGET)
			}
		} finally {
			redrawing = forcing = false
		}
	}

	m.redraw.strategy = m.prop()
	function redraw() {
		if (computePreRedrawHook) {
			computePreRedrawHook()
			computePreRedrawHook = null
		}
		forEach(roots, function (root, i) {
			var component = components[i]
			if (controllers[i]) {
				var args = [controllers[i]]
				m.render(root,
					component.view ? component.view(controllers[i], args) : "")
			}
		})
		// after rendering within a routed context, we need to scroll back to
		// the top, and fetch the document title for history.pushState
		if (computePostRedrawHook) {
			computePostRedrawHook()
			computePostRedrawHook = null
		}
		lastRedrawId = null
		lastRedrawCallTime = new Date()
		m.redraw.strategy("diff")
	}

	function endFirstComputation() {
		if (m.redraw.strategy() === "none") {
			pendingRequests--
			m.redraw.strategy("diff")
		} else {
			m.endComputation()
		}
	}

	m.withAttr = function (prop, withAttrCallback, callbackThis) {
		return function (e) {
			e = e || event
			/* eslint-disable no-invalid-this */
			var currentTarget = e.currentTarget || this
			var _this = callbackThis || this
			/* eslint-enable no-invalid-this */
			var target = prop in currentTarget ?
				currentTarget[prop] :
				currentTarget.getAttribute(prop)
			withAttrCallback.call(_this, target)
		}
	}

	// routing
	var modes = {pathname: "", hash: "#", search: "?"}
	var redirect = noop
	var isDefaultRoute = false
	var routeParams, currentRoute

	m.route = function (root, arg1, arg2, vdom) { // eslint-disable-line
		// m.route()
		if (arguments.length === 0) return currentRoute
		// m.route(el, defaultRoute, routes)
		if (arguments.length === 3 && isString(arg1)) {
			redirect = function (source) {
				var path = currentRoute = normalizeRoute(source)
				if (!routeByValue(root, arg2, path)) {
					if (isDefaultRoute) {
						throw new Error("Ensure the default route matches " +
							"one of the routes defined in m.route")
					}

					isDefaultRoute = true
					m.route(arg1, true)
					isDefaultRoute = false
				}
			}

			var listener = m.route.mode === "hash" ?
				"onhashchange" :
				"onpopstate"

			global[listener] = function () {
				var path = $location[m.route.mode]
				if (m.route.mode === "pathname") path += $location.search
				if (currentRoute !== normalizeRoute(path)) redirect(path)
			}

			computePreRedrawHook = setScroll
			global[listener]()

			return
		}

		// config: m.route
		if (root.addEventListener || root.attachEvent) {
			var base = m.route.mode !== "pathname" ? $location.pathname : ""
			root.href = base + modes[m.route.mode] + vdom.attrs.href
			if (root.addEventListener) {
				root.removeEventListener("click", routeUnobtrusive)
				root.addEventListener("click", routeUnobtrusive)
			} else {
				root.detachEvent("onclick", routeUnobtrusive)
				root.attachEvent("onclick", routeUnobtrusive)
			}

			return
		}
		// m.route(route, params, shouldReplaceHistoryEntry)
		if (isString(root)) {
			var oldRoute = currentRoute
			currentRoute = root

			var args = arg1 || {}
			var queryIndex = currentRoute.indexOf("?")
			var params

			if (queryIndex > -1) {
				params = parseQueryString(currentRoute.slice(queryIndex + 1))
			} else {
				params = {}
			}

			for (var i in args) if (hasOwn.call(args, i)) {
				params[i] = args[i]
			}

			var querystring = buildQueryString(params)
			var currentPath

			if (queryIndex > -1) {
				currentPath = currentRoute.slice(0, queryIndex)
			} else {
				currentPath = currentRoute
			}

			if (querystring) {
				currentRoute = currentPath +
					(currentPath.indexOf("?") === -1 ? "?" : "&") +
					querystring
			}

			var replaceHistory =
				(arguments.length === 3 ? arg2 : arg1) === true ||
				oldRoute === root

			if (global.history.pushState) {
				var method = replaceHistory ? "replaceState" : "pushState"
				computePreRedrawHook = setScroll
				computePostRedrawHook = function () {
					global.history[method](null, $document.title,
						modes[m.route.mode] + currentRoute)
				}
				redirect(modes[m.route.mode] + currentRoute)
			} else {
				$location[m.route.mode] = currentRoute
				redirect(modes[m.route.mode] + currentRoute)
			}
		}
	}

	m.route.param = function (key) {
		if (!routeParams) {
			throw new Error("You must call m.route(element, defaultRoute, " +
				"routes) before calling m.route.param()")
		}

		if (!key) {
			return routeParams
		}

		return routeParams[key]
	}

	m.route.mode = "search"

	function normalizeRoute(route) {
		return route.slice(modes[m.route.mode].length)
	}

	function routeByValue(root, router, path) {
		routeParams = {}

		var queryStart = path.indexOf("?")
		if (queryStart !== -1) {
			routeParams = parseQueryString(
				path.substr(queryStart + 1, path.length))
			path = path.substr(0, queryStart)
		}

		// Get all routes and check if there's
		// an exact match for the current path
		var keys = Object.keys(router)
		var index = keys.indexOf(path)

		if (index !== -1){
			m.mount(root, router[keys [index]])
			return true
		}

		for (var route in router) if (hasOwn.call(router, route)) {
			if (route === path) {
				m.mount(root, router[route])
				return true
			}

			var matcher = new RegExp("^" + route
				.replace(/:[^\/]+?\.{3}/g, "(.*?)")
				.replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")

			if (matcher.test(path)) {
				/* eslint-disable no-loop-func */
				path.replace(matcher, function () {
					var keys = route.match(/:[^\/]+/g) || []
					var values = [].slice.call(arguments, 1, -2)
					forEach(keys, function (key, i) {
						routeParams[key.replace(/:|\./g, "")] =
							decodeURIComponent(values[i])
					})
					m.mount(root, router[route])
				})
				/* eslint-enable no-loop-func */
				return true
			}
		}
	}

	function routeUnobtrusive(e) {
		e = e || event
		if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return

		if (e.preventDefault) {
			e.preventDefault()
		} else {
			e.returnValue = false
		}

		var currentTarget = e.currentTarget || e.srcElement
		var args

		if (m.route.mode === "pathname" && currentTarget.search) {
			args = parseQueryString(currentTarget.search.slice(1))
		} else {
			args = {}
		}

		while (currentTarget && !/a/i.test(currentTarget.nodeName)) {
			currentTarget = currentTarget.parentNode
		}

		// clear pendingRequests because we want an immediate route change
		pendingRequests = 0
		m.route(currentTarget[m.route.mode]
			.slice(modes[m.route.mode].length), args)
	}

	function setScroll() {
		if (m.route.mode !== "hash" && $location.hash) {
			$location.hash = $location.hash
		} else {
			global.scrollTo(0, 0)
		}
	}

	function buildQueryString(object, prefix) {
		var duplicates = {}
		var str = []

		for (var prop in object) if (hasOwn.call(object, prop)) {
			var key = prefix ? prefix + "[" + prop + "]" : prop
			var value = object[prop]

			if (value === null) {
				str.push(encodeURIComponent(key))
			} else if (isObject(value)) {
				str.push(buildQueryString(value, key))
			} else if (isArray(value)) {
				var keys = []
				duplicates[key] = duplicates[key] || {}
				/* eslint-disable no-loop-func */
				forEach(value, function (item) {
					/* eslint-enable no-loop-func */
					if (!duplicates[key][item]) {
						duplicates[key][item] = true
						keys.push(encodeURIComponent(key) + "=" +
							encodeURIComponent(item))
					}
				})
				str.push(keys.join("&"))
			} else if (value !== undefined) {
				str.push(encodeURIComponent(key) + "=" +
					encodeURIComponent(value))
			}
		}
		return str.join("&")
	}

	function parseQueryString(str) {
		if (str === "" || str == null) return {}
		if (str.charAt(0) === "?") str = str.slice(1)

		var pairs = str.split("&")
		var params = {}

		forEach(pairs, function (string) {
			var pair = string.split("=")
			var key = decodeURIComponent(pair[0])
			var value = pair.length === 2 ? decodeURIComponent(pair[1]) : null
			if (params[key] != null) {
				if (!isArray(params[key])) params[key] = [params[key]]
				params[key].push(value)
			}
			else params[key] = value
		})

		return params
	}

	m.route.buildQueryString = buildQueryString
	m.route.parseQueryString = parseQueryString

	function reset(root) {
		var cacheKey = getCellCacheKey(root)
		clear(root.childNodes, cellCache[cacheKey])
		cellCache[cacheKey] = undefined
	}

	m.deferred = function () {
		var deferred = new Deferred()
		deferred.promise = propify(deferred.promise)
		return deferred
	}

	function propify(promise, initialValue) {
		var prop = m.prop(initialValue)
		promise.then(prop)
		prop.then = function (resolve, reject) {
			return propify(promise.then(resolve, reject), initialValue)
		}

		prop.catch = prop.then.bind(null, null)
		return prop
	}
	// Promiz.mithril.js | Zolmeister | MIT
	// a modified version of Promiz.js, which does not conform to Promises/A+
	// for two reasons:
	//
	// 1) `then` callbacks are called synchronously (because setTimeout is too
	//    slow, and the setImmediate polyfill is too big
	//
	// 2) throwing subclasses of Error cause the error to be bubbled up instead
	//    of triggering rejection (because the spec does not account for the
	//    important use case of default browser error handling, i.e. message w/
	//    line number)

	var RESOLVING = 1
	var REJECTING = 2
	var RESOLVED = 3
	var REJECTED = 4

	function Deferred(onSuccess, onFailure) {
		var self = this
		var state = 0
		var promiseValue = 0
		var next = []

		self.promise = {}

		self.resolve = function (value) {
			if (!state) {
				promiseValue = value
				state = RESOLVING

				fire()
			}

			return self
		}

		self.reject = function (value) {
			if (!state) {
				promiseValue = value
				state = REJECTING

				fire()
			}

			return self
		}

		self.promise.then = function (onSuccess, onFailure) {
			var deferred = new Deferred(onSuccess, onFailure)

			if (state === RESOLVED) {
				deferred.resolve(promiseValue)
			} else if (state === REJECTED) {
				deferred.reject(promiseValue)
			} else {
				next.push(deferred)
			}

			return deferred.promise
		}

		function finish(type) {
			state = type || REJECTED
			next.map(function (deferred) {
				if (state === RESOLVED) {
					deferred.resolve(promiseValue)
				} else {
					deferred.reject(promiseValue)
				}
			})
		}

		function thennable(then, success, failure, notThennable) {
			if (((promiseValue != null && isObject(promiseValue)) ||
					isFunction(promiseValue)) && isFunction(then)) {
				try {
					// count protects against abuse calls from spec checker
					var count = 0
					then.call(promiseValue, function (value) {
						if (count++) return
						promiseValue = value
						success()
					}, function (value) {
						if (count++) return
						promiseValue = value
						failure()
					})
				} catch (e) {
					m.deferred.onerror(e)
					promiseValue = e
					failure()
				}
			} else {
				notThennable()
			}
		}

		function fire() {
			// check if it's a thenable
			var then
			try {
				then = promiseValue && promiseValue.then
			} catch (e) {
				m.deferred.onerror(e)
				promiseValue = e
				state = REJECTING
				return fire()
			}

			if (state === REJECTING) {
				m.deferred.onerror(promiseValue)
			}

			thennable(then, function () {
				state = RESOLVING
				fire()
			}, function () {
				state = REJECTING
				fire()
			}, function () {
				try {
					if (state === RESOLVING && isFunction(onSuccess)) {
						promiseValue = onSuccess(promiseValue)
					} else if (state === REJECTING && isFunction(onFailure)) {
						promiseValue = onFailure(promiseValue)
						state = RESOLVING
					}
				} catch (e) {
					m.deferred.onerror(e)
					promiseValue = e
					return finish()
				}

				if (promiseValue === self) {
					promiseValue = TypeError()
					finish()
				} else {
					thennable(then, function () {
						finish(RESOLVED)
					}, finish, function () {
						finish(state === RESOLVING && RESOLVED)
					})
				}
			})
		}
	}

	m.deferred.onerror = function (e) {
		if (type.call(e) === "[object Error]" &&
				!/ Error/.test(e.constructor.toString())) {
			pendingRequests = 0
			throw e
		}
	}

	m.sync = function (args) {
		var deferred = m.deferred()
		var outstanding = args.length
		var results = new Array(outstanding)
		var method = "resolve"

		function synchronizer(pos, resolved) {
			return function (value) {
				results[pos] = value
				if (!resolved) method = "reject"
				if (--outstanding === 0) {
					deferred.promise(results)
					deferred[method](results)
				}
				return value
			}
		}

		if (args.length > 0) {
			forEach(args, function (arg, i) {
				arg.then(synchronizer(i, true), synchronizer(i, false))
			})
		} else {
			deferred.resolve([])
		}

		return deferred.promise
	}

	function identity(value) { return value }

	function handleJsonp(options) {
		var callbackKey = "mithril_callback_" +
			new Date().getTime() + "_" +
			(Math.round(Math.random() * 1e16)).toString(36)

		var script = $document.createElement("script")

		global[callbackKey] = function (resp) {
			script.parentNode.removeChild(script)
			options.onload({
				type: "load",
				target: {
					responseText: resp
				}
			})
			global[callbackKey] = undefined
		}

		script.onerror = function () {
			script.parentNode.removeChild(script)

			options.onerror({
				type: "error",
				target: {
					status: 500,
					responseText: JSON.stringify({
						error: "Error making jsonp request"
					})
				}
			})
			global[callbackKey] = undefined

			return false
		}

		script.onload = function () {
			return false
		}

		script.src = options.url +
			(options.url.indexOf("?") > 0 ? "&" : "?") +
			(options.callbackKey ? options.callbackKey : "callback") +
			"=" + callbackKey +
			"&" + buildQueryString(options.data || {})

		$document.body.appendChild(script)
	}

	function createXhr(options) {
		var xhr = new global.XMLHttpRequest()
		xhr.open(options.method, options.url, true, options.user,
			options.password)

		xhr.onreadystatechange = function () {
			if (xhr.readyState === 4) {
				if (xhr.status >= 200 && xhr.status < 300) {
					options.onload({type: "load", target: xhr})
				} else {
					options.onerror({type: "error", target: xhr})
				}
			}
		}

		if (options.serialize === JSON.stringify &&
				options.data &&
				options.method !== "GET") {
			xhr.setRequestHeader("Content-Type",
				"application/json; charset=utf-8")
		}

		if (options.deserialize === JSON.parse) {
			xhr.setRequestHeader("Accept", "application/json, text/*")
		}

		if (isFunction(options.config)) {
			var maybeXhr = options.config(xhr, options)
			if (maybeXhr != null) xhr = maybeXhr
		}

		var data = options.method === "GET" || !options.data ? "" : options.data

		if (data && !isString(data) && data.constructor !== global.FormData) {
			throw new Error("Request data should be either be a string or " +
				"FormData. Check the `serialize` option in `m.request`")
		}

		xhr.send(data)
		return xhr
	}

	function ajax(options) {
		if (options.dataType && options.dataType.toLowerCase() === "jsonp") {
			return handleJsonp(options)
		} else {
			return createXhr(options)
		}
	}

	function bindData(options, data, serialize) {
		if (options.method === "GET" && options.dataType !== "jsonp") {
			var prefix = options.url.indexOf("?") < 0 ? "?" : "&"
			var querystring = buildQueryString(data)
			options.url += (querystring ? prefix + querystring : "")
		} else {
			options.data = serialize(data)
		}
	}

	function parameterizeUrl(url, data) {
		if (data) {
			url = url.replace(/:[a-z]\w+/gi, function(token){
				var key = token.slice(1)
				var value = data[key]
				delete data[key]
				return value
			})
		}
		return url
	}

	m.request = function (options) {
		if (options.background !== true) m.startComputation()
		var deferred = new Deferred()
		var isJSONP = options.dataType &&
			options.dataType.toLowerCase() === "jsonp"

		var serialize, deserialize, extract

		if (isJSONP) {
			serialize = options.serialize =
			deserialize = options.deserialize = identity

			extract = function (jsonp) { return jsonp.responseText }
		} else {
			serialize = options.serialize = options.serialize || JSON.stringify

			deserialize = options.deserialize =
				options.deserialize || JSON.parse
			extract = options.extract || function (xhr) {
				if (xhr.responseText.length || deserialize !== JSON.parse) {
					return xhr.responseText
				} else {
					return null
				}
			}
		}

		options.method = (options.method || "GET").toUpperCase()
		options.url = parameterizeUrl(options.url, options.data)
		bindData(options, options.data, serialize)
		options.onload = options.onerror = function (ev) {
			try {
				ev = ev || event
				var response = deserialize(extract(ev.target, options))
				if (ev.type === "load") {
					if (options.unwrapSuccess) {
						response = options.unwrapSuccess(response, ev.target)
					}

					if (isArray(response) && options.type) {
						forEach(response, function (res, i) {
							response[i] = new options.type(res)
						})
					} else if (options.type) {
						response = new options.type(response)
					}

					deferred.resolve(response)
				} else {
					if (options.unwrapError) {
						response = options.unwrapError(response, ev.target)
					}

					deferred.reject(response)
				}
			} catch (e) {
				deferred.reject(e)
			} finally {
				if (options.background !== true) m.endComputation()
			}
		}

		ajax(options)
		deferred.promise = propify(deferred.promise, options.initialValue)
		return deferred.promise
	}

	return m
})

});
require.register("mithril.min", function(exports, require, module) {
/*
Mithril v0.2.3
http://mithril.js.org
(c) 2014-2016 Leo Horie
License: MIT
*/
!function(a,b){"use strict";var c=b(a);"object"==typeof module&&null!=module&&module.exports?module.exports=c:"function"==typeof define&&define.amd?define(function(){return c}):a.m=c}("undefined"!=typeof window?window:{},function(a,b){"use strict";function c(a){return"function"==typeof a}function d(a){return"[object Object]"===Aa.call(a)}function e(a){return"[object String]"===Aa.call(a)}function f(){}function g(a){va=a.document,wa=a.location,ya=a.cancelAnimationFrame||a.clearTimeout,xa=a.requestAnimationFrame||a.setTimeout}function h(a,b){for(var c,d=[],e=/(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g;c=e.exec(b);)if(""===c[1]&&c[2])a.tag=c[2];else if("#"===c[1])a.attrs.id=c[2];else if("."===c[1])d.push(c[2]);else if("["===c[3][0]){var f=/\[(.+?)(?:=("|'|)(.*?)\2)?\]/.exec(c[3]);a.attrs[f[1]]=f[3]||(f[2]?"":!0)}return d}function i(a,b){var c=b?a.slice(1):a;return 1===c.length&&Ba(c[0])?c[0]:c}function j(a,b,c){var d="class"in b?"class":"className";for(var e in b)za.call(b,e)&&(e===d&&null!=b[e]&&""!==b[e]?(c.push(b[e]),a[e]=""):a[e]=b[e]);c.length&&(a[d]=c.join(" "))}function k(a,b){var c=[].slice.call(arguments,1);if(d(a))return ba(a,c);if(!e(a))throw new Error("selector in m(selector, attrs, children) should be a string");var f=null!=b&&d(b)&&!("tag"in b||"view"in b||"subtree"in b),g=f?b:{},k={tag:"div",attrs:{},children:i(c,f)};return j(k.attrs,g,h(k,a)),k}function l(a,b){for(var c=0;c<a.length&&!b(a[c],c++););}function m(a,b){l(a,function(a,c){return(a=a&&a.attrs)&&null!=a.key&&b(a,c)})}function n(a){try{if(null!=a&&null!=a.toString())return a}catch(b){}return""}function o(a,b,c,d){try{q(a,b,c),b.nodeValue=d}catch(e){}}function p(a){for(var b=0;b<a.length;b++)Ba(a[b])&&(a=a.concat.apply([],a),b--);return a}function q(a,b,c){a.insertBefore(b,a.childNodes[c]||null)}function r(a,b,c,d){m(a,function(a,d){b[a=a.key]=b[a]?{action:Fa,index:d,from:b[a].index,element:c.nodes[b[a].index]||va.createElement("div")}:{action:Ea,index:d}});var e=[];for(var f in b)za.call(b,f)&&e.push(b[f]);var g=e.sort(R),h=new Array(c.length);return h.nodes=c.nodes.slice(),l(g,function(b){var e=b.index;if(b.action===Da&&(W(c[e].nodes,c[e]),h.splice(e,1)),b.action===Ea){var f=va.createElement("div");f.key=a[e].attrs.key,q(d,f,e),h.splice(e,0,{attrs:{key:a[e].attrs.key},nodes:[f]}),h.nodes[e]=f}if(b.action===Fa){var g=b.element,i=d.childNodes[e];i!==g&&null!==g&&d.insertBefore(g,i||null),h[e]=c[b.from],h.nodes[e]=g}}),h}function s(a,b,c,d){var e=a.length!==b.length;return e||m(a,function(a,c){var d=b[c];return e=d&&d.attrs&&d.attrs.key!==a.key}),e?r(a,c,b,d):b}function t(a,b,c){l(a,function(a,d){null!=b[d]&&c.push.apply(c,b[d].nodes)}),l(b.nodes,function(a,d){null!=a.parentNode&&c.indexOf(a)<0&&W([a],[b[d]])}),a.length<b.length&&(b.length=a.length),b.nodes=c}function u(a){var b=0;m(a,function(){return l(a,function(a){(a=a&&a.attrs)&&null==a.key&&(a.key="__mithril__"+b++)}),1})}function v(a,b,c){return a.tag!==b.tag?!0:c.sort().join()!==Object.keys(b.attrs).sort().join()?!0:a.attrs.id!==b.attrs.id?!0:a.attrs.key!==b.attrs.key?!0:"all"===k.redraw.strategy()?!b.configContext||b.configContext.retain!==!0:"diff"===k.redraw.strategy()?b.configContext&&b.configContext.retain===!1:!1}function w(a,b,d){v(a,b,d)&&(b.nodes.length&&W(b.nodes),b.configContext&&c(b.configContext.onunload)&&b.configContext.onunload(),b.controllers&&l(b.controllers,function(a){a.onunload&&a.onunload({preventDefault:f})}))}function x(a,b){return a.attrs.xmlns?a.attrs.xmlns:"svg"===a.tag?"http://www.w3.org/2000/svg":"math"===a.tag?"http://www.w3.org/1998/Math/MathML":b}function y(a,b,c){c.length&&(a.views=b,a.controllers=c,l(c,function(a){if(a.onunload&&a.onunload.$old&&(a.onunload=a.onunload.$old),Ga&&a.onunload){var b=a.onunload;a.onunload=f,a.onunload.$old=b}}))}function z(a,b,d,e,f){if(c(b.attrs.config)){var g=f.configContext=f.configContext||{};a.push(function(){return b.attrs.config.call(b,d,!e,g,f)})}}function A(a,c,d,e,f,g,h,i){var j=a.nodes[0];return e&&V(j,c.tag,c.attrs,a.attrs,f),a.children=Q(j,c.tag,b,b,c.children,a.children,!1,0,c.attrs.contenteditable?j:d,f,h),a.nodes.intact=!0,i.length&&(a.views=g,a.controllers=i),j}function B(a,b,c){var d;a.$trusted?d=Z(b,c,a):(d=[va.createTextNode(a)],b.nodeName in Ca||q(b,d[0],c));var e;return e="string"==typeof a||"number"==typeof a||"boolean"==typeof a?new a.constructor(a):a,e.nodes=d,e}function C(a,b,c,d,e,f){var g=b.nodes;return d&&d===va.activeElement||(a.$trusted?(W(g,b),g=Z(c,e,a)):"textarea"===f?c.value=a:d?d.innerHTML=a:((1===g[0].nodeType||g.length>1||g[0].nodeValue.trim&&!g[0].nodeValue.trim())&&(W(b.nodes,b),g=[va.createTextNode(a)]),o(c,g[0],e,a))),b=new a.constructor(a),b.nodes=g,b}function D(a,b,c,d,e,f,g){return a.nodes.length?a.valueOf()!==b.valueOf()||e?C(b,a,d,f,c,g):(a.nodes.intact=!0,a):B(b,d,c)}function E(a){if(a.$trusted){var b=a.match(/<[^\/]|\>\s*[^<]/g);if(null!=b)return b.length}else if(Ba(a))return a.length;return 1}function F(a,c,d,e,f,g,h,i,j){a=p(a);var k=[],l=c.length===a.length,n=0,o={},q=!1;m(c,function(a,b){q=!0,o[c[b].attrs.key]={action:Da,index:b}}),u(a),q&&(c=s(a,c,o,d));for(var r=0,v=0,w=a.length;w>v;v++){var x=Q(d,f,c,e,a[v],c[r],g,e+n||n,h,i,j);x!==b&&(l=l&&x.nodes.intact,n+=E(x),c[r++]=x)}return l||t(a,c,k),c}function G(a,b,c,d,e){if(null!=b){if(Aa.call(b)===Aa.call(a))return b;if(e&&e.nodes){var f=c-d,g=f+(Ba(a)?a:b.nodes).length;W(e.nodes.slice(f,g),e.slice(f,g))}else b.nodes&&W(b.nodes,b)}return b=new a.constructor,b.tag&&(b={}),b.nodes=[],b}function H(a,b){return a.attrs.is?null==b?va.createElement(a.tag,a.attrs.is):va.createElementNS(b,a.tag,a.attrs.is):null==b?va.createElement(a.tag):va.createElementNS(b,a.tag)}function I(a,b,c,d){return d?V(b,a.tag,a.attrs,{},c):a.attrs}function J(a,c,d,e,f,g){return null!=a.children&&a.children.length>0?Q(c,a.tag,b,b,a.children,d.children,!0,0,a.attrs.contenteditable?c:e,f,g):a.children}function K(a,b,c,d,e,f,g){var h={tag:a.tag,attrs:b,children:c,nodes:[d]};return y(h,f,g),h.children&&!h.children.nodes&&(h.children.nodes=[]),"select"===a.tag&&"value"in a.attrs&&V(d,a.tag,{value:a.attrs.value},{},e),h}function L(a,b,d,e){var f;return f="diff"===k.redraw.strategy()&&a?a.indexOf(b):-1,f>-1?d[f]:c(e)?new e:{}}function M(a,b,c,d){null!=d.onunload&&Ia.map(function(a){return a.handler}).indexOf(d.onunload)<0&&Ia.push({controller:d,handler:d.onunload}),a.push(c),b.push(d)}function N(a,b,c,d,e,f){var g=L(c.views,b,d,a.controller),h=a&&a.attrs&&a.attrs.key;return a=0===Ga||Ja||d&&d.indexOf(g)>-1?a.view(g):{tag:"placeholder"},"retain"===a.subtree?a:(a.attrs=a.attrs||{},a.attrs.key=h,M(f,e,b,g),a)}function O(a,b,c,d){for(var e=b&&b.controllers;null!=a.view;)a=N(a,a.view.$original||a.view,b,e,d,c);return a}function P(a,b,c,d,f,g,h,i){var j=[],k=[];if(a=O(a,b,j,k),"retain"===a.subtree)return b;if(!a.tag&&k.length)throw new Error("Component template must return a virtual element, not an array, string, etc.");a.attrs=a.attrs||{},b.attrs=b.attrs||{};var l=Object.keys(a.attrs),m=l.length>("key"in a.attrs?1:0);if(w(a,b,l),e(a.tag)){var n=0===b.nodes.length;h=x(a,h);var o;if(n){o=H(a,h);var p=I(a,o,h,m),r=J(a,o,b,c,h,i);b=K(a,p,r,o,h,j,k)}else o=A(b,a,c,m,h,j,i,k);return(n||g===!0&&null!=o)&&q(d,o,f),z(i,a,o,n,b),b}}function Q(a,b,e,f,g,h,i,j,k,l,m){return g=n(g),"retain"===g.subtree?h:(h=G(g,h,j,f,e),Ba(g)?F(g,h,a,j,b,i,k,l,m):null!=g&&d(g)?P(g,h,k,a,j,i,l,m):c(g)?h:D(h,g,j,a,i,k,b))}function R(a,b){return a.action-b.action||a.index-b.index}function S(a,b,c){for(var d in b)za.call(b,d)&&(null==c||c[d]!==b[d])&&(a.style[d]=b[d]);for(d in c)za.call(c,d)&&(za.call(b,d)||(a.style[d]=""))}function T(a,b,e,f,g,h){if("config"===b||"key"===b)return!0;if(c(e)&&"on"===b.slice(0,2))a[b]=$(e,a);else if("style"===b&&null!=e&&d(e))S(a,e,f);else if(null!=h)"href"===b?a.setAttributeNS("http://www.w3.org/1999/xlink","href",e):a.setAttribute("className"===b?"class":b,e);else if(b in a&&!Ka[b])try{("input"!==g||a[b]!==e)&&(a[b]=e)}catch(i){a.setAttribute(b,e)}else a.setAttribute(b,e)}function U(a,b,c,d,e,f,g){if(b in e&&d===c)"value"===b&&"input"===f&&a.value!==c&&(a.value=c);else{e[b]=c;try{return T(a,b,c,d,f,g)}catch(h){if(h.message.indexOf("Invalid argument")<0)throw h}}}function V(a,b,c,d,e){for(var f in c)if(za.call(c,f)&&U(a,f,c[f],d[f],d,b,e))continue;return d}function W(a,b){for(var c=a.length-1;c>-1;c--)if(a[c]&&a[c].parentNode){try{a[c].parentNode.removeChild(a[c])}catch(d){}b=[].concat(b),b[c]&&X(b[c])}a.length&&(a.length=0)}function X(a){a.configContext&&c(a.configContext.onunload)&&(a.configContext.onunload(),a.configContext.onunload=null),a.controllers&&l(a.controllers,function(a){c(a.onunload)&&a.onunload({preventDefault:f})}),a.children&&(Ba(a.children)?l(a.children,X):a.children.tag&&X(a.children))}function Y(a,b){try{a.appendChild(va.createRange().createContextualFragment(b))}catch(c){a.insertAdjacentHTML("beforeend",b)}}function Z(a,b,c){var d=a.childNodes[b];if(d){var e=1!==d.nodeType,f=va.createElement("span");e?(a.insertBefore(f,d||null),f.insertAdjacentHTML("beforebegin",c),a.removeChild(f)):d.insertAdjacentHTML("beforebegin",c)}else Y(a,c);for(var g=[];a.childNodes[b]!==d;)g.push(a.childNodes[b]),b++;return g}function $(a,b){return function(c){c=c||event,k.redraw.strategy("diff"),k.startComputation();try{return a.call(b,c)}finally{fa()}}}function _(a){var b=Ma.indexOf(a);return 0>b?Ma.push(a)-1:b}function aa(a){function b(){return arguments.length&&(a=arguments[0]),a}return b.toJSON=function(){return a},b}function ba(a,b){function c(){return(a.controller||f).apply(this,b)||this}function d(c){for(var d=[c].concat(b),e=1;e<arguments.length;e++)d.push(arguments[e]);return a.view.apply(a,d)}a.controller&&(c.prototype=a.controller.prototype),d.$original=a.view;var e={controller:c,view:d};return b[0]&&null!=b[0].key&&(e.attrs={key:b[0].key}),e}function ca(a,b,c,d){if(!d){k.redraw.strategy("all"),k.startComputation(),Pa[c]=b;var e;e=Oa=a?a:a={controller:f};var g=new(a.controller||f);return e===Oa&&(Ra[c]=g,Qa[c]=a),fa(),null===a&&da(b,c),Ra[c]}null==a&&da(b,c)}function da(a,b){Pa.splice(b,1),Ra.splice(b,1),Qa.splice(b,1),ma(a),Ma.splice(_(a),1)}function ea(){Ua&&(Ua(),Ua=null),l(Pa,function(a,b){var c=Qa[b];if(Ra[b]){var d=[Ra[b]];k.render(a,c.view?c.view(Ra[b],d):"")}}),Va&&(Va(),Va=null),Sa=null,Ta=new Date,k.redraw.strategy("diff")}function fa(){"none"===k.redraw.strategy()?(Ga--,k.redraw.strategy("diff")):k.endComputation()}function ga(a){return a.slice($a[k.route.mode].length)}function ha(a,b,c){Ya={};var d=c.indexOf("?");-1!==d&&(Ya=la(c.substr(d+1,c.length)),c=c.substr(0,d));var e=Object.keys(b),f=e.indexOf(c);if(-1!==f)return k.mount(a,b[e[f]]),!0;for(var g in b)if(za.call(b,g)){if(g===c)return k.mount(a,b[g]),!0;var h=new RegExp("^"+g.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(h.test(c))return c.replace(h,function(){var c=g.match(/:[^\/]+/g)||[],d=[].slice.call(arguments,1,-2);l(c,function(a,b){Ya[a.replace(/:|\./g,"")]=decodeURIComponent(d[b])}),k.mount(a,b[g])}),!0}}function ia(a){if(a=a||event,!(a.ctrlKey||a.metaKey||a.shiftKey||2===a.which)){a.preventDefault?a.preventDefault():a.returnValue=!1;var b,c=a.currentTarget||a.srcElement;for(b="pathname"===k.route.mode&&c.search?la(c.search.slice(1)):{};c&&!/a/i.test(c.nodeName);)c=c.parentNode;Ga=0,k.route(c[k.route.mode].slice($a[k.route.mode].length),b)}}function ja(){"hash"!==k.route.mode&&wa.hash?wa.hash=wa.hash:a.scrollTo(0,0)}function ka(a,c){var e={},f=[];for(var g in a)if(za.call(a,g)){var h=c?c+"["+g+"]":g,i=a[g];if(null===i)f.push(encodeURIComponent(h));else if(d(i))f.push(ka(i,h));else if(Ba(i)){var j=[];e[h]=e[h]||{},l(i,function(a){e[h][a]||(e[h][a]=!0,j.push(encodeURIComponent(h)+"="+encodeURIComponent(a)))}),f.push(j.join("&"))}else i!==b&&f.push(encodeURIComponent(h)+"="+encodeURIComponent(i))}return f.join("&")}function la(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));var b=a.split("&"),c={};return l(b,function(a){var b=a.split("="),d=decodeURIComponent(b[0]),e=2===b.length?decodeURIComponent(b[1]):null;null!=c[d]?(Ba(c[d])||(c[d]=[c[d]]),c[d].push(e)):c[d]=e}),c}function ma(a){var c=_(a);W(a.childNodes,Na[c]),Na[c]=b}function na(a,b){var c=k.prop(b);return a.then(c),c.then=function(c,d){return na(a.then(c,d),b)},c["catch"]=c.then.bind(null,null),c}function oa(a,b){function e(a){i=a||eb,l.map(function(a){i===db?a.resolve(j):a.reject(j)})}function f(a,b,e,f){if((null!=j&&d(j)||c(j))&&c(a))try{var g=0;a.call(j,function(a){g++||(j=a,b())},function(a){g++||(j=a,e())})}catch(h){k.deferred.onerror(h),j=h,e()}else f()}function g(){var d;try{d=j&&j.then}catch(l){return k.deferred.onerror(l),j=l,i=cb,g()}i===cb&&k.deferred.onerror(j),f(d,function(){i=bb,g()},function(){i=cb,g()},function(){try{i===bb&&c(a)?j=a(j):i===cb&&c(b)&&(j=b(j),i=bb)}catch(g){return k.deferred.onerror(g),j=g,e()}j===h?(j=TypeError(),e()):f(d,function(){e(db)},e,function(){e(i===bb&&db)})})}var h=this,i=0,j=0,l=[];h.promise={},h.resolve=function(a){return i||(j=a,i=bb,g()),h},h.reject=function(a){return i||(j=a,i=cb,g()),h},h.promise.then=function(a,b){var c=new oa(a,b);return i===db?c.resolve(j):i===eb?c.reject(j):l.push(c),c.promise}}function pa(a){return a}function qa(c){var d="mithril_callback_"+(new Date).getTime()+"_"+Math.round(1e16*Math.random()).toString(36),e=va.createElement("script");a[d]=function(f){e.parentNode.removeChild(e),c.onload({type:"load",target:{responseText:f}}),a[d]=b},e.onerror=function(){return e.parentNode.removeChild(e),c.onerror({type:"error",target:{status:500,responseText:JSON.stringify({error:"Error making jsonp request"})}}),a[d]=b,!1},e.onload=function(){return!1},e.src=c.url+(c.url.indexOf("?")>0?"&":"?")+(c.callbackKey?c.callbackKey:"callback")+"="+d+"&"+ka(c.data||{}),va.body.appendChild(e)}function ra(b){var d=new a.XMLHttpRequest;if(d.open(b.method,b.url,!0,b.user,b.password),d.onreadystatechange=function(){4===d.readyState&&(d.status>=200&&d.status<300?b.onload({type:"load",target:d}):b.onerror({type:"error",target:d}))},b.serialize===JSON.stringify&&b.data&&"GET"!==b.method&&d.setRequestHeader("Content-Type","application/json; charset=utf-8"),b.deserialize===JSON.parse&&d.setRequestHeader("Accept","application/json, text/*"),c(b.config)){var f=b.config(d,b);null!=f&&(d=f)}var g="GET"!==b.method&&b.data?b.data:"";if(g&&!e(g)&&g.constructor!==a.FormData)throw new Error("Request data should be either be a string or FormData. Check the `serialize` option in `m.request`");return d.send(g),d}function sa(a){return a.dataType&&"jsonp"===a.dataType.toLowerCase()?qa(a):ra(a)}function ta(a,b,c){if("GET"===a.method&&"jsonp"!==a.dataType){var d=a.url.indexOf("?")<0?"?":"&",e=ka(b);a.url+=e?d+e:""}else a.data=c(b)}function ua(a,b){return b&&(a=a.replace(/:[a-z]\w+/gi,function(a){var c=a.slice(1),d=b[c];return delete b[c],d})),a}k.version=function(){return"v0.2.3"};var va,wa,xa,ya,za={}.hasOwnProperty,Aa={}.toString,Ba=Array.isArray||function(a){return"[object Array]"===Aa.call(a)},Ca={AREA:1,BASE:1,BR:1,COL:1,COMMAND:1,EMBED:1,HR:1,IMG:1,INPUT:1,KEYGEN:1,LINK:1,META:1,PARAM:1,SOURCE:1,TRACK:1,WBR:1};k.deps=function(b){return g(a=b||window),a},k.deps(a);var Da=1,Ea=2,Fa=3,Ga=0;k.startComputation=function(){Ga++},k.endComputation=function(){Ga>1?Ga--:(Ga=0,k.redraw())};var Ha,Ia=[],Ja=!1,Ka={list:1,style:1,form:1,type:1,width:1,height:1},La={appendChild:function(a){Ha===b&&(Ha=va.createElement("html")),va.documentElement&&va.documentElement!==a?va.replaceChild(a,va.documentElement):va.appendChild(a),this.childNodes=va.childNodes},insertBefore:function(a){this.appendChild(a)},childNodes:[]},Ma=[],Na={};k.render=function(a,c,d){if(!a)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var e,f=[],g=_(a),h=a===va;e=h||a===va.documentElement?La:a,h&&"html"!==c.tag&&(c={tag:"html",attrs:{},children:c}),Na[g]===b&&W(e.childNodes),d===!0&&ma(a),Na[g]=Q(e,null,b,b,c,Na[g],!1,0,null,b,f),l(f,function(a){a()})},k.trust=function(a){return a=new String(a),a.$trusted=!0,a},k.prop=function(a){return(null!=a&&d(a)||c(a))&&c(a.then)?na(a):aa(a)};var Oa,Pa=[],Qa=[],Ra=[],Sa=null,Ta=0,Ua=null,Va=null,Wa=16;k.component=function(a){var b=[].slice.call(arguments,1);return ba(a,b)},k.mount=k.module=function(a,b){if(!a)throw new Error("Please ensure the DOM element exists before rendering a template into it.");var d=Pa.indexOf(a);0>d&&(d=Pa.length);var e=!1,f={preventDefault:function(){e=!0,Ua=Va=null}};return l(Ia,function(a){a.handler.call(a.controller,f),a.controller.onunload=null}),e?l(Ia,function(a){a.controller.onunload=a.handler}):Ia=[],Ra[d]&&c(Ra[d].onunload)&&Ra[d].onunload(f),ca(b,a,d,e)};var Xa=!1;k.redraw=function(b){if(!Xa){Xa=!0,b&&(Ja=!0);try{Sa&&!b?(xa===a.requestAnimationFrame||new Date-Ta>Wa)&&(Sa>0&&ya(Sa),Sa=xa(ea,Wa)):(ea(),Sa=xa(function(){Sa=null},Wa))}finally{Xa=Ja=!1}}},k.redraw.strategy=k.prop(),k.withAttr=function(a,b,c){return function(d){d=d||event;var e=d.currentTarget||this,f=c||this,g=a in e?e[a]:e.getAttribute(a);b.call(f,g)}};var Ya,Za,$a={pathname:"",hash:"#",search:"?"},_a=f,ab=!1;k.route=function(b,c,d,f){if(0===arguments.length)return Za;if(3===arguments.length&&e(c)){_a=function(a){var e=Za=ga(a);if(!ha(b,d,e)){if(ab)throw new Error("Ensure the default route matches one of the routes defined in m.route");ab=!0,k.route(c,!0),ab=!1}};var g="hash"===k.route.mode?"onhashchange":"onpopstate";return a[g]=function(){var a=wa[k.route.mode];"pathname"===k.route.mode&&(a+=wa.search),Za!==ga(a)&&_a(a)},Ua=ja,void a[g]()}if(b.addEventListener||b.attachEvent){var h="pathname"!==k.route.mode?wa.pathname:"";return b.href=h+$a[k.route.mode]+f.attrs.href,void(b.addEventListener?(b.removeEventListener("click",ia),b.addEventListener("click",ia)):(b.detachEvent("onclick",ia),b.attachEvent("onclick",ia)))}if(e(b)){var i=Za;Za=b;var j,l=c||{},m=Za.indexOf("?");j=m>-1?la(Za.slice(m+1)):{};for(var n in l)za.call(l,n)&&(j[n]=l[n]);var o,p=ka(j);o=m>-1?Za.slice(0,m):Za,p&&(Za=o+(-1===o.indexOf("?")?"?":"&")+p);var q=(3===arguments.length?d:c)===!0||i===b;if(a.history.pushState){var r=q?"replaceState":"pushState";Ua=ja,Va=function(){a.history[r](null,va.title,$a[k.route.mode]+Za)},_a($a[k.route.mode]+Za)}else wa[k.route.mode]=Za,_a($a[k.route.mode]+Za)}},k.route.param=function(a){if(!Ya)throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()");return a?Ya[a]:Ya},k.route.mode="search",k.route.buildQueryString=ka,k.route.parseQueryString=la,k.deferred=function(){var a=new oa;return a.promise=na(a.promise),a};var bb=1,cb=2,db=3,eb=4;return k.deferred.onerror=function(a){if("[object Error]"===Aa.call(a)&&!/ Error/.test(a.constructor.toString()))throw Ga=0,a},k.sync=function(a){function b(a,b){return function(g){return e[a]=g,b||(f="reject"),0===--d&&(c.promise(e),c[f](e)),g}}var c=k.deferred(),d=a.length,e=new Array(d),f="resolve";return a.length>0?l(a,function(a,c){a.then(b(c,!0),b(c,!1))}):c.resolve([]),c.promise},k.request=function(a){a.background!==!0&&k.startComputation();var b,c,d,e=new oa,f=a.dataType&&"jsonp"===a.dataType.toLowerCase();return f?(b=a.serialize=c=a.deserialize=pa,d=function(a){return a.responseText}):(b=a.serialize=a.serialize||JSON.stringify,c=a.deserialize=a.deserialize||JSON.parse,d=a.extract||function(a){return a.responseText.length||c!==JSON.parse?a.responseText:null}),a.method=(a.method||"GET").toUpperCase(),a.url=ua(a.url,a.data),ta(a,a.data,b),a.onload=a.onerror=function(b){try{b=b||event;var f=c(d(b.target,a));"load"===b.type?(a.unwrapSuccess&&(f=a.unwrapSuccess(f,b.target)),Ba(f)&&a.type?l(f,function(b,c){f[c]=new a.type(b)}):a.type&&(f=new a.type(f)),e.resolve(f)):(a.unwrapError&&(f=a.unwrapError(f,b.target)),e.reject(f))}catch(g){e.reject(g)}finally{a.background!==!0&&k.endComputation()}},sa(a),e.promise=na(e.promise,a.initialValue),e.promise},k});
//# sourceMappingURL=mithril.min.js.map
});
require.register("peers", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

module.exports = {view: function(){
    var peers = rs("peers");
    //console.log("peers:" + peers);

    //waiting for peerlist ...
    if(peers === undefined || peers == null){
        return m("div",[
            m("h2","peers"),
            m("h3","waiting_server"),
        ]);
    };
    peers = peers.sort(rs.sort("name"));

    //building peerlist (prebuild for counting)
    var online = 0;
    var peerlist = peers.map(function(peer){
        var isonline = false;
        var avatar_address ="";

        //building location list (prebuild for state + icon)
        var loclist = peer.locations.map(function(location){
            if (location.is_online && ! isonline){
                online +=1;
                isonline = true;
            }
            if (location.avatar_address != "" && avatar_address =="") {
                avatar_address=location.avatar_address;
            }
            return m("li",{
                style:"color:" + (location.is_online ? "lime": "grey")
                    + ";cursor:pointer",
                onclick: function(){
                    m.route("/chat?lobby=" + location.chat_id)
                }

            }, location.location);
        });

        //return friend (peer + locations)
        return m("div.flexbox[style=color:lime]",[
            // avatar-icon
            m("div", [
                avatar_address == "" ? "" : (
                    m("img",{
                        src: rs.apiurl("peers" + avatar_address),
                        style:"border-radius:3mm;margin:2mm;",
                    })
                )
            ]),
            //peername + locations
            m("div.flexwidemember",[
                m("h1[style=margin-bottom:1mm;]",
                    {style:"color:" + (isonline ? "lime": "grey")} ,
                    peer.name
                ),
                m("ul", loclist ),
            ]),
            //remove-button
            m("div", {
                style: "color:red;" +
                    "font-size:1.5em;" +
					"padding:0.2em;" +
					"cursor:pointer",
				onclick: function (){
				    var yes = window.confirm(
				        "Remove " + peer.name + " from friendslist?");
				    if(yes){
					    rs.request("peers/" + peer.pgp_id +"/delete");
				    }
				}
            }, "X")
        ]);
    });

    // return add-peer-button + peerlist
    return m("div",[
        m("div.btn2",{onclick: function(){m.route("/addpeer")}},"add new friend"),
        m("h2","peers (online: " + online  +" / " + peers.length + "):"),
        m("div", [
            peerlist,
        ]),
    ]);
}
};

});
require.register("retroshare", function(exports, require, module) {
/*
var rs = requires("rs");
var m = require("mithril");

function main(){
    var state = rs("runstate");
    if(state=== undefined){
        return m("div", "waiting for server");
    }
    if(state === "waiting_login"){
        return require("login")();
    }
    if(state === "running_ok"){
        return require("mainwindow")();
    }
}
*/

/*
idea: statetokenservice could just send the date instead of the token
*/

"use strict";

var m = require("mithril");

var api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v2/";
var filestreamer_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/fstream/";
var upload_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/upload/";

function for_key_in_obj(obj, callback){
    var key;
    for(key in obj){
        callback(key, obj[key]);
    }
}

var cache = {};
var last_update_ts = 0;

function check_for_changes(){
    var tokens = [];
    var paths_to_fetch = [];
//    console.log("start-check " + Object.keys(cache));
    for_key_in_obj(cache, function(path, item){
        var token = item.statetoken;
        if(token === undefined || token== null) {
            paths_to_fetch.push(path)
        } else if (tokens.indexOf(token)<0) {
            tokens.push(token);
        }
    });
//    console.log("tokens found: " + tokens);
    var req = m.request({
        method: "POST",
        url: api_url + "statetokenservice",
        background: true,
        data: tokens,
    });
    
    req.then(function handle_statetoken_response(response){
//        console.log("checking result " + response.data ? Object.keys(response.data) : "<null>") ;
        for_key_in_obj(cache, function(path, item){
            var found = false;
            for(var i = 0; i < response.data.length; i++){
                if(response.data[i] === item.statetoken){
                    found = true;
                }
            }
            if(found){
                paths_to_fetch.push(path);
            }
        });
//        console.log("generating Results for paths " + paths_to_fetch);
        var requests = [];
        paths_to_fetch.map(function request_it(path){
            var req2 = m.request({
                method: "GET",
                url: api_url + path,
                background: true,
            });
            req2 = req2.then(function fill_in_result(response){
                cache[path].data = response.data;
                cache[path].statetoken = response.statetoken;
            });
            requests.push(req2);
        });
        if(requests.length > 0){
//            console.log("requesting " + requests.length + " requests");
            m.sync(requests).then(function trigger_render(){
                m.startComputation();
                m.endComputation();
                checkFocus();
                setTimeout(check_for_changes, 500);
            });
        }
        else{
//            console.log("no requests");
            setTimeout(check_for_changes, 500);
        }
    }, function errhandling(value){
//        console.log("server disconnected " + value);
        setTimeout(check_for_changes, 500);
    });
}

check_for_changes();

var update_scheduled = false;
function schedule_request_missing(){
    if(update_scheduled)
        return;
    update_scheduled = true;
    // place update logic outside of render loop, this way we can fetch multiple things at once
    // (because after the render loop everything we should fetch is in the list)
    // if we fetch multiple things at once, we can delay a re-rende runtil everything is done
    // so we need only one re-render for multiple updates
    setTimeout(function request_missing(){
        update_scheduled = false;
        var requests = [];
        for_key_in_obj(cache, function(path, item){
            if(!item.requested){
                var req = m.request({
                    method: "GET",
                    url: api_url + path,
                    background: true,
                });

                req.then(function fill_data(response){
                    // TODO: add errorhandling
                    item.data = response.data;
                    item.statetoken = response.statetoken;
                    if (item.then != undefined && item.then != null) {
                        try {
                            item.then(response);
                        } catch (ex) {
                            if (item.errorCallback != undefined && item.errorCallback != null) {
                                item.errorCallback(ex);
                            };
                        }
                    };
                }, function errhandling(value){
                    if (item.errorCallback != undefined && item.errorCallback != null) {
                        item.errorCallback(value);
                    }
                });
                requests.push(req);
            }
            item.requested = true;
        });
        m.sync(requests).then(function trigger_render(){
            m.startComputation();
            m.endComputation();
            checkFocus();
        });
    });
}

function checkFocus(){
	if (m.initControl != undefined) {
	    var ctrl = document.getElementById(m.initControl);
	    if (ctrl!= null) {
		    ctrl.focus();
		    m.initControl = undefined;
	    } else {
	        console.log("focus-control '" + m.initControl + "' not found!");
	        m.initControl = undefined;
	    }
	}
}

// called every time, rs or rs.request failed, only response or value is set
function requestFail(path, response, value) {
    rs.error = "error on " + path;
    console.log("Error on " + path +
        (response == null ? ", value: " + value : (", response: " +
            (response.debug_msg === undefined ? response : response.debug_msg)
        ))
    );
}

function rs(path, args, callback, options){
    if(cache[path] === undefined){
        options=optionsPrep(options,path);
        var req = {
            data: args,
            statetoken: undefined,
            requested: false,
            allow: options.allow,
            then: function(response){
                options.log(path + ": response: " + response.returncode);
                if (!this.allow.match(response.returncode)) {
                    options.onmismatch(response);
                } else if (callback != undefined && callback != null) {
                    callback(response.data, response.statetoken);
                }
            },
            errorCallback: options.onfail
        };
        cache[path] = req;
        schedule_request_missing();
    }
    return cache[path].data;
}

module.exports = rs;

rs.for_key_in_obj = for_key_in_obj;

// single request for action
rs.request=function(path, args, callback, options){
    options = optionsPrep(options, path);
    var req = m.request({
        method: options.method === undefined ? "POST" : options.method,
        url: api_url + path,
        data: args,
        background: true
    });
    req.then(function checkResponseAndCallback(response){
        options.log(path + ": response: " + response.returncode);
        if (!options.allow.match(response.returncode)) {
            options.onmismatch(response);
        } else if (callback != undefined && callback != null) {
            callback(response.data, response.statetoken);
        }
    }, options.onfail);
    return req;
};

//set default-values for shared options in rs() and rs.request()
function optionsPrep(options, path) {
    if (options === undefined) {
        options = {};
    }

    if (options.onfail === undefined) {
        options.onfail = function errhandling(value){
            requestFail(path, null, value);
        }
    };
    if (options.onmismatch === undefined) {
        options.onmismatch = function errhandling(response){
            requestFail(path, response,null);
        }
    };

    if (options.log === undefined)  {
        options.log = function(message) {
            console.log(message);
        }
    }

    if (options.allow === undefined) {
        options.allow = "ok";
    };
    return options;
}

// force reload for path
rs.forceUpdate = function(path, removeCache){
    if (removeCache === undefined || !removeCache) {
        cache[path].requested=false;
    } else {
        delete cache[path];
    }
}

// force reload for all
rs.clearCache = function(path, removeCache){
    console.log("clearing Cache ...")
    cache = {};
    console.log("update_scheduled: " + update_scheduled);
    update_scheduled = false;
    check_for_changes();
    console.log("Cache cleared.")
}

// dismiss statetoken (= force reload)
rs.untoken = function(path) {
    cache[path].statetoken = null;
}


//return api-path
rs.apiurl = function(path) {
    if (path === undefined) {
        path="";
    }
    if (path.length > 0 && "^\\\\|\\/".match(path)) {
        path=path.substr(1);
    }
    return api_url + path;
}

// counting in menu
rs.counting = function(path, counterfnkt) {
    return function () {
        var data=rs(path);
        if (data != undefined) {
            if (counterfnkt === undefined) {
                return " (" + data.length + ")";
            }
            return " (" + counterfnkt(data) + ")";
        }
        return "";
    }
};

// counting in menu
rs.counting2 = function(targets) {
    return function () {
        var sum = 0;
        for (var path in targets) {
            var data=rs(path);
            if (data != undefined) {
                data.map(function(item){
                    sum += parseInt(targets[path](item));
                });
            };
        };
        if (sum > 0) {
            return " (" + sum + ")";
        }
        return "";
    }
};

// listing data-elements
rs.list = function(path, buildfktn, sortfktn){
    var list = rs(path);
    if (list === undefined|| list == null) {
        return "< waiting for server ... >"
    };
    if (sortfktn != undefined && sortfktn != null) {
        list=list.sort(sortfktn);
    }
    return list.map(buildfktn);
};

//remember additional data (feature of last resort)
rs.memory = function(path, args){
    var item = cache[path];
    if (item === undefined) {
        rs(path, args);
        item =  cache[path];
    }
    if (item.memory === undefined) {
        item.memory = {};
    }
    return item.memory;
};

// Sortierfunktion für Texte von Objekten,
// falls einfache Namen nicht funktionieren
rs.stringSort = function(textA,textB, innersort, objectA, objectB){
        if (textA.toLowerCase() == textB.toLowerCase()) {
            if (innersort === undefined) {
                return 0
            }
            return innersort(objectA,objectB);
        } else if (textA.toLowerCase() < textB.toLowerCase()) {
            return -1
        } else {
            return 1
        }
    }


//return sorting-function for string, based on property name
//using: list.sort(rs.sort("name"));
// -----
//innersort: cascading sorting - using:
//list.sort(rs.sort("type",rs.sort("name")))
rs.sort = function(name, innersort){
    return function(a,b) {
        return rs.stringSort(a[name],b[name],innersort,a,b);
    }
}

//return sorting-function for boolean, based on property name
rs.sort.bool = function(name, innersort){
    return function(a,b){
        if (a[name] == b[name]) {
            if (innersort === undefined) {
                return 0
            }
            return innersort(a,b);
        } else if (a[name]) {
            return -1
        } else {
            return 1
        }
    }
}

// searching a element in a list
// items: list to search in
// name: name of attribute to lookup
// value: attribute's value to compare
rs.find = function(items, name, value) {
    if (items === undefined||items == null) {
        return null;
    };
    for(var i = 0, l = items.length; i < l; ++i) {
        if (items[i][name] == value) {
            return items[i];
        }
    }
    return null;
}

});
require.register("search", function(exports, require, module) {
var m = require("mithril");
var rs = require("retroshare");

var state = {};
var searchText = "";

function updateText(newText) {
    searchText = newText;
}

function dosearch(){
	console.log("searching for: "+searchText);
	rs.request(
	    "filesearch/create_search", {
	        distant: true,
	        search_string: searchText
	    },
        function(resp){
            m.route("/search/" + resp.search_id);
        }
	);
}

module.exports = {
    view: function(){
        var results = rs("filesearch");
        if (results === undefined||results == null) {
            results = [];
        };
        return m("div",[
            m("h2","turtle file search"),
            m("div", [
                m("input[type=text]", {onchange:m.withAttr("value", updateText)}),
                m("input[type=button][value=search]",{onclick:dosearch})
            ]),
            m("hr"),
            m("h2","previous searches:"),
            m("div", [
                results.map(function(item){
                    var res = rs("filesearch/" + item.id,{},null,{allow:"not_set|ok"});
                    if (res === undefined) {
                        res =[];
                    };
                    return m("div.btn2",{
                        onclick:function(){
		                    m.route("/search/" + item.id);
		                    }
                    }, item.search_string + " (" + res.length + ")");
                })
            ])
        ])
    }
}


});
require.register("searchresult", function(exports, require, module) {
var m = require("mithril");
var rs = require("retroshare");

module.exports = {
    view: function(){
        var id=m.route.param("id");
        var results = rs("filesearch/" + id ,{},null,{allow:"not_set|ok"});
        if (results === undefined || results.length == undefined) {
            results = [];
        }
        var searches = rs("filesearch");
        var searchdetail = "<unknown>";
        if (!(searches === undefined) && !(searches.length === undefined)) {
            searches.forEach(function(s){
                if (s.id == id) {
                    searchdetail = s.search_string;
                }
            });
        }

        var dl_ids = [];

        var downloads =rs("transfers/downloads");
        if (downloads !== undefined) {
            downloads.map(function(item){
                dl_ids.push(item.hash);
            })
        }

        return m("div",[
            m("h2","turtle file search results"),
            m("h3", "searchtext: " + searchdetail + " (" + results.length + ")"),
            m("hr"),
            m("table", [
                m("tr" ,[
                    m("th","name"),
                    m("th","size"),
                    m("th",""),
                ]),
                results.map(function(file){
                    if (dl_ids.indexOf(file.hash)>=0) {
                        file.state="in download queue"
                    }
                    return m("tr",[
                        m("th",file.name),
                        m("th",file.size),
                        m("th",[
                            file.state === undefined
                            ? m("span.btn", {
                                onclick:function(){
				                    rs.request("transfers/control_download", {
					                    action: "begin",
					                    name: file.name,
					                    size: file.size,
					                    hash: file.hash,
				                    }, function(){
				                      result="added";
				                    });
                                    m.startComputation();
                                    m.endComputation();
                                }
                            },  "download")
                            : file.state
                        ]),
                    ])
                })
            ])
        ])
    }
}


});
require.register("servicecontrol", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

function setOption(id,value) {
    return function(){
        rs.request("servicecontrol", {
            service_id: id,
            default_allowed: value,
        });
        rs.forceUpdate("servicecontrol", true);
    }
}

function setUserOption(serviceid, userid, value) {
    return function(){
        rs.request("servicecontrol/user", {
            service_id: serviceid,
            peer_id: userid,
            enabled: value
        }, function(){
            rs.forceUpdate("servicecontrol", true)
        });
    }
}

function createSwitch(isOn, width) {
    if (width === undefined) {
        width = "2.1em";
    }
    return [
        m("div.menu", {
            style: {
                float:"left",
                width: width,
                textAlign: "center",
                color: "#303030",
                borderColor: isOn
                    ? "lime"
                    : "red",
                backgroundColor: !isOn
                    ? "black"
                    : "lime",
            }
        }, "ON"),
        m("div.menu",{
            style: {
                float:"left",
                width: width,
                textAlign: "center",
                marginRight:"5px",
                color: "#303030",
                borderColor: isOn
                    ? "lime"
                    : "red",
                backgroundColor: isOn
                    ? "black"
                    : "red",
            }
        }, "OFF"),
    ];
}

function breadcrums(name, parts){
    var result = [];
    rs.for_key_in_obj(parts, function(partname,item){
        result.push(
            m("span.btn",{
                onclick: function(){
                    m.route(item)
                }
            },partname)
        );
        result.push(" / ");
    });
    result.push(name);
    return result;
}

function serviceView(serviceid) {
    var service, liste;
    service = rs.find(rs("servicecontrol"),"service_id",serviceid);
    if (service == null) {
        return m("h3","<please wait ... >");
    }
    liste = service.default_allowed
        ? service.peers_denied
        : service.peers_allowed;
    return m("div", [
        m("h2", breadcrums(service.service_name, {
            settings:"/settings",
            rights: "/settings/servicecontrol",
        })),
        m("hr"),
        m("h2",{
            style:{
                float:"left",
            }
        },[
            m("div",{
                style:{
                    float:"left",
                }
            },"user rights for: " + service.service_name + ", default: "),
            m("div", {
                onclick: setOption(
                    serviceid,
                    !service.default_allowed
                ),
                style: {
                    float:"left",
                    marginLeft: "0.4em",
                    marginRight: "0.4em",
                }
            },createSwitch(service.default_allowed)),
        ]),
        m("div", {
            style: {
                clear:"left",
            }
        }),
        m("ul", rs.list("peers",function(peer){
            var locs;
            locs = peer.locations;
            locs.sort(rs.sort("location"));
            return peer.locations.map(function(location){
                var isExcept, isOn;
                isExcept = liste != null
                    && liste.indexOf(location.peer_id)>=0;
                isOn = service.default_allowed ? !isExcept: isExcept;
                return m("li", {
                    style: {
                        margin: "5px",
                        color: isOn ? "lime" :"red",
                    }
                }, [
                    m("div"),
                    m("div", {
                        onclick: setUserOption(
                            serviceid,
                            location.peer_id,
                            !isOn
                        ),
                        style: {
                            float:"left",
                        },
                    },createSwitch(isOn)),
                    m("div",
                        {
                            style: {
                                //color: "lime",
                                float:"left",
                                marginLeft: "5px",
                                marginRight: "5px",
                                fontWeight: "bold",
                            }
                        },
                        peer.name + (location.location
                            ? " (" + location.location + ")"
                            : "")
                    ),
                    m("div", {
                        style: {
                            clear: "left"
                        }
                    }),
                ]);
            })
        }, rs.sort("name")))
    ]);
}


module.exports = {
    view: function(){
        if (m.route.param("service_id")) {
            return serviceView(m.route.param("service_id"));
        }
        return m("div", [
            m("h2", breadcrums("rights", {
                settings:"/settings",
            })),
            m("hr"),
            m("ul", rs.list("servicecontrol", function(item){
                    return m("li", {
                        style: {
                            margin: "5px",
                            color: item.default_allowed ? "lime" :"red",
                        }
                    }, [
                        m("div"),
                        m("div", {
                            onclick: setOption(
                                item.service_id,
                                !item.default_allowed
                            ),
                            style: {
                                float:"left",
                            }
                        },createSwitch(item.default_allowed)),
                        m("div.menu",
                            {
                                style: {
                                    // color: "lime",
                                    borderColor: item.default_allowed
                                        ? "lime"
                                        : "red",
                                    float: "left",
                                    marginLeft: "5px",
                                    marginRight: "5px",
                                    paddingLeft: "2px",
                                    paddingRight: "2px",
                                },
                                onclick: function(){
                                    m.route("/settings/servicecontrol/", {
                                        service_id: item.service_id,
                                    })
                                }
                            }, "more"
                        ),
                        m("div",
                            {
                                style: {
                                    // color: "lime",
                                    float:"left",
                                    marginLeft: "5px",
                                    marginRight: "5px",
                                    fontWeight: "bold",
                                }
                            },
                            item.service_name
                        ),
                        m("div",
                            {
                                style: {
                                    color: "lime",
                                    float:"left",
                                    marginLeft: "5px",
                                    marginRight: "5px",
                                }
                            },
                            (
                                item.default_allowed
                                ? ( item.peers_denied != null
                                    ? "(" + item.peers_denied.length + " denied)"
                                    : "")
                                : ( item.peers_allowed != null
                                    ? "(" + item.peers_allowed.length + " allowed)"
                                    : "")
                            )
                        ),
                        m("div", {
                            style: {
                                clear: "left"
                            }
                        }),
                    ]);
                })
            )
        ]);
    }
}


});
require.register("settings", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

module.exports = {
    view: function(){
        return m("div", [
            m("h2","settings"),
            m("hr"),
            m("div.btn2",{
                onclick: function(){
                    m.route("/settings/servicecontrol");
                },
            }, "rights")
        ]);
    }
}


});
require.register("waiting", function(exports, require, module) {
"use strict";

var m = require("mithril");
var rs = require("retroshare");

module.exports = {view: function(){
    return m("h2","please wait ...");
}}

});
