<commit_before>/**
 * Activation Main component.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Button from '../button';
import Logo from '../logo';
import Optin from '../optin';

export function ActivationMain( { buttonURL, onButtonClick, buttonLabel } ) {
	return (

					<Logo />

						{ __( 'Congratulations, the Site Kit plugin is now activated.', 'google-site-kit' ) }

						<Optin />

					<Button
						id="start-setup-link"
						className="googlesitekit-start-setup googlesitekit-activation__button"
						href={ buttonURL }
						onClick={ onButtonClick }
					>
						{ buttonLabel }
					</Button>

	);
}

ActivationMain.propTypes = {
	buttonURL: PropTypes.string.isRequired,
	onButtonClick: PropTypes.func,
	buttonLabel: PropTypes.string.isRequired,
};
<commit_msg>Update Activation with CompatibilityChecks.<commit_after>/**
 * Activation Main component.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Button from '../button';
import Logo from '../logo';
import Optin from '../optin';
import CompatibilityChecks from '../setup/compatibility-checks';

export function ActivationMain( { buttonURL, onButtonClick, buttonLabel } ) {
	return (

					<Logo />

						{ __( 'Congratulations, the Site Kit plugin is now activated.', 'google-site-kit' ) }

					<CompatibilityChecks>
						{ ( { complete, inProgressFeedback, CTAFeedback } ) => (
							<>
								{ CTAFeedback }

									<Optin />

									<Button
										id="start-setup-link"
										className="googlesitekit-start-setup googlesitekit-activation__button"
										/* href must only be passed if not disabled to work and display correctly. */
										href={ complete ? buttonURL : undefined }
										onClick={ onButtonClick }
										disabled={ ! complete }
									>
										{ buttonLabel }
									</Button>
									{ inProgressFeedback }

							</>
						) }
					</CompatibilityChecks>

	);
}

ActivationMain.propTypes = {
	buttonURL: PropTypes.string.isRequired,
	onButtonClick: PropTypes.func,
	buttonLabel: PropTypes.string.isRequired,
};
<|endoftext|>


