<commit_before>
function createViewBuilderFactory(filterFactory, sortFactory) {

	const _createBuilder = function(itemType, viewInstancer, asyncLoader) {

		let _view = null;

		let _eagerType = 'full';

		let _offset = 0;

		let _count = 0;

		let _pageSize = 0;

		const _filters = [];

		const _sorts = [];

		const _getEagerType = function() { return _eagerType; },

		const _getOffset = function() {	return _offset;	},

		const _getCount = function() { return _count; },

		const _getPageSize = function() { return _pageSize;	}

		const _getFilters = function() { return _filters; },

		const _getSorts = function() { return _sorts; },

		return {

			hasView: function() {
				return !!_view;
			},

			view: function() {
				if (!this.hasView())
				{
					_view = viewInstancer.createView(itemType, asyncLoader, {
						getEagerType: _getEagerType,
						getOffset: _getOffset,
						getCount: _getCount,
						getPageSize: _getPageSize,
						getFilters: _getFilters,
						getSorts: _getSorts
					});
				}
				return _view;
			},

			addFilter: function(filter) {
				if (!this.hasView()) {
					_filters[] = filter;
				}
				return this;
			},

			filterId: function(id) {
				return this.addFilter(filterFactory.createFilterId(id))
			},

			filterIds: function(ids) {
				return this.addFilter(filterFactory.createFilterIds(ids))
			},

			filterExp: function(key, operator, value) {
				return this.addFilter(filterFactory.createFilterExp(key, operator, value))
			},

			filterIn: function(key, values) {
				return this.addFilter(filterFactory.createFilterIn(key, values))
			},

			addSort: function(sort) {
				if (!this.hasView()) {
					_sorts[] = sort;
				}
				return this;
			},

			sort: function(key, order) {
				return this.addSort(sortFactory.createSort(key, order));
			},

			eagerType: function(eagerType) {
				if (!this.hasView()) {
					_eagerType = eagerType;
				}
				return this;
			},

			paginate: function(page, pageSize) {
				if (!this.hasView()) {
					_offset = 0;
					_count = pageSize;
					_pageSize = pageSize;
				}
				return this;
			},

			limit: function(offset, count) {
				if (!this.hasView()) {
					_offset = offset;
					_count = count;
					_pageSize = 0;
				}
				return this;
			},

			viewId: function() {
				return view().viewId();
			},

			ready: function() {
				return view().ready();
			},

			outdated: function() {
				return view().outdated();
			},

			itemType: function() {
				return view().itemType();
			},

			load: function() {
				return view().load();
			},

			loading: function() {
				return _loading;
			},

			loadingFailed: function() {
				return _loadingFailed;
			},

			loadingMetaOffset: function() {
				return view().loadingMetaOffset();
			},

			loadingMetaCount: function() {
				return view().loadingMetaCount();
			},

			loadingMetaPage: function() {
				return view().loadingMetaPage();
			},

			loadingMetaPageSize: function() {
				return view().loadingMetaPageSize();
			},

			loadingMetaTotalCount: function() {
				return view().loadingMetaTotalCount();
			},

			hash: function() {
				return view().hash();
			},

			items: function() {
				return view().items();
			},

			first: function() {
				return view().first();
			},

			last: function() {
				return view().last();
			},

		}
	}

	return {

		createBuilder: _createBuilder,

	};
 }

export default createViewBuilderFactory;
<commit_msg>added itemType() as viewInstancer function<commit_after>
function createViewBuilderFactory(filterFactory, sortFactory) {

	const _createBuilder = function(viewInstancer, asyncLoader) {

		let _view = null;

		let _eagerType = 'full';

		let _offset = 0;

		let _count = 0;

		let _pageSize = 0;

		const _filters = [];

		const _sorts = [];

		const _getEagerType = function() { return _eagerType; },

		const _getOffset = function() {	return _offset;	},

		const _getCount = function() { return _count; },

		const _getPageSize = function() { return _pageSize;	}

		const _getFilters = function() { return _filters; },

		const _getSorts = function() { return _sorts; },

		return {

			hasView: function() {
				return !!_view;
			},

			view: function() {
				if (!this.hasView())
				{
					_view = viewInstancer.createView(asyncLoader, {
						getEagerType: _getEagerType,
						getOffset: _getOffset,
						getCount: _getCount,
						getPageSize: _getPageSize,
						getFilters: _getFilters,
						getSorts: _getSorts
					});
				}
				return _view;
			},

			addFilter: function(filter) {
				if (!this.hasView()) {
					_filters[] = filter;
				}
				return this;
			},

			filterId: function(id) {
				return this.addFilter(filterFactory.createFilterId(id))
			},

			filterIds: function(ids) {
				return this.addFilter(filterFactory.createFilterIds(ids))
			},

			filterExp: function(key, operator, value) {
				return this.addFilter(filterFactory.createFilterExp(key, operator, value))
			},

			filterIn: function(key, values) {
				return this.addFilter(filterFactory.createFilterIn(key, values))
			},

			addSort: function(sort) {
				if (!this.hasView()) {
					_sorts[] = sort;
				}
				return this;
			},

			sort: function(key, order) {
				return this.addSort(sortFactory.createSort(key, order));
			},

			eagerType: function(eagerType) {
				if (!this.hasView()) {
					_eagerType = eagerType;
				}
				return this;
			},

			paginate: function(page, pageSize) {
				if (!this.hasView()) {
					_offset = 0;
					_count = pageSize;
					_pageSize = pageSize;
				}
				return this;
			},

			limit: function(offset, count) {
				if (!this.hasView()) {
					_offset = offset;
					_count = count;
					_pageSize = 0;
				}
				return this;
			},

			viewId: function() {
				return view().viewId();
			},

			ready: function() {
				return view().ready();
			},

			outdated: function() {
				return view().outdated();
			},

			itemType: function() {
				return viewInstancer.itemType();
			},

			load: function() {
				return view().load();
			},

			loading: function() {
				return _loading;
			},

			loadingFailed: function() {
				return _loadingFailed;
			},

			loadingMetaOffset: function() {
				return view().loadingMetaOffset();
			},

			loadingMetaCount: function() {
				return view().loadingMetaCount();
			},

			loadingMetaPage: function() {
				return view().loadingMetaPage();
			},

			loadingMetaPageSize: function() {
				return view().loadingMetaPageSize();
			},

			loadingMetaTotalCount: function() {
				return view().loadingMetaTotalCount();
			},

			hash: function() {
				return view().hash();
			},

			items: function() {
				return view().items();
			},

			first: function() {
				return view().first();
			},

			last: function() {
				return view().last();
			},

		}
	}

	return {

		createBuilder: _createBuilder,

	};
 }

export default createViewBuilderFactory;
<|endoftext|>


<commit_before>/**
@function Matreshka.binders.existence
@module matreshka/binders/existence
@importance 2
@since 2.3
@summary Returns a binder which controls an existence of DOM node (using ``style.display``) at DOM tree depending on an object property value
@desc The binder works the same way as {@link Matreshka.binders.display}, but instead of visibility change the existence at page DOM is changed. The binder is useful for:

- Big appications: show one or another page depending on route state;
- For infinite scrolling;
- For other cases where you need to hide an element but its existence at DOM tree isn't necessary.

@param {boolean} [bool=true] - If the argument equals ``true``, a node is removed when a property value is falsy; if it equals ``false``, it is removed when a property value is truly
@returns {binder}
@example
this.bindNode('myKey', '.my-element', Matreshka.binders.existence(true));
@example
this.bindNode('myKey', '.my-element', Matreshka.binders.existence(false));
*/
<commit_msg>Update Matreshka.binders.existence.jsdoc.js<commit_after>/**
@function Matreshka.binders.existence
@module matreshka/binders/existence
@importance 2
@since 2.3
@summary Returns a binder which controls an existence of DOM node at DOM tree depending on an object property value
@desc The binder works the same way as {@link Matreshka.binders.display}, but instead of visibility change the existence at page DOM is changed. The binder is useful for:

- Big appications: show one or another page depending on route state;
- For infinite scrolling;
- For other cases where you need to hide an element but its existence at DOM tree isn't necessary.

@param {boolean} [bool=true] - If the argument equals ``true``, a node is removed when a property value is falsy; if it equals ``false``, it is removed when a property value is truly
@returns {binder}
@example
this.bindNode('myKey', '.my-element', Matreshka.binders.existence(true));
@example
this.bindNode('myKey', '.my-element', Matreshka.binders.existence(false));
*/
<|endoftext|>


<commit_before><script type="text/javascript">
/*
 *  Chooses a suitable stylesheet based on browser.
 */
    var IE4 = (document.all && !document.getElementById) ? true : false;
    var NS4 = (document.layers) ? true : false;
    var IE5 = (document.all && document.getElementById) ? true : false;
    var NS6 = (document.getElementById && !document.all) ? true : false;
    var IE  = IE4 || IE5;
    var NS  = NS4 || NS6;
    var Mac = (navigator.platform.indexOf("Mac") == -1) ? false : true;

    var sheet = "";

    if( NS4 )
    {
        sheet = "jspwiki_ns.css";
    }
    else if( Mac )
    {
        sheet = "jspwiki_mac.css";
    }
    else if( IE )
    {
        sheet = "jspwiki_ie.css";
    }

    if( sheet != "" )
    {
        document.write("<link rel=\"stylesheet\" href=\"templates/<wiki:TemplateDir />/"+sheet+"\" />");
    }
