<commit_before>/**
 * DropdownWidgets are not menus themselves, rather they contain a menu of options created with
 * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that
 * users can interact with it.
 *
 * If you want to use this within an HTML form, such as a OO.ui.FormLayout, use
 * OO.ui.DropdownInputWidget instead.
 *
 *     @example
 *     // A DropdownWidget with a menu that contains three options.
 *     var dropDown = new OO.ui.DropdownWidget( {
 *         label: 'Dropdown menu: Select a menu option',
 *         menu: {
 *             items: [
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'a',
 *                     label: 'First'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'b',
 *                     label: 'Second'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'c',
 *                     label: 'Third'
 *                 } )
 *             ]
 *         }
 *     } );
 *
 *     $( document.body ).append( dropDown.$element );
 *
 *     dropDown.getMenu().selectItemByData( 'b' );
 *
 *     dropDown.getMenu().findSelectedItem().getData(); // Returns 'b'.
 *
 * For more information, please see the [OOUI documentation on MediaWiki] [1].
 *
 * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options#Menu_selects_and_options
 *
 * @class
 * @extends OO.ui.Widget
 * @mixins OO.ui.mixin.IconElement
 * @mixins OO.ui.mixin.IndicatorElement
 * @mixins OO.ui.mixin.LabelElement
 * @mixins OO.ui.mixin.TitledElement
 * @mixins OO.ui.mixin.TabIndexedElement
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {Object} [menu] Configuration options to pass to {@link OO.ui.MenuSelectWidget menu select widget}
 * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
 *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
 *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
 *  See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
 */
OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
	// Configuration initialization
	config = $.extend( { indicator: 'down' }, config );

	// Parent constructor
	OO.ui.DropdownWidget.parent.call( this, config );

	// Properties (must be set before TabIndexedElement constructor call)
	this.$handle = $( '<button>' );
	this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element;

	// Mixin constructors
	OO.ui.mixin.IconElement.call( this, config );
	OO.ui.mixin.IndicatorElement.call( this, config );
	OO.ui.mixin.LabelElement.call( this, config );
	OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) );
	OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );

	// Properties
	this.menu = new OO.ui.MenuSelectWidget( $.extend( {
		widget: this,
		$floatableContainer: this.$element
	}, config.menu ) );

	// Events
	this.$handle.on( {
		click: this.onClick.bind( this ),
		keydown: this.onKeyDown.bind( this ),
		// Hack? Handle type-to-search when menu is not expanded and not handling its own events
		keypress: this.menu.onDocumentKeyPressHandler,
		blur: this.menu.clearKeyPressBuffer.bind( this.menu )
	} );
	this.menu.connect( this, {
		select: 'onMenuSelect',
		toggle: 'onMenuToggle'
	} );

	// Initialization
	this.$handle
		.addClass( 'oo-ui-dropdownWidget-handle' )
		.attr( {
			'aria-owns': this.menu.getElementId(),
			'aria-haspopup': 'listbox'
		} )
		.append( this.$icon, this.$label, this.$indicator );
	this.$element
		.addClass( 'oo-ui-dropdownWidget' )
		.append( this.$handle );
	this.$overlay.append( this.menu.$element );
};

/* Setup */

OO.inheritClass( OO.ui.DropdownWidget, OO.ui.Widget );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IconElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IndicatorElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.LabelElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TitledElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TabIndexedElement );

/* Methods */

/**
 * Get the menu.
 *
 * @return {OO.ui.MenuSelectWidget} Menu of widget
 */
OO.ui.DropdownWidget.prototype.getMenu = function () {
	return this.menu;
};

/**
 * Handles menu select events.
 *
 * @private
 * @param {OO.ui.MenuOptionWidget} item Selected menu item
 */
OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
	var selectedLabel;

	if ( !item ) {
		this.setLabel( null );
		return;
	}

	selectedLabel = item.getLabel();

	// If the label is a DOM element, clone it, because setLabel will append() it
	if ( selectedLabel instanceof $ ) {
		selectedLabel = selectedLabel.clone();
	}

	this.setLabel( selectedLabel );
};

/**
 * Handle menu toggle events.
 *
 * @private
 * @param {boolean} isVisible Open state of the menu
 */
OO.ui.DropdownWidget.prototype.onMenuToggle = function ( isVisible ) {
	this.$element.toggleClass( 'oo-ui-dropdownWidget-open', isVisible );
};

/**
 * Handle mouse click events.
 *
 * @private
 * @param {jQuery.Event} e Mouse click event
 * @return {undefined/boolean} False to prevent default if event is handled
 */
OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
	if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
		this.menu.toggle();
	}
	return false;
};

/**
 * Handle key down events.
 *
 * @private
 * @param {jQuery.Event} e Key down event
 * @return {undefined/boolean} False to prevent default if event is handled
 */
OO.ui.DropdownWidget.prototype.onKeyDown = function ( e ) {
	if (
		!this.isDisabled() &&
		(
			e.which === OO.ui.Keys.ENTER ||
			(
				e.which === OO.ui.Keys.SPACE &&
				// Avoid conflicts with type-to-search, see SelectWidget#onKeyPress.
				// Space only closes the menu is the user is not typing to search.
				this.menu.keyPressBuffer === ''
			) ||
			(
				!this.menu.isVisible() &&
				(
					e.which === OO.ui.Keys.UP ||
					e.which === OO.ui.Keys.DOWN
				)
			)
		)
	) {
		this.menu.toggle();
		return false;
	}
};
<commit_msg>DropdownWidget: `$handle` needs to carry `type="button"`<commit_after>/**
 * DropdownWidgets are not menus themselves, rather they contain a menu of options created with
 * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that
 * users can interact with it.
 *
 * If you want to use this within an HTML form, such as a OO.ui.FormLayout, use
 * OO.ui.DropdownInputWidget instead.
 *
 *     @example
 *     // A DropdownWidget with a menu that contains three options.
 *     var dropDown = new OO.ui.DropdownWidget( {
 *         label: 'Dropdown menu: Select a menu option',
 *         menu: {
 *             items: [
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'a',
 *                     label: 'First'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'b',
 *                     label: 'Second'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'c',
 *                     label: 'Third'
 *                 } )
 *             ]
 *         }
 *     } );
 *
 *     $( document.body ).append( dropDown.$element );
 *
 *     dropDown.getMenu().selectItemByData( 'b' );
 *
 *     dropDown.getMenu().findSelectedItem().getData(); // Returns 'b'.
 *
 * For more information, please see the [OOUI documentation on MediaWiki] [1].
 *
 * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options#Menu_selects_and_options
 *
 * @class
 * @extends OO.ui.Widget
 * @mixins OO.ui.mixin.IconElement
 * @mixins OO.ui.mixin.IndicatorElement
 * @mixins OO.ui.mixin.LabelElement
 * @mixins OO.ui.mixin.TitledElement
 * @mixins OO.ui.mixin.TabIndexedElement
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {Object} [menu] Configuration options to pass to {@link OO.ui.MenuSelectWidget menu select widget}
 * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
 *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
 *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
 *  See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
 */
OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
	// Configuration initialization
	config = $.extend( { indicator: 'down' }, config );

	// Parent constructor
	OO.ui.DropdownWidget.parent.call( this, config );

	// Properties (must be set before TabIndexedElement constructor call)
	this.$handle = $( '<button>' );
	this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element;

	// Mixin constructors
	OO.ui.mixin.IconElement.call( this, config );
	OO.ui.mixin.IndicatorElement.call( this, config );
	OO.ui.mixin.LabelElement.call( this, config );
	OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) );
	OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );

	// Properties
	this.menu = new OO.ui.MenuSelectWidget( $.extend( {
		widget: this,
		$floatableContainer: this.$element
	}, config.menu ) );

	// Events
	this.$handle.on( {
		click: this.onClick.bind( this ),
		keydown: this.onKeyDown.bind( this ),
		// Hack? Handle type-to-search when menu is not expanded and not handling its own events
		keypress: this.menu.onDocumentKeyPressHandler,
		blur: this.menu.clearKeyPressBuffer.bind( this.menu )
	} );
	this.menu.connect( this, {
		select: 'onMenuSelect',
		toggle: 'onMenuToggle'
	} );

	// Initialization
	this.$handle
		.addClass( 'oo-ui-dropdownWidget-handle' )
		.attr( {
			type: 'button',
			'aria-owns': this.menu.getElementId(),
			'aria-haspopup': 'listbox'
		} )
		.append( this.$icon, this.$label, this.$indicator );
	this.$element
		.addClass( 'oo-ui-dropdownWidget' )
		.append( this.$handle );
	this.$overlay.append( this.menu.$element );
};

/* Setup */

OO.inheritClass( OO.ui.DropdownWidget, OO.ui.Widget );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IconElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.IndicatorElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.LabelElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TitledElement );
OO.mixinClass( OO.ui.DropdownWidget, OO.ui.mixin.TabIndexedElement );

/* Methods */

/**
 * Get the menu.
 *
 * @return {OO.ui.MenuSelectWidget} Menu of widget
 */
OO.ui.DropdownWidget.prototype.getMenu = function () {
	return this.menu;
};

/**
 * Handles menu select events.
 *
 * @private
 * @param {OO.ui.MenuOptionWidget} item Selected menu item
 */
OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
	var selectedLabel;

	if ( !item ) {
		this.setLabel( null );
		return;
	}

	selectedLabel = item.getLabel();

	// If the label is a DOM element, clone it, because setLabel will append() it
	if ( selectedLabel instanceof $ ) {
		selectedLabel = selectedLabel.clone();
	}

	this.setLabel( selectedLabel );
};

/**
 * Handle menu toggle events.
 *
 * @private
 * @param {boolean} isVisible Open state of the menu
 */
OO.ui.DropdownWidget.prototype.onMenuToggle = function ( isVisible ) {
	this.$element.toggleClass( 'oo-ui-dropdownWidget-open', isVisible );
};

