<commit_before>'use strict';

// namespace definition
var Viewer3D = Viewer3D || {};

Viewer3D.MonoCamera = ( function () {
    var MonoCameraClass = function (vport) {
        this.vport = vport;

        if ( window.WebGLRenderingContext ) {
            console.log("WebGL available... Yoohoo");
            this.renderer = new THREE.WebGLRenderer( {alpha: true} );
        } else {
            console.log("WebGL NOT available... :-( Using Canvas");
            this.renderer = new THREE.CanvasRenderer();
        }

        this.renderer.setSize(vport.width, vport.height);
        vport.container.appendChild(this.renderer.domElement);

        this.renderer.setClearColor(0xeeeeee, 1);

        this.camera = new THREE.PerspectiveCamera(
            35,
            vport.width / vport.height,
            1,
            1000
            );

        this.camera.position.z = 100;
        this.camera.position.y = 20;

        this.scene = vport.scene;
    };

    MonoCameraClass.prototype.onResize = function () {
        // get the new width and height
        var width, height, aspect;
        width = this.vport.width;
        height = this.vport.height;

        // update renderer size
        this.renderer.setSize(width, height);
        // update camera aspect ratio
        aspect = width / height;
        console.log( "aspect = " + aspect.toString() );
        this.camera.aspect = aspect;
        this.camera.updateProjectionMatrix();
    };

    MonoCameraClass.prototype.render = function () {
        this.renderer.render(this.scene, this.camera);
    };

    return MonoCameraClass;
} ) ();

Viewer3D.StereoCamera = ( function () {
    var StereoCameraClass = function (vport) {
        this.vport = vport;
        this.sepWidth = 2;

        if ( window.WebGLRenderingContext ) {
            console.log("WebGL available... Yoohoo");
            this.renderer = new THREE.WebGLRenderer( {alpha: true} );
        } else {
            console.log("WebGL NOT available... :-( Using Canvas");
            this.renderer = new THREE.CanvasRenderer();
        }

        this.width = vport.width;
        this.height = vport.height;
        this.width = this.width % 2 == 0 ? this.width : this.width - 1;

        this.renderer.setSize(this.width + this.sepWidth, this.height);
        this.renderer.autoClear = false;
        this.renderer.setClearColor(0xeeeeee, 1);

        vport.container.appendChild(this.renderer.domElement);

        this.camera1 = new THREE.PerspectiveCamera(
            35,
            this.width / (2 * vport.height),
            1,
            1000
            );

        this.camera1.position.z = 200;
        this.camera1.position.y = 50;

        this.camera2 = new THREE.PerspectiveCamera(
            35,
            this.width / (2 * vport.height),
            1,
            1000
            );

        this.camera2.position.z = 200;
        this.camera2.position.y = 50;

        this.scene = vport.scene;
    };

    StereoCameraClass.prototype.onResize = function () {
        // get the new width and height
        var width, height, aspect;
        width = this.vport.width;
        width = width % 2 == 0 ? width : width - 1;
        this.width = width;

        height = this.vport.height;
        this.height = height;

        // update renderer size
        this.renderer.setSize(width + this.sepWidth, height);

        // update camera aspect ratio
        aspect = width / (2 * height);
        this.camera1.aspect = aspect;
        this.camera1.updateProjectionMatrix();
        this.camera2.aspect = aspect;
        this.camera2.updateProjectionMatrix();
    };

    StereoCameraClass.prototype.render = function () {
        this.renderer.clear();
        this.renderer.setViewport(0, 0, this.width/2, this.height);
        this.renderer.render(this.scene, this.camera1);

        this.renderer.setViewport(this.width/2 + this.sepWidth, 0, this.width/2, this.height);
        this.renderer.render(this.scene, this.camera2, undefined, false);
    };

    StereoCameraClass.prototype.getCoordsAndCamera = function (x, y) {
        var x1, y1;
        if ( ( y >= 0 ) && ( y <= this.height )) {
            if ( ( x >= 0 ) && ( x <= (this.width / 2)) ) {
                x1 = 2 * ( x / (this.width/2)) - 1;
                y1 = 1 - 2 * ( y / this.height );
                // camera 1
                return [x1, y1, this.camera1];
            } else if ( ( x >= (this.width/2 + this.sepWidth) ) &&
                        ( x <= (this.width + this.sepWidth)) ) {
                x1 = 2 * ( (x - this.width/2 - this.sepWidth ) / (this.width/2)) - 1;
                y1 = 1 - 2 * ( y / this.height );
                // camera 2
                return [x1, y1, this.camera2];
            }
        }
        return null;
    };

    return StereoCameraClass;
} ) ();

Viewer3D.Picker = ( function () {
    var PickerClass = function (el, scene, cameraWrap, viewer) {
        this.el = el;
        this.scene = scene;
        this.cameraWrap = cameraWrap;
        this.pickedObject = null;
        this.viewer = viewer;
        this.pickClone = null;

        var that = this;

        function _onMouseMove(evt) {
            that.onMouseMove(evt);
        }

        el.onmousemove = _onMouseMove;
    };

    PickerClass.prototype.onMouseMove = function (evt) {
        console.log("mouse moved");
        var mouseVector = new THREE.Vector3();
        var offsetx, offsety, offsetAttrs;
        var coordsAndCam;
        var objects = this.scene.theObjects;

        offsetAttrs = this.el.getClientRects()[0];
        offsetx = offsetAttrs.left;
        offsety = offsetAttrs.top;

        coordsAndCam = this.cameraWrap.getCoordsAndCamera(evt.clientX - offsetx,
                                  evt.clientY - offsety);

        if (coordsAndCam === null) {
            this.pickedObject = null;
            return;
        }

        mouseVector.x = coordsAndCam[0];
        mouseVector.y = coordsAndCam[1];

        var raycaster = new THREE.Raycaster();
        raycaster.setFromCamera( mouseVector.clone(), coordsAndCam[2] );
        var intersects = raycaster.intersectObjects( objects.children );
        var newPick = null;

        if (intersects.length > 0) {
            newPick = intersects[0].object;
        }

        if ((newPick !== null) && (newPick !== this.pickedObject)) {
            this.pickedObject = newPick;
            this.viewer.render();
        } else {
            if ((newPick === null) && (this.pickedObject !== null)) {
                this.pickedObject = null;
                this.viewer.render();
            }
        }
    };

    PickerClass.prototype.highlightPickedObject = function () {
        var cloneGeo, cloneMat;
        var pos, rot;
        var scale = 1.002;

        if ( this.pickedObject !== null ) {
            if (this.pickClone !== null ) {
                this.scene.remove(this.pickClone);
                this.pickClone = null;
            }
            cloneGeo = this.pickedObject.geometry.clone();
            cloneMat = new THREE.MeshBasicMaterial( {
                color: 0xffffff,
                opacity: 0.75,
                transparent: true
            } );

            this.pickClone = new THREE.Mesh(cloneGeo, cloneMat);
            pos = this.pickedObject.position;
            rot = this.pickedObject.rotation;
            this.pickClone.position.set(pos.x, pos.y, pos.z);
            this.pickClone.rotation.set(rot.x, rot.y, rot.z);
            this.pickClone.scale.set(scale, scale, scale);

            this.scene.add(this.pickClone);
        } else {
            if (this.pickClone !== null ) {
                this.scene.remove(this.pickClone);
                this.pickClone = null;
            }
        }
    };

    return PickerClass;
}) ();

Viewer3D.Viewport = ( function () {
    var ViewportClass = function (attrs) {
        var that = this;

        this.container = attrs.container;
        this.updateViewAttrs();

        function _onResize() {
            that.onResize();
        }
        // subscribe to resize event
        var resizer = new ResizeSensor(this.container, _onResize);

        this.scene = attrs.scene;

        if (attrs.cameraType === 'mono') {
            this.cameraWrap = new Viewer3D.MonoCamera(this);
            this.scene.add(this.cameraWrap.camera);
        } else if (attrs.cameraType === 'stereo') {
            this.cameraWrap = new Viewer3D.StereoCamera(this);
            this.scene.add(this.cameraWrap.camera1);
            this.scene.add(this.cameraWrap.camera2);
        }

        this.picker = new Viewer3D.Picker(this.cameraWrap.renderer.domElement,
                                          this.scene,
                                          this.cameraWrap,
                                          this
        );

        this.render();
    }

    ViewportClass.prototype.updateViewAttrs = function () {
        var crect = this.container.getClientRects()[0];

        this.width = crect.width - 15;
        this.height = crect.height - 15;
    }

    ViewportClass.prototype.onResize = function (el) {
        console.log("resize event works");

        this.updateViewAttrs();
        this.cameraWrap.onResize();
        this.render();
    }

    ViewportClass.prototype.render = function () {
        this.picker.highlightPickedObject();
        this.cameraWrap.render();
    }

    return ViewportClass;
} ) ();

Viewer3D.Scene = ( function () {
    return THREE.Scene;
} ) ();