</script>
<commit_msg>Deleted, obsolete.<commit_after><script type="text/javascript">
/*
 *  Chooses a suitable stylesheet based on browser.
 */
    var IE4 = (document.all && !document.getElementById) ? true : false;
    var NS4 = (document.layers) ? true : false;
    var IE5 = (document.all && document.getElementById) ? true : false;
    var NS6 = (document.getElementById && !document.all) ? true : false;
    var IE  = IE4 || IE5;
    var NS  = NS4 || NS6;
    var Mac = (navigator.platform.indexOf("Mac") == -1) ? false : true;

    var sheet = "";

    if( NS4 )
    {
        sheet = "jspwiki_ns.css";
    }
    else if( Mac )
    {
        sheet = "jspwiki_mac.css";
    }
    else if( IE )
    {
        sheet = "jspwiki_ie.css";
    }

    if( sheet != "" )
    {
        document.write('<link rel="stylesheet" href="templates/<wiki:TemplateDir />/'+sheet+'"' />');
    }
</script>
<|endoftext|>


<commit_before>/*
 *  Chooses a suitable stylesheet based on browser.
 */
    var IE4 = (document.all && !document.getElementById) ? true : false;
    var NS4 = (document.layers) ? true : false;
    var IE5 = (document.all && document.getElementById) ? true : false;
    var NS6 = (document.getElementById && !document.all) ? true : false;
    var IE  = IE4 || IE5;
    var NS  = NS4 || NS6;
    var Mac = (navigator.platform.indexOf("Mac") == -1) ? false : true;

    var sheet;

    if( NS4 )
    {
        sheet = "jspwiki_ns.css";
    }
    else if( Mac )
    {
        sheet = "jspwiki_mac.css";
    }
    else
    {
        // Let's assume all the rest of the browsers are sane
        // and standard's compliant.
        sheet = "jspwiki_ie.css";
    }

    document.write("<link rel=\"stylesheet\" href=\"templates/<wiki:TemplateDir />/"+sheet+"\" />");
<commit_msg>Now actually works as intended...<commit_after><script type="text/javascript">
/*
 *  Chooses a suitable stylesheet based on browser.
 */
    var IE4 = (document.all && !document.getElementById) ? true : false;
    var NS4 = (document.layers) ? true : false;
    var IE5 = (document.all && document.getElementById) ? true : false;
    var NS6 = (document.getElementById && !document.all) ? true : false;
    var IE  = IE4 || IE5;
    var NS  = NS4 || NS6;
    var Mac = (navigator.platform.indexOf("Mac") == -1) ? false : true;

    var sheet = "";

    if( NS4 )
    {
        sheet = "jspwiki_ns.css";
    }
    else if( Mac )
    {
        sheet = "jspwiki_mac.css";
    }
    else if( IE )
    {
        sheet = "jspwiki_ie.css";
    }

    if( sheet != "" )
    {
        document.write("<link rel=\"stylesheet\" href=\"templates/<wiki:TemplateDir />/"+sheet+"\" />");
    }
</script>
<|endoftext|>


<commit_before>import { t } from 'ttag';
import ga from 'src/lib/ga';

import {
  POSTBACK_YES,
  POSTBACK_NO,
  ManipulationError,
  createTextMessage,
  MANUAL_FACT_CHECKERS,
  createPostbackAction,
  createAskArticleSubmissionConsentReply,
} from './utils';

import { TUTORIAL_STEPS } from './tutorial';

export default async function askingArticleSource(params) {
  let { data, state, event, userId, replies } = params;

  const visitor = ga(userId, state, data.searchedText);

  switch (event.input) {
    default:
      throw new ManipulationError(t`Please choose from provided options.`);

    case POSTBACK_NO:
      replies = [
        createTextMessage({
          text: t`Instructions`,
          contents: [
            {
              type: 'span',
              text: /* t: ~ entire message that ... */ t`I am a bot which only recognizes messages forwarded on LINE, therefore it is important to send me the`,
            },
            {
              type: 'span',
              text: /* t: emphasized text in sentence "It is important to send me the ~ that is being passed around" */ t` entire message `,
              color: '#ffb600',
              weight: 'bold',
            },
            {
              type: 'span',
              text: /* t: the entire message ~ */ t`that is being passed around so I can identify it.`,
            },
          ],
        }),
        createTextMessage({ text: t`You can try:` }),
        {
          type: 'flex',
          altText: t`Provide more detail`,
          contents: {
            type: 'carousel',
            contents: [
              {
                type: 'bubble',
                header: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`Try again with the whole message`,
                      wrap: true,
                      size: 'lg',
                      color: '#00B172',
                    },
                  ],
                  paddingBottom: 'none',
                },
                body: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`If you have access to the whole message on LINE, please use the “Share” function to share it with me.`,
                      wrap: true,
                    },
                  ],
                },
                footer: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'button',
                      action: createPostbackAction(
                        t`See Tutorial`,
                        TUTORIAL_STEPS.RICH_MENU,
                        TUTORIAL_STEPS.RICH_MENU,
                        data.sessionId,
                        'TUTORIAL'
                      ),
                      style: 'primary',
                      color: '#00B172',
                    },
                  ],
                  spacing: 'sm',
                },
              },
              {
                type: 'bubble',
                header: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`Find a real person`,
                      wrap: true,
                      size: 'lg',
                      color: '#333333',
                    },
                  ],
                  paddingBottom: 'none',
                },
                body: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`You can forward your question to another LINE account which provides a human response`,
                      wrap: true,
                    },
                  ],
                },
                footer: {
                  type: 'box',
                  layout: 'vertical',
                  contents: MANUAL_FACT_CHECKERS.map(({ label, value }) => ({
                    type: 'button',
                    action: {
                      type: 'uri',
                      label,
                      uri: value,
                    },
                    style: 'primary',
                    color: '#333333',
                  })),
                  spacing: 'sm',
                },
              },
            ],
          },
        },
      ];
      state = '__INIT__';
      visitor.event({
        ec: 'UserInput',
        ea: 'IsForwarded',
        el: 'No',
      });
      break;

    case POSTBACK_YES:
      replies = [
        createTextMessage({
          text: t`I see. Don’t trust the message just yet!`,
        }),
        createTextMessage({
          text: t`Do you want someone to fact-check this message?`,
        }),
        createAskArticleSubmissionConsentReply(data.sessionId),
      ];
      state = 'ASKING_ARTICLE_SUBMISSION_CONSENT';
      visitor.event({
        ec: 'UserInput',
        ea: 'IsForwarded',
        el: 'Yes',
      });
  }

  visitor.send();

  return { data, event, userId, replies };
}
<commit_msg>Remove unused state, it’s only updated by postback event now<commit_after>import { t } from 'ttag';
import ga from 'src/lib/ga';

import {
  POSTBACK_YES,
  POSTBACK_NO,
  ManipulationError,
  createTextMessage,
  MANUAL_FACT_CHECKERS,
  createPostbackAction,
  createAskArticleSubmissionConsentReply,
} from './utils';

import { TUTORIAL_STEPS } from './tutorial';

export default async function askingArticleSource(params) {
  let { data, state, event, userId, replies } = params;

  const visitor = ga(userId, state, data.searchedText);

  switch (event.input) {
    default:
      throw new ManipulationError(t`Please choose from provided options.`);

    case POSTBACK_NO:
      replies = [
        createTextMessage({
          text: t`Instructions`,
          contents: [
            {
              type: 'span',
              text: /* t: ~ entire message that ... */ t`I am a bot which only recognizes messages forwarded on LINE, therefore it is important to send me the`,
            },
            {
              type: 'span',
              text: /* t: emphasized text in sentence "It is important to send me the ~ that is being passed around" */ t` entire message `,
              color: '#ffb600',
              weight: 'bold',
            },
            {
              type: 'span',
              text: /* t: the entire message ~ */ t`that is being passed around so I can identify it.`,
            },
          ],
        }),
        createTextMessage({ text: t`You can try:` }),
        {
          type: 'flex',
          altText: t`Provide more detail`,
          contents: {
            type: 'carousel',
            contents: [
              {
                type: 'bubble',
                header: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`Try again with the whole message`,
                      wrap: true,
                      size: 'lg',
                      color: '#00B172',
                    },
                  ],
                  paddingBottom: 'none',
                },
                body: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`If you have access to the whole message on LINE, please use the “Share” function to share it with me.`,
                      wrap: true,
                    },
                  ],
                },
                footer: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'button',
                      action: createPostbackAction(
                        t`See Tutorial`,
                        TUTORIAL_STEPS.RICH_MENU,
                        TUTORIAL_STEPS.RICH_MENU,
                        data.sessionId,
                        'TUTORIAL'
                      ),
                      style: 'primary',
                      color: '#00B172',
                    },
                  ],
                  spacing: 'sm',
                },
              },
              {
                type: 'bubble',
                header: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`Find a real person`,
                      wrap: true,
                      size: 'lg',
                      color: '#333333',
                    },
                  ],
                  paddingBottom: 'none',
                },
                body: {
                  type: 'box',
                  layout: 'vertical',
                  contents: [
                    {
                      type: 'text',
                      text: t`You can forward your question to another LINE account which provides a human response`,
                      wrap: true,
                    },
                  ],
                },
                footer: {
                  type: 'box',
                  layout: 'vertical',
                  contents: MANUAL_FACT_CHECKERS.map(({ label, value }) => ({
                    type: 'button',
                    action: {
                      type: 'uri',
                      label,
                      uri: value,
                    },
                    style: 'primary',
                    color: '#333333',
                  })),
                  spacing: 'sm',
                },
              },
            ],
          },
        },
      ];
      visitor.event({
        ec: 'UserInput',
        ea: 'IsForwarded',
        el: 'No',
      });
      break;

    case POSTBACK_YES:
      replies = [
        createTextMessage({
          text: t`I see. Don’t trust the message just yet!`,
        }),
        createTextMessage({
          text: t`Do you want someone to fact-check this message?`,
        }),
        createAskArticleSubmissionConsentReply(data.sessionId),
      ];
      visitor.event({
        ec: 'UserInput',
        ea: 'IsForwarded',
        el: 'Yes',
      });
  }

  visitor.send();

  return { data, event, userId, replies };
}
<|endoftext|>