/**
 * Handle mouse click events.
 *
 * @private
 * @param {jQuery.Event} e Mouse click event
 * @return {undefined/boolean} False to prevent default if event is handled
 */
OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
	if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
		this.menu.toggle();
	}
	return false;
};

/**
 * Handle key down events.
 *
 * @private
 * @param {jQuery.Event} e Key down event
 * @return {undefined/boolean} False to prevent default if event is handled
 */
OO.ui.DropdownWidget.prototype.onKeyDown = function ( e ) {
	if (
		!this.isDisabled() &&
		(
			e.which === OO.ui.Keys.ENTER ||
			(
				e.which === OO.ui.Keys.SPACE &&
				// Avoid conflicts with type-to-search, see SelectWidget#onKeyPress.
				// Space only closes the menu is the user is not typing to search.
				this.menu.keyPressBuffer === ''
			) ||
			(
				!this.menu.isVisible() &&
				(
					e.which === OO.ui.Keys.UP ||
					e.which === OO.ui.Keys.DOWN
				)
			)
		)
	) {
		this.menu.toggle();
		return false;
	}
};
<|endoftext|>


<commit_before>"use strict";
var etg = require("./etg");
var Cards = require("./Cards");
var etgutil = require("./etgutil");
var elecols = [
	"#a98", "#a59", "#768", "#963", "#654", "#5a0", "#c52", "#258", "#887", "#38d", "#ca2", "#333", "#5ac",
	"#dcb", "#dbc", "#bac", "#cb9", "#ba9", "#ac7", "#da8", "#8ac", "#ccb", "#9be", "#ed8", "#999", "#ade"];
var cssPrefix = ""+ret+"</svg>";
}
exports.deck = function(deck){
	function classString(){
		var ret = "";
		for(var cls in classes){
			ret += "."+cls+"{"+classes[cls]+"}";
		}
		return ret;
	}
	var prefix = "<svg xmlns='http://www.w3.org/2000/svg'",
		texts="", x=16, y=0, classes = {}, mark, paths = {}, suffix, pathsvg="";
	etgutil.iterdeck(deck, function(code){
		if (!(code in Cards.Codes)){
			var ismark = etg.fromTrueMark(code);
			if (~ismark) mark = ismark;
			return;
		}
		var card = Cards.Codes[code];
		var elech = eleChar(card), elecls = (card.shiny?"A":"B") + " " + elech;
		if (card.shiny) classes.A = "stroke:#daa520;stroke-width:.5";
		else classes.B = "stroke:black;stroke-width:.5";
		classes[elech] = "fill:"+elecols[card.element+(card.upped?13:0)];
		var textColor = card.upped ? "" : " fill='#fff'";
		if (!paths[elecls]) paths[elecls]="";
		paths[elecls]+="M"+x+" "+y+"h100v16h-100";
		texts += "<text x='" + (x+2) + "' y='" + (y+13) + "'" + textColor + ">" + card.name + "</text>";
		y += 16;
		if (y == 160){
			y=0;
			x+=100;
		}
	});
	for (var elecls in paths){
		pathsvg += "<path class='"+elecls+"' d='"+paths[elecls]+"'/>";
	}
	if (mark !== undefined){
		var cls = String.fromCharCode(97+mark);
		classes[cls] = "fill:"+elecols[mark];
		suffix = "<path class='"+cls+"' d='M0 0h16v160H0'/><text x='5' y='-4' transform='rotate(90)'"+(~[0,8,10,12].indexOf(mark)?"":" fill='#fff'")+">"+etg.eleNames[mark]+"</text></svg>";
	}else suffix = "</svg>"
	return prefix+" height='160' width='"+(y?x+100:x)+"'>"+cssPrefix+classString()+"]]></style>"+pathsvg+texts+suffix;
}
<commit_msg>Card svg: include rarity/type<commit_after>"use strict";
var etg = require("./etg");
var Cards = require("./Cards");
var etgutil = require("./etgutil");
var elecols = [
	"#a98", "#a59", "#768", "#963", "#654", "#5a0", "#c52", "#258", "#887", "#38d", "#ca2", "#333", "#5ac",
	"#dcb", "#dbc", "#bac", "#cb9", "#ba9", "#ac7", "#da8", "#8ac", "#ccb", "#9be", "#ed8", "#999", "#ade"];
var cssPrefix = ""+ret+"</svg>";
}
exports.deck = function(deck){
	function classString(){
		var ret = "";
		for(var cls in classes){
			ret += "."+cls+"{"+classes[cls]+"}";
		}
		return ret;
	}
	var prefix = "<svg xmlns='http://www.w3.org/2000/svg'",
		texts="", x=16, y=0, classes = {}, mark, paths = {}, suffix, pathsvg="";
	etgutil.iterdeck(deck, function(code){
		if (!(code in Cards.Codes)){
			var ismark = etg.fromTrueMark(code);
			if (~ismark) mark = ismark;
			return;
		}
		var card = Cards.Codes[code];
		var elech = eleChar(card), elecls = (card.shiny?"A":"B") + " " + elech;
		if (card.shiny) classes.A = "stroke:#daa520;stroke-width:.5";
		else classes.B = "stroke:black;stroke-width:.5";
		classes[elech] = "fill:"+elecols[card.element+(card.upped?13:0)];
		var textColor = card.upped ? "" : " fill='#fff'";
		if (!paths[elecls]) paths[elecls]="";
		paths[elecls]+="M"+x+" "+y+"h100v16h-100";
		texts += "<text x='" + (x+2) + "' y='" + (y+13) + "'" + textColor + ">" + card.name + "</text>";
		y += 16;
		if (y == 160){
			y=0;
			x+=100;
		}
	});
	for (var elecls in paths){
		pathsvg += "<path class='"+elecls+"' d='"+paths[elecls]+"'/>";
	}
	if (mark !== undefined){
		var cls = String.fromCharCode(97+mark);
		classes[cls] = "fill:"+elecols[mark];
		suffix = "<path class='"+cls+"' d='M0 0h16v160H0'/><text x='5' y='-4' transform='rotate(90)'"+(~[0,8,10,12].indexOf(mark)?"":" fill='#fff'")+">"+etg.eleNames[mark]+"</text></svg>";
	}else suffix = "</svg>"
	return prefix+" height='160' width='"+(y?x+100:x)+"'>"+cssPrefix+classString()+"]]></style>"+pathsvg+texts+suffix;
}
<|endoftext|>


<commit_before>// Brunch configuration
// See http://brunch.io for documentation.
'use strict';

module.exports = {
  files: {
    javascripts: {
      joinTo: {
        'scripts/main.js': ['app/scripts/**/*.js', /^node_modules/]
      }
    },
    stylesheets: {
      joinTo: {
        'styles/main.css': ['app/styles/main.scss']
      }
    }
  },
  modules: {
    autoRequire: {
      'scripts/main.js': ['scripts/main']
    }
  },
  paths: {
    // Exclude test files from compilation
    watched: ['app']
  },
  plugins: {
    copycat: {
      fonts: ['node_modules/typeface-ubuntu/files'],
      verbose: false,
      onlyChanged: true
    },
    postcss: {
      processors: [
        require('autoprefixer')({
          browsers: ['> 0.1%']
        })
      ]
    },
    swPrecache: {
      swFileName: 'service-worker.js',
      options: {
        cacheId: 'connect-four',
        staticFileGlobs: ['public/**/!(*map*)'],
        stripPrefix: 'public',
        replacePrefix: '/connect-four'
      }
    }
  }
};
<commit_msg>Copy only the required fonts when building<commit_after>// Brunch configuration
// See http://brunch.io for documentation.
'use strict';

module.exports = {
  files: {
    javascripts: {
      joinTo: {
        'scripts/main.js': ['app/scripts/**/*.js', /^node_modules/]
      }
    },
    stylesheets: {
      joinTo: {
        'styles/main.css': ['app/styles/main.scss']
      }
    }
  },
  modules: {
    autoRequire: {
      'scripts/main.js': ['scripts/main']
    }
  },
  paths: {
    // Exclude test files from compilation
    watched: ['app']
  },
  plugins: {
    copycat: {
      fonts: [
        'node_modules/typeface-ubuntu/files/ubuntu-latin-400.woff2',
        'node_modules/typeface-ubuntu/files/ubuntu-latin-400.woff',
        'node_modules/typeface-ubuntu/files/ubuntu-latin-400.eot',
        'node_modules/typeface-ubuntu/files/ubuntu-latin-400.svg'
      ],
      verbose: false,
      onlyChanged: true
    },
    postcss: {
      processors: [
        require('autoprefixer')({
          browsers: ['> 0.1%']
        })
      ]
    },
    swPrecache: {
      swFileName: 'service-worker.js',
      options: {
        cacheId: 'connect-four',
        staticFileGlobs: ['public/**/!(*map*)'],
        stripPrefix: 'public',
        replacePrefix: '/connect-four'
      }
    }
  }
};
<|endoftext|>


<commit_before>import * as BudgetsActions from '../../actions/budgets';
import Form from './Item/Form';
import List from './List';
import * as search from 'searchtabular';
import React, { Component } from 'react';
import Table from './Table';
import { connect } from 'react-redux';
import { Route, Link } from 'react-router-dom';
import { compose } from 'redux';
import { multiInfix } from '../../helpers/utils';

class Budgets extends Component {
	constructor(props) {
		super();

    this.state = {
      filter: {
        field: 'summary',
        value: '',
      },
    };

    this.filterItems = this.filterItems.bind(this);
    this.onAdd    = this.onAdd.bind(this);
    this.onDelete = this.onDelete.bind(this);
    this.onEdit   = this.onEdit.bind(this);
    this.onFilter = this.onFilter.bind(this);
    this.onFormSubmit = this.onFormSubmit.bind(this);
    this.renderList = this.renderList.bind(this);
    this.renderTable = this.renderTable.bind(this);
    this.search   = this.search.bind(this);
    this.toggleColumn = this.toggleColumn.bind(this);
  }