<commit_before>( function () {
	/* global moment:false */
	/**
	 * A base widget for displaying notification items.
	 *
	 * @class
	 * @abstract
	 * @extends OO.ui.Widget
	 *
	 * @constructor
	 * @param {mw.echo.Controller} controller Echo controller
	 * @param {mw.echo.dm.NotificationItem} model Notification item model
	 * @param {Object} [config] Configuration options
	 * @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
	 *  for popups.
	 * @cfg {boolean} [bundle=false] This notification item is part of a bundle.
	 */
	mw.echo.ui.NotificationItemWidget = function MwEchoUiNotificationItemWidget( controller, model, config ) {
		var i, secondaryUrls, urlObj, linkButton, $icon, isOutsideMenu, echoMoment,
			outsideMenuItemCounter = 0,
			$message = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content-message' );

		config = config || {};

		// Parent constructor
		mw.echo.ui.NotificationItemWidget.super.call( this, $.extend( { data: model.getId() }, config ) );

		this.controller = controller;
		this.model = model;

		this.$overlay = config.$overlay || this.$element;
		this.bundle = !!config.bundle;

		this.$content = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content' );
		this.$actions = $( '<div>' )
			.addClass( 'mw-echo-ui-notificationItemWidget-content-actions' );

		// Mark as read
		this.markAsReadButton = new mw.echo.ui.ToggleReadCircleButtonWidget( {
			framed: false,
			title: mw.msg( 'echo-notification-markasread-tooltip' ),
			classes: [ 'mw-echo-ui-notificationItemWidget-markAsReadButton' ],
			markAsRead: !this.model.isRead()
		} );

		// Icon
		if ( this.model.getIconURL() ) {
			$icon = $( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-icon' )
				.append( $( '<img>' ).attr( {
					src: this.model.getIconURL(),
					role: 'presentation',
					alt: ' '
				} ) );
		}

		// Content
		$message.append(
			$( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-content-message-header-wrapper' )
				.append(
					$( '<div>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-content-message-header' )
						.append( this.model.getContentHeader() )
				)
		);
		if ( !this.bundle && this.model.getContentBody() ) {
			$message.append(
				$( '<div>' )
					.addClass( 'mw-echo-ui-notificationItemWidget-content-message-body' )
					.append( this.model.getContentBody() )
					// dir=auto has a similar effect to wrapping the content in <bdi>, but
					// makes text-overflow: ellipsis; behave less strangely
					.attr( 'dir', 'auto' )
			);
		}

		// Actions menu
		this.actionsButtonSelectWidget = new OO.ui.ButtonSelectWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-buttons' ]
		} );

		// Popup menu
		this.menuPopupButtonWidget = new mw.echo.ui.ActionMenuPopupWidget( {
			framed: false,
			icon: 'ellipsis',
			$overlay: this.$overlay,
			menuWidth: 200,
			title: mw.msg( 'echo-notification-more-options-tooltip' ),
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-menu' ]
		} );

		// Timestamp
		// We want to use extra-short timestamp strings; we change the locale
		// to our echo-defined one and use that instead of the normal moment locale
		echoMoment = moment.utc( this.model.getTimestamp() );
		echoMoment.locale( 'echo-shortRelativeTime' );
		echoMoment.local();

		this.timestampWidget = new OO.ui.LabelWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-timestamp' ],
			// Get the time 'fromNow' without the suffix 'ago'
			label: echoMoment.fromNow( true )
		} );

		// Build the actions line
		if ( this.bundle ) {
			// In a bundled item, the timestamp should go before the menu
			this.$actions.append(
				$( '<div>' )
					// We are wrapping the actions in a 'row' div so that the
					// internal pieces are also a table layout
					.addClass( 'mw-echo-ui-notificationItemWidget-content-actions-row' )
					.append(
						this.actionsButtonSelectWidget.$element,
						this.timestampWidget.$element,
						this.menuPopupButtonWidget.$element
					)
			);
		} else {
			this.$actions.append(
				this.actionsButtonSelectWidget.$element,
				this.menuPopupButtonWidget.$element,
				this.timestampWidget.$element
			);
		}

		// Actions
		secondaryUrls = this.model.getSecondaryUrls();
		for ( i = 0; i < secondaryUrls.length; i++ ) {
			urlObj = secondaryUrls[ i ];

			// Items are placed outside the dotdotdot menu if they are
			// prioritized explicitly, *except* for items inside a bundle
			// (where all actions are inside the menu) or there are more than
			// two prioritized actions (all others go into the menu)
			isOutsideMenu = !this.bundle &&
					(
						(
							// Make sure we don't have too many prioritized items
							urlObj.prioritized &&
							outsideMenuItemCounter < mw.echo.config.maxPrioritizedActions
						) ||
						// If the number of total items are equal to or less than the
						// maximum allowed, they all go outside the menu
						// mw.echo.config.maxPrioritizedActions is 2 on desktop and 1 on mobile.
						secondaryUrls.length <= mw.echo.config.maxPrioritizedActions
					);

			linkButton = new mw.echo.ui.MenuItemWidget( {
				type: urlObj.type,
				actionData: urlObj.data,
				icon: urlObj.icon || 'next',
				label: urlObj.label,
				tooltip: urlObj.tooltip,
				description: urlObj.description,
				url: urlObj.url,
				prioritized: isOutsideMenu
			} );

			// Limit to 2 items outside the menu
			if ( isOutsideMenu ) {
				this.actionsButtonSelectWidget.addItems( [ linkButton ] );
				outsideMenuItemCounter++;
			} else {
				this.menuPopupButtonWidget.getMenu().addItems( [ linkButton ] );
			}
		}

		if ( this.bundle ) {
			// In a bundle, we have table layout, so the icon is
			// inserted into the content, and the 'mark as read'
			// button is not floating, and should be at the end
			this.$content.append(
				$icon,
				$message,
				this.$actions,
				this.markAsReadButton.$element
			);
			this.$element.append( this.$content );
		} else {
			this.$content.append(
				this.markAsReadButton.$element,
				$message,
				$( '<div>' )
					.addClass( 'mw-echo-ui-notificationItemWidget-content-table' )
					.append( this.$actions )
			);
			this.$element.append( $icon, this.$content );
		}

		// Events
		this.menuPopupButtonWidget.getMenu().connect( this, { choose: 'onPopupButtonWidgetChoose' } );
		this.markAsReadButton.connect( this, { click: 'onMarkAsReadButtonClick' } );

		this.$element
			.addClass( 'mw-echo-ui-notificationItemWidget' )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen', !this.model.isSeen() && !this.bundle )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-bundled', this.bundle );

		// Wrap the entire item with primary url
		if ( this.model.getPrimaryUrl() ) {
			this.$element.contents()
				.wrapAll(
					// HACK: Wrap the entire item with a link that takes
					// the user to the primary url. This is not perfect,
					// but it makes the behavior native to the browser rather
					// than us listening to click events and opening new
					// windows.
					$( '<a>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-linkWrapper' )
						.attr( 'href', this.model.getPrimaryUrl() )
						.on( 'click', this.onPrimaryLinkClick.bind( this ) )
				);
		}
	};

	OO.inheritClass( mw.echo.ui.NotificationItemWidget, OO.ui.Widget );

	/**
	 * Respond to primary link click.
	 * Override this in the descendents.
	 *
	 * @return {boolean} true
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onPrimaryLinkClick = function () {
		return true;
	};

	/**
	 * Manage a click on a dynamic secondary link.
	 * We can't know what the link intends us to do in the API, so we trust the 'apiParams'
	 * to tell the controller. When the link is clicked, we will pass the information on
	 * to the controller, which will manage whatever promise and action is needed.
	 *
	 * NOTE: The messages are parsed as HTML. If user-input is expected
	 * please make sure to properly escape it.
	 *
	 * @param {mw.echo.ui.MenuItemWidget} item The selected item
	 * @return {boolean} False to prevent the native event
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onPopupButtonWidgetChoose = function ( item ) {
		var actionData = item && item.getActionData(),
			messages = item && item.getConfirmationMessages(),
			widget = this;

		// Send to controller
		item.pushPending();
		this.controller.performDynamicAction( actionData, this.getModel().getSource() )
			.then( function () {
				var $title = $( '<p>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-notify-title' )
						.append( $.parseHTML( messages.title ) ),
					$description = $( '<p>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-notify-description' )
						.append( $.parseHTML( messages.description ) ),
					$confirmation;

				// Get rid of the button
				item.disconnect( this );
				if ( item.isPrioritized() ) {
					widget.actionsButtonSelectWidget.removeItems( [ item ] );
				} else {
					// It's inside the popup menu
					widget.menuPopupButtonWidget.getMenu().removeItems( [ item ] );
				}

				// Make sure to hide either piece if it is empty
				$title.toggle( !!$title.text() );
				$description.toggle( !!$description.text() );

				// Display confirmation
				$confirmation = $( '<div>' )
					.append( $title, $description );

				// Send to mw.notify
				mw.notify( $confirmation );
			} );

		// Prevent the click propagation
		return false;
	};

	/**
	 * Respond to mark as read button click
	 *
	 * @return {boolean} False to prevent the native event
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onMarkAsReadButtonClick = function () {
		// If we're marking read or unread, the notification was already seen.
		// Remove the animation class
		this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
		this.markRead( !this.model.isRead() );
		// Prevent propogation in case there's a link wrapping the content
		// and the mark as read/unread button
		return false;
	};

	/**
	 * Mark this notification as read
	 *
	 * @method
	 * @abstract
	 * @param {boolean} [isRead=true] Notification is marked as read
	 */
	mw.echo.ui.NotificationItemWidget.prototype.markRead = null;

	/**
	 * Get the notification link
	 *
	 * @return {string} Notification link
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getPrimaryUrl = function () {
		return this.model.getPrimaryUrl();
	};

	/**
	 * Get the item id
	 *
	 * @return {number} Notification id
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getTimestamp = function () {
		return this.model.getTimestamp();
	};

	/**
	 * Get the notification Id
	 *
	 * @return {number} Notification id
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getId = function () {
		return this.model.getId();
	};

	/**
	 * Check whether this item is seen.
	 *
	 * @return {boolean} Item is seen
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isSeen = function () {
		return this.model.isSeen();
	};

	/**
	 * Check whether this item is read.
	 *
	 * @return {boolean} Item is read
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isRead = function () {
		return this.model.isRead();
	};

	/**
	 * Check whether this item is foreign.
	 *
	 * @return {boolean} Item is foreign
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isForeign = function () {
		return this.model.isForeign();
	};

	/**
	 * Toggle the function of the 'mark as read' buttons from 'mark as read' to 'mark as unread'
	 * and vice versa.
	 *
	 * @param {boolean} [showMarkAsRead] Show the 'mark as read' buttons
	 *  - "false" means that the item is marked as read, whereby we show the user 'mark unread'
	 *    buttons.
	 *  - "true" means that the item is marked as unread and we show the user 'mark as read'
	 *    buttons
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleMarkAsReadButtons = function ( showMarkAsRead ) {
		showMarkAsRead = showMarkAsRead !== undefined ? showMarkAsRead : !this.model.isRead();

		this.markAsReadButton.toggleState( showMarkAsRead );
		this.menuPopupButtonWidget.toggle( !this.menuPopupButtonWidget.getMenu().isEmpty() );
	};

	/**
	 * Toggle the read state of the widget
	 *
	 * @param {boolean} [read] The current read state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleRead = function ( read ) {
		this.read = read !== undefined ? read : !this.read;

		this.$element.toggleClass( 'mw-echo-ui-notificationItemWidget-unread', !this.read );
		this.toggleMarkAsReadButtons( !this.read );
	};

	/**
	 * Toggle the seen state of the widget
	 *
	 * @param {boolean} [seen] The current seen state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleSeen = function ( seen ) {
		this.seen = seen !== undefined ? seen : !this.seen;

		this.$element
			.toggleClass( 'mw-echo-ui-notificationItemWidget-unseen', !this.seen );
	};

	/**
	 * Get the model associated with this widget.
	 *
	 * @return {mw.echo.dm.NotificationItem} Item model
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getModel = function () {
		return this.model;
	};

	/**
	 * Disconnect events when widget is destroyed.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.destroy = function () {
		this.model.disconnect( this );
	};

	/**
	 * Remove the 'initiallyUnseen' class, which was only used for the
	 * unseen animation when the user has first seen it.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.resetInitiallyUnseen = function () {
		this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
	};

	/**
	 * Declares whether this widget is a cloned fake.
	 *
	 * @return {boolean} false
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isFake = function () {
		return false;
	};
}() );
<commit_msg>Remove unnecessary table markup announcement in notification item widget<commit_after>( function () {
	/* global moment:false */
	/**
	 * A base widget for displaying notification items.
	 *
	 * @class
	 * @abstract
	 * @extends OO.ui.Widget
	 *
	 * @constructor
	 * @param {mw.echo.Controller} controller Echo controller
	 * @param {mw.echo.dm.NotificationItem} model Notification item model
	 * @param {Object} [config] Configuration options
	 * @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
	 *  for popups.
	 * @cfg {boolean} [bundle=false] This notification item is part of a bundle.
	 */
	mw.echo.ui.NotificationItemWidget = function MwEchoUiNotificationItemWidget( controller, model, config ) {
		var i, secondaryUrls, urlObj, linkButton, $icon, isOutsideMenu, echoMoment,
			outsideMenuItemCounter = 0,
			$message = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content-message' );

		config = config || {};

		// Parent constructor
		mw.echo.ui.NotificationItemWidget.super.call( this, $.extend( { data: model.getId() }, config ) );

		this.controller = controller;
		this.model = model;

		this.$overlay = config.$overlay || this.$element;
		this.bundle = !!config.bundle;

		this.$content = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content' );
		this.$actions = $( '<div>' )
			.addClass( 'mw-echo-ui-notificationItemWidget-content-actions' );

		// Mark as read
		this.markAsReadButton = new mw.echo.ui.ToggleReadCircleButtonWidget( {
			framed: false,
			title: mw.msg( 'echo-notification-markasread-tooltip' ),
			classes: [ 'mw-echo-ui-notificationItemWidget-markAsReadButton' ],
			markAsRead: !this.model.isRead()
		} );

		// Icon
		if ( this.model.getIconURL() ) {
			$icon = $( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-icon' )
				.append( $( '<img>' ).attr( {
					src: this.model.getIconURL(),
					role: 'presentation',
					alt: ' '
				} ) );
		}

		// Content
		$message.append(
			$( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-content-message-header-wrapper' )
				.append(
					$( '<div>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-content-message-header' )
						.append( this.model.getContentHeader() )
				)
		);
		if ( !this.bundle && this.model.getContentBody() ) {
			$message.append(
				$( '<div>' )
					.addClass( 'mw-echo-ui-notificationItemWidget-content-message-body' )
					.append( this.model.getContentBody() )
					// dir=auto has a similar effect to wrapping the content in <bdi>, but
					// makes text-overflow: ellipsis; behave less strangely
					.attr( 'dir', 'auto' )
			);
		}

		// Actions menu
		this.actionsButtonSelectWidget = new OO.ui.ButtonSelectWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-buttons' ],
			tabIndex: -1
		} );

		// Popup menu
		this.menuPopupButtonWidget = new mw.echo.ui.ActionMenuPopupWidget( {
			framed: false,
			icon: 'ellipsis',
			$overlay: this.$overlay,
			menuWidth: 200,
			title: mw.msg( 'echo-notification-more-options-tooltip' ),
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-menu' ]
		} );

		// Timestamp
		// We want to use extra-short timestamp strings; we change the locale
		// to our echo-defined one and use that instead of the normal moment locale
		echoMoment = moment.utc( this.model.getTimestamp() );
		echoMoment.locale( 'echo-shortRelativeTime' );
		echoMoment.local();

		this.timestampWidget = new OO.ui.LabelWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-timestamp' ],
			// Get the time 'fromNow' without the suffix 'ago'
			label: echoMoment.fromNow( true )
		} );

		// Build the actions line
		if ( this.bundle ) {
			// In a bundled item, the timestamp should go before the menu
			this.$actions.append(
				$( '<div>' )
					// We are wrapping the actions in a 'row' div so that the
					// internal pieces are also a table layout
					.addClass( 'mw-echo-ui-notificationItemWidget-content-actions-row' )
					.append(
						this.actionsButtonSelectWidget.$element,
						this.timestampWidget.$element,
						this.menuPopupButtonWidget.$element
					)
			);
		} else {
			this.$actions.append(
				this.actionsButtonSelectWidget.$element,
				this.menuPopupButtonWidget.$element,
				this.timestampWidget.$element
			);
		}

		// Actions
		secondaryUrls = this.model.getSecondaryUrls();
		for ( i = 0; i < secondaryUrls.length; i++ ) {
			urlObj = secondaryUrls[ i ];

			// Items are placed outside the dotdotdot menu if they are
			// prioritized explicitly, *except* for items inside a bundle
			// (where all actions are inside the menu) or there are more than
			// two prioritized actions (all others go into the menu)
			isOutsideMenu = !this.bundle &&
					(
						(
							// Make sure we don't have too many prioritized items
							urlObj.prioritized &&
							outsideMenuItemCounter < mw.echo.config.maxPrioritizedActions
						) ||
						// If the number of total items are equal to or less than the
						// maximum allowed, they all go outside the menu
						// mw.echo.config.maxPrioritizedActions is 2 on desktop and 1 on mobile.
						secondaryUrls.length <= mw.echo.config.maxPrioritizedActions
					);

			linkButton = new mw.echo.ui.MenuItemWidget( {
				type: urlObj.type,
				actionData: urlObj.data,
				icon: urlObj.icon || 'next',
				label: urlObj.label,
				tooltip: urlObj.tooltip,
				description: urlObj.description,
				url: urlObj.url,
				prioritized: isOutsideMenu
			} );

			// Limit to 2 items outside the menu
			if ( isOutsideMenu ) {
				this.actionsButtonSelectWidget.addItems( [ linkButton ] );
				this.actionsButtonSelectWidget.setTabIndex( 0 );
				outsideMenuItemCounter++;
			} else {
				this.menuPopupButtonWidget.getMenu().addItems( [ linkButton ] );
			}
		}

		if ( this.bundle ) {
			// In a bundle, we have table layout, so the icon is
			// inserted into the content, and the 'mark as read'
			// button is not floating, and should be at the end
			this.$content.append(
				$icon,
				$message,
				this.$actions,
				this.markAsReadButton.$element
			);
			this.$element.append( this.$content );
		} else {
			this.$content.append(
				this.markAsReadButton.$element,
				$message,
				$( '<div>' )
					.addClass( 'mw-echo-ui-notificationItemWidget-content-table' )
					.append( this.$actions )
			);
			this.$element.append( $icon, this.$content );
		}

		// Events
		this.menuPopupButtonWidget.getMenu().connect( this, { choose: 'onPopupButtonWidgetChoose' } );
		this.markAsReadButton.connect( this, { click: 'onMarkAsReadButtonClick' } );

		this.$element
			.addClass( 'mw-echo-ui-notificationItemWidget' )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen', !this.model.isSeen() && !this.bundle )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-bundled', this.bundle );

		// Wrap the entire item with primary url
		if ( this.model.getPrimaryUrl() ) {
			this.$element.contents()
				.wrapAll(
					// HACK: Wrap the entire item with a link that takes
					// the user to the primary url. This is not perfect,
					// but it makes the behavior native to the browser rather
					// than us listening to click events and opening new
					// windows.
					$( '<a>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-linkWrapper' )
						.attr( 'href', this.model.getPrimaryUrl() )
						.on( 'click', this.onPrimaryLinkClick.bind( this ) )
				);
		}
	};

	OO.inheritClass( mw.echo.ui.NotificationItemWidget, OO.ui.Widget );

	/**
	 * Respond to primary link click.
	 * Override this in the descendents.
	 *
	 * @return {boolean} true
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onPrimaryLinkClick = function () {
		return true;
	};

	/**
	 * Manage a click on a dynamic secondary link.
	 * We can't know what the link intends us to do in the API, so we trust the 'apiParams'
	 * to tell the controller. When the link is clicked, we will pass the information on
	 * to the controller, which will manage whatever promise and action is needed.
	 *
	 * NOTE: The messages are parsed as HTML. If user-input is expected
	 * please make sure to properly escape it.
	 *
	 * @param {mw.echo.ui.MenuItemWidget} item The selected item
	 * @return {boolean} False to prevent the native event
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onPopupButtonWidgetChoose = function ( item ) {
		var actionData = item && item.getActionData(),
			messages = item && item.getConfirmationMessages(),
			widget = this;

		// Send to controller
		item.pushPending();
		this.controller.performDynamicAction( actionData, this.getModel().getSource() )
			.then( function () {
				var $title = $( '<p>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-notify-title' )
						.append( $.parseHTML( messages.title ) ),
					$description = $( '<p>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-notify-description' )
						.append( $.parseHTML( messages.description ) ),
					$confirmation;

				// Get rid of the button
				item.disconnect( this );
				if ( item.isPrioritized() ) {
					widget.actionsButtonSelectWidget.removeItems( [ item ] );
				} else {
					// It's inside the popup menu
					widget.menuPopupButtonWidget.getMenu().removeItems( [ item ] );
				}

				// Make sure to hide either piece if it is empty
				$title.toggle( !!$title.text() );
				$description.toggle( !!$description.text() );

				// Display confirmation
				$confirmation = $( '<div>' )
					.append( $title, $description );

				// Send to mw.notify
				mw.notify( $confirmation );
			} );

		// Prevent the click propagation
		return false;
	};

	/**
	 * Respond to mark as read button click
	 *
	 * @return {boolean} False to prevent the native event
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onMarkAsReadButtonClick = function () {
		// If we're marking read or unread, the notification was already seen.
		// Remove the animation class
		this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
		this.markRead( !this.model.isRead() );
		// Prevent propogation in case there's a link wrapping the content
		// and the mark as read/unread button
		return false;
	};

	/**
	 * Mark this notification as read
	 *
	 * @method
	 * @abstract
	 * @param {boolean} [isRead=true] Notification is marked as read
	 */
	mw.echo.ui.NotificationItemWidget.prototype.markRead = null;

	/**
	 * Get the notification link
	 *
	 * @return {string} Notification link
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getPrimaryUrl = function () {
		return this.model.getPrimaryUrl();
	};

	/**
	 * Get the item id
	 *
	 * @return {number} Notification id
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getTimestamp = function () {
		return this.model.getTimestamp();
	};

	/**
	 * Get the notification Id
	 *
	 * @return {number} Notification id
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getId = function () {
		return this.model.getId();
	};

	/**
	 * Check whether this item is seen.
	 *
	 * @return {boolean} Item is seen
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isSeen = function () {
		return this.model.isSeen();
	};

	/**
	 * Check whether this item is read.
	 *
	 * @return {boolean} Item is read
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isRead = function () {
		return this.model.isRead();
	};

	/**
	 * Check whether this item is foreign.
	 *
	 * @return {boolean} Item is foreign
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isForeign = function () {
		return this.model.isForeign();
	};

	/**
	 * Toggle the function of the 'mark as read' buttons from 'mark as read' to 'mark as unread'
	 * and vice versa.
	 *
	 * @param {boolean} [showMarkAsRead] Show the 'mark as read' buttons
	 *  - "false" means that the item is marked as read, whereby we show the user 'mark unread'
	 *    buttons.
	 *  - "true" means that the item is marked as unread and we show the user 'mark as read'
	 *    buttons
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleMarkAsReadButtons = function ( showMarkAsRead ) {
		showMarkAsRead = showMarkAsRead !== undefined ? showMarkAsRead : !this.model.isRead();

		this.markAsReadButton.toggleState( showMarkAsRead );
		this.menuPopupButtonWidget.toggle( !this.menuPopupButtonWidget.getMenu().isEmpty() );
	};

	/**
	 * Toggle the read state of the widget
	 *
	 * @param {boolean} [read] The current read state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleRead = function ( read ) {
		this.read = read !== undefined ? read : !this.read;

		this.$element.toggleClass( 'mw-echo-ui-notificationItemWidget-unread', !this.read );
		this.toggleMarkAsReadButtons( !this.read );
	};

	/**
	 * Toggle the seen state of the widget
	 *
	 * @param {boolean} [seen] The current seen state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleSeen = function ( seen ) {
		this.seen = seen !== undefined ? seen : !this.seen;

		this.$element
			.toggleClass( 'mw-echo-ui-notificationItemWidget-unseen', !this.seen );
	};

	/**
	 * Get the model associated with this widget.
	 *
	 * @return {mw.echo.dm.NotificationItem} Item model
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getModel = function () {
		return this.model;
	};

	/**
	 * Disconnect events when widget is destroyed.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.destroy = function () {
		this.model.disconnect( this );
	};

	/**
	 * Remove the 'initiallyUnseen' class, which was only used for the
	 * unseen animation when the user has first seen it.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.resetInitiallyUnseen = function () {
		this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
	};

	/**
	 * Declares whether this widget is a cloned fake.
	 *
	 * @return {boolean} false
	 */
	mw.echo.ui.NotificationItemWidget.prototype.isFake = function () {
		return false;
	};
}() );
<|endoftext|>