<commit_before>// A jQuery plugin for PAging and taILing data (i.e., a
// 'PAILer'). Paging occurs when scrolling reaches the "top" and
// tailing occurs when scrolling has reached the "bottom".

// A 'read' function must be provided for reading the data (in
// bytes). This function should expect an "options" object with the
// fields 'offset' and 'length' set for reading the data. The result
// from of the function should be a "promise" like value which has a
// 'success' and 'error' callback which each take a function. An
// object with at least two fields defined ('offset' and 'data') is
// expected on success. The length of 'data' may be smaller than the
// amount requested. If the offset requested is greater than the
// available offset, the result should be an object with the 'offset'
// field set to the available offset (i.e., the total length of the
// data) with an empty 'data' field.

// The plugin prepends, appends, and updates the "html" component of
// the elements specified in the jQuery selector (e.g., doing
// $('#data').pailer(...) means that data will be updated within
// $('#data') via $('#data').prepend(...) and $('#data').append(...)
// and $('#data').html(...) calls).

// An indicator paragraph element (i.e.,
) can be specified that
// the plugin will write text to describing any status/errors that
// have been encountered.

// Data will automagically get truncated at some specified length,
// configurable via the 'truncate-length' option. Likewise, the amount
// of data paged in at a time can be configured via the 'page-size'
// option.

// Example:
//   HTML:
//

//
//

//

//

//  Javascript:
//    $('#data').pailer({
//      'read': function(options) {
//        var settings = $.extend({
//          'offset': -1,
//          'length': -1
//        }, options);
//        var url = '/url/for/data'
//          + '?offset=' + settings.offset
//          + '&length=' + settings.length;
//        return $.getJSON(url);
//      },
//      'indicator': $('#indicator')
//    });

(function($) {
  // Helper for escaping html, based on _.escape from underscore.js.
  function escapeHTML(string) {
    if (string == null) {
      return '';
    }

    var escapes = {
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": ''',
      '/': '/'
    };
    var regex = new RegExp('[' + Object.keys(escapes).join('') + ']', 'g');

    return ('' + string).replace(regex, function (match) {
      return escapes[match];
    });
  }

  function Pailer(read, element, indicator, page_size, truncate_length) {
    var this_ = this;

    this_.read = read;
    this_.element = element;
    this_.indicator = indicator;
    this_.initialized = false;
    this_.start = -1;
    this_.end = -1;
    this_.paging = false;
    this_.tailing = true;

    page_size || $.error('Expecting page_size to be defined');
    truncate_length || $.error('Expecting truncate_length to be defined');

    this_.page_size = page_size;
    this_.truncate_length = truncate_length;

    this_.element.css('overflow', 'auto');

    this_.element.scroll(function () {
      var scrollTop = this_.element.scrollTop();
      var height = this_.element.height();
      var scrollHeight = this_.element[0].scrollHeight;

      if (scrollTop === 0) {
        this_.page();
      } else if (scrollTop + height >= scrollHeight) {
        if (!this_.tailing) {
          this_.tailing = true;
          this_.tail();
        }
      } else {
        this_.tailing = false;
      }
    });
  }

  Pailer.prototype.initialize = function() {
    var this_ = this;

    // Set an indicator while we load the data.
    this_.indicate('(LOADING)');

    this_.read({'offset': -1})
      .success(function(data) {
        this_.indicate('');

        // Get the last page of data.
        if (data.offset > this_.page_size) {
          this_.start = this_.end = data.offset - this_.page_size;
        } else {
          this_.start = this_.end = 0;
        }

        this_.initialized = true;
        this_.element.html('');
        setTimeout(function() { this_.tail(); }, 0);
      })
      .error(function(response, msg, code) {
        if ([401, 403].indexOf(response.status) > -1) {
          // Unauthorized user.
          this_.indicate('YOU ARE UNAUTHORIZED TO ACCESS THIS CONTENT');
        } else {
          this_.indicate('(FAILED TO INITIALIZE ... RETRYING)');
          setTimeout(function() {
            this_.indicate('');
            this_.initialize();
          }, 1000);
        }
      });
  };

  Pailer.prototype.page = function() {
    var this_ = this;

    if (!this_.initialized) {
      return;
    }

    if (this_.paging) {
      return;
    }

    this_.paging = true;
    this_.indicate('(PAGING)');

    if (this_.start === 0) {
      this_.paging = false;
      this_.indicate('(AT BEGINNING OF FILE)');
      setTimeout(function() { this_.indicate(''); }, 1000);
      return;
    }

    var offset = this_.start - this_.page_size;
    var length = this_.page_size;

    if (offset < 0) {
      offset = 0;
      length = this_.start;
    }

    // Buffer the data in case what gets read is less than 'length'.
    var buffer = '';

    var read = function(offset, length) {
      this_.read({'offset': offset, 'length': length})
        .success(function(data) {
          if (data.data.length < length) {
            buffer += data.data;
            read(offset + data.data.length, length - data.data.length);
          } else if (data.data.length > 0) {
            this_.indicate('(PAGED)');
            setTimeout(function() { this_.indicate(''); }, 1000);

            // Prepend buffer onto data.
            data.offset -= buffer.length;
            data.data = buffer + data.data;

            // Truncate to the first newline (unless this is the beginning).
            if (data.offset !== 0) {
              var index = data.data.indexOf('\n') + 1;
              data.offset += index;
              data.data = data.data.substring(index);
            }

            this_.start = data.offset;

            var scrollTop = this_.element.scrollTop();
            var scrollHeight = this_.element[0].scrollHeight;

            this_.element.prepend(escapeHTML(data.data));

            scrollTop += this_.element[0].scrollHeight - scrollHeight;
            this_.element.scrollTop(scrollTop);

            this_.paging = false;
          }
        })
        .error(function() {
          this_.indicate('(FAILED TO PAGE ... RETRYING)');
          setTimeout(function() {
            this_.indicate('');
            this_.page();
          }, 1000);
        });
    };

    read(offset, length);
  };

  Pailer.prototype.tail = function() {
    var this_ = this;

    if (!this_.initialized) {
      return;
    }

    this_.read({'offset': this_.end, 'length': this_.truncate_length})
      .success(function(data) {
        var scrollTop = this_.element.scrollTop();
        var height = this_.element.height();
        var scrollHeight = this_.element[0].scrollHeight;

        // Check if we are still at the bottom (since this event might
        // have fired before the scroll event has been dispatched).
        if (scrollTop + height < scrollHeight) {
          this_.tailing = false;
          return;
        }

        if (data.data.length > 0) {
          // Truncate to the first newline if this is the first time
          // (and we aren't reading from the beginning of the log).
          if (this_.start === this_.end && data.offset !== 0) {
            var index = data.data.indexOf('\n') + 1;
            data.offset += index;
            data.data = data.data.substring(index);
            this_.start = data.offset; // Adjust the actual start too!
          }

          this_.end = data.offset + data.data.length;

          this_.element.append(escapeHTML(data.data));

          scrollTop += this_.element[0].scrollHeight - scrollHeight;
          this_.element.scrollTop(scrollTop);

          // Also, only if we're at the bottom, truncate data so that we
          // don't consume too much memory. TODO(benh): Only do
          // truncations if we've been at the bottom for a while.
          this_.truncate();
        }

        // Tail immediately if we got as much data as requested (since
        // this probably means we've waited around a while). The
        // alternative here would be to not get data in chunks, but the
        // potential issue here is that we might end up requesting GB of
        // log data at a time ... the right solution here might be to do
        // a request to determine the new ending offset and then request
        // the proper length.
        if (data.data.length === this_.truncate_length) {
          setTimeout(function() { this_.tail(); }, 0);
        } else {
          setTimeout(function() { this_.tail(); }, 1000);
        }
      })
      .error(function() {
        this_.indicate('(FAILED TO TAIL ... RETRYING)');
        this_.initialized = false;
        setTimeout(function() {
          this_.indicate('');
          this_.initialize();
        }, 1000);
      });
  };

  Pailer.prototype.indicate = function(text) {
    var this_ = this;

    if (this_.indicator) {
      this_.indicator.text(text);
    }
  };

  Pailer.prototype.truncate = function() {
    var this_ = this;

    var length = this_.element.html().length;
    if (length >= this_.truncate_length) {
      var index = length - this_.truncate_length;
      this_.start = this_.end - this_.truncate_length;
      this_.element.html(this_.element.html().substring(index));
    }
  };

  $.fn.pailer = function(options) {
    var settings = $.extend({
      read: function() {
        return {
          success: function() {},
          error: function(f) { f(); }
        };
      },
      'page_size': 8 * 4096, // 8 "pages".
      'truncate_length': 50000
    }, options);

    this.each(function() {
      var pailer = $.data(this, 'pailer');
      if (!pailer) {
        pailer = new Pailer(settings.read,
                            $(this),
                            settings.indicator,
                            settings.page_size,
                            settings.truncate_length);
        $.data(this, 'pailer', pailer);
        pailer.initialize();
      }
    });
  };

})(jQuery);
<commit_msg>Updated pailer to work with jquery 3.2.1.<commit_after>// A jQuery plugin for PAging and taILing data (i.e., a
// 'PAILer'). Paging occurs when scrolling reaches the "top" and
// tailing occurs when scrolling has reached the "bottom".

