<commit_before>// Copyright 2012 Google Inc.
//
// 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.

var Range = ace.require('ace/range').Range;
var EditSession = ace.require('ace/edit_session').EditSession;
var UndoManager = ace.require('ace/undomanager').UndoManager;

// A "tab" represents both an open tab (which may be associated with a
// file), or a "shelved" file, with contents but not an active editor.
function Tab(workspace) {
    this.workspace = workspace;
}

Tab.prototype.attachui = function(tabmenuelement, contentid) {
    this.tabmenuelement = tabmenuelement;
    this.contentid = contentid;
}

Tab.prototype.show = function() {
    document.getElementById(this.contentid).style.display = "block";
    if (this.session) {
        document.getElementById("editor").style.display = "block";
        this.workspace.editor.setSession(this.session);
        this.workspace.editor.resize();
        this.workspace.editor.focus();
    }
    this.tabmenuelement.className = "active";
}

Tab.prototype.hide = function() {
    document.getElementById(this.contentid).style.display = "none";
    if (this.session) {
        document.getElementById("editor").style.display = "none";
    }
   this.tabmenuelement.className = "";
}

Tab.prototype.shelve = function() {
    if (this.session) {
        this.contents = this.session.getValue();
    }
    this.session = undefined;
    this.tabmenuelement = undefined;
    this.contentid = undefined;
}

Tab.prototype.unshelve = function() {
    this.session.setValue(this.contents);
    this.contents = undefined;
}

// Get file contents of the tab (assuming it exists)
Tab.prototype.getcontents = function() {
    if (this.session) {
        return this.session.getValue();
    } else {
        return this.contents;
    }
}

Tab.prototype.setcontents = function(contents) {
    if (this.original === undefined) {
        this.original = contents;
    }
    if (this.session) {
        this.session.setValue(contents);
    } else {
        this.contents = contents;
    }
}

function Workspace() {
    // The tabs array only contains ui-attached tabs (not shelved files)
    this.tabs = [];
    // All files (whether mapped to ui or shelved)
    this.filemap = {};
    this.currenttab = null;
    this.tabcounter = 0;
    this.initmenus();
}

Workspace.prototype.initmenus = function() {
    var self = this;
    document.getElementById("menu-newtab").addEventListener('click',
        function (event) {
            self.selecttab(self.newtab('new tab'));
            self.finishmenuselect(event);
        });
    document.getElementById("menu-dirtab").addEventListener('click',
        function (event) {
            self.selecttab(self.newdirtab());
            self.finishmenuselect(event);
        });
    document.getElementById("menu-revert").addEventListener('click',
        function (event) {
            self.revert();
            self.finishmenuselect(event);
        });
}

Workspace.prototype.selecttab = function(tab) {
    if (this.currenttab) {
        if (tab === this.currenttab) {
            return;
        }
        this.currenttab.hide();
    }
    tab.show();
    this.currenttab = tab;
}

Workspace.prototype.newtab = function(tabname, tab) {
    var self = this;
    var tabmenu = document.getElementById("tabmenu");
    var li = document.createElement("li");
    tabmenu.appendChild(li);
    var a = document.createElement("a");
    li.appendChild(a);
    a.appendChild(document.createTextNode(tabname));
    a.href="#";
    a.addEventListener('click', function(event) {
        self.clicktab(event);
    });
    a2 = li.appendChild(document.createElement("a"));
    a2.href="#";
    a2.className = "close";
    a2.appendChild(document.createTextNode("×"));
    a2.addEventListener('click', function(event) {
        self.closetab(event);
    });
    this.tabcounter++;
    var contentid = "tab-" + this.tabcounter
    var div = document.getElementById("content")
        .appendChild(document.createElement("div"));
    div.id = contentid;
    div.style.display = "none";
    if (tab === undefined) {
        tab = new Tab(this);
    }
    tab.attachui(li, contentid);
    this.tabs.push(tab);
    return tab;
}

Workspace.prototype.neweditortab = function(tabname, tab) {
    var tab = this.newtab(tabname, tab);
    if (!tab.session) {
        if (this.editor) {
            // create a new session for an existing editor object
            tab.session = new EditSession('');
            tab.session.setUndoManager(new UndoManager());
        } else {
            this.editor = ace.edit("editor");
            tab.session = this.editor.getSession();
        }
    }
    return tab;
}

Workspace.prototype.loadfile = function(filename) {
    if (filename in this.filemap) {
        var tab = this.filemap[filename];
        console.log(tab);
        if (tab.session) {
            return tab;
        }
    }
    var tabname = filename
    var slashix = tabname.lastIndexOf('/');
    if (slashix >= 0) {
        tabname = tabname.substring(slashix + 1);
    }
    tab = this.neweditortab(tabname, tab);
    if (tab.contents !== undefined) {
        tab.unshelve();
        return tab;
    }
    this.filemap[filename] = tab;
    var x = new XMLHttpRequest();
    var url = '/git/' + filename;
    x.onreadystatechange = function() {
        if (x.readyState == 4) {
            var contents = x.responseText;
            tab.setcontents(contents);
        }
    };
    x.open('GET', url, true);
    x.send(null);
    return tab;
}

Workspace.prototype.deletetab = function(tab) {
    var ix = this.tabs.indexOf(tab);
    if (ix >= 0) {
        if (tab === this.currenttab) {
            if (ix > 0) {
                this.selecttab(this.tabs[ix - 1]);
            } else if (ix + 1 < this.tabs.length) {
                this.selecttab(this.tabs[ix + 1]);
            } else {
                tab.hide();
                this.currenttab = null;
            }
        }
        this.tabs.splice(ix, 1);
    }
    document.getElementById("tabmenu").removeChild(tab.tabmenuelement);
    document.getElementById("content").removeChild(
        document.getElementById(tab.contentid));
    tab.shelve();
}

Workspace.prototype.revert = function() {
    var tab = this.currenttab;
    if (tab.session && tab.original !== undefined) {
        tab.session.setValue(tab.original);
    }
}

Workspace.prototype.newdirtab = function() {
    var self = this;
    var tab = this.newtab('dir');
    var x = new XMLHttpRequest();
    var url = '/workspace/_dir';
    x.onreadystatechange = function() {
        if (x.readyState == 4) {
            self.populatedir(tab.contentid, JSON.parse(x.responseText));
        }
    }
    x.open('GET', url, true);
    x.send(null);
    return tab;
}

Workspace.prototype.populatedir = function(id, dir) {
    var self = this;
    var container = document.getElementById(id);
    // container.style.overflow = 'scroll';
    function rec(dir, prefix) {
        for (var i = 0; i < dir.length; i++) {
            if (typeof dir[i] == 'string') {
                var div = container.appendChild(document.createElement('div'));
                var a = div.appendChild(document.createElement('a'));
                a.href = '#';
                var fn = prefix + dir[i];
                a.appendChild(document.createTextNode(fn));
                (function (fn) {
                    a.addEventListener('click', function(event) {
                        self.selecttab(self.loadfile(fn));
                        event.preventDefault();
                    })
                })(fn);
            } else {
                rec(dir[i][1], prefix + dir[i][0] + '/');
            }
        }
    }
    rec(dir, '');
}

Workspace.prototype.finishmenuselect = function(event) {
    document.getElementById("nav").className = "off";
    window.setTimeout(function () {document.getElementById("nav").className = "";}, 50);
    event.preventDefault();
}

function foo() {
    var session = workspace.currenttab.session;
    range = new Range(1, 4, 1, 7);
    session.addMarker(range, "gh_error", "text", false);
    session.setAnnotations([{row:0, column:10, text:"annotation text", type:"warning"}]);
    return false;
}

// Finds a tab object from the li element of the tab
Workspace.prototype.findtab = function(element) {
    for (var i = 0; i < this.tabs.length; i++) {
        var tab = this.tabs[i];
        if (element === tab.tabmenuelement) {
            return tab;
        }
    }
    return null;
}

Workspace.prototype.clicktab = function(event) {
    var tab = this.findtab(event.target.parentNode);
    if (tab) {
        this.selecttab(tab);
    }
    event.preventDefault();
}

Workspace.prototype.closetab = function(event) {
    var tab = this.findtab(event.target.parentNode);
    if (tab) {
        this.deletetab(tab);
    }
    event.preventDefault();
}
<commit_msg>Workspace resize<commit_after>// Copyright 2012 Google Inc.
//
// 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.

var Range = ace.require('ace/range').Range;
var EditSession = ace.require('ace/edit_session').EditSession;
var UndoManager = ace.require('ace/undomanager').UndoManager;

// A "tab" represents both an open tab (which may be associated with a
// file), or a "shelved" file, with contents but not an active editor.
function Tab(workspace) {
    this.workspace = workspace;
}