  componentDidMount() {
    this.props.dispatch(BudgetsActions.subscribe());
  }

  filterItems(items, query) {
    const rows = this.props.items;
    const columns = this.props.fields.map((field) => ({
      property: field.name,
      header: {
        label: field.label,
      },
      filterType:field.filterType,
    }));

    const searchExecutor = search.multipleColumns({
      columns,
      query,
      strategy: multiInfix });
    const visibleItems = compose(searchExecutor)(rows);

    return visibleItems;
  }

  onFilter({ field = this.state.filter.field, value = this.state.filter.value }) {
    this.setState({
      filter: {
        field,
        value,
      },
    });

    const newQuery = {
      ...this.props.query,
      [field]: value,
    };

    this.props.dispatch(BudgetsActions.search({ query: newQuery }));
  }

  onFormSubmit(item) {
    this.onAdd(item);
  }

  onAdd(item) {
    this.props.dispatch(BudgetsActions.addItem({ item }));
  }

  onDelete(itemId) {
    this.props.dispatch(BudgetsActions.removeItem({ itemId }));
  }

  onEdit(updatedItem) {
    this.props.dispatch(BudgetsActions.updateItem({ updatedItem }));
  }

  renderList() {

     return (
       <List
         items={this.props.items}
         filter={this.state.filter}
         filterItems={this.filterItems}
         fields={this.props.fields}
         onFilter={this.onFilter}
         onEdit={this.onEdit}
         onDelete={this.onDelete}
         query={this.props.query}
       />
     );
   }

   renderTable() {

    return (

    );
   }

   search(query) {
     this.props.dispatch(BudgetsActions.search({ query }));
   }

   toggleColumn(payload){
     this.props.dispatch(BudgetsActions.toggleColumn({ columnName: payload.columnName }));
   }

  render() {

    return (

Budget Tool

            <Form
              onSubmit={this.onFormSubmit}
              fields={this.props.fields}
            />

View Selection

                <Link to={this.props.match.url + '/list'}>List</Link>

                <Link to={this.props.match.url + '/table'}>Table</Link>

            <Route
              exact path={this.props.match.url}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/list'}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/table'}
              render={this.renderTable}
            />

    );
  }
}

const mapStateToProps = state => ({
  items: state.budgets.items,
  fields: state.budgets.fields,
  query: state.budgets.query,
  userColumns: state.budgets.userColumns,
})

export default connect(mapStateToProps)(Budgets);
<commit_msg>Remove onFormSubmit<commit_after>import * as BudgetsActions from '../../actions/budgets';
import Form from './Item/Form';
import List from './List';
import * as search from 'searchtabular';
import React, { Component } from 'react';
import Table from './Table';
import { connect } from 'react-redux';
import { Route, Link } from 'react-router-dom';
import { compose } from 'redux';
import { multiInfix } from '../../helpers/utils';

class Budgets extends Component {
	constructor(props) {
		super();

    this.state = {
      filter: {
        field: 'summary',
        value: '',
      },
    };

    this.filterItems = this.filterItems.bind(this);
    this.onAdd    = this.onAdd.bind(this);
    this.onDelete = this.onDelete.bind(this);
    this.onEdit   = this.onEdit.bind(this);
    this.onFilter = this.onFilter.bind(this);
    this.renderList = this.renderList.bind(this);
    this.renderTable = this.renderTable.bind(this);
    this.search   = this.search.bind(this);
    this.toggleColumn = this.toggleColumn.bind(this);
  }

  componentDidMount() {
    this.props.dispatch(BudgetsActions.subscribe());
  }

  filterItems(items, query) {
    const rows = this.props.items;
    const columns = this.props.fields.map((field) => ({
      property: field.name,
      header: {
        label: field.label,
      },
      filterType:field.filterType,
    }));

    const searchExecutor = search.multipleColumns({
      columns,
      query,
      strategy: multiInfix });
    const visibleItems = compose(searchExecutor)(rows);

    return visibleItems;
  }

  onFilter({ field = this.state.filter.field, value = this.state.filter.value }) {
    this.setState({
      filter: {
        field,
        value,
      },
    });

    const newQuery = {
      ...this.props.query,
      [field]: value,
    };

    this.props.dispatch(BudgetsActions.search({ query: newQuery }));
  }

  onAdd(item) {
    this.props.dispatch(BudgetsActions.addItem({ item }));
  }

  onDelete(itemId) {
    this.props.dispatch(BudgetsActions.removeItem({ itemId }));
  }

  onEdit(updatedItem) {
    this.props.dispatch(BudgetsActions.updateItem({ updatedItem }));
  }

  renderList() {

     return (
       <List
         items={this.props.items}
         filter={this.state.filter}
         filterItems={this.filterItems}
         fields={this.props.fields}
         onFilter={this.onFilter}
         onEdit={this.onEdit}
         onDelete={this.onDelete}
         query={this.props.query}
       />
     );
   }

   renderTable() {

    return (

    );
   }

   search(query) {
     this.props.dispatch(BudgetsActions.search({ query }));
   }

   toggleColumn(payload){
     this.props.dispatch(BudgetsActions.toggleColumn({ columnName: payload.columnName }));
   }

  render() {

    return (

Budget Tool

            <Form
              onSubmit={this.onAdd}
              fields={this.props.fields}
            />

View Selection

                <Link to={this.props.match.url + '/list'}>List</Link>

                <Link to={this.props.match.url + '/table'}>Table</Link>

            <Route
              exact path={this.props.match.url}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/list'}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/table'}
              render={this.renderTable}
            />

    );
  }
}

const mapStateToProps = state => ({
  items: state.budgets.items,
  fields: state.budgets.fields,
  query: state.budgets.query,
  userColumns: state.budgets.userColumns,
})

export default connect(mapStateToProps)(Budgets);
<|endoftext|>


<commit_before>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* 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.
*/

'use strict';

var fs = require('fs');
var request = require('request');
var util = require('util');

var profile = require('../../../util/profile');
var utils = require('../../../util/utils');

var groupUtils = require('./groupUtils');

var $ = utils.getLocaleString;

var azure = require('azure');

exports.init = function(cli) {
  profile.addKnownResourceNamespace('successbricks.cleardb', 'microsoft.insights');
  profile.addKnownProvider('website', 'visualstudio.account', 'sqlserver');

  var log = cli.output;
  var group = cli.category('group');

  var groupTemplate = group.category('template')
    .description($('Commands to manage your local or gallery resource group template'));

  groupTemplate.command('list')
    .description($('Lists gallery resource group templates'))
    .option('-c --category [category]', $('the category of the templates to list'))
    .option('-p --publisher [publisher]', $('the publisher of the templates to list'))
    .option('--subscription <subscription>', $('the Azure subscription to run against'))
    .execute(function (options, _) {
      var client = createGalleryClient(profile.current.getSubscription(options.subscription));

      var filters = [];
      if (options.publisher) {
        filters.push(util.format('Publisher eq \'%s\'', options.publisher));
      }

      if (options.category) {
        filters.push(util.format('CategoryIds/any(c: c eq \'%s\')', options.category));
      }

      var result = cli.interaction.withProgress($('Listing gallery resource group templates'),
        function (log, _) {
          return client.items.list(filters.length === 0 ? null : { filter: filters.join(' and ') }, _);
        }, _);

      cli.interaction.formatOutput(result.items, function (data) {
        if (data.length === 0) {
          log.info($('No gallery resource group templates'));
        } else {
          var validItems = data.filter(function (c) {
            return !utils.stringEndsWith(c.version, '-placeholder', true);
          });
          var sortedItems = validItems.sort(function (left, right) {
            return left.publisher.localeCompare(right.publisher);
          });
          log.table(sortedItems, function (row, item) {
            row.cell($('Publisher'), item.publisher);
            row.cell($('Name'), item.identity);
          });
        }
      });
    });

  groupTemplate.command('show [name]')
    .description($('Shows a gallery resource group template'))
    .usage('[options] <name>')
    .option('-n --name <name>', $('the name of template to show'))
    .option('--subscription <subscription>', $('the Azure subscription to run against'))
    .execute(function (name, options, _) {

      if (!name) {
        return cli.missingArgument('name');
      }

      var client = createGalleryClient(profile.current.getSubscription(options.subscription));

      var result = cli.interaction.withProgress($('Showing a gallery resource group template'),
        function (log, _) {
          return client.items.get(name, _);
        }, _);

      cli.interaction.formatOutput(result.item, function (data) {
        log.data($('Name:        '), data.identity);
        log.data($('Publisher:   '), data.publisher);
        log.data($('Version:     '), data.version);
        log.data($('Url:         '), groupUtils.getTemplateDownloadUrl(data));
        log.data($('Summary:     '), data.summary);
        log.data($('Description: '), data.description);
      });
    });

  groupTemplate.command('download [name] [file]')
    .description($('Downloads a gallery resource group template'))
    .usage('[options] [name] [file]')
    .option('-n --name <name>', $('the name of the template to download'))
    .option('-f --file <file>', $('the name of the destination directory or file'))
    .option('-q --quiet', $('quiet mode (do not prompt for overwrite if output file exists)'))
    .option('--subscription <subscription>', $('the Azure subscription to run against'))
    .execute(function (name, file, options, _) {

      if (!name) {
        return cli.missingArgument('name');
      }
      var confirm = cli.interaction.confirm.bind(cli.interaction);

      var downloadFileName = groupUtils.normalizeDownloadFileName(name, file, options.quiet, confirm, _);
      if (!downloadFileName) {
        // overwrite not confirmed, stop
        return;
      }

      var client = createGalleryClient(profile.current.getSubscription(options.subscription));
      var result = cli.interaction.withProgress(
        util.format($('Getting gallery resource group template %s'), name),
        function (log, _) {
          return client.items.get(name, _);
        }, _);

      var downloadUrl = groupUtils.getTemplateDownloadUrl(result.item);
      var waitForDownloadEnd = function (stream, callback) {
        stream.on('close', function () {
          callback(null);
        });
        stream.on('error', function (ex) {
          callback(ex);
        });
      };

      return waitForDownloadEnd(request(downloadUrl).pipe(fs.createWriteStream(downloadFileName)), _);
    });

  groupTemplate.command('validate [resource-group]')
    .description($('Validates a template to see whether it\'s using the right syntax, resource providers, resource types, etc.'))
    .usage('[options] <resource-group>')
    .option('-g --resource-group <resource-group>', $('the name of the resource group'))
    .option('-y --gallery-template <gallery-template>', $('the name of the template in the gallery'))
    .option('-f --template-file <template-file>', $('the path to the template file in the file system'))
    .option('--template-uri <template-uri>', $('the uri to the remote template file'))
    //TODO: comment out till ARM supports contentHash
    //.option('--template-hash <template-hash>', $('the content hash of the template'))
    //.option('--template-hash-algorithm <template-hash-algorithm>', $('the algorithm used to hash the template content'))
    .option('--template-version <template-version>', $('the content version of the template'))
    .option('-s --storage-account <storage-account>', $('the storage account where we will upload the template file'))
    //TODO: comment out till ARM supports validation mode
    //.option('-m --mode <mode>', $('the mode of the template deployment. Valid values are Replace, New and Incremental'))
    .option('-p --parameters <parameters>', $('a JSON-formatted string containing parameters'))
    .option('-e --parameters-file <parametersFile>', $('a file containing parameters'))
    //TODO: comment out till ARM supports contentHash
    //.option('--parameters-hash <parameters-hash>', $('the content hash of the parameters'))
    //.option('--parameters-hash-algorithm <parameters-hash-algorithm>', $('the algorithm used to hash the parameters content'))
    //.option('--parameters-version <parameters-version>', $('the expected content version of the parameters'))
    .option('--subscription <subscription>', $('the subscription identifier'))
    .execute(function (resourceGroup, options, _) {
      if (!resourceGroup) {
        return cli.missingArgument('resourceGroup');
      }
      groupUtils.validateTemplate(cli, resourceGroup, options, _);
    });
};