<commit_before>/*globals bind */

// Picker widget
function hex2rgb(hex) {
  if (hex.length==4) {
    return {r: parseInt(hex.substr(1,1),16)*17,
            g: parseInt(hex.substr(2,1),16)*17,
            b: parseInt(hex.substr(3,1),16)*17};
  } else if (hex.length==7) {
    return {r: parseInt(hex.substr(1,2),16),
            g: parseInt(hex.substr(3,2),16),
            b: parseInt(hex.substr(5,2),16)};
  } else {
    return {r:0,g:0,b:0};
  }
}
function Picker(onPickColor, onPickBrush) {
  var self = this;
  this.colorsJq = $("<div>").css({
    position: "fixed",
    cursor: 'pointer',
    "z-index": "100001",
    top: "0",
    left: "0",
    width: "32px",
    border: 'solid 1px #000'
  });
  var chosenColor = $();
  // different brush colors!
  var colors = ["#000", "#fff", "#e50", "#fa0", "#1ba", "#e07", "#ab0"]
    .map(function(color) {
      var colorjq = $("<div>").css({"background-color": color,
                                    'color': (color=="#000"?"#fff":"#000"),
                                    'line-height': '64px',
                                    'font-size': '300%',
                                    'text-align': 'center',
                                    width: 32, height: 64});
      colorjq.click(function(){
        if (colorjq == chosenColor) { return; }
          onPickColor(hex2rgb(color));
          chosenColor.text("");
          chosenColor = colorjq;
          colorjq.html("•");
        });
      colorjq.appendTo(self.colorsJq);
      return colorjq;
    });
  colors[0].click();

  // different brush sizes!
  var chosenBrush = $();
  this.brushesJq = $("<div>")
        .css({
            position: "fixed",
            cursor: 'pointer',
            "z-index": "100001",
            top: "0",
            left: "32px",
            'float': 'right',
            'background-color':'#fff',
            border: 'solid 1px #000',
            color:'#000',
            margin:0
          });
  var brushes = [1,2,5,10,15,20]
    .map(function(size) {
        var brushJq = $("<div>")
              .css({
                display: 'block',
                'float': 'left',
                width: "32px",
                padding:0,
                height: "32px"
              });
        brushJq.append($("<div>")
            .css({
                position: "absolute",
                height: size+"px",
                'background-color': "#000",
                top: (16-size/2)+"px",
                width: "32px"
              }));
        brushJq.click(function(){
          if (brushJq == chosenBrush) { return; }
            onPickBrush(size);
            chosenBrush.css({'background-color': "#fff"});
            chosenBrush = brushJq;
            chosenBrush.css({'background-color': "#21f"});
          });
        brushJq.appendTo(self.brushesJq);
        return brushJq;
    });
  brushes[2].click();
}
Picker.prototype.del = function() {
  this.colorsJq.fadeOut('fast', bind(this,function(){this.colorsJq.remove();}));
  this.brushesJq.fadeOut('fast', bind(this,function(){this.brushesJq.remove();}));
};
Picker.prototype.show = function() {
  this.brushesJq.hide().appendTo($("body")).slideDown('medium');
  this.colorsJq.hide().appendTo($("body")).slideDown('medium');
};