// A 'read' function must be provided for reading the data (in
// bytes). This function should expect an "options" object with the
// fields 'offset' and 'length' set for reading the data. The result
// from of the function should be a "promise" like value which has a
// 'then' and 'fail' callback which each take a function. An
// object with at least two fields defined ('offset' and 'data') is
// expected on success. The length of 'data' may be smaller than the
// amount requested. If the offset requested is greater than the
// available offset, the result should be an object with the 'offset'
// field set to the available offset (i.e., the total length of the
// data) with an empty 'data' field.

// The plugin prepends, appends, and updates the "html" component of
// the elements specified in the jQuery selector (e.g., doing
// $('#data').pailer(...) means that data will be updated within
// $('#data') via $('#data').prepend(...) and $('#data').append(...)
// and $('#data').html(...) calls).

// An indicator paragraph element (i.e.,
) can be specified that
// the plugin will write text to describing any status/errors that
// have been encountered.

// Data will automagically get truncated at some specified length,
// configurable via the 'truncate-length' option. Likewise, the amount
// of data paged in at a time can be configured via the 'page-size'
// option.

// Example:
//   HTML:
//

//
//

//

//

//  Javascript:
//    $('#data').pailer({
//      'read': function(options) {
//        var settings = $.extend({
//          'offset': -1,
//          'length': -1
//        }, options);
//        var url = '/url/for/data'
//          + '?offset=' + settings.offset
//          + '&length=' + settings.length;
//        return $.getJSON(url);
//      },
//      'indicator': $('#indicator')
//    });

(function($) {
  // Helper for escaping html, based on _.escape from underscore.js.
  function escapeHTML(string) {
    if (string == null) {
      return '';
    }

    var escapes = {
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": ''',
      '/': '/'
    };
    var regex = new RegExp('[' + Object.keys(escapes).join('') + ']', 'g');

    return ('' + string).replace(regex, function (match) {
      return escapes[match];
    });
  }

  function Pailer(read, element, indicator, page_size, truncate_length) {
    var this_ = this;

    this_.read = read;
    this_.element = element;
    this_.indicator = indicator;
    this_.initialized = false;
    this_.start = -1;
    this_.end = -1;
    this_.paging = false;
    this_.tailing = true;

    page_size || $.error('Expecting page_size to be defined');
    truncate_length || $.error('Expecting truncate_length to be defined');

    this_.page_size = page_size;
    this_.truncate_length = truncate_length;

    this_.element.css('overflow', 'auto');

    this_.element.scroll(function () {
      var scrollTop = this_.element.scrollTop();
      var height = this_.element.height();
      var scrollHeight = this_.element[0].scrollHeight;

      if (scrollTop === 0) {
        this_.page();
      } else if (scrollTop + height >= scrollHeight) {
        if (!this_.tailing) {
          this_.tailing = true;
          this_.tail();
        }
      } else {
        this_.tailing = false;
      }
    });
  }

  Pailer.prototype.initialize = function() {
    var this_ = this;

    // Set an indicator while we load the data.
    this_.indicate('(LOADING)');

    this_.read({'offset': -1})
      .then(function(data) {
        this_.indicate('');

        // Get the last page of data.
        if (data.offset > this_.page_size) {
          this_.start = this_.end = data.offset - this_.page_size;
        } else {
          this_.start = this_.end = 0;
        }

        this_.initialized = true;
        this_.element.html('');
        setTimeout(function() { this_.tail(); }, 0);
      })
      .fail(function(response, msg, code) {
        if ([401, 403].indexOf(response.status) > -1) {
          // Unauthorized user.
          this_.indicate('YOU ARE UNAUTHORIZED TO ACCESS THIS CONTENT');
        } else {
          this_.indicate('(FAILED TO INITIALIZE ... RETRYING)');
          setTimeout(function() {
            this_.indicate('');
            this_.initialize();
          }, 1000);
        }
      });
  };

  Pailer.prototype.page = function() {
    var this_ = this;

    if (!this_.initialized) {
      return;
    }

    if (this_.paging) {
      return;
    }

    this_.paging = true;
    this_.indicate('(PAGING)');

    if (this_.start === 0) {
      this_.paging = false;
      this_.indicate('(AT BEGINNING OF FILE)');
      setTimeout(function() { this_.indicate(''); }, 1000);
      return;
    }

    var offset = this_.start - this_.page_size;
    var length = this_.page_size;

    if (offset < 0) {
      offset = 0;
      length = this_.start;
    }

    // Buffer the data in case what gets read is less than 'length'.
    var buffer = '';

    var read = function(offset, length) {
      this_.read({'offset': offset, 'length': length})
        .then(function(data) {
          if (data.data.length < length) {
            buffer += data.data;
            read(offset + data.data.length, length - data.data.length);
          } else if (data.data.length > 0) {
            this_.indicate('(PAGED)');
            setTimeout(function() { this_.indicate(''); }, 1000);

            // Prepend buffer onto data.
            data.offset -= buffer.length;
            data.data = buffer + data.data;

            // Truncate to the first newline (unless this is the beginning).
            if (data.offset !== 0) {
              var index = data.data.indexOf('\n') + 1;
              data.offset += index;
              data.data = data.data.substring(index);
            }

            this_.start = data.offset;

            var scrollTop = this_.element.scrollTop();
            var scrollHeight = this_.element[0].scrollHeight;

            this_.element.prepend(escapeHTML(data.data));

            scrollTop += this_.element[0].scrollHeight - scrollHeight;
            this_.element.scrollTop(scrollTop);

            this_.paging = false;
          }
        })
        .fail(function() {
          this_.indicate('(FAILED TO PAGE ... RETRYING)');
          setTimeout(function() {
            this_.indicate('');
            this_.page();
          }, 1000);
        });
    };

    read(offset, length);
  };

  Pailer.prototype.tail = function() {
    var this_ = this;

    if (!this_.initialized) {
      return;
    }

    this_.read({'offset': this_.end, 'length': this_.truncate_length})
      .then(function(data) {
        var scrollTop = this_.element.scrollTop();
        var height = this_.element.height();
        var scrollHeight = this_.element[0].scrollHeight;

        // Check if we are still at the bottom (since this event might
        // have fired before the scroll event has been dispatched).
        if (scrollTop + height < scrollHeight) {
          this_.tailing = false;
          return;
        }

        if (data.data.length > 0) {
          // Truncate to the first newline if this is the first time
          // (and we aren't reading from the beginning of the log).
          if (this_.start === this_.end && data.offset !== 0) {
            var index = data.data.indexOf('\n') + 1;
            data.offset += index;
            data.data = data.data.substring(index);
            this_.start = data.offset; // Adjust the actual start too!
          }

          this_.end = data.offset + data.data.length;

          this_.element.append(escapeHTML(data.data));

          scrollTop += this_.element[0].scrollHeight - scrollHeight;
          this_.element.scrollTop(scrollTop);

          // Also, only if we're at the bottom, truncate data so that we
          // don't consume too much memory. TODO(benh): Only do
          // truncations if we've been at the bottom for a while.
          this_.truncate();
        }

        // Tail immediately if we got as much data as requested (since
        // this probably means we've waited around a while). The
        // alternative here would be to not get data in chunks, but the
        // potential issue here is that we might end up requesting GB of
        // log data at a time ... the right solution here might be to do
        // a request to determine the new ending offset and then request
        // the proper length.
        if (data.data.length === this_.truncate_length) {
          setTimeout(function() { this_.tail(); }, 0);
        } else {
          setTimeout(function() { this_.tail(); }, 1000);
        }
      })
      .fail(function() {
        this_.indicate('(FAILED TO TAIL ... RETRYING)');
        this_.initialized = false;
        setTimeout(function() {
          this_.indicate('');
          this_.initialize();
        }, 1000);
      });
  };

  Pailer.prototype.indicate = function(text) {
    var this_ = this;

    if (this_.indicator) {
      this_.indicator.text(text);
    }
  };

  Pailer.prototype.truncate = function() {
    var this_ = this;

    var length = this_.element.html().length;
    if (length >= this_.truncate_length) {
      var index = length - this_.truncate_length;
      this_.start = this_.end - this_.truncate_length;
      this_.element.html(this_.element.html().substring(index));
    }
  };

  $.fn.pailer = function(options) {
    var settings = $.extend({
      read: function() {
        return {
          then: function() {},
          fail: function(f) { f(); }
        };
      },
      'page_size': 8 * 4096, // 8 "pages".
      'truncate_length': 50000
    }, options);

    this.each(function() {
      var pailer = $.data(this, 'pailer');
      if (!pailer) {
        pailer = new Pailer(settings.read,
                            $(this),
                            settings.indicator,
                            settings.page_size,
                            settings.truncate_length);
        $.data(this, 'pailer', pailer);
        pailer.initialize();
      }
    });
  };

})(jQuery);
<|endoftext|>