Tab.prototype.attachui = function(tabmenuelement, contentid) {
    this.tabmenuelement = tabmenuelement;
    this.contentid = contentid;
}

Tab.prototype.show = function() {
    document.getElementById(this.contentid).style.display = "block";
    if (this.session) {
        document.getElementById("editor").style.display = "block";
        this.workspace.editor.setSession(this.session);
        // Note: editor requires resize, but this is done in Workspace.resize()
        this.workspace.editor.focus();
    }
    this.tabmenuelement.className = "active";
}

Tab.prototype.hide = function() {
    document.getElementById(this.contentid).style.display = "none";
    if (this.session) {
        document.getElementById("editor").style.display = "none";
    }
   this.tabmenuelement.className = "";
}

Tab.prototype.shelve = function() {
    if (this.session) {
        this.contents = this.session.getValue();
    }
    this.session = undefined;
    this.tabmenuelement = undefined;
    this.contentid = undefined;
}

Tab.prototype.unshelve = function() {
    this.session.setValue(this.contents);
    this.contents = undefined;
}

// Get file contents of the tab (assuming it exists)
Tab.prototype.getcontents = function() {
    if (this.session) {
        return this.session.getValue();
    } else {
        return this.contents;
    }
}

Tab.prototype.setcontents = function(contents) {
    if (this.original === undefined) {
        this.original = contents;
    }
    if (this.session) {
        this.session.setValue(contents);
    } else {
        this.contents = contents;
    }
}

function Workspace() {
    // The tabs array only contains ui-attached tabs (not shelved files)
    this.tabs = [];
    // All files (whether mapped to ui or shelved)
    this.filemap = {};
    this.currenttab = null;
    this.tabcounter = 0;
    this.initmenus();
    var self = this;
    window.addEventListener('resize', function() {
        self.resize();
    });
}

Workspace.prototype.initmenus = function() {
    var self = this;
    document.getElementById("menu-newtab").addEventListener('click',
        function (event) {
            self.selecttab(self.newtab('new tab'));
            self.finishmenuselect(event);
        });
    document.getElementById("menu-dirtab").addEventListener('click',
        function (event) {
            self.selecttab(self.newdirtab());
            self.finishmenuselect(event);
        });
    document.getElementById("menu-revert").addEventListener('click',
        function (event) {
            self.revert();
            self.finishmenuselect(event);
        });
}

Workspace.prototype.selecttab = function(tab) {
    if (this.currenttab) {
        if (tab === this.currenttab) {
            return;
        }
        this.currenttab.hide();
    }
    tab.show();
    this.currenttab = tab;
    this.resize();
}

Workspace.prototype.newtab = function(tabname, tab) {
    var self = this;
    var tabmenu = document.getElementById("tabmenu");
    var li = document.createElement("li");
    tabmenu.appendChild(li);
    var a = document.createElement("a");
    li.appendChild(a);
    a.appendChild(document.createTextNode(tabname));
    a.href="#";
    a.addEventListener('click', function(event) {
        self.clicktab(event);
    });
    a2 = li.appendChild(document.createElement("a"));
    a2.href="#";
    a2.className = "close";
    a2.appendChild(document.createTextNode("×"));
    a2.addEventListener('click', function(event) {
        self.closetab(event);
    });
    this.tabcounter++;
    var contentid = "tab-" + this.tabcounter
    var div = document.getElementById("content")
        .appendChild(document.createElement("div"));
    div.id = contentid;
    div.style.display = "none";
    if (tab === undefined) {
        tab = new Tab(this);
    }
    tab.attachui(li, contentid);
    this.tabs.push(tab);
    return tab;
}

Workspace.prototype.neweditortab = function(tabname, tab) {
    var tab = this.newtab(tabname, tab);
    if (!tab.session) {
        if (this.editor) {
            // create a new session for an existing editor object
            tab.session = new EditSession('');
            tab.session.setUndoManager(new UndoManager());
        } else {
            this.editor = ace.edit("editor");
            tab.session = this.editor.getSession();
        }
    }
    return tab;
}

Workspace.prototype.loadfile = function(filename) {
    if (filename in this.filemap) {
        var tab = this.filemap[filename];
        console.log(tab);
        if (tab.session) {
            return tab;
        }
    }
    var tabname = filename
    var slashix = tabname.lastIndexOf('/');
    if (slashix >= 0) {
        tabname = tabname.substring(slashix + 1);
    }
    tab = this.neweditortab(tabname, tab);
    if (tab.contents !== undefined) {
        tab.unshelve();
        return tab;
    }
    this.filemap[filename] = tab;
    var x = new XMLHttpRequest();
    var url = '/git/' + filename;
    x.onreadystatechange = function() {
        if (x.readyState == 4) {
            var contents = x.responseText;
            tab.setcontents(contents);
        }
    };
    x.open('GET', url, true);
    x.send(null);
    return tab;
}

Workspace.prototype.deletetab = function(tab) {
    var ix = this.tabs.indexOf(tab);
    if (ix >= 0) {
        if (tab === this.currenttab) {
            if (ix > 0) {
                this.selecttab(this.tabs[ix - 1]);
            } else if (ix + 1 < this.tabs.length) {
                this.selecttab(this.tabs[ix + 1]);
            } else {
                tab.hide();
                this.currenttab = null;
            }
        }
        this.tabs.splice(ix, 1);
    }
    document.getElementById("tabmenu").removeChild(tab.tabmenuelement);
    document.getElementById("content").removeChild(
        document.getElementById(tab.contentid));
    tab.shelve();
}

Workspace.prototype.revert = function() {
    var tab = this.currenttab;
    if (tab.session && tab.original !== undefined) {
        tab.session.setValue(tab.original);
    }
}

Workspace.prototype.newdirtab = function() {
    var self = this;
    var tab = this.newtab('dir');
    var x = new XMLHttpRequest();
    var url = '/workspace/_dir';
    x.onreadystatechange = function() {
        if (x.readyState == 4) {
            self.populatedir(tab.contentid, JSON.parse(x.responseText));
        }
    }
    x.open('GET', url, true);
    x.send(null);
    return tab;
}

Workspace.prototype.populatedir = function(id, dir) {
    var self = this;
    var container = document.getElementById(id);
    container.style.overflow = 'auto';
    function rec(dir, prefix) {
        for (var i = 0; i < dir.length; i++) {
            if (typeof dir[i] == 'string') {
                var div = container.appendChild(document.createElement('div'));
                var a = div.appendChild(document.createElement('a'));
                a.href = '#';
                var fn = prefix + dir[i];
                a.appendChild(document.createTextNode(fn));
                (function (fn) {
                    a.addEventListener('click', function(event) {
                        self.selecttab(self.loadfile(fn));
                        event.preventDefault();
                    })
                })(fn);
            } else {
                rec(dir[i][1], prefix + dir[i][0] + '/');
            }
        }
    }
    rec(dir, '');
}

Workspace.prototype.finishmenuselect = function(event) {
    document.getElementById("nav").className = "off";
    window.setTimeout(function () {document.getElementById("nav").className = "";}, 50);
    event.preventDefault();
}

function foo() {
    var session = workspace.currenttab.session;
    range = new Range(1, 4, 1, 7);
    session.addMarker(range, "gh_error", "text", false);
    session.setAnnotations([{row:0, column:10, text:"annotation text", type:"warning"}]);
    return false;
}

// Finds a tab object from the li element of the tab
Workspace.prototype.findtab = function(element) {
    for (var i = 0; i < this.tabs.length; i++) {
        var tab = this.tabs[i];
        if (element === tab.tabmenuelement) {
            return tab;
        }
    }
    return null;
}

Workspace.prototype.clicktab = function(event) {
    var tab = this.findtab(event.target.parentNode);
    if (tab) {
        this.selecttab(tab);
    }
    event.preventDefault();
}

Workspace.prototype.closetab = function(event) {
    var tab = this.findtab(event.target.parentNode);
    if (tab) {
        this.deletetab(tab);
    }
    event.preventDefault();
}

// Ideally, this should be done with flexboxes, but I can't figure it out
Workspace.prototype.resize = function() {
    var height = window.innerHeight;
    height -= document.getElementById("nav").offsetHeight;
    height -= document.getElementById("tabmenu").offsetHeight;
    console.log("height = " + height);
    var tab = this.currenttab;
    if (tab.session) {
        document.getElementById("editor").style.height = height + "px";
        this.editor.resize();
    } else {
        document.getElementById(tab.contentid).style.height = height + "px";
    }
}
<|endoftext|>


<commit_before>$( document ).ready( function () {
    $( '.xq-select > select' ).xqselect();
} );

/**
 * xqSelect v1.0 (https://github.com/exactquery/xq-select)
 *
 * @author  Aaron M Jones [aaron@jonesiscoding.com]
 * @licence MIT (https://github.com/exactquery/xq-select/blob/master/LICENSE)
 */
