<commit_before>/*global
 define
 */
/*jslint
 browser: true,
 white: true
 */

/**
 * A route path ParamComponent
 *
 * @typedef {Object} RoutePathComponent
 * @property {string} type - the type of the component, either 'param' or 'path'
 * @property {string} name - type name of the component. For param, it is the name of the query variable and of the resulting property in the param object; for path it is literal text of the path component.
 *
 */

/**
 * A definition of path and parameters which may match a route, and a payload.
 *
 * @typedef {Object} RouteSpecification
 * @property {Array.<String|RoutePathComponent>} path - an array of path elements, either literal strings or path component objects
 * @property {Object} params - an object whose properties are parameters may be present in a query string
 * @property {Object} payload - an arbitrary object which represents the state associated with the route path.
 */

/**
 * A simple hash-path router service.
 *
 * @module router
 *
 *
 * @returns {unresolved}
 */
define([], function () {
    'use strict';
    function factory(config) {
        // Routing
        var routes = [],
            defaultRoute = config.defaultRoute,
            notFoundRoute = config.notFoundRoute;

        if (!defaultRoute) {
            throw new Error('The defaultRoute must be provided');
        }
        if (!notFoundRoute) {
            throw new Error('The notFound route must be provided');
        }

        function addRoute(pathSpec) {
            /*
             * The path spec is an array of elements. Each element is either a
             * string, in which case it is a literal path component,
             * regular expression, which case it is matched on a path component,
             * object with type:param
             */
            /* TODO: do something on overlapping routes */
            /* TODO: better mapping method for routes. */
            /* still, with a relatively short list of routes, this is far from a performance issue. */
            routes.push(pathSpec);
        }

        function parseQueryString(s) {
            var fields = s.split(/[\?\&]/),
                params = {};
            fields.forEach(function (field) {
                if (field.length > 0) {
                    var pair = field.split('=');
                    if (pair[0].length > 0) {
                        params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
                    }
                }
            });
            return params;
        }

        function getCurrentRequest() {
            var path = [],
                query = {},
                hash, pathQuery;

            // The path is (for now) from the hash component.
            if (window.location.hash && window.location.hash.length > 1) {
                hash = window.location.hash.substr(1);
                pathQuery = hash.split('?', 2);

                if (pathQuery.length === 2) {
                    query = parseQueryString(pathQuery[1]);
                }
                path = pathQuery[0].split('/').filter(function (x) {
                    return (x.length > 0);
                });
            }

            return {
                path: path,
                query: query
            };
        }

        function findRoute(req) {
            var foundRoute, i, j, route, params, found, elValue, elType, allowableParams;
            if ((req.path.length === 0) && (Object.keys(req.query).length === 0)) {
                return {
                    params: {},
                    route: defaultRoute
                };
            }
            for (i = 0; i < routes.length; i += 1) {
                route = routes[i];
                if (route.path.length !== req.path.length) {
                    continue;
                }
                params = {};
                found = true;
                for (j = 0; j < req.path.length; j += 1) {
                    elValue = route.path[j];
                    elType = typeof elValue;
                    if (elType === 'string' && elValue !== req.path[j]) {
                        found = false;
                        break;
                    }
                    if (elType === 'object' && elValue.type === 'param') {
                        params[elValue.name] = req.path[j];
                    }
                }
                if (found) {
                    foundRoute = {
                        params: params,
                        route: route
                    };
                    break;
                }
            }
            // The total params is the path params and query params
            if (foundRoute) {
                allowableParams = foundRoute.route.queryParams || {};
                Object.keys(req.query).forEach(function (key) {
                    var paramDef = allowableParams[key];
                    /* TODO: implement the param def for conversion, validation, etc. */
                    if (paramDef) {
                        foundRoute.params[key] = req.query[key];
                    }
                });
            } else {
                return {
                    params: {},
                    route: notFoundRoute
                };
            }
            return foundRoute;
        }
        function findCurrentRoute() {
            var req = getCurrentRequest();
            return findRoute(req);
        }

        function listRoutes() {
            return routes.map(function (route) {
                return route.path;
            });
        }

        // TODO: move this stuff to router?
        /**
         * A simple adapter to trigger a routing event for the current
         * browser hash-path.
         *
         * @returns {undefined}
         */

        function navigateTo(location) {
            //if (window.history.pushState) {
            //    window.history.pushState(null, '', '#' + location);
            //} else {
            if (typeof location === 'string') {
                location = {path: location};
            }

            var loc = location.path;
            if (location.params) {
                loc += '?' + paramsToQuery(location.params);

            }
            window.location.hash = '#' + loc;
            //}
        }
        function replacePath(location) {
            window.location.replace(location);
        }
        function redirectTo(location, newWindow) {
            if (newWindow) {
                window.open(location);
            } else {
                window.location.replace(location);
            }
        }

        return {
            addRoute: addRoute,
            listRoutes: listRoutes,
            findCurrentRoute: findCurrentRoute,
            getCurrentRequest: getCurrentRequest,
            findRoute: findRoute,

            navigateTo: navigateTo,
            redirectTo: redirectTo
        };
    }

    return {
        make: function (config) {
            return factory(config);
        }
    };
});<commit_msg>support proper location object (path may be array),  included parsed request in found route<commit_after>/*global
 define
 */
/*jslint
 browser: true,
 white: true
 */

/**
 * A route path ParamComponent
 *
 * @typedef {Object} RoutePathComponent
 * @property {string} type - the type of the component, either 'param' or 'path'
 * @property {string} name - type name of the component. For param, it is the name of the query variable and of the resulting property in the param object; for path it is literal text of the path component.
 *
 */

/**
 * A definition of path and parameters which may match a route, and a payload.
 *
 * @typedef {Object} RouteSpecification
 * @property {Array.<String|RoutePathComponent>} path - an array of path elements, either literal strings or path component objects
 * @property {Object} params - an object whose properties are parameters may be present in a query string
 * @property {Object} payload - an arbitrary object which represents the state associated with the route path.
 */

/**
 * A simple hash-path router service.
 *
 * @module router
 *
 *
 * @returns {unresolved}
 */
define([], function () {
    'use strict';
    function factory(config) {
        // Routing
        var routes = [],
                defaultRoute = config.defaultRoute,
                notFoundRoute = config.notFoundRoute;

        if (!defaultRoute) {
            throw new Error('The defaultRoute must be provided');
        }
        if (!notFoundRoute) {
            throw new Error('The notFound route must be provided');
        }

        function addRoute(pathSpec) {
            /*
             * The path spec is an array of elements. Each element is either a
             * string, in which case it is a literal path component,
             * regular expression, which case it is matched on a path component,
             * object with type:param
             */
            /* TODO: do something on overlapping routes */
            /* TODO: better mapping method for routes. */
            /* still, with a relatively short list of routes, this is far from a performance issue. */
            routes.push(pathSpec);
        }

        function parseQueryString(s) {
            var fields = s.split(/[\?\&]/),
                    params = {};
            fields.forEach(function (field) {
                if (field.length > 0) {
                    var pair = field.split('=');
                    if (pair[0].length > 0) {
                        params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
                    }
                }
            });
            return params;
        }

        function getCurrentRequest() {
            var path = [],
                    query = {},
                    hash, pathQuery;

            // The path is (for now) from the hash component.
            if (window.location.hash && window.location.hash.length > 1) {
                hash = window.location.hash.substr(1);
                pathQuery = hash.split('?', 2);

                if (pathQuery.length === 2) {
                    query = parseQueryString(pathQuery[1]);
                }
                path = pathQuery[0].split('/').filter(function (x) {
                    return (x.length > 0);
                });
            }

            return {
                path: path,
                query: query
            };
        }

        function findRoute(req) {
            var foundRoute, i, j, route, params, found, elValue, elType, allowableParams;
            if ((req.path.length === 0) && (Object.keys(req.query).length === 0)) {
                return {
                    request: req,
                    params: {},
                    route: defaultRoute
                };
            }
            for (i = 0; i < routes.length; i += 1) {
                route = routes[i];
                if (route.path.length !== req.path.length) {
                    continue;
                }
                params = {};
                found = true;
                for (j = 0; j < req.path.length; j += 1) {
                    elValue = route.path[j];
                    elType = typeof elValue;
                    if (elType === 'string' && elValue !== req.path[j]) {
                        found = false;
                        break;
                    }
                    if (elType === 'object' && elValue.type === 'param') {
                        params[elValue.name] = req.path[j];
                    }
                }
                if (found) {
                    foundRoute = {
                        request: req,
                        params: params,
                        route: route
                    };
                    break;
                }
            }
            // The total params is the path params and query params
            if (foundRoute) {
                allowableParams = foundRoute.route.queryParams || {};
                Object.keys(req.query).forEach(function (key) {
                    var paramDef = allowableParams[key];
                    /* TODO: implement the param def for conversion, validation, etc. */
                    if (paramDef) {
                        foundRoute.params[key] = req.query[key];
                    }
                });
            } else {
                return {
                    request: req,
                    params: {},
                    route: notFoundRoute
                };
            }
            return foundRoute;
        }
        function findCurrentRoute() {
            var req = getCurrentRequest();
            return findRoute(req);
        }

        function listRoutes() {
            return routes.map(function (route) {
                return route.path;
            });
        }

        // TODO: move this stuff to router?
        /**
         * A simple adapter to trigger a routing event for the current
         * browser hash-path.
         *
         * @returns {undefined}
         */

        function paramsToQuery(params) {
            return Object.keys(params).map(function (key) {
                return key + '=' + encodeURIComponent(params[key]);
            }).join('&');
        }

        function navigateTo(location) {
            //if (window.history.pushState) {
            //    window.history.pushState(null, '', '#' + location);
            //} else {
            if (typeof location === 'string') {
                location = {path: location};
            }
            // path may be an array.
            var loc;
            if (location.path.pop) {
                loc = location.path.join('/');
            } else {
                loc = location.path;
            }
            if (location.params) {
                loc += '?' + paramsToQuery(location.params);
            }
            window.location.hash = '#' + loc;
            //}
        }
        function replacePath(location) {
            window.location.replace(location);
        }
        function redirectTo(location, newWindow) {
            if (newWindow) {
                window.open(location);
            } else {
                window.location.replace(location);
            }
        }

        return {
            addRoute: addRoute,
            listRoutes: listRoutes,
            findCurrentRoute: findCurrentRoute,
            getCurrentRequest: getCurrentRequest,
            findRoute: findRoute,
            navigateTo: navigateTo,
            redirectTo: redirectTo
        };
    }

    return {
        make: function (config) {
            return factory(config);
        }
    };
});<|endoftext|>