<commit_msg>now toggle backgrounds black on white and vice versa instead of blue<commit_after>/*globals bind */

// Picker widget
function hex2rgb(hex) {
  if (hex.length==4) {
    return {r: parseInt(hex.substr(1,1),16)*17,
            g: parseInt(hex.substr(2,1),16)*17,
            b: parseInt(hex.substr(3,1),16)*17};
  } else if (hex.length==7) {
    return {r: parseInt(hex.substr(1,2),16),
            g: parseInt(hex.substr(3,2),16),
            b: parseInt(hex.substr(5,2),16)};
  } else {
    return {r:0,g:0,b:0};
  }
}
function Picker(onPickColor, onPickBrush) {
  var self = this;
  this.colorsJq = $("<div>").css({
    position: "fixed",
    cursor: 'pointer',
    "z-index": "100001",
    top: "0",
    left: "0",
    width: "32px",
    border: 'solid 1px #000'
  });
  var chosenColor = $();
  // different brush colors!
  var colors = ["#000", "#fff", "#e50", "#fa0", "#1ba", "#e07", "#ab0"]
    .map(function(color) {
      var colorjq = $("<div>").css({"background-color": color,
                                    'color': (color=="#000"?"#fff":"#000"),
                                    'line-height': '64px',
                                    'font-size': '300%',
                                    'text-align': 'center',
                                    width: 32, height: 64});
      colorjq.click(function(){
        if (colorjq == chosenColor) { return; }
          onPickColor(hex2rgb(color));
          chosenColor.text("");
          chosenColor = colorjq;
          colorjq.html("•");
        });
      colorjq.appendTo(self.colorsJq);
      return colorjq;
    });
  colors[0].click();

  // different brush sizes!
  var chosenBrush = $();
  this.brushesJq = $("<div>")
        .css({
            position: "fixed",
            cursor: 'pointer',
            "z-index": "100001",
            top: "0",
            left: "32px",
            'float': 'right',
            'background-color':'#fff',
            border: 'solid 1px #000',
            color:'#000',
            margin:0
          });
  var brushes = [1,2,5,10,15,20]
    .map(function(size) {
        var brushJq = $("<div>")
              .css({
                display: 'block',
                'float': 'left',
                width: "32px",
                padding:0,
                height: "32px"
              });
        brushJq.append($("<div>")
            .css({
                position: "absolute",
                height: size+"px",
                'background-color': "#000",
                top: (16-size/2)+"px",
                width: "32px"
              }));
        brushJq.click(function(){
          if (brushJq == chosenBrush) { return; }
            onPickBrush(size);
            chosenBrush.css({'background-color': "#fff"});
            chosenBrush.find("div").css({'background-color': "#000"});
            chosenBrush = brushJq;
            chosenBrush.css({'background-color': "#000"});
            chosenBrush.find("div").css({'background-color': "#fff"});
          });
        brushJq.appendTo(self.brushesJq);
        return brushJq;
    });
  brushes[2].click();
}
Picker.prototype.del = function() {
  this.colorsJq.fadeOut('fast', bind(this,function(){this.colorsJq.remove();}));
  this.brushesJq.fadeOut('fast', bind(this,function(){this.brushesJq.remove();}));
};
Picker.prototype.show = function() {
  this.brushesJq.hide().appendTo($("body")).slideDown('medium');
  this.colorsJq.hide().appendTo($("body")).slideDown('medium');
};

<|endoftext|>


<commit_before>const path = require('path');

const Nightmare = require('nightmare');
const blessed = require('blessed');
const delay = require('delay');
const leftPad = require('left-pad');

function fetchPlayByPlay(gameUrlCode) {
  const nightmare = Nightmare();

  return nightmare
    .goto(`https://watch.nba.com/game/${gameUrlCode}`)
    .click('.play-by-play')
    .click('.filter-buttons .all')
    .evaluate(() => {
      const away = {
        team: document.querySelector('.game-away').getAttribute('data-id'),
        score: document.querySelector('.game-score .away-score').innerText,
      };

      const home = {
        team: document.querySelector('.game-home').getAttribute('data-id'),
        score: document.querySelector('.game-score .home-score').innerText,
      };

      const isFinal =
        document.querySelector('.game-score .game-state').innerText === 'Final';

      const $infos = document.querySelectorAll(
        '.playbyplay-content .items .player-right'
      );

      const plays = [].slice.apply($infos).map($info => $info.innerText);

      return {
        away,
        home,
        plays,
        isFinal,
      };
    })
    .end();
}

function createBox(left) {
  return blessed.box({
    top: '0',
    left,
    width: '35%',
    height: '100%',
    tags: true,
    scrollable: true,
    scrollbar: true,
    mouse: true,
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
      scrollbar: {
        bg: 'blue',
      },
    },
  });
}

function getRenderContent(plays, info) {
  return plays
    .map(play => (play.indexOf(info.team) !== -1 ? play.trim() : ''))
    .join('\n');
}