function addGround(pick, nonpick) {

    // create the rectangle
    var rectLengthBy2 = 50,
        rectWidthBy2 = 50;
    var rectShape = new THREE.Shape();
    rectShape.moveTo(-rectLengthBy2, -rectWidthBy2);
    rectShape.lineTo(rectLengthBy2, -rectWidthBy2);
    rectShape.lineTo(rectLengthBy2, rectWidthBy2);
    rectShape.lineTo(-rectLengthBy2, rectWidthBy2);
    rectShape.lineTo(-rectLengthBy2, -rectWidthBy2);

    // create the lines
    var offset, jump, coord;
    var geometry = new THREE.Geometry();

    var material = new THREE.LineBasicMaterial({
        color: 0xaaaaaa
    });

    var linelen = 500;

    for (var x = 0; x <= 25; x++) {
        offset = 100 * (Math.pow(2, Math.floor(x / 10)) - 1)
        jump = ((x % 10) * 10 * Math.pow(2, Math.floor(x / 10)))
        coord = offset + jump;

        geometry = new THREE.Geometry();
        geometry.vertices.push(new THREE.Vector3(-linelen, 0, coord));
        geometry.vertices.push(new THREE.Vector3(linelen, 0, coord));
        nonpick.add( new THREE.Line(geometry, material) );

        if (coord !== 0) {
            geometry = new THREE.Geometry();
            geometry.vertices.push(new THREE.Vector3(-linelen, 0, -coord));
            geometry.vertices.push(new THREE.Vector3(linelen, 0, -coord));
            nonpick.add( new THREE.Line(geometry, material) );
        }

        geometry = new THREE.Geometry();
        geometry.vertices.push(new THREE.Vector3(coord, 0, -linelen));
        geometry.vertices.push(new THREE.Vector3(coord, 0, linelen));
        nonpick.add( new THREE.Line(geometry, material) );

        if (coord !== 0) {
            geometry = new THREE.Geometry();
            geometry.vertices.push(new THREE.Vector3(-coord, 0, -linelen));
            geometry.vertices.push(new THREE.Vector3(-coord, 0, linelen));
            nonpick.add( new THREE.Line(geometry, material) );
        }

    };

    var rectGeom = new THREE.ShapeGeometry(rectShape);
    var rectMesh = new THREE.Mesh(rectGeom, new THREE.MeshBasicMaterial({
            color: 0xcccccc
        }));

    rectMesh.rotation.x = -Math.PI/2;
    rectMesh.position.y -= 0.01;

    nonpick.add(rectMesh);
}

function main() {
    var boxgeo, boxmat, box;
    var pickableObjects = new THREE.Object3D();
    var nonPickableObjects = new THREE.Object3D();

    var scene = new Viewer3D.Scene();

    var attrs1 = {
        container: document.getElementById("explorer1"),
        cameraType: 'stereo',
        scene: scene
    }

    boxgeo = new THREE.BoxGeometry(5, 5, 5);
    boxmat = new THREE.MeshBasicMaterial( { color: 0xff2288 } );
    box = new THREE.Mesh( boxgeo, boxmat );
    box.position.y = 2.5;
    pickableObjects.add( box );

    addGround(pickableObjects, nonPickableObjects);
    scene.theObjects = pickableObjects;

    scene.add(pickableObjects);
    scene.add(nonPickableObjects);

    new Viewer3D.Viewport(attrs1);
}

window.onload = main;
// console.log(vp);

<commit_msg>adding code scaffolding for panning and orbit<commit_after>'use strict';

// namespace definition
var Viewer3D = Viewer3D || {};

Viewer3D.MonoCamera = ( function () {
    var MonoCameraClass = function (vport) {
        this.vport = vport;

        if ( window.WebGLRenderingContext ) {
            console.log("WebGL available... Yoohoo");
            this.renderer = new THREE.WebGLRenderer( {alpha: true} );
        } else {
            console.log("WebGL NOT available... :-( Using Canvas");
            this.renderer = new THREE.CanvasRenderer();
        }

        this.renderer.setSize(vport.width, vport.height);
        vport.container.appendChild(this.renderer.domElement);

        this.renderer.setClearColor(0xeeeeee, 1);

        this.camera = new THREE.PerspectiveCamera(
            35,
            vport.width / vport.height,
            1,
            1000
            );

        this.camera.position.z = 100;
        this.camera.position.y = 20;

        this.scene = vport.scene;
    };

    MonoCameraClass.prototype.onResize = function () {
        // get the new width and height
        var width, height, aspect;
        width = this.vport.width;
        height = this.vport.height;

        // update renderer size
        this.renderer.setSize(width, height);
        // update camera aspect ratio
        aspect = width / height;
        console.log( "aspect = " + aspect.toString() );
        this.camera.aspect = aspect;
        this.camera.updateProjectionMatrix();
    };

    MonoCameraClass.prototype.render = function () {
        this.renderer.render(this.scene, this.camera);
    };

    return MonoCameraClass;
} ) ();

Viewer3D.StereoCamera = ( function () {
    var StereoCameraClass = function (vport) {
        this.vport = vport;
        this.sepWidth = 2;

        if ( window.WebGLRenderingContext ) {
            console.log("WebGL available... Yoohoo");
            this.renderer = new THREE.WebGLRenderer( {alpha: true} );
        } else {
            console.log("WebGL NOT available... :-( Using Canvas");
            this.renderer = new THREE.CanvasRenderer();
        }

        this.width = vport.width;
        this.height = vport.height;
        this.width = this.width % 2 === 0 ? this.width : this.width - 1;

        this.renderer.setSize(this.width + this.sepWidth, this.height);
        this.renderer.autoClear = false;
        this.renderer.setClearColor(0xeeeeee, 1);

        vport.container.appendChild(this.renderer.domElement);

        this.camera1 = new THREE.PerspectiveCamera(
            35,
            this.width / (2 * vport.height),
            1,
            1000
            );

        this.camera1.position.z = 200;
        this.camera1.position.y = 50;

        this.camera2 = new THREE.PerspectiveCamera(
            35,
            this.width / (2 * vport.height),
            1,
            1000
            );

        this.camera2.position.z = 200;
        this.camera2.position.y = 50;

        this.scene = vport.scene;
    };

    StereoCameraClass.prototype.onResize = function () {
        // get the new width and height
        var width, height, aspect;
        width = this.vport.width;
        width = width % 2 === 0 ? width : width - 1;
        this.width = width;

        height = this.vport.height;
        this.height = height;

        // update renderer size
        this.renderer.setSize(width + this.sepWidth, height);

        // update camera aspect ratio
        aspect = width / (2 * height);
        this.camera1.aspect = aspect;
        this.camera1.updateProjectionMatrix();
        this.camera2.aspect = aspect;
        this.camera2.updateProjectionMatrix();
    };

    StereoCameraClass.prototype.render = function () {
        this.renderer.clear();
        this.renderer.setViewport(0, 0, this.width/2, this.height);
        this.renderer.render(this.scene, this.camera1);

        this.renderer.setViewport(this.width/2 + this.sepWidth, 0, this.width/2, this.height);
        this.renderer.render(this.scene, this.camera2, undefined, false);
    };

    StereoCameraClass.prototype.getCoordsAndCamera = function (x, y) {
        var x1, y1;
        if ( ( y >= 0 ) && ( y <= this.height )) {
            if ( ( x >= 0 ) && ( x <= (this.width / 2)) ) {
                x1 = 2 * ( x / (this.width/2)) - 1;
                y1 = 1 - 2 * ( y / this.height );
                // camera 1
                return [x1, y1, this.camera1];
            } else if ( ( x >= (this.width/2 + this.sepWidth) ) &&
                        ( x <= (this.width + this.sepWidth)) ) {
                x1 = 2 * ( (x - this.width/2 - this.sepWidth ) / (this.width/2)) - 1;
                y1 = 1 - 2 * ( y / this.height );
                // camera 2
                return [x1, y1, this.camera2];
            }
        }
        return null;
    };

    return StereoCameraClass;
} ) ();