<commit_before>//
// パズル固有スクリプト部 なわばり・フォーセルズ・ファイブセルズ版 nawabari.js v3.4.0
//
pzpr.createCustoms('nawabari', {
//---------------------------------------------------------
// マウス入力系
MouseEvent:{
	mouseinput : function(){
		if(this.owner.playmode){
			if(this.mousestart || this.mousemove){
				if(this.btn.Left && this.isBorderMode()){ this.inputborder();}
				else{ this.inputQsubLine();}
			}
		}
		else if(this.owner.editmode){
			if(this.mousestart){ this.inputqnum();}
		}
	}
},

//---------------------------------------------------------
// キーボード入力系
KeyEvent:{
	enablemake : true
},

"KeyEvent@fourcells,fivecells":{
	keyinput : function(ca){
		if(ca=='w'){ this.key_inputvalid(ca);}
		else{ this.key_inputqnum(ca);}
	},
	key_inputvalid : function(ca){
		if(ca=='w'){
			var cell = this.cursor.getTCC();
			if(!cell.isnull){
				cell.setQues(cell.getQues()!==7?7:0);
				cell.setNum(-1);
				cell.draw();
			}
		}
	}
},

//---------------------------------------------------------
// 盤面管理系
Cell:{
	posthook : {
		ques : function(num){ this.owner.board.setInfoByCell(this);}
	},

	getdir4BorderCount : function(){
		var cnt=0, cblist=this.getdir4cblist();
		for(var i=0;i<cblist.length;i++){
			var tcell=cblist[i][0], tborder=cblist[i][1];
			if(tcell.isnull || tcell.isEmpty() || tborder.isBorder()){ cnt++;}
		}
		return cnt;
	}
},
"Cell@nawabari":{
	maxnum : 4,
	minnum : 0
},
"Cell@fourcells":{
	maxnum : 3
},
"Cell@fivecells":{
	maxnum : 3,
	minnum : 0
},

"Border@fourcells,fivecells":{
	isGrid : function(){
		return (this.sidecell[0].isValid() && this.sidecell[1].isValid());
	},
	isBorder : function(){
		return ((this.qans>0) || this.isQuesBorder());
	},
	isQuesBorder : function(){
		return !!(this.sidecell[0].isEmpty()^this.sidecell[1].isEmpty());
	}
},

"Board@nawabari":{
	hasborder : 1
},
"Board@fourcells,fivecells":{
	hasborder : 2,

	initBoardSize : function(col,row){
		this.Common.prototype.initBoardSize.call(this,col,row);

		var odd = (col*row)%(this.owner.pid==='fivecells'?5:4);
		if(odd>=1){ this.getc(this.minbx+1,this.minby+1).ques=7;}
		if(odd>=2){ this.getc(this.maxbx-1,this.minby+1).ques=7;}
		if(odd>=3){ this.getc(this.minbx+1,this.maxby-1).ques=7;}
		if(odd>=4){ this.getc(this.maxbx-1,this.maxby-1).ques=7;}
	}
},

AreaRoomManager:{
	enabled : true
},

//---------------------------------------------------------
// 画像表示系
Graphic:{
	initialize : function(){
		this.Common.prototype.initialize.call(this);

		this.gridcolor = this.gridcolor_DLIGHT;
		if(this.owner.pid==='nawabari'){ this.setBorderColorFunc('qans');}
	},
	paint : function(){
		this.drawBGCells();

		if(this.owner.pid==='nawabari'){
			this.drawDashedGrid();
			this.drawBorders();
		}
		else{
			this.drawValidDashedGrid();
			this.drawQansBorders();
			this.drawQuesBorders();
		}

		this.drawNumbers();
		this.drawBorderQsubs();

		if(this.owner.pid==='nawabari'){ this.drawChassis();}

		this.drawTarget();
	}
},
"Graphic@fourcells,fivecells":{
	getQansBorderColor : function(border){
		if(border.qans===1){
			var err = border.error;
			if     (err=== 1){ return this.errcolor1;       }
			else if(err===-1){ return this.errborderbgcolor;}
			else             { return this.borderQanscolor; }
		}
		return null;
	},
	getQuesBorderColor : function(border){
		return (border.isQuesBorder() ? this.cellcolor : null);
	},

	drawValidDashedGrid : function(){
		var g = this.vinc('grid_waritai', 'crispEdges');

		var dotmax   = this.cw/10+3;
		var dotCount = Math.max(this.cw/dotmax, 1);
		var dotSize  = this.cw/(dotCount*2);

		var csize = this.cw*0.20;
		var header = "b_grid_wari_";
		var blist = this.range.borders;
		for(var n=0;n<blist.length;n++){
			var border = blist[n];
			if(border.isGrid()){
				var px = border.bx*this.bw, py = border.by*this.bh;
				if(this.vnop(header+border.id,this.NONE)){
					/* strokeぶん0.5ずらす */
					g.lineWidth = 1;
					g.strokeStyle = this.gridcolor;

					if(border.isVert()){ g.strokeDashedLine(px+0.5, py-this.bh, px+0.5, py+this.bh, [dotSize]);}
					else               { g.strokeDashedLine(px-this.bw, py+0.5, px+this.bw, py+0.5, [dotSize]);}
				}
			}
			else{ this.vhide(header+border.id);}
		}
	}
},

//---------------------------------------------------------
// URLエンコード/デコード処理
Encode:{
	decodePzpr : function(type){
		this.decodeFivecells();
	},
	encodePzpr : function(type){
		this.encodeFivecells();
	},

	// decode/encodeNumber10関数の改造版にします
	decodeFivecells : function(){
		var c=0, i=0, bstr = this.outbstr, bd = this.owner.board;
		for(i=0;i<bstr.length;i++){
			var obj = bd.cell[c], ca = bstr.charAt(i);

			obj.ques = 0;
			if     (ca == '7')				 { obj.ques = 7;}
			else if(ca == '.')				 { obj.qnum = -2;}
			else if(this.include(ca,"0","9")){ obj.qnum = parseInt(ca,10);}
			else if(this.include(ca,"a","z")){ c += (parseInt(ca,36)-10);}

			c++;
			if(c > bd.cellmax){ break;}
		}
		this.outbstr = bstr.substr(i);
	},
	encodeFivecells : function(){
		var cm="", count=0, bd = this.owner.board;
		for(var c=0;c<bd.cellmax;c++){
			var pstr="", qn=bd.cell[c].qnum, qu=bd.cell[c].ques;

			if     (qu=== 7){ pstr = "7";}
			else if(qn===-2){ pstr = ".";}
			else if(qn!==-1){ pstr = qn.toString(10);} // 0～3
			else{ count++;}

			if(count==0){ cm += pstr;}
			else if(pstr || count==26){ cm+=((9+count).toString(36)+pstr); count=0;}
		}
		if(count>0){ cm+=(9+count).toString(36);}

		this.outbstr += cm;
	}
},
//---------------------------------------------------------
FileIO:{
	decodeData : function(){
		this.decodeCell( function(obj,ca){
			obj.ques = 0;
			if     (ca==="*"){ obj.ques = 7;}
			else if(ca==="-"){ obj.qnum = -2;}
			else if(ca!=="."){ obj.qnum = parseInt(ca);}
		});
		this.decodeBorderAns();
	},
	encodeData : function(){
		if(this.owner.pid==='fourcells'){ this.filever=1;}
		this.encodeCell( function(obj){
			if     (obj.ques=== 7){ return "* ";}
			else if(obj.qnum===-2){ return "- ";}
			else if(obj.qnum>=  0){ return (obj.qnum.toString() + " ");}
			else                  { return ". ";}
		});
		this.encodeBorderAns();
	}
},

//---------------------------------------------------------
// 正解判定処理実行部
AnsCheck:{
	checkAns : function(){
		var o=this.owner, bd=o.board, pid=o.pid;

		var rinfo = bd.getRoomInfo();
		if( (pid==='nawabari') && !this.checkAreaRect(rinfo) ){ return 'bkNotRect';}

		if( (pid==='nawabari') && !this.checkNoNumber(rinfo) ){ return 'bkNoNum';}

		if( (pid==='nawabari') && !this.checkDoubleNumber(rinfo) ){ return 'bkNumGe2';}

		if( (pid==='fourcells') && !this.checkOverFourCells(rinfo) ){ return 'bkSizeLt4';}
		if( (pid==='fivecells') && !this.checkOverFiveCells(rinfo) ){ return 'bkSizeLt5';}

		if( !this.checkdir4BorderAns() ){ return 'nmBorderNe';}

		if( !this.checkBorderCount(1,0) ){ return 'bdDeadEnd';}

		if( (pid==='fourcells') && !this.checkLessFourCells(rinfo) ){ return 'bkSizeGt4';}
		if( (pid==='fivecells') && !this.checkLessFiveCells(rinfo) ){ return 'bkSizeGt5';}

		return null;
	},

	checkOverFourCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a>=4);});
	},
	checkLessFourCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a<=4);});
	},
	checkOverFiveCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a>=5);});
	},
	checkLessFiveCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a<=5);});
	},

	checkdir4BorderAns : function(){
		var result = true, bd = this.owner.board;
		for(var c=0;c<bd.cellmax;c++){
			var cell = bd.cell[c];
			if(cell.isValidNum() && cell.getdir4BorderCount()!=cell.getQnum()){
				if(this.checkOnly){ return false;}
				cell.seterr(1);
				result = false;
			}
		}
		return result;
	}
},