function createGalleryClient(subscription) {
  return utils.createClient('createGalleryClient', new azure.AnonymousCloudCredentials(),
    subscription.galleryEndpointUrl);
}
<commit_msg>[RBAC] Use CSM RP registration API for website and VSO in XplatCLI<commit_after>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* 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.
*/

'use strict';

var fs = require('fs');
var request = require('request');
var util = require('util');

var profile = require('../../../util/profile');
var utils = require('../../../util/utils');

var groupUtils = require('./groupUtils');

var $ = utils.getLocaleString;

var azure = require('azure');

exports.init = function(cli) {
  profile.addKnownResourceNamespace('microsoft.web', 'microsoft.visualstudio', 'successbricks.cleardb', 'microsoft.insights');
  profile.addKnownProvider('website', 'sqlserver');

  var log = cli.output;
  var group = cli.category('group');

  var groupTemplate = group.category('template')
    .description($('Commands to manage your local or gallery resource group template'));

  groupTemplate.command('list')
    .description($('Lists gallery resource group templates'))
    .option('-c --category [category]', $('the category of the templates to list'))
    .option('-p --publisher [publisher]', $('the publisher of the templates to list'))
    .option('--subscription <subscription>', $('the Azure subscription to run against'))
    .execute(function (options, _) {
      var client = createGalleryClient(profile.current.getSubscription(options.subscription));

      var filters = [];
      if (options.publisher) {
        filters.push(util.format('Publisher eq \'%s\'', options.publisher));
      }

      if (options.category) {
        filters.push(util.format('CategoryIds/any(c: c eq \'%s\')', options.category));
      }

      var result = cli.interaction.withProgress($('Listing gallery resource group templates'),
        function (log, _) {
          return client.items.list(filters.length === 0 ? null : { filter: filters.join(' and ') }, _);
        }, _);

      cli.interaction.formatOutput(result.items, function (data) {
        if (data.length === 0) {
          log.info($('No gallery resource group templates'));
        } else {
          var validItems = data.filter(function (c) {
            return !utils.stringEndsWith(c.version, '-placeholder', true);
          });
          var sortedItems = validItems.sort(function (left, right) {
            return left.publisher.localeCompare(right.publisher);
          });
          log.table(sortedItems, function (row, item) {
            row.cell($('Publisher'), item.publisher);
            row.cell($('Name'), item.identity);
          });
        }
      });
    });

  groupTemplate.command('show [name]')
    .description($('Shows a gallery resource group template'))
    .usage('[options] <name>')
    .option('-n --name <name>', $('the name of template to show'))
    .option('--subscription <subscription>', $('the Azure subscription to run against'))
    .execute(function (name, options, _) {

      if (!name) {
        return cli.missingArgument('name');
      }

      var client = createGalleryClient(profile.current.getSubscription(options.subscription));

      var result = cli.interaction.withProgress($('Showing a gallery resource group template'),
        function (log, _) {
          return client.items.get(name, _);
        }, _);

      cli.interaction.formatOutput(result.item, function (data) {
        log.data($('Name:        '), data.identity);
        log.data($('Publisher:   '), data.publisher);
        log.data($('Version:     '), data.version);
        log.data($('Url:         '), groupUtils.getTemplateDownloadUrl(data));
        log.data($('Summary:     '), data.summary);
        log.data($('Description: '), data.description);
      });
    });

  groupTemplate.command('download [name] [file]')
    .description($('Downloads a gallery resource group template'))
    .usage('[options] [name] [file]')
    .option('-n --name <name>', $('the name of the template to download'))
    .option('-f --file <file>', $('the name of the destination directory or file'))
    .option('-q --quiet', $('quiet mode (do not prompt for overwrite if output file exists)'))
    .option('--subscription <subscription>', $('the Azure subscription to run against'))
    .execute(function (name, file, options, _) {

      if (!name) {
        return cli.missingArgument('name');
      }
      var confirm = cli.interaction.confirm.bind(cli.interaction);

      var downloadFileName = groupUtils.normalizeDownloadFileName(name, file, options.quiet, confirm, _);
      if (!downloadFileName) {
        // overwrite not confirmed, stop
        return;
      }

      var client = createGalleryClient(profile.current.getSubscription(options.subscription));
      var result = cli.interaction.withProgress(
        util.format($('Getting gallery resource group template %s'), name),
        function (log, _) {
          return client.items.get(name, _);
        }, _);

      var downloadUrl = groupUtils.getTemplateDownloadUrl(result.item);
      var waitForDownloadEnd = function (stream, callback) {
        stream.on('close', function () {
          callback(null);
        });
        stream.on('error', function (ex) {
          callback(ex);
        });
      };

      return waitForDownloadEnd(request(downloadUrl).pipe(fs.createWriteStream(downloadFileName)), _);
    });

  groupTemplate.command('validate [resource-group]')
    .description($('Validates a template to see whether it\'s using the right syntax, resource providers, resource types, etc.'))
    .usage('[options] <resource-group>')
    .option('-g --resource-group <resource-group>', $('the name of the resource group'))
    .option('-y --gallery-template <gallery-template>', $('the name of the template in the gallery'))
    .option('-f --template-file <template-file>', $('the path to the template file in the file system'))
    .option('--template-uri <template-uri>', $('the uri to the remote template file'))
    //TODO: comment out till ARM supports contentHash
    //.option('--template-hash <template-hash>', $('the content hash of the template'))
    //.option('--template-hash-algorithm <template-hash-algorithm>', $('the algorithm used to hash the template content'))
    .option('--template-version <template-version>', $('the content version of the template'))
    .option('-s --storage-account <storage-account>', $('the storage account where we will upload the template file'))
    //TODO: comment out till ARM supports validation mode
    //.option('-m --mode <mode>', $('the mode of the template deployment. Valid values are Replace, New and Incremental'))
    .option('-p --parameters <parameters>', $('a JSON-formatted string containing parameters'))
    .option('-e --parameters-file <parametersFile>', $('a file containing parameters'))
    //TODO: comment out till ARM supports contentHash
    //.option('--parameters-hash <parameters-hash>', $('the content hash of the parameters'))
    //.option('--parameters-hash-algorithm <parameters-hash-algorithm>', $('the algorithm used to hash the parameters content'))
    //.option('--parameters-version <parameters-version>', $('the expected content version of the parameters'))
    .option('--subscription <subscription>', $('the subscription identifier'))
    .execute(function (resourceGroup, options, _) {
      if (!resourceGroup) {
        return cli.missingArgument('resourceGroup');
      }
      groupUtils.validateTemplate(cli, resourceGroup, options, _);
    });
};

function createGalleryClient(subscription) {
  return utils.createClient('createGalleryClient', new azure.AnonymousCloudCredentials(),
    subscription.galleryEndpointUrl);
}
<|endoftext|>


<commit_before>'use strict';

const async = require('async');
const path = require('path');
const targz = require('tar.gz');
const logger = require('../../logging').getLogger();
const storage = require('../../storage');
const storeEnvelope = require('./store').storeEnvelope;
const removeEnvelopes = require('./remove').removeEnvelopes;

/**
 * @description Store new content into the content store from an uploaded tarball.
 */