module.exports = async function watchGame(gameUrlCode, duration = 30000) {
  // gameUrlCode = '20170601/CLEGSW'; // Test..
  // Create a screen object.
  const screen = blessed.screen({
    smartCSR: true,
  });

  screen.title = gameUrlCode;

  // Create two box
  const awayBox = createBox('0');
  const homeBox = createBox('35%');

  // Append our box to the screen.
  screen.append(awayBox);
  screen.append(homeBox);

  // Create a box perfectly centered horizontally and vertically.
  const teamBox = blessed.bigtext({
    font: __dirname + '/../fonts/ter-u12n.json',
    fontBold: __dirname + '/../fonts/ter-u12b.json',
    top: 0,
    left: '70%',
    width: '30%',
    height: '40%',
    tags: true,
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
    },
  });

  screen.append(teamBox);

  const scoreBox = blessed.bigtext({
    font: __dirname + '/../fonts/ter-u12n.json',
    fontBold: __dirname + '/../fonts/ter-u12b.json',
    top: '40%',
    left: '70%',
    width: '30%',
    height: '40%',
    tags: true,
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
    },
  });

  screen.append(scoreBox);

  const metaBox = blessed.box({
    top: '70%',
    left: '70%',
    width: '30%',
    height: '30%',
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
    },
  });

  screen.append(metaBox);

  metaBox.setContent('Github Repo:\nhttps://github.com/chentsulin/watch-nba');

  // Quit on Escape, q, or Control-C.
  screen.key(['escape', 'q', 'C-c'], () => {
    return process.exit(0);
  });

  // Render the screen.
  screen.render();

  while (true) {
    const { away, home, plays, isFinal } = await fetchPlayByPlay(gameUrlCode);

    awayBox.setContent(getRenderContent(plays, away));
    homeBox.setContent(getRenderContent(plays, home));

    teamBox.setContent(`${away.team} ${home.team}`);
    scoreBox.setContent(`${leftPad(away.score, 3)}-${leftPad(home.score, 3)}`);

    screen.render();

    await delay(duration);
  }
};
<commit_msg>Fix some wording<commit_after>const path = require('path');

const Nightmare = require('nightmare');
const blessed = require('blessed');
const delay = require('delay');
const leftPad = require('left-pad');

function fetchPlayByPlay(gameUrlCode) {
  const nightmare = Nightmare();

  return nightmare
    .goto(`https://watch.nba.com/game/${gameUrlCode}`)
    .click('.play-by-play')
    .click('.filter-buttons .all')
    .evaluate(() => {
      const away = {
        team: document.querySelector('.game-away').getAttribute('data-id'),
        score: document.querySelector('.game-score .away-score').innerText,
      };

      const home = {
        team: document.querySelector('.game-home').getAttribute('data-id'),
        score: document.querySelector('.game-score .home-score').innerText,
      };

      const isFinal =
        document.querySelector('.game-score .game-state').innerText === 'Final';

      const $infos = document.querySelectorAll(
        '.playbyplay-content .items .player-right'
      );

      const plays = [].slice.apply($infos).map($info => $info.innerText);

      return {
        away,
        home,
        plays,
        isFinal,
      };
    })
    .end();
}

function createBox(left) {
  return blessed.box({
    top: '0',
    left,
    width: '35%',
    height: '100%',
    tags: true,
    scrollable: true,
    scrollbar: true,
    mouse: true,
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
      scrollbar: {
        bg: 'blue',
      },
    },
  });
}

function getRenderContent(plays, info) {
  return plays
    .map(play => (play.indexOf(info.team) !== -1 ? play.trim() : ''))
    .join('\n');
}

module.exports = async function watchGame(gameUrlCode, duration = 30000) {
  // gameUrlCode = '20170601/CLEGSW'; // Test..
  // Create a screen object.
  const screen = blessed.screen({
    smartCSR: true,
  });

  screen.title = gameUrlCode;

  // Create two box
  const awayBox = createBox('0');
  const homeBox = createBox('35%');

  // Append our box to the screen.
  screen.append(awayBox);
  screen.append(homeBox);

  // Create a box perfectly centered horizontally and vertically.
  const teamBox = blessed.bigtext({
    font: __dirname + '/../fonts/ter-u12n.json',
    fontBold: __dirname + '/../fonts/ter-u12b.json',
    top: 0,
    left: '70%',
    width: '30%',
    height: '40%',
    tags: true,
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
    },
  });

  screen.append(teamBox);

  const scoreBox = blessed.bigtext({
    font: __dirname + '/../fonts/ter-u12n.json',
    fontBold: __dirname + '/../fonts/ter-u12b.json',
    top: '40%',
    left: '70%',
    width: '30%',
    height: '40%',
    tags: true,
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
    },
  });

  screen.append(scoreBox);

  const metaBox = blessed.box({
    top: '80%',
    left: '70%',
    width: '30%',
    height: '20%',
    border: {
      type: 'line',
    },
    style: {
      fg: 'white',
      bg: 'black',
      border: {
        fg: '#f0f0f0',
      },
    },
  });

  screen.append(metaBox);

  metaBox.setContent(
    'GitHub Repo Link:\n - https://github.com/chentsulin/watch-nba'
  );

  // Quit on Escape, q, or Control-C.
  screen.key(['escape', 'q', 'C-c'], () => {
    return process.exit(0);
  });

  // Render the screen.
  screen.render();

  while (true) {
    const { away, home, plays, isFinal } = await fetchPlayByPlay(gameUrlCode);

    awayBox.setContent(getRenderContent(plays, away));
    homeBox.setContent(getRenderContent(plays, home));

    teamBox.setContent(`${away.team} ${home.team}`);
    scoreBox.setContent(`${leftPad(away.score, 3)}-${leftPad(home.score, 3)}`);

    screen.render();

    await delay(duration);
  }
};
<|endoftext|>