FailCode:{
	bkNoNum  : ["数字の入っていない部屋があります。","A room has no numbers."],
	bkNumGe2 : ["1つの部屋に2つ以上の数字が入っています。","A room has plural numbers."],
	bkSizeLt4 : ["サイズが4マスより小さいブロックがあります。","The size of block is smaller than four."],
	bkSizeLt5 : ["サイズが5マスより小さいブロックがあります。","The size of block is smaller than five."],
	bkSizeGt4 : ["サイズが4マスより大きいブロックがあります。","The size of block is larger than four."],
	bkSizeGt5 : ["サイズが5マスより大きいブロックがあります。","The size of block is larger than five."]
}
});
<commit_msg>fivecells: 無効なセルを入力したときの境界線の描画方法を修正<commit_after>//
// パズル固有スクリプト部 なわばり・フォーセルズ・ファイブセルズ版 nawabari.js v3.4.0
//
pzpr.createCustoms('nawabari', {
//---------------------------------------------------------
// マウス入力系
MouseEvent:{
	mouseinput : function(){
		if(this.owner.playmode){
			if(this.mousestart || this.mousemove){
				if(this.btn.Left && this.isBorderMode()){ this.inputborder();}
				else{ this.inputQsubLine();}
			}
		}
		else if(this.owner.editmode){
			if(this.mousestart){ this.inputqnum();}
		}
	}
},

//---------------------------------------------------------
// キーボード入力系
KeyEvent:{
	enablemake : true
},

"KeyEvent@fourcells,fivecells":{
	keyinput : function(ca){
		if(ca=='w'){ this.key_inputvalid(ca);}
		else{ this.key_inputqnum(ca);}
	},
	key_inputvalid : function(ca){
		if(ca=='w'){
			var cell = this.cursor.getTCC();
			if(!cell.isnull){
				cell.setQues(cell.getQues()!==7?7:0);
				cell.setNum(-1);
				cell.drawaround();
			}
		}
	}
},

//---------------------------------------------------------
// 盤面管理系
Cell:{
	posthook : {
		ques : function(num){ this.owner.board.setInfoByCell(this);}
	},

	getdir4BorderCount : function(){
		var cnt=0, cblist=this.getdir4cblist();
		for(var i=0;i<cblist.length;i++){
			var tcell=cblist[i][0], tborder=cblist[i][1];
			if(tcell.isnull || tcell.isEmpty() || tborder.isBorder()){ cnt++;}
		}
		return cnt;
	}
},
"Cell@nawabari":{
	maxnum : 4,
	minnum : 0
},
"Cell@fourcells":{
	maxnum : 3
},
"Cell@fivecells":{
	maxnum : 3,
	minnum : 0
},

"Border@fourcells,fivecells":{
	isGrid : function(){
		return (this.sidecell[0].isValid() && this.sidecell[1].isValid());
	},
	isBorder : function(){
		return ((this.qans>0) || this.isQuesBorder());
	},
	isQuesBorder : function(){
		return !!(this.sidecell[0].isEmpty()^this.sidecell[1].isEmpty());
	}
},

"Board@nawabari":{
	hasborder : 1
},
"Board@fourcells,fivecells":{
	hasborder : 2,

	initBoardSize : function(col,row){
		this.Common.prototype.initBoardSize.call(this,col,row);

		var odd = (col*row)%(this.owner.pid==='fivecells'?5:4);
		if(odd>=1){ this.getc(this.minbx+1,this.minby+1).ques=7;}
		if(odd>=2){ this.getc(this.maxbx-1,this.minby+1).ques=7;}
		if(odd>=3){ this.getc(this.minbx+1,this.maxby-1).ques=7;}
		if(odd>=4){ this.getc(this.maxbx-1,this.maxby-1).ques=7;}
	}
},

AreaRoomManager:{
	enabled : true
},

//---------------------------------------------------------
// 画像表示系
Graphic:{
	initialize : function(){
		this.Common.prototype.initialize.call(this);

		this.gridcolor = this.gridcolor_DLIGHT;
		if(this.owner.pid==='nawabari'){ this.setBorderColorFunc('qans');}
	},
	paint : function(){
		this.drawBGCells();

		if(this.owner.pid==='nawabari'){
			this.drawDashedGrid();
			this.drawBorders();
		}
		else{
			this.drawValidDashedGrid();
			this.drawQansBorders();
			this.drawQuesBorders();
		}

		this.drawNumbers();
		this.drawBorderQsubs();

		if(this.owner.pid==='nawabari'){ this.drawChassis();}

		this.drawTarget();
	}
},
"Graphic@fourcells,fivecells":{
	getQansBorderColor : function(border){
		if(border.qans===1){
			var err = border.error;
			if     (err=== 1){ return this.errcolor1;       }
			else if(err===-1){ return this.errborderbgcolor;}
			else             { return this.borderQanscolor; }
		}
		return null;
	},
	getQuesBorderColor : function(border){
		return (border.isQuesBorder() ? this.cellcolor : null);
	},

	drawValidDashedGrid : function(){
		var g = this.vinc('grid_waritai', 'crispEdges');

		var dotmax   = this.cw/10+3;
		var dotCount = Math.max(this.cw/dotmax, 1);
		var dotSize  = this.cw/(dotCount*2);

		var csize = this.cw*0.20;
		var header = "b_grid_wari_";
		var blist = this.range.borders;
		for(var n=0;n<blist.length;n++){
			var border = blist[n];
			if(border.isGrid()){
				var px = border.bx*this.bw, py = border.by*this.bh;
				if(this.vnop(header+border.id,this.NONE)){
					/* strokeぶん0.5ずらす */
					g.lineWidth = 1;
					g.strokeStyle = this.gridcolor;

					if(border.isVert()){ g.strokeDashedLine(px+0.5, py-this.bh, px+0.5, py+this.bh, [dotSize]);}
					else               { g.strokeDashedLine(px-this.bw, py+0.5, px+this.bw, py+0.5, [dotSize]);}
				}
			}
			else{ this.vhide(header+border.id);}
		}
	}
},

//---------------------------------------------------------
// URLエンコード/デコード処理
Encode:{
	decodePzpr : function(type){
		this.decodeFivecells();
	},
	encodePzpr : function(type){
		this.encodeFivecells();
	},

	// decode/encodeNumber10関数の改造版にします
	decodeFivecells : function(){
		var c=0, i=0, bstr = this.outbstr, bd = this.owner.board;
		for(i=0;i<bstr.length;i++){
			var obj = bd.cell[c], ca = bstr.charAt(i);

			obj.ques = 0;
			if     (ca == '7')				 { obj.ques = 7;}
			else if(ca == '.')				 { obj.qnum = -2;}
			else if(this.include(ca,"0","9")){ obj.qnum = parseInt(ca,10);}
			else if(this.include(ca,"a","z")){ c += (parseInt(ca,36)-10);}

			c++;
			if(c > bd.cellmax){ break;}
		}
		this.outbstr = bstr.substr(i);
	},
	encodeFivecells : function(){
		var cm="", count=0, bd = this.owner.board;
		for(var c=0;c<bd.cellmax;c++){
			var pstr="", qn=bd.cell[c].qnum, qu=bd.cell[c].ques;

			if     (qu=== 7){ pstr = "7";}
			else if(qn===-2){ pstr = ".";}
			else if(qn!==-1){ pstr = qn.toString(10);} // 0～3
			else{ count++;}

			if(count==0){ cm += pstr;}
			else if(pstr || count==26){ cm+=((9+count).toString(36)+pstr); count=0;}
		}
		if(count>0){ cm+=(9+count).toString(36);}

		this.outbstr += cm;
	}
},
//---------------------------------------------------------
FileIO:{
	decodeData : function(){
		this.decodeCell( function(obj,ca){
			obj.ques = 0;
			if     (ca==="*"){ obj.ques = 7;}
			else if(ca==="-"){ obj.qnum = -2;}
			else if(ca!=="."){ obj.qnum = parseInt(ca);}
		});
		this.decodeBorderAns();
	},
	encodeData : function(){
		if(this.owner.pid==='fourcells'){ this.filever=1;}
		this.encodeCell( function(obj){
			if     (obj.ques=== 7){ return "* ";}
			else if(obj.qnum===-2){ return "- ";}
			else if(obj.qnum>=  0){ return (obj.qnum.toString() + " ");}
			else                  { return ". ";}
		});
		this.encodeBorderAns();
	}
},

//---------------------------------------------------------
// 正解判定処理実行部
AnsCheck:{
	checkAns : function(){
		var o=this.owner, bd=o.board, pid=o.pid;

		var rinfo = bd.getRoomInfo();
		if( (pid==='nawabari') && !this.checkAreaRect(rinfo) ){ return 'bkNotRect';}

		if( (pid==='nawabari') && !this.checkNoNumber(rinfo) ){ return 'bkNoNum';}

		if( (pid==='nawabari') && !this.checkDoubleNumber(rinfo) ){ return 'bkNumGe2';}

		if( (pid==='fourcells') && !this.checkOverFourCells(rinfo) ){ return 'bkSizeLt4';}
		if( (pid==='fivecells') && !this.checkOverFiveCells(rinfo) ){ return 'bkSizeLt5';}

		if( !this.checkdir4BorderAns() ){ return 'nmBorderNe';}

		if( !this.checkBorderCount(1,0) ){ return 'bdDeadEnd';}

		if( (pid==='fourcells') && !this.checkLessFourCells(rinfo) ){ return 'bkSizeGt4';}
		if( (pid==='fivecells') && !this.checkLessFiveCells(rinfo) ){ return 'bkSizeGt5';}

		return null;
	},

	checkOverFourCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a>=4);});
	},
	checkLessFourCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a<=4);});
	},
	checkOverFiveCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a>=5);});
	},
	checkLessFiveCells : function(rinfo){
		return this.checkAllArea(rinfo, function(w,h,a,n){ return (a<=5);});
	},

	checkdir4BorderAns : function(){
		var result = true, bd = this.owner.board;
		for(var c=0;c<bd.cellmax;c++){
			var cell = bd.cell[c];
			if(cell.isValidNum() && cell.getdir4BorderCount()!=cell.getQnum()){
				if(this.checkOnly){ return false;}
				cell.seterr(1);
				result = false;
			}
		}
		return result;
	}
},