<commit_before>/**
 * Created by zcf on 2017/2/20.
 */
BI.DatePaneWidget = BI.inherit(BI.Widget, {
    _defaultConfig: function () {
        var conf = BI.DatePaneWidget.superclass._defaultConfig.apply(this, arguments);
        return BI.extend(conf, {
            baseCls: "bi-date-pane-widget",
            min: '1900-01-01', //最小日期
            max: '2099-12-31', //最大日期
            selectedTime: null
        })
    },
    _init: function () {
        BI.DatePaneWidget.superclass._init.apply(this, arguments);
        var self = this, o = this.options;

        this.today = new Date();
        this._year = this.today.getFullYear();
        this._month = this.today.getMonth();

        this.selectedTime = o.selectedTime || {
                year: this._year,
                month: this._month
            };

        this.datePicker = BI.createWidget({
            type: "bi.date_picker",
            min: o.min,
            max: o.max
        });
        this.datePicker.on(BI.DatePicker.EVENT_CHANGE, function () {
            self.selectedTime = self.datePicker.getValue();
            // self.selectedTime.day = 1;
            self.calendar.setSelect(BI.Calendar.getPageByDateJSON(self.selectedTime));
        });

        this.calendar = BI.createWidget({
            direction: "top",
            element: this,
            logic: {
                dynamic: true
            },
            type: "bi.navigation",
            tab: this.datePicker,
            cardCreator: BI.bind(this._createNav, this)

            // afterCardCreated: function () {
            //
            // },
            //
            // afterCardShow: function () {
            //     // this.setValue(self.selectedTime);
            // }
        });
        this.calendar.on(BI.Navigation.EVENT_CHANGE, function () {
            self.selectedTime = self.calendar.getValue();
            self.calendar.empty();
            self.setValue(self.selectedTime);
            self.fireEvent(BI.DateCalendarPopup.EVENT_CHANGE);
        });

    },

    _createNav: function (v) {
        var date = BI.Calendar.getDateJSONByPage(v);
        var calendar = BI.createWidget({
            type: "bi.calendar",
            logic: {
                dynamic: true
            },
            min: this.options.min,
            max: this.options.max,
            year: date.year,
            month: date.month,
            day: this.selectedTime.day
        });
        return calendar;
    },

    _getNewCurrentDate: function () {
        var today = new Date();
        return {
            year: today.getFullYear(),
            month: today.getMonth()
        }
    },

    _setCalenderValue: function (date) {
        this.calendar.setSelect(BI.Calendar.getPageByDateJSON(date));
        this.calendar.setValue(date);
        this.selectedTime = date;
    },

    _setDatePicker: function (timeOb) {
        if (BI.isNull(timeOb) || BI.isNull(timeOb.year) || BI.isNull(timeOb.month)) {
            this.datePicker.setValue(this._getNewCurrentDate());
        } else {
            this.datePicker.setValue(timeOb);
        }
    },

    _setCalendar: function (timeOb) {
        if (BI.isNull(timeOb) || BI.isNull(timeOb.day)) {
            this.calendar.empty();
            this._setCalenderValue(this._getNewCurrentDate());
        } else {
            this._setCalenderValue(timeOb)
        }
    },

    setValue: function (timeOb) {
        this._setDatePicker(timeOb);
        this._setCalendar(timeOb);
    },

    getValue: function () {
        return this.selectedTime;
    }

});
BI.shortcut("bi.date_pane_widget", BI.DatePaneWidget);<commit_msg>日期面板控件<commit_after>/**
 * Created by zcf on 2017/2/20.
 */
BI.DatePaneWidget = BI.inherit(BI.Widget, {
    _defaultConfig: function () {
        var conf = BI.DatePaneWidget.superclass._defaultConfig.apply(this, arguments);
        return BI.extend(conf, {
            baseCls: "bi-date-pane-widget",
            min: '1900-01-01', //最小日期
            max: '2099-12-31', //最大日期
            selectedTime: null
        })
    },
    _init: function () {
        BI.DatePaneWidget.superclass._init.apply(this, arguments);
        var self = this, o = this.options;

        this.today = new Date();
        this._year = this.today.getFullYear();
        this._month = this.today.getMonth();

        this.selectedTime = o.selectedTime || {
                year: this._year,
                month: this._month
            };

        this.datePicker = BI.createWidget({
            type: "bi.date_picker",
            min: o.min,
            max: o.max
        });
        this.datePicker.on(BI.DatePicker.EVENT_CHANGE, function () {
            self.selectedTime = self.datePicker.getValue();
            // self.selectedTime.day = 1;
            self.calendar.setSelect(BI.Calendar.getPageByDateJSON(self.selectedTime));
        });

        this.calendar = BI.createWidget({
            direction: "top",
            element: this,
            logic: {
                dynamic: false
            },
            type: "bi.navigation",
            tab: this.datePicker,
            cardCreator: BI.bind(this._createNav, this)

            // afterCardCreated: function () {
            //
            // },
            //
            // afterCardShow: function () {
            //     // this.setValue(self.selectedTime);
            // }
        });
        this.calendar.on(BI.Navigation.EVENT_CHANGE, function () {
            self.selectedTime = self.calendar.getValue();
            self.calendar.empty();
            self.setValue(self.selectedTime);
            self.fireEvent(BI.DateCalendarPopup.EVENT_CHANGE);
        });

    },

    _createNav: function (v) {
        var date = BI.Calendar.getDateJSONByPage(v);
        var calendar = BI.createWidget({
            type: "bi.calendar",
            logic: {
                dynamic: false
            },
            min: this.options.min,
            max: this.options.max,
            year: date.year,
            month: date.month,
            day: this.selectedTime.day
        });
        return calendar;
    },

    _getNewCurrentDate: function () {
        var today = new Date();
        return {
            year: today.getFullYear(),
            month: today.getMonth()
        }
    },

    _setCalenderValue: function (date) {
        this.calendar.setSelect(BI.Calendar.getPageByDateJSON(date));
        this.calendar.setValue(date);
        this.selectedTime = date;
    },

    _setDatePicker: function (timeOb) {
        if (BI.isNull(timeOb) || BI.isNull(timeOb.year) || BI.isNull(timeOb.month)) {
            this.datePicker.setValue(this._getNewCurrentDate());
        } else {
            this.datePicker.setValue(timeOb);
        }
    },

    _setCalendar: function (timeOb) {
        if (BI.isNull(timeOb) || BI.isNull(timeOb.day)) {
            this.calendar.empty();
            this._setCalenderValue(this._getNewCurrentDate());
        } else {
            this._setCalenderValue(timeOb)
        }
    },

    setValue: function (timeOb) {
        this._setDatePicker(timeOb);
        this._setCalendar(timeOb);
    },

    getValue: function () {
        return this.selectedTime;
    }

});
BI.shortcut("bi.date_pane_widget", BI.DatePaneWidget);<|endoftext|>


<commit_before>var request = require('request');
var async = require('async');
var jsdom = require('jsdom');

tags = ['html', 'head', 'body', 'p', 'h1', 'h2', 'h3', 'ol', 'ul', 'li', 'strong', 'em', 'br', 'title', 'div', 'span']

