<commit_before>// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// For license information, please see license.txt

frappe.provide("erpnext.payment_tool");

// Help content
frappe.ui.form.on("Payment Tool", "onload", function(frm) {
	frm.set_value("make_jv_help", ' '
		+ __("Note: If payment is not made against any reference, make Journal Voucher manually."));

	frm.set_query("payment_account", function() {
		return {
			filters: [
				['Account', 'account_type', 'in', 'Bank, Cash'],
				['Account', 'group_or_ledger', '=', 'Ledger'],
				['Account', 'company', '=', frm.doc.company]
			]
		}
	});

	frm.set_query("against_voucher_type", "payment_tool_details", function() {
		return {
			filters: {"name": ["in", ["Sales Invoice", "Purchase Invoice", "Journal Voucher", "Sales Order", "Purchase Order"]]}
		};
	});
});

frappe.ui.form.on("Payment Tool", "refresh", function(frm) {
	frappe.ui.form.trigger("Payment Tool", "party_type");
});

frappe.ui.form.on("Payment Tool", "party_type", function(frm) {
	frm.toggle_reqd("customer", frm.doc.party_type == "Customer");
	frm.toggle_reqd("supplier", frm.doc.party_type == "Supplier");
});

frappe.ui.form.on("Payment Tool", "company", function(frm) {
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

frappe.ui.form.on("Payment Tool", "received_or_paid", function(frm) {
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

// Fetch bank/cash account based on payment mode
cur_frm.add_fetch("payment_mode", "default_account", "payment_account");

// Set party account name
frappe.ui.form.on("Payment Tool", "customer", function(frm) {
	erpnext.payment_tool.set_party_account(frm);
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

frappe.ui.form.on("Payment Tool", "supplier", function(frm) {
	erpnext.payment_tool.set_party_account(frm);
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

erpnext.payment_tool.check_mandatory_to_set_button = function(frm) {
	if (frm.doc.company && frm.doc.party_type && frm.doc.received_or_paid && (frm.doc.customer || frm.doc.supplier)) {
		frm.fields_dict.get_outstanding_vouchers.$input.addClass("btn-primary");
	}
}

//Set Button color
erpnext.payment_tool.set_party_account = function(frm) {
	if(frm.doc.party_type == "Customer") {
		var party_name = frm.doc.customer;
	} else {
		var party_name = frm.doc.supplier;
	}
	return  frappe.call({
		method: 'erpnext.accounts.doctype.payment_tool.payment_tool.get_party_account',
		args: {
			party_type: frm.doc.party_type,
			party_name: party_name
		},
		callback: function(r, rt) {
			if(!r.exc) {
				frm.set_value("party_account", r.message);
			}
		}
	});
}

// Get outstanding vouchers
frappe.ui.form.on("Payment Tool", "get_outstanding_vouchers", function(frm) {
	erpnext.payment_tool.check_mandatory_to_fetch(frm.doc);

	frm.set_value("payment_tool_details", []);

	return  frappe.call({
		method: 'erpnext.accounts.doctype.payment_tool.payment_tool.get_outstanding_vouchers',
		args: {
			args: {
				"company": frm.doc.company,
				"party_type": frm.doc.party_type,
				"received_or_paid": frm.doc.received_or_paid,
				"party_name": frm.doc.party_type == "Customer" ? frm.doc.customer : frm.doc.supplier,
				"party_account": frm.doc.party_account
			}
		},
		callback: function(r, rt) {
			if(r.message) {
				frm.fields_dict.get_outstanding_vouchers.$input.removeClass("btn-primary");
				frm.fields_dict.make_journal_voucher.$input.addClass("btn-primary");

				frappe.model.clear_table(frm.doc, "payment_tool_details");
				$.each(r.message, function(i, d) {
					var invoice_detail = frappe.model.add_child(frm.doc, "Payment Tool Detail", "payment_tool_details");
					invoice_detail.against_voucher_type = d.voucher_type;
					invoice_detail.against_voucher_no = d.voucher_no;
					invoice_detail.total_amount = d.invoice_amount;
					invoice_detail.outstanding_amount = d.outstanding_amount;
				});
			}
			refresh_field("payment_tool_details");
			erpnext.payment_tool.set_total_payment_amount(frm);
		}
	});
});

// validate against_voucher_type
frappe.ui.form.on("Payment Tool Detail", "against_voucher_type", function(frm) {
	erpnext.payment_tool.validate_against_voucher(frm);
});

erpnext.payment_tool.validate_against_voucher = function(frm) {
	$.each(frm.doc.payment_tool_details || [], function(i, row) {
		if(frm.doc.party_type=="Customer"
			&& !in_list(["Sales Order", "Sales Invoice", "Journal Voucher"], row.against_voucher_type)) {
				frappe.model.set_value(row.doctype, row.name, "against_voucher_type", "");
				frappe.throw(__("Against Voucher Type must be one of Sales Order, Sales Invoice or Journal Voucher"))
			}

		if(frm.doc.party_type=="Supplier"
			&& !in_list(["Purchase Order", "Purchase Invoice", "Journal Voucher"], row.against_voucher_type)) {
				frappe.model.set_value(row.doctype, row.name, "against_voucher_type", "");
				frappe.throw(__("Against Voucher Type must be one of Purchase Order, Purchase Invoice or Journal Voucher"))
			}

	});
}

// validate against_voucher_type
frappe.ui.form.on("Payment Tool Detail", "against_voucher_no", function(frm, cdt, cdn) {
	var row = locals[cdt][cdn];
	frappe.call({
		method: 'erpnext.accounts.doctype.payment_tool.payment_tool.get_against_voucher_amount',
		args: {
			"against_voucher_type": row.against_voucher_type,
			"against_voucher_no": row.against_voucher_no
		},
		callback: function(r) {
			if(!r.exc) {
				$.each(r.message, function(k, v) {
					frappe.model.set_value(cdt, cdn, k, v);
				});
			}
		}
	});
});

// Set total payment amount
frappe.ui.form.on("Payment Tool Detail", "payment_amount", function(frm) {
	erpnext.payment_tool.set_total_payment_amount(frm);
});

frappe.ui.form.on("Payment Tool Detail", "payment_tool_details_remove", function(frm) {
	erpnext.payment_tool.set_total_payment_amount(frm);
});

erpnext.payment_tool.set_total_payment_amount = function(frm) {
	var total_amount = 0.00;
	$.each(frm.doc.payment_tool_details || [], function(i, row) {
		if (row.payment_amount && (row.payment_amount <= row.outstanding_amount)) {
			total_amount = total_amount + row.payment_amount;
		} else {
			if(row.payment_amount < 0)
				msgprint(__("Row {0}: Payment amount can not be negative", [row.idx]));
			else if(row.payment_amount >= row.outstanding_amount)
				msgprint(__("Row {0}: Payment Amount cannot be greater than Outstanding Amount", [__(row.idx)]));

			frappe.model.set_value(row.doctype, row.name, "payment_amount", 0.0);
		}
	});
	frm.set_value("total_payment_amount", total_amount);
}

// Make Journal voucher
frappe.ui.form.on("Payment Tool", "make_journal_voucher", function(frm) {
	erpnext.payment_tool.check_mandatory_to_fetch(frm.doc);

	return  frappe.call({
		method: 'make_journal_voucher',
		doc: frm.doc,
		callback: function(r) {
			frm.fields_dict.make_journal_voucher.$input.addClass("btn-primary");
			var doclist = frappe.model.sync(r.message);
			frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
		}
	});
});

erpnext.payment_tool.check_mandatory_to_fetch = function(doc) {
	var check_fields = [
		['Company', doc.company],
		['Party Type', doc.party_type],
		['Received Or Paid', doc.received_or_paid],
		['Customer / Supplier', doc.party_type == "Customer" ? doc.customer : doc.supplier]
	];

	$.each(check_fields, function(i, v) {
		if(!v[1]) frappe.throw(__("Please select {0} first", [v[0]]));
	});
}
<commit_msg>temporary fix in payment tool<commit_after>// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// For license information, please see license.txt

frappe.provide("erpnext.payment_tool");

// Help content
frappe.ui.form.on("Payment Tool", "onload", function(frm) {
	frm.set_value("make_jv_help", ' '
		+ __("Note: If payment is not made against any reference, make Journal Voucher manually."));

	frm.set_query("payment_account", function() {
		return {
			filters: [
				['Account', 'account_type', 'in', 'Bank, Cash'],
				['Account', 'group_or_ledger', '=', 'Ledger'],
				['Account', 'company', '=', frm.doc.company]
			]
		}
	});

	frm.set_query("against_voucher_type", "payment_tool_details", function() {
		return {
			filters: {"name": ["in", ["Sales Invoice", "Purchase Invoice", "Journal Voucher", "Sales Order", "Purchase Order"]]}
		};
	});
});

frappe.ui.form.on("Payment Tool", "refresh", function(frm) {
	frappe.ui.form.trigger("Payment Tool", "party_type");
});

frappe.ui.form.on("Payment Tool", "party_type", function(frm) {
	frm.toggle_reqd("customer", frm.doc.party_type == "Customer");
	frm.toggle_reqd("supplier", frm.doc.party_type == "Supplier");
});

frappe.ui.form.on("Payment Tool", "company", function(frm) {
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

frappe.ui.form.on("Payment Tool", "received_or_paid", function(frm) {
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

// Set party account name
frappe.ui.form.on("Payment Tool", "customer", function(frm) {
	erpnext.payment_tool.set_party_account(frm);
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

frappe.ui.form.on("Payment Tool", "supplier", function(frm) {
	erpnext.payment_tool.set_party_account(frm);
	erpnext.payment_tool.check_mandatory_to_set_button(frm);
});

erpnext.payment_tool.check_mandatory_to_set_button = function(frm) {
	if (frm.doc.company && frm.doc.party_type && frm.doc.received_or_paid && (frm.doc.customer || frm.doc.supplier)) {
		frm.fields_dict.get_outstanding_vouchers.$input.addClass("btn-primary");
	}
}

//Set Button color
erpnext.payment_tool.set_party_account = function(frm) {
	if(frm.doc.party_type == "Customer") {
		var party_name = frm.doc.customer;
	} else {
		var party_name = frm.doc.supplier;
	}
	return  frappe.call({
		method: 'erpnext.accounts.doctype.payment_tool.payment_tool.get_party_account',
		args: {
			party_type: frm.doc.party_type,
			party_name: party_name
		},
		callback: function(r, rt) {
			if(!r.exc) {
				frm.set_value("party_account", r.message);
			}
		}
	});
}

// Get outstanding vouchers
frappe.ui.form.on("Payment Tool", "get_outstanding_vouchers", function(frm) {
	erpnext.payment_tool.check_mandatory_to_fetch(frm.doc);

	frm.set_value("payment_tool_details", []);

	return  frappe.call({
		method: 'erpnext.accounts.doctype.payment_tool.payment_tool.get_outstanding_vouchers',
		args: {
			args: {
				"company": frm.doc.company,
				"party_type": frm.doc.party_type,
				"received_or_paid": frm.doc.received_or_paid,
				"party_name": frm.doc.party_type == "Customer" ? frm.doc.customer : frm.doc.supplier,
				"party_account": frm.doc.party_account
			}
		},
		callback: function(r, rt) {
			if(r.message) {
				frm.fields_dict.get_outstanding_vouchers.$input.removeClass("btn-primary");
				frm.fields_dict.make_journal_voucher.$input.addClass("btn-primary");

				frappe.model.clear_table(frm.doc, "payment_tool_details");
				$.each(r.message, function(i, d) {
					var invoice_detail = frappe.model.add_child(frm.doc, "Payment Tool Detail", "payment_tool_details");
					invoice_detail.against_voucher_type = d.voucher_type;
					invoice_detail.against_voucher_no = d.voucher_no;
					invoice_detail.total_amount = d.invoice_amount;
					invoice_detail.outstanding_amount = d.outstanding_amount;
				});
			}
			refresh_field("payment_tool_details");
			erpnext.payment_tool.set_total_payment_amount(frm);
		}
	});
});

// validate against_voucher_type
frappe.ui.form.on("Payment Tool Detail", "against_voucher_type", function(frm) {
	erpnext.payment_tool.validate_against_voucher(frm);
});

erpnext.payment_tool.validate_against_voucher = function(frm) {
	$.each(frm.doc.payment_tool_details || [], function(i, row) {
		if(frm.doc.party_type=="Customer"
			&& !in_list(["Sales Order", "Sales Invoice", "Journal Voucher"], row.against_voucher_type)) {
				frappe.model.set_value(row.doctype, row.name, "against_voucher_type", "");
				frappe.throw(__("Against Voucher Type must be one of Sales Order, Sales Invoice or Journal Voucher"))
			}

		if(frm.doc.party_type=="Supplier"
			&& !in_list(["Purchase Order", "Purchase Invoice", "Journal Voucher"], row.against_voucher_type)) {
				frappe.model.set_value(row.doctype, row.name, "against_voucher_type", "");
				frappe.throw(__("Against Voucher Type must be one of Purchase Order, Purchase Invoice or Journal Voucher"))
			}

	});
}

// validate against_voucher_type
frappe.ui.form.on("Payment Tool Detail", "against_voucher_no", function(frm, cdt, cdn) {
	var row = locals[cdt][cdn];
	frappe.call({
		method: 'erpnext.accounts.doctype.payment_tool.payment_tool.get_against_voucher_amount',
		args: {
			"against_voucher_type": row.against_voucher_type,
			"against_voucher_no": row.against_voucher_no
		},
		callback: function(r) {
			if(!r.exc) {
				$.each(r.message, function(k, v) {
					frappe.model.set_value(cdt, cdn, k, v);
				});
			}
		}
	});
});

// Set total payment amount
frappe.ui.form.on("Payment Tool Detail", "payment_amount", function(frm) {
	erpnext.payment_tool.set_total_payment_amount(frm);
});

frappe.ui.form.on("Payment Tool Detail", "payment_tool_details_remove", function(frm) {
	erpnext.payment_tool.set_total_payment_amount(frm);
});

erpnext.payment_tool.set_total_payment_amount = function(frm) {
	var total_amount = 0.00;
	$.each(frm.doc.payment_tool_details || [], function(i, row) {
		if (row.payment_amount && (row.payment_amount <= row.outstanding_amount)) {
			total_amount = total_amount + row.payment_amount;
		} else {
			if(row.payment_amount < 0)
				msgprint(__("Row {0}: Payment amount can not be negative", [row.idx]));
			else if(row.payment_amount >= row.outstanding_amount)
				msgprint(__("Row {0}: Payment Amount cannot be greater than Outstanding Amount", [__(row.idx)]));

			frappe.model.set_value(row.doctype, row.name, "payment_amount", 0.0);
		}
	});
	frm.set_value("total_payment_amount", total_amount);
}

// Make Journal voucher
frappe.ui.form.on("Payment Tool", "make_journal_voucher", function(frm) {
	erpnext.payment_tool.check_mandatory_to_fetch(frm.doc);

	return  frappe.call({
		method: 'make_journal_voucher',
		doc: frm.doc,
		callback: function(r) {
			frm.fields_dict.make_journal_voucher.$input.addClass("btn-primary");
			var doclist = frappe.model.sync(r.message);
			frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
		}
	});
});

erpnext.payment_tool.check_mandatory_to_fetch = function(doc) {
	var check_fields = [
		['Company', doc.company],
		['Party Type', doc.party_type],
		['Received Or Paid', doc.received_or_paid],
		['Customer / Supplier', doc.party_type == "Customer" ? doc.customer : doc.supplier]
	];

	$.each(check_fields, function(i, v) {
		if(!v[1]) frappe.throw(__("Please select {0} first", [v[0]]));
	});
}
<|endoftext|>


<commit_before>(function (){

    /**
       the following inheritance method is copied from
       http://killdream.github.com/blog/2011/10/understanding-javascript-oop
     */
    // Aliases for the rather verbose methods on ES5
    var descriptor  = Object.getOwnPropertyDescriptor
    , properties  = Object.getOwnPropertyNames
    , define_prop = Object.defineProperty

    // (target:Object, source:Object) → Object
    // Copies properties from `source' to `target'
    function extend(target, source) {
        properties(source).forEach(function(key) {
            define_prop(target, key, descriptor(source, key))
        });

        return target;
    }

    var rst = function (config){

        this.json = config.json;

        this.container = {};

        this.adj = [];

        this.nodes = {};

        this.canvas = {};

        this.config = {
            siblingOffet : 200,
            subTreeOffset : 100,
            nodeWith : 60,
            container : config.container,
            wrapper :  config.wrapper || "wrapper"
        };
    }

    rst.buildAdjazenzArray = function()
        {
            function buildAdjazenzArrayHelper(json, adj) {
                var children = json.children;
                if (children && children.length > 0)
                {
                    for (var child in children)
                    {
                        adj.push({
                            from : json,
                            to : children[child]
                        });
                        buildAdjazenzArrayHelper(children[child], adj);
                    }
                }};
            buildAdjazenzArrayHelper(this.json, this.adj);
        },

    rst.layoutTree = function (f)
    {
        var lastChild;

        function layoutTreeHelper(json, height, config, nodes, f)
        {

            //build hashmap by the way, ugly
            nodes[json.id] = json;

            if (json.children && json.children.length > 0)
            {
                for(var item in json.children)
                {
                    layoutTreeHelper(json.children[item], height +
                                     config.subTreeOffset, config, nodes);
                }

                //center parent
                var left = json.children[0].pos.x,
                right = json.children[json.children.length -1].pos.x,
                pos = {
                    x : Math.floor((left + right) / 2),
                    y : height
                }
                json.pos = pos;

            }  else {
                // calc posititions of the segments
                json.pos = {x : 0, y : height};
                if (lastChild != undefined)
                {
                    json.pos.x += lastChild.pos.x +
                        config.siblingOffet;
                }
                lastChild = json;
            }
        }

        layoutTreeHelper(this.json, 0, this.config, this.nodes, f);
    };

    rst.getDepth = function(json)
    {
        function getDepthHelper(json) {

            if (json.children == undefined || json.children.length === 0)
                return 0;
            else
                return 1 + (json.children.map(getDepthHelper)).reduce(function(x, y){
                    if (x > y)
                        return x;
                    else
                        return y;
                });
        }

        return getDepthHelper(json);
    };

    rst.getMostRightNode = function(json)
    {
        if (json.children == undefined || json.children.length == 0)
        {
            return json;
        }
        else
        {
            return this.getMostRightNode(json.children[json.children.length - 1]);
        }
    };

    rst.getMostLeftNode = function getMostLeftNode(json)
    {
        if (json.children == undefined || json.children.length == 0)
        {
            return json;
        }
        else
        {
            return this.getMostLeftNode(json.children[0]);
        }
    };

    rst.getDim = function(json)
    {
        var right = this.getMostRightNode(json).pos.x + this.config.siblingOffet;
        var depth = this.getDepth(json) * this.config.subTreeOffset + this.config.subTreeOffset;
        return {x : right, y : depth};
    };

    rst.initWrapper = function()
    {
        var container = document.getElementById(this.config.container);

        if (!container)
        {
            container = document.createElement("div");
            container.setAttribute("id", this.config.container);
            document.getElementById(this.config.wrapper).appendChild(container);
        }

        dim = this.getDim(this.json);
        container.style.position = "relative";
        container.style.width = dim.x + "px";
        container.style.height = dim.y + "px";
        container.style.background = "#DCE5EE";
        this.container = container;
    };

    rst.plotNodes = function()
    {

        function plotNodesHelper(json, conf, container) {
            var elem = document.createElement("div");

            container.appendChild(elem);
            //set content
            if (json.data.sentence_left != undefined && json.data.sentence_right !=  undefined)
                elem.innerHTML = json.data.sentence_left + " - " + json.data.sentence_right + "
";

            elem.innerHTML += (json.data.sentence != undefined) ? json.data.sentence : "";

            elem.style.position = "absolute";
            elem.style.top = json.pos.y + "px";
            elem.style.left = json.pos.x + "px";
            elem.style.textAlign = "center";
            elem.style.width = conf.nodeWith;

            if (json.children != undefined)
            {
                for (var item in json.children)
                {
                    plotNodesHelper(json.children[item], conf, container);
                }
            }
        }

        plotNodesHelper(this.json, this.config, this.container);
    };

    rst.plotEdges = function(f)
    {
        this.buildAdjazenzArray(this.json);
        var adj = this.adj,
        nodes = this.nodes;

        for (var item in adj) {

            //this.drawLine(canvas, json, children[item]);
            var from = adj[item].from,
            to = adj[item].to;

            // check if this is not member of the invisible relations.
            if (!from.data.invisibleRelations[to.id])
            {
                this.drawRSTRelation(from, to);
            }

            if (to.data.edges.length > 0)
            {
                var edges = to.data.edges;
                for (var i = 0; i < edges.length; i++)
                {
                    var from = edges[i].from,
                    to = edges[i].to;
                    this.drawBezierCurve(nodes[from], nodes[to]);
                    this.plotEdgeLabel(nodes[from], nodes[to],
                                       edges[i].annotation);
                }
            }
        }
    };

    rst.getTopCenter = function(node)
    {
        return (((2* node.pos.x + this.config.nodeWith) / 2));
    };

    rst.initCanvas = function()
    {
        var wrapperElem = this.container;
        this.canvas = document.createElement('canvas');
        wrapperElem.appendChild(this.canvas);
        this.canvas.style.width = wrapperElem.style.width + "px";
        this.canvas.style.height = wrapperElem.style.height + "px";
        this.canvas.setAttribute("width", wrapperElem.style.width);
        this.canvas.setAttribute("height", wrapperElem.style.height);
        this.canvas.style.position = "relative";
        this.canvas.style.background = "#DCE5EE";
    };

    rst.drawLine = function(source, target)
    {
        var ctx = this.canvas.getContext("2d"),
        fromPosX = this.getTopCenter(source),
        toPosX = this.getTopCenter(target);

        ctx.beginPath();
        ctx.fillStyle = "#000000";
        ctx.lineWidth = '1';
        ctx.moveTo(fromPosX, source.pos.y);
        ctx.lineTo(toPosX, target.pos.y);
        ctx.stroke();
    };

    rst.drawRSTRelation = function(source, target)
    {
        var ctx = this.canvas.getContext("2d"),
        mostLeftChild = this.getMostLeftNode(source).pos.x,
        mostRightChild = this.getMostRightNode(source).pos.x + this.config.nodeWith,
        targetCenterX = this.getTopCenter(target);

        // draw horizontal line
        ctx.beginPath();
        ctx.fillStyle = "#000000";
        ctx.lineWidth = '1';
        ctx.moveTo(mostLeftChild, source.pos.y);
        ctx.lineTo(mostRightChild, source.pos.y);
        ctx.stroke();

        // draw vertical line
        ctx.beginPath();
        ctx.fillStyle = "#000000";
        ctx.lineWidth = '1';
        ctx.moveTo(targetCenterX, source.pos.y);
        ctx.lineTo(targetCenterX, target.pos.y);
        ctx.stroke();
    };

    rst.drawBezierCurve = function(source, target)
    {
        var from = source.pos,
        to = target.pos,
        fromX = this.getTopCenter(source),
        toX = this.getTopCenter(target),
        dim = 15,
        ctx = this.canvas.getContext("2d"),
        controllPoint = {};

        if (fromX != toX)
        {
            controllPoint.x = (fromX + toX) / 2;
            controllPoint.y = from.y - 2 * dim;
        } else {
            controllPoint.x = fromX + 2 * dim;
            controllPoint.y = (from.y + to.y) / 2;
        }

        //draw line
        ctx.beginPath();
        ctx.moveTo(fromX, from.y);
        ctx.quadraticCurveTo(controllPoint.x, controllPoint.y, toX, to.y, toX, to.y);
        ctx.stroke();

        var headlen = 10;   // length of head in pixels
        var angle = Math.atan2(to.y - from.y, to.x - fromX);
        ctx.lineTo(toX - headlen*Math.cos(angle - Math.PI/6), to.y - headlen*Math.sin(angle - Math.PI/6));
        ctx.moveTo(toX, to.y);
        ctx.lineTo(toX - headlen*Math.cos(angle + Math.PI/6), to.y - headlen*Math.sin(angle + Math.PI/6));
        ctx.stroke();
    };

    rst.plotEdgeLabel = function(source, target, annotation)
    {
        var fromX = this.getTopCenter(source),
        toX = this.getTopCenter(target),
        label = document.createElement("label");

        this.container.appendChild(label);
        label.style.position = "absolute";
        label.innerHTML = annotation;

        labelPos = {
            x : (fromX + toX) / 2 - label.offsetWidth / 2,
            y : source.pos.y - 35
        };

        label.style.top = labelPos.y + "px";
        label.style.left = labelPos.x + "px";
    };

    window.$viz = function (config)
    {
        var viz = new rst(config);
        extend(viz, rst);
        console.log(Object.keys(viz));
        return viz;
    }
}());<commit_msg>Solve some problemes with setting dimensions of html elements.<commit_after>(function (){

    /**
       the following inheritance method is copied from
       http://killdream.github.com/blog/2011/10/understanding-javascript-oop
     */
    // Aliases for the rather verbose methods on ES5
    var descriptor  = Object.getOwnPropertyDescriptor
    , properties  = Object.getOwnPropertyNames
    , define_prop = Object.defineProperty

    // (target:Object, source:Object) → Object
    // Copies properties from `source' to `target'
    function extend(target, source) {
        properties(source).forEach(function(key) {
            define_prop(target, key, descriptor(source, key))
        });

        return target;
    }

    var rst = function (config){

        this.json = config.json;

        this.container = {};

        this.adj = [];

        this.nodes = {};

        this.canvas = {};

        this.config = {
            siblingOffet : config.subTreeOffset || 200 ,
            subTreeOffset : config.subTreeOffset || 100,
            nodeWith : config.nodeWith  || 60 ,
            labelSize : config.labelSize || 11,
            container : config.container || "container",
            background : config.background || "#FFFFFF",
            wrapper :  config.wrapper || "wrapper"
        };
    }

    rst.buildAdjazenzArray = function()
    {
      function buildAdjazenzArrayHelper(json, adj) {
          var children = json.children;
          if (children && children.length > 0)
          {
              for (var child in children)
              {
                  adj.push({
                      from : json,
                      to : children[child]
                  });
                  buildAdjazenzArrayHelper(children[child], adj);
              }
          }};
      buildAdjazenzArrayHelper(this.json, this.adj);
    },

    rst.layoutTree = function (f)
    {
        var lastChild;

        function layoutTreeHelper(json, height, config, nodes, f)
        {

            //build hashmap by the way, ugly
            nodes[json.id] = json;

            if (json.children && json.children.length > 0)
            {
                for(var item in json.children)
                {
                    layoutTreeHelper(json.children[item], height +
                                     config.subTreeOffset, config, nodes);
                }

                //center parent
                var left = json.children[0].pos.x,
                right = json.children[json.children.length -1].pos.x,
                pos = {
                    x : Math.floor((left + right) / 2),
                    y : height
                }
                json.pos = pos;

            }  else {
                // calc posititions of the segments
                json.pos = {x : 0, y : height};
                if (lastChild != undefined)
                {
                    json.pos.x += lastChild.pos.x +
                        config.siblingOffet;
                }
                lastChild = json;
            }
        }

        layoutTreeHelper(this.json, 0, this.config, this.nodes, f);
    };

    rst.getDepth = function(json)
    {
        function getDepthHelper(json) {

            if (json.children == undefined || json.children.length === 0)
                return 0;
            else
                return 1 + (json.children.map(getDepthHelper)).reduce(function(x, y){
                    if (x > y)
                        return x;
                    else
                        return y;
                });
        }

        return getDepthHelper(json);
    };

    rst.getMostRightNode = function(json)
    {
        if (json.children == undefined || json.children.length == 0)
        {
            return json;
        }
        else
        {
            return this.getMostRightNode(json.children[json.children.length - 1]);
        }
    };

    rst.getMostLeftNode = function getMostLeftNode(json)
    {
        if (json.children == undefined || json.children.length == 0)
        {
            return json;
        }
        else
        {
            return this.getMostLeftNode(json.children[0]);
        }
    };

    rst.getDim = function(json)
    {
        var right = this.getMostRightNode(json).pos.x + this.config.siblingOffet;
        var depth = this.getDepth(json) * this.config.subTreeOffset + this.config.subTreeOffset;
        return {x : right, y : depth};
    };

    rst.initWrapper = function()
    {
        var container = document.getElementById(this.config.container);

        if (!container)
        {
            container = document.createElement("div");
            container.setAttribute("id", this.config.container);
            document.getElementById(this.config.wrapper).appendChild(container);
        }

        dim = this.getDim(this.json);
        container.style.position = "relative";
        container.style.width = dim.x + "px";
        container.style.height = dim.y + "px";
        container.style.background = this.config.background;
        this.container = container;
    };

    rst.plotNodes = function()
    {

        function plotNodesHelper(json, conf, container) {
            var elem = document.createElement("div");

            container.appendChild(elem);
            //set content
            if (json.data.sentence_left != undefined && json.data.sentence_right !=  undefined)
                elem.innerHTML = json.data.sentence_left + " - " + json.data.sentence_right + "
";

            elem.innerHTML += (json.data.sentence != undefined) ? json.data.sentence : "";

            elem.style.position = "absolute";
            elem.style.top = json.pos.y + "px";
            elem.style.left = json.pos.x + "px";
            elem.style.textAlign = "center";
            elem.style.fontSize = conf.labelSize + "px";
            elem.style.width = conf.nodeWith + "px";

            if (json.children != undefined)
            {
                for (var item in json.children)
                {
                    plotNodesHelper(json.children[item], conf, container);
                }
            }
        }

        plotNodesHelper(this.json, this.config, this.container);
    };

    rst.plotEdges = function(f)
    {
        this.buildAdjazenzArray(this.json);
        var adj = this.adj,
        nodes = this.nodes;

        for (var item in adj) {

            //this.drawLine(canvas, json, children[item]);
            var from = adj[item].from,
            to = adj[item].to;

            // check if this is not member of the invisible relations.
            if (!from.data.invisibleRelations[to.id])
            {
                this.drawRSTRelation(from, to);
            }

            if (to.data.edges.length > 0)
            {
                var edges = to.data.edges;
                for (var i = 0; i < edges.length; i++)
                {
                    var from = edges[i].from,
                    to = edges[i].to;
                    this.drawBezierCurve(nodes[from], nodes[to]);
                    this.plotEdgeLabel(nodes[from], nodes[to],
                                       edges[i].annotation);
                }
            }
        }
    };

    rst.getTopCenter = function(node)
    {
        return (((2* node.pos.x + this.config.nodeWith) / 2));
    };

    rst.initCanvas = function()
    {
        var wrapperElem = this.container;
        this.canvas = document.createElement('canvas');
        wrapperElem.appendChild(this.canvas);
        this.canvas.style.width = wrapperElem.style.width + "px";
        this.canvas.style.height = wrapperElem.style.height + "px";
        this.canvas.setAttribute("width", wrapperElem.style.width);
        this.canvas.setAttribute("height", wrapperElem.style.height);
        this.canvas.style.position = "relative";
    };

    rst.drawLine = function(source, target)
    {
        var ctx = this.canvas.getContext("2d"),
        fromPosX = this.getTopCenter(source),
        toPosX = this.getTopCenter(target);

        ctx.beginPath();
        ctx.fillStyle = "#000000";
        ctx.lineWidth = '1';
        ctx.moveTo(fromPosX, source.pos.y);
        ctx.lineTo(toPosX, target.pos.y);
        ctx.stroke();
    };

    rst.drawRSTRelation = function(source, target)
    {
        var ctx = this.canvas.getContext("2d"),
        mostLeftChild = this.getMostLeftNode(source).pos.x,
        mostRightChild = this.getMostRightNode(source).pos.x + this.config.nodeWith,
        targetCenterX = this.getTopCenter(target);

        // draw horizontal line
        ctx.beginPath();
        ctx.fillStyle = "#000000";
        ctx.lineWidth = '1';
        ctx.moveTo(mostLeftChild, source.pos.y);
        ctx.lineTo(mostRightChild, source.pos.y);
        ctx.stroke();

        // draw vertical line
        ctx.beginPath();
        ctx.fillStyle = "#000000";
        ctx.lineWidth = '1';
        ctx.moveTo(targetCenterX, source.pos.y);
        ctx.lineTo(targetCenterX, target.pos.y);
        ctx.stroke();
    };

    rst.drawBezierCurve = function(source, target)
    {
        var from = source.pos,
        to = target.pos,
        fromX = this.getTopCenter(source),
        toX = this.getTopCenter(target),
        dim = 15,
        ctx = this.canvas.getContext("2d"),
        controllPoint = {};

        if (fromX != toX)
        {
            controllPoint.x = (fromX + toX) / 2;
            controllPoint.y = from.y - 2 * dim;
        } else {
            controllPoint.x = fromX + 2 * dim;
            controllPoint.y = (from.y + to.y) / 2;
        }

        //draw line
        ctx.beginPath();
        ctx.moveTo(fromX, from.y);
        ctx.quadraticCurveTo(controllPoint.x, controllPoint.y, toX, to.y, toX, to.y);
        ctx.stroke();

        var headlen = 10;   // length of head in pixels
        var angle = Math.atan2(to.y - from.y, to.x - fromX);
        ctx.lineTo(toX - headlen*Math.cos(angle - Math.PI/6), to.y - headlen*Math.sin(angle - Math.PI/6));
        ctx.moveTo(toX, to.y);
        ctx.lineTo(toX - headlen*Math.cos(angle + Math.PI/6), to.y - headlen*Math.sin(angle + Math.PI/6));
        ctx.stroke();
    };

    rst.plotEdgeLabel = function(source, target, annotation)
    {
        var fromX = this.getTopCenter(source),
        toX = this.getTopCenter(target),
        label = document.createElement("label");

        this.container.appendChild(label);
        label.style.position = "absolute";
        label.innerHTML = annotation;

        labelPos = {
            x : (fromX + toX) / 2 - label.offsetWidth / 2,
            y : source.pos.y - 35
        };

        label.style.top = labelPos.y + "px";
        label.style.left = labelPos.x + "px";
    };

    window.$viz = function (config)
    {
        var viz = new rst(config);
        extend(viz, rst);
        return viz;
    }
}());<|endoftext|>


<commit_before>// shim for using process in browser
var process = module.exports = {};

// cached from whatever global is present so that test runners that stub it
// don't break things.  But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals.  It's inside a
// function because try/catches deoptimize in certain engines.

var cachedSetTimeout;
var cachedClearTimeout;

function defaultSetTimout() {
    throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
    throw new Error('clearTimeout has not been defined');
}
(function () {
    try {
        if (typeof setTimeout === 'function') {
            cachedSetTimeout = setTimeout;
        } else {
            cachedSetTimeout = defaultSetTimout;
        }
    } catch (e) {
        cachedSetTimeout = defaultSetTimout;
    }
    try {
        if (typeof clearTimeout === 'function') {
            cachedClearTimeout = clearTimeout;
        } else {
            cachedClearTimeout = defaultClearTimeout;
        }
    } catch (e) {
        cachedClearTimeout = defaultClearTimeout;
    }
} ())
function runTimeout(fun) {
    if (cachedSetTimeout === setTimeout) {
        //normal enviroments in sane situations
        return setTimeout(fun, 0);
    }
    // if setTimeout wasn't available but was latter defined
    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
        cachedSetTimeout = setTimeout;
        return setTimeout(fun, 0);
    }
    try {
        // when when somebody has screwed with setTimeout but no I.E. maddness
        return cachedSetTimeout(fun, 0);
    } catch(e){
        try {
            // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
            return cachedSetTimeout.call(null, fun, 0);
        } catch(e){
            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
            return cachedSetTimeout.call(this, fun, 0);
        }
    }

}
function runClearTimeout(marker) {
    if (cachedClearTimeout === clearTimeout) {
        //normal enviroments in sane situations
        return clearTimeout(marker);
    }
    // if clearTimeout wasn't available but was latter defined
    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
        cachedClearTimeout = clearTimeout;
        return clearTimeout(marker);
    }
    try {
        // when when somebody has screwed with setTimeout but no I.E. maddness
        return cachedClearTimeout(marker);
    } catch (e){
        try {
            // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
            return cachedClearTimeout.call(null, marker);
        } catch (e){
            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
            // Some versions of I.E. have different rules for clearTimeout vs setTimeout
            return cachedClearTimeout.call(this, marker);
        }
    }

}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;

function cleanUpNextTick() {
    if (!draining || !currentQueue) {
        return;
    }
    draining = false;
    if (currentQueue.length) {
        queue = currentQueue.concat(queue);
    } else {
        queueIndex = -1;
    }
    if (queue.length) {
        drainQueue();
    }
}

function drainQueue() {
    if (draining) {
        return;
    }
    var timeout = runTimeout(cleanUpNextTick);
    draining = true;

    var len = queue.length;
    while(len) {
        currentQueue = queue;
        queue = [];
        while (++queueIndex < len) {
            if (currentQueue) {
                currentQueue[queueIndex].run();
            }
        }
        queueIndex = -1;
        len = queue.length;
    }
    currentQueue = null;
    draining = false;
    runClearTimeout(timeout);
}

process.nextTick = function (fun) {
    var args = new Array(arguments.length - 1);
    if (arguments.length > 1) {
        for (var i = 1; i < arguments.length; i++) {
            args[i - 1] = arguments[i];
        }
    }
    queue.push(new Item(fun, args));
    if (queue.length === 1 && !draining) {
        runTimeout(drainQueue);
    }
};

// v8 likes predictible objects
function Item(fun, array) {
    this.fun = fun;
    this.array = array;
}
Item.prototype.run = function () {
    this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};

function noop() {}

process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;

process.binding = function (name) {
    throw new Error('process.binding is not supported');
};

process.cwd = function () { return '/' };
process.chdir = function (dir) {
    throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
<commit_msg>new event methods (#78)<commit_after>// shim for using process in browser
var process = module.exports = {};

// cached from whatever global is present so that test runners that stub it
// don't break things.  But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals.  It's inside a
// function because try/catches deoptimize in certain engines.

var cachedSetTimeout;
var cachedClearTimeout;

function defaultSetTimout() {
    throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
    throw new Error('clearTimeout has not been defined');
}
(function () {
    try {
        if (typeof setTimeout === 'function') {
            cachedSetTimeout = setTimeout;
        } else {
            cachedSetTimeout = defaultSetTimout;
        }
    } catch (e) {
        cachedSetTimeout = defaultSetTimout;
    }
    try {
        if (typeof clearTimeout === 'function') {
            cachedClearTimeout = clearTimeout;
        } else {
            cachedClearTimeout = defaultClearTimeout;
        }
    } catch (e) {
        cachedClearTimeout = defaultClearTimeout;
    }
} ())
function runTimeout(fun) {
    if (cachedSetTimeout === setTimeout) {
        //normal enviroments in sane situations
        return setTimeout(fun, 0);
    }
    // if setTimeout wasn't available but was latter defined
    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
        cachedSetTimeout = setTimeout;
        return setTimeout(fun, 0);
    }
    try {
        // when when somebody has screwed with setTimeout but no I.E. maddness
        return cachedSetTimeout(fun, 0);
    } catch(e){
        try {
            // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
            return cachedSetTimeout.call(null, fun, 0);
        } catch(e){
            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
            return cachedSetTimeout.call(this, fun, 0);
        }
    }

}
function runClearTimeout(marker) {
    if (cachedClearTimeout === clearTimeout) {
        //normal enviroments in sane situations
        return clearTimeout(marker);
    }
    // if clearTimeout wasn't available but was latter defined
    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
        cachedClearTimeout = clearTimeout;
        return clearTimeout(marker);
    }
    try {
        // when when somebody has screwed with setTimeout but no I.E. maddness
        return cachedClearTimeout(marker);
    } catch (e){
        try {
            // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
            return cachedClearTimeout.call(null, marker);
        } catch (e){
            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
            // Some versions of I.E. have different rules for clearTimeout vs setTimeout
            return cachedClearTimeout.call(this, marker);
        }
    }

}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;

function cleanUpNextTick() {
    if (!draining || !currentQueue) {
        return;
    }
    draining = false;
    if (currentQueue.length) {
        queue = currentQueue.concat(queue);
    } else {
        queueIndex = -1;
    }
    if (queue.length) {
        drainQueue();
    }
}

function drainQueue() {
    if (draining) {
        return;
    }
    var timeout = runTimeout(cleanUpNextTick);
    draining = true;

    var len = queue.length;
    while(len) {
        currentQueue = queue;
        queue = [];
        while (++queueIndex < len) {
            if (currentQueue) {
                currentQueue[queueIndex].run();
            }
        }
        queueIndex = -1;
        len = queue.length;
    }
    currentQueue = null;
    draining = false;
    runClearTimeout(timeout);
}

process.nextTick = function (fun) {
    var args = new Array(arguments.length - 1);
    if (arguments.length > 1) {
        for (var i = 1; i < arguments.length; i++) {
            args[i - 1] = arguments[i];
        }
    }
    queue.push(new Item(fun, args));
    if (queue.length === 1 && !draining) {
        runTimeout(drainQueue);
    }
};

// v8 likes predictible objects
function Item(fun, array) {
    this.fun = fun;
    this.array = array;
}
Item.prototype.run = function () {
    this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};

function noop() {}

process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.prependListener = noop;
process.prependOnceListener = noop;

process.listeners = function (name) { return [] }

process.binding = function (name) {
    throw new Error('process.binding is not supported');
};

process.cwd = function () { return '/' };
process.chdir = function (dir) {
    throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
<|endoftext|>


<commit_before>export const DEFAULT_SETTINGS = {
    special_subject: 'all'
};
<commit_msg>Added a 'SPECIAL_SUBJECTS' object to the defaults to use it in multiple places<commit_after>export const DEFAULT_SETTINGS = {
    special_subject: 'all'
};

export const SPECIAL_SUBJECTS = {
    bmm: [
        {
            shortname: "im",
            longname: "Interaktive Medien"
        },
        {
            shortname: "av",
            longname: "AV-Medien"
        },
    ]
};
<|endoftext|>


<commit_before>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
    "use strict";
    var ws = require("ws");
    var inspect = require("util").inspect;
    var url = require("url");
    var HttpsProxyAgent = require('https-proxy-agent');

    var serverUpgradeAdded = false;
    function handleServerUpgrade(request, socket, head) {
        const pathname = url.parse(request.url).pathname;
        if (listenerNodes.hasOwnProperty(pathname)) {
            listenerNodes[pathname].server.handleUpgrade(request, socket, head, function done(ws) {
                listenerNodes[pathname].server.emit('connection', ws, request);
            });
        } else {
            // Don't destroy the socket as other listeners may want to handle the
            // event.
        }
    }
    var listenerNodes = {};
    var activeListenerNodes = 0;

    // A node red node that sets up a local websocket server
    function WebSocketListenerNode(n) {
        // Create a RED node
        RED.nodes.createNode(this,n);
        var node = this;

        // Store local copies of the node configuration (as defined in the .html)
        node.path = n.path;
        node.wholemsg = (n.wholemsg === "true");

        node._inputNodes = [];    // collection of nodes that want to receive events
        node._clients = {};
        // match absolute url
        node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
        node.closing = false;
        node.tls = n.tls;

        if (n.hb) {
            var heartbeat = parseInt(n.hb);
            if (heartbeat > 0) {
                node.heartbeat = heartbeat * 1000;
            }
        }

        function startconn() {    // Connect to remote endpoint
            node.tout = null;
            var prox, noprox;
            if (process.env.http_proxy) { prox = process.env.http_proxy; }
            if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
            if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
            if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }

            var noproxy = false;
            if (noprox) {
                for (var i in noprox) {
                    if (node.path.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
                }
            }

            var agent = undefined;
            if (prox && !noproxy) {
                agent = new HttpsProxyAgent(prox);
            }

            var options = {};
            if (agent) {
                options.agent = agent;
            }
            if (node.tls) {
                var tlsNode = RED.nodes.getNode(node.tls);
                if (tlsNode) {
                    tlsNode.addTLSOptions(options);
                }
            }
            var socket = new ws(node.path,options);
            socket.setMaxListeners(0);
            node.server = socket; // keep for closing
            handleConnection(socket);
        }

        function handleConnection(/*socket*/socket) {
            var id = RED.util.generateId();
            socket.nrId = id;
            socket.nrPendingHeartbeat = false;
            if (node.isServer) {
                node._clients[id] = socket;
                node.emit('opened',{count:Object.keys(node._clients).length,id:id});
            } else {
                if (node.heartbeat) {
                    node.heartbeatInterval = setInterval(function() {
                        if (socket.nrPendingHeartbeat) {
                            // No pong received
                            socket.terminate();
                            socket.nrErrorHandler(new Error("timeout"));
                            return;
                        }
                        socket.nrPendingHeartbeat = true;
                        socket.ping();
                    },node.heartbeat);
                }
            }
            socket.on('open',function() {
                if (!node.isServer) {
                    node.emit('opened',{count:'',id:id});
                }
            });
            socket.on('close',function() {
                clearInterval(node.heartbeatInterval);
                if (node.isServer) {
                    delete node._clients[id];
                    node.emit('closed',{count:Object.keys(node._clients).length,id:id});
                } else {
                    node.emit('closed',{count:'',id:id});
                }
                if (!node.closing && !node.isServer) {
                    clearTimeout(node.tout);
                    node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
                }
            });
            socket.on('message',function(data,flags) {
                node.handleEvent(id,socket,'message',data,flags);
            });
            socket.nrErrorHandler = function(err) {
                clearInterval(node.heartbeatInterval);
                node.emit('erro',{err:err,id:id});
                if (!node.closing && !node.isServer) {
                    clearTimeout(node.tout);
                    node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
                }
            }
            socket.on('error',socket.nrErrorHandler);
            socket.on('ping', function() {
                socket.nrPendingHeartbeat = false;
            })
            socket.on('pong', function() {
                socket.nrPendingHeartbeat = false;
            })
        }

        if (node.isServer) {
            activeListenerNodes++;
            if (!serverUpgradeAdded) {
                RED.server.on('upgrade', handleServerUpgrade);
                serverUpgradeAdded = true
            }

            var path = RED.settings.httpNodeRoot || "/";
            path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
            node.fullPath = path;

            if (listenerNodes.hasOwnProperty(path)) {
                node.error(RED._("websocket.errors.duplicate-path",{path: node.path}));
                return;
            }
            listenerNodes[node.fullPath] = node;
            var serverOptions = {
                noServer: true
            }
            if (RED.settings.webSocketNodeVerifyClient) {
                serverOptions.verifyClient = RED.settings.webSocketNodeVerifyClient;
            }
            // Create a WebSocket Server
            node.server = new ws.Server(serverOptions);
            node.server.setMaxListeners(0);
            node.server.on('connection', handleConnection);
            // Not adding server-initiated heartbeats yet
            // node.heartbeatInterval = setInterval(function() {
            //     node.server.clients.forEach(function(ws) {
            //         if (ws.nrPendingHeartbeat) {
            //             // No pong received
            //             ws.terminate();
            //             ws.nrErrorHandler(new Error("timeout"));
            //             return;
            //         }
            //         ws.nrPendingHeartbeat = true;
            //         ws.ping();
            //     });
            // })
        }
        else {
            node.closing = false;
            startconn(); // start outbound connection
        }

        node.on("close", function() {
            if (node.heartbeatInterval) {
                clearInterval(node.heartbeatInterval);
            }
            if (node.isServer) {
                delete listenerNodes[node.fullPath];
                node.server.close();
                node._inputNodes = [];
                activeListenerNodes--;
                // if (activeListenerNodes === 0 && serverUpgradeAdded) {
                //     RED.server.removeListener('upgrade', handleServerUpgrade);
                //     serverUpgradeAdded = false;
                // }
            }
            else {
                node.closing = true;
                node.server.close();
                if (node.tout) {
                    clearTimeout(node.tout);
                    node.tout = null;
                }
            }
        });
    }
    RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
    RED.nodes.registerType("websocket-client",WebSocketListenerNode);

    WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
        this._inputNodes.push(handler);
    }

    WebSocketListenerNode.prototype.removeInputNode = function(/*Node*/handler) {
        this._inputNodes.forEach(function(node, i, inputNodes) {
            if (node === handler) {
                inputNodes.splice(i, 1);
            }
        });
    }

    WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags) {
        var msg;
        if (this.wholemsg) {
            try {
                msg = JSON.parse(data);
                if (typeof msg !== "object" && !Array.isArray(msg) && (msg !== null)) {
                    msg = { payload:msg };
                }
            }
            catch(err) {
                msg = { payload:data };
            }
        } else {
            msg = {
                payload:data
            };
        }
        msg._session = {type:"websocket",id:id};
        for (var i = 0; i < this._inputNodes.length; i++) {
            this._inputNodes[i].send(msg);
        }
    }

    WebSocketListenerNode.prototype.broadcast = function(data) {
        if (this.isServer) {
            for (let client in this._clients) {
                if (this._clients.hasOwnProperty(client)) {
                    try {
                        this._clients[client].send(data);
                    } catch(err) {
                        this.warn(RED._("websocket.errors.send-error")+" "+client+" "+err.toString())
                    }
                }
            }
        }
        else {
            try {
                this.server.send(data);
            } catch(err) {
                this.warn(RED._("websocket.errors.send-error")+" "+err.toString())
            }
        }
    }

    WebSocketListenerNode.prototype.reply = function(id,data) {
        var session = this._clients[id];
        if (session) {
            try {
                session.send(data);
            }
            catch(e) { // swallow any errors
            }
        }
    }

    function WebSocketInNode(n) {
        RED.nodes.createNode(this,n);
        this.server = (n.client)?n.client:n.server;
        var node = this;
        this.serverConfig = RED.nodes.getNode(this.server);
        if (this.serverConfig) {
            this.serverConfig.registerInputNode(this);
            // TODO: nls
            this.serverConfig.on('opened', function(event) {
                node.status({
                    fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
                    event:"connect",
                    _session: {type:"websocket",id:event.id}
                });
            });
            this.serverConfig.on('erro', function(event) {
                node.status({
                    fill:"red",shape:"ring",text:"common.status.error",
                    event:"error",
                    _session: {type:"websocket",id:event.id}
                });
            });
            this.serverConfig.on('closed', function(event) {
                var status;
                if (event.count > 0) {
                    status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
                } else {
                    status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
                }
                status.event = "disconnect";
                status._session = {type:"websocket",id:event.id}
                node.status(status);
            });
        } else {
            this.error(RED._("websocket.errors.missing-conf"));
        }
        this.on('close', function() {
            if (node.serverConfig) {
                node.serverConfig.removeInputNode(node);
            }
            node.status({});
        });
    }
    RED.nodes.registerType("websocket in",WebSocketInNode);

    function WebSocketOutNode(n) {
        RED.nodes.createNode(this,n);
        var node = this;
        this.server = (n.client)?n.client:n.server;
        this.serverConfig = RED.nodes.getNode(this.server);
        if (!this.serverConfig) {
            return this.error(RED._("websocket.errors.missing-conf"));
        }
        else {
            // TODO: nls
            this.serverConfig.on('opened', function(event) {
                node.status({
                    fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
                    event:"connect",
                    _session: {type:"websocket",id:event.id}
                });
            });
            this.serverConfig.on('erro', function(event) {
                node.status({
                    fill:"red",shape:"ring",text:"common.status.error",
                    event:"error",
                    _session: {type:"websocket",id:event.id}
                })
            });
            this.serverConfig.on('closed', function(event) {
                var status;
                if (event.count > 0) {
                    status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
                } else {
                    status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
                }
                status.event = "disconnect";
                status._session = {type:"websocket",id:event.id}
                node.status(status);
            });
        }
        this.on("input", function(msg, nodeSend, nodeDone) {
            var payload;
            if (this.serverConfig.wholemsg) {
                var sess;
                if (msg._session) { sess = JSON.stringify(msg._session); }
                delete msg._session;
                payload = JSON.stringify(msg);
                if (sess) { msg._session = JSON.parse(sess); }
            }
            else if (msg.hasOwnProperty("payload")) {
                if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
                    payload = RED.util.ensureString(msg.payload);
                }
                else {
                    payload = msg.payload;
                }
            }
            if (payload) {
                if (msg._session && msg._session.type == "websocket") {
                    node.serverConfig.reply(msg._session.id,payload);
                } else {
                    node.serverConfig.broadcast(payload,function(error) {
                        if (!!error) {
                            node.warn(RED._("websocket.errors.send-error")+inspect(error));
                        }
                    });
                }
            }
            nodeDone();
        });
        this.on('close', function() {
            node.status({});
        });
    }
    RED.nodes.registerType("websocket out",WebSocketOutNode);
}
<commit_msg>Only setup ws client heartbeat once it is connected Fixes #3264<commit_after>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
    "use strict";
    var ws = require("ws");
    var inspect = require("util").inspect;
    var url = require("url");
    var HttpsProxyAgent = require('https-proxy-agent');

    var serverUpgradeAdded = false;
    function handleServerUpgrade(request, socket, head) {
        const pathname = url.parse(request.url).pathname;
        if (listenerNodes.hasOwnProperty(pathname)) {
            listenerNodes[pathname].server.handleUpgrade(request, socket, head, function done(ws) {
                listenerNodes[pathname].server.emit('connection', ws, request);
            });
        } else {
            // Don't destroy the socket as other listeners may want to handle the
            // event.
        }
    }
    var listenerNodes = {};
    var activeListenerNodes = 0;

    // A node red node that sets up a local websocket server
    function WebSocketListenerNode(n) {
        // Create a RED node
        RED.nodes.createNode(this,n);
        var node = this;

        // Store local copies of the node configuration (as defined in the .html)
        node.path = n.path;
        node.wholemsg = (n.wholemsg === "true");

        node._inputNodes = [];    // collection of nodes that want to receive events
        node._clients = {};
        // match absolute url
        node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
        node.closing = false;
        node.tls = n.tls;

        if (n.hb) {
            var heartbeat = parseInt(n.hb);
            if (heartbeat > 0) {
                node.heartbeat = heartbeat * 1000;
            }
        }

        function startconn() {    // Connect to remote endpoint
            node.tout = null;
            var prox, noprox;
            if (process.env.http_proxy) { prox = process.env.http_proxy; }
            if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
            if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
            if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }

            var noproxy = false;
            if (noprox) {
                for (var i in noprox) {
                    if (node.path.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
                }
            }

            var agent = undefined;
            if (prox && !noproxy) {
                agent = new HttpsProxyAgent(prox);
            }

            var options = {};
            if (agent) {
                options.agent = agent;
            }
            if (node.tls) {
                var tlsNode = RED.nodes.getNode(node.tls);
                if (tlsNode) {
                    tlsNode.addTLSOptions(options);
                }
            }
            var socket = new ws(node.path,options);
            socket.setMaxListeners(0);
            node.server = socket; // keep for closing
            handleConnection(socket);
        }

        function handleConnection(/*socket*/socket) {
            var id = RED.util.generateId();
            socket.nrId = id;
            socket.nrPendingHeartbeat = false;
            if (node.isServer) {
                node._clients[id] = socket;
                node.emit('opened',{count:Object.keys(node._clients).length,id:id});
            }
            socket.on('open',function() {
                if (!node.isServer) {
                    if (node.heartbeat) {
                        clearInterval(node.heartbeatInterval);
                        node.heartbeatInterval = setInterval(function() {
                            if (socket.nrPendingHeartbeat) {
                                // No pong received
                                socket.terminate();
                                socket.nrErrorHandler(new Error("timeout"));
                                return;
                            }
                            socket.nrPendingHeartbeat = true;
                            try {
                                socket.ping();
                            } catch(err) {}
                        },node.heartbeat);
                    }
                    node.emit('opened',{count:'',id:id});
                }
            });
            socket.on('close',function() {
                clearInterval(node.heartbeatInterval);
                if (node.isServer) {
                    delete node._clients[id];
                    node.emit('closed',{count:Object.keys(node._clients).length,id:id});
                } else {
                    node.emit('closed',{count:'',id:id});
                }
                if (!node.closing && !node.isServer) {
                    clearTimeout(node.tout);
                    node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
                }
            });
            socket.on('message',function(data,flags) {
                node.handleEvent(id,socket,'message',data,flags);
            });
            socket.nrErrorHandler = function(err) {
                clearInterval(node.heartbeatInterval);
                node.emit('erro',{err:err,id:id});
                if (!node.closing && !node.isServer) {
                    clearTimeout(node.tout);
                    node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
                }
            }
            socket.on('error',socket.nrErrorHandler);
            socket.on('ping', function() {
                socket.nrPendingHeartbeat = false;
            })
            socket.on('pong', function() {
                socket.nrPendingHeartbeat = false;
            })
        }

        if (node.isServer) {
            activeListenerNodes++;
            if (!serverUpgradeAdded) {
                RED.server.on('upgrade', handleServerUpgrade);
                serverUpgradeAdded = true
            }

            var path = RED.settings.httpNodeRoot || "/";
            path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
            node.fullPath = path;

            if (listenerNodes.hasOwnProperty(path)) {
                node.error(RED._("websocket.errors.duplicate-path",{path: node.path}));
                return;
            }
            listenerNodes[node.fullPath] = node;
            var serverOptions = {
                noServer: true
            }
            if (RED.settings.webSocketNodeVerifyClient) {
                serverOptions.verifyClient = RED.settings.webSocketNodeVerifyClient;
            }
            // Create a WebSocket Server
            node.server = new ws.Server(serverOptions);
            node.server.setMaxListeners(0);
            node.server.on('connection', handleConnection);
            // Not adding server-initiated heartbeats yet
            // node.heartbeatInterval = setInterval(function() {
            //     node.server.clients.forEach(function(ws) {
            //         if (ws.nrPendingHeartbeat) {
            //             // No pong received
            //             ws.terminate();
            //             ws.nrErrorHandler(new Error("timeout"));
            //             return;
            //         }
            //         ws.nrPendingHeartbeat = true;
            //         ws.ping();
            //     });
            // })
        }
        else {
            node.closing = false;
            startconn(); // start outbound connection
        }

        node.on("close", function() {
            if (node.heartbeatInterval) {
                clearInterval(node.heartbeatInterval);
            }
            if (node.isServer) {
                delete listenerNodes[node.fullPath];
                node.server.close();
                node._inputNodes = [];
                activeListenerNodes--;
                // if (activeListenerNodes === 0 && serverUpgradeAdded) {
                //     RED.server.removeListener('upgrade', handleServerUpgrade);
                //     serverUpgradeAdded = false;
                // }
            }
            else {
                node.closing = true;
                node.server.close();
                if (node.tout) {
                    clearTimeout(node.tout);
                    node.tout = null;
                }
            }
        });
    }
    RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
    RED.nodes.registerType("websocket-client",WebSocketListenerNode);

    WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
        this._inputNodes.push(handler);
    }

    WebSocketListenerNode.prototype.removeInputNode = function(/*Node*/handler) {
        this._inputNodes.forEach(function(node, i, inputNodes) {
            if (node === handler) {
                inputNodes.splice(i, 1);
            }
        });
    }

    WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags) {
        var msg;
        if (this.wholemsg) {
            try {
                msg = JSON.parse(data);
                if (typeof msg !== "object" && !Array.isArray(msg) && (msg !== null)) {
                    msg = { payload:msg };
                }
            }
            catch(err) {
                msg = { payload:data };
            }
        } else {
            msg = {
                payload:data
            };
        }
        msg._session = {type:"websocket",id:id};
        for (var i = 0; i < this._inputNodes.length; i++) {
            this._inputNodes[i].send(msg);
        }
    }

    WebSocketListenerNode.prototype.broadcast = function(data) {
        if (this.isServer) {
            for (let client in this._clients) {
                if (this._clients.hasOwnProperty(client)) {
                    try {
                        this._clients[client].send(data);
                    } catch(err) {
                        this.warn(RED._("websocket.errors.send-error")+" "+client+" "+err.toString())
                    }
                }
            }
        }
        else {
            try {
                this.server.send(data);
            } catch(err) {
                this.warn(RED._("websocket.errors.send-error")+" "+err.toString())
            }
        }
    }

    WebSocketListenerNode.prototype.reply = function(id,data) {
        var session = this._clients[id];
        if (session) {
            try {
                session.send(data);
            }
            catch(e) { // swallow any errors
            }
        }
    }

    function WebSocketInNode(n) {
        RED.nodes.createNode(this,n);
        this.server = (n.client)?n.client:n.server;
        var node = this;
        this.serverConfig = RED.nodes.getNode(this.server);
        if (this.serverConfig) {
            this.serverConfig.registerInputNode(this);
            // TODO: nls
            this.serverConfig.on('opened', function(event) {
                node.status({
                    fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
                    event:"connect",
                    _session: {type:"websocket",id:event.id}
                });
            });
            this.serverConfig.on('erro', function(event) {
                node.status({
                    fill:"red",shape:"ring",text:"common.status.error",
                    event:"error",
                    _session: {type:"websocket",id:event.id}
                });
            });
            this.serverConfig.on('closed', function(event) {
                var status;
                if (event.count > 0) {
                    status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
                } else {
                    status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
                }
                status.event = "disconnect";
                status._session = {type:"websocket",id:event.id}
                node.status(status);
            });
        } else {
            this.error(RED._("websocket.errors.missing-conf"));
        }
        this.on('close', function() {
            if (node.serverConfig) {
                node.serverConfig.removeInputNode(node);
            }
            node.status({});
        });
    }
    RED.nodes.registerType("websocket in",WebSocketInNode);

    function WebSocketOutNode(n) {
        RED.nodes.createNode(this,n);
        var node = this;
        this.server = (n.client)?n.client:n.server;
        this.serverConfig = RED.nodes.getNode(this.server);
        if (!this.serverConfig) {
            return this.error(RED._("websocket.errors.missing-conf"));
        }
        else {
            // TODO: nls
            this.serverConfig.on('opened', function(event) {
                node.status({
                    fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
                    event:"connect",
                    _session: {type:"websocket",id:event.id}
                });
            });
            this.serverConfig.on('erro', function(event) {
                node.status({
                    fill:"red",shape:"ring",text:"common.status.error",
                    event:"error",
                    _session: {type:"websocket",id:event.id}
                })
            });
            this.serverConfig.on('closed', function(event) {
                var status;
                if (event.count > 0) {
                    status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
                } else {
                    status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
                }
                status.event = "disconnect";
                status._session = {type:"websocket",id:event.id}
                node.status(status);
            });
        }
        this.on("input", function(msg, nodeSend, nodeDone) {
            var payload;
            if (this.serverConfig.wholemsg) {
                var sess;
                if (msg._session) { sess = JSON.stringify(msg._session); }
                delete msg._session;
                payload = JSON.stringify(msg);
                if (sess) { msg._session = JSON.parse(sess); }
            }
            else if (msg.hasOwnProperty("payload")) {
                if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
                    payload = RED.util.ensureString(msg.payload);
                }
                else {
                    payload = msg.payload;
                }
            }
            if (payload) {
                if (msg._session && msg._session.type == "websocket") {
                    node.serverConfig.reply(msg._session.id,payload);
                } else {
                    node.serverConfig.broadcast(payload,function(error) {
                        if (!!error) {
                            node.warn(RED._("websocket.errors.send-error")+inspect(error));
                        }
                    });
                }
            }
            nodeDone();
        });
        this.on('close', function() {
            node.status({});
        });
    }
    RED.nodes.registerType("websocket out",WebSocketOutNode);
}
<|endoftext|>


<commit_before>/* This is similar to the server-side main.js.  We don't
   need to add handlePath here, because handling the path
   happens on the server side.  Also (for some reason) we
   don't need any importing here; I guess the three
   client-side hooks are defined in this file, is why. */

function init() {
  this.hooks = ['aceInitInnerdocbodyHead', 'aceGetFilterStack', 'aceCreateDomLine'];
  this.aceInitInnerdocbodyHead = aceInitInnerdocbodyHead;
  this.aceGetFilterStack = aceGetFilterStack;
  this.aceCreateDomLine = aceCreateDomLine;
}

/* All this iframe stuff is obviously client-side. */

function aceInitInnerdocbodyHead(args) {
  console.log("Hi!");
  args.iframeHTML.push('\'<link rel="stylesheet" type="text/css" href="/static/css/plugins/wikiStyleLinks/pad.css"/>\'');
}

/* aceGetFilterStack will run both server- and client-side. */

/* Returns a set of functions that filters the pad content
   [[using a regexp]].  The args parameter here has
   methods, including linestylefilter.getRegexpFilter. */

/* Cf. ~/etherpadding/pad-may-9/infrastructure/ace/www/linestylefilter.js */

/* getRegexpFilter is a function that adds the 'wikilink'
   attribute to everything that matches the regexp. */

/* Note, we may have to be a bit careful because we might
   see [[this is just designed to confuse #people]] or
   [[have you seen http://doc.etherpad.org lately?]] or
   even worse, #what-is-this-[[supposed]]-to-be?

   On the other hand, to keep the code simple and modular,
   I think what's going to happen is that the "outer"
   syntax will dominate.

   And what about things like [[this one is [[tricky]]
   or [[this one is [[tricky]]]] ?

   It seems like the 1st will work as a link, and the 2nd
   will link to the same thing.  I guess that's fine.*/

 /* Right now, the regexp is supposed to say:
    1. find [[
    2. followed by anything that isn't ]
    3. and then by ]]
    NEXT PASS THROUGH THE CODE:
    -- and put a bracket around that 2nd bit
    -- then indicate the 1st bracketed expression. */

function aceGetFilterStack(args) {
  return [getWikiLinkFilter(args.linestylefilter)];
}

/* This is where we actually do things.  Now that
   different ranges of text are labelled in the document,
   you look through the text, you add HTML around the
   labelled bits.  (This works on an abstract, object
   oriented version of the pad content, whereas the former
   works on a SIMPLE version of the text -- a string --
   and returns a string with properties!)  */

/* args.cls is a list of attributes.
   args.cls.indexOf >= 0 means that the thing is contained in the list*/

function aceCreateDomLine(args) {
  if (args.cls.indexOf('wikilink') >= 0) {
    var href;
    /* cls contains the name of a pad that's being linked to --
       We replace it with the word "wikilink" so that args.cls
       is a list of valid CSS classes.
       cls = "whatever-class other-class wikilink:[[whatever goes here]] yet-another-class" */
    cls = args.cls.replace(/wikilink:(\S+)/g, function(x0,linktext) {
      /*At the same time we convert the name of the pad
        into a proper URL.*/
      href = "/" + linktext;
      return "wikilink";
    });

   return [{
     cls: cls,

     /* Not sure about this replacement relative to the
        other one above... */

     extraOpenTags: '',
     extraCloseTags: ''}];
  }
}

/* This is a modified version of linestylefilter.getRegexpFilter,
   changed to do exactly what I want done with links [[like this]].
   I can't use getRegexpFilter to do it, because it adds a
   property to the entire
   The only reason to call something `getFoo' is because
   it returns a function.  But that doesn't happen here.  */

function getWikiLinkFilter (linestylefilter) {
  return function (lineText, textAndClassFunc) {

    var regExp = new RegExp("\\[\\[[^\\[\\]]*]]", "g");

    regExp.lastIndex = 0;
    var regExpMatchs = null;
    var splitPoints = null;
    var execResult;
    while ((execResult = regExp.exec(lineText))) {
      if (! regExpMatchs) {
	regExpMatchs = [];
	splitPoints = [];
      }
      var startIndex = execResult.index+2;
      var regExpMatch = execResult[0].substring(2,execResult[0].length - 2).replace(/ /g, '-');

      regExpMatchs.push([startIndex, regExpMatch]);
      splitPoints.push(startIndex, startIndex + regExpMatch.length);
    }

    if (! regExpMatchs) return textAndClassFunc;

    function regExpMatchForIndex(idx) {
      for(var k=0; k<regExpMatchs.length; k++) {
	var u = regExpMatchs[k];
	if (idx >= u[0] && idx < u[0]+u[1].length) {
	  return u[1];
	}
      }
      return false;
    }

    var handleRegExpMatchsAfterSplit = (function() {
      var curIndex = 0;
      return function(txt, cls) {
	var txtlen = txt.length;
	var newCls = cls;
	var regExpMatch = regExpMatchForIndex(curIndex);
	if (regExpMatch) {
	  newCls += " wikilink:"+regExpMatch;
	}
	textAndClassFunc(txt, newCls);
	curIndex += txtlen;
      };
    })();

    return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit,
						    splitPoints);
  };
};

/* used on the client side only; something similar needed
   in every client-side plugin to get it to go*/

wikiStyleLinks = new init();
<commit_msg>Bugfixed Joes wikiStyleLinks<commit_after>/* This is similar to the server-side main.js.  We don't
 * need to add handlePath here, because handling the path
 * happens on the server side.  Also (for some reason) we
 * don't need any importing here; I guess the three
 * client-side hooks are defined in this file, is why.
 */

function init() {
  this.hooks = ['aceInitInnerdocbodyHead', 'aceGetFilterStack', 'aceCreateDomLine'];
  this.aceInitInnerdocbodyHead = aceInitInnerdocbodyHead;
  this.aceGetFilterStack = aceGetFilterStack;
  this.aceCreateDomLine = aceCreateDomLine;
}

/* All this iframe stuff is obviously client-side. */

function aceInitInnerdocbodyHead(args) {
  args.iframeHTML.push('\'<link rel="stylesheet" type="text/css" href="/static/css/plugins/wikiStyleLinks/pad.css"/>\'');
}

/* aceGetFilterStack will run both server- and client-side.
 *
 * Returns a set of functions that filters the pad content
 * [[using a regexp]].  The args parameter here has
 * methods, including linestylefilter.getRegexpFilter.
 *
 * Cf. ~/etherpadding/pad-may-9/infrastructure/ace/www/linestylefilter.js
 *
 * getRegexpFilter is a function that adds the 'wikilink'
 * attribute to everything that matches the regexp.
 *
 * Note, we may have to be a bit careful because we might
 * see [[this is just designed to confuse #people]] or
 * [[have you seen http://doc.etherpad.org lately?]] or
 * even worse, #what-is-this-[[supposed]]-to-be?
 *
 * On the other hand, to keep the code simple and modular,
 * I think what's going to happen is that the "outer"
 * syntax will dominate.
 *
 * And what about things like [[this one is [[tricky]]
 * or [[this one is [[tricky]]]] ?
 *
 * It seems like the 1st will work as a link, and the 2nd
 * will link to the same thing.  I guess that's fine.
 *
 * Right now, the regexp is supposed to say:
 *  1. find [[
 *  2. followed by anything that isn't ]
 *  3. and then by ]]
 *  NEXT PASS THROUGH THE CODE:
 *  -- and put a bracket around that 2nd bit
 *  -- then indicate the 1st bracketed expression.
 */

function aceGetFilterStack(args) {
  return [getWikiLinkFilter(args.linestylefilter)];
}

/* This is where we actually do things.  Now that
 * different ranges of text are labelled in the document,
 * you look through the text, you add HTML around the
 * labelled bits.  (This works on an abstract, object
 * oriented version of the pad content, whereas the former
 * works on a SIMPLE version of the text -- a string --
 * and returns a string with properties!)

 * args.cls is a list of attributes.
 * args.cls.indexOf >= 0 means that the thing is contained in the list
 */

function aceCreateDomLine(args) {
  if (args.cls.indexOf('wikilink') >= 0) {
    var href;
    /* cls contains the name of a pad that's being linked to --
     * We replace it with the word "wikilink" so that args.cls
     * is a list of valid CSS classes.
     * cls = "whatever-class other-class wikilink:[[whatever goes here]] yet-another-class"
     */
    cls = args.cls.replace(/wikilink:(\S+)/g, function(x0,linktext) {
      /* At the same time we convert the name of the pad
       * into a proper URL.
       */
      href = "/" + linktext;
      return "wikilink";
    });

   return [{
     cls: cls,

     /* Not sure about this replacement relative to the
      * other one above...
      */

     extraOpenTags: '',
     extraCloseTags: ''}];
  }
}

/* This is a modified version of linestylefilter.getRegexpFilter,
 * changed to do exactly what I want done with links [[like this]].
 * I can't use getRegexpFilter to do it, because it adds a
 * property to the entire
 * The only reason to call something `getFoo' is because
 * it returns a function.  But that doesn't happen here.
 */

function getWikiLinkFilter (linestylefilter) {
  return function (lineText, textAndClassFunc) {

    var regExp = new RegExp("\\[\\[[^\\[\\]]*]]", "g");

    regExp.lastIndex = 0;
    var regExpMatchs = null;
    var splitPoints = null;
    var execResult;
    while ((execResult = regExp.exec(lineText))) {
      if (! regExpMatchs) {
	regExpMatchs = [];
	splitPoints = [];
      }
      var startIndex = execResult.index+2;
      var regExpMatch = execResult[0].substring(2,execResult[0].length - 2).replace(/ /g, '-');

      regExpMatchs.push([startIndex, regExpMatch]);
      splitPoints.push(startIndex, startIndex + regExpMatch.length);
    }

    if (! regExpMatchs) return textAndClassFunc;

    function regExpMatchForIndex(idx) {
      for(var k=0; k<regExpMatchs.length; k++) {
	var u = regExpMatchs[k];
	if (idx >= u[0] && idx < u[0]+u[1].length) {
	  return u[1];
	}
      }
      return false;
    }

    var handleRegExpMatchsAfterSplit = (function() {
      var curIndex = 0;
      return function(txt, cls) {
	var txtlen = txt.length;
	var newCls = cls;
	var regExpMatch = regExpMatchForIndex(curIndex);
	if (regExpMatch) {
	  newCls += " wikilink:"+regExpMatch;
	}
	textAndClassFunc(txt, newCls);
	curIndex += txtlen;
      };
    })();

    return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit,
						    splitPoints);
  };
};

/* used on the client side only; something similar needed
 * in every client-side plugin to get it to go
 */

wikiStyleLinks = new init();
<|endoftext|>