FailCode:{
	bkNoNum  : ["数字の入っていない部屋があります。","A room has no numbers."],
	bkNumGe2 : ["1つの部屋に2つ以上の数字が入っています。","A room has plural numbers."],
	bkSizeLt4 : ["サイズが4マスより小さいブロックがあります。","The size of block is smaller than four."],
	bkSizeLt5 : ["サイズが5マスより小さいブロックがあります。","The size of block is smaller than five."],
	bkSizeGt4 : ["サイズが4マスより大きいブロックがあります。","The size of block is larger than four."],
	bkSizeGt5 : ["サイズが5マスより大きいブロックがあります。","The size of block is larger than five."]
}
});
<|endoftext|>


<commit_before>import { Feature } from 'toolkit/extension/features/feature';
import { getToolkitStorageKey, setToolkitStorageKey } from 'toolkit/extension/utils/toolkit';
import { getCurrentRouteName } from 'toolkit/extension/utils/ynab';

let flags;
let redFlagLabel;
let blueFlagLabel;
let orangeFlagLabel;
let yellowFlagLabel;
let greenFlagLabel;
let purpleFlagLabel;

export class CustomFlagNames extends Feature {
  constructor() {
    super();
    if (!getToolkitStorageKey('flags')) {
      this.storeDefaultFlags();
    }
    if (typeof flags === 'undefined') {
      flags = getToolkitStorageKey('flags');
      this.updateFlagLabels();
    }
  }

  shouldInvoke() {
    return getCurrentRouteName().indexOf('account') !== -1;
  }