(function($){
    $.fn.xqselect = function(options){

        var base = this;

        /**
         * Loops through the given selectors to turn them into faux select boxes.
         *
         * @param sel   The selector for the object.
         */
        base.init = function(sel){

            base.options = $.extend({},$.fn.xqselect.defaultOptions, options);

            $( sel ).each( function ( index ) {
                base.RenderSelect(this, index);
            } );

        };

        /**
         * Renders the given select object as an bootstrap dropdown menu and button.
         *
         * @param select    An HTML select object.
         * @param index     The index of this select object within the HTML document.
         */
        base.RenderSelect = function(select, index) {
            var $select = $(select);
            var $wrapper  = $select.parents( base.options.wrapper );
            var $dropdown = $( base.options.templateFauxSelect );
            var target    = $select.prop('id');
            if ( typeof target == 'undefined' ) {
                target = 'xqSelect' + index;
                $select.attr( 'id', target );
            }
            $wrapper.off( 'click', '.xq-select-item' );
            $wrapper.find( '.xq-select-dropdown' ).remove();
            $wrapper.find( 'button' ).remove();
            var opts = $select.children();
            opts.each( function () {
                var $opt = $(this);
                if($opt.prop('tagName') == 'OPTION') {
                    var $ddItem = base.CreateOption( $opt, target );
                    $dropdown.append( $ddItem );
                } else if($opt.prop('tagName') == 'OPTGROUP') {
                    var $groupItem = base.CreateOptGroup( $opt );
                    $dropdown.append( $groupItem );
                    var gOpts = $opt.children();
                    gOpts.each(function() {
                        var $ddItem = base.CreateOption( $(this), target );
                        $dropdown.append( $ddItem );
                    });
                }
            } );
            $select.addClass( 'xq-select-enabled' );
            $wrapper.on( 'click','.xq-select-item', function() { base.onClick(this); } );
            $wrapper.append( $(base.option.templateFauxButton) );
            $wrapper.append( $dropdown );
        };

        /**
         * Creates an LI to match the given HTML select optgroup.  Note that the optgroups in the faux select are
         * not nested like HTML select optgroups and options.
         *
         * @param $grpObj               The optgroup from which to create the faux optgroup.
         * @returns {*|HTMLElement}     The faux optgroup as a list item with an anchor.
         */
        base.CreateOptGroup = function($grpObj) {
            var groupText = $grpObj.attr('label');
            var $groupItem = $( '

' );
            $groupItem.addClass( 'optgroup' );
            $groupItem.text( groupText );

            return $groupItem;
        };

        /**
         * Creates an LI to match the given HTML select option.
         *
         * @param $optObj               The option from which to create the faux option.
         * @param target                The ID of the faux select dropdown.
         * @returns {*|HTMLElement}     The faux option as a list item with an anchor.
         */
        base.CreateOption = function($optObj, target) {
            // Basics
            var $ddLink = $( '' );
            var $ddItem = $( '

' );
            $ddLink.attr( 'data-value', $optObj.val() );
            $ddLink.attr( 'data-target', '#' + target );
            $ddLink.attr( 'data-index', $optObj.index('#' + target + ' option') );
            $ddLink.addClass( 'xq-select-item' );
            if($optObj.prop('selected')) {
                $ddLink.addClass( 'selected' );
            }
            $ddLink.text( $optObj.text() );

            // Disabled
            if($optObj.attr('disabled')) {
                $ddLink.addClass( 'disabled' );
            }

            // Subtext
            if(typeof $optObj.attr('data-original-text') != "undefined") {
                $ddLink.text( $optObj.attr( 'data-original-text' ) );
            }
            var subText = $optObj.attr( 'data-subtext' );
            if(typeof subText != "undefined") {
                var $subText = $( '' );
                $subText.text( subText );
                $ddLink.append( $subText );
                if($optObj.text().length < subText.length) {
                    $optObj.attr( 'data-original-text', $optObj.text() );
                    $optObj.append( ' (' + subText + ')' );
                }
            }

            // Put it together & return it
            $ddItem.append( $ddLink );
            return $ddItem;
        };

        /**
         * Called when an item in the faux select is clicked on.
         *
         * @param obj
         */
        base.onClick = function(obj) {
            var target = $( obj ).attr( 'data-target' );
            $( target ).val( $( obj ).attr( 'data-value' ) );
            $( base.options.fauxOption ).removeClass( 'selected' );
            $( obj ).addClass( 'selected' );
            $(target).trigger('change');
        };

        // Run initializer
        base.init(this);

        return base;
    };

    $.fn.xqselect.defaultOptions = {
        fauxOption: '.xq-select-item',
        trigger : '.xq-select > select',
        wrapper      : '.xq-select',
        templateFauxSelect: '

',
        templateFauxButton: '<button type="button" class="btn dropdown-toggle" data-toggle="dropdown"> </button>'
    };
})(jQuery);

/**
 * Credit to Zoltán Tamási - http://stackoverflow.com/questions/21232685/bootstrap-drop-down-menu-auto-dropup-according-to-screen-position
 */
$(document).on("shown.bs.dropdown", ".xq-select", function () {
    // calculate the required sizes, spaces
    var $ul = $(this).children(".dropdown-menu");
    var $button = $(this).children(".dropdown-toggle");
    var ulOffset = $ul.offset();
    // how much space would be left on the top if the dropdown opened that direction
    var spaceUp = (ulOffset.top - $button.height() - $ul.height()) - $(window).scrollTop();
    // how much space is left at the bottom
    var spaceDown = $(window).scrollTop() + $(window).height() - (ulOffset.top + $ul.height());
    // switch to dropup only if there is no space at the bottom AND there is space at the top, or there isn't either but
    // it would be still better fit
    if (spaceDown < 0 && (spaceUp >= 0 || spaceUp > spaceDown))
        $(this).addClass("dropup");
}).on("hidden.bs.dropdown", ".dropdown", function() {
    // always reset after close
    $(this).removeClass("dropup");
});<commit_msg>Typo fixed.<commit_after>$( document ).ready( function () {
    $( '.xq-select > select' ).xqselect();
} );

/**
 * xqSelect v1.0 (https://github.com/exactquery/xq-select)
 *
 * @author  Aaron M Jones [aaron@jonesiscoding.com]
 * @licence MIT (https://github.com/exactquery/xq-select/blob/master/LICENSE)
 */
(function($){
    $.fn.xqselect = function(options){

        var base = this;

        /**
         * Loops through the given selectors to turn them into faux select boxes.
         *
         * @param sel   The selector for the object.
         */
        base.init = function(sel){

            base.options = $.extend({},$.fn.xqselect.defaultOptions, options);

            $( sel ).each( function ( index ) {
                base.RenderSelect(this, index);
            } );

        };

        /**
         * Renders the given select object as an bootstrap dropdown menu and button.
         *
         * @param select    An HTML select object.
         * @param index     The index of this select object within the HTML document.
         */
        base.RenderSelect = function(select, index) {
            var $select = $(select);
            var $wrapper  = $select.parents( base.options.wrapper );
            var $dropdown = $( base.options.templateFauxSelect );
            var target    = $select.prop('id');
            if ( typeof target == 'undefined' ) {
                target = 'xqSelect' + index;
                $select.attr( 'id', target );
            }
            $wrapper.off( 'click', '.xq-select-item' );
            $wrapper.find( '.xq-select-dropdown' ).remove();
            $wrapper.find( 'button' ).remove();
            var opts = $select.children();
            opts.each( function () {
                var $opt = $(this);
                if($opt.prop('tagName') == 'OPTION') {
                    var $ddItem = base.CreateOption( $opt, target );
                    $dropdown.append( $ddItem );
                } else if($opt.prop('tagName') == 'OPTGROUP') {
                    var $groupItem = base.CreateOptGroup( $opt );
                    $dropdown.append( $groupItem );
                    var gOpts = $opt.children();
                    gOpts.each(function() {
                        var $ddItem = base.CreateOption( $(this), target );
                        $dropdown.append( $ddItem );
                    });
                }
            } );
            $select.addClass( 'xq-select-enabled' );
            $wrapper.on( 'click','.xq-select-item', function() { base.onClick(this); } );
            $wrapper.append( $(base.options.templateFauxButton) );
            $wrapper.append( $dropdown );
        };

        /**
         * Creates an LI to match the given HTML select optgroup.  Note that the optgroups in the faux select are
         * not nested like HTML select optgroups and options.
         *
         * @param $grpObj               The optgroup from which to create the faux optgroup.
         * @returns {*|HTMLElement}     The faux optgroup as a list item with an anchor.
         */
        base.CreateOptGroup = function($grpObj) {
            var groupText = $grpObj.attr('label');
            var $groupItem = $( '

' );
            $groupItem.addClass( 'optgroup' );
            $groupItem.text( groupText );

            return $groupItem;
        };

        /**
         * Creates an LI to match the given HTML select option.
         *
         * @param $optObj               The option from which to create the faux option.
         * @param target                The ID of the faux select dropdown.
         * @returns {*|HTMLElement}     The faux option as a list item with an anchor.
         */
        base.CreateOption = function($optObj, target) {
            // Basics
            var $ddLink = $( '' );
            var $ddItem = $( '

' );
            $ddLink.attr( 'data-value', $optObj.val() );
            $ddLink.attr( 'data-target', '#' + target );
            $ddLink.attr( 'data-index', $optObj.index('#' + target + ' option') );
            $ddLink.addClass( 'xq-select-item' );
            if($optObj.prop('selected')) {
                $ddLink.addClass( 'selected' );
            }
            $ddLink.text( $optObj.text() );

            // Disabled
            if($optObj.attr('disabled')) {
                $ddLink.addClass( 'disabled' );
            }

            // Subtext
            if(typeof $optObj.attr('data-original-text') != "undefined") {
                $ddLink.text( $optObj.attr( 'data-original-text' ) );
            }
            var subText = $optObj.attr( 'data-subtext' );
            if(typeof subText != "undefined") {
                var $subText = $( '' );
                $subText.text( subText );
                $ddLink.append( $subText );
                if($optObj.text().length < subText.length) {
                    $optObj.attr( 'data-original-text', $optObj.text() );
                    $optObj.append( ' (' + subText + ')' );
                }
            }

            // Put it together & return it
            $ddItem.append( $ddLink );
            return $ddItem;
        };

        /**
         * Called when an item in the faux select is clicked on.
         *
         * @param obj
         */
        base.onClick = function(obj) {
            var target = $( obj ).attr( 'data-target' );
            $( target ).val( $( obj ).attr( 'data-value' ) );
            $( base.options.fauxOption ).removeClass( 'selected' );
            $( obj ).addClass( 'selected' );
            $(target).trigger('change');
        };

        // Run initializer
        base.init(this);

        return base;
    };

    $.fn.xqselect.defaultOptions = {
        fauxOption: '.xq-select-item',
        trigger : '.xq-select > select',
        wrapper      : '.xq-select',
        templateFauxSelect: '

',
        templateFauxButton: '<button type="button" class="btn dropdown-toggle" data-toggle="dropdown"> </button>'
    };
})(jQuery);

/**
 * Credit to Zoltán Tamási - http://stackoverflow.com/questions/21232685/bootstrap-drop-down-menu-auto-dropup-according-to-screen-position
 */
$(document).on("shown.bs.dropdown", ".xq-select", function () {
    // calculate the required sizes, spaces
    var $ul = $(this).children(".dropdown-menu");
    var $button = $(this).children(".dropdown-toggle");
    var ulOffset = $ul.offset();
    // how much space would be left on the top if the dropdown opened that direction
    var spaceUp = (ulOffset.top - $button.height() - $ul.height()) - $(window).scrollTop();
    // how much space is left at the bottom
    var spaceDown = $(window).scrollTop() + $(window).height() - (ulOffset.top + $ul.height());
    // switch to dropup only if there is no space at the bottom AND there is space at the top, or there isn't either but
    // it would be still better fit
    if (spaceDown < 0 && (spaceUp >= 0 || spaceUp > spaceDown))
        $(this).addClass("dropup");
}).on("hidden.bs.dropdown", ".dropdown", function() {
    // always reset after close
    $(this).removeClass("dropup");
});
<|endoftext|>


<commit_before>/* eslint-disable */
/*global $, jQuery, ga, _, AV, FastClick, wx, campaignTools, Swiper, performance */

/*
* @Author: Jiyun
* @Date:   2015-06-25 03:35:03
* @Last Modified by:   hanjiyun
* @Last Modified time: 2016-12-18 00:13:27
*/

// jshint ignore:start

var express = require('express');
var fs = require('fs');
var request = require('request');
var url = require('url');
// var querystring = require('querystring');
// require('request-debug')(request);

var schedule = require('node-schedule');
var session = require('express-session');
// var everyauthCN = require('everyauth-cn');
var config = require('./config/app-config');
// everyauthCN.douban.scope(config.douban.scope);
// var authSettings = require('./config/everyauthCN/auth-settings');
var nodemailer = require('nodemailer');
var cheerio = require('cheerio')
var curl = require('curlrequest');
// var remoteFileSize = require('remote-file-size');
var sizeOf = require('image-size'); // 获取图片长宽
var resizer = require('limby-resize')({
  imagemagick: require('imagemagick'),
});
// var images = require("images");

var app = express();
app.set('views','cloud/views');
app.set('view engine', 'jade');
app.disable('x-powered-by');

var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var cookieSession = require('cookie-session');

app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(bodyParser.json());

app.use(express.static('./public'));

var date;
var now;
var imageUrl = 'http://dabenji.doubanclock.com/pic/test-small.gif';
var isLaunched = false;

var accessToken = null;
var refresh_token;
var currentUserId;

if (!String.repeat) {
    String.prototype.repeat = function (l) {
        return new Array(l + 1).join(this);
    }
}

console.log('====== start =====');

// 每次启动先去自动取图
// getImageUrl();

app.get('/', function (req, res) {

    if (accessToken) {

        if (config.userId.indexOf(currentUserId) >= 0) {

            // var accessToken = req.session.auth.douban.accessToken;
            // var refresh_token = req.session.auth.douban.user.accessTokenExtra.refresh_token;

            /* just for testing */
            // var text = generateText();
            // console.log(text);
            /* test */

            if (!isLaunched) {
                var ruleGetImage = new schedule.RecurrenceRule();
                var rulePostStatus = new schedule.RecurrenceRule();

                ruleGetImage.minute = [0, 30]; // 每到 30 分钟时取图片
                rulePostStatus.minute = [0, 60]; // 整点发广播

                var autoGetImage = schedule.scheduleJob(ruleGetImage, function() {
                    getImageUrl();
                });

                var autoPostStatusTask = schedule.scheduleJob(rulePostStatus, function () {
                    var d = new Date();
                    var localTime = d.getTime();
                    var localOffset = d.getTimezoneOffset() * 60000;
                    var utc = localTime + localOffset;
                    var offset = 8;
                    var beijing = utc + (3600000 * offset);
                    date = new Date(beijing);
                    now = date.getHours();

                    var text = generateText();

                    postToDouban(accessToken, refresh_token, text, date, function (err, httpResponse, body) {});
                });

                isLaunched = true;

                var data = {
                    currentUser: true,
                    tryLogged: true,
                    message: '欢迎大笨鸡，嘻嘻嘻嘻嘻！',
                    imageUrl: imageUrl
                }

                res.render('hello', data);
            } else {
                var data = {
                    currentUser: true,
                    tryLogged: true,
                    message: '欢迎大笨鸡，哈哈哈哈哈哈！',
                    imageUrl: imageUrl
                }
                res.render('hello', data);
            }

        } else {
            var data = {
                currentUser: false,
                tryLogged: true,
                message: '你不是大笨鸡，只有大笨鸡才能报时。',
                imageUrl: imageUrl
            }
            res.render('hello', data);
        }

    } else {
        var data = {
            currentUser: false,
            tryLogged: false,
            message: '你是大笨鸡吗？不是大笨鸡不要点下面的按钮。',
            imageUrl: imageUrl
        }
        res.render('hello', data);
    }
});

app.get('/new', function (req, res) {
    if (config.userId.indexOf(currentUserId) >= 0) {
        getImageUrl({
            isRefresh: true,
        }, function(imageUrl) {
            res.send('');
        });
    } else {
        res.redirect('/');
    }
});

app.get('/auth/douban', function (req, res) {
    res.redirect('https://www.douban.com/service/auth2/auth?client_id='
        + config.douban.apiKey
        + '&redirect_uri='
        + config.douban.redirect_uri
        + '&response_type=code&scope='
        + config.douban.scope);
});

app.get('/auth/douban/callback', function (req, res) {
    var parsedUrl = url.parse(req.url, true);

    if (!parsedUrl.query || !parsedUrl.query.code) {
        console.error("Missing code in querystring. The url looks like " + req.url);
        res.redirect('/');
        return;
    }

    var code = parsedUrl.query && parsedUrl.query.code;

    var oauth = {
        grant_type: 'authorization_code',
        code: code,
        client_id: config.douban.apiKey,
        client_secret: config.douban.Secret,
        redirect_uri: config.douban.redirect_uri
    };

    // get accessToken
    curl.request({
        url: 'https://www.douban.com/service/auth2/token',
        method: 'POST',
        data: oauth
    }, function (err, parts) {
        parts = parts.split('\r\n');
        var data = JSON.parse(parts.pop());

        accessToken = data.access_token;
        refresh_token = data.refresh_token;
        currentUserId = data.douban_user_id;

        // console.log('accessToken!!', data.access_token);

        res.redirect('/');

    });
});

app.get('/reAuth', function (req, res) {
    accessToken = null;
    refresh_token = null;
    currentUserId = null;

    res.redirect('/');
});

function generateText () {
    var string = '🐔 ';
    var text;

    if (now < 12 && now > 6 || now === 6) {
        half = '早上';
    } else if (now === 12) {
        half = '中午';
    } else if (now > 12 && now < 18 || now === 18) {
        half = '下午';
        now = now - 12;
    } else if (now > 18 && now < 23 || now === 23) {
        half = '晚上';
        now = now - 12;
    } else if (now < 6 && now > 0) {
        half = '凌晨';
    } else if (now === 0) {
        half = '午夜';
    }

    if (now !== 0) {
        text = half + now + '点。#不动戳大# \r\n 为什么 gif 图在电脑上必须要查看原图播放？ \r\n' + string.repeat(now);
    } else {
        text = half + now + '点。#不动戳大# \r\n🌙😪💤';
    }

    return text;
}

function postToDouban (accessToken, refresh_token, text, date, callback) {
    // 纯文字版
    // request.post({
    //     url: 'https://api.douban.com/shuo/v2/statuses/',
    //     headers: {'Authorization': 'Bearer ' + accessToken},
    //     encoding: 'utf8',
    //     json: true,
    //     form: {text: text, image: image}
    // }, function(err, httpResponse, body) {
    //     if (err && err.code === 106) {
    //         console.error(date + '\r\nHoly fuck! Clock fail! We need to refresh token!', err);

    //         refreshToken(refresh_token);
    //         console.log('===========');
    //     } else if (err || typeof body.code !== 'undefined') {
    //         console.error(date + '\r\nFuck! Clock fail!, Error:', err, '\r\n Body:', body);
    //         mailSender('FxxK dabenji!', body, function (mailError, mailResponse) {
    //             console.log('Sender feedback:', mailError, mailResponse);
    //         });
    //         console.log('===========');
    //     } else {
    //         console.log(date + '\r\nLOL clock success!');
    //         console.log('===========');
    //     }

    //     if (callback && typeof callback === 'function') {
    //         callback(err, httpResponse, body);
    //     }
    // });

    // 带图版
    var r = request.post('https://api.douban.com/shuo/v2/statuses/', {
            method: 'POST',
            headers: {'Authorization': 'Bearer ' + accessToken},
            // timeout: 70000 // 7秒超时吧
        }, function (err, httpResponse, body) {
        if (err && err.code === 106) {
            console.error(date + '\r\nHoly fuck! Clock fail! We need to refresh token!', err);

            refreshToken(refresh_token);
            console.log('===========');
        } else if (err || typeof body.code !== 'undefined') {
            console.error(date + '\r\nFuck! Clock fail!, Error:', err, '\r\n Body:', body);
            mailSender('FxxK dabenji!', body, function (mailError, mailResponse) {
                console.log('Sender feedback:', mailError, mailResponse);
            });
            console.log('===========');
        } else {
            console.log(date + '\r\nLOL clock success!');
            console.log('===========');
            // console.log('body = ', body)
        }

        if (callback && typeof callback === 'function') {
            callback(err, httpResponse, body);
        }
    });

    var form = r.form();
    form.append('text', text);
    form.append('image', request.get(imageUrl));
}

// todo 这里有 bug 导致无法正常 refreshToken
function refreshToken (refresh_token) {
    var client_id = config.douban.apiKey;
    var client_secret = config.douban.Secret;
    var redirect_uri = config.douban.redirect_uri;

    request.post({
        url: 'https://www.douban.com/service/auth2/token?client_id=' + client_id + '&client_secret=' + client_secret + '&redirect_uri=' + redirect_uri + '&grant_type=refresh_token&refresh_token=' + refresh_token,
    }, function (error, response, resBody) {
        if (!error && response.statusCode == 200) {
            postToDouban(resBody.access_token, resBody.refresh_token, text, date, callback);
        } else {
            console.error(date + 'refresh_token fail!', error, resBody);
        }
    });
}

function mailSender (subject, text, callback) {
    if (!config.mailer.recipient || !config.mailer.user || !config.mailer.pass) {
        return;
    }

    console.log(date + 'sendMail...');
    var mailOptions = {
        from: 'dabenji <'+ config.mailer.user + '>',
        to: config.mailer.recipient.join(','), // list of receivers
        subject: subject,
        text: text
    };

    var smtpTransport = nodemailer.createTransport('SMTP',{
        service: 'Gmail',
        auth: {
            user: config.mailer.user,
            pass: config.mailer.pass
        }
    });

    smtpTransport.sendMail(mailOptions, function(mailError, mailResponse){
        if (callback && typeof callback === 'function') {
            callback(mailError, mailResponse);
        }
    });
}

// get random gif image url
function getImageUrl(option, callback) {
    request('http://www.funcage.com/gif/?', function(error, response, body) {
        if (error) {
          return;
        }

        var $ = cheerio.load(response.body, {
          decodeEntities: false,
          xmlMode: false,
          normalizeWhitespace: false
        });

        imageUrl = $('.cimg img').attr('src').replace('./', 'http://www.funcage.com/gif/');

        console.log('imageUrl', imageUrl)

        request.head(imageUrl, function(_err, _res, _body) {
            var size = _res.headers['content-length'];
            console.log('size:', size)

            // 如果图片大于2.8M，重新获取 (其实豆瓣规定的是不大于 3M，但为了发广播的速度更快，这里缩小标准到2.8M)
            if (size > 2800000) {
                console.log('image\'s too big to send');
                getImageUrl(option, callback);
            } else {
                var chunks = [];

                request.get(imageUrl).on('data', function(chunk) {
                    chunks.push(chunk);
                    // console.log('chunk = ', chunk);
                }).on('end', function() {
                    var buffer = Buffer.concat(chunks);
                    console.log(sizeOf(buffer));

                    // 如果图片宽度大于300
                    if (sizeOf(buffer).width > 150 || sizeOf(buffer).height > 150) {
                        console.log('image reszing');

                        // images(buffer).size(200).save("output.jpg", {
                        //     quality : 100
                        // });

                        // 保存图片到本地
                        download(imageUrl, 'test.gif', function() {
                            console.log("downloaded to test.gif")

                            // 将图片缩小尺寸，重新保存
                            resizer.resize('test.gif', {
                              width: 150,
                              height: 150,
                              coalesce: true,
                              destination: './public/pic/test-small.gif'
                            });

                            console.log('保存完成');

                            if (option && option.isRefresh) {
                                if (callback && typeof callback === 'function') {
                                    callback(imageUrl);
                                }
                            }
                        })
                    } else {
                        if (option && option.isRefresh) {
                            if (callback && typeof callback === 'function') {
                                callback(imageUrl);
                            }
                        }
                    }
                });
            }
        });
    });
}

function download(uri, filename, callback){
  request.head(uri, function(err, res, body){
    // console.log('content-type:', res.headers['content-type']);
    console.log('content-length:', res.headers['content-length']);
    request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
  });
};

app.listen(8181);

// jshint ignore:end<commit_msg>fix imageUrl<commit_after>/* eslint-disable */
/*global $, jQuery, ga, _, AV, FastClick, wx, campaignTools, Swiper, performance */

/*
* @Author: Jiyun
* @Date:   2015-06-25 03:35:03
* @Last Modified by:   hanjiyun
* @Last Modified time: 2016-12-18 12:14:11
*/

// jshint ignore:start

var express = require('express');
var fs = require('fs');
var request = require('request');
var url = require('url');
// var querystring = require('querystring');
// require('request-debug')(request);

var schedule = require('node-schedule');
var session = require('express-session');
// var everyauthCN = require('everyauth-cn');
var config = require('./config/app-config');
// everyauthCN.douban.scope(config.douban.scope);
// var authSettings = require('./config/everyauthCN/auth-settings');
var nodemailer = require('nodemailer');
var cheerio = require('cheerio')
var curl = require('curlrequest');
// var remoteFileSize = require('remote-file-size');
var sizeOf = require('image-size'); // 获取图片长宽
var resizer = require('limby-resize')({
  imagemagick: require('imagemagick'),
});
// var images = require("images");

var app = express();
app.set('views','cloud/views');
app.set('view engine', 'jade');
app.disable('x-powered-by');

var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var cookieSession = require('cookie-session');

app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(bodyParser.json());

app.use(express.static('./public'));

var date;
var now;
var imageUrl = 'http://dabenji.doubanclock.com/pic/test-small.gif';
var isLaunched = false;

var accessToken = null;
var refresh_token;
var currentUserId;

if (!String.repeat) {
    String.prototype.repeat = function (l) {
        return new Array(l + 1).join(this);
    }
}

console.log('====== start =====');

// 每次启动先去自动取图
// getImageUrl();

app.get('/', function (req, res) {

    if (accessToken) {

        if (config.userId.indexOf(currentUserId) >= 0) {

            // var accessToken = req.session.auth.douban.accessToken;
            // var refresh_token = req.session.auth.douban.user.accessTokenExtra.refresh_token;

            /* just for testing */
            // var text = generateText();
            // console.log(text);
            /* test */

            if (!isLaunched) {
                var ruleGetImage = new schedule.RecurrenceRule();
                var rulePostStatus = new schedule.RecurrenceRule();

                ruleGetImage.minute = [0, 30]; // 每到 30 分钟时取图片
                rulePostStatus.minute = [0, 60]; // 整点发广播

                var autoGetImage = schedule.scheduleJob(ruleGetImage, function() {
                    getImageUrl();
                });

                var autoPostStatusTask = schedule.scheduleJob(rulePostStatus, function () {
                    var d = new Date();
                    var localTime = d.getTime();
                    var localOffset = d.getTimezoneOffset() * 60000;
                    var utc = localTime + localOffset;
                    var offset = 8;
                    var beijing = utc + (3600000 * offset);
                    date = new Date(beijing);
                    now = date.getHours();

                    var text = generateText();

                    postToDouban(accessToken, refresh_token, text, date, function (err, httpResponse, body) {});
                });

                isLaunched = true;

                var data = {
                    currentUser: true,
                    tryLogged: true,
                    message: '欢迎大笨鸡，嘻嘻嘻嘻嘻！',
                    imageUrl: imageUrl
                }

                res.render('hello', data);
            } else {
                var data = {
                    currentUser: true,
                    tryLogged: true,
                    message: '欢迎大笨鸡，哈哈哈哈哈哈！',
                    imageUrl: imageUrl
                }
                res.render('hello', data);
            }

        } else {
            var data = {
                currentUser: false,
                tryLogged: true,
                message: '你不是大笨鸡，只有大笨鸡才能报时。',
                imageUrl: imageUrl
            }
            res.render('hello', data);
        }

    } else {
        var data = {
            currentUser: false,
            tryLogged: false,
            message: '你是大笨鸡吗？不是大笨鸡不要点下面的按钮。',
            imageUrl: imageUrl
        }
        res.render('hello', data);
    }
});

app.get('/new', function (req, res) {
    if (config.userId.indexOf(currentUserId) >= 0) {
        getImageUrl({
            isRefresh: true,
        }, function(imageUrl) {
            res.send('');
        });
    } else {
        res.redirect('/');
    }
});

app.get('/auth/douban', function (req, res) {
    res.redirect('https://www.douban.com/service/auth2/auth?client_id='
        + config.douban.apiKey
        + '&redirect_uri='
        + config.douban.redirect_uri
        + '&response_type=code&scope='
        + config.douban.scope);
});

app.get('/auth/douban/callback', function (req, res) {
    var parsedUrl = url.parse(req.url, true);

    if (!parsedUrl.query || !parsedUrl.query.code) {
        console.error("Missing code in querystring. The url looks like " + req.url);
        res.redirect('/');
        return;
    }

    var code = parsedUrl.query && parsedUrl.query.code;

    var oauth = {
        grant_type: 'authorization_code',
        code: code,
        client_id: config.douban.apiKey,
        client_secret: config.douban.Secret,
        redirect_uri: config.douban.redirect_uri
    };

    // get accessToken
    curl.request({
        url: 'https://www.douban.com/service/auth2/token',
        method: 'POST',
        data: oauth
    }, function (err, parts) {
        parts = parts.split('\r\n');
        var data = JSON.parse(parts.pop());

        accessToken = data.access_token;
        refresh_token = data.refresh_token;
        currentUserId = data.douban_user_id;

        // console.log('accessToken!!', data.access_token);

        res.redirect('/');

    });
});

app.get('/reAuth', function (req, res) {
    accessToken = null;
    refresh_token = null;
    currentUserId = null;

    res.redirect('/');
});

function generateText () {
    var string = '🐔 ';
    var text;

    if (now < 12 && now > 6 || now === 6) {
        half = '早上';
    } else if (now === 12) {
        half = '中午';
    } else if (now > 12 && now < 18 || now === 18) {
        half = '下午';
        now = now - 12;
    } else if (now > 18 && now < 23 || now === 23) {
        half = '晚上';
        now = now - 12;
    } else if (now < 6 && now > 0) {
        half = '凌晨';
    } else if (now === 0) {
        half = '午夜';
    }

    if (now !== 0) {
        text = half + now + '点。#不动戳大# \r\n 为什么 gif 图在电脑上必须要查看原图播放？ \r\n' + string.repeat(now);
    } else {
        text = half + now + '点。#不动戳大# \r\n🌙😪💤';
    }

    return text;
}

function postToDouban (accessToken, refresh_token, text, date, callback) {
    // 纯文字版
    // request.post({
    //     url: 'https://api.douban.com/shuo/v2/statuses/',
    //     headers: {'Authorization': 'Bearer ' + accessToken},
    //     encoding: 'utf8',
    //     json: true,
    //     form: {text: text, image: image}
    // }, function(err, httpResponse, body) {
    //     if (err && err.code === 106) {
    //         console.error(date + '\r\nHoly fuck! Clock fail! We need to refresh token!', err);

    //         refreshToken(refresh_token);
    //         console.log('===========');
    //     } else if (err || typeof body.code !== 'undefined') {
    //         console.error(date + '\r\nFuck! Clock fail!, Error:', err, '\r\n Body:', body);
    //         mailSender('FxxK dabenji!', body, function (mailError, mailResponse) {
    //             console.log('Sender feedback:', mailError, mailResponse);
    //         });
    //         console.log('===========');
    //     } else {
    //         console.log(date + '\r\nLOL clock success!');
    //         console.log('===========');
    //     }

    //     if (callback && typeof callback === 'function') {
    //         callback(err, httpResponse, body);
    //     }
    // });

    // 带图版
    var r = request.post('https://api.douban.com/shuo/v2/statuses/', {
            method: 'POST',
            headers: {'Authorization': 'Bearer ' + accessToken},
            // timeout: 70000 // 7秒超时吧
        }, function (err, httpResponse, body) {
        if (err && err.code === 106) {
            console.error(date + '\r\nHoly fuck! Clock fail! We need to refresh token!', err);

            refreshToken(refresh_token);
            console.log('===========');
        } else if (err || typeof body.code !== 'undefined') {
            console.error(date + '\r\nFuck! Clock fail!, Error:', err, '\r\n Body:', body);
            mailSender('FxxK dabenji!', body, function (mailError, mailResponse) {
                console.log('Sender feedback:', mailError, mailResponse);
            });
            console.log('===========');
        } else {
            console.log(date + '\r\nLOL clock success!');
            console.log('===========');
            // console.log('body = ', body)
        }

        if (callback && typeof callback === 'function') {
            callback(err, httpResponse, body);
        }
    });

    var form = r.form();
    form.append('text', text);
    form.append('image', request.get(imageUrl));
}

// todo 这里有 bug 导致无法正常 refreshToken
function refreshToken (refresh_token) {
    var client_id = config.douban.apiKey;
    var client_secret = config.douban.Secret;
    var redirect_uri = config.douban.redirect_uri;

    request.post({
        url: 'https://www.douban.com/service/auth2/token?client_id=' + client_id + '&client_secret=' + client_secret + '&redirect_uri=' + redirect_uri + '&grant_type=refresh_token&refresh_token=' + refresh_token,
    }, function (error, response, resBody) {
        if (!error && response.statusCode == 200) {
            postToDouban(resBody.access_token, resBody.refresh_token, text, date, callback);
        } else {
            console.error(date + 'refresh_token fail!', error, resBody);
        }
    });
}

function mailSender (subject, text, callback) {
    if (!config.mailer.recipient || !config.mailer.user || !config.mailer.pass) {
        return;
    }

    console.log(date + 'sendMail...');
    var mailOptions = {
        from: 'dabenji <'+ config.mailer.user + '>',
        to: config.mailer.recipient.join(','), // list of receivers
        subject: subject,
        text: text
    };

    var smtpTransport = nodemailer.createTransport('SMTP',{
        service: 'Gmail',
        auth: {
            user: config.mailer.user,
            pass: config.mailer.pass
        }
    });

    smtpTransport.sendMail(mailOptions, function(mailError, mailResponse){
        if (callback && typeof callback === 'function') {
            callback(mailError, mailResponse);
        }
    });
}

// get random gif image url
function getImageUrl(option, callback) {
    request('http://www.funcage.com/gif/?', function(error, response, body) {
        if (error) {
          return;
        }

        var $ = cheerio.load(response.body, {
          decodeEntities: false,
          xmlMode: false,
          normalizeWhitespace: false
        });

        imageUrl = $('.cimg img').attr('src').replace('./', 'http://www.funcage.com/gif/');

        console.log('imageUrl', imageUrl)

        request.head(imageUrl, function(_err, _res, _body) {
            var size = _res.headers['content-length'];
            console.log('size:', size)

            // 如果图片大于2.8M，重新获取 (其实豆瓣规定的是不大于 3M，但为了发广播的速度更快，这里缩小标准到2.8M)
            if (size > 2800000) {
                console.log('image\'s too big to send');
                getImageUrl(option, callback);
            } else {
                var chunks = [];

                request.get(imageUrl).on('data', function(chunk) {
                    chunks.push(chunk);
                    // console.log('chunk = ', chunk);
                }).on('end', function() {
                    var buffer = Buffer.concat(chunks);
                    console.log(sizeOf(buffer));

                    // 如果图片宽度大于300
                    if (sizeOf(buffer).width > 150 || sizeOf(buffer).height > 150) {
                        console.log('image reszing');

                        // images(buffer).size(200).save("output.jpg", {
                        //     quality : 100
                        // });

                        // 保存图片到本地
                        download(imageUrl, 'test.gif', function() {
                            console.log("downloaded to test.gif")

                            // 将图片缩小尺寸，重新保存
                            resizer.resize('test.gif', {
                              width: 150,
                              height: 150,
                              coalesce: true,
                              destination: './public/pic/test-small.gif'
                            });

                            console.log('保存完成');

                            if (option && option.isRefresh) {
                                if (callback && typeof callback === 'function') {
                                    callback('http://dabneji.doubanclock.com/pic/test-small.gif');
                                }
                            }
                        })
                    } else {
                        if (option && option.isRefresh) {
                            if (callback && typeof callback === 'function') {
                                callback(imageUrl);
                            }
                        }
                    }
                });
            }
        });
    });
}

function download(uri, filename, callback){
  request.head(uri, function(err, res, body){
    // console.log('content-type:', res.headers['content-type']);
    console.log('content-length:', res.headers['content-length']);
    request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
  });
};

app.listen(8181);

// jshint ignore:end<|endoftext|>


<commit_before>module.exports = {
  extends: ['plugin:@typescript-eslint/recommended-requiring-type-checking'],
  rules: {
    // Only allow boolean variables with appropriate prefixes
    //  - PascalCase needed for reasons, it is really camelCase: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md#enforce-that-boolean-variables-are-prefixed-with-an-allowed-verb
    // Only allow PascalCase for enum members
    '@typescript-eslint/naming-convention': [
      'warn',
      {
        selector: 'variable',
        types: ['boolean'],
        format: ['PascalCase'],
        prefix: ['is', 'should', 'has', 'can', 'did', 'will']
      },
      { selector: 'enumMember', format: ['PascalCase'] }
    ]
  }
};
<commit_msg>feat(typescript-typechecking): reduce rules to warn or off (#10)<commit_after>module.exports = {
  extends: ['plugin:@typescript-eslint/recommended-requiring-type-checking'],
  rules: {
    // Only allow boolean variables with appropriate prefixes
    //  - PascalCase needed for reasons, it is really camelCase: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md#enforce-that-boolean-variables-are-prefixed-with-an-allowed-verb
    // Only allow PascalCase for enum members
    '@typescript-eslint/naming-convention': [
      'warn',
      {
        selector: 'variable',
        types: ['boolean'],
        format: ['PascalCase'],
        prefix: ['is', 'should', 'has', 'can', 'did', 'will']
      },
      { selector: 'enumMember', format: ['PascalCase'] }
    ],

    // Warnings related to unexpected usages of the 'any' type
    '@typescript-eslint/no-unsafe-assignment': 'warn',
    '@typescript-eslint/no-unsafe-return': 'warn',
    '@typescript-eslint/no-unsafe-call': 'warn',
    '@typescript-eslint/no-unsafe-member-access': 'warn',

    // Helps to catch unhandled nullability in string templates
    '@typescript-eslint/restrict-template-expressions': 'warn',

    // We basically never bind our methods, and it's not very applicable as we move away from class components
    '@typescript-eslint/unbound-method': 'off'
  }
};
<|endoftext|>


<commit_before>function ajax(params){
	params = params||{};
	if (!params.url) {
        throw new Error('Necessary parameters are missing.'); //必要参数未填
    }
    var random = +new Date;
    var hander = null;
	var options = {
		url: '',								//接口地址
		type: 'GET',							//请求方式
		timeout: 5000,							//超时等待时间
		cache: true,							//缓存
		async: true,							//是否异步
		xhrFields: {},							//设置XHR对象属性键值对。如果需要，可设置withCredentials为true的跨域请求。
		dataType: 'json',						//请求的数据类型
		data: {},								//参数
		jsonp: 'callback',						//传递请求完成后的函数名
		jsonpCallback: 'jsonp_' + random,		//请求完成后的函数名
		error: function() {},					//请求失败后调用
		success: function(){},					//请求成功后调用
		complete: function(){}					//请求完成后调用
	};
	var formatParams = function(json) {
        var arr = [];
        for(var i in json) {
            arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(json[i]));
        }
        return arr.join("&");
    };
    for(var i in params){
    	switch(i){
    		case 'type':
    			options[i] = params[i].toUpperCase();
    			break;
    		case 'dataType':
    			options[i] = params[i].toLowerCase();
    			break;
    		default:
    			options[i] = params[i];
    	}
    }
    if(typeof options.data =='object'){
        options.data = formatParams(options.data);
    }
	if(options.dataType=='jsonp'){
		options.cache = params.cache||false;
		//插入动态脚本及回调函数
		var $head = document.getElementsByTagName('head')[0];
		var $script = document.createElement('script');
		$head.appendChild($script);
        window[options.jsonpCallback] = function (json) {
            $head.removeChild($script);
            window[options.jsonpCallback] = null;
            hander && clearTimeout(hander);
            options.success(json);
            options.complete();
        };
        //发送请求
        if(options.cache){
        	options.data += options.data?'&_'+random:'_'+random;
        }
        options.data += '&'+options.jsonp+'='+options.jsonpCallback;
        $script.src = (options.url + '?' + options.data).replace('?&','?');
        //超时处理
       	hander = setTimeout(function(){
            $head.removeChild($script);
            window[options.jsonpCallback] = null;
            options.error();
            options.complete();
        }, options.timeout);
	}else{
		if(options.cache){
			options.data += options.data?'&_'+random:'_'+random;
        }
		//创建xhr对象
		var xhr = new (self.XMLHttpRequest||ActiveXObject)("Microsoft.XMLHTTP");
		if(!xhr){
			return false;
		}
		//发送请求
		if (options.type == 'POST') {
			xhr.open(options.type, options.url, options.async);
			xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
		}else{
			options.url += options.url.indexOf('?')>-1?'&'+options.data:'?'+options.data;
			xhr.open(options.type, options.url, options.async);
			options.data = null;
		}
		if(options.xhrFields){
			for(var field in options.xhrFields){
				xhr[field]= options.xhrFields[field];
			}
		}
		xhr.send(options.data);
		//超时处理
		var requestDone = false;
		hander = setTimeout(function() {
			requestDone = true;
			if(xhr.readyState != 4){
				xhr.abort();
				options.error();
			}
			options.complete();
		}, options.timeout);
		//状态处理
		xhr.onreadystatechange = function(){
			if(xhr.readyState == 4&&!requestDone) {
				if(xhr.status>=200 && xhr.status<300||xhr.status == 304) {
					var data = options.dataType == "xml" ? xhr.responseXML : xhr.responseText;
					if (options.dataType == "json") {
						try{
							data =  JSON.parse(data);
						}catch(e){
							data = eval('(' + data + ')');
						}
					}
					options.success(data);
				} else {
					options.error();
				}
				hander && clearTimeout(hander);
				options.complete();
			}
		};
	}
}
<commit_msg>updated ajax库添加header设置<commit_after>function ajax(params){
	params = params||{};
	if (!params.url) {
        throw new Error('Necessary parameters are missing.'); //必要参数未填
    }
    var random = +new Date;
    var hander = null;
	var options = {
		url: '',								//接口地址
		type: 'GET',							//请求方式
		timeout: 5000,							//超时等待时间
		cache: true,							//缓存
		async: true,							//是否异步
		headers: {},							//
		xhrFields: {},							//设置XHR对象属性键值对。如果需要，可设置withCredentials为true的跨域请求。
		dataType: 'json',						//请求的数据类型
		data: {},								//参数
		jsonp: 'callback',						//传递请求完成后的函数名
		jsonpCallback: 'jsonp_' + random,		//请求完成后的函数名
		error: function() {},					//请求失败后调用
		success: function(){},					//请求成功后调用
		complete: function(){}					//请求完成后调用
	};
	var formatParams = function(json) {
        var arr = [];
        for(var i in json) {
            arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(json[i]));
        }
        return arr.join("&");
    };
    for(var i in params){
    	switch(i){
    		case 'type':
    			options[i] = params[i].toUpperCase();
    			break;
    		case 'dataType':
    			options[i] = params[i].toLowerCase();
    			break;
    		default:
    			options[i] = params[i];
    	}
    }
    if(typeof options.data =='object'){
        options.data = formatParams(options.data);
    }
	if(options.dataType=='jsonp'){
		options.cache = params.cache||false;
		//插入动态脚本及回调函数
		var $head = document.getElementsByTagName('head')[0];
		var $script = document.createElement('script');
		$head.appendChild($script);
        window[options.jsonpCallback] = function (json) {
            $head.removeChild($script);
            window[options.jsonpCallback] = null;
            hander && clearTimeout(hander);
            options.success(json);
            options.complete();
        };
        //发送请求
        if(options.cache){
        	options.data += options.data?'&_'+random:'_'+random;
        }
        options.data += '&'+options.jsonp+'='+options.jsonpCallback;
        $script.src = (options.url + '?' + options.data).replace('?&','?');
        //超时处理
       	hander = setTimeout(function(){
            $head.removeChild($script);
            window[options.jsonpCallback] = null;
            options.error();
            options.complete();
        }, options.timeout);
	}else{
		if(options.cache){
			options.data += options.data?'&_'+random:'_'+random;
        }
		//创建xhr对象
		var xhr = new (self.XMLHttpRequest||ActiveXObject)("Microsoft.XMLHTTP");
		if(!xhr){
			return false;
		}
		//发送请求
		if (options.type == 'POST') {
			xhr.open(options.type, options.url, options.async);
			xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
		}else{
			options.url += options.url.indexOf('?')>-1?'&'+options.data:'?'+options.data;
			xhr.open(options.type, options.url, options.async);
			options.data = null;
		}
		for(var name in options.headers){
			xhr.setRequestHeader(name,options.headers[name]);
		}
		if(options.xhrFields){
			for(var field in options.xhrFields){
				xhr[field]= options.xhrFields[field];
			}
		}
		xhr.send(options.data);
		//超时处理
		var requestDone = false;
		hander = setTimeout(function() {
			requestDone = true;
			if(xhr.readyState != 4){
				xhr.abort();
				options.error();
			}
			options.complete();
		}, options.timeout);
		//状态处理
		xhr.onreadystatechange = function(){
			if(xhr.readyState == 4&&!requestDone) {
				if(xhr.status>=200 && xhr.status<300||xhr.status == 304) {
					var data = options.dataType == "xml" ? xhr.responseXML : xhr.responseText;
					if (options.dataType == "json") {
						try{
							data =  JSON.parse(data);
						}catch(e){
							data = eval('(' + data + ')');
						}
					}
					options.success(data);
				} else {
					options.error();
				}
				hander && clearTimeout(hander);
				options.complete();
			}
		};
	}
}<|endoftext|>


<commit_before>
module.exports = function(fn){
	function component(View){
		var set = View.prototype.init
		if (fn) {
			set.addNode(fn)
			component.deps.forEach(function(dep){
				set.addRelationship(fn, dep)
			})
		}
		component.events.forEach(function(event, i){
			View.prototype.on(event, component.fns[i])
		})
		return View
	}

	component.init = fn
	component.events = []
	component.fns = []
	component.deps = []
	component.need = addDep
	component.on = addListener

	return component
}

function addDep(dep){
	if (dep.init) this.deps.push(dep.init)
	this.deps = this.deps.concat(dep.deps)
	return this
}

function addListener(type, fn){
	this.events.push(type)
	this.fns.push(fn)
	return this
}<commit_msg>update topograph<commit_after>
module.exports = function(fn){
	function component(View){
		var graph = View.prototype.init
		if (fn) {
			graph.addNode(fn)
			component.deps.forEach(function(dep){
				graph.addNode(dep)
				graph.addRelationship(fn, dep)
			})
		}
		component.events.forEach(function(event, i){
			View.prototype.on(event, component.fns[i])
		})
		return View
	}

	component.init = fn
	component.events = []
	component.fns = []
	component.deps = []
	component.need = addDep
	component.on = addListener

	return component
}

function addDep(dep){
	if (dep.init) this.deps.push(dep.init)
	this.deps = this.deps.concat(dep.deps)
	return this
}

function addListener(type, fn){
	this.events.push(type)
	this.fns.push(fn)
	return this
}<|endoftext|>


<commit_before>// Karma configuration
// Generated on Wed Jul 15 2015 09:44:02 GMT+0200 (Romance Daylight Time)
'use strict';

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: './',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      'node_modules/zone.js/dist/zone-microtask.js',
      'node_modules/zone.js/dist/long-stack-trace-zone.js',
      'node_modules/zone.js/dist/jasmine-patch.js',
      'node_modules/es6-module-loader/dist/es6-module-loader.js',
      'node_modules/traceur/bin/traceur-runtime.js', // Required by PhantomJS2, otherwise it shouts ReferenceError: Can't find variable: require
      'node_modules/traceur/bin/traceur.js',
      'node_modules/systemjs/dist/system.src.js',
      'node_modules/reflect-metadata/Reflect.js',

      { pattern: 'node_modules/angular2/**/*.js', included: false, watched: false },
      { pattern: 'node_modules/ionic-framework/*.js', included: false, watched: false },
      { pattern: 'node_modules/ionic-framework/**/*.js', included: false, watched: false },
      { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false },
      { pattern: 'www/build/test/**/*.js', included: false, watched: true },
      { pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: false, watched: false }, // PhantomJS2 (and possibly others) might require it

      'test-main.js'
    ],

    // list of files to exclude
    exclude: [
      'node_modules/angular2/**/*_spec.js',
      'node_modules/ionic-framework/**/*spec*'
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['mocha'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: [
      'PhantomJS2'
    ],

    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    },

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });

  if (process.env.APPVEYOR) {
    config.browsers = ['IE'];
    config.singleRun = true;
    config.browserNoActivityTimeout = 90000; // Note: default value (10000) is not enough
  }

  if (process.env.TRAVIS || process.env.CIRCLECI) {
    config.browsers = ['Chrome_travis_ci'];
    config.singleRun = true;
  }
};
<commit_msg>Enabling coverage reporter<commit_after>// Karma configuration
// Generated on Wed Jul 15 2015 09:44:02 GMT+0200 (Romance Daylight Time)
'use strict';

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: './',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      'node_modules/zone.js/dist/zone-microtask.js',
      'node_modules/zone.js/dist/long-stack-trace-zone.js',
      'node_modules/zone.js/dist/jasmine-patch.js',
      'node_modules/es6-module-loader/dist/es6-module-loader.js',
      'node_modules/traceur/bin/traceur-runtime.js', // Required by PhantomJS2, otherwise it shouts ReferenceError: Can't find variable: require
      'node_modules/traceur/bin/traceur.js',
      'node_modules/systemjs/dist/system.src.js',
      'node_modules/reflect-metadata/Reflect.js',

      { pattern: 'node_modules/angular2/**/*.js', included: false, watched: false },
      { pattern: 'node_modules/ionic-framework/*.js', included: false, watched: false },
      { pattern: 'node_modules/ionic-framework/**/*.js', included: false, watched: false },
      { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false },
      { pattern: 'www/build/test/**/*.js', included: false, watched: true },
      { pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: false, watched: false }, // PhantomJS2 (and possibly others) might require it

      'test-main.js'
    ],

    // list of files to exclude
    exclude: [
      'node_modules/angular2/**/*_spec.js',
      'node_modules/ionic-framework/**/*spec*'
    ],

    // options on how to report coverage:
    coverageReporter: {
      type: 'text',
      includeAllSources: true
    },

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      'www/build/test/app/**/*.js': 'coverage'
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['mocha','coverage'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: [
      'PhantomJS2'
    ],

    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    },

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });

  if (process.env.APPVEYOR) {
    config.browsers = ['IE'];
    config.singleRun = true;
    config.browserNoActivityTimeout = 90000; // Note: default value (10000) is not enough
  }

  if (process.env.TRAVIS || process.env.CIRCLECI) {
    config.browsers = ['Chrome_travis_ci'];
    config.singleRun = true;
  }
};
<|endoftext|>