module.exports = function(url, cb) {
	if (!url) {
		return cb("URL not found")
	}
	tests = [{
		description: "submitted URL was accessible from the internet",
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					this.passed = true
					cb(null, this)
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}]

	for (var i = 0; i < tags.length; i++) {
		tests.push({
			tag: tags[i],
			description: "site has a `<" + tags[i] + ">` tag",
			assert: function(url, cb) {
				request(url, function(error, response, body) {
					if (!error && response.statusCode == 200) {
						jsdom.env(body, function(err, window) {
							if (window.document.getElementsByTagName(this.tag).length >= 1) {
								this.passed = true
							} else {
								this.passed = false
							}
							cb(null, this)
						}.bind(this))
					} else {
						this.passed = false
						cb(null, this)
					}
				}.bind(this))
			}
		})
	}

	tests.push({
		description: 'site has a `<meta name="viewport" content="width=device-width, initial-scale=1">` tag',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						metaTags = window.document.getElementsByTagName('meta')
						if ((metaTags.length >= 1) && (metaTags[0].getAttribute("content") == "width=device-width, initial-scale=1") && (metaTags[0].getAttribute("name") == "viewport")) {
							this.passed = true
						} else {
							this.passed = false
						}
						cb(null, this)
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<a>` tag that points to a *valid page* on a *different domain* (e.g. `href` starts with `http` or `https`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						aTags = window.document.getElementsByTagName('a')
						if (aTags.length >= 1) {
							async.detectSeries(aTags, function(aTag, asyncCB) {
								if (aTag.getAttribute('href').startsWith('http') || aTag.getAttribute('href').startsWith('https')) {
									request(aTag.getAttribute('href'), function(err, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}, function(aTag) {
								if (aTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<a>` tag that points to a *valid page* on the *same domain* using an *absolute path* (e.g. `href` starts with `/`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						aTags = window.document.getElementsByTagName('a')
						if (aTags.length >= 1) {
							async.detectSeries(aTags, function(aTag, asyncCB) {
								if (aTag.getAttribute('href').startsWith('/')) {
									pathArray = url.split('/');
									protocol = pathArray[0];
									host = pathArray[2];
									origin = protocol + '//' + host;
									request(origin + aTag.getAttribute('href'), function(errrr, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(aTag) {
								if (aTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<a>` tag that points to a *valid page* on the *same domain* using a *relative path* (e.g. `href` does not start starts with `/` or `http`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						aTags = window.document.getElementsByTagName('a')
						if (aTags.length >= 1) {
							async.detectSeries(aTags, function(aTag, asyncCB) {
								if (!aTag.getAttribute('href').startsWith('/') && !aTag.getAttribute('href').startsWith('http')) {
									// lop off the last item, replace with href
									pathArray = url.split('/');
									pathArray.splice(-1, 1)
									request(pathArray.join('/') + "/" + aTag.getAttribute('href'), function(err, response, body) {
										console.log(err, response.statusCode);
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(aTag) {
								if (aTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<img>` tag that points to a *valid image* on a *different domain* (e.g. `src` starts with `http` or `https`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						imgTags = window.document.getElementsByTagName('img')
						if (imgTags.length >= 1) {
							async.detectSeries(imgTags, function(imgTag, asyncCB) {
								if (imgTag.getAttribute('src').startsWith('http') || imgTag.getAttribute('src').startsWith('https')) {
									request(imgTag.getAttribute('src'), function(err, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}, function(imgTag) {
								if (imgTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<img>` tag that points to a *valid image* on the *same domain* using an *absolute path* (e.g. `src` starts with `/`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						imgTags = window.document.getElementsByTagName('img')
						if (imgTags.length >= 1) {
							async.detectSeries(imgTags, function(imgTag, asyncCB) {
								if (imgTag.getAttribute('src').startsWith('/')) {
									pathArray = url.split('/');
									protocol = pathArray[0];
									host = pathArray[2];
									origin = protocol + '//' + host;
									request(origin + imgTag.getAttribute('src'), function(errrr, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(imgTag) {
								if (imgTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<img>` tag that points to a *valid image* on the *same domain* using a *relative path* (e.g. `src` does not start starts with `/` or `http`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						imgTags = window.document.getElementsByTagName('img')
						if (imgTags.length >= 1) {
							async.detectSeries(imgTags, function(imgTag, asyncCB) {
								if (!imgTag.getAttribute('src').startsWith('/') && !imgTag.getAttribute('src').startsWith('http')) {
									// lop off the last item, replace with src
									pathArray = url.split('/');
									pathArray.splice(-1, 1)
									request(pathArray.join('/') + "/" + imgTag.getAttribute('src'), function(errrr, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(imgTag) {
								if (imgTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	})

	async.map(tests, function(test, cb) {
		test.assert(url, cb)
	}, function(err, results) {
		for (test of tests) {
			delete test.assert
		}
		scoreObject = {
			score: calculateScore(tests),
			tests: tests
		}
		return cb(null, scoreObject)
	})
}
<commit_msg>cleanup<commit_after>var request = require('request');
var async = require('async');
var jsdom = require('jsdom');

tags = ['html', 'head', 'body', 'p', 'h1', 'h2', 'h3', 'ol', 'ul', 'li', 'strong', 'em', 'br', 'title', 'div', 'span']

module.exports = function(url, cb) {
	if (!url) {
		return cb("URL not found")
	}
	tests = [{
		description: "submitted URL was accessible from the internet",
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					this.passed = true
					cb(null, this)
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}]

	for (var i = 0; i < tags.length; i++) {
		tests.push({
			tag: tags[i],
			description: "site has a `<" + tags[i] + ">` tag",
			assert: function(url, cb) {
				request(url, function(error, response, body) {
					if (!error && response.statusCode == 200) {
						jsdom.env(body, function(err, window) {
							if (window.document.getElementsByTagName(this.tag).length >= 1) {
								this.passed = true
							} else {
								this.passed = false
							}
							cb(null, this)
						}.bind(this))
					} else {
						this.passed = false
						cb(null, this)
					}
				}.bind(this))
			}
		})
	}

	tests.push({
		description: 'site has a `<meta name="viewport" content="width=device-width, initial-scale=1">` tag',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						metaTags = window.document.getElementsByTagName('meta')
						if ((metaTags.length >= 1) && (metaTags[0].getAttribute("content") == "width=device-width, initial-scale=1") && (metaTags[0].getAttribute("name") == "viewport")) {
							this.passed = true
						} else {
							this.passed = false
						}
						cb(null, this)
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<a>` tag that points to a *valid page* on a *different domain* (e.g. `href` starts with `http` or `https`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						aTags = window.document.getElementsByTagName('a')
						if (aTags.length >= 1) {
							async.detectSeries(aTags, function(aTag, asyncCB) {
								if (aTag.getAttribute('href').startsWith('http') || aTag.getAttribute('href').startsWith('https')) {
									request(aTag.getAttribute('href'), function(err, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}, function(aTag) {
								if (aTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<a>` tag that points to a *valid page* on the *same domain* using an *absolute path* (e.g. `href` starts with `/`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						aTags = window.document.getElementsByTagName('a')
						if (aTags.length >= 1) {
							async.detectSeries(aTags, function(aTag, asyncCB) {
								if (aTag.getAttribute('href').startsWith('/')) {
									pathArray = url.split('/');
									protocol = pathArray[0];
									host = pathArray[2];
									origin = protocol + '//' + host;
									request(origin + aTag.getAttribute('href'), function(errrr, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(aTag) {
								if (aTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<a>` tag that points to a *valid page* on the *same domain* using a *relative path* (e.g. `href` does not start starts with `/` or `http`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						aTags = window.document.getElementsByTagName('a')
						if (aTags.length >= 1) {
							async.detectSeries(aTags, function(aTag, asyncCB) {
								if (!aTag.getAttribute('href').startsWith('/') && !aTag.getAttribute('href').startsWith('http')) {
									// lop off the last item, replace with href
									pathArray = url.split('/');
									pathArray.splice(-1, 1)
									request(pathArray.join('/') + "/" + aTag.getAttribute('href'), function(err, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(aTag) {
								if (aTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<img>` tag that points to a *valid image* on a *different domain* (e.g. `src` starts with `http` or `https`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						imgTags = window.document.getElementsByTagName('img')
						if (imgTags.length >= 1) {
							async.detectSeries(imgTags, function(imgTag, asyncCB) {
								if (imgTag.getAttribute('src').startsWith('http') || imgTag.getAttribute('src').startsWith('https')) {
									request(imgTag.getAttribute('src'), function(err, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}, function(imgTag) {
								if (imgTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<img>` tag that points to a *valid image* on the *same domain* using an *absolute path* (e.g. `src` starts with `/`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						imgTags = window.document.getElementsByTagName('img')
						if (imgTags.length >= 1) {
							async.detectSeries(imgTags, function(imgTag, asyncCB) {
								if (imgTag.getAttribute('src').startsWith('/')) {
									pathArray = url.split('/');
									protocol = pathArray[0];
									host = pathArray[2];
									origin = protocol + '//' + host;
									request(origin + imgTag.getAttribute('src'), function(errrr, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(imgTag) {
								if (imgTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	}, {
		description: 'site has an `<img>` tag that points to a *valid image* on the *same domain* using a *relative path* (e.g. `src` does not start starts with `/` or `http`)',
		assert: function(url, cb) {
			request(url, function(error, response, body) {
				if (!error && response.statusCode == 200) {
					jsdom.env(body, function(err, window) {
						imgTags = window.document.getElementsByTagName('img')
						if (imgTags.length >= 1) {
							async.detectSeries(imgTags, function(imgTag, asyncCB) {
								if (!imgTag.getAttribute('src').startsWith('/') && !imgTag.getAttribute('src').startsWith('http')) {
									// lop off the last item, replace with src
									pathArray = url.split('/');
									pathArray.splice(-1, 1)
									request(pathArray.join('/') + "/" + imgTag.getAttribute('src'), function(errrr, response, body) {
										if (parseInt(response.statusCode) >= 400) {
											asyncCB(false)
										} else {
											asyncCB(true)
										}
									})
								} else {
									asyncCB(false)
								}
							}.bind(this), function(imgTag) {
								if (imgTag) {
									this.passed = true
								} else {
									this.passed = false
								}
								cb(null, this)
							}.bind(this))
						} else {
							this.passed = false
							cb(null, this)
						}
					}.bind(this))
				} else {
					this.passed = false
					cb(null, this)
				}
			}.bind(this))
		}
	})

	async.map(tests, function(test, cb) {
		test.assert(url, cb)
	}, function(err, results) {
		for (test of tests) {
			delete test.assert
		}
		scoreObject = {
			score: calculateScore(tests),
			tests: tests
		}
		return cb(null, scoreObject)
	})
}
<|endoftext|>


<commit_before>/* global google Gabra $ */
// $(document).ready(function () {
  google.load('visualization', '1.0', {'packages': ['corechart']})
  google.setOnLoadCallback(function () {
    $.ajax({
      method: 'GET',
      url: Gabra.api_url + 'logs/chart',
      success: function (data) {
        var table = new google.visualization.DataTable()
        table.addColumn('date', 'Date')

        // Just totals
        table.addColumn('number', Gabra.i18n.updates)
        for (var day in data) {
          table.addRow([new Date(day), data[day]['total']])
        }

        var options = {
          legend: {
            position: 'none'
          },
          hAxis: {
            textPosition: 'none',
            gridlines: {
              count: 0
            }
          },
          vAxis: {
            textPosition: 'none',
            gridlines: {
              count: 4,
              color: '#eeeeee'
            }
          },
          height: 100,
          chartArea: {
            width: '100%',
            height: '90%'
          }
        }
        var chart = new google.visualization.AreaChart(document.getElementById('log-chart'))
        chart.draw(table, options)
      },
      complete: function () {
      }
    })
  })
// })
<commit_msg>Hide log chart when empty<commit_after>/* global google Gabra $ */
// $(document).ready(function () {
  google.load('visualization', '1.0', {'packages': ['corechart']})
  google.setOnLoadCallback(function () {
    $.ajax({
      method: 'GET',
      url: Gabra.api_url + 'logs/chart',
      success: function (data) {
        var table = new google.visualization.DataTable()
        table.addColumn('date', 'Date')

        // Just totals
        var empty = true
        table.addColumn('number', Gabra.i18n.updates)
        for (var day in data) {
          if (data[day]['total'] > 0) empty = false
          table.addRow([new Date(day), data[day]['total']])
        }
        if (empty) return

        var options = {
          legend: {
            position: 'none'
          },
          hAxis: {
            textPosition: 'none',
            gridlines: {
              count: 0
            }
          },
          vAxis: {
            textPosition: 'none',
            gridlines: {
              count: 4,
              color: '#eeeeee'
            }
          },
          height: 100,
          chartArea: {
            width: '100%',
            height: '90%'
          }
        }
        var chart = new google.visualization.AreaChart(document.getElementById('log-chart'))
        chart.draw(table, options)
      },
      complete: function () {
      }
    })
  })
// })
<|endoftext|>


<commit_before>// Format times as minutes and seconds.
var timeFormat = function(secs) {
    if (secs == undefined || isNaN(secs)) {
        return '0:00';
    }
    secs = Math.round(secs);
    var mins = '' + Math.round(secs / 60);
    secs = '' + (secs % 60);
    if (secs.length < 2) {
        secs = '0' + secs;
    }
    return mins + ':' + secs;
}

// jQuery extension encapsulating event hookups for audio element controls.
$.fn.player = function(debug) {
    // Selected element should contain an HTML5 Audio element.
    var audio = $('audio', this).get(0);

    // Control elements that may be present, identified by class.
    var playBtn = $('.play', this);
    var pauseBtn = $('.pause', this);
    var disabledInd = $('.disabled', this);
    var timesEl = $('.times', this);
    var curTimeEl = $('.currentTime', this);
    var totalTimeEl  = $('.totalTime', this);
    var sliderPlayedEl = $('.slider .played', this);
    var sliderLoadedEl = $('.slider .loaded', this);

    // Button events.
    playBtn.click(function() {
        audio.play();
    });
    pauseBtn.click(function(ev) {
        audio.pause();
    });

    // Utilities.
    var timePercent = function(cur, total) {
        if (cur == undefined || isNaN(cur) ||
                total == undefined || isNaN(total) || total == 0) {
            return 0;
        }
        var ratio = cur / total;
        if (ratio > 1.0) {
            ratio = 1.0;
        }
        return (Math.round(ratio * 10000) / 100) + '%';
    }

    // Event helpers.
    var dbg = function(msg) {
        if (debug)
            console.log(msg);
    }
    var showState = function() {
        if (audio.duration == undefined || isNaN(audio.duration)) {
            playBtn.hide();
            pauseBtn.hide();
            disabledInd.show();
            timesEl.hide();
        } else if (audio.paused) {
            playBtn.show();
            pauseBtn.hide();
            disabledInd.hide();
            timesEl.show();
        } else {
            playBtn.hide();
            pauseBtn.show();
            disabledInd.hide();
            timesEl.show();
        }
    }
    var showTimes = function() {
        curTimeEl.text(timeFormat(audio.currentTime));
        totalTimeEl.text(timeFormat(audio.duration));

        sliderPlayedEl.css('width',
                timePercent(audio.currentTime, audio.duration));

        // last time buffered
        var bufferEnd = 0;
        for (var i = 0; i < audio.buffered.length; ++i) {
            if (audio.buffered.end(i) > bufferEnd)
                bufferEnd = audio.buffered.end(i);
        }
        sliderLoadedEl.css('width',
                timePercent(bufferEnd, audio.duration));
    }

    // Initialize controls.
    showState();
    showTimes();

    // Bind events.
    $('audio', this).bind({
        playing: function() {
            dbg('playing');
            showState();
        },
        pause: function() {
            dbg('pause');
            showState();
        },
        ended: function() {
            dbg('ended');
            showState();
        },
        progress: function() {
            dbg('progress ' + audio.buffered);
        },
        timeupdate: function() {
            dbg('timeupdate ' + audio.currentTime);
            showTimes();
        },
        durationchange: function() {
            dbg('durationchange ' + audio.duration);
            showState();
            showTimes();
        },
        loadeddata: function() {
            dbg('loadeddata');
        },
        loadedmetadata: function() {
            dbg('loadedmetadata');
        }
    });
}

// Simple selection disable for jQuery.
// Cut-and-paste from:
// http://stackoverflow.com/questions/2700000
$.fn.disableSelection = function() {
    $(this).attr('unselectable', 'on')
           .css('-moz-user-select', 'none')
           .each(function() {
               this.onselectstart = function() { return false; };
            });
};

$(function() {

// Routes.
var BeetsRouter = Backbone.Router.extend({
    routes: {
        "item/query/:query": "itemQuery",
    },
    itemQuery: function(query) {
        var queryURL = query.split(/\s+/).map(encodeURIComponent).join('/');
        $.getJSON('item/query/' + queryURL, function(data) {
            var models = _.map(
                data['results'],
                function(d) { return new Item(d); }
            );
            var results = new Items(models);
            app.showItems(results);
        });
    }
});
var router = new BeetsRouter();

// Model.
var Item = Backbone.Model.extend({
    urlRoot: 'item'
});
var Items = Backbone.Collection.extend({
    model: Item
});

// Item views.
var ItemEntryView = Backbone.View.extend({
    tagName: "li",
    template: _.template($('#item-entry-template').html()),
    events: {
        'click': 'select',
        'dblclick': 'play'
    },
    initialize: function() {
        this.playing = false;
    },
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        this.setPlaying(this.playing);
        return this;
    },
    select: function() {
        app.selectItem(this);
    },
    play: function() {
        app.playItem(this.model);
    },
    setPlaying: function(val) {
        this.playing = val;
        if (val)
            this.$('.playing').show();
        else
            this.$('.playing').hide();
    }
});
//Holds Title, Artist, Album etc.
var ItemMainDetailView = Backbone.View.extend({
    tagName: "div",
    template: _.template($('#item-main-detail-template').html()),
    events: {
        'click .play': 'play',
    },
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
    },
    play: function() {
        app.playItem(this.model);
    }
});
// Holds Track no., Format, MusicBrainz link, Lyrics, Comments etc.
var ItemExtraDetailView = Backbone.View.extend({
    tagName: "div",
    template: _.template($('#item-extra-detail-template').html()),
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
    }
});
// Main app view.
var AppView = Backbone.View.extend({
    el: $('body'),
    events: {
        'submit #queryForm': 'querySubmit',
    },
    querySubmit: function(ev) {
        ev.preventDefault();
        router.navigate('item/query/' + encodeURIComponent($('#query').val()), true);
    },
    initialize: function() {
        this.playingItem = null;
        this.shownItems = null;

        // Not sure why these events won't bind automatically.
        this.$('audio').bind({
            'play': _.bind(this.audioPlay, this),
            'pause': _.bind(this.audioPause, this),
            'ended': _.bind(this.audioEnded, this)
        });
    },
    showItems: function(items) {
        this.shownItems = items;
        $('#results').empty();
        items.each(function(item) {
            var view = new ItemEntryView({model: item});
            item.entryView = view;
            $('#results').append(view.render().el);
        });
    },
    selectItem: function(view) {
        // Mark row as selected.
        $('#results li').removeClass("selected");
        $(view.el).addClass("selected");

        // Show main and extra detail.
        var mainDetailView = new ItemMainDetailView({model: view.model});
        $('#main-detail').empty().append(mainDetailView.render().el);

        var extraDetailView = new ItemExtraDetailView({model: view.model});
        $('#extra-detail').empty().append(extraDetailView.render().el);
    },
    playItem: function(item) {
        var url = 'item/' + item.get('id') + '/file';
        $('#player audio').attr('src', url);
        $('#player audio').get(0).play();

        if (this.playingItem != null) {
            this.playingItem.entryView.setPlaying(false);
        }
        item.entryView.setPlaying(true);
        this.playingItem = item;
    },

    audioPause: function() {
        this.playingItem.entryView.setPlaying(false);
    },
    audioPlay: function() {
        if (this.playingItem != null)
            this.playingItem.entryView.setPlaying(true);
    },
    audioEnded: function() {
        this.playingItem.entryView.setPlaying(false);

        // Try to play the next track.
        var idx = this.shownItems.indexOf(this.playingItem);
        if (idx == -1) {
            // Not in current list.
            return;
        }
        var nextIdx = idx + 1;
        if (nextIdx >= this.shownItems.size()) {
            // End of  list.
            return;
        }
        this.playItem(this.shownItems.at(nextIdx));
    }
});
var app = new AppView();

// App setup.
Backbone.history.start({pushState: false});

// Disable selection on UI elements.
$('#entities ul').disableSelection();
$('#header').disableSelection();

// Audio player setup.
$('#player').player();

});
<commit_msg>Fix jumping time in beets.js<commit_after>// Format times as minutes and seconds.
var timeFormat = function(secs) {
    if (secs == undefined || isNaN(secs)) {
        return '0:00';
    }
    secs = Math.round(secs);
    var mins = '' + Math.floor(secs / 60);
    secs = '' + (secs % 60);
    if (secs.length < 2) {
        secs = '0' + secs;
    }
    return mins + ':' + secs;
}

// jQuery extension encapsulating event hookups for audio element controls.
$.fn.player = function(debug) {
    // Selected element should contain an HTML5 Audio element.
    var audio = $('audio', this).get(0);

    // Control elements that may be present, identified by class.
    var playBtn = $('.play', this);
    var pauseBtn = $('.pause', this);
    var disabledInd = $('.disabled', this);
    var timesEl = $('.times', this);
    var curTimeEl = $('.currentTime', this);
    var totalTimeEl  = $('.totalTime', this);
    var sliderPlayedEl = $('.slider .played', this);
    var sliderLoadedEl = $('.slider .loaded', this);

    // Button events.
    playBtn.click(function() {
        audio.play();
    });
    pauseBtn.click(function(ev) {
        audio.pause();
    });

    // Utilities.
    var timePercent = function(cur, total) {
        if (cur == undefined || isNaN(cur) ||
                total == undefined || isNaN(total) || total == 0) {
            return 0;
        }
        var ratio = cur / total;
        if (ratio > 1.0) {
            ratio = 1.0;
        }
        return (Math.round(ratio * 10000) / 100) + '%';
    }

    // Event helpers.
    var dbg = function(msg) {
        if (debug)
            console.log(msg);
    }
    var showState = function() {
        if (audio.duration == undefined || isNaN(audio.duration)) {
            playBtn.hide();
            pauseBtn.hide();
            disabledInd.show();
            timesEl.hide();
        } else if (audio.paused) {
            playBtn.show();
            pauseBtn.hide();
            disabledInd.hide();
            timesEl.show();
        } else {
            playBtn.hide();
            pauseBtn.show();
            disabledInd.hide();
            timesEl.show();
        }
    }
    var showTimes = function() {
        curTimeEl.text(timeFormat(audio.currentTime));
        totalTimeEl.text(timeFormat(audio.duration));

        sliderPlayedEl.css('width',
                timePercent(audio.currentTime, audio.duration));

        // last time buffered
        var bufferEnd = 0;
        for (var i = 0; i < audio.buffered.length; ++i) {
            if (audio.buffered.end(i) > bufferEnd)
                bufferEnd = audio.buffered.end(i);
        }
        sliderLoadedEl.css('width',
                timePercent(bufferEnd, audio.duration));
    }

    // Initialize controls.
    showState();
    showTimes();

    // Bind events.
    $('audio', this).bind({
        playing: function() {
            dbg('playing');
            showState();
        },
        pause: function() {
            dbg('pause');
            showState();
        },
        ended: function() {
            dbg('ended');
            showState();
        },
        progress: function() {
            dbg('progress ' + audio.buffered);
        },
        timeupdate: function() {
            dbg('timeupdate ' + audio.currentTime);
            showTimes();
        },
        durationchange: function() {
            dbg('durationchange ' + audio.duration);
            showState();
            showTimes();
        },
        loadeddata: function() {
            dbg('loadeddata');
        },
        loadedmetadata: function() {
            dbg('loadedmetadata');
        }
    });
}

// Simple selection disable for jQuery.
// Cut-and-paste from:
// http://stackoverflow.com/questions/2700000
$.fn.disableSelection = function() {
    $(this).attr('unselectable', 'on')
           .css('-moz-user-select', 'none')
           .each(function() {
               this.onselectstart = function() { return false; };
            });
};

$(function() {

// Routes.
var BeetsRouter = Backbone.Router.extend({
    routes: {
        "item/query/:query": "itemQuery",
    },
    itemQuery: function(query) {
        var queryURL = query.split(/\s+/).map(encodeURIComponent).join('/');
        $.getJSON('item/query/' + queryURL, function(data) {
            var models = _.map(
                data['results'],
                function(d) { return new Item(d); }
            );
            var results = new Items(models);
            app.showItems(results);
        });
    }
});
var router = new BeetsRouter();

// Model.
var Item = Backbone.Model.extend({
    urlRoot: 'item'
});
var Items = Backbone.Collection.extend({
    model: Item
});

// Item views.
var ItemEntryView = Backbone.View.extend({
    tagName: "li",
    template: _.template($('#item-entry-template').html()),
    events: {
        'click': 'select',
        'dblclick': 'play'
    },
    initialize: function() {
        this.playing = false;
    },
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        this.setPlaying(this.playing);
        return this;
    },
    select: function() {
        app.selectItem(this);
    },
    play: function() {
        app.playItem(this.model);
    },
    setPlaying: function(val) {
        this.playing = val;
        if (val)
            this.$('.playing').show();
        else
            this.$('.playing').hide();
    }
});
//Holds Title, Artist, Album etc.
var ItemMainDetailView = Backbone.View.extend({
    tagName: "div",
    template: _.template($('#item-main-detail-template').html()),
    events: {
        'click .play': 'play',
    },
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
    },
    play: function() {
        app.playItem(this.model);
    }
});
// Holds Track no., Format, MusicBrainz link, Lyrics, Comments etc.
var ItemExtraDetailView = Backbone.View.extend({
    tagName: "div",
    template: _.template($('#item-extra-detail-template').html()),
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
    }
});
// Main app view.
var AppView = Backbone.View.extend({
    el: $('body'),
    events: {
        'submit #queryForm': 'querySubmit',
    },
    querySubmit: function(ev) {
        ev.preventDefault();
        router.navigate('item/query/' + encodeURIComponent($('#query').val()), true);
    },
    initialize: function() {
        this.playingItem = null;
        this.shownItems = null;

        // Not sure why these events won't bind automatically.
        this.$('audio').bind({
            'play': _.bind(this.audioPlay, this),
            'pause': _.bind(this.audioPause, this),
            'ended': _.bind(this.audioEnded, this)
        });
    },
    showItems: function(items) {
        this.shownItems = items;
        $('#results').empty();
        items.each(function(item) {
            var view = new ItemEntryView({model: item});
            item.entryView = view;
            $('#results').append(view.render().el);
        });
    },
    selectItem: function(view) {
        // Mark row as selected.
        $('#results li').removeClass("selected");
        $(view.el).addClass("selected");

        // Show main and extra detail.
        var mainDetailView = new ItemMainDetailView({model: view.model});
        $('#main-detail').empty().append(mainDetailView.render().el);

        var extraDetailView = new ItemExtraDetailView({model: view.model});
        $('#extra-detail').empty().append(extraDetailView.render().el);
    },
    playItem: function(item) {
        var url = 'item/' + item.get('id') + '/file';
        $('#player audio').attr('src', url);
        $('#player audio').get(0).play();

        if (this.playingItem != null) {
            this.playingItem.entryView.setPlaying(false);
        }
        item.entryView.setPlaying(true);
        this.playingItem = item;
    },

    audioPause: function() {
        this.playingItem.entryView.setPlaying(false);
    },
    audioPlay: function() {
        if (this.playingItem != null)
            this.playingItem.entryView.setPlaying(true);
    },
    audioEnded: function() {
        this.playingItem.entryView.setPlaying(false);

        // Try to play the next track.
        var idx = this.shownItems.indexOf(this.playingItem);
        if (idx == -1) {
            // Not in current list.
            return;
        }
        var nextIdx = idx + 1;
        if (nextIdx >= this.shownItems.size()) {
            // End of  list.
            return;
        }
        this.playItem(this.shownItems.at(nextIdx));
    }
});
var app = new AppView();

// App setup.
Backbone.history.start({pushState: false});

// Disable selection on UI elements.
$('#entities ul').disableSelection();
$('#header').disableSelection();

// Audio player setup.
$('#player').player();

});
<|endoftext|>