  invoke() {
    $('.ynab-grid-cell-flag .ynab-flag-red').parent().attr('title', redFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-blue').parent().attr('title', blueFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-orange').parent().attr('title', orangeFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-yellow').parent().attr('title', yellowFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-green').parent().attr('title', greenFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-purple').parent().attr('title', purpleFlagLabel);
  }

  observe(changedNodes) {
    if (!this.shouldInvoke()) return;

    if (changedNodes.has('layout user-logged-in') || changedNodes.has('ynab-grid-body')) {
      this.invoke();
    }

    if (changedNodes.has('ynab-u modal-popup modal-account-flags ember-view modal-overlay active')) {
      $('.ynab-flag-red .label, .ynab-flag-red .label-bg').text(redFlagLabel);
      $('.ynab-flag-blue .label, .ynab-flag-blue .label-bg').text(blueFlagLabel);
      $('.ynab-flag-orange .label, .ynab-flag-orange .label-bg').text(orangeFlagLabel);
      $('.ynab-flag-yellow .label, .ynab-flag-yellow .label-bg').text(yellowFlagLabel);
      $('.ynab-flag-green .label, .ynab-flag-green .label-bg').text(greenFlagLabel);
      $('.ynab-flag-purple .label, .ynab-flag-purple .label-bg').text(purpleFlagLabel);

      $('.modal-account-flags .modal').css({ height: '22em' }).append($('<div>', { id: 'account-flags-actions' }).css({ padding: '0 .3em' }).append($('<button>', { id: 'flags-edit', class: 'button button-primary' }).append('Edit ').append($('<i>', { class: 'flaticon stroke compose-3' }))));

      this.addEventListeners();
    }
  }

  onRouteChanged() {
    if (!this.shouldInvoke()) return;

    this.invoke();
  }

  addEventListeners() {
    let $this = this;
    $('#flags-edit').click(function () {
      $('.modal-account-flags .modal-list').empty();

      for (let key in flags) {
        let flag = flags[key];

        $('.modal-account-flags .modal-list').append($('<li>').append($('<input>', {
          id: key, type: 'text', class: 'flag-input', value: flag.label, placeholder: flag.label
        }).css({
          color: '#fff', fill: flag.color, 'background-color': flag.color, height: 30, padding: '0 .7em', 'margin-bottom': '.3em', border: 'none'
        })));
      }

      $('#account-flags-actions').empty();

      $('#account-flags-actions').append($('<button>', { id: 'flags-close', class: 'button button-primary' }).append('Ok ').append($('<i>', { class: 'flaticon stroke checkmark-2' })));

      $('input.flag-input').focus(function () {
        $(this).css({
          color: '#000'
        });
      });

      $('input.flag-input').blur(function () {
        $(this).css({
          color: '#fff'
        });
        $this.saveFlag($(this));
      });

      $('#flags-close').click(function () {
        $('.modal-overlay').click();
      });
    });
  }

  saveFlag(flag) {
    if (flag.attr('placeholder') !== flag.val()) {
      let key = flag.attr('id');

      flags[key].label = flag.val();
      setToolkitStorageKey('flags', flags);

      this.updateFlagLabels();
      this.invoke();
    }
  }

  updateFlagLabels() {
    redFlagLabel = flags.red.label;
    blueFlagLabel = flags.blue.label;
    orangeFlagLabel = flags.orange.label;
    yellowFlagLabel = flags.yellow.label;
    greenFlagLabel = flags.green.label;
    purpleFlagLabel = flags.purple.label;
  }

  storeDefaultFlags() {
    const flagsJSON = {
      red: {
        label: 'Red',
        color: '#d43d2e'
      },
      orange: {
        label: 'Orange',
        color: '#ff7b00'
      },
      yellow: {
        label: 'Yellow',
        color: '#f8e136'
      },
      green: {
        label: 'Green',
        color: '#9ac234'
      },
      blue: {
        label: 'Blue',
        color: '#0082cb'
      },
      purple: {
        label: 'Purple',
        color: '#9384b7'
      }
    };

    setToolkitStorageKey('flags', flagsJSON);
  }
}
<commit_msg>Use applicationController.send('closeModal') to close modals (#1305)<commit_after>import { Feature } from 'toolkit/extension/features/feature';
import { getToolkitStorageKey, setToolkitStorageKey } from 'toolkit/extension/utils/toolkit';
import { getCurrentRouteName } from 'toolkit/extension/utils/ynab';
import { controllerLookup } from 'toolkit/extension/utils/ember';

let flags;
let redFlagLabel;
let blueFlagLabel;
let orangeFlagLabel;
let yellowFlagLabel;
let greenFlagLabel;
let purpleFlagLabel;

export class CustomFlagNames extends Feature {
  constructor() {
    super();
    if (!getToolkitStorageKey('flags')) {
      this.storeDefaultFlags();
    }
    if (typeof flags === 'undefined') {
      flags = getToolkitStorageKey('flags');
      this.updateFlagLabels();
    }
  }

  shouldInvoke() {
    return getCurrentRouteName().indexOf('account') !== -1;
  }

  invoke() {
    $('.ynab-grid-cell-flag .ynab-flag-red').parent().attr('title', redFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-blue').parent().attr('title', blueFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-orange').parent().attr('title', orangeFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-yellow').parent().attr('title', yellowFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-green').parent().attr('title', greenFlagLabel);
    $('.ynab-grid-cell-flag .ynab-flag-purple').parent().attr('title', purpleFlagLabel);
  }

  observe(changedNodes) {
    if (!this.shouldInvoke()) return;

    if (changedNodes.has('layout user-logged-in') || changedNodes.has('ynab-grid-body')) {
      this.invoke();
    }

    if (changedNodes.has('ynab-u modal-popup modal-account-flags ember-view modal-overlay active')) {
      $('.ynab-flag-red .label, .ynab-flag-red .label-bg').text(redFlagLabel);
      $('.ynab-flag-blue .label, .ynab-flag-blue .label-bg').text(blueFlagLabel);
      $('.ynab-flag-orange .label, .ynab-flag-orange .label-bg').text(orangeFlagLabel);
      $('.ynab-flag-yellow .label, .ynab-flag-yellow .label-bg').text(yellowFlagLabel);
      $('.ynab-flag-green .label, .ynab-flag-green .label-bg').text(greenFlagLabel);
      $('.ynab-flag-purple .label, .ynab-flag-purple .label-bg').text(purpleFlagLabel);

      $('.modal-account-flags .modal').css({ height: '22em' })
        .append($('<div>', { id: 'account-flags-actions' }).css({ padding: '0 .3em' })
          .append($('<button>', { id: 'flags-edit', class: 'button button-primary' })
            .append('Edit Flag Names ')
            .append($('<i>', { class: 'flaticon stroke compose-3' }))));

      this.addEventListeners();
    }
  }

  onRouteChanged() {
    if (!this.shouldInvoke()) return;

    this.invoke();
  }

  addEventListeners() {
    let $this = this;
    $('#flags-edit').click(function () {
      $('.modal-account-flags .modal-list').empty();

      for (let key in flags) {
        let flag = flags[key];

        $('.modal-account-flags .modal-list')
          .append($('<li>')
            .append($('<input>', {
              id: key, type: 'text', class: 'flag-input', value: flag.label, placeholder: flag.label
            }).css({
              color: '#fff',
              fill: flag.color,
              'background-color': flag.color,
              height: 30,
              padding: '0 .7em',
              'margin-bottom': '.3em',
              border: 'none'
            })));
      }

      $('#account-flags-actions').empty();

      $('#account-flags-actions')
        .append($('<button>', {
          id: 'flags-close',
          class: 'button button-primary'
        }).append('Ok ')
          .append($('<i>', {
            class: 'flaticon stroke checkmark-2'
          })));

      $('input.flag-input').focus(function () {
        $(this).css({
          color: '#000'
        });
      });

      $('input.flag-input').blur(function () {
        $(this).css({
          color: '#fff'
        });
        $this.saveFlag($(this));
      });

      $('#flags-close').click(function () {
        controllerLookup('application').send('closeModal');
      });
    });
  }

  saveFlag(flag) {
    if (flag.attr('placeholder') !== flag.val()) {
      let key = flag.attr('id');

      flags[key].label = flag.val();
      setToolkitStorageKey('flags', flags);

      this.updateFlagLabels();
      this.invoke();
    }
  }

  updateFlagLabels() {
    redFlagLabel = flags.red.label;
    blueFlagLabel = flags.blue.label;
    orangeFlagLabel = flags.orange.label;
    yellowFlagLabel = flags.yellow.label;
    greenFlagLabel = flags.green.label;
    purpleFlagLabel = flags.purple.label;
  }

  storeDefaultFlags() {
    const flagsJSON = {
      red: {
        label: 'Red',
        color: '#d43d2e'
      },
      orange: {
        label: 'Orange',
        color: '#ff7b00'
      },
      yellow: {
        label: 'Yellow',
        color: '#f8e136'
      },
      green: {
        label: 'Green',
        color: '#9ac234'
      },
      blue: {
        label: 'Blue',
        color: '#0082cb'
      },
      purple: {
        label: 'Purple',
        color: '#9384b7'
      }
    };

    setToolkitStorageKey('flags', flagsJSON);
  }
}
<|endoftext|>


<commit_before>// Author: Tamio Patrick Honma <tamio@honma.de>
// License: MIT

(function app() {

    const bbsContent = {
        baseUrl: "assets/articles/",
        boards: [
            {
                name: "bulletin-board.de",
                articles: [
                    {
                        "name": "index.md"
                    }
                ]
            },
            {
                name: "personal",
                articles: [
                    {
                        "name": "tamio.md"
                    }
                ]
            },
            {
                name: "links",
                articles: [
                    {
                        "name": "amiga.md"
                    }
                ]
            }
        ]
    };

    const event = {

        init() {
            view.$body.on('keyup', this.onKeyUp)
                .on('keydown', this.onKeyDown)
                .on('keypress', this.onKeyPress);
            this.initPrompt();
            view.$window.on('scroll touchmove mousewheel', this.onScroll);
            view.initCursor();
        },

        initPrompt() {
            view.$prompt.on('ctrlChar', this.onCtrlChar)
                .on('command', this.onCommand)
                .on('async:feed', this.onAsyncFeed)
                .on('async:markdown', this.onAsyncText);
        },

        onCommand(e, c) {
            e.preventDefault();
            view.outputCommandResult(controller.executeCommand(c));
        },

        onAsyncFeed(e, d) {
            e.preventDefault();
            view.outputCommandResult(controller.getFeedArticles(d));
        },

        onAsyncText(e, d) {
            e.preventDefault();
            view.outputCommandResult(d);
        },

        onCtrlChar(e, t) {
            e.preventDefault();
            //console.log(`codename: ${t}`);
            switch (t.toLowerCase()) {
                case 'backspace':
                    view.deleteChar();
                    break;
                case 'delete':
                    view.moveCursorForward();
                    view.deleteChar();
                    break;
                case 'arrowleft':
                    view.moveCursorBack();
                    break;
                case 'arrowright':
                    view.moveCursorForward();
                    break;
                case 'arrowup':
                    view.promptHistory(true);
                    break;
                case 'arrowdown':
                    view.promptHistory(false);
                    break;
                case 'end':
                    view.removeCursor();
                    view.moveCursor(view.$prompt.text().length);
                    break;
                case 'home':
                    view.moveCursor(0);
                    break;
                case 'pagedown':
                    view.scrollPage(1);
                    break;
                case 'pageup':
                    view.scrollPage(-1);
                    break;
                case 'enter':
                    view.enterCommandLine();
                    break;
            }
        },

        onKeyUp(e) {
            e.preventDefault();
        },

        onKeyDown(e) {
            e.preventDefault();
            view.typeChar(e.key);
        },

        onKeyPress(e) {
            e.preventDefault();
        },

        onScroll(e) {
            if (view.isScrolling === true) {
                e.preventDefault();
                e.stopPropagation();
            }
        }
    };

    const view = {

        init() {
            this.$document = $(document);
            this.$window = $(window);
            this.$scroll = $('html, body');
            this.$body = $('body');
            this.$terminal = $('#terminal');
            if (this.$terminal.length===0) {
                this.$body.append('

');
                this.$terminal = $('#terminal');
            }
            this.initTerminal();
        },

        initTerminal() {
            this.$cli = $('#cli');
            if (this.$cli.length===0) {
                this.clearTerminal();
            }
            this.$prompt = $('#cli .prompt');
            this.$history = false;
            this.curPos = 0;
            this.isScrolling = false;
            this.scrollSpeed = 800;
        },

        clearTerminal() {
            this.$terminal.html(`<div class="print"></div><div id="cli"><span class="label"></span><span class="prompt"></span></div>`);
            this.initTerminal();
            event.initPrompt();
        },

        typeChar(c) {
            let realChr = controller.triggerCtrlCodes(c);
            if (realChr != '') {
                this.removeCursor();
                let p = this.$prompt.html();
                let pright = (this.curPos == p.length) ?
                    this.getCursor() :
                this.getCursor(p.substring(this.curPos, this.curPos+1)) +
                p.substring(this.curPos+1);
                this.$prompt.html(p.substring(0, this.curPos) + realChr + pright);
                this.curPos = this.curPos + 1;
            }
        },

        deleteChar() {
            if (this.curPos > 0) {
                this.removeCursor();
                let p = this.$prompt.html();
                this.$prompt.html(p.substring(0, this.curPos-1) + p.substring(this.curPos));
                this.curPos = this.curPos - 1;
                this.setCursor();
            }
        },

        moveCursorBack() {
            if (this.curPos > 0) {
                this.removeCursor();
                this.curPos = this.curPos - 1;
                this.setCursor();
            }
        },

        moveCursorForward() {
            this.removeCursor();
            if (this.curPos < this.$prompt.text().length) {
                this.curPos = this.curPos + 1;
            }
            this.setCursor();
        },

        moveCursor(pos = 0) {
            this.curPos = pos;
            this.setCursor();
        },

        initCursor(char = " ") {
            this.removeCursor();
            this.curPos = 0;
            this.$history = false;
            this.$prompt.html(this.getCursor(char));
        },

        setCursor() {
            if (this.curPos >= 0) {
                this.removeCursor();
                let p = this.$prompt.html();
                this.$prompt.html(
                    p.substring(0, this.curPos) + (
                        (this.curPos == p.length) ?
                            this.getCursor() :
                            this.getCursor(p.substring(this.curPos, this.curPos+1))
                    ) + p.substring(this.curPos+1)
                );
            } else {
                if (p.length === 0) {
                    this.initCursor();
                }
            }
        },

        getCursor(char = " ") {
            return `<span class="cursor">${char}</span>`;
        },

        removeCursor() {
            let $cur = this.$prompt.children('.cursor');
            let chr = $cur.html();
            if (chr == " ") {
                $cur.remove();
            } else {
                this.$prompt.html(this.$prompt.text());
            }
        },

        printTerminal(txt, cssClasses="command") {
            this.$cli.prev().append(`<div class="${cssClasses}">${txt}</div>`);
            this.$terminal.scrollTop(this.$terminal[0].scrollHeight);
        },

        enterCommandLine() {
            let p = $.trim(this.$prompt.text());
            this.printTerminal(p, "command label");
            controller.triggerCommand(p);
            this.initCursor();
        },

        outputCommandResult(out) {
            this.printTerminal(out, "command output");
        },

        promptHistory(prev = true) {
            if (this.$history === false) {
                this.$history = (prev) ? $("#terminal .print .command.label").last() : $("#terminal .print .command.label").first();
            } else {
                this.$history = (prev) ? this.$history.prevAll(".command.label").first() : this.$history.nextAll(".command.label").first();
            }
            if (this.$history.length) {
                const h = this.$history.text();
                this.$prompt.html(h);
                this.moveCursor(h.length);
            } else {
                this.initCursor();
            }
        },

        scrollPage(direction) {
            if (this.isScrolling === false) {
                this.isScrolling = true;
                direction = ($.isNumeric(direction) && Math.abs(direction) === 1) ? direction : 1;
                let th = this.$terminal[0].scrollHeight,
                    ch = this.$terminal.height(),
                    offset = this.$terminal.scrollTop() + ch * direction,
                    adjusted = (offset < 0) ? 0 : (offset + ch > th) ? th - ch : offset;
                    view.$terminal.animate({
                        scrollTop: adjusted
                    },
                    (offset !== adjusted) ? Math.floor(this.scrollSpeed / 6.6666) : this.scrollSpeed,
                    () => {
                        this.isScrolling = false
                    }
                );
            }
        },

        scrollDocumentPage(direction) {
            if (this.isScrolling === false) {
                this.isScrolling = true;
                direction = ($.isNumeric(direction) && Math.abs(direction) === 1) ? direction : 1;
                let dh = this.$document.height(),
                    wh = this.$window.height(),
                    offset = this.$window.scrollTop() + wh * direction,
                    adjusted = (offset < 0) ? 0 : (offset + wh > dh) ? dh - wh : offset;
                    view.$scroll.animate({
                        scrollTop: adjusted
                    },
                    (offset !== adjusted) ? Math.floor(this.scrollSpeed / 6.6666) : this.scrollSpeed,
                    () => {
                        this.isScrolling = false
                    }
                );
            }
        }
    };

    const controller = {

        init() {
            this.markdown = new showdown.Converter();
        },

        triggerCtrlCodes(codename) {
            let r = '';
            if (codename.length > 1) {
                view.$prompt.trigger('ctrlChar', [codename]);
            } else {
                r = codename;
            }
            return r;
        },

        triggerCommand(prompt = '') {
            view.$prompt.trigger('command', this.getCommand(prompt));
        },

        executeCommand(cmd = {}) {
            let out = '';
            switch (cmd.command) {
                case '':
                    break;
                case 'cls':
                    view.clearTerminal();
                    break;
                case 'exit':
                    view.$terminal.remove();
                    window.history.back();
                    break;
                case 'about':
                    out = `Made by Tamio Patrick Honma (about.me)
MIT License (MIT) Copyright (c) 2016 Tamio Honma
Use the command 'licences' to see all licences of this website.`;
                    break;
                case 'licences':
                case 'licence':
                case 'legal':
                case 'terms':
                    out = `TinyCLI, Bulletin Board CLI are MIT licensed by Tamio Honma

                        Efecto Matrix en 40 líneas by 0utKast (http://codepen.io/0utKast/pen/GpzobR)`;
                    break;
                case '?':
                case 'h':
                case 'help':
                    out = `Commands:

                        bbs [contents of this site]

                        calc <simple math> [simple math calculator]

                        rss <url> [list contents of an rss feed]

                        search <phrase> [search the web]

                        web <url> [go to url]

                        loadwb [dive into nostalgia]

                        shirt [express nostalgia]

                        cls [clear screen]

                        about, licences, help

                        exit`;
                    break;
                case 'eval':
                case 'calc':
                    out = `${eval(cmd.arguments.join(' '))}`;
                    break;
                case 'google':
                    this.openUrl(encodeURI(`https://www.google.com/search?q=${cmd.arguments.join(' ')}`));
                    break;
                case 'search':
                    this.openUrl(encodeURI(`http://www.goosh.org/#${cmd.arguments.join(' ')}`));
                    break;
                case 'goto':
                case 'web':
                    if (this.openUrl(encodeURI(`${cmd.arguments[0]}`))) {
                        out = `Opened '${cmd.arguments[0]}'`;
                    } else {
                        out = 'Usage: web <url>';
                    }
                    break;
                case 'rss':
                    if (this.getFeedYQL(cmd.arguments[0])) {
                        out = `Trying to open '${cmd.arguments[0]}' `;
                    } else {
                        out = 'Usage: rss <url>';
                    }
                    break;
                case 'bbs':
                    if (cmd.arguments.length === 0) {
                        out = this.listBBSBoards(bbsContent.boards);
                        out = `${out}<br>Type 'bbs <article name>' to read an article.`;
                    } else {
                        this.getBBSArticle(cmd.arguments[0], bbsContent.baseUrl);
                    }
                    break;
                case 'loadwb':
                    this.openUrl("http://pnacl-amiga-emulator.appspot.com/");
                    break;
                case 'shirt':
                    this.openUrl("http://lemon.spreadshirt.co.uk/de/shell-A8380690/customize/color/317");
                    break;
                default:
                    out = `Command '${cmd.command}' not found`;
                    break;
            }
            return out;
        },

        getCommand(prompt = '') {
            let arrPrompt = prompt.split(' ');
            return {
                'command': arrPrompt.shift().toLowerCase(),
                'arguments': arrPrompt.filter((arg) => {return arg.length > 0;})
            }
        },

        getFeedArticles(json) {
            let out = '';
            try {
                if ($.isArray(json.query.results.item)) {
                    $.each(json.query.results.item, (i, item) => {
                        out = `${out}<a href="${item.link}" title="${this.encodeHtmlEntity(item.description)}">${item.title}</a><br>`;
                    });
                } else {
                    out = 'Error: No feed articles found.';
                }
            } catch(e) {
                out = `Error: Invalid feed. Please use a valid url.<br><i>(${e})</i>`;
            }
            return out;
        },

        getFeedYQL(url) {
            if (typeof url === 'undefined' || url === 'undefined' || url.length === 0) {
                return false;
            } else {
                const yql = `https://query.yahooapis.com/v1/public/yql?q=select%20title%2Clink%2Cdescription%20from%20rss%20where%20url%3D%22${encodeURI(url)}%3Fformat%3Dxml%22&format=json&diagnostics=true&callback=`;
                $.getJSON(yql, (data) => {
                    view.$prompt.trigger('async:feed', data);
                }, "jsonp");
                return true;
            }
        },

        getBBSArticle(name, baseUrl = '') {
            let out = '';
            try {
                if (name.length) {
                    $.get(baseUrl + name, (data) => {
                        view.$prompt.trigger('async:markdown', this.markdown.makeHtml(data));
                    }, "text");
                } else {
                    out = `Error: Article not found.`;
                }
            } catch(e) {
                out = `Error: Invalid article name '${name}'.<br><i>(${e})</i>`;
            }
            return out;
        },

        listBBSArticles(bbsArticles = null, prefix = '') {
            let out = '';
            $.each(bbsArticles, (i, item) => {
                out = `${out}${prefix}${item.name}<br>`;
            });
            return out;
        },

        listBBSBoards(bbsBoards = null) {
            let out = '';
            $.each(bbsBoards, (i, item) => {
                out = `${out}<i>Chapter '${item.name}'</i><br>`;
                if (item.articles.length>0) {
                    out = `${out}${this.listBBSArticles(item.articles, '    ')}`;
                }
            });
            return out;
        },

        decodeHtmlEntity(str) {
            return str.replace(/&#(\d+);/g, function(match, dec) {
                return String.fromCharCode(dec);
            });
        },

        encodeHtmlEntity(str) {
            var buf = [];
            for (var i=str.length-1;i>=0;i--) {
                buf.unshift(['&#', str[i].charCodeAt(), ';'].join(''));
            }
            return buf.join('');
        },

        openUrl(url) {
            if (typeof url === 'undefined' || url === 'undefined' || url.length === 0) {
                return false;
            } else {
                window.open(url, '_blank');
                return true;
            }
        }
    };

    (function init() {
        $(document).ready(
            () => {
                controller.init();
                view.init();
                event.init();
            }
        )
    }());

}());
<commit_msg>DONE: Execute hash in terminal on document load<commit_after>// Author: Tamio Patrick Honma <tamio@honma.de>
// License: MIT

(function app() {

    const bbsContent = {
        baseUrl: "assets/articles/",
        boards: [
            {
                name: "bulletin-board.de",
                articles: [
                    {
                        "name": "index.md"
                    }
                ]
            },
            {
                name: "personal",
                articles: [
                    {
                        "name": "tamio.md"
                    }
                ]
            },
            {
                name: "links",
                articles: [
                    {
                        "name": "amiga.md"
                    }
                ]
            }
        ]
    };

    const event = {

        init() {
            view.$body.on('keyup', this.onKeyUp)
                .on('keydown', this.onKeyDown)
                .on('keypress', this.onKeyPress);
            this.initPrompt();
            view.$window.on('scroll touchmove mousewheel', this.onScroll);
            view.initCursor();
            this.executeHashInTerminal();
        },

        initPrompt() {
            view.$prompt.on('ctrlChar', this.onCtrlChar)
                .on('command', this.onCommand)
                .on('async:feed', this.onAsyncFeed)
                .on('async:markdown', this.onAsyncText);
        },

        executeHashInTerminal() {
            const hash = location.hash.substr(1);
            if (hash) {
                controller.executePresetString(hash);
            }
        },

        onCommand(e, c) {
            e.preventDefault();
            view.outputCommandResult(controller.executeCommand(c));
        },

        onAsyncFeed(e, d) {
            e.preventDefault();
            view.outputCommandResult(controller.getFeedArticles(d));
        },

        onAsyncText(e, d) {
            e.preventDefault();
            view.outputCommandResult(d);
        },

        onCtrlChar(e, t) {
            e.preventDefault();
            //console.log(`codename: ${t}`);
            switch (t.toLowerCase()) {
                case 'backspace':
                    view.deleteChar();
                    break;
                case 'delete':
                    view.moveCursorForward();
                    view.deleteChar();
                    break;
                case 'arrowleft':
                    view.moveCursorBack();
                    break;
                case 'arrowright':
                    view.moveCursorForward();
                    break;
                case 'arrowup':
                    view.promptHistory(true);
                    break;
                case 'arrowdown':
                    view.promptHistory(false);
                    break;
                case 'end':
                    view.removeCursor();
                    view.moveCursor(view.$prompt.text().length);
                    break;
                case 'home':
                    view.moveCursor(0);
                    break;
                case 'pagedown':
                    view.scrollPage(1);
                    break;
                case 'pageup':
                    view.scrollPage(-1);
                    break;
                case 'enter':
                    view.enterCommandLine();
                    break;
            }
        },

        onKeyUp(e) {
            e.preventDefault();
        },

        onKeyDown(e) {
            e.preventDefault();
            view.typeChar(e.key);
        },

        onKeyPress(e) {
            e.preventDefault();
        },

        onScroll(e) {
            if (view.isScrolling === true) {
                e.preventDefault();
                e.stopPropagation();
            }
        }
    };

    const view = {

        init() {
            this.$document = $(document);
            this.$window = $(window);
            this.$scroll = $('html, body');
            this.$body = $('body');
            this.$terminal = $('#terminal');
            if (this.$terminal.length===0) {
                this.$body.append('

');
                this.$terminal = $('#terminal');
            }
            this.initTerminal();
        },

        initTerminal() {
            this.$cli = $('#cli');
            if (this.$cli.length===0) {
                this.clearTerminal();
            }
            this.$prompt = $('#cli .prompt');
            this.$history = false;
            this.curPos = 0;
            this.isScrolling = false;
            this.scrollSpeed = 800;
        },

        clearTerminal() {
            this.$terminal.html(`<div class="print"></div><div id="cli"><span class="label"></span><span class="prompt"></span></div>`);
            this.initTerminal();
            event.initPrompt();
        },

        typeChar(c) {
            let realChr = controller.triggerCtrlCodes(c);
            if (realChr != '') {
                this.removeCursor();
                let p = this.$prompt.html();
                let pright = (this.curPos == p.length) ?
                    this.getCursor() :
                this.getCursor(p.substring(this.curPos, this.curPos+1)) +
                p.substring(this.curPos+1);
                this.$prompt.html(p.substring(0, this.curPos) + realChr + pright);
                this.curPos = this.curPos + 1;
            }
        },

        deleteChar() {
            if (this.curPos > 0) {
                this.removeCursor();
                let p = this.$prompt.html();
                this.$prompt.html(p.substring(0, this.curPos-1) + p.substring(this.curPos));
                this.curPos = this.curPos - 1;
                this.setCursor();
            }
        },

        moveCursorBack() {
            if (this.curPos > 0) {
                this.removeCursor();
                this.curPos = this.curPos - 1;
                this.setCursor();
            }
        },

        moveCursorForward() {
            this.removeCursor();
            if (this.curPos < this.$prompt.text().length) {
                this.curPos = this.curPos + 1;
            }
            this.setCursor();
        },

        moveCursor(pos = 0) {
            this.curPos = pos;
            this.setCursor();
        },

        initCursor(char = " ") {
            this.removeCursor();
            this.curPos = 0;
            this.$history = false;
            this.$prompt.html(this.getCursor(char));
        },

        setCursor() {
            if (this.curPos >= 0) {
                this.removeCursor();
                let p = this.$prompt.html();
                this.$prompt.html(
                    p.substring(0, this.curPos) + (
                        (this.curPos == p.length) ?
                            this.getCursor() :
                            this.getCursor(p.substring(this.curPos, this.curPos+1))
                    ) + p.substring(this.curPos+1)
                );
            } else {
                if (p.length === 0) {
                    this.initCursor();
                }
            }
        },

        getCursor(char = " ") {
            return `<span class="cursor">${char}</span>`;
        },

        removeCursor() {
            let $cur = this.$prompt.children('.cursor');
            let chr = $cur.html();
            if (chr == " ") {
                $cur.remove();
            } else {
                this.$prompt.html(this.$prompt.text());
            }
        },

        printTerminal(txt, cssClasses="command") {
            this.$cli.prev().append(`<div class="${cssClasses}">${txt}</div>`);
            this.$terminal.scrollTop(this.$terminal[0].scrollHeight);
        },

        enterCommandLine() {
            let p = $.trim(this.$prompt.text());
            this.printTerminal(p, "command label");
            controller.triggerCommand(p);
            this.initCursor();
        },

        outputCommandResult(out) {
            this.printTerminal(out, "command output");
        },

        promptHistory(prev = true) {
            if (this.$history === false) {
                this.$history = (prev) ? $("#terminal .print .command.label").last() : $("#terminal .print .command.label").first();
            } else {
                this.$history = (prev) ? this.$history.prevAll(".command.label").first() : this.$history.nextAll(".command.label").first();
            }
            if (this.$history.length) {
                const h = this.$history.text();
                this.$prompt.html(h);
                this.moveCursor(h.length);
            } else {
                this.initCursor();
            }
        },

        scrollPage(direction) {
            if (this.isScrolling === false) {
                this.isScrolling = true;
                direction = ($.isNumeric(direction) && Math.abs(direction) === 1) ? direction : 1;
                let th = this.$terminal[0].scrollHeight,
                    ch = this.$terminal.height(),
                    offset = this.$terminal.scrollTop() + ch * direction,
                    adjusted = (offset < 0) ? 0 : (offset + ch > th) ? th - ch : offset;
                    view.$terminal.animate({
                        scrollTop: adjusted
                    },
                    (offset !== adjusted) ? Math.floor(this.scrollSpeed / 6.6666) : this.scrollSpeed,
                    () => {
                        this.isScrolling = false
                    }
                );
            }
        },

        scrollDocumentPage(direction) {
            if (this.isScrolling === false) {
                this.isScrolling = true;
                direction = ($.isNumeric(direction) && Math.abs(direction) === 1) ? direction : 1;
                let dh = this.$document.height(),
                    wh = this.$window.height(),
                    offset = this.$window.scrollTop() + wh * direction,
                    adjusted = (offset < 0) ? 0 : (offset + wh > dh) ? dh - wh : offset;
                    view.$scroll.animate({
                        scrollTop: adjusted
                    },
                    (offset !== adjusted) ? Math.floor(this.scrollSpeed / 6.6666) : this.scrollSpeed,
                    () => {
                        this.isScrolling = false
                    }
                );
            }
        }
    };

    const controller = {

        init() {
            this.markdown = new showdown.Converter();
        },

        triggerCtrlCodes(codename) {
            let r = '';
            if (codename.length > 1) {
                view.$prompt.trigger('ctrlChar', [codename]);
            } else {
                r = codename;
            }
            return r;
        },

        triggerCommand(prompt = '') {
            view.$prompt.trigger('command', this.getCommand(prompt));
        },

        executeCommand(cmd = {}) {
            let out = '';
            switch (cmd.command) {
                case '':
                    break;
                case 'cls':
                    view.clearTerminal();
                    break;
                case 'exit':
                    view.$terminal.remove();
                    window.history.back();
                    break;
                case 'about':
                    out = `Made by Tamio Patrick Honma (about.me)
MIT License (MIT) Copyright (c) 2016 Tamio Honma
Use the command 'licences' to see all licences of this website.`;
                    break;
                case 'licences':
                case 'licence':
                case 'legal':
                case 'terms':
                    out = `TinyCLI, Bulletin Board CLI are MIT licensed by Tamio Honma

                        Efecto Matrix en 40 líneas by 0utKast (http://codepen.io/0utKast/pen/GpzobR)`;
                    break;
                case '?':
                case 'h':
                case 'help':
                    out = `Commands:

                        bbs [contents of this site]

                        calc <simple math> [simple math calculator]

                        rss <url> [list contents of an rss feed]

                        search <phrase> [search the web]

                        web <url> [go to url]

                        loadwb [dive into nostalgia]

                        shirt [express nostalgia]

                        cls [clear screen]

                        about, licences, help

                        exit`;
                    break;
                case 'eval':
                case 'calc':
                    out = `${eval(cmd.arguments.join(' '))}`;
                    break;
                case 'google':
                    this.openUrl(encodeURI(`https://www.google.com/search?q=${cmd.arguments.join(' ')}`));
                    break;
                case 'search':
                    this.openUrl(encodeURI(`http://www.goosh.org/#${cmd.arguments.join(' ')}`));
                    break;
                case 'goto':
                case 'web':
                    if (this.openUrl(encodeURI(`${cmd.arguments[0]}`))) {
                        out = `Opened '${cmd.arguments[0]}'`;
                    } else {
                        out = 'Usage: web <url>';
                    }
                    break;
                case 'rss':
                    if (this.getFeedYQL(cmd.arguments[0])) {
                        out = `Trying to open '${cmd.arguments[0]}' `;
                    } else {
                        out = 'Usage: rss <url>';
                    }
                    break;
                case 'bbs':
                    if (cmd.arguments.length === 0) {
                        out = this.listBBSBoards(bbsContent.boards);
                        out = `${out}<br>Type 'bbs <article name>' to read an article.`;
                    } else {
                        this.getBBSArticle(cmd.arguments[0], bbsContent.baseUrl);
                    }
                    break;
                case 'loadwb':
                    this.openUrl("http://pnacl-amiga-emulator.appspot.com/");
                    break;
                case 'shirt':
                    this.openUrl("http://lemon.spreadshirt.co.uk/de/shell-A8380690/customize/color/317");
                    break;
                default:
                    out = `Command '${cmd.command}' not found`;
                    break;
            }
            return out;
        },

        executePresetString(str) {
            if (str) {
                view.$prompt.html(str);
                view.moveCursor(str.length);
                view.enterCommandLine();
            }
        },

        getCommand(prompt = '') {
            let arrPrompt = prompt.split(' ');
            return {
                'command': arrPrompt.shift().toLowerCase(),
                'arguments': arrPrompt.filter((arg) => {return arg.length > 0;})
            }
        },

        getFeedArticles(json) {
            let out = '';
            try {
                if ($.isArray(json.query.results.item)) {
                    $.each(json.query.results.item, (i, item) => {
                        out = `${out}<a href="${item.link}" title="${this.encodeHtmlEntity(item.description)}">${item.title}</a><br>`;
                    });
                } else {
                    out = 'Error: No feed articles found.';
                }
            } catch(e) {
                out = `Error: Invalid feed. Please use a valid url.<br><i>(${e})</i>`;
            }
            return out;
        },

        getFeedYQL(url) {
            if (typeof url === 'undefined' || url === 'undefined' || url.length === 0) {
                return false;
            } else {
                const yql = `https://query.yahooapis.com/v1/public/yql?q=select%20title%2Clink%2Cdescription%20from%20rss%20where%20url%3D%22${encodeURI(url)}%3Fformat%3Dxml%22&format=json&diagnostics=true&callback=`;
                $.getJSON(yql, (data) => {
                    view.$prompt.trigger('async:feed', data);
                }, "jsonp");
                return true;
            }
        },

        getBBSArticle(name, baseUrl = '') {
            let out = '';
            try {
                if (name.length) {
                    $.get(baseUrl + name, (data) => {
                        view.$prompt.trigger('async:markdown', this.markdown.makeHtml(data));
                    }, "text");
                } else {
                    out = `Error: Article not found.`;
                }
            } catch(e) {
                out = `Error: Invalid article name '${name}'.<br><i>(${e})</i>`;
            }
            return out;
        },

        listBBSArticles(bbsArticles = null, prefix = '') {
            let out = '';
            $.each(bbsArticles, (i, item) => {
                out = `${out}${prefix}${item.name}<br>`;
            });
            return out;
        },

        listBBSBoards(bbsBoards = null) {
            let out = '';
            $.each(bbsBoards, (i, item) => {
                out = `${out}<i>Chapter '${item.name}'</i><br>`;
                if (item.articles.length>0) {
                    out = `${out}${this.listBBSArticles(item.articles, '    ')}`;
                }
            });
            return out;
        },

        decodeHtmlEntity(str) {
            return str.replace(/&#(\d+);/g, function(match, dec) {
                return String.fromCharCode(dec);
            });
        },

        encodeHtmlEntity(str) {
            var buf = [];
            for (var i=str.length-1;i>=0;i--) {
                buf.unshift(['&#', str[i].charCodeAt(), ';'].join(''));
            }
            return buf.join('');
        },

        openUrl(url) {
            if (typeof url === 'undefined' || url === 'undefined' || url.length === 0) {
                return false;
            } else {
                window.open(url, '_blank');
                return true;
            }
        }
    };

    (function init() {
        $(document).ready(
            () => {
                controller.init();
                view.init();
                event.init();
            }
        )
    }());

}());
<|endoftext|>