Viewer3D.Picker = ( function () {
    var PickerClass = function (el, scene, cameraWrap, viewer) {
        this.el = el;
        this.scene = scene;
        this.cameraWrap = cameraWrap;
        this.pickedObject = null;
        this.viewer = viewer;
        this.pickClone = null;
        this.leftDown = false;
        this.leftDownPos = null;
        this.middleDown = false;
        this.middleDownPos = null;
        this.dragging = false;
        this.latchPoint = null;

        var that = this;

        function _onMouseMove ( evt ) {
            that.onMouseMove(evt);
        }

        function _onMouseDown ( evt ) {
            var button = evt.button;

            if ( button === 0 ) {
                that.leftDown = true;
                that.leftDownPos = [evt.clientX, evt.clientY];
            } else if ( button === 1 ) {
                that.middleDown = true;
                that.middleDownPos = [evt.clientX, evt.clientY];
            }
        }

        function _onMouseUp ( evt ) {
            var button = evt.button;

            if ( button === 0 ) {
                that.leftDown = false;
                if (!that.dragging) {
                    that.onMouseLeftClick(evt);
                }
            } else if ( button === 1 ) {
                that.middleDown = false;
                if (!that.dragging) {
                    that.onMouseMiddleClick(evt);
                }
            }

            if (!(that.leftDown || that.middleDown)) {
                that.dragging = false;
            }
        }

        el.onmousemove = _onMouseMove;
        el.onmousedown = _onMouseDown;
        el.onmouseup = _onMouseUp;
    };

    PickerClass.prototype.getDragDist = function (p1, p2) {
        var x1, y1, x2, y2;
        x1 = p1[0];
        y1 = p1[1];
        x2 = p2[0];
        y2 = p2[1];
        return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
    };

    PickerClass.prototype.onMouseLeftClick = function (evt) {
        console.log("on left click");
    };

    PickerClass.prototype.onMouseMiddleClick = function (evt) {
        console.log("on middle click");
    };

    PickerClass.prototype.doPanning = function (evt) {
        if (this.latchPoint === null) {
            this.latchPoint = this.findLatchPoint();
            // move camera such that latch point moves exactly same distances
        }
        console.log("panning");
    };

    PickerClass.prototype.doOrbit = function (evt) {
        console.log("orbiting");
    };

    PickerClass.prototype.onMouseMove = function (evt) {
        var pos, dist;

        if (this.leftDown) {
            pos = [evt.clientX, evt.clientY];
            dist = this.getDragDist(this.leftDownPos, pos);
            if (this.dragging || (dist >= 3)) {
                this.dragging = true;
                this.doPanning(evt);
                return;
            }
        }

        if (this.middleDown) {
            pos = [evt.clientX, evt.clientY];
            dist = this.getDragDist(this.middleDownPos, pos);
            if (this.dragging || (dist >= 3)) {
                this.dragging = true;
                this.doOrbit(evt);
                return;
            }
        }

        var mouseVector = new THREE.Vector3();
        var offsetx, offsety, offsetAttrs;
        var coordsAndCam;
        var objects = this.scene.theObjects;

        offsetAttrs = this.el.getClientRects()[0];
        offsetx = offsetAttrs.left;
        offsety = offsetAttrs.top;

        coordsAndCam = this.cameraWrap.getCoordsAndCamera(evt.clientX - offsetx,
                                  evt.clientY - offsety);

        if (coordsAndCam === null) {
            this.pickedObject = null;
            return;
        }

        mouseVector.x = coordsAndCam[0];
        mouseVector.y = coordsAndCam[1];

        var raycaster = new THREE.Raycaster();
        raycaster.setFromCamera( mouseVector.clone(), coordsAndCam[2] );
        var intersects = raycaster.intersectObjects( objects.children );
        var newPick = null;

        if (intersects.length > 0) {
            newPick = intersects[0].object;
        }

        if ((newPick !== null) && (newPick !== this.pickedObject)) {
            this.pickedObject = newPick;
            this.viewer.render();
        } else {
            if ((newPick === null) && (this.pickedObject !== null)) {
                this.pickedObject = null;
                this.viewer.render();
            }
        }
    };

    PickerClass.prototype.highlightPickedObject = function () {
        var cloneGeo, cloneMat;
        var pos, rot;
        var scale = 1.002;

        if ( this.pickedObject !== null ) {
            if (this.pickClone !== null ) {
                this.scene.remove(this.pickClone);
                this.pickClone = null;
            }
            cloneGeo = this.pickedObject.geometry.clone();
            cloneMat = new THREE.MeshBasicMaterial( {
                color: 0xffffff,
                opacity: 0.75,
                transparent: true
            } );

            this.pickClone = new THREE.Mesh(cloneGeo, cloneMat);
            pos = this.pickedObject.position;
            rot = this.pickedObject.rotation;
            this.pickClone.position.set(pos.x, pos.y, pos.z);
            this.pickClone.rotation.set(rot.x, rot.y, rot.z);
            this.pickClone.scale.set(scale, scale, scale);

            this.scene.add(this.pickClone);
        } else {
            if (this.pickClone !== null ) {
                this.scene.remove(this.pickClone);
                this.pickClone = null;
            }
        }
    };

    return PickerClass;
}) ();

Viewer3D.Viewport = ( function () {
    var ViewportClass = function (attrs) {
        var that = this;
        this.scene = attrs.scene;
        this.container = attrs.container;

        this.updateViewAttrs();

        function _onResize() {
            that.onResize();
        }
        // subscribe to resize event
        new ResizeSensor(this.container, _onResize);

        if (attrs.cameraType === 'mono') {
            this.cameraWrap = new Viewer3D.MonoCamera(this);
            this.scene.add(this.cameraWrap.camera);
        } else if (attrs.cameraType === 'stereo') {
            this.cameraWrap = new Viewer3D.StereoCamera(this);
            this.scene.add(this.cameraWrap.camera1);
            this.scene.add(this.cameraWrap.camera2);
        }

        this.picker = new Viewer3D.Picker(this.cameraWrap.renderer.domElement,
                                          this.scene,
                                          this.cameraWrap,
                                          this
        );

        this.render();
    };

    ViewportClass.prototype.updateViewAttrs = function () {
        var crect = this.container.getClientRects()[0];

        this.width = crect.width - 15;
        this.height = crect.height - 15;
    };

    ViewportClass.prototype.onResize = function (el) {
        this.updateViewAttrs();
        this.cameraWrap.onResize();
        this.render();
    };

    ViewportClass.prototype.render = function () {
        this.picker.highlightPickedObject();
        this.cameraWrap.render();
    };

    return ViewportClass;
} ) ();

Viewer3D.Scene = ( function () {
    return THREE.Scene;
} ) ();

function addGround(pick, nonpick) {

    // create the rectangle
    var rectLengthBy2 = 50,
        rectWidthBy2 = 50;
    var rectShape = new THREE.Shape();
    rectShape.moveTo(-rectLengthBy2, -rectWidthBy2);
    rectShape.lineTo(rectLengthBy2, -rectWidthBy2);
    rectShape.lineTo(rectLengthBy2, rectWidthBy2);
    rectShape.lineTo(-rectLengthBy2, rectWidthBy2);
    rectShape.lineTo(-rectLengthBy2, -rectWidthBy2);

    // create the lines
    var offset, jump, coord;
    var geometry = new THREE.Geometry();

    var material = new THREE.LineBasicMaterial({
        color: 0xaaaaaa
    });

    var linelen = 500;

    for (var x = 0; x <= 25; x++) {
        offset = 100 * (Math.pow(2, Math.floor(x / 10)) - 1)
        jump = ((x % 10) * 10 * Math.pow(2, Math.floor(x / 10)))
        coord = offset + jump;

        geometry = new THREE.Geometry();
        geometry.vertices.push(new THREE.Vector3(-linelen, 0, coord));
        geometry.vertices.push(new THREE.Vector3(linelen, 0, coord));
        nonpick.add( new THREE.Line(geometry, material) );

        if (coord !== 0) {
            geometry = new THREE.Geometry();
            geometry.vertices.push(new THREE.Vector3(-linelen, 0, -coord));
            geometry.vertices.push(new THREE.Vector3(linelen, 0, -coord));
            nonpick.add( new THREE.Line(geometry, material) );
        }

        geometry = new THREE.Geometry();
        geometry.vertices.push(new THREE.Vector3(coord, 0, -linelen));
        geometry.vertices.push(new THREE.Vector3(coord, 0, linelen));
        nonpick.add( new THREE.Line(geometry, material) );

        if (coord !== 0) {
            geometry = new THREE.Geometry();
            geometry.vertices.push(new THREE.Vector3(-coord, 0, -linelen));
            geometry.vertices.push(new THREE.Vector3(-coord, 0, linelen));
            nonpick.add( new THREE.Line(geometry, material) );
        }

    }

    var rectGeom = new THREE.ShapeGeometry(rectShape);
    var rectMesh = new THREE.Mesh(rectGeom, new THREE.MeshBasicMaterial({
            color: 0xcccccc
        }));

    rectMesh.rotation.x = -Math.PI/2;
    rectMesh.position.y -= 0.1;

    nonpick.add(rectMesh);
}

function main() {
    var boxgeo, boxmat, box;
    var pickableObjects = new THREE.Object3D();
    var nonPickableObjects = new THREE.Object3D();

    var scene = new Viewer3D.Scene();

    var attrs1 = {
        container: document.getElementById("explorer1"),
        cameraType: 'stereo',
        scene: scene
    };

    boxgeo = new THREE.BoxGeometry(5, 5, 5);
    boxmat = new THREE.MeshBasicMaterial( { color: 0xff2288 } );
    box = new THREE.Mesh( boxgeo, boxmat );
    box.position.y = 2.5;
    pickableObjects.add( box );

    addGround(pickableObjects, nonPickableObjects);
    scene.theObjects = pickableObjects;

    scene.add(pickableObjects);
    scene.add(nonPickableObjects);

    new Viewer3D.Viewport(attrs1);
}

window.onload = main;

<|endoftext|>


<commit_before>(function () {
    'use strict';

    /**
     * define factory of `debugHelper`
     */
    angular
        .module('core')
        .factory('debugHelper', debugHelper)
        .run(debugHelperRun);

    /**
     * API of `debugHelper`
     * @ngInject
     */
    function debugHelper($document) {

        return {
            /**
             * get angular service on console
             * @returns {*}
             */
            get: function () {
                var injector = angular.element($document).injector();
                return injector.get.apply(injector.get, arguments);
            }
        };
    }

    /**
     * run `debugHelper`
     * @param debugHelper
     * @param $window
     * @ngInject
     */
    function debugHelperRun(debugHelper, $window) {
        $window._debugHelper = debugHelper;
    }

})();

<commit_msg>move `ng-app` property to `body` node<commit_after>(function () {
    'use strict';

    /**
     * define factory of `debugHelper`
     */
    angular
        .module('core')
        .factory('debugHelper', debugHelper)
        .run(debugHelperRun);

    /**
     * API of `debugHelper`
     * @ngInject
     */
    function debugHelper() {

        return {
            /**
             * get angular service on console
             * @returns {*}
             */
            get: function () {
                var injector = angular.element(document.body).injector();
                return injector.get.apply(injector.get, arguments);
            }
        };
    }

    /**
     * run `debugHelper`
     * @param debugHelper
     * @param $window
     * @ngInject
     */
    function debugHelperRun(debugHelper, $window) {
        $window._debugHelper = debugHelper;
    }

})();

<|endoftext|>


<commit_before>var exec = require('child_process').exec;

module.exports = function(grunt){
  grunt.registerMultiTask('banner', 'Append a banner to production', function(){
    var done = this.async(),
      task = this;
    exec('git describe --tags',
      function(tags_error, tags_stdout, tags_stderr) {
        var tags =  tags_stdout;
        exec('git log -n 1 --format="%h (%ci)"',
          function(sha_error, sha_stdout, sha_stderr) {
            var sha  = sha_stdout;
            var banner = '';

            if (!tags_stderr.code) {
              banner = banner + "// " + tags;
            }

            if (!sha_stderr.code) {
              banner = banner + "// " + sha;
            }

            var options = task.options(),
                license = grunt.file.read(options.license),
                gitInfo = banner,
                code = grunt.file.read(task.file.src);

            grunt.file.write(task.file.src, [license, gitInfo, code].join("\n"));
            done();
          });
      });
  });
};
<commit_msg>Fixed indentantion and how to handle errors.<commit_after>var exec = require('child_process').exec;