exports.handler = function (req, res, next) {
  const reqStart = Date.now();

  let contentIDBase = null;
  let envelopeCount = 0;
  let deletionCount = 0;
  let toKeep = {};

  const envelopeWorker = (task, cb) => storeEnvelope(task.contentID, task.envelope, cb);
  const uploadQueue = async.queue(envelopeWorker, 10);

  // Log an error and optionally report it to the user.
  const reportError = (err, entryPath, description, fatal) => {
    const logPayload = {
      action: 'bulkcontentstore',
      apikeyName: req.apikeyName,
      entryPath,
      err: err.message,
      stack: err.stack,
      statusCode: 400,
      totalReqDuration: Date.now() - reqStart
    };

    if (fatal) {
      logger.error(`Fatal bulk upload problem: ${description}`, logPayload);

      err.statusCode = 400;

      next(err);
    } else {
      logger.warn(`Bulk upload problem: ${description}`, logPayload);
    }
  };

  // Handle a metadata/config.json entry.
  const handleConfigEntry = (entry) => {
    jsonFromStream(entry, (err, config) => {
      if (err) return reportError(err, entry.path, 'parsing config.json');

      if (!config.contentIDBase) {
        let e = new Error('Missing required key: contentIDBase');
        return reportError(e, entry.path, 'parsing config.json');
      }

      contentIDBase = config.contentIDBase;
    });
  };

  // Handle a metadata/keep.json entry.
  const handleKeepEntry = (entry) => {
    jsonFromStream(entry, (err, keep) => {
      if (err) return reportError(err, entry.path, 'parsing keep.json');

      if (!keep.keep) {
        let e = new Error('Missing required key: keep');
        return reportError(e, entry.path, 'parsing keep.json');
      }

      keep.keep.forEach((contentID) => {
        toKeep[contentID] = true;
      });
    });
  };

  const handleEnvelopeEntry = (entry) => {
    let encodedContentID = path.basename(entry.path, '.json');
    let contentID = decodeURIComponent(encodedContentID);
    toKeep[contentID] = true;

    jsonFromStream(entry, (err, envelope) => {
      if (err) return reportError(err, entry.path, 'parsing metadata envelope');

      // TODO validate envelope contents against a schema

      uploadQueue.push({ contentID, envelope }, (err) => {
        if (err) return reportError(err, entry.path, 'storing metadata envelope');

        envelopeCount++;

        logger.debug('Envelope stored successfully', {
          entryPath: entry.path,
          envelopeCount
        });
      });
    });
  };

  const removeDeletedContent = (cb) => {
    if (!contentIDBase) {
      logger.debug('Skipping content deletion.');
      return cb(null);
    }

    let existingContentIDs = [];

    storage.listContent(contentIDBase, (err, ids, next) => {
      if (err) return cb(err);

      if (ids.length > 0) {
        // Page of content.
        existingContentIDs = existingContentIDs.concat(ids);
        next();
      } else {
        // All content consumed.
        let toDelete = existingContentIDs.filter((id) => !toKeep[id]);

        logger.debug('Deleting removed envelopes.', {
          deleteCount: toDelete.length
        });

        removeEnvelopes(toDelete, cb);
      }
    });
  };

  const reportCompletion = () => {
    logger.info('Bulk content upload completed successfully.', {
      action: 'bulkcontentstore',
      apikeyName: req.apikeyName,
      acceptedCount: envelopeCount,
      deletedCount: deletionCount,
      totalReqDuration: Date.now() - reqStart
    });

    res.send(204);
    next();
  };

  const parse = targz().createParseStream();

  parse.on('entry', (entry) => {
    if (entry.type !== 'File') return;

    let dirs = path.dirname(entry.path).split(path.sep);
    let dname = dirs[dirs.length - 1];
    let bname = path.basename(entry.path);

    logger.debug('Received entry for path', { path: entry.path });

    if (dname === 'metadata') {
      // metadata/ entries
      switch (bname) {
        case 'config.json':
          handleConfigEntry(entry);
          break;
        case 'keep.json':
          handleKeepEntry(entry);
          break;
        default:
          logger.warn('Unrecognized metadata entry', {
            entryPath: entry.path
          });
          break;
      }
    } else if (bname.endsWith('.json')) {
      handleEnvelopeEntry(entry);
    } else {
      logger.warn('Unrecognized entry', {
        entryPath: entry.path
      });
    }
  });

  parse.on('error', (err) => {
    logger.info('Corrupted tarball uploaded', {
      err: err.message,
      stack: err.stack
    });

    res.send(400, err);

    next();
  });

  parse.on('end', () => {
    removeDeletedContent((err) => {
      if (err) return reportError(err, null, 'deleted content removal', true);

      reportCompletion();
    });
  });

  req.pipe(parse);
};

const jsonFromStream = function (stream, callback) {
  let chunks = [];

  stream.on('data', (chunk) => chunks.push(chunk));
  stream.on('error', callback);
  stream.on('end', () => {
    try {
      let b = Buffer.concat(chunks, stream.size);
      let s = b.toString('utf-8');
      let payload = JSON.parse(s);
      return callback(null, payload);
    } catch (err) {
      return callback(err);
    }
  });
};
<commit_msg>Fiddling with the concurrency level.<commit_after>'use strict';

const async = require('async');
const path = require('path');
const targz = require('tar.gz');
const logger = require('../../logging').getLogger();
const storage = require('../../storage');
const storeEnvelope = require('./store').storeEnvelope;
const removeEnvelopes = require('./remove').removeEnvelopes;

/**
 * @description Store new content into the content store from an uploaded tarball.
 */
exports.handler = function (req, res, next) {
  const reqStart = Date.now();

  let contentIDBase = null;
  let envelopeCount = 0;
  let deletionCount = 0;
  let toKeep = {};

  const envelopeWorker = (task, cb) => {
    logger.debug('Beginning envelope storage', {
      contentID: task.contentID
    });

    storeEnvelope(task.contentID, task.envelope, cb);
  };

  const uploadQueue = async.queue(envelopeWorker, 4);

  // Log an error and optionally report it to the user.
  const reportError = (err, entryPath, description, fatal) => {
    const logPayload = {
      action: 'bulkcontentstore',
      apikeyName: req.apikeyName,
      entryPath,
      err: err.message,
      stack: err.stack,
      statusCode: 400,
      totalReqDuration: Date.now() - reqStart
    };

    if (fatal) {
      logger.error(`Fatal bulk upload problem: ${description}`, logPayload);

      err.statusCode = 400;

      next(err);
    } else {
      logger.warn(`Bulk upload problem: ${description}`, logPayload);
    }
  };

  // Handle a metadata/config.json entry.
  const handleConfigEntry = (entry) => {
    jsonFromStream(entry, (err, config) => {
      if (err) return reportError(err, entry.path, 'parsing config.json');

      if (!config.contentIDBase) {
        let e = new Error('Missing required key: contentIDBase');
        return reportError(e, entry.path, 'parsing config.json');
      }

      contentIDBase = config.contentIDBase;
    });
  };

  // Handle a metadata/keep.json entry.
  const handleKeepEntry = (entry) => {
    jsonFromStream(entry, (err, keep) => {
      if (err) return reportError(err, entry.path, 'parsing keep.json');

      if (!keep.keep) {
        let e = new Error('Missing required key: keep');
        return reportError(e, entry.path, 'parsing keep.json');
      }

      keep.keep.forEach((contentID) => {
        toKeep[contentID] = true;
      });
    });
  };

  const handleEnvelopeEntry = (entry) => {
    let encodedContentID = path.basename(entry.path, '.json');
    let contentID = decodeURIComponent(encodedContentID);
    toKeep[contentID] = true;

    jsonFromStream(entry, (err, envelope) => {
      if (err) return reportError(err, entry.path, 'parsing metadata envelope');

      // TODO validate envelope contents against a schema

      uploadQueue.push({ contentID, envelope }, (err) => {
        if (err) return reportError(err, entry.path, 'storing metadata envelope');

        envelopeCount++;

        logger.debug('Envelope stored successfully', {
          entryPath: entry.path,
          contentID,
          envelopeCount
        });
      });
    });
  };

  const removeDeletedContent = (cb) => {
    if (!contentIDBase) {
      logger.debug('Skipping content deletion.');
      return cb(null);
    }

    let existingContentIDs = [];

    storage.listContent(contentIDBase, (err, ids, next) => {
      if (err) return cb(err);

      if (ids.length > 0) {
        // Page of content.
        existingContentIDs = existingContentIDs.concat(ids);
        next();
      } else {
        // All content consumed.
        let toDelete = existingContentIDs.filter((id) => !toKeep[id]);
        deletionCount = toDelete.length;

        logger.debug('Deleting removed envelopes.', {
          deletionCount
        });

        removeEnvelopes(toDelete, (err, results) => {
          if (err) return cb(err);

          logger.debug('Envelopes deleted.', results);

          cb();
        });
      }
    });
  };

  const reportCompletion = () => {
    logger.info('Bulk content upload completed successfully.', {
      action: 'bulkcontentstore',
      apikeyName: req.apikeyName,
      acceptedCount: envelopeCount,
      deletedCount: deletionCount,
      totalReqDuration: Date.now() - reqStart
    });

    res.send(204);
    next();
  };

  const parse = targz().createParseStream();

  parse.on('entry', (entry) => {
    if (entry.type !== 'File') return;

    let dirs = path.dirname(entry.path).split(path.sep);
    let dname = dirs[dirs.length - 1];
    let bname = path.basename(entry.path);

    logger.debug('Received entry for path', { path: entry.path });

    if (dname === 'metadata') {
      // metadata/ entries
      switch (bname) {
        case 'config.json':
          handleConfigEntry(entry);
          break;
        case 'keep.json':
          handleKeepEntry(entry);
          break;
        default:
          logger.warn('Unrecognized metadata entry', {
            entryPath: entry.path
          });
          break;
      }
    } else if (bname.endsWith('.json')) {
      handleEnvelopeEntry(entry);
    } else {
      logger.warn('Unrecognized entry', {
        entryPath: entry.path
      });
    }
  });

  parse.on('error', (err) => {
    logger.info('Corrupted tarball uploaded', {
      err: err.message,
      stack: err.stack
    });

    res.send(400, err);

    next();
  });

  parse.on('end', () => {
    removeDeletedContent((err) => {
      if (err) return reportError(err, null, 'deleted content removal', true);

      reportCompletion();
    });
  });

  req.pipe(parse);
};