<commit_before>javascript: (function () {

var w = null;
var kDoc = null;
var kObj = null;

if (typeof window.KindleReaderContextMenu !== 'undefined') {
    w = window;
} else if (window.length) {
    for (var i=0;i<window.length;i++) {
        if (typeof window[i].KindleReaderContextMenu !== 'undefined') {
            w = window[i];
            break;
        }
    }
}

if (typeof w === 'object') {
    kObj = w.KindleReaderContextMenu;
    kDoc = w.document;

    if (typeof kObj.ACRExtensions === 'undefined') {
        kObj.ACRExtensions = true;
        var oldMethod = kObj.show;
        kObj.show = function () {
            var res = oldMethod.apply(kObj, arguments);
            var txtDoc = null;
            var r = null;

            if (typeof (arguments[3]) !== 'undefined' && typeof (arguments[3]['start']) !== 'undefined') {
                var sId = arguments[3]['start'];
                var eId = arguments[3]['end'];

                $('iframe', kDoc).each(function (j, textIframe) {
                    var textIFrameDoc = $(textIframe).contents().get(0);
                    if ($('#'+sId, textIFrameDoc).get(0)) {
                        txtDoc = textIFrameDoc;
                        return false;
                    }
                });

                if (txtDoc) {
                    r = txtDoc.createRange();
                    r.setStartBefore($('#'+sId, txtDoc).get(0));
                    r.setEndAfter($('#'+eId, txtDoc).get(0));
                }
            }

            $('#ACRExtensions_copyB_sep', kDoc).remove();
            $('#ACRExtensions_copyB', kDoc).remove();
            var sepEl = $('

');
            var copyB = $('
Copy
');
            $('#kindle_menu_border', kDoc).append(sepEl).append(copyB);
            $('#ACRExtensions_copyB', kDoc).click(function (evt) {
                if (r) {
                    var newW = window.open('', 'ACRExtensions', "height=400,width=400,location=0,menubar=0,scrollbars=1,toolbar=0");
                    newW.document.body.appendChild(r.cloneContents());
                }
            });

            return res;
        };

        alert('ACRExtensions is now active.');
    } else {
        alert('ACRExtensions is already active.');
    }
} else {
    alert('Error: ACRExtensions is not active. Te Amazon Cloud Reader window could not be found.');
}
})();

<commit_msg>Added timeout to show Copy button<commit_after>javascript: (function () {

var w = null;
var kDoc = null;
var kObj = null;

if (typeof window.KindleReaderContextMenu !== 'undefined') {
    w = window;
} else if (window.length) {
    for (var i=0;i<window.length;i++) {
        if (typeof window[i].KindleReaderContextMenu !== 'undefined') {
            w = window[i];
            break;
        }
    }
}

if (typeof w === 'object') {
    kObj = w.KindleReaderContextMenu;
    kDoc = w.document;

    if (typeof kObj.ACRExtensions === 'undefined') {
        kObj.ACRExtensions = true;
        var oldMethod = kObj.show;
        kObj.show = function () {
            var res = oldMethod.apply(kObj, arguments);
            var txtDoc = null;
            var r = null;

            if (typeof (arguments[3]) !== 'undefined' && typeof (arguments[3]['start']) !== 'undefined') {
                var sId = arguments[3]['start'];
                var eId = arguments[3]['end'];

                $('iframe', kDoc).each(function (j, textIframe) {
                    var textIFrameDoc = $(textIframe).contents().get(0);
                    if ($('#'+sId, textIFrameDoc).get(0)) {
                        txtDoc = textIFrameDoc;
                        return false;
                    }
                });

                if (txtDoc) {
                    r = txtDoc.createRange();
                    r.setStartBefore($('#'+sId, txtDoc).get(0));
                    r.setEndAfter($('#'+eId, txtDoc).get(0));
                }
            }

            $('#ACRExtensions_copyB_sep', kDoc).remove();
            $('#ACRExtensions_copyB', kDoc).remove();
            var sepEl = $('

');
            var copyB = $('
Copy
');
            $('#kindle_menu_border', kDoc).append(sepEl).append(copyB);
            setTimeout(function(){
                sepEl.show();
                copyB.removeClass('button_hidden');
            }, 500);
            $('#ACRExtensions_copyB', kDoc).click(function (evt) {
                if (r) {
                    var newW = window.open('', 'ACRExtensions', "height=400,width=400,location=0,menubar=0,scrollbars=1,toolbar=0");
                    newW.document.body.appendChild(r.cloneContents());
                }
            });

            return res;
        };

        alert('ACRExtensions is now active.');
    } else {
        alert('ACRExtensions is already active.');
    }
} else {
    alert('Error: ACRExtensions is not active. Te Amazon Cloud Reader window could not be found.');
}
})();

<|endoftext|>


<commit_before>const _ = require('lodash')
const axios = require('axios')
const {parseString} = require('xml2js')

const malToNormal = {
  // Anime values
  series_animedb_id: 'id',
  series_title: 'title',
  series_synonyms: 'synonyms',
  series_type: 'type',
  series_episodes: 'nbEpisodes',
  series_status: 'seriesStatus',
  series_start: 'seriesStart',
  series_end: 'seriesEnd',
  series_image: 'picture',
  my_id: 'myID',
  my_watched_episodes: 'nbWatchedEpisode',
  my_start_date: 'myStartDate',
  my_finish_date: 'myEndDate',
  my_score: 'score',
  my_status: 'status',
  my_rewatching: 'rewatching',
  my_rewatching_ep: 'rewatchingEp',
  my_last_updated: 'lastUpdate',
  my_tags: 'tags',
  // MyAnimeList values
  user_id: 'userID',
  user_watching: 'nbWatching',
  user_completed: 'nbCompleted',
  user_onhold: 'nbOnHold',
  user_dropped: 'nbDropped',
  user_plantowatch: 'nbPlanToWatch',
  user_days_spent_watching: 'nbDaysSpentWatching'
}

const flatten = (obj) => {
  const res = {}

  _.each(obj, (value, key) => {
    res[malToNormal[key]] = value[0]
  })

  return res
}

/**
 * Allows to retrieve a user's watch lists and stuff.
 * @param {string} user The name of the user.
 * @param {string} type Can be either 'anime' or 'manga'
 *
 * @returns {promise}
 */

const getWatchListFromUser = (user) => {
  return new Promise((resolve, reject) => {
    if (!user) {
      reject(new Error('[Mal-Scraper]: No user received.'))
      return
    }

    axios.get(`https://myanimelist.net/malappinfo.php`, {
      params: {
        u: user,
        status: 'all',
        type: 'anime' // This can be changed to 'manga' too to retrieve manga lists.
      }
    })
      .then(({data}) => {
        parseString(data, (err, res) => {
          /* istanbul ignore next */
          if (err) reject(err)

          const mal = res.myanimelist

          resolve({
            stats: flatten(mal.myinfo[0]),
            lists: _.map(mal.anime, obj => flatten(obj))
          })
        })
      })
      .catch(/* istanbul ignore next */(err) => reject(err))
  })
}

module.exports = {
  getWatchListFromUser
}
<commit_msg>Fix #6<commit_after>const _ = require('lodash')
const axios = require('axios')
const {parseString} = require('xml2js')

const malToNormal = {
  // Anime values
  series_animedb_id: 'id',
  series_title: 'title',
  series_synonyms: 'synonyms',
  series_type: 'type',
  series_episodes: 'nbEpisodes',
  series_status: 'seriesStatus',
  series_start: 'seriesStart',
  series_end: 'seriesEnd',
  series_image: 'picture',
  my_id: 'myID',
  my_watched_episodes: 'nbWatchedEpisode',
  my_start_date: 'myStartDate',
  my_finish_date: 'myEndDate',
  my_score: 'score',
  my_status: 'status',
  my_rewatching: 'rewatching',
  my_rewatching_ep: 'rewatchingEp',
  my_last_updated: 'lastUpdate',
  my_tags: 'tags',
  // MyAnimeList values
  user_id: 'userID',
  user_watching: 'nbWatching',
  user_completed: 'nbCompleted',
  user_onhold: 'nbOnHold',
  user_dropped: 'nbDropped',
  user_plantowatch: 'nbPlanToWatch',
  user_days_spent_watching: 'nbDaysSpentWatching'
}

const flatten = (obj) => {
  const res = {}

  _.each(obj, (value, key) => {
    res[malToNormal[key]] = value[0]
  })

  return res
}

/**
 * Allows to retrieve a user's watch lists and stuff.
 * @param {string} user The name of the user.
 * @param {string} type Can be either 'anime' or 'manga'
 *
 * @returns {promise}
 */

const getWatchListFromUser = (user) => {
  return new Promise((resolve, reject) => {
    if (!user) {
      reject(new Error('[Mal-Scraper]: No user received.'))
      return
    }

    axios.get(`https://myanimelist.net/malappinfo.php`, {
      params: {
        u: user,
        status: 'all',
        type: 'anime' // This can be changed to 'manga' too to retrieve manga lists.
      }
    })
      .then(({data}) => {
        parseString(data, (err, res) => {
          /* istanbul ignore next */
          if (err) reject(err)

          const mal = res.myanimelist

          if (!mal) {
            reject(new Error('[Mal-Scraper]: It seems this user does not exist.'))
          }

          resolve({
            stats: flatten(mal.myinfo[0]),
            lists: _.map(mal.anime, obj => flatten(obj))
          })
        })
      })
      .catch(/* istanbul ignore next */(err) => reject(err))
  })
}

module.exports = {
  getWatchListFromUser
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4,
maxerr: 50, node: true */
/*global */

(function () {
    "use strict";

    var http    = require("http"),
        connect = require("connect"),
        os      = require("os");

    var _domainManager;

    /**
     * @private
     * @type {Object.<string, http.Server>}
     * A map from root paths to server instances.
     */
    var _servers = {};

    var PATH_KEY_PREFIX = "edge-code-inspect-";

    /**
     * @private
     * Removes trailing forward slash for the project root absolute path
     * @param {string} path Absolute path for a server
     * @returns {string}
     */
    function normalizeRootPath(path) {
        return (path && path[path.length - 1] === "/") ? path.slice(0, -1) : path;
    }

    /**
     * @private
     * Generates a key based on a server's absolute path
     * @param {string} path Absolute path for a server
     * @returns {string}
     */
    function getPathKey(path) {
        return PATH_KEY_PREFIX + normalizeRootPath(path);
    }

    /**
     * @private
     * Helper function to create a new server.
     * @param {string} path The absolute path that should be the document root
     * @param {function(?string, ?httpServer)} cb Callback function that receives
     *    an error (or null if there was no error) and the server (or null if there
     *    was an error).
     */
    function _createServer(path, createCompleteCallback) {
        var server,
            app,
            externalAddress,
            port    = 51740,
            pathKey = getPathKey(path);

        function getExternalAddress() {
            var interfaces = os.networkInterfaces();
            var addresses = [];

            Object.keys(interfaces).forEach(function (k) {
                interfaces[k].forEach(function (addr) {
                    if (addr.family === "IPv4" && !addr.internal) {
                        addresses.push(addr.address);
                    }
                });
            });

            console.log("Addresses: " + addresses);

            return addresses[0];
        }

        function requestRoot(server, cb) {
            // Request the root file from the project in order to ensure that the
            // server is actually initialized. If we don't do this, it seems like
            // connect takes time to warm up the server.
            var req = http.get(
                {host: externalAddress, port: port},
                function (res) {
                    cb(null, res);
                }
            );
            req.on("error", function (err) {
                cb(err, null);
            });
        }

        externalAddress = getExternalAddress();
        console.log("Address: " + externalAddress);

        app = connect();
        // JSLint complains if we use `connect.static` because static is a
        // reserved word.
        app.use(connect["static"](path));
        app.use(connect.directory(path));

        server = http.createServer(app);
        server.listen(port, "0.0.0.0", function () {
            requestRoot(
                server,
                function (err, res) {
                    if (err) {
                        createCompleteCallback("Could not GET root after launching server", null, null);
                    } else {
                        createCompleteCallback(null, server, {address: externalAddress, port: port});
                    }
                }
            );
        });
    }

    /**
     * @private
     * Handler function for the staticServer.getServer command. If a server
     * already exists for the given path, returns that, otherwise starts a new
     * one.
     * @param {string} path The absolute path that should be the document root
     * @param {function(?string, ?{address: string, family: string,
     *    port: number})} cb Callback that should receive the address information
     *    for the server. First argument is the error string (or null if no error),
     *    second argument is the address object (or null if there was an error).
     *    The "family" property of the address indicates whether the address is,
     *    for example, IPv4, IPv6, or a UNIX socket.
     */
    function _cmdGetServer(path, cb) {
        // Make sure the key doesn't conflict with some built-in property of Object.
        var pathKey = getPathKey(path);
        if (_servers[pathKey]) {
            cb(null, _servers[pathKey].address);
        } else {
            _createServer(path, function (err, server, address) {
                if (err) {
                    cb(err, null);
                } else {
                    _servers[pathKey] = {server: server, address: address};
                    cb(null, address);
                }
            });
        }
    }

    /**
     * @private
     * Handler function for the staticServer.closeServer command. If a server
     * exists for the given path, closes it, otherwise does nothing. Note that
     * this function doesn't wait for the actual socket to close, since the
     * server will actually wait for all client connections to close (which can
     * be awhile); but once it returns, you're guaranteed to get a different
     * server the next time you call getServer() on the same path.
     *
     * @param {string} path The absolute path whose server we should close.
     * @return {boolean} true if there was a server for that path, false otherwise
     */
    function _cmdCloseServer(path, cba) {
        var pathKey = getPathKey(path);
        if (_servers[pathKey]) {
            var serverToClose = _servers[pathKey].server;
            delete _servers[pathKey];
            serverToClose.close();
            return true;
        }
        return false;
    }

    /**
     * Initializes the StaticServer domain with its commands.
     * @param {DomainManager} domainManager The DomainManager for the server
     */
    function init(domainManager) {
        _domainManager = domainManager;

        if (!domainManager.hasDomain("inspectHttpServer")) {
            domainManager.registerDomain("inspectHttpServer", {major: 0, minor: 1});
        }
        _domainManager.registerCommand(
            "inspectHttpServer",
            "getServer",
            _cmdGetServer,
            true,
            "Starts or returns an existing server for the given path.",
            [{
                name: "path",
                type: "string",
                description: "absolute filesystem path for root of server"
            }],
            [{
                name: "address",
                type: "{address: string, family: string, port: number}",
                description: "hostname (stored in 'address' parameter), port, and socket type (stored in 'family' parameter) for the server. Currently, 'family' will always be 'IPv4'."
            }]
        );
        _domainManager.registerCommand(
            "inspectHttpServer",
            "closeServer",
            _cmdCloseServer,
            false,
            "Closes the server for the given path.",
            [{
                name: "path",
                type: "string",
                description: "absolute filesystem path for root of server"
            }],
            [{
                name: "result",
                type: "boolean",
                description: "indicates whether a server was found for the specific path then closed"
            }]
        );
    }

    console.log("hi");

    exports.init = init;

}());
<commit_msg>Use a random HTTP port on each connect.<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4,
maxerr: 50, node: true */
/*global */

(function () {
    "use strict";

    var http    = require("http"),
        connect = require("connect"),
        os      = require("os");

    var _domainManager;

    /**
     * @private
     * @type {Object.<string, http.Server>}
     * A map from root paths to server instances.
     */
    var _servers = {};

    var PATH_KEY_PREFIX = "edge-code-inspect-";

    /**
     * @private
     * Removes trailing forward slash for the project root absolute path
     * @param {string} path Absolute path for a server
     * @returns {string}
     */
    function normalizeRootPath(path) {
        return (path && path[path.length - 1] === "/") ? path.slice(0, -1) : path;
    }

    /**
     * @private
     * Generates a key based on a server's absolute path
     * @param {string} path Absolute path for a server
     * @returns {string}
     */
    function getPathKey(path) {
        return PATH_KEY_PREFIX + normalizeRootPath(path);
    }

    /**
     * @private
     * Helper function to create a new server.
     * @param {string} path The absolute path that should be the document root
     * @param {function(?string, ?httpServer)} cb Callback function that receives
     *    an error (or null if there was no error) and the server (or null if there
     *    was an error).
     */
    function _createServer(path, createCompleteCallback) {
        var server,
            app,
            externalAddress,
            pathKey = getPathKey(path);

        function getExternalAddress() {
            var interfaces = os.networkInterfaces();
            var addresses = [];

            Object.keys(interfaces).forEach(function (k) {
                interfaces[k].forEach(function (addr) {
                    if (addr.family === "IPv4" && !addr.internal) {
                        addresses.push(addr.address);
                    }
                });
            });

            console.log("Addresses: " + addresses);

            return addresses[0];
        }

        function requestRoot(server, cb) {
            // Request the root file from the project in order to ensure that the
            // server is actually initialized. If we don't do this, it seems like
            // connect takes time to warm up the server.
            var req = http.get(
                {host: externalAddress, port: server.address().port},
                function (res) {
                    cb(null, res);
                }
            );
            req.on("error", function (err) {
                cb(err, null);
            });
        }

        externalAddress = getExternalAddress();
        console.log("Address: " + externalAddress);

        app = connect();
        // JSLint complains if we use `connect.static` because static is a
        // reserved word.
        app.use(connect["static"](path));
        app.use(connect.directory(path));

        server = http.createServer(app);
        server.listen(0, function () {
            requestRoot(
                server,
                function (err, res) {
                    if (err) {
                        createCompleteCallback("Could not GET root after launching server", null, null);
                    } else {
                        createCompleteCallback(null, server, {address: externalAddress,
                                                              port: server.address().port});
                    }
                }
            );
        });
    }

    /**
     * @private
     * Handler function for the staticServer.getServer command. If a server
     * already exists for the given path, returns that, otherwise starts a new
     * one.
     * @param {string} path The absolute path that should be the document root
     * @param {function(?string, ?{address: string, family: string,
     *    port: number})} cb Callback that should receive the address information
     *    for the server. First argument is the error string (or null if no error),
     *    second argument is the address object (or null if there was an error).
     *    The "family" property of the address indicates whether the address is,
     *    for example, IPv4, IPv6, or a UNIX socket.
     */
    function _cmdGetServer(path, cb) {
        // Make sure the key doesn't conflict with some built-in property of Object.
        var pathKey = getPathKey(path);
        if (_servers[pathKey]) {
            cb(null, _servers[pathKey].address);
        } else {
            _createServer(path, function (err, server, address) {
                if (err) {
                    cb(err, null);
                } else {
                    _servers[pathKey] = {server: server, address: address};
                    cb(null, address);
                }
            });
        }
    }

    /**
     * @private
     * Handler function for the staticServer.closeServer command. If a server
     * exists for the given path, closes it, otherwise does nothing. Note that
     * this function doesn't wait for the actual socket to close, since the
     * server will actually wait for all client connections to close (which can
     * be awhile); but once it returns, you're guaranteed to get a different
     * server the next time you call getServer() on the same path.
     *
     * @param {string} path The absolute path whose server we should close.
     * @return {boolean} true if there was a server for that path, false otherwise
     */
    function _cmdCloseServer(path, cba) {
        var pathKey = getPathKey(path);
        if (_servers[pathKey]) {
            var serverToClose = _servers[pathKey].server;
            delete _servers[pathKey];
            serverToClose.close();
            return true;
        }
        return false;
    }

    /**
     * Initializes the StaticServer domain with its commands.
     * @param {DomainManager} domainManager The DomainManager for the server
     */
    function init(domainManager) {
        _domainManager = domainManager;

        if (!domainManager.hasDomain("inspectHttpServer")) {
            domainManager.registerDomain("inspectHttpServer", {major: 0, minor: 1});
        }
        _domainManager.registerCommand(
            "inspectHttpServer",
            "getServer",
            _cmdGetServer,
            true,
            "Starts or returns an existing server for the given path.",
            [{
                name: "path",
                type: "string",
                description: "absolute filesystem path for root of server"
            }],
            [{
                name: "address",
                type: "{address: string, family: string, port: number}",
                description: "hostname (stored in 'address' parameter), port, and socket type (stored in 'family' parameter) for the server. Currently, 'family' will always be 'IPv4'."
            }]
        );
        _domainManager.registerCommand(
            "inspectHttpServer",
            "closeServer",
            _cmdCloseServer,
            false,
            "Closes the server for the given path.",
            [{
                name: "path",
                type: "string",
                description: "absolute filesystem path for root of server"
            }],
            [{
                name: "result",
                type: "boolean",
                description: "indicates whether a server was found for the specific path then closed"
            }]
        );
    }

    console.log("hi");

    exports.init = init;

}());
<|endoftext|>


<commit_before>JSMpeg.Source.WebSocket = (function(){ "use strict";

var WSSource = function(url, options) {
	this.url = url;
	this.options = options;
	this.socket = null;
	this.streaming = true;

	this.callbacks = {connect: [], data: []};
	this.destination = null;

	this.reconnectInterval = options.reconnectInterval !== undefined
		? options.reconnectInterval
		: 5;
	this.shouldAttemptReconnect = !!this.reconnectInterval;

	this.completed = false;
	this.established = false;
	this.progress = 0;

	this.reconnectTimeoutId = 0;

	this.onEstablishedCallback = options.onSourceEstablished;
	this.onCompletedCallback = options.onSourceCompleted; // Never used
};

WSSource.prototype.connect = function(destination) {
	this.destination = destination;
};

WSSource.prototype.destroy = function() {
	clearTimeout(this.reconnectTimeoutId);
	this.shouldAttemptReconnect = false;
	this.socket.close();
};

WSSource.prototype.start = function() {
	this.shouldAttemptReconnect = !!this.reconnectInterval;
	this.progress = 0;
	this.established = false;

	this.socket = new WebSocket(this.url, this.options.protocols || null);
	this.socket.binaryType = 'arraybuffer';
	this.socket.onmessage = this.onMessage.bind(this);
	this.socket.onopen = this.onOpen.bind(this);
	this.socket.onerror = this.onClose.bind(this);
	this.socket.onclose = this.onClose.bind(this);
};

WSSource.prototype.resume = function(secondsHeadroom) {
	// Nothing to do here
};

WSSource.prototype.onOpen = function() {
	this.progress = 1;
};

WSSource.prototype.onClose = function() {
	if (this.shouldAttemptReconnect) {
		clearTimeout(this.reconnectTimeoutId);
		this.reconnectTimeoutId = setTimeout(function(){
			this.start();
		}.bind(this), this.reconnectInterval*1000);
	}
};

WSSource.prototype.onMessage = function(ev) {
	var isFirstChunk = !this.established;
	this.established = true;

	if (isFirstChunk && this.onEstablishedCallback) {
		this.onEstablishedCallback(this);
	}

	if (this.destination) {
		this.destination.write(ev.data);
	}
};

return WSSource;

})();

<commit_msg>Fix WebSocket constructor chocking on empty protocol string; close #143<commit_after>JSMpeg.Source.WebSocket = (function(){ "use strict";

var WSSource = function(url, options) {
	this.url = url;
	this.options = options;
	this.socket = null;
	this.streaming = true;

	this.callbacks = {connect: [], data: []};
	this.destination = null;

	this.reconnectInterval = options.reconnectInterval !== undefined
		? options.reconnectInterval
		: 5;
	this.shouldAttemptReconnect = !!this.reconnectInterval;

	this.completed = false;
	this.established = false;
	this.progress = 0;

	this.reconnectTimeoutId = 0;

	this.onEstablishedCallback = options.onSourceEstablished;
	this.onCompletedCallback = options.onSourceCompleted; // Never used
};

WSSource.prototype.connect = function(destination) {
	this.destination = destination;
};

WSSource.prototype.destroy = function() {
	clearTimeout(this.reconnectTimeoutId);
	this.shouldAttemptReconnect = false;
	this.socket.close();
};

WSSource.prototype.start = function() {
	this.shouldAttemptReconnect = !!this.reconnectInterval;
	this.progress = 0;
	this.established = false;

	if (this.options.protocols) {
		this.socket = new WebSocket(this.url, this.options.protocols);
	}
	else {
		this.socket = new WebSocket(this.url);
	}
	this.socket.binaryType = 'arraybuffer';
	this.socket.onmessage = this.onMessage.bind(this);
	this.socket.onopen = this.onOpen.bind(this);
	this.socket.onerror = this.onClose.bind(this);
	this.socket.onclose = this.onClose.bind(this);
};

WSSource.prototype.resume = function(secondsHeadroom) {
	// Nothing to do here
};

WSSource.prototype.onOpen = function() {
	this.progress = 1;
};

WSSource.prototype.onClose = function() {
	if (this.shouldAttemptReconnect) {
		clearTimeout(this.reconnectTimeoutId);
		this.reconnectTimeoutId = setTimeout(function(){
			this.start();
		}.bind(this), this.reconnectInterval*1000);
	}
};

WSSource.prototype.onMessage = function(ev) {
	var isFirstChunk = !this.established;
	this.established = true;

	if (isFirstChunk && this.onEstablishedCallback) {
		this.onEstablishedCallback(this);
	}

	if (this.destination) {
		this.destination.write(ev.data);
	}
};

return WSSource;

})();

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * TodoStore
 */

var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TodoConstants = require('../constants/TodoConstants');
var assign = require('object-assign');

var CHANGE_EVENT = 'change';

var _todos = {};

/**
 * Create a TODO item.
 * @param  {string} text The content of the TODO
 */
function create(text) {
  // Hand waving here -- not showing how this interacts with XHR or persistent
  // server-side storage.
  // Using the current timestamp + random number in place of a real id.
  var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
  _todos[id] = {
    id: id,
    complete: false,
    text: text
  };
}

/**
 * Update a TODO item.
 * @param  {string} id
 * @param {object} updates An object literal containing only the data to be
 *     updated.
 */
function update(id, updates) {
  _todos[id] = assign({}, _todos[id], updates);
}

/**
 * Update all of the TODO items with the same object.
 *     the data to be updated.  Used to mark all TODOs as completed.
 * @param  {object} updates An object literal containing only the data to be
 *     updated.

 */
function updateAll(updates) {
  for (var id in _todos) {
    update(id, updates);
  }
}

/**
 * Delete a TODO item.
 * @param  {string} id
 */
function destroy(id) {
  delete _todos[id];
}

/**
 * Delete all the completed TODO items.
 */
function destroyCompleted() {
  for (var id in _todos) {
    if (_todos[id].complete) {
      destroy(id);
    }
  }
}

var TodoStore = assign({}, EventEmitter.prototype, {

  /**
   * Tests whether all the remaining TODO items are marked as completed.
   * @return {boolean}
   */
  areAllComplete: function() {
    for (var id in _todos) {
      if (!_todos[id].complete) {
        return false;
      }
    }
    return true;
  },

  /**
   * Get the entire collection of TODOs.
   * @return {object}
   */
  getAll: function() {
    return _todos;
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
});

// Register callback to handle all updates
AppDispatcher.register(function(action) {
  var text;

  switch(action.actionType) {
    case TodoConstants.TODO_CREATE:
      text = action.text.trim();
      if (text !== '') {
        create(text);
      }
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_TOGGLE_COMPLETE_ALL:
      if (TodoStore.areAllComplete()) {
        updateAll({complete: false});
      } else {
        updateAll({complete: true});
      }
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_UNDO_COMPLETE:
      update(action.id, {complete: false});
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_COMPLETE:
      update(action.id, {complete: true});
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_UPDATE_TEXT:
      text = action.text.trim();
      if (text !== '') {
        update(action.id, {text: text});
      }
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_DESTROY:
      destroy(action.id);
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_DESTROY_COMPLETED:
      destroyCompleted();
      TodoStore.emitChange();
      break;

    default:
      // no op
  }
});

module.exports = TodoStore;
<commit_msg>I think it should be like that. Isn't it ?<commit_after>/*
 * Copyright (c) 2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * TodoStore
 */

var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TodoConstants = require('../constants/TodoConstants');
var assign = require('object-assign');

var CHANGE_EVENT = 'change';

var _todos = {};

/**
 * Create a TODO item.
 * @param  {string} text The content of the TODO
 */
function create(text) {
  // Hand waving here -- not showing how this interacts with XHR or persistent
  // server-side storage.
  // Using the current timestamp + random number in place of a real id.
  var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
  _todos[id] = {
    id: id,
    complete: false,
    text: text
  };
}

/**
 * Update a TODO item.
 * @param  {string} id
 * @param {object} updates An object literal containing only the data to be
 *     updated.
 */
function update(id, updates) {
  _todos[id] = assign({}, _todos[id], updates);
}

/**
 * Update all of the TODO items with the same object.
 *     the data to be updated.  Used to mark all TODOs as completed.
 * @param  {object} updates An object literal containing only the data to be
 *     updated.

 */
function updateAll(updates) {
  for (var id in _todos) {
    update(id, updates);
  }
}

/**
 * Delete a TODO item.
 * @param  {string} id
 */
function destroy(id) {
  delete _todos[id];
}

/**
 * Delete all the completed TODO items.
 */
function destroyCompleted() {
  for (var id in _todos) {
    if (_todos[id].complete) {
      destroy(id);
    }
  }
}

var TodoStore = assign({}, EventEmitter.prototype, {

  /**
   * Tests whether all the remaining TODO items are marked as completed.
   * @return {boolean}
   */
  areAllComplete: function() {
    for (var id in _todos) {
      if (!_todos[id].complete) {
        return false;
      }
    }
    return true;
  },

  /**
   * Get the entire collection of TODOs.
   * @return {object}
   */
  getAll: function() {
    return _todos;
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
});

// Register callback to handle all updates
AppDispatcher.register(function(action) {
  var text;

  switch(action.actionType) {
    case TodoConstants.TODO_CREATE:
      text = action.text.trim();
      if (text !== '') {
        create(text);
        TodoStore.emitChange();
      }
      break;

    case TodoConstants.TODO_TOGGLE_COMPLETE_ALL:
      if (TodoStore.areAllComplete()) {
        updateAll({complete: false});
      } else {
        updateAll({complete: true});
      }
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_UNDO_COMPLETE:
      update(action.id, {complete: false});
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_COMPLETE:
      update(action.id, {complete: true});
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_UPDATE_TEXT:
      text = action.text.trim();
      if (text !== '') {
        update(action.id, {text: text});
        TodoStore.emitChange();
      }
      break;

    case TodoConstants.TODO_DESTROY:
      destroy(action.id);
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_DESTROY_COMPLETED:
      destroyCompleted();
      TodoStore.emitChange();
      break;

    default:
      // no op
  }
});

module.exports = TodoStore;
<|endoftext|>