module.exports = function(grunt){
  grunt.registerMultiTask('banner', 'Append a banner to production', function(){
    var done = this.async(),
        task = this;
    exec('git describe --tags',
      function(tags_error, tags_stdout, tags_stderr) {
        var tags =  tags_stdout;
        exec('git log -n 1 --format="%h (%ci)"',
          function(sha_error, sha_stdout, sha_stderr) {
            var sha  = sha_stdout,
                banner = '';

            if (!tags_error) {
              banner = banner + "// " + tags;
            }

            if (!sha_error) {
              banner = banner + "// " + sha;
            }

            var options = task.options(),
                license = grunt.file.read(options.license),
                gitInfo = banner,
                code = grunt.file.read(task.file.src);

            grunt.file.write(task.file.src, [license, gitInfo, code].join("\n"));
            done();
          });
      });
  });
};
<|endoftext|>


<commit_before>/* global AutoForm, Template */

import noUiSlider from 'nouislider'
import 'nouislider/distribute/nouislider.css'

AutoForm.addInputType('noUiSlider', {
  template: 'afNoUiSlider',
  valueOut: function () {
    const slider = this.find('.nouislider')[ 0 ]
    const sliderData = slider.noUiSlider.get()

    if (this.attr('data-type') === 'Object') {
      const isDecimal = this.closest('.at-nouislider').data('decimal')
      const parser = (isDecimal) ? parseFloat : parseInt
      const first = parser.call(null, sliderData[ 0 ])
      const second = parser.call(null, sliderData[ 1 ])
      return {
        lower: first > second ? second : first,
        upper: first > second ? first : second
      }
    } else {
      return sliderData
    }
  }
})

const omit = (object, ...keys) => {
  const copy = Object.assign({}, object)
  keys.forEach(key => {
    delete copy[key]
  })
  return copy
}

const pick = (object, ...keys) => {
  const tmp = {}
  keys.forEach(key => {
    tmp[key] = object[key]
  })
  return tmp
}

const exists = x => typeof x !== 'undefined' && x !== null

const merge = (...objects) => {
  const tmp = {}
  objects.forEach(object => {
    Object.keys(object).forEach(key => {
      const value = object[key]
      if (exists(value)) {
        tmp[key] = value
      }
    })
  })
  return tmp
}

Template.afNoUiSlider.helpers({
  atts: function () {
    const data = Template.currentData() // get data reactively
    const atts = Object.assign({}, data.atts)
    atts[ 'data-type' ] = data.schemaType.name || 'Object' // fallback if type is a Schema and name becomes undefined
    if (atts[ 'class' ]) {
      atts[ 'class' ] += ' at-nouislider'
    } else {
      atts[ 'class' ] = 'at-nouislider'
    }

    atts.doLabels = (atts.labelLeft || atts.labelRight)
    atts[ 'data-decimal' ] = data.decimal

    return omit(atts, 'noUiSliderOptions', 'noUiSlider_pipsOptions')
  }
})

const calculateOptions = function (data) {
  debugger
  const schemaMinMax = pick(data, 'max', 'min')
  const autoformOptions = pick(data.atts || {}, 'max', 'min', 'step', 'start', 'range')
  const noUiSliderOptions = (data.atts || {}).noUiSliderOptions
  const options = merge(schemaMinMax, autoformOptions, noUiSliderOptions)

  // Adjust data initialization based on schema type
  if (options.start === undefined) {
    if (data.schemaType.name === 'Object') {
      if (data.value && data.value.lower) {
        options.start = [
          data.value.lower,
          data.value.upper
        ]
      } else {
        options.start = [
          typeof data.min === 'number' ? data.min : 0,
          typeof data.max === 'number' ? data.max : 100
        ]
      }
      options.connect = true
    } else {
      options.start = data.value || 0
    }
  }

  if (options.range === undefined) {
    options.range = {
      min: typeof options.min === 'number' ? options.min : 0,
      max: typeof options.max === 'number' ? options.max : 100
    }
  }

  delete options.min
  delete options.max

  // default step to 1 if not otherwise defined
  if (options.step === undefined) {
    options.step = 1
  }

  return options
}

Template.afNoUiSlider.rendered = function () {
  const template = this
  const $s = template.$('.nouislider')

  const setup = function (c) {
    const data = Template.currentData() // get data reactively
    const options = calculateOptions(data)
    const sliderElem = $s[ 0 ]

    if (sliderElem.noUiSlider) {
      sliderElem.noUiSlider.updateOptions(options, true)
    } else {
      noUiSlider.create(sliderElem, options)
    }

    if (c.firstRun) {
      sliderElem.noUiSlider.on('slide', function () {
        // This is a trick to fool some logic in AutoForm that makes
        // sure values have actually changed on whichever element
        // emits a change event. Eventually AutoForm will give
        // input types the control of indicating exactly when
        // their value changes rather than relying on the change event
        $s.parent()[ 0 ].value = JSON.stringify(sliderElem.noUiSlider.get())
        $s.parent().change()
        $s.data('changed', 'true')
      })
    }

    if (data.atts.noUiSlider_pipsOptions) {
      sliderElem.noUiSlider.pips(
        data.atts.noUiSlider_pipsOptions
      )
    }
  }

  template.autorun(setup)
}
<commit_msg>remove debugger<commit_after>/* global AutoForm, Template */

import noUiSlider from 'nouislider'
import 'nouislider/distribute/nouislider.css'
import './autoform-nouislider.html'

AutoForm.addInputType('noUiSlider', {
  template: 'afNoUiSlider',
  valueOut: function () {
    const slider = this.find('.nouislider')[ 0 ]
    const sliderData = slider.noUiSlider.get()

    if (this.attr('data-type') === 'Object') {
      const isDecimal = this.closest('.at-nouislider').data('decimal')
      const parser = (isDecimal) ? parseFloat : parseInt
      const first = parser.call(null, sliderData[ 0 ])
      const second = parser.call(null, sliderData[ 1 ])
      return {
        lower: first > second ? second : first,
        upper: first > second ? first : second
      }
    } else {
      return sliderData
    }
  }
})

const omit = (object, ...keys) => {
  const copy = Object.assign({}, object)
  keys.forEach(key => {
    delete copy[key]
  })
  return copy
}

const pick = (object, ...keys) => {
  const tmp = {}
  keys.forEach(key => {
    tmp[key] = object[key]
  })
  return tmp
}

const exists = x => typeof x !== 'undefined' && x !== null

const merge = (...objects) => {
  const tmp = {}
  objects.forEach(object => {
    Object.keys(object).forEach(key => {
      const value = object[key]
      if (exists(value)) {
        tmp[key] = value
      }
    })
  })
  return tmp
}

Template.afNoUiSlider.helpers({
  atts: function () {
    const data = Template.currentData() // get data reactively
    const atts = Object.assign({}, data.atts)
    atts[ 'data-type' ] = data.schemaType.name || 'Object' // fallback if type is a Schema and name becomes undefined
    if (atts[ 'class' ]) {
      atts[ 'class' ] += ' at-nouislider'
    } else {
      atts[ 'class' ] = 'at-nouislider'
    }

    atts.doLabels = (atts.labelLeft || atts.labelRight)
    atts[ 'data-decimal' ] = data.decimal

    return omit(atts, 'noUiSliderOptions', 'noUiSlider_pipsOptions')
  }
})

const calculateOptions = function (data) {
  const schemaMinMax = pick(data, 'max', 'min')
  const autoformOptions = pick(data.atts || {}, 'max', 'min', 'step', 'start', 'range')
  const noUiSliderOptions = (data.atts || {}).noUiSliderOptions
  const options = merge(schemaMinMax, autoformOptions, noUiSliderOptions)

  // Adjust data initialization based on schema type
  if (options.start === undefined) {
    if (data.schemaType.name === 'Object') {
      if (data.value && data.value.lower) {
        options.start = [
          data.value.lower,
          data.value.upper
        ]
      } else {
        options.start = [
          typeof data.min === 'number' ? data.min : 0,
          typeof data.max === 'number' ? data.max : 100
        ]
      }
      options.connect = true
    } else {
      options.start = data.value || 0
    }
  }

  if (options.range === undefined) {
    options.range = {
      min: typeof options.min === 'number' ? options.min : 0,
      max: typeof options.max === 'number' ? options.max : 100
    }
  }

  delete options.min
  delete options.max

  // default step to 1 if not otherwise defined
  if (options.step === undefined) {
    options.step = 1
  }

  return options
}

Template.afNoUiSlider.rendered = function () {
  const template = this
  const $s = template.$('.nouislider')

  const setup = function (c) {
    const data = Template.currentData() // get data reactively
    const options = calculateOptions(data)
    const sliderElem = $s[ 0 ]

    if (sliderElem.noUiSlider) {
      sliderElem.noUiSlider.updateOptions(options, true)
    } else {
      noUiSlider.create(sliderElem, options)
    }

    if (c.firstRun) {
      sliderElem.noUiSlider.on('slide', function () {
        // This is a trick to fool some logic in AutoForm that makes
        // sure values have actually changed on whichever element
        // emits a change event. Eventually AutoForm will give
        // input types the control of indicating exactly when
        // their value changes rather than relying on the change event
        $s.parent()[ 0 ].value = JSON.stringify(sliderElem.noUiSlider.get())
        $s.parent().change()
        $s.data('changed', 'true')
      })
    }

    if (data.atts.noUiSlider_pipsOptions) {
      sliderElem.noUiSlider.pips(
        data.atts.noUiSlider_pipsOptions
      )
    }
  }

  template.autorun(setup)
}
<|endoftext|>