const jsonFromStream = function (stream, callback) {
  let chunks = [];

  stream.on('data', (chunk) => chunks.push(chunk));
  stream.on('error', callback);
  stream.on('end', () => {
    try {
      let b = Buffer.concat(chunks, stream.size);
      let s = b.toString('utf-8');
      let payload = JSON.parse(s);
      return callback(null, payload);
    } catch (err) {
      return callback(err);
    }
  });
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name       Twitch Player Plus
// @namespace  http://twitch.tv/ehsankia
// @version    0.2
// @description  Various tweaks to the Twitch HTML5 player UI
// @match      http://www.twitch.tv/*
// @match      http://player.twitch.tv/*
// @copyright  2015+, Ehsan Kia
// ==/UserScript==

var qualityOptions;
var waitForPlayerReadyTimer = setInterval(function() {
    var html5Player = $('div.player');
    if (html5Player.length > 0) {
        setTimeout(applyFixes, 3000);
        clearInterval(waitForPlayerReadyTimer);
    }
}, 1000);

function applyFixes() {
    // Sticky volume slider
    $('.js-volume-container').css('width', '13em');

    // Move quality options to main bar if available
    qualityOptions = $(".js-quality");
    qualityOptions.insertAfter($('.js-quality-display-contain'));
    qualityOptions.css({
      float: "left",
      margin: "5px",
      color: "white",
      background: "rgba(20, 20, 20, 0.8)",
      border: "1px solid grey",
      boxShadow: "0 0 black",
    });

    // Remove remaining label
    $("span:contains('Video Quality:')").remove();

    // Hide options if there are no transcoders
    setInterval(checkForQualityOptions, 5000);

    // Bind F key to fullscreen
    // document.addEventListener("keypress", function(e){
    //     if(e.which === 102) enterFS($('div.player')[0]);
    // });
}

function checkForQualityOptions() {
  var numQualityOptions = $(".js-quality > option").length;
  if (numQualityOptions > 1) {
    qualityOptions.css('display', 'block');
  } else {
    qualityOptions.css('display', 'none');
  }
}

function enterFS(elem) {
  if (elem.requestFullscreen) {
      elem.requestFullscreen();
  } else if (elem.msRequestFullscreen) {
      elem.msRequestFullscreen();
  } else if (elem.mozRequestFullScreen) {
      elem.mozRequestFullScreen();
  } else if (elem.webkitRequestFullscreen) {
      elem.webkitRequestFullscreen();
  }
}
<commit_msg>quality options clean style, hover cursor/color, hide intitially<commit_after>// ==UserScript==
// @name       Twitch Player Plus
// @namespace  http://twitch.tv/ehsankia
// @version    0.3
// @description  Various tweaks to the Twitch HTML5 player UI
// @match      http://www.twitch.tv/*
// @match      http://player.twitch.tv/*
// @copyright  2015+, Ehsan Kia
// ==/UserScript==

var qualityOptions;
var waitForPlayerReadyTimer = setInterval(function() {
    var html5Player = $('div.player');
    if (html5Player.length > 0) {
        setTimeout(applyFixes, 3000);
        clearInterval(waitForPlayerReadyTimer);
    }
}, 1000);

function applyFixes() {
    // Sticky volume slider
    $('.js-volume-container').css('width', '13em');

    // Move quality options to main bar and style appropriately
    qualityOptions = $(".js-quality");
    qualityOptions.insertAfter($('.js-quality-display-contain'));
    qualityOptions.css({
      float: "left",
      margin: "5px",
      color: "white",
      background: "none",
      border: "none",
      boxShadow: "0 0 black",
      appearance: "none",
      "-moz-appearance": "none",
      "-webkit-appearance": "none",
      cursor: "pointer",
    });
    qualityOptions.mouseover(function() {
        $(this).css("color","#a991d4");
        $(this).find("> option").css("color","white");
    }).mouseout(function() {
        $(this).css("color","white");
    });

    // Remove remaining label
    $("span:contains('Video Quality:')").remove();

    // Hide options if there are no transcoders
    setInterval(checkForQualityOptions, 5000);
    // Remove initially, otherwise there's an empty space for a bit
    checkForQualityOptions()

    // Bind F key to fullscreen
    // document.addEventListener("keypress", function(e){
    //     if(e.which === 102) enterFS($('div.player')[0]);
    // });
}

function checkForQualityOptions() {
  var numQualityOptions = $(".js-quality > option").length;
  if (numQualityOptions > 1) {
    qualityOptions.css('display', 'block');
  } else {
    qualityOptions.css('display', 'none');
  }
}

function enterFS(elem) {
  if (elem.requestFullscreen) {
      elem.requestFullscreen();
  } else if (elem.msRequestFullscreen) {
      elem.msRequestFullscreen();
  } else if (elem.mozRequestFullScreen) {
      elem.mozRequestFullScreen();
  } else if (elem.webkitRequestFullscreen) {
      elem.webkitRequestFullscreen();
  }
}

<|endoftext|>


<commit_before>const fileName = __filename.slice(__dirname.length + 1)
var commands = [
    { cmd: "!!inspect", fn: "inspect", file: fileName }
];

module.exports = function(bot, sendSync) {
    return {
        commands: commands,
        inspect: function (message) {
            var words = message.content.split(" "), user = message.channel.server.members.get("username", words[1]);
            bot.sendMessage(message.author, "Name: " + user.username + "\nID: " + user.id);
            bot.sendMessage(message.channel, "_Inspect info for " + words[1] + " sent._");
            return;
        }
    };
}<commit_msg>Changed to support names with spaces<commit_after>const fileName = __filename.slice(__dirname.length + 1)
var commands = [
    { cmd: "!!inspect", fn: "inspect", file: fileName }
];

module.exports = function(bot, sendSync) {
    return {
        commands: commands,
        inspect: function (message) {
            //extract username
            var words = message.content.split(" ");
            words.splice(0, 1);
            words = words.join(" ");
            var user = message.channel.server.members.get("username", words)
            bot.sendMessage(message.author, "Name: " + user.username + "\nID: " + user.id);
            bot.sendMessage(message.channel, "_Inspect info for " + words + " sent._");
            return;
        }
    };
}<|endoftext|>


<commit_before>window.FactsView = Backbone.View.extend({
  tagName: "div",
  className: "facts",
  _loading: true,
  _timestamp: undefined,
  tmpl: $('#facts_tmpl').html(),
  _previousLength: 0,

  initialize: function(options) {
    var self = this;

    this.collection.bind('add', this.addFact, this);
    this.collection.bind('reset', this.resetFacts, this);

    //TODO split this into two views, one for channels, one for searchresults
    if(options.channel !== undefined ) {
      $(this.el).html(Mustache.to_html(this.tmpl,options.channel.toJSON()));
    } else {
      $(this.el).html(Mustache.to_html(this.tmpl));
    }
    this.bindScroll();
  },

  render: function() {
    if (this.collection.length > 0) {
      this.resetFacts();
    }

    return this;
  },

  remove: function() {
    Backbone.View.prototype.remove.apply(this);

    this.unbindScroll();
  },

  addFact: function(fact) {
    var view = new FactView({
      model: fact
    });

    $(this.el).find('.facts').append(view.render().el);
  },

  resetFacts: function(e) {
    var self = this;

    this.stopLoading();

    if (this.collection.length === 0) {
      this.showNoFacts();
    } else {
      this.collection.forEach(function(fact) {
        self.addFact(fact);
      });
    }

    if ( this._previousLength === this.collection.length ) {
      this.hasMore = false;
    } else {
      this._previousLength = this.collection.length;
    }

    this.loadMore();
  },

  _moreNeeded: true,

  moreNeeded: function() {
    var bottomOfTheViewport = window.pageYOffset + window.innerHeight;
    var bottomOfEl = $(this.el).offset().top + $(this.el).outerHeight();

    if ( this.hasMore ) {
      if ( bottomOfEl < bottomOfTheViewport ) {
        return true;
      } else if ($(document).height() - ($(window).scrollTop() + $(window).height()) < 700) {
        return true;
      }
    }

    return false;
  },

  loadMore: function() {
    var self = this;
    var lastModel = self.collection.models[(self.collection.length - 1) || 0];
    var new_timestamp = (lastModel ? lastModel.get('timestamp') : 0);

    if ( self.moreNeeded() && ! self._loading && self._timestamp !== new_timestamp ) {
      self.setLoading();

      self._timestamp = new_timestamp;

      self.collection.fetch({
        add: true,
        data: {
          timestamp: self._timestamp
        },
        success: function() {
          self.stopLoading();
          self.loadMore();
        },
        error: function() {
          self.stopLoading();
          self.hasMore = false;
        }
      });
    }
  },

  hasMore: true,

  showNoFacts: function() {
    $(this.el).find('div.no_facts').show();
  },

  hideNoFacts: function() {
    $(this.el).find('div.no_facts').hide();
  },

  setLoading: function() {
    this._loading = true;
    $(this.el).find('div.loading').show();
  },

  stopLoading: function() {
    this._loading = false;
    $(this.el).find('div.loading').hide();
  },

  //TODO: Unbind on remove?
  bindScroll: function() {
    var self = this;
    $(window).bind('scroll.' + this.cid, function MCBiggah() {
      self.loadMore.apply(self);
    });
  },

  unbindScroll: function() {
    $(window).unbind('scroll.' + this.cid);
  }
});
<commit_msg>Use the useTemplate<commit_after>window.FactsView = Backbone.View.extend({
  tagName: "div",
  className: "facts",
  _loading: true,
  _timestamp: undefined,
  _previousLength: 0,

  initialize: function(options) {
    this.useTemplate("channels", "_facts");

    var self = this;

    this.collection.bind('add', this.addFact, this);
    this.collection.bind('reset', this.resetFacts, this);

    //TODO split this into two views, one for channels, one for searchresults
    if(options.channel !== undefined ) {
      $(this.el).html(Mustache.to_html(this.tmpl,options.channel.toJSON(), this.partials));
    } else {
      $(this.el).html(Mustache.to_html(this.tmpl, {}, this.partials));
    }
    this.bindScroll();
  },

  render: function() {
    if (this.collection.length > 0) {
      this.resetFacts();
    }

    return this;
  },

  remove: function() {
    Backbone.View.prototype.remove.apply(this);

    this.unbindScroll();
  },

  addFact: function(fact) {
    var view = new FactView({
      model: fact
    });

    $(this.el).find('.facts').append(view.render().el);
  },

  resetFacts: function(e) {
    var self = this;

    this.stopLoading();

    if (this.collection.length === 0) {
      this.showNoFacts();
    } else {
      this.collection.forEach(function(fact) {
        self.addFact(fact);
      });
    }

    if ( this._previousLength === this.collection.length ) {
      this.hasMore = false;
    } else {
      this._previousLength = this.collection.length;
    }

    this.loadMore();
  },

  _moreNeeded: true,

  moreNeeded: function() {
    var bottomOfTheViewport = window.pageYOffset + window.innerHeight;
    var bottomOfEl = $(this.el).offset().top + $(this.el).outerHeight();

    if ( this.hasMore ) {
      if ( bottomOfEl < bottomOfTheViewport ) {
        return true;
      } else if ($(document).height() - ($(window).scrollTop() + $(window).height()) < 700) {
        return true;
      }
    }

    return false;
  },

  loadMore: function() {
    var self = this;
    var lastModel = self.collection.models[(self.collection.length - 1) || 0];
    var new_timestamp = (lastModel ? lastModel.get('timestamp') : 0);

    if ( self.moreNeeded() && ! self._loading && self._timestamp !== new_timestamp ) {
      self.setLoading();

      self._timestamp = new_timestamp;

      self.collection.fetch({
        add: true,
        data: {
          timestamp: self._timestamp
        },
        success: function() {
          self.stopLoading();
          self.loadMore();
        },
        error: function() {
          self.stopLoading();
          self.hasMore = false;
        }
      });
    }
  },

  hasMore: true,

  showNoFacts: function() {
    $(this.el).find('div.no_facts').show();
  },

  hideNoFacts: function() {
    $(this.el).find('div.no_facts').hide();
  },

  setLoading: function() {
    this._loading = true;
    $(this.el).find('div.loading').show();
  },

  stopLoading: function() {
    this._loading = false;
    $(this.el).find('div.loading').hide();
  },

  //TODO: Unbind on remove?
  bindScroll: function() {
    var self = this;
    $(window).bind('scroll.' + this.cid, function MCBiggah() {
      self.loadMore.apply(self);
    });
  },

  unbindScroll: function() {
    $(window).unbind('scroll.' + this.cid);
  }
});
<|endoftext|>


<commit_before>/*\
title: $:/plugins/dullroar/atomfeed/atomsmasher.js
type: application/javascript
module-type: library

Encapsulating class for constructing atom feeds

\*/

/**
 * @module Atomfeed
 */
(function() { // jshint ignore:line
  var uuidHasher = require('$:/plugins/dullroar/atomfeed/md5hashToGuid');

  // jshint ignore:start
  var LAST_UPDATED_FILTER =
    '[!is[system]!has[draft.of]!untagged[]!tag[static]!is[tag]!sort[modified]limit[1]]';
  // jshint ignore:end

  function toISODate(twDateString) {
    if (!twDateString) { return ''; }
    var twDate = $tw.utils.parseDate(twDateString);
    return $tw.utils.formatDateString(twDate, "YYYY-0MM-0DDT0hh:0mm:0ss");
  }

  function pathJoin(parts, sep){
    var separator = sep || '/';
    var replace   = new RegExp(separator+'{1,}', 'g');
    return parts.join(separator).replace(replace, separator);
  }

  function domBuilderToXml(domBuilder) {
    var _htmlVoidElements = $tw.config.htmlVoidElements;
    $tw.config.htmlVoidElements = [];
    var output = domBuilder.toString();
    $tw.config.htmlVoidElements = _htmlVoidElements;
    return output;
  }

  function truncateWords(text, words) {
    return text.split(/\s/, words || 20).join(' ');
  }

  function toPermalink(title) {
    return '#' + encodeURIComponent(title);
  }

  function toFileName(title) {
    return encodeURIComponent(encodeURIComponent(title)) + '.html';
  }

  /**
   * Manage the creation and stringification of a list of tiddler titles.
   *
   * @class AtomSmasher
   * @constructor
   * @param {Object} options hash of options to provide context
   * @param {Wiki} options.wiki the $tw.wiki instance
   * @param {DOMDocument} [options.document=window.document] pass in a fake DOM
   * when `$tw.browser` is false (running in Node.JS)
   * @public
   */
  function AtomSmasher(options) {
    this.wiki = options.wiki;
    this.document = options.document || window.document;
    this.metadata = this.lookupMetadata();
  }

  /**
   * Lookup site information for caching
   * @method lookupMetadata
   * @return {Object} hash of metadata
   * @private
   */
  AtomSmasher.prototype.lookupMetadata = function lookupMetadata() {
    var atomserver = this.wiki.getTiddlerText('$:/config/atomserver');
    var lastUpdatedTiddler =
      this.wiki.getTiddler(this.wiki.filterTiddlers(LAST_UPDATED_FILTER)[0]);
    var sitetitle = this.wiki.getTiddlerText('$:/SiteTitle');
    return {
      title:    sitetitle,
      subtitle: this.wiki.getTiddlerText('$:/SiteSubtitle'),
      feedhref: pathJoin([atomserver, 'atom.xml']),
      sitehref: atomserver,
      author:   lastUpdatedTiddler.fields.creator,
      updated:  toISODate(lastUpdatedTiddler.fields.modified),
      uuid:     uuidHasher.run(sitetitle),
    };
  };

  /**
   * Lookup tiddler information for building an atom entry.
   * @method lookupMetadata
   * @return {Object} hash of data
   * @private
   */
  AtomSmasher.prototype.lookupEntryData = function lookupEntryData(tiddler) {
    var title = tiddler.getFieldString('title');
    return {
      title: title,
      updated: toISODate(tiddler.getFieldString('modified')),
      uuid: uuidHasher.run(title),
      href: pathJoin([this.metadata.sitehref, toPermalink(title)]),
      statichref: pathJoin([
        this.metadata.sitehref, 'static', toFileName(title)
      ]),
      summary: tiddler.getFieldString('summary') ||
        truncateWords(tiddler.getFieldString('text')),
      author: tiddler.getFieldString('modifier') ||
        tiddler.getFieldString('creator') ||
        this.metadata.author
    };
  };

  /**
   * Return a DomBuilder for the ATOM feed with headers.
   *
   * @method atomFeed
   * @return {DomBuilder}
   * @private
   */
  AtomSmasher.prototype.atomFeed = function atomHeader() {
    return $tw.utils.DomBuilder('feed', this.document)
      .attr('xmlns', 'http://www.w3.org/2005/Atom')
      .add('title').renderText(this.metadata.title).end()
      .add('subtitle').renderText(this.metadata.subtitle).end()
      .add('link')
        .attr('href', this.metadata.feedhref)
        .attr('rel', 'self')
      .end()
      .add('link')
        .attr('href', this.metadata.sitehref)
      .end()
      .add('author')
        .add('name')
          .text(this.metadata.author)
        .end()
      .end()
      .add('id').text(this.metadata.uuid).end()
      .add('updated').text(this.metadata.updated).end();
  };

  /**
   * Render a tiddler into a widgetNode
   *
   * @method renderTiddler
   * @param {String} title tiddler title
   * @return {DomBuilder}
   * @private
   */
  AtomSmasher.prototype.renderTiddler = function renderTiddler(title) {
    var widgetNode = this.wiki.makeWidget(this.wiki.parseTiddler(title));
    var container = $tw.utils.DomBuilder('div', this.document)
      .attr('xmlns', 'http://www.w3.org/1999/xhtml')
      .toDOM();
    widgetNode.render(container, null);
    return $tw.utils.DomBuilder(container, this.document);
  };

  /**
   * Render arbitrary TiddlyWiki text.
   *
   * @method renderText
   * @param {String} test the TiddlyWiki text to render
   * @return {String} the rendered text
   * @private
   */
  AtomSmasher.prototype.renderText = function renderText(text) {
    return this.wiki.renderText('text/plain', 'text/vnd.tiddlywiki', text);
  };

  /**
   * Return a DomBuilder for a specific ATOM entry from a Tiddler.
   *
   * @method atomEntry
   * @param {Tiddler} tiddler the tiddler to use to create the ATOM entry
   * @return {DomBuilder}
   * @private
   */
  AtomSmasher.prototype.atomEntry = function atomEntry(tiddler) {
    var data = this.lookupEntryData(tiddler);
    return $tw.utils.DomBuilder('entry', this.document)
      .add('title').text(data.title).end()
      .add('link')
        .attr('href', data.href)
      .end()
      .add('link')
        .attr('rel', 'alternative')
        .attr('type', 'text/html')
        .attr('href', data.statichref)
      .end()
      .add('id').text(data.uuid).end()
      .add('updated').text(data.updated).end()
      .add('summary').text(data.summary).end()
      .add('content')
        .attr('type', 'xhtml')
        .renderTiddler(data.title)
          .attr('xmlns', 'http://www.w3.org/1999/xhtml')
        .end()
      .end()
      .add('author')
        .add('name').text(data.author).end()
      .end();
  };

  /**
   * Construct and stringify the list of tiddler titles.
   *
   * @method feedify
   * @param {Array} titles the list of tiddlers to process
   * @return {String} the XML of a feed file as a String
   * @public
   */
  AtomSmasher.prototype.feedify = function feedify(titles) {
    var feed = this.atomFeed();
    titles.forEach(function(title) {
      feed.add(this.atomEntry(this.wiki.getTiddler(title)));
    }, this);
    return '<?xml version="1.0" encoding="utf-8"?>\n' + domBuilderToXml(feed);
  };

  module.exports = AtomSmasher;
})();
<commit_msg>Allow http:// in path joiner<commit_after>/*\
title: $:/plugins/dullroar/atomfeed/atomsmasher.js
type: application/javascript
module-type: library

Encapsulating class for constructing atom feeds

\*/

/**
 * @module Atomfeed
 */
(function() { // jshint ignore:line
  var uuidHasher = require('$:/plugins/dullroar/atomfeed/md5hashToGuid');

  // jshint ignore:start
  var LAST_UPDATED_FILTER =
    '[!is[system]!has[draft.of]!untagged[]!tag[static]!is[tag]!sort[modified]limit[1]]';
  // jshint ignore:end

  function toISODate(twDateString) {
    if (!twDateString) { return ''; }
    var twDate = $tw.utils.parseDate(twDateString);
    return $tw.utils.formatDateString(twDate, "YYYY-0MM-0DDT0hh:0mm:0ss");
  }

  function pathJoin(parts){
    return parts.join('/').replace(/(:\/)*\/{1,}/g, '$1/');
  }

  function domBuilderToXml(domBuilder) {
    var _htmlVoidElements = $tw.config.htmlVoidElements;
    $tw.config.htmlVoidElements = [];
    var output = domBuilder.toString();
    $tw.config.htmlVoidElements = _htmlVoidElements;
    return output;
  }

  function truncateWords(text, words) {
    return text.split(/\s/, words || 20).join(' ');
  }

  function toPermalink(title) {
    return '#' + encodeURIComponent(title);
  }

  function toFileName(title) {
    return encodeURIComponent(encodeURIComponent(title)) + '.html';
  }

  /**
   * Manage the creation and stringification of a list of tiddler titles.
   *
   * @class AtomSmasher
   * @constructor
   * @param {Object} options hash of options to provide context
   * @param {Wiki} options.wiki the $tw.wiki instance
   * @param {DOMDocument} [options.document=window.document] pass in a fake DOM
   * when `$tw.browser` is false (running in Node.JS)
   * @public
   */
  function AtomSmasher(options) {
    this.wiki = options.wiki;
    this.document = options.document || window.document;
    this.metadata = this.lookupMetadata();
  }

  /**
   * Lookup site information for caching
   * @method lookupMetadata
   * @return {Object} hash of metadata
   * @private
   */
  AtomSmasher.prototype.lookupMetadata = function lookupMetadata() {
    var atomserver = this.wiki.getTiddlerText('$:/config/atomserver');
    var lastUpdatedTiddler =
      this.wiki.getTiddler(this.wiki.filterTiddlers(LAST_UPDATED_FILTER)[0]);
    var sitetitle = this.wiki.getTiddlerText('$:/SiteTitle');
    return {
      title:    sitetitle,
      subtitle: this.wiki.getTiddlerText('$:/SiteSubtitle'),
      feedhref: pathJoin([atomserver, 'atom.xml']),
      sitehref: atomserver,
      author:   lastUpdatedTiddler.fields.creator,
      updated:  toISODate(lastUpdatedTiddler.fields.modified),
      uuid:     uuidHasher.run(sitetitle),
    };
  };

  /**
   * Lookup tiddler information for building an atom entry.
   * @method lookupMetadata
   * @return {Object} hash of data
   * @private
   */
  AtomSmasher.prototype.lookupEntryData = function lookupEntryData(tiddler) {
    var title = tiddler.getFieldString('title');
    return {
      title: title,
      updated: toISODate(tiddler.getFieldString('modified')),
      uuid: uuidHasher.run(title),
      href: pathJoin([this.metadata.sitehref, toPermalink(title)]),
      statichref: pathJoin([
        this.metadata.sitehref, 'static', toFileName(title)
      ]),
      summary: tiddler.getFieldString('summary') ||
        truncateWords(tiddler.getFieldString('text')),
      author: tiddler.getFieldString('modifier') ||
        tiddler.getFieldString('creator') ||
        this.metadata.author
    };
  };

  /**
   * Return a DomBuilder for the ATOM feed with headers.
   *
   * @method atomFeed
   * @return {DomBuilder}
   * @private
   */
  AtomSmasher.prototype.atomFeed = function atomHeader() {
    return $tw.utils.DomBuilder('feed', this.document)
      .attr('xmlns', 'http://www.w3.org/2005/Atom')
      .add('title').renderText(this.metadata.title).end()
      .add('subtitle').renderText(this.metadata.subtitle).end()
      .add('link')
        .attr('href', this.metadata.feedhref)
        .attr('rel', 'self')
      .end()
      .add('link')
        .attr('href', this.metadata.sitehref)
      .end()
      .add('author')
        .add('name')
          .text(this.metadata.author)
        .end()
      .end()
      .add('id').text(this.metadata.uuid).end()
      .add('updated').text(this.metadata.updated).end();
  };

  /**
   * Render a tiddler into a widgetNode
   *
   * @method renderTiddler
   * @param {String} title tiddler title
   * @return {DomBuilder}
   * @private
   */
  AtomSmasher.prototype.renderTiddler = function renderTiddler(title) {
    var widgetNode = this.wiki.makeWidget(this.wiki.parseTiddler(title));
    var container = $tw.utils.DomBuilder('div', this.document)
      .attr('xmlns', 'http://www.w3.org/1999/xhtml')
      .toDOM();
    widgetNode.render(container, null);
    return $tw.utils.DomBuilder(container, this.document);
  };

  /**
   * Render arbitrary TiddlyWiki text.
   *
   * @method renderText
   * @param {String} test the TiddlyWiki text to render
   * @return {String} the rendered text
   * @private
   */
  AtomSmasher.prototype.renderText = function renderText(text) {
    return this.wiki.renderText('text/plain', 'text/vnd.tiddlywiki', text);
  };

  /**
   * Return a DomBuilder for a specific ATOM entry from a Tiddler.
   *
   * @method atomEntry
   * @param {Tiddler} tiddler the tiddler to use to create the ATOM entry
   * @return {DomBuilder}
   * @private
   */
  AtomSmasher.prototype.atomEntry = function atomEntry(tiddler) {
    var data = this.lookupEntryData(tiddler);
    return $tw.utils.DomBuilder('entry', this.document)
      .add('title').text(data.title).end()
      .add('link')
        .attr('href', data.href)
      .end()
      .add('link')
        .attr('rel', 'alternative')
        .attr('type', 'text/html')
        .attr('href', data.statichref)
      .end()
      .add('id').text(data.uuid).end()
      .add('updated').text(data.updated).end()
      .add('summary').text(data.summary).end()
      .add('content')
        .attr('type', 'xhtml')
        .renderTiddler(data.title)
          .attr('xmlns', 'http://www.w3.org/1999/xhtml')
        .end()
      .end()
      .add('author')
        .add('name').text(data.author).end()
      .end();
  };

  /**
   * Construct and stringify the list of tiddler titles.
   *
   * @method feedify
   * @param {Array} titles the list of tiddlers to process
   * @return {String} the XML of a feed file as a String
   * @public
   */
  AtomSmasher.prototype.feedify = function feedify(titles) {
    var feed = this.atomFeed();
    titles.forEach(function(title) {
      feed.add(this.atomEntry(this.wiki.getTiddler(title)));
    }, this);
    return '<?xml version="1.0" encoding="utf-8"?>\n' + domBuilderToXml(feed);
  };

  module.exports = AtomSmasher;
})();
<|endoftext|>


<commit_before>/**
 * TableWire.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

define(
  'tinymce.plugins.table.actions.TableWire',

  [
    'ephox.snooker.api.ResizeWire',
    'ephox.sugar.api.dom.Insert',
    'ephox.sugar.api.dom.Remove',
    'ephox.sugar.api.node.Body',
    'ephox.sugar.api.node.Element',
    'tinymce.plugins.table.alien.Util'
  ],

  function (ResizeWire, Insert, Remove, Body, Element, Util) {
    var createContainer = function () {
      var container = Element.fromTag('div');

      Insert.append(Body.body(), container);

      return container;
    };

    var get = function (editor, container) {
      return editor.inline ? ResizeWire.body(Util.getBody(editor), createContainer()) : ResizeWire.only(Element.fromDom(editor.getDoc()));
    };

    var remove = function (editor, wire) {
      if (editor.inline) {
        Remove.remove(wire.parent());
      }
    };

    return {
      get: get,
      remove: remove
    };
  }
);
<commit_msg>TINY-1394: added some reset styles<commit_after>/**
 * TableWire.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

define(
  'tinymce.plugins.table.actions.TableWire',

  [
    'ephox.snooker.api.ResizeWire',
    'ephox.sugar.api.dom.Insert',
    'ephox.sugar.api.dom.Remove',
    'ephox.sugar.api.node.Body',
    'ephox.sugar.api.node.Element',
    'ephox.sugar.api.properties.Css',
    'tinymce.plugins.table.alien.Util'
  ],

  function (ResizeWire, Insert, Remove, Body, Element, Css, Util) {
    var createContainer = function () {
      var container = Element.fromTag('div');

      Css.setAll(container, {
        position: 'static',
        height: '0',
        width: '0',
        padding: '0',
        margin: '0',
        border: '0'
      });

      Insert.append(Body.body(), container);

      return container;
    };

    var get = function (editor, container) {
      return editor.inline ? ResizeWire.body(Util.getBody(editor), createContainer()) : ResizeWire.only(Element.fromDom(editor.getDoc()));
    };

    var remove = function (editor, wire) {
      if (editor.inline) {
        Remove.remove(wire.parent());
      }
    };

    return {
      get: get,
      remove: remove
    };
  }
);
<|endoftext|>