<commit_before>/*
 * grunt-csscss
 */

'use strict';

module.exports = function(grunt) {

  grunt.registerMultiTask('csscss', 'CSSCSS redundancy analyzer.', function() {
    /**
     * Retrieves defined options.
     */
    var options = this.options();
    grunt.verbose.writeflags(options, 'Options');

    /**
     * Flag for whether any duplicates were found by CSSCSS.
     */
    var hasDuplicates = false;

    var done = this.async();

    var args = [];

    /**
     * Outputs the rules that have been matched.
     */
    if (options.verbose) {
      args.push('-v');
    }

    /**
     * Flag indicating whether the output should be colorized. This is true by
     * default.
     */
    if (options.colorize === false) {
      args.push('--no-color');
    }

    /**
     * Enables Compass extensions when parsing Sass files. This argument is not
     * set if the compassConfig property has been defined.
     */
    if (options.compass && !options.compassConfig) {
      args.push('--compass');
    }

    /**
     * Enables Compass extensions when parsing Sass files and specifies the path
     * to the config file.
     * The compassConfig option is deprecated and will be removed in a future release.
     * The require option should be used instead.
     */
    if (options.compassConfig && !options.require) {
      args.push('--compass');
      args.push('--require');
      args.push(options.compassConfig);

      grunt.log.writeln('WARNING: compassConifg is DEPRECATED, please use the "require" option');
    }

    /**
     * Enables Compass extensions when parsing Sass files and specifies the path
     * to the config file.
     */
    if (options.require) {
      args.push('--compass');
      args.push('--require');
      args.push(options.require);
    }

    /**
     * Returns analysis in JSON format.
     */
    if (options.outputJson) {
      args.push('-j');
    }

    /**
     * Sets the minimum number of rules before a match is found.
     */
    if (options.minMatch) {
      args.push('-n');
      args.push(options.minMatch);
    }

    /**
     * Sets which properties should be ignored.
     */
    if (options.ignoreProperties) {
      args.push('--ignore-properties');
      args.push(options.ignoreProperties);
    }

    /**
     * Sets whether Sass mixins should be ignored.
     */
    if (options.ignoreSassMixins) {
      args.push('--ignore-sass-mixins');
    }

    /**
     * Sets which selectors should be ignored.
     */
    if (options.ignoreSelectors) {
      args.push('--ignore-selectors');
      args.push(options.ignoreSelectors);
    }

    /**
     * Sets whether parser errors should be outputted.
     */
    if (options.showParserErrors) {
      args.push('--show-parser-errors');
    }

    /**
     * Sets whether shorthand should be matched.
     */
    if (options.shorthand === false) {
      args.push('--no-match-shorthand');
    }

    grunt.util.async.forEachSeries(this.data.src, function(f, next) {

      /**
       * adds the file path as the final argument, this goes into a new array so
       * the file doesn't get used in the next iteration.
       */
      var cmdArgs = args.concat([f]);

      /**
       * Outputs the file that is being analysed.
       */
      grunt.log.writeln(f);
      grunt.verbose.writeln('csscss ' + cmdArgs.join(' '));

      /**
       * Executes the csscss command.
       */
      var child = grunt.util.spawn({
        cmd: 'csscss',
        args: cmdArgs
      }, function(error, result, code) {
        if (code === 127) {
          return grunt.warn('Ruby and csscss have to be installed and in your PATH for this task to run.');
        }

        next(error);
      });

      /**
       * displays the output and error streams via the parent process.
       */
      child.stdout.on('data', function(buf) {
        var output = String(buf);
        grunt.log.writeln(output);

        /**
         * When outputting JSON from CSSCSS an empty array will be outputted, this
         * should be ignored and shouldn't cause the grunt task to fail if no other
         * duplicates are found.
         */
        if (!(options.outputJson && JSON.parse(output).length === 0)) {
          hasDuplicates = true;
        }
      });

      child.stderr.on('data', function(buf) {
        grunt.log.writeln(String(buf));
      });

    }, function () {

      /**
       * If instructed to fail when a match happens and matches found lets fail
       * the grunt build.
       */
      if (options.failWhenDuplicates && hasDuplicates) {
        grunt.fail.warn("Failed due to matches found by CSSCSS.");
      }

      done();
    });

  });

};
<commit_msg>fixed typo in deprecated message when using compassConfig option. (#4)<commit_after>/*
 * grunt-csscss
 */

'use strict';

module.exports = function(grunt) {

  grunt.registerMultiTask('csscss', 'CSSCSS redundancy analyzer.', function() {
    /**
     * Retrieves defined options.
     */
    var options = this.options();
    grunt.verbose.writeflags(options, 'Options');

    /**
     * Flag for whether any duplicates were found by CSSCSS.
     */
    var hasDuplicates = false;

    var done = this.async();

    var args = [];

    /**
     * Outputs the rules that have been matched.
     */
    if (options.verbose) {
      args.push('-v');
    }

    /**
     * Flag indicating whether the output should be colorized. This is true by
     * default.
     */
    if (options.colorize === false) {
      args.push('--no-color');
    }

    /**
     * Enables Compass extensions when parsing Sass files. This argument is not
     * set if the compassConfig property has been defined.
     */
    if (options.compass && !options.compassConfig) {
      args.push('--compass');
    }

    /**
     * Enables Compass extensions when parsing Sass files and specifies the path
     * to the config file.
     * The compassConfig option is deprecated and will be removed in a future release.
     * The require option should be used instead.
     */
    if (options.compassConfig && !options.require) {
      args.push('--compass');
      args.push('--require');
      args.push(options.compassConfig);

      grunt.log.writeln('WARNING: compassConfig is DEPRECATED, please use the "require" option.');
    }

    /**
     * Enables Compass extensions when parsing Sass files and specifies the path
     * to the config file.
     */
    if (options.require) {
      args.push('--compass');
      args.push('--require');
      args.push(options.require);
    }

    /**
     * Returns analysis in JSON format.
     */
    if (options.outputJson) {
      args.push('-j');
    }

    /**
     * Sets the minimum number of rules before a match is found.
     */
    if (options.minMatch) {
      args.push('-n');
      args.push(options.minMatch);
    }

    /**
     * Sets which properties should be ignored.
     */
    if (options.ignoreProperties) {
      args.push('--ignore-properties');
      args.push(options.ignoreProperties);
    }

    /**
     * Sets whether Sass mixins should be ignored.
     */
    if (options.ignoreSassMixins) {
      args.push('--ignore-sass-mixins');
    }

    /**
     * Sets which selectors should be ignored.
     */
    if (options.ignoreSelectors) {
      args.push('--ignore-selectors');
      args.push(options.ignoreSelectors);
    }

    /**
     * Sets whether parser errors should be outputted.
     */
    if (options.showParserErrors) {
      args.push('--show-parser-errors');
    }

    /**
     * Sets whether shorthand should be matched.
     */
    if (options.shorthand === false) {
      args.push('--no-match-shorthand');
    }

    grunt.util.async.forEachSeries(this.data.src, function(f, next) {

      /**
       * adds the file path as the final argument, this goes into a new array so
       * the file doesn't get used in the next iteration.
       */
      var cmdArgs = args.concat([f]);

      /**
       * Outputs the file that is being analysed.
       */
      grunt.log.writeln(f);
      grunt.verbose.writeln('csscss ' + cmdArgs.join(' '));

      /**
       * Executes the csscss command.
       */
      var child = grunt.util.spawn({
        cmd: 'csscss',
        args: cmdArgs
      }, function(error, result, code) {
        if (code === 127) {
          return grunt.warn('Ruby and csscss have to be installed and in your PATH for this task to run.');
        }

        next(error);
      });

      /**
       * displays the output and error streams via the parent process.
       */
      child.stdout.on('data', function(buf) {
        var output = String(buf);
        grunt.log.writeln(output);

        /**
         * When outputting JSON from CSSCSS an empty array will be outputted, this
         * should be ignored and shouldn't cause the grunt task to fail if no other
         * duplicates are found.
         */
        if (!(options.outputJson && JSON.parse(output).length === 0)) {
          hasDuplicates = true;
        }
      });

      child.stderr.on('data', function(buf) {
        grunt.log.writeln(String(buf));
      });

    }, function () {

      /**
       * If instructed to fail when a match happens and matches found lets fail
       * the grunt build.
       */
      if (options.failWhenDuplicates && hasDuplicates) {
        grunt.fail.warn("Failed due to matches found by CSSCSS.");
      }

      done();
    });

  });

};
<|endoftext|>


<commit_before>/*
 * grunt-contrib-cssmin
 * http://gruntjs.com/
 *
 * Copyright (c) 2014 Tim Branyen, contributors
 * Licensed under the MIT license.
 */

'use strict';
var path = require('path');
var CleanCSS = require('clean-css');
var chalk = require('chalk');
var maxmin = require('maxmin');

module.exports = function(grunt) {
  var minify = function(source, options) {
    try {
      return new CleanCSS(options).minify(source);
    } catch (err) {
      grunt.log.error(err);
      grunt.fail.warn('CSS minification failed.');
    }
  };

  grunt.registerMultiTask('cssmin', 'Minify CSS', function() {
    var options = this.options({
      report: 'min'
    });

    this.files.forEach(function(file) {
      var valid = file.src.filter(function(filepath) {
        // Warn on and remove invalid source files (if nonull was set).
        if (!grunt.file.exists(filepath)) {
          grunt.log.warn('Source file ' + chalk.cyan(filepath) + ' not found.');
          return false;
        } else {
          return true;
        }
      });

      var max = '';
      var min = valid.map(function(file) {
        var src = grunt.file.read(file);
        max += src;
        options.relativeTo = path.dirname(file);
        return minify(src, options);
      }).join('');

      if (min.length === 0) {
        return grunt.log.warn('Destination not written because minified CSS was empty.');
      }

      if (options.banner) {
        min = options.banner + grunt.util.linefeed + min;
      }

      grunt.file.write(file.dest, min);

      grunt.log.writeln('File ' + chalk.cyan(file.dest) + ' created: ' + maxmin(max, min, options.report === 'gzip'));
    });
  });
};
<commit_msg>use verbose.writeln for report<commit_after>/*
 * grunt-contrib-cssmin
 * http://gruntjs.com/
 *
 * Copyright (c) 2014 Tim Branyen, contributors
 * Licensed under the MIT license.
 */

'use strict';
var path = require('path');
var CleanCSS = require('clean-css');
var chalk = require('chalk');
var maxmin = require('maxmin');

module.exports = function(grunt) {
  var minify = function(source, options) {
    try {
      return new CleanCSS(options).minify(source);
    } catch (err) {
      grunt.log.error(err);
      grunt.fail.warn('CSS minification failed.');
    }
  };

  grunt.registerMultiTask('cssmin', 'Minify CSS', function() {
    var options = this.options({
      report: 'min'
    });

    this.files.forEach(function(file) {
      var valid = file.src.filter(function(filepath) {
        // Warn on and remove invalid source files (if nonull was set).
        if (!grunt.file.exists(filepath)) {
          grunt.log.warn('Source file ' + chalk.cyan(filepath) + ' not found.');
          return false;
        } else {
          return true;
        }
      });

      var max = '';
      var min = valid.map(function(file) {
        var src = grunt.file.read(file);
        max += src;
        options.relativeTo = path.dirname(file);
        return minify(src, options);
      }).join('');

      if (min.length === 0) {
        return grunt.log.warn('Destination not written because minified CSS was empty.');
      }

      if (options.banner) {
        min = options.banner + grunt.util.linefeed + min;
      }

      grunt.file.write(file.dest, min);

      grunt.verbose.writeln('File ' + chalk.cyan(file.dest) + ' created: ' + maxmin(max, min, options.report === 'gzip'));
    });
  });
};
<|endoftext|>


<commit_before>'use strict';

var paths = require('./_paths');
var gulp = require('gulp');
var filter = require('gulp-filter');
var sass = require('gulp-ruby-sass');
var browserSync = require('browser-sync');
var reload = browserSync.reload;

gulp.task('ie-css', ['clean-iecss'], function() {
  return gulp
    .src(paths.ie_styles)
    .pipe(sass({
      lineNumbers: true,
      style: 'compact'
    }))
    .on('error', function (err) { console.log(err.message); })
    .pipe(gulp.dest(paths.dest + 'ie-stylesheets/'))
    .pipe(filter('**/*.css'))  // don't react to .map files when reloading browser
    .pipe(reload({ stream:true }));
});
<commit_msg>Fix Guild IE CSS loadPath<commit_after>'use strict';

var paths = require('./_paths');
var gulp = require('gulp');
var filter = require('gulp-filter');
var sass = require('gulp-ruby-sass');
var browserSync = require('browser-sync');
var reload = browserSync.reload;

gulp.task('ie-css', ['clean-iecss'], function() {
  return gulp
    .src(paths.ie_styles)
    .pipe(sass({
      lineNumbers: true,
      style: 'compact',
      loadPath: 'node_modules/govuk_frontend_toolkit/' // add node module toolkit path
    }))
    .on('error', function (err) { console.log(err.message); })
    .pipe(gulp.dest(paths.dest + 'ie-stylesheets/'))
    .pipe(filter('**/*.css'))  // don't react to .map files when reloading browser
    .pipe(reload({ stream:true }));
});
<|endoftext|>


<commit_before>/*
 * grunt-contrib-rename (unofficial)
 * https://github.com/jasonlam604/grunt-contrib-rename
 *
 * Copyright (c) 2013 Jason Lam
 * Licensed under the MIT license.
 * https://github.com/jasonlam604/grunt-contrib-rename/blob/master/LICENSE-MIT
 */
'use strict';

var fs = require('fs');

module.exports = function(grunt) {

  grunt.registerMultiTask('rename', 'Rename file', function() {

    var options = this.options({
      force: false
    });

    grunt.verbose.writeflags(options, 'Options');

    this.files.forEach(function(filePair) {

      filePair.src.forEach(function(src) {

				if (!grunt.file.exists(String(filePair.src))) {
          grunt.log.writeln('Cannot rename non-existent file.');
				} else {

					if (fs.statSync(String(filePair.src)).isDirectory()) {
						grunt.log.writeln('Renaming Directory ' + filePair.src + ' -> ' + filePair.dest);
					} else {
						grunt.log.writeln('Renaming File ' + filePair.src + ' -> ' + filePair.dest);
					}

					fs.renameSync(String(filePair.src), String(filePair.dest), function (err) {
						if (err) {
							grunt.log.error();
							grunt.verbose.error();
							grunt.fail.warn('Rename operation failed.');
						}
					});
				}
      });
    });
  });
};<commit_msg>Update comment to reflect new package name<commit_after>/*
 * grunt-rename-util
 * https://github.com/jasonlam604/grunt-rename-util
 *
 * Copyright (c) 2013 Jason Lam
 * Licensed under the MIT license.
 * https://github.com/jasonlam604/grunt-rename-util/blob/master/LICENSE-MIT
 */
'use strict';

var fs = require('fs');

module.exports = function(grunt) {

  grunt.registerMultiTask('rename', 'Rename file', function() {

    var options = this.options({
      force: false
    });

    grunt.verbose.writeflags(options, 'Options');

    this.files.forEach(function(filePair) {

      filePair.src.forEach(function(src) {

				if (!grunt.file.exists(String(filePair.src))) {
          grunt.log.writeln('Cannot rename non-existent file.');
				} else {

					if (fs.statSync(String(filePair.src)).isDirectory()) {
						grunt.log.writeln('Renaming Directory ' + filePair.src + ' -> ' + filePair.dest);
					} else {
						grunt.log.writeln('Renaming File ' + filePair.src + ' -> ' + filePair.dest);
					}

					fs.renameSync(String(filePair.src), String(filePair.dest), function (err) {
						if (err) {
							grunt.log.error();
							grunt.verbose.error();
							grunt.fail.warn('Rename operation failed.');
						}
					});
				}
      });
    });
  });
};<|endoftext|>


<commit_before><commit_msg>Update namespace<commit_after><|endoftext|>


<commit_before>'use strict';

/**
 * Defines a passport cas strategy.
 *
 * @module passport/cas/CasStrategy
 * @ignore
 */

const url = require('url');
const Strategy = require('passport-strategy');

/** @ignore */
class CasStrategy extends Strategy {

  /**
   * Defines a passport cas strategy to authenticate requests using a cas server.
   *
   * @example
   * // Configuration example
   * // {
   * //   "service": "https://my-application-service-host", // Application service
   * //   "url": "https://my-cas-server-host:8443/cas", // CAS server url
   * //   "version": "3", // CAS protocol version (could be 1, 2, 3)
   * //   "certificate": "/home/test/cas.crt" // CAS full chain certificate
   * // }
   *
   * @param {Object} options The list of cas strategy options
   * @param {String} options.service The service to use to authenticate to the CAS server
   * @param {String} options.version The version of the CAS server
   * @param {String} options.url The url of the CAS server
   * @param {String} options.certificate The absolute path to the CAS server certificate
   * @param {Function} verify Function to call to validate user as returnd by CAS
   */
  constructor(options, verify) {
    super();

    if (!options)
      throw new Error('Missing passport cas strategy options');

    if (!options.service)
      throw new Error('Missing cas service for cas strategy');

    const version = options.version ? options.version : '3';
    let cas = null;

    try {
      const CAS = require(`./CAS${version}.js`);
      cas = new CAS(options);
    } catch (error) {
      throw new Error(`This version of cas is not implemented : ${error.message}`);
    }

    Object.defineProperties(this,

      /** @lends module:passport/cas/CasStrategy~CasStrategy */
      {

        /**
         * Passport cas strategy name.
         *
         * @type {String}
         * @instance
         * @readonly
         */
        name: {
          value: 'cas'
        },

        /**
         * Application service registered in CAS.
         *
         * @type {String}
         * @instance
         * @readonly
         */
        service: {
          value: options.service
        },

        /**
         * CAS protocol version.
         *
         * @type {String}
         * @instance
         * @readonly
         */
        version: {
          value: version
        },

        /**
         * CAS client implementation.
         *
         * @type {module:passport/cas/CAS~CAS}
         * @instance
         * @readonly
         */
        cas: {
          value: cas
        },

        /**
         * Passport verify function to call to validate user returned by CAS.
         *
         * @type {Function}
         * @instance
         * @readonly
         */
        verify: {
          value: verify
        },

        /**
         * URI to return to after logging out.
         *
         * @type {Function}
         * @instance
         * @readonly
         */
        logoutUri: {
          value: options.logoutUri
        }

      }

    );

  }

  /**
   * Authenticates a request using cas.
   *
   * @param {Object} request The express authenticate request
   * @param {Object} options Passport authenticate options such as redirects
   */
  authenticate(request, options) {
    if (!request._passport) return this.error(new Error('passport.initialize() middleware not in use'));

    const self = this;
    const requestUrl = new url.URL(request.originalUrl || request.url, this.service);
    const serviceUrl = new url.URL(requestUrl.pathname, this.service);

    if (request.query && request.query.ticket) {

      // Got a ticket to validate

      // Interrogate cas to validate the ticket
      this.cas.validateTicket(url.format(serviceUrl), request.query.ticket).then((user) => {

        // Offer the possibility to verify the user returned by CAS server
        // before considering him authenticated
        if (self.verify) {
          return self.verify(user, (error, verifiedUser, info) => {
            if (error) return self.error(error);
            if (!verifiedUser) return self.fail(info);
            return self.success(verifiedUser, info);
          });
        } else
          return self.success(user);
      }).catch((error) => {
        const message = `Authentication failed with message : "${error.message}"`;
        process.logger.error(message);
        return self.fail(message);
      });

    } else {

      // No ticket, redirect to cas server login page

      // Build login url with service registered in cas server
      const casUrl = new url.URL(this.cas.getUrl());
      const loginUrl = new url.URL(`${casUrl.pathname}${this.cas.getLoginUri()}`, this.cas.getUrl());
      loginUrl.searchParams.append('service', url.format(serviceUrl));

      // Redirect to cas login page
      this.redirect(url.format(loginUrl), 307);
    }
  }

  /**
   * Logouts from cas.
   *
   * @param {Object} request The express logout request
   * @param {Object} request The express response
   */
  logout(request, response) {
    const serviceUrl = new url.URL(this.service);
    serviceUrl.pathname = '/';

    // Build login url with service registered in cas server
    const casUrl = new url.URL(this.cas.getUrl());
    const redirectUrl = new url.URL(this.logoutUri, serviceUrl);
    const logoutUrl = new url.URL(`${casUrl.pathname}${this.cas.getLogoutUri()}`, casUrl);
    logoutUrl.searchParams.append('service', url.format(serviceUrl));
    logoutUrl.searchParams.append('url', url.format(redirectUrl));

    // Logout from cas by redirecting to cas logout url
    response.statusCode = 307;
    response.setHeader('Location', url.format(logoutUrl));
    response.setHeader('Content-Length', '0');
    response.end();
  }

}

module.exports = CasStrategy;
<commit_msg>passport: fix CAS authentication when no sub path<commit_after>'use strict';

/**
 * Defines a passport cas strategy.
 *
 * @module passport/cas/CasStrategy
 * @ignore
 */

const url = require('url');
const Strategy = require('passport-strategy');

/** @ignore */
class CasStrategy extends Strategy {

  /**
   * Defines a passport cas strategy to authenticate requests using a cas server.
   *
   * @example
   * // Configuration example
   * // {
   * //   "service": "https://my-application-service-host", // Application service
   * //   "url": "https://my-cas-server-host:8443/cas", // CAS server url
   * //   "version": "3", // CAS protocol version (could be 1, 2, 3)
   * //   "certificate": "/home/test/cas.crt" // CAS full chain certificate
   * // }
   *
   * @param {Object} options The list of cas strategy options
   * @param {String} options.service The service to use to authenticate to the CAS server
   * @param {String} options.version The version of the CAS server
   * @param {String} options.url The url of the CAS server
   * @param {String} options.certificate The absolute path to the CAS server certificate
   * @param {Function} verify Function to call to validate user as returnd by CAS
   */
  constructor(options, verify) {
    super();

    if (!options)
      throw new Error('Missing passport cas strategy options');

    if (!options.service)
      throw new Error('Missing cas service for cas strategy');

    const version = options.version ? options.version : '3';
    let cas = null;

    try {
      const CAS = require(`./CAS${version}.js`);
      cas = new CAS(options);
    } catch (error) {
      throw new Error(`This version of cas is not implemented : ${error.message}`);
    }

    Object.defineProperties(this,

      /** @lends module:passport/cas/CasStrategy~CasStrategy */
      {

        /**
         * Passport cas strategy name.
         *
         * @type {String}
         * @instance
         * @readonly
         */
        name: {
          value: 'cas'
        },

        /**
         * Application service registered in CAS.
         *
         * @type {String}
         * @instance
         * @readonly
         */
        service: {
          value: options.service
        },

        /**
         * CAS protocol version.
         *
         * @type {String}
         * @instance
         * @readonly
         */
        version: {
          value: version
        },

        /**
         * CAS client implementation.
         *
         * @type {module:passport/cas/CAS~CAS}
         * @instance
         * @readonly
         */
        cas: {
          value: cas
        },

        /**
         * Passport verify function to call to validate user returned by CAS.
         *
         * @type {Function}
         * @instance
         * @readonly
         */
        verify: {
          value: verify
        },

        /**
         * URI to return to after logging out.
         *
         * @type {Function}
         * @instance
         * @readonly
         */
        logoutUri: {
          value: options.logoutUri
        }

      }

    );

  }

  /**
   * Authenticates a request using cas.
   *
   * @param {Object} request The express authenticate request
   * @param {Object} options Passport authenticate options such as redirects
   */
  authenticate(request, options) {
    if (!request._passport) return this.error(new Error('passport.initialize() middleware not in use'));

    const self = this;
    const requestUrl = new url.URL(request.originalUrl || request.url, this.service);
    const serviceUrl = new url.URL(requestUrl.pathname, this.service);

    if (request.query && request.query.ticket) {

      // Got a ticket to validate

      // Interrogate cas to validate the ticket
      this.cas.validateTicket(serviceUrl.href, request.query.ticket).then((user) => {

        // Offer the possibility to verify the user returned by CAS server
        // before considering him authenticated
        if (self.verify) {
          return self.verify(user, (error, verifiedUser, info) => {
            if (error) return self.error(error);
            if (!verifiedUser) return self.fail(info);
            return self.success(verifiedUser, info);
          });
        } else
          return self.success(user);
      }).catch((error) => {
        const message = `Authentication failed with message : "${error.message}"`;
        process.logger.error(message);
        return self.fail(message);
      });

    } else {

      // No ticket, redirect to cas server login page

      // Build login url with service registered in cas server
      const casUrl = new url.URL(this.cas.getUrl());
      const loginUrl = new url.URL(
        `${casUrl.pathname === '/' ? '' : casUrl.pathname}${this.cas.getLoginUri()}`,
        casUrl
      );

      loginUrl.searchParams.append('service', serviceUrl.href);

      // Redirect to cas login page
      this.redirect(loginUrl.href, 307);
    }
  }

  /**
   * Logouts from cas.
   *
   * @param {Object} request The express logout request
   * @param {Object} request The express response
   */
  logout(request, response) {
    const serviceUrl = new url.URL(this.service);
    serviceUrl.pathname = '/';

    // Build login url with service registered in cas server
    const casUrl = new url.URL(this.cas.getUrl());
    const redirectUrl = new url.URL(this.logoutUri, serviceUrl);
    const logoutUrl = new url.URL(
      `${casUrl.pathname === '/' ? '' : casUrl.pathname}${this.cas.getLogoutUri()}`,
      casUrl
    );
    logoutUrl.searchParams.append('service', serviceUrl.href);
    logoutUrl.searchParams.append('url', redirectUrl.href);

    // Logout from cas by redirecting to cas logout url
    response.statusCode = 307;
    response.setHeader('Location', logoutUrl.href);
    response.setHeader('Content-Length', '0');
    response.end();
  }

}

module.exports = CasStrategy;
<|endoftext|>


<commit_before>/**
 * bh-php
 * ======
 *
 * Собирает *bh.php*-файлы по deps'ам инклудами, сохраняет в виде `?.bh.php`.
 *
 * **Опции**
 *
 * * *String* **target** — Результирующий таргет. По умолчанию — `?.bh.php`.
 * * *String* **filesTarget** — files-таргет, на основе которого получается список исходных файлов
 *   (его предоставляет технология `files`). По умолчанию — `?.files`.
 * * *String* **sourceSuffixes** — суффиксы файлов, по которым строится `files`-таргет. По умолчанию — 'bh.php'.
 * * *String* **phpBootstrap** — Путь к библиотеке `bem/bh` относительно корня проекта. По умолчанию — `vendor/bem/bh/index.php`.
 *
 * **Пример**
 *
 * ```javascript
 * nodeConfig.addTech(require('enb-bh-php'));
 */

var util = require('../lib/util');

module.exports = require('enb/lib/build-flow').create()
    .name('bh.php')
    .target('target', '?.bh.php')
    .useFileList('bh.php')
    .builder(function(sourceFiles) {
        var node = this.node;

        var bhChunk = [
            '<?php',
            'require_once __DIR__ . "/' + (this._options.phpBootstrap || 'vendor/bem/bh/index.php') + '";',
            '$bh = new \\BEM\\BH();',
            '$bh->setOptions('  + util.innerPackData(this._options) + ');'
        ].join('\n');

        return [bhChunk].concat(sourceFiles.map(function(file) {
            return '\n$fn = include __DIR__ . "/' + node.relativePath(file.fullname) + '"; $fn($bh);';
        })).join('\n');
    })
    .createTech();
<commit_msg>experimental inlining php files into resulting one<commit_after>/**
 * bh-php
 * ======
 *
 * Собирает *bh.php*-файлы по deps'ам инклудами, сохраняет в виде `?.bh.php`.
 *
 * **Опции**
 *
 * * *String* **target** — Результирующий таргет. По умолчанию — `?.bh.php`.
 * * *String* **filesTarget** — files-таргет, на основе которого получается список исходных файлов
 *   (его предоставляет технология `files`). По умолчанию — `?.files`.
 * * *String* **sourceSuffixes** — суффиксы файлов, по которым строится `files`-таргет. По умолчанию — 'bh.php'.
 * * *String* **phpBootstrap** — Путь к библиотеке `bem/bh` относительно корня проекта. По умолчанию — `vendor/bem/bh/index.php`.
 *
 * **Пример**
 *
 * ```javascript
 * nodeConfig.addTech(require('enb-bh-php'));
 */

var util = require('../lib/util');
var fs = require('fs');

module.exports = require('enb/lib/build-flow').create()
    .name('bh.php')
    .target('target', '?.bh.php')
    .defineOption('phpBootstrap', 'vendor/bem/bh/index.php')
    .useFileList('bh.php')
    .builder(function(sourceFiles) {
        var node = this.node;
        var opts = this._options;

        var bhOpts = {};
        ['jsAttrName', 'jsAttrScheme', 'escapeContent'].forEach(function (opt) {
            if (opts.hasOwnProperty(opt)) bhOpts[opt] = opts[opt];
        });

        var bhChunk = [
            '<?php',
            opts.phpBootstrap !== false?
                'require_once __DIR__ . "/' + (opts.phpBootstrap) + '";'
                : '',
            '$bh = new \\BEM\\BH();',
            '$bh->setOptions('  + util.innerPackData(bhOpts) + ');'
        ];

        return bhChunk.concat(
                sourceFiles.map(readFile),
                [ 'return $bh;\n' ]
            ).join('\n');

        function readFile(file) {
            if (opts.devMode) {
                return '\n$fn = include __DIR__ . "/' + node.relativePath(file.fullname) + '"; $fn($bh);';
            }

            return [
                '// file: ' + node.relativePath(file.fullname),
                stripPhp(fs.readFileSync(file.fullname)),
                ''
            ].join('\n');
        }
    })
    .createTech();

function stripPhp (php) {
    // <?php\nreturn function ($bh) {\n(...)\n};\n?>\n
    return String(php).replace(/^(<\?(php)?\n\s*)?(return\s+function\s*\(\s*\$bh\s*\)\s*{\s*\n)?|\s*(\n\s*}\s*;)?\s*(\n\s*\?>\s*)?$/ig, '');
}
<|endoftext|>


<commit_before>define([
  'helpers',
  'plumbing'
], function(helpers, plumbing) {
  /**
   * @ngdoc overview
   * @name discussions
   * @description
   * Functions related to discussions
   *
   * {@link https://familysearch.org/developers/docs/api/resources#discussions FamilySearch API Docs}
   */

  var maybe = helpers.maybe; // shorthand

  var exports = {};

  /**
   * @ngdoc function
   * @name discussions.functions:getPersonDiscussionReferences
   * @function
   *
   * @description
   * Get references to discussions for a person
   * The response includes the following convenience function
   *
   * - `getIds()` - get an array of discussion ids from the response; pass the id into `getDiscussion` for more information
   *
   * {@link https://familysearch.org/developers/docs/api/tree/Person_Discussion_References_resource FamilySearch API Docs}
   *
   * {@link http://jsfiddle.net/DallanQ/kd39K/ editable example}
   *
   * @param {String} id of the person to read
   * @param {Object=} params currently unused
   * @param {Object=} opts options to pass to the http function specified during init
   * @return {Object} promise for the response
   */
  exports.getPersonDiscussionReferences = function(id, params, opts) {
    return plumbing.get('/platform/tree/persons/'+encodeURI(id)+'/discussion-references', params, {}, opts,
      helpers.objectExtender({getIds: function() {
        return helpers.map(maybe(maybe(this.persons)[0])['discussion-references'], function(uri) {
          return uri.replace(/^.*\//, '');
        });
      }}));
  };

  /**
   * @ngdoc function
   * @name discussions.functions:getDiscussion
   * @function
   *
   * @description
   * Get information about a discussion
   * The response includes the following convenience functions
   *
   * - `getId()` - discussion id
   * - `getTitle()` - title string
   * - `getDetails()` - details string
   * - `getNumberOfComments()` - number of comments
   *
   * {@link https://familysearch.org/developers/docs/api/discussions/Discussion_resource FamilySearch API Docs}
   *
   * {@link http://jsfiddle.net/DallanQ/FzWSu/ editable example}
   *
   * @param {String} id of the discussion to read
   * @param {Object=} params currently unused
   * @param {Object=} opts options to pass to the http function specified during init
   * @return {Object} promise for the response
   */
  exports.getDiscussion = function(id, params, opts) {
    return plumbing.get('/platform/discussions/discussions/'+encodeURI(id), params, {}, opts,
      helpers.objectExtender(discussionConvenienceFunctions));
  };

  var discussionConvenienceFunctions = {
    getId:               function() { return maybe(maybe(this.discussions)[0]).id; },
    getTitle:            function() { return maybe(maybe(this.discussions)[0]).title; },
    getDetails:          function() { return maybe(maybe(this.discussions)[0]).details; },
    getNumberOfComments: function() { return maybe(maybe(this.discussions)[0]).numberOfComments; }
  };

  /**
   * @ngdoc function
   * @name discussions.functions:getComments
   * @function
   *
   * @description
   * Get comments for a discussion
   * The response includes the following convenience function
   *
   * - `getComments()` - get the array of comments from the response; each comment has an `id` and `text`
   *
   * {@link https://familysearch.org/developers/docs/api/discussions/Comments_resource FamilySearch API Docs}
   *
   * {@link http://jsfiddle.net/DallanQ// editable example}
   *
   * @param {String} id of the discussion to read
   * @param {Object=} params currently unused
   * @param {Object=} opts options to pass to the http function specified during init
   * @return {Object} promise for the response
   */
  exports.getComments = function(id, params, opts) {
    return plumbing.get('/platform/discussions/discussions/'+encodeURI(id)+'/comments', params, {}, opts,
      helpers.objectExtender({getComments: function() { return maybe(maybe(this.discussions)[0]).comments || []; }}));
  };

  return exports;
});
<commit_msg>add jsFiddle for getComments<commit_after>define([
  'helpers',
  'plumbing'
], function(helpers, plumbing) {
  /**
   * @ngdoc overview
   * @name discussions
   * @description
   * Functions related to discussions
   *
   * {@link https://familysearch.org/developers/docs/api/resources#discussions FamilySearch API Docs}
   */

  var maybe = helpers.maybe; // shorthand

  var exports = {};

  /**
   * @ngdoc function
   * @name discussions.functions:getPersonDiscussionReferences
   * @function
   *
   * @description
   * Get references to discussions for a person
   * The response includes the following convenience function
   *
   * - `getIds()` - get an array of discussion ids from the response; pass the id into `getDiscussion` for more information
   *
   * {@link https://familysearch.org/developers/docs/api/tree/Person_Discussion_References_resource FamilySearch API Docs}
   *
   * {@link http://jsfiddle.net/DallanQ/kd39K/ editable example}
   *
   * @param {String} id of the person to read
   * @param {Object=} params currently unused
   * @param {Object=} opts options to pass to the http function specified during init
   * @return {Object} promise for the response
   */
  exports.getPersonDiscussionReferences = function(id, params, opts) {
    return plumbing.get('/platform/tree/persons/'+encodeURI(id)+'/discussion-references', params, {}, opts,
      helpers.objectExtender({getIds: function() {
        return helpers.map(maybe(maybe(this.persons)[0])['discussion-references'], function(uri) {
          return uri.replace(/^.*\//, '');
        });
      }}));
  };

  /**
   * @ngdoc function
   * @name discussions.functions:getDiscussion
   * @function
   *
   * @description
   * Get information about a discussion
   * The response includes the following convenience functions
   *
   * - `getId()` - discussion id
   * - `getTitle()` - title string
   * - `getDetails()` - details string
   * - `getNumberOfComments()` - number of comments
   *
   * {@link https://familysearch.org/developers/docs/api/discussions/Discussion_resource FamilySearch API Docs}
   *
   * {@link http://jsfiddle.net/DallanQ/FzWSu/ editable example}
   *
   * @param {String} id of the discussion to read
   * @param {Object=} params currently unused
   * @param {Object=} opts options to pass to the http function specified during init
   * @return {Object} promise for the response
   */
  exports.getDiscussion = function(id, params, opts) {
    return plumbing.get('/platform/discussions/discussions/'+encodeURI(id), params, {}, opts,
      helpers.objectExtender(discussionConvenienceFunctions));
  };

  var discussionConvenienceFunctions = {
    getId:               function() { return maybe(maybe(this.discussions)[0]).id; },
    getTitle:            function() { return maybe(maybe(this.discussions)[0]).title; },
    getDetails:          function() { return maybe(maybe(this.discussions)[0]).details; },
    getNumberOfComments: function() { return maybe(maybe(this.discussions)[0]).numberOfComments; }
  };

  /**
   * @ngdoc function
   * @name discussions.functions:getComments
   * @function
   *
   * @description
   * Get comments for a discussion
   * The response includes the following convenience function
   *
   * - `getComments()` - get the array of comments from the response; each comment has an `id` and `text`
   *
   * {@link https://familysearch.org/developers/docs/api/discussions/Comments_resource FamilySearch API Docs}
   *
   * {@link http://jsfiddle.net/DallanQ/b56hz/ editable example}
   *
   * @param {String} id of the discussion to read
   * @param {Object=} params currently unused
   * @param {Object=} opts options to pass to the http function specified during init
   * @return {Object} promise for the response
   */
  exports.getComments = function(id, params, opts) {
    return plumbing.get('/platform/discussions/discussions/'+encodeURI(id)+'/comments', params, {}, opts,
      helpers.objectExtender({getComments: function() { return maybe(maybe(this.discussions)[0]).comments || []; }}));
  };

  return exports;
});
<|endoftext|>