<commit_before>// @flow

import Avatar from '@atlaskit/avatar';
import InlineMessage from '@atlaskit/inline-message';
import React from 'react';
import type { Dispatch } from 'redux';

import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
import { Dialog, hideDialog } from '../../../../base/dialog';
import { translate, translateToHTML } from '../../../../base/i18n';
import { getLocalParticipant } from '../../../../base/participants';
import { MultiSelectAutocomplete } from '../../../../base/react';
import { connect } from '../../../../base/redux';

import AbstractAddPeopleDialog, {
    type Props as AbstractProps,
    type State,
    _mapStateToProps as _abstractMapStateToProps
} from '../AbstractAddPeopleDialog';

declare var interfaceConfig: Object;

/**
 * The type of the React {@code Component} props of {@link AddPeopleDialog}.
 */
type Props = AbstractProps & {

    /**
     * The {@link JitsiMeetConference} which will be used to invite "room"
     * participants through the SIP Jibri (Video SIP gateway).
     */
    _conference: Object,

    /**
     * Whether to show a footer text after the search results as a last element.
     */
    _footerTextEnabled: boolean,

    /**
     * The redux {@code dispatch} function.
     */
    dispatch: Dispatch<any>,

    /**
     * Invoked to obtain translated strings.
     */
    t: Function,
};

/**
 * The dialog that allows to invite people to the call.
 */
class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
    _multiselect = null;

    _resourceClient: Object;

    state = {
        addToCallError: false,
        addToCallInProgress: false,
        inviteItems: []
    };

    /**
     * Initializes a new {@code AddPeopleDialog} instance.
     *
     * @param {Object} props - The read-only properties with which the new
     * instance is to be initialized.
     */
    constructor(props: Props) {
        super(props);

        // Bind event handlers so they are only bound once per instance.
        this._onItemSelected = this._onItemSelected.bind(this);
        this._onSelectionChange = this._onSelectionChange.bind(this);
        this._onSubmit = this._onSubmit.bind(this);
        this._parseQueryResults = this._parseQueryResults.bind(this);
        this._setMultiSelectElement = this._setMultiSelectElement.bind(this);

        this._resourceClient = {
            makeQuery: this._query,
            parseResults: this._parseQueryResults
        };
    }

    /**
     * Sends an analytics event to record the dialog has been shown.
     *
     * @inheritdoc
     * @returns {void}
     */
    componentDidMount() {
        sendAnalytics(createInviteDialogEvent(
            'invite.dialog.opened', 'dialog'));
    }

    /**
     * React Component method that executes once component is updated.
     *
     * @param {Object} prevProps - The state object before the update.
     * @param {Object} prevState - The state object before the update.
     * @returns {void}
     */
    componentDidUpdate(prevProps, prevState) {
        /**
         * Clears selected items from the multi select component on successful
         * invite.
         */
        if (prevState.addToCallError
                && !this.state.addToCallInProgress
                && !this.state.addToCallError
                && this._multiselect) {
            this._multiselect.setSelectedItems([]);
        }
    }

    /**
     * Sends an analytics event to record the dialog has been closed.
     *
     * @inheritdoc
     * @returns {void}
     */
    componentWillUnmount() {
        sendAnalytics(createInviteDialogEvent(
            'invite.dialog.closed', 'dialog'));
    }

    /**
     * Renders the content of this component.
     *
     * @returns {ReactElement}
     */
    render() {
        const {
            _addPeopleEnabled,
            _dialOutEnabled,
            _footerTextEnabled,
            t
        } = this.props;
        let isMultiSelectDisabled = this.state.addToCallInProgress || false;
        let placeholder;
        let loadingMessage;
        let noMatches;
        let footerText;

        if (_addPeopleEnabled && _dialOutEnabled) {
            loadingMessage = 'addPeople.loading';
            noMatches = 'addPeople.noResults';
            placeholder = 'addPeople.searchPeopleAndNumbers';
        } else if (_addPeopleEnabled) {
            loadingMessage = 'addPeople.loadingPeople';
            noMatches = 'addPeople.noResults';
            placeholder = 'addPeople.searchPeople';
        } else if (_dialOutEnabled) {
            loadingMessage = 'addPeople.loadingNumber';
            noMatches = 'addPeople.noValidNumbers';
            placeholder = 'addPeople.searchNumbers';
        } else {
            isMultiSelectDisabled = true;
            noMatches = 'addPeople.noResults';
            placeholder = 'addPeople.disabled';
        }

        if (_footerTextEnabled) {
            footerText = {
                content:

                    { translateToHTML(t, 'addPeople.footerText') }

            };
        }

        return (
            <Dialog
                okDisabled = { this._isAddDisabled() }
                okKey = 'addPeople.add'
                onSubmit = { this._onSubmit }
                titleKey = 'addPeople.title'
                width = 'medium'>

                    { this._renderErrorMessage() }
                    <MultiSelectAutocomplete
                        footer = { footerText }
                        isDisabled = { isMultiSelectDisabled }
                        loadingMessage = { t(loadingMessage) }
                        noMatchesFound = { t(noMatches) }
                        onItemSelected = { this._onItemSelected }
                        onSelectionChange = { this._onSelectionChange }
                        placeholder = { t(placeholder) }
                        ref = { this._setMultiSelectElement }
                        resourceClient = { this._resourceClient }
                        shouldFitContainer = { true }
                        shouldFocus = { true } />

            </Dialog>
        );
    }

    _invite: Array<Object> => Promise<*>

    _isAddDisabled: () => boolean;

    _onItemSelected: (Object) => Object;

    /**
     * Callback invoked when a selection has been made but before it has been
     * set as selected.
     *
     * @param {Object} item - The item that has just been selected.
     * @private
     * @returns {Object} The item to display as selected in the input.
     */
    _onItemSelected(item) {
        if (item.item.type === 'phone') {
            item.content = item.item.number;
        }

        return item;
    }

    _onSelectionChange: (Map<*, *>) => void;

    /**
     * Handles a selection change.
     *
     * @param {Map} selectedItems - The list of selected items.
     * @private
     * @returns {void}
     */
    _onSelectionChange(selectedItems) {
        this.setState({
            inviteItems: selectedItems
        });
    }

    _onSubmit: () => void;

    /**
     * Submits the selection for inviting.
     *
     * @private
     * @returns {void}
     */
    _onSubmit() {
        const { inviteItems } = this.state;
        const invitees = inviteItems.map(({ item }) => item);

        this._invite(invitees)
            .then(invitesLeftToSend => {
                if (invitesLeftToSend.length) {
                    const unsentInviteIDs
                        = invitesLeftToSend.map(invitee =>
                            invitee.id || invitee.number);
                    const itemsToSelect
                        = inviteItems.filter(({ item }) =>
                            unsentInviteIDs.includes(item.id || item.number));

                    if (this._multiselect) {
                        this._multiselect.setSelectedItems(itemsToSelect);
                    }
                } else {
                    this.props.dispatch(hideDialog());
                }
            });
    }

    _parseQueryResults: (?Array<Object>) => Array<Object>;

    /**
     * Processes results from requesting available numbers and people by munging
     * each result into a format {@code MultiSelectAutocomplete} can use for
     * display.
     *
     * @param {Array} response - The response object from the server for the
     * query.
     * @private
     * @returns {Object[]} Configuration objects for items to display in the
     * search autocomplete.
     */
    _parseQueryResults(response = []) {
        const { t } = this.props;
        const users = response.filter(item => item.type !== 'phone');
        const userDisplayItems = users.map(user => {
            return {
                content: user.name,
                elemBefore: <Avatar
                    size = 'small'
                    src = { user.avatar } />,
                item: user,
                tag: {
                    elemBefore: <Avatar
                        size = 'xsmall'
                        src = { user.avatar } />
                },
                value: user.id
            };
        });

        const numbers = response.filter(item => item.type === 'phone');
        const telephoneIcon = this._renderTelephoneIcon();

        const numberDisplayItems = numbers.map(number => {
            const numberNotAllowedMessage
                = number.allowed ? '' : t('addPeople.countryNotSupported');
            const countryCodeReminder = number.showCountryCodeReminder
                ? t('addPeople.countryReminder') : '';
            const description
                = `${numberNotAllowedMessage} ${countryCodeReminder}`.trim();

            return {
                filterValues: [
                    number.originalEntry,
                    number.number
                ],
                content: t('addPeople.telephone', { number: number.number }),
                description,
                isDisabled: !number.allowed,
                elemBefore: telephoneIcon,
                item: number,
                tag: {
                    elemBefore: telephoneIcon
                },
                value: number.number
            };
        });

        return [
            ...userDisplayItems,
            ...numberDisplayItems
        ];
    }

    _query: (string) => Promise<Array<Object>>;

    /**
     * Renders the error message if the add doesn't succeed.
     *
     * @private
     * @returns {ReactElement|null}
     */
    _renderErrorMessage() {
        if (!this.state.addToCallError) {
            return null;
        }

        const { t } = this.props;
        const supportString = t('inlineDialogFailure.supportMsg');
        const supportLink = interfaceConfig.SUPPORT_URL;
        const supportLinkContent
            = (

                        { supportString.padEnd(supportString.length + 1) }

                            { t('inlineDialogFailure.support') }

                    .

            );

        return (

                <InlineMessage
                    title = { t('addPeople.failedToAdd') }
                    type = 'error'>
                    { supportLinkContent }
                </InlineMessage>

        );
    }

    /**
     * Renders a telephone icon.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderTelephoneIcon() {
        return (

        );
    }

    _setMultiSelectElement: (React$ElementRef<*> | null) => mixed;

    /**
     * Sets the instance variable for the multi select component
     * element so it can be accessed directly.
     *
     * @param {Object} element - The DOM element for the component's dialog.
     * @private
     * @returns {void}
     */
    _setMultiSelectElement(element) {
        this._multiselect = element;
    }
}

/**
 * Maps (parts of) the Redux state to the associated
 * {@code AddPeopleDialog}'s props.
 *
 * @param {Object} state - The Redux state.
 * @private
 * @returns {{
 *     _dialOutAuthUrl: string,
 *     _jwt: string,
 *     _peopleSearchQueryTypes: Array<string>,
 *     _peopleSearchUrl: string
 * }}
 */
function _mapStateToProps(state) {
    const {
        enableFeaturesBasedOnToken
    } = state['features/base/config'];
    let footerTextEnabled = false;

    if (enableFeaturesBasedOnToken) {
        const { features = {} } = getLocalParticipant(state);

        if (String(features['outbound-call']) !== 'true') {
            footerTextEnabled = true;
        }
    }

    return {
        ..._abstractMapStateToProps(state),
        _footerTextEnabled: footerTextEnabled
    };
}

export default translate(connect(_mapStateToProps)(AddPeopleDialog));
<commit_msg>fix(invite): show telephone icon<commit_after>// @flow

import Avatar from '@atlaskit/avatar';
import InlineMessage from '@atlaskit/inline-message';
import React from 'react';
import type { Dispatch } from 'redux';

import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
import { Dialog, hideDialog } from '../../../../base/dialog';
import { translate, translateToHTML } from '../../../../base/i18n';
import { getLocalParticipant } from '../../../../base/participants';
import { MultiSelectAutocomplete } from '../../../../base/react';
import { connect } from '../../../../base/redux';

import AbstractAddPeopleDialog, {
    type Props as AbstractProps,
    type State,
    _mapStateToProps as _abstractMapStateToProps
} from '../AbstractAddPeopleDialog';

declare var interfaceConfig: Object;

/**
 * The type of the React {@code Component} props of {@link AddPeopleDialog}.
 */
type Props = AbstractProps & {

    /**
     * The {@link JitsiMeetConference} which will be used to invite "room"
     * participants through the SIP Jibri (Video SIP gateway).
     */
    _conference: Object,

    /**
     * Whether to show a footer text after the search results as a last element.
     */
    _footerTextEnabled: boolean,

    /**
     * The redux {@code dispatch} function.
     */
    dispatch: Dispatch<any>,

    /**
     * Invoked to obtain translated strings.
     */
    t: Function,
};

/**
 * The dialog that allows to invite people to the call.
 */
class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
    _multiselect = null;

    _resourceClient: Object;

    state = {
        addToCallError: false,
        addToCallInProgress: false,
        inviteItems: []
    };

    /**
     * Initializes a new {@code AddPeopleDialog} instance.
     *
     * @param {Object} props - The read-only properties with which the new
     * instance is to be initialized.
     */
    constructor(props: Props) {
        super(props);

        // Bind event handlers so they are only bound once per instance.
        this._onItemSelected = this._onItemSelected.bind(this);
        this._onSelectionChange = this._onSelectionChange.bind(this);
        this._onSubmit = this._onSubmit.bind(this);
        this._parseQueryResults = this._parseQueryResults.bind(this);
        this._setMultiSelectElement = this._setMultiSelectElement.bind(this);

        this._resourceClient = {
            makeQuery: this._query,
            parseResults: this._parseQueryResults
        };
    }

    /**
     * Sends an analytics event to record the dialog has been shown.
     *
     * @inheritdoc
     * @returns {void}
     */
    componentDidMount() {
        sendAnalytics(createInviteDialogEvent(
            'invite.dialog.opened', 'dialog'));
    }

    /**
     * React Component method that executes once component is updated.
     *
     * @param {Object} prevProps - The state object before the update.
     * @param {Object} prevState - The state object before the update.
     * @returns {void}
     */
    componentDidUpdate(prevProps, prevState) {
        /**
         * Clears selected items from the multi select component on successful
         * invite.
         */
        if (prevState.addToCallError
                && !this.state.addToCallInProgress
                && !this.state.addToCallError
                && this._multiselect) {
            this._multiselect.setSelectedItems([]);
        }
    }

    /**
     * Sends an analytics event to record the dialog has been closed.
     *
     * @inheritdoc
     * @returns {void}
     */
    componentWillUnmount() {
        sendAnalytics(createInviteDialogEvent(
            'invite.dialog.closed', 'dialog'));
    }

    /**
     * Renders the content of this component.
     *
     * @returns {ReactElement}
     */
    render() {
        const {
            _addPeopleEnabled,
            _dialOutEnabled,
            _footerTextEnabled,
            t
        } = this.props;
        let isMultiSelectDisabled = this.state.addToCallInProgress || false;
        let placeholder;
        let loadingMessage;
        let noMatches;
        let footerText;

        if (_addPeopleEnabled && _dialOutEnabled) {
            loadingMessage = 'addPeople.loading';
            noMatches = 'addPeople.noResults';
            placeholder = 'addPeople.searchPeopleAndNumbers';
        } else if (_addPeopleEnabled) {
            loadingMessage = 'addPeople.loadingPeople';
            noMatches = 'addPeople.noResults';
            placeholder = 'addPeople.searchPeople';
        } else if (_dialOutEnabled) {
            loadingMessage = 'addPeople.loadingNumber';
            noMatches = 'addPeople.noValidNumbers';
            placeholder = 'addPeople.searchNumbers';
        } else {
            isMultiSelectDisabled = true;
            noMatches = 'addPeople.noResults';
            placeholder = 'addPeople.disabled';
        }

        if (_footerTextEnabled) {
            footerText = {
                content:

                    { translateToHTML(t, 'addPeople.footerText') }

            };
        }

        return (
            <Dialog
                okDisabled = { this._isAddDisabled() }
                okKey = 'addPeople.add'
                onSubmit = { this._onSubmit }
                titleKey = 'addPeople.title'
                width = 'medium'>

                    { this._renderErrorMessage() }
                    <MultiSelectAutocomplete
                        footer = { footerText }
                        isDisabled = { isMultiSelectDisabled }
                        loadingMessage = { t(loadingMessage) }
                        noMatchesFound = { t(noMatches) }
                        onItemSelected = { this._onItemSelected }
                        onSelectionChange = { this._onSelectionChange }
                        placeholder = { t(placeholder) }
                        ref = { this._setMultiSelectElement }
                        resourceClient = { this._resourceClient }
                        shouldFitContainer = { true }
                        shouldFocus = { true } />

            </Dialog>
        );
    }

    _invite: Array<Object> => Promise<*>

    _isAddDisabled: () => boolean;

    _onItemSelected: (Object) => Object;

    /**
     * Callback invoked when a selection has been made but before it has been
     * set as selected.
     *
     * @param {Object} item - The item that has just been selected.
     * @private
     * @returns {Object} The item to display as selected in the input.
     */
    _onItemSelected(item) {
        if (item.item.type === 'phone') {
            item.content = item.item.number;
        }

        return item;
    }

    _onSelectionChange: (Map<*, *>) => void;

    /**
     * Handles a selection change.
     *
     * @param {Map} selectedItems - The list of selected items.
     * @private
     * @returns {void}
     */
    _onSelectionChange(selectedItems) {
        this.setState({
            inviteItems: selectedItems
        });
    }

    _onSubmit: () => void;

    /**
     * Submits the selection for inviting.
     *
     * @private
     * @returns {void}
     */
    _onSubmit() {
        const { inviteItems } = this.state;
        const invitees = inviteItems.map(({ item }) => item);

        this._invite(invitees)
            .then(invitesLeftToSend => {
                if (invitesLeftToSend.length) {
                    const unsentInviteIDs
                        = invitesLeftToSend.map(invitee =>
                            invitee.id || invitee.number);
                    const itemsToSelect
                        = inviteItems.filter(({ item }) =>
                            unsentInviteIDs.includes(item.id || item.number));

                    if (this._multiselect) {
                        this._multiselect.setSelectedItems(itemsToSelect);
                    }
                } else {
                    this.props.dispatch(hideDialog());
                }
            });
    }

    _parseQueryResults: (?Array<Object>) => Array<Object>;

    /**
     * Processes results from requesting available numbers and people by munging
     * each result into a format {@code MultiSelectAutocomplete} can use for
     * display.
     *
     * @param {Array} response - The response object from the server for the
     * query.
     * @private
     * @returns {Object[]} Configuration objects for items to display in the
     * search autocomplete.
     */
    _parseQueryResults(response = []) {
        const { t } = this.props;
        const users = response.filter(item => item.type !== 'phone');
        const userDisplayItems = users.map(user => {
            return {
                content: user.name,
                elemBefore: <Avatar
                    size = 'small'
                    src = { user.avatar } />,
                item: user,
                tag: {
                    elemBefore: <Avatar
                        size = 'xsmall'
                        src = { user.avatar } />
                },
                value: user.id
            };
        });

        const numbers = response.filter(item => item.type === 'phone');
        const telephoneIcon = this._renderTelephoneIcon();

        const numberDisplayItems = numbers.map(number => {
            const numberNotAllowedMessage
                = number.allowed ? '' : t('addPeople.countryNotSupported');
            const countryCodeReminder = number.showCountryCodeReminder
                ? t('addPeople.countryReminder') : '';
            const description
                = `${numberNotAllowedMessage} ${countryCodeReminder}`.trim();

            return {
                filterValues: [
                    number.originalEntry,
                    number.number
                ],
                content: t('addPeople.telephone', { number: number.number }),
                description,
                isDisabled: !number.allowed,
                elemBefore: telephoneIcon,
                item: number,
                tag: {
                    elemBefore: telephoneIcon
                },
                value: number.number
            };
        });

        return [
            ...userDisplayItems,
            ...numberDisplayItems
        ];
    }

    _query: (string) => Promise<Array<Object>>;

    /**
     * Renders the error message if the add doesn't succeed.
     *
     * @private
     * @returns {ReactElement|null}
     */
    _renderErrorMessage() {
        if (!this.state.addToCallError) {
            return null;
        }

        const { t } = this.props;
        const supportString = t('inlineDialogFailure.supportMsg');
        const supportLink = interfaceConfig.SUPPORT_URL;
        const supportLinkContent
            = (

                        { supportString.padEnd(supportString.length + 1) }

                            { t('inlineDialogFailure.support') }

                    .

            );

        return (

                <InlineMessage
                    title = { t('addPeople.failedToAdd') }
                    type = 'error'>
                    { supportLinkContent }
                </InlineMessage>

        );
    }

    /**
     * Renders a telephone icon.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderTelephoneIcon() {
        return (

        );
    }

    _setMultiSelectElement: (React$ElementRef<*> | null) => mixed;

    /**
     * Sets the instance variable for the multi select component
     * element so it can be accessed directly.
     *
     * @param {Object} element - The DOM element for the component's dialog.
     * @private
     * @returns {void}
     */
    _setMultiSelectElement(element) {
        this._multiselect = element;
    }
}

/**
 * Maps (parts of) the Redux state to the associated
 * {@code AddPeopleDialog}'s props.
 *
 * @param {Object} state - The Redux state.
 * @private
 * @returns {{
 *     _dialOutAuthUrl: string,
 *     _jwt: string,
 *     _peopleSearchQueryTypes: Array<string>,
 *     _peopleSearchUrl: string
 * }}
 */
function _mapStateToProps(state) {
    const {
        enableFeaturesBasedOnToken
    } = state['features/base/config'];
    let footerTextEnabled = false;

    if (enableFeaturesBasedOnToken) {
        const { features = {} } = getLocalParticipant(state);

        if (String(features['outbound-call']) !== 'true') {
            footerTextEnabled = true;
        }
    }

    return {
        ..._abstractMapStateToProps(state),
        _footerTextEnabled: footerTextEnabled
    };
}

export default translate(connect(_mapStateToProps)(AddPeopleDialog));
<|endoftext|>


<commit_before>function setupCKEditorInstance(editor, path) {
	var atd_core = undefined;

        /*
	 * Step 1: Initialize AtD Shared UI Module
         */

	var load_AtD_core = function(success) {
		if (!success) {
			alert("Couldn't load: scripts/atd.core.js");
			return;
		}

		atd_core = new AtDCore();

		/* initialize all functions that AtD/Core will require */

		atd_core.map = function(array, callback) {
			for (var x = 0; x < array.length; x++) {
				callback(array[x]);
			}
		};

		atd_core.hasClass = function(node, className) {
			return node != null && node.nodeType != 3 && CKEDITOR.dom.element.get(node).hasClass(className);
		};

	        atd_core.contents = function(node) {
			if (node.$)
				return node.$.childNodes;
			return node.childNodes;
		};

	        atd_core.replaceWith = function(old_node, new_node) {
			return new_node.replace(CKEDITOR.dom.element.get(old_node));
		};

	        atd_core.create = function(node_html) {
			return CKEDITOR.dom.element.createFromHtml( '' + node_html + '' );
		};

	        atd_core.removeParent = function(node) {
			return CKEDITOR.dom.element.get(node).remove(true);
		};

		atd_core.remove = function(node) {
			return CKEDITOR.dom.element.get(node).remove(false);
		};

		atd_core.getAttrib = function(node, key) {
			return CKEDITOR.dom.element.get(node).getAttribute(key);
		};

		atd_core.findSpans = function(parent) {
			var results = [];
			var elements = editor.document.getElementsByTag('span');
			for (var x = 0; x < elements.count(); x++)
				results.push(elements.getItem(x).$);
			return results;
		};

		/* set options */
		atd_core.showTypes('Bias Language,Cliches,Complex Expression,Diacritical Marks,Double Negatives,Hidden Verbs,Jargon Language,Passive voice,Phrases to Avoid,Redundant Expression');
	};

	/* tell CKEditor to actually load the AtD Core UI module */
	CKEDITOR.scriptLoader.load(path + 'scripts/atd.core.js', load_AtD_core);

	/*
	 * Step 2. Install listeners to remove AtD markup at key points.
	 */

	/* remove markup if view source command is executed */
	editor.on( 'beforeCommandExec', function(event) {
		if ((event.data.name == 'source' ||  event.data.name == 'newpage') && editor.mode == 'wysiwyg') {
			atd_core.removeWords(undefined);
		}
	});

	/* setup filter to remove AtD markup when editor contents is grabbed */

	/* I'd prefer hooking some sort of on "get" event to call removeWords, but this will have to do. Filters the AtD
	   markup when returning the contents of the editor */
	var dataProcessor = editor.dataProcessor, htmlFilter = dataProcessor && dataProcessor.htmlFilter;
	if ( htmlFilter ) {
		htmlFilter.addRules({
			elements : {
				span : function( element ) {
					if ( atd_core.isMarkedNode(element.$) ) {
						delete element.name;
						return element;
					}
				}
			}
		});
	}

	/*
	 * Step 3. attach AtD listener to context menu
 	 */

	if ( editor.contextMenu && editor.addMenuItems ) {
		editor.contextMenu.addListener(function(element) {
			if (atd_core.isMarkedNode(element.$)) {
				var meta = atd_core.findSuggestion(element.$);
				var commands = {};

				addItem(editor, meta.description, function() { }, 0, commands, 'AtD_description');

				for (var x = 0; x < meta.suggestions.length; x++)
					addItem(editor, meta.suggestions[x], makeCallback(editor, element.$, meta.suggestions[x]), x + 1, commands, 'AtD_suggestions');

				addItem(editor, 'Ignore', makeIgnoreCallback(editor, element.$, element.$.innerHTML), 1, commands, 'AtD_ignore');
				addItem(editor, 'Ignore All', makeIgnoreAllCallback(editor, element.$, element.$.innerHTML), 0, commands, 'AtD_ignore');

				return commands;
			}
		});
	}

	var addItem = function(editor, label, callback, index, suggestions, group) {
		var fixedLabel = label.replace(/[^\w]/g, '_');

		editor.addCommand( 'command_' + fixedLabel, { 'exec': callback });
		editor.addMenuItem( 'command_' + fixedLabel, {
			label : label,
			command: 'command_' + fixedLabel,
			group: group,
			order: index
		});
		suggestions[ 'command_' + fixedLabel ] = CKEDITOR.TRISTATE_OFF;
	}

	var makeCallback = function(editor, element, suggestion) {
		return function() {
			atd_core.applySuggestion(element, suggestion);
		};
	};

	var makeIgnoreAllCallback = function(editor, element, word) {
		return function() {
			atd_core.removeWords(undefined, word);
		};
	};

	var makeIgnoreCallback = function(editor, element, word) {
		return function() {
			CKEDITOR.dom.element.get(element).remove(true);
		};
	};

	/*
	 * Step 4. Button to proofread contents of FCKEditor
	 */

	var errors_received = function(errors) {
		try {
			/* process the errors into something AtD can use */
			var results = atd_core.processXML(errors);

			/* highlight the errors (it would probably help to have the AtD stylesheet loaded */

				/* no matter how much abstracting I do, I can't save you the pain of digging for information like this */
			var nodes = editor.document.getDocumentElement().getChildren().getItem(1)['$'].childNodes;
			atd_core.markMyWords(nodes, results.errors);
		}
		catch (ex) {
			if (console != undefined) {
				console.log("After the Deadline caused an exception:");
				console.log(ex);
			}
		}
	};

	var proofread_action = function() {
		/* obtain the editor contents */
		var editor_contents = editor.document.getBody().getHtml();

		/* remove the AtD markup from the editor */
		atd_core.removeWords(undefined);

		var proxy_server = editor.config.atd_rpc == undefined ? path + 'proxy.php?url=/checkDocument' : editor.config.atd_rpc;
		var atd_api_key  = editor.config.atd_api_key;

		/* post the editor contents to the AtD service */
		var results = post_to_AtD(proxy_server + '/checkDocument', 'data=' + encodeURI(editor_contents).replace(/&/g, '%26') + '&key=' + atd_api_key, errors_received);
	};

	return proofread_action;
}

/* CKEditor's AJAX mechanism doesn't give us a way to post content, we get to make our own... */

function post_to_AtD(url, data, callback) {
	var xhr = (function() {
		try { return new XMLHttpRequest(); } catch(e) {}
		try { return new ActiveXObject( 'Msxml2.XMLHTTP' ); } catch (e) {}
		try { return new ActiveXObject( 'Microsoft.XMLHTTP' ); } catch (e) {}
	})();

	if (!xhr)
		return null;

	xhr.open('POST', url, true );

	xhr.onreadystatechange = function() {
		if ( xhr.readyState == 4 )
			callback( xhr.responseXML ); /* need to make this work with IE */
	};

	xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	//These cause an error
	//http://stackoverflow.com/questions/7210507/ajax-post-error-refused-to-set-unsafe-header-connection
	//xhr.setRequestHeader("Content-length", data.length);
	//xhr.setRequestHeader("Connection", "close");
	xhr.send(data);
};

/* Tell CKEditor we exist and life is never going to be the same */
CKEDITOR.plugins.add('atd-ckeditor', {

	requires : [ 'menubutton' ],

	beforeInit : function(editor) {
		editor.config.menu_groups = 'AtD_description,AtD_suggestions,AtD_ignore,' + editor.config.menu_groups;
	},

        init : function(editor) {
		/* setup the plugin particulars */
		var proofread_action = setupCKEditorInstance(editor, this.path);

                var cmd     = editor.addCommand('AtD', { exec: undefined });
                cmd.modes   = { wysiwyg:1, source:1 };
                cmd.canUndo = false;
		editor.addCommand( 'atd_check', { 'exec': proofread_action });
                editor.ui.addButton('atd-ckeditor', { label: 'Proofread writing', command: 'atd_check', icon: this.path + 'images/atdbuttontr.gif' });
        }
});
<commit_msg>SAK-22412 - ATD plugin doesn't remove markup when saving<commit_after>function setupCKEditorInstance(editor, path) {
	var atd_core = undefined;

        /*
	 * Step 1: Initialize AtD Shared UI Module
         */

	var load_AtD_core = function(success) {
		if (!success) {
			alert("Couldn't load: scripts/atd.core.js");
			return;
		}

		atd_core = new AtDCore();

		/* initialize all functions that AtD/Core will require */

		atd_core.map = function(array, callback) {
			for (var x = 0; x < array.length; x++) {
				callback(array[x]);
			}
		};

		atd_core.hasClass = function(node, className) {
			//For elements just check the class
			if (node != null && node.type == CKEDITOR.NODE_ELEMENT) {
				return node != null && node["type"] != 3 && node["attributes"]["class"] == className;
			}
			return node != null && node.nodeType != 3 && CKEDITOR.dom.element.get(node).hasClass(className);
		};

	        atd_core.contents = function(node) {
			if (node.$)
				return node.$.childNodes;
			return node.childNodes;
		};

	        atd_core.replaceWith = function(old_node, new_node) {
			return new_node.replace(CKEDITOR.dom.element.get(old_node));
		};

	        atd_core.create = function(node_html) {
			return CKEDITOR.dom.element.createFromHtml( '' + node_html + '' );
		};

	        atd_core.removeParent = function(node) {
			return CKEDITOR.dom.element.get(node).remove(true);
		};

		atd_core.remove = function(node) {
			return CKEDITOR.dom.element.get(node).remove(false);
		};

		atd_core.getAttrib = function(node, key) {
			return CKEDITOR.dom.element.get(node).getAttribute(key);
		};

		atd_core.findSpans = function(parent) {
			var results = [];
			var elements = editor.document.getElementsByTag('span');
			for (var x = 0; x < elements.count(); x++)
				results.push(elements.getItem(x).$);
			return results;
		};

		/* set options */
		atd_core.showTypes('Bias Language,Cliches,Complex Expression,Diacritical Marks,Double Negatives,Hidden Verbs,Jargon Language,Passive voice,Phrases to Avoid,Redundant Expression');
	};

	/* tell CKEditor to actually load the AtD Core UI module */
	CKEDITOR.scriptLoader.load(path + 'scripts/atd.core.js', load_AtD_core);

	/*
	 * Step 2. Install listeners to remove AtD markup at key points.
	 */

	/* remove markup if view source command is executed */
	editor.on( 'beforeCommandExec', function(event) {
		if ((event.data.name == 'source' ||  event.data.name == 'newpage') && editor.mode == 'wysiwyg') {
			atd_core.removeWords(undefined);
		}
	});

	/* setup filter to remove AtD markup when editor contents is grabbed */

	/* I'd prefer hooking some sort of on "get" event to call removeWords, but this will have to do. Filters the AtD
	   markup when returning the contents of the editor */
	var dataProcessor = editor.dataProcessor, htmlFilter = dataProcessor && dataProcessor.htmlFilter;
	if ( htmlFilter ) {
		htmlFilter.addRules({
			elements : {
				span : function( element ) {
					if ( atd_core.isMarkedNode(element) ) {
						delete element.name;
						return element;
					}
				}
			}
		});
	}

	/*
	 * Step 3. attach AtD listener to context menu
 	 */

	if ( editor.contextMenu && editor.addMenuItems ) {
		editor.contextMenu.addListener(function(element) {
			if (atd_core.isMarkedNode(element.$)) {
				var meta = atd_core.findSuggestion(element.$);
				var commands = {};

				addItem(editor, meta.description, function() { }, 0, commands, 'AtD_description');

				for (var x = 0; x < meta.suggestions.length; x++)
					addItem(editor, meta.suggestions[x], makeCallback(editor, element.$, meta.suggestions[x]), x + 1, commands, 'AtD_suggestions');

				addItem(editor, 'Ignore', makeIgnoreCallback(editor, element.$, element.$.innerHTML), 1, commands, 'AtD_ignore');
				addItem(editor, 'Ignore All', makeIgnoreAllCallback(editor, element.$, element.$.innerHTML), 0, commands, 'AtD_ignore');

				return commands;
			}
		});
	}

	var addItem = function(editor, label, callback, index, suggestions, group) {
		var fixedLabel = label.replace(/[^\w]/g, '_');

		editor.addCommand( 'command_' + fixedLabel, { 'exec': callback });
		editor.addMenuItem( 'command_' + fixedLabel, {
			label : label,
			command: 'command_' + fixedLabel,
			group: group,
			order: index
		});
		suggestions[ 'command_' + fixedLabel ] = CKEDITOR.TRISTATE_OFF;
	}

	var makeCallback = function(editor, element, suggestion) {
		return function() {
			atd_core.applySuggestion(element, suggestion);
		};
	};

	var makeIgnoreAllCallback = function(editor, element, word) {
		return function() {
			atd_core.removeWords(undefined, word);
		};
	};

	var makeIgnoreCallback = function(editor, element, word) {
		return function() {
			CKEDITOR.dom.element.get(element).remove(true);
		};
	};

	/*
	 * Step 4. Button to proofread contents of FCKEditor
	 */

	var errors_received = function(errors) {
		try {
			/* process the errors into something AtD can use */
			var results = atd_core.processXML(errors);

			/* highlight the errors (it would probably help to have the AtD stylesheet loaded */

				/* no matter how much abstracting I do, I can't save you the pain of digging for information like this */
			var nodes = editor.document.getDocumentElement().getChildren().getItem(1)['$'].childNodes;
			atd_core.markMyWords(nodes, results.errors);
		}
		catch (ex) {
			if (console != undefined) {
				console.log("After the Deadline caused an exception:");
				console.log(ex);
			}
		}
	};

	var proofread_action = function() {
		/* obtain the editor contents */
		var editor_contents = editor.document.getBody().getHtml();

		/* remove the AtD markup from the editor */
		atd_core.removeWords(undefined);

		var proxy_server = editor.config.atd_rpc == undefined ? path + 'proxy.php?url=/checkDocument' : editor.config.atd_rpc;
		var atd_api_key  = editor.config.atd_api_key;

		/* post the editor contents to the AtD service */
		var results = post_to_AtD(proxy_server + '/checkDocument', 'data=' + encodeURI(editor_contents).replace(/&/g, '%26') + '&key=' + atd_api_key, errors_received);
	};

	return proofread_action;
}

/* CKEditor's AJAX mechanism doesn't give us a way to post content, we get to make our own... */

function post_to_AtD(url, data, callback) {
	var xhr = (function() {
		try { return new XMLHttpRequest(); } catch(e) {}
		try { return new ActiveXObject( 'Msxml2.XMLHTTP' ); } catch (e) {}
		try { return new ActiveXObject( 'Microsoft.XMLHTTP' ); } catch (e) {}
	})();

	if (!xhr)
		return null;

	xhr.open('POST', url, true );

	xhr.onreadystatechange = function() {
		if ( xhr.readyState == 4 )
			callback( xhr.responseXML ); /* need to make this work with IE */
	};

	xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	//These cause an error
	//http://stackoverflow.com/questions/7210507/ajax-post-error-refused-to-set-unsafe-header-connection
	//xhr.setRequestHeader("Content-length", data.length);
	//xhr.setRequestHeader("Connection", "close");
	xhr.send(data);
};

/* Tell CKEditor we exist and life is never going to be the same */
CKEDITOR.plugins.add('atd-ckeditor', {

	requires : [ 'menubutton' ],

	beforeInit : function(editor) {
		editor.config.menu_groups = 'AtD_description,AtD_suggestions,AtD_ignore,' + editor.config.menu_groups;
	},

        init : function(editor) {
		/* setup the plugin particulars */
		var proofread_action = setupCKEditorInstance(editor, this.path);

                var cmd     = editor.addCommand('AtD', { exec: undefined });
                cmd.modes   = { wysiwyg:1, source:1 };
                cmd.canUndo = false;
		editor.addCommand( 'atd_check', { 'exec': proofread_action });
                editor.ui.addButton('atd-ckeditor', { label: 'Proofread writing', command: 'atd_check', icon: this.path + 'images/atdbuttontr.gif' });
        }
});
<|endoftext|>


<commit_before>import { Template } from 'meteor/templating'
import { Currencies } from '../../../api/indexDB.js'
import { HashAlgorithm, FormData } from '../../../api/indexDB.js'

import './currencyDetail.html'
import './features.html'
import './redflags.html'
import './fundamentalMetrics'
import './redFlagCurrency'

Template.currencyDetail.onCreated(function bodyOnCreated() {
  var self = this
  self.autorun(function(){
    // Gets the _id of the current currency and only subscribes to that particular currency
    SubsCache.subscribe('approvedcurrency', FlowRouter.getParam('slug'))
    SubsCache.subscribe('hashalgorithm')
    SubsCache.subscribe('formdata')
  })
});

Template.currencyDetail.events({});

Template.currencyDetail.helpers({
  thiscurrency () {
    return Currencies.findOne({slug: FlowRouter.getParam("slug")});
  },
  currencyName () {
    return Currencies.findOne({slug: FlowRouter.getParam("slug")}).currencyName;
  },

    finalValue () {
      if (this.maxCoins && this.marketCap) {
      return Math.round(this.marketCap / this.maxCoins).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    } else {
      return "calculating..."
    }
    },
    marketCap () {
      return Math.round(this.marketCap).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    },
    circulating () {
      return Math.round(this.circulating).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    },
    launchDate () {
      if (this.genesisTimestamp) {
      return "Launched " + moment(this.genesisTimestamp).fromNow();
    } else {
      return "";
    }
    },
    link () {
      if (this.genesisTimestamp) {
      return "#";
    } else {
      return "/currency/" + this._id + "?edit=launchdate";
    }
    },
    linktext () {
      if (this.genesisTimestamp) {
        return "";
      } else {
        return "Add the " + this.currencyName + " launch date!"
      }
    }

});

Template.currencyInfo.onRendered(function() {
    $('[data-toggle="tooltip"]').tooltip()

    $.fn.editable.defaults.mode = 'inline' // display them inline
    $.fn.editableform.buttons = `<button type="submit" class="btn btn-primary btn-sm editable-submit"></button><button type="button" class="btn btn-default btn-sm editable-cancel"></button>` // custom buttons with fa icons

    // editable fields
    let editables = ['currencyName', 'currencySymbol', 'premine', 'circulating', 'maxCoins', 'marketCap']

    const validate = function(val) { // the actual proposing part
      if ($(this).attr('id') === 'genesisTimestamp') {
        val = new Date(val).getTime()
      }

      if ($(this).text() !== val) {
        Meteor.call('editCoin', [{
          coin_id: $('#_id').val(),
          coinName: $('#name').val(),
          field: $(this).attr('id'),
          old: $(this).text(),
          new: val,
          changedDate: new Date().getTime(),
          score: 0,
          status: 'pending review'
        }], (error, result) => {
            if (error) {
              console.log(error.reason)
              sAlert.error(error.reason)
            } else {
              console.log('yay')
              sAlert.success('Change proposed.')
            }
        })

        return ''
      }

      return 'Please change the value if you want to propose a change.'
    }

    editables.forEach(i => $(`#${i}`).editable({
      validate: validate
    }))

    $('#genesisTimestamp').editable({
      validate: validate,
      type: 'text',
      value: '01/01/2009'
      /*type: 'combodate', // combodate is refusing to cooperate
      format: 'DD/MM/YYYY',
      value: '01/01/2009',
      template: 'DD / MM / YYYY',
      combodate: {
        minYear: 2009,
        maxYear: new Date().getUTCFullYear(),
        smartDays: true
      }*/
    })

    $('#consensusSecurity').editable({
      validate: validate,
      type: 'select',
      source: FormData.find({}, {}).fetch().map(i => ({
        text: i.name,
        value: i.name
      }))
    })

    $('#hashAlgorithm').editable({
      validate: validate,
      type: ($('#consensusSecurity').text() === 'Proof of Work' || $('#consensusSecurity').text() === 'Hybrid') ? 'select' : 'text', // only show select for these two
      source: () => {
        if ($('#consensusSecurity').text() === 'Proof of Work') {
          return HashAlgorithm.find({}).fetch().map(i => ({
            text: i.name,
            value: i._id
          }))
        } else if ($('#consensusSecurity').text() === 'Hybrid') {
          return HashAlgorithm.find({}).fetch().map(i => {
            i.name = `Staking and ${i.name}`

            return {
              text: i.name,
              value: i._id
            }
          })
        }

        return []
      }
    })
});

Template.currencyInfo.events({
    'click .contribute': function(event) {
        event.preventDefault();
        let slug = FlowRouter.getParam("slug");
        FlowRouter.go('/currencyEdit/' + slug + '/' + event.currentTarget.id);
    }
});

Template.currencyInfo.helpers({
    truncate (val) {
       if (val.length > 20){
      return val.substring(0,20)+'...';
   }
   else{
      return val;
   }
 },
     isDateNull(val, field) {

    if (typeof val == "number") {
                return moment(val).format(_globalDateFormat);
        } else {
            if (field) {
                return 'N\\A'
            }
        }

    },
    isNull(val, field) {

        if (val || val === 0) {
            if (typeof val == "string") {
                return val;
            } else if (typeof val == "object") {

                return val[0].join(", ");
            } else if (typeof val == "number") {
                return val;
            }
        } else {
            if (field) {
                return Spacebars.SafeString(' Contribute');
            }
        }

    },
        isNullReadOnly(val,field) {

        if (val || val === 0) {
            if (typeof val == "string") {
                return val;
            } else if (typeof val == "object") {

                return val[0].join(", ");
            } else if (typeof val == "number") {
                return val;
            }
        } else {

          return '-'
        }

    },
    hashAlgorithm: function() {
      let prefix = ''
      if (this.consensusSecurity === 'Hybrid') {
        prefix = 'Staking and '
      }

      let algo = HashAlgorithm.findOne({
        _id: this.hashAlgorithm
      })

      return algo ? `${prefix}${algo.name}` : this.hashAlgorithm
    }
});<commit_msg>Problem: Data populated by API should not be editable (#451)<commit_after>import { Template } from 'meteor/templating'
import { Currencies } from '../../../api/indexDB.js'
import { HashAlgorithm, FormData } from '../../../api/indexDB.js'

import './currencyDetail.html'
import './features.html'
import './redflags.html'
import './fundamentalMetrics'
import './redFlagCurrency'

Template.currencyDetail.onCreated(function bodyOnCreated() {
  var self = this
  self.autorun(function(){
    // Gets the _id of the current currency and only subscribes to that particular currency
    SubsCache.subscribe('approvedcurrency', FlowRouter.getParam('slug'))
    SubsCache.subscribe('hashalgorithm')
    SubsCache.subscribe('formdata')
  })
});

Template.currencyDetail.events({});

Template.currencyDetail.helpers({
  thiscurrency () {
    return Currencies.findOne({slug: FlowRouter.getParam("slug")});
  },
  currencyName () {
    return Currencies.findOne({slug: FlowRouter.getParam("slug")}).currencyName;
  },

    finalValue () {
      if (this.maxCoins && this.marketCap) {
      return Math.round(this.marketCap / this.maxCoins).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    } else {
      return "calculating..."
    }
    },
    marketCap () {
      return Math.round(this.marketCap).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    },
    circulating () {
      return Math.round(this.circulating).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    },
    launchDate () {
      if (this.genesisTimestamp) {
      return "Launched " + moment(this.genesisTimestamp).fromNow();
    } else {
      return "";
    }
    },
    link () {
      if (this.genesisTimestamp) {
      return "#";
    } else {
      return "/currency/" + this._id + "?edit=launchdate";
    }
    },
    linktext () {
      if (this.genesisTimestamp) {
        return "";
      } else {
        return "Add the " + this.currencyName + " launch date!"
      }
    }

});

Template.currencyInfo.onRendered(function() {
    $('[data-toggle="tooltip"]').tooltip()

    $.fn.editable.defaults.mode = 'inline' // display them inline
    $.fn.editableform.buttons = `<button type="submit" class="btn btn-primary btn-sm editable-submit"></button><button type="button" class="btn btn-default btn-sm editable-cancel"></button>` // custom buttons with fa icons

    // editable fields
    let editables = ['currencyName', 'currencySymbol', 'premine', 'maxCoins']

    const validate = function(val) { // the actual proposing part
      if ($(this).attr('id') === 'genesisTimestamp') {
        val = new Date(val).getTime()
      }

      if ($(this).text() !== val) {
        Meteor.call('editCoin', [{
          coin_id: $('#_id').val(),
          coinName: $('#name').val(),
          field: $(this).attr('id'),
          old: $(this).text(),
          new: val,
          changedDate: new Date().getTime(),
          score: 0,
          status: 'pending review'
        }], (error, result) => {
            if (error) {
              console.log(error.reason)
              sAlert.error(error.reason)
            } else {
              console.log('yay')
              sAlert.success('Change proposed.')
            }
        })

        return ''
      }

      return 'Please change the value if you want to propose a change.'
    }

    editables.forEach(i => $(`#${i}`).editable({
      validate: validate
    }))

    $('#genesisTimestamp').editable({
      validate: validate,
      type: 'text',
      value: '01/01/2009'
      /*type: 'combodate', // combodate is refusing to cooperate
      format: 'DD/MM/YYYY',
      value: '01/01/2009',
      template: 'DD / MM / YYYY',
      combodate: {
        minYear: 2009,
        maxYear: new Date().getUTCFullYear(),
        smartDays: true
      }*/
    })

    $('#consensusSecurity').editable({
      validate: validate,
      type: 'select',
      source: FormData.find({}, {}).fetch().map(i => ({
        text: i.name,
        value: i.name
      }))
    })

    $('#hashAlgorithm').editable({
      validate: validate,
      type: ($('#consensusSecurity').text() === 'Proof of Work' || $('#consensusSecurity').text() === 'Hybrid') ? 'select' : 'text', // only show select for these two
      source: () => {
        if ($('#consensusSecurity').text() === 'Proof of Work') {
          return HashAlgorithm.find({}).fetch().map(i => ({
            text: i.name,
            value: i._id
          }))
        } else if ($('#consensusSecurity').text() === 'Hybrid') {
          return HashAlgorithm.find({}).fetch().map(i => {
            i.name = `Staking and ${i.name}`

            return {
              text: i.name,
              value: i._id
            }
          })
        }

        return []
      }
    })
});

Template.currencyInfo.events({
    'click .contribute': function(event) {
        event.preventDefault();
        let slug = FlowRouter.getParam("slug");
        FlowRouter.go('/currencyEdit/' + slug + '/' + event.currentTarget.id);
    }
});

Template.currencyInfo.helpers({
    truncate (val) {
       if (val.length > 20){
      return val.substring(0,20)+'...';
   }
   else{
      return val;
   }
 },
     isDateNull(val, field) {

    if (typeof val == "number") {
                return moment(val).format(_globalDateFormat);
        } else {
            if (field) {
                return 'N\\A'
            }
        }

    },
    isNull(val, field) {

        if (val || val === 0) {
            if (typeof val == "string") {
                return val;
            } else if (typeof val == "object") {

                return val[0].join(", ");
            } else if (typeof val == "number") {
                return val;
            }
        } else {
            if (field) {
                return Spacebars.SafeString(' Contribute');
            }
        }

    },
        isNullReadOnly(val,field) {

        if (val || val === 0) {
            if (typeof val == "string") {
                return val;
            } else if (typeof val == "object") {

                return val[0].join(", ");
            } else if (typeof val == "number") {
                return val;
            }
        } else {

          return '-'
        }

    },
    hashAlgorithm: function() {
      let prefix = ''
      if (this.consensusSecurity === 'Hybrid') {
        prefix = 'Staking and '
      }

      let algo = HashAlgorithm.findOne({
        _id: this.hashAlgorithm
      })

      return algo ? `${prefix}${algo.name}` : this.hashAlgorithm
    }
});<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Router, browserHistory } from 'react-router';

export default class Routes extends Component {
  render() {
    return (
      <Router history={ browserHistory }>
      </Router>
    );
  }
}
<commit_msg>Adding a route<commit_after>import React, { Component } from 'react';
import { Router, Route, browserHistory } from 'react-router';

import Layout from './layout/layout.js'

export default class Routes extends Component {
  render() {
    return (
      <Router history={ browserHistory }>
        <Route path="/" component={Layout}>
        </Route>
      </Router>
    );
  }
}
<|endoftext|>


<commit_before>// index.js

// request message on server
//Calls SimpleServlet to get the "Hello World" message
xhrGet("SimpleServlet", function(responseText){
	// add to document
	var mytitle = document.getElementById('message');
	mytitle.innerHTML = responseText;

}, function(err){
	console.log(err);
});

//utilities
function createXHR(){
	if(typeof XMLHttpRequest != 'undefined'){
		return new XMLHttpRequest();
	}else{
		try{
			return new ActiveXObject('Msxml2.XMLHTTP');
		}catch(e){
			try{
				return new ActiveXObject('Microsoft.XMLHTTP');
			}catch(e){}
		}
	}
	return null;
}
function xhrGet(url, callback, errback){
	var xhr = new createXHR();
	xhr.open("GET", url, true);
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 4){
			if(xhr.status == 200){
				callback(xhr.responseText);
			}else{
				errback('service not available');
			}
		}
	};
	xhr.timeout = 3000;
	xhr.ontimeout = errback;
	xhr.send();
}

function xhrAttach(url, data, callback, errback)
{
	var xhr = new createXHR();
	xhr.open("POST", url, true);
	//xhr.setRequestHeader("Content-type", "multipart/form-data");
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 4){
			if(xhr.status == 200){
				callback(parseJson(xhr.responseText));
			}else{
				errback("Error: "+xhr.responseText);
			}
		}
	};
	xhr.timeout = 1000000;
	xhr.ontimeout = errback;
	xhr.send(data);
}

function parseJson(str){
	return window.JSON ? JSON.parse(str) : eval('(' + str + ')');
}
function prettyJson(str){
	// If browser does not have JSON utilities, just print the raw string value.
	return window.JSON ? JSON.stringify(JSON.parse(str), null, '  ') : str;
}

<commit_msg>Update index.js<commit_after>// index.js

// request message on server
//Calls SimpleServlet to get the "Hello World" message
xhrGet("SimpleServlet", function(responseText){
	// add to document
	var mytitle = document.getElementById('message');
	mytitle.innerHTML = responseText;

}, function(err){
	console.log(err);
});

//utilities
function createXHR(){
	if(typeof XMLHttpRequest != 'undefined'){
		return new XMLHttpRequest();
	}else{
		try{
			return new ActiveXObject('Msxml2.XMLHTTP');
		}catch(e){
			try{
				return new ActiveXObject('Microsoft.XMLHTTP');
			}catch(e){}
		}
	}
	return null;
}
function xhrGet(url, callback, errback){
	var xhr = new createXHR();
	xhr.open("GET", url, true);
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 4){
			if(xhr.status == 200){
				callback(xhr.responseText);
			}else{
				errback('service not available');
			}
		}
	};
	xhr.timeout = 3000;
	xhr.ontimeout = errback;
	xhr.send();
}

function xhrAttach(url, data, callback, errback)
{
	var xhr = new createXHR();
	xhr.open("POST", url, true);
	//xhr.setRequestHeader("Content-type", "multipart/form-data");
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 4){
			if(xhr.status == 200){
				callback(parseJson(xhr.responseText));
			}else{
				errback("Error: "+xhr.responseText);
			}
		}
	};
	xhr.timeout = 1000000;
	xhr.ontimeout = errback;
	xhr.send(data);
}

function uploadFile(node)
{

	var file = node.previousSibling.files[0];
	//if file not selected, throw error
	if(!file)
	{
		alert("File not selected for upload... \t\t\t\t \n\n - Choose a file to upload. \n - Then click on Upload button.");
		return;
	}

	var row = node.parentNode.parentNode;

	var form = new FormData();
	form.append("file", file);

	var id = row.getAttribute('data-id');

	var queryParams = (id == null) ? "" : "id=" + id;
	queryParams+= "&name="+row.firstChild.firstChild.value;
	queryParams+="&value="+row.firstChild.nextSibling.firstChild.firstChild.firstChild.firstChild.firstChild.value;
	queryParams+= "&filename="+file.name;

	//var table = row.firstChild.nextSibling.firstChild;
	//var newRow = addNewRow(table);

	//startProgressIndicator(newRow);

	xhrAttach("/attach?"+queryParams, form, function(item){
		console.log('attached: ', item);
		//row.setAttribute('data-id', item.id);
		//removeProgressIndicator(row);
		//setRowContent(item, row);
	}, function(err){
		console.log(err);
		//stop showing loading message
		//stopLoadingMessage();
		//document.getElementById('errorDiv').innerHTML = err;
	});

}

function parseJson(str){
	return window.JSON ? JSON.parse(str) : eval('(' + str + ')');
}
function prettyJson(str){
	// If browser does not have JSON utilities, just print the raw string value.
	return window.JSON ? JSON.stringify(JSON.parse(str), null, '  ') : str;
}

<|endoftext|>


<commit_before>var lastMovieLoaded = 0;
var allMoviesLoaded = false;
var moviesLoading = false;
var movieRequest = null;
var movieLimit = 99999;
function loadMovies(options) {
    if ($('#movie-grid').attr('data-scroll-limit') !== 0) {
        movieLimit = parseInt($('#movie-grid').attr('data-scroll-limit'));
    }

    if (movieRequest != null) {
        movieRequest.abort();
        moviesLoading = false;
    }

    var sendData = {
        start: lastMovieLoaded,
        end: (lastMovieLoaded + movieLimit),
        search: '',
        sortorder: 'ascending',
        sortmethod: 'videotitle'
    };
    $.extend(sendData, options);

    if (allMoviesLoaded) return;
    if (moviesLoading) return;

    movieRequest = $.ajax({
        url: '/xbmc/GetMovies',
        type: 'get',
        data: sendData,
        beforeSend: function() {
            $('#movie-loader').show();
            moviesLoading = true;
        },
        dataType: 'json',
        success: function (data) {
            $('#movie-loader').hide();
            lastMovieLoaded += movieLimit;

            if (data == null) {;
                notify('Error','Cannot connect to XBMC','error');
                $('#xbmc-wake').show();
            }

            if (data.limits.end == data.limits.total) {
                allMoviesLoaded = true;
            }

            $.each(data.movies, function (i, movie) {
                var moviePicture = $('');
                moviePicture.css('height', '150px');
                moviePicture.css('width', '100px');
                moviePicture.attr('src', '/xbmc/GetThumb?thumb='+encodeURIComponent(movie.thumbnail)+'&w=100&h=150');

                var movieAnchor = $('');
                movieAnchor.addClass('thumbnail');
                movieAnchor.attr('href', 'javascript:void(0);');
                movieAnchor.css('height', '150px');
                movieAnchor.css('width', '100px');
                movieAnchor.append(moviePicture);

                movieAnchor.click(function () {
                    xbmcShowMovie(movie);
                });

                var movieItem = $('
').attr('title', movie.title);
                movieItem.attr('id', movie.title);
                movieItem.append(movieAnchor);
                movieItem.append($('
').addClass('movie-title').html(shortenText(movie.title, 12)));

                $('#movie-grid').append(movieItem);

                moviesLoading = false;
            });
        },
        error: function() {
            notify('Error','An error occurred','error');
        }
    });
}

function xbmcShowMovie(movie) {
    var modalMoviePicture = $('');
    modalMoviePicture.attr('src', '/xbmc/GetThumb?thumb='+encodeURIComponent(movie.thumbnail)+'&w=200&h=300');

    var modalMovieAnchor = $('
');
    modalMovieAnchor.addClass('thumbnail pull-left');
    modalMovieAnchor.css('margin-right', '20px')
    modalMovieAnchor.append(modalMoviePicture);

    var movieStudio = $('
').html(movie.studio);
    var movieGenre = $('
').html(movie.genre);
    var moviePlot = $('
').html(movie.plot);

    var movieRating = $('').raty({
        readOnly: true,
        score: (movie.rating / 2),
    });

    var modalMovieInfo = $('
');
    modalMovieInfo.append(modalMovieAnchor);
    modalMovieInfo.append(moviePlot);
    modalMovieInfo.append(movieGenre);
    modalMovieInfo.append(movieStudio);
    modalMovieInfo.append(movieRating);

    showModal(movie.title + ' (' + movie.year + ')',  modalMovieInfo, {
        'Play' : function () {
            playItem(movie.file);
            hideModal();
        }
    })
    $('.modal-fanart').css({
        'background' : '#ffffff url(/xbmc/GetThumb?thumb='+encodeURIComponent(movie.fanart)+'&w=675&h=400&o=10) top center no-repeat',
        'background-size' : '100%'
    });
}

var showSteps = 99999;
var lastShowLoaded = 0;
var allShowsLoaded = false;
var showsLoading = false;
var showRequest = null;
function loadXbmcShows(options) {
    if ($('#movie-grid').attr('data-scroll-limit') !== 0) {
        showSteps = parseInt($('#movie-grid').attr('data-scroll-limit'));
    }

    if (showRequest != null) {
        showRequest.abort();
        showsLoading = false;
    }

    var sendData = {
        start: lastShowLoaded,
        end: (lastShowLoaded + showSteps),
        search: ''
    };
    $.extend(sendData, options);

    if (allShowsLoaded) return;
    if (showsLoading) return;

    showRequest = $.ajax({
        url: '/xbmc/GetShows',
        type: 'get',
        dataType: 'json',
        data: sendData,
        beforeSend: function() {
            $('#show-loader').show();
            showsLoading = true;
        },
        success: function (data) {
            lastShowLoaded += showSteps;

            if (data == null) return;

            if (data.limits.end == data.limits.total) {
                allShowsLoaded = true;
            }

            if (data == null) return;
            $.each(data.tvshows, function (i, show) {
                var showPicture = $('');
                if ($('#show-grid').hasClass('banners')) {
                    showPicture.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(show.thumbnail) + '&h=80&w=500');
                    showPicture.css('height', '90px');
                    showPicture.css('width', '500px');
                } else {
                    showPicture.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(show.thumbnail) + '&h=150&w=100');
                    showPicture.css('height', '150px');
                    showPicture.css('width', '100px');
                }

                var showAnchor = $('');
                showAnchor.addClass('thumbnail');
                showAnchor.attr('href', 'javascript:void(0);');
                if ($('#show-grid').hasClass('banners')) {
                    showAnchor.css('height', '90px');
                    showAnchor.css('width', '500px');
                } else {
                    showAnchor.css('height', '150px');
                    showAnchor.css('width', '100px');
                }
                showAnchor.append(showPicture);
                showAnchor.click(function () {
                    loadXBMCShow(show);
                });

                var showItem = $('
');
                showItem.addClass('show-item');
                showItem.append(showAnchor);
                if ($('#show-grid').hasClass('banners')) {
                    showItem.append($('
').addClass('show-title').html(show.title));
                } else {
                    showItem.append($('
').addClass('show-title').html(shortenText(show.title, 12)));
                }

                $('#show-grid').append(showItem);
                $('#show-loader').hide();
                showsLoading = false;
            });
        }
    });
}

function xbmcShowEpisode(episode) {
    var modalshowPicture = $('');
    modalshowPicture.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(episode.thumbnail) + '&w=200&h=125');

    var modalshowAnchor = $('
');
    modalshowAnchor.addClass('thumbnail pull-left');
    modalshowAnchor.css('margin-right', '20px')
    modalshowAnchor.append(modalshowPicture);

    var modalPlot = $('
').html(episode.plot);

    var modalEpisodeInfo = $('
');
    modalEpisodeInfo.append(modalshowAnchor);
    modalEpisodeInfo.append(modalPlot);

    showModal(episode.label, modalEpisodeInfo, {
        'Play' : function () {
            playItem(episode.file);
            hideModal();
        }
    })

    $('.modal-fanart').css({
        'background' : '#ffffff url(/xbmc/GetThumb?thumb=' + encodeURIComponent(episode.fanart) + '&w=675&h=400&o=10) top center',
        'background-size' : '100%;'
    });
}

function loadXBMCShow(show) {
    $.ajax({
        url: '/xbmc/GetShow?tvshowid=' + show.tvshowid,
        type: 'get',
        dataType: 'json',
        beforeSend: function () {
            $('#show-seasons').empty();
            $('#show-grid').hide();
            $('#show-loader').show();
        },
        success: function (data) {
            $('#show-title').html(show.title)
            var accordion = $('
').addClass('accordion').attr('id', 'show-accordion');

            var seasonsArray = [];
            var seasonsCounter = 0;
            $.each(data.seasons, function(seasonNumber, episodes) {
                if (seasonsArray[seasonsCounter] == undefined) {
                    seasonsArray[seasonsCounter] = {};
                }
                seasonsArray[seasonsCounter].season = seasonNumber;
                seasonsArray[seasonsCounter].episodes = episodes;
                seasonsCounter ++;
            });

            $.each(seasonsArray, function(seasonCounter, season) {
                var episodesTable = $('
').addClass('accordion-inner');
                episodesTable.addClass('table');
                episodesTable.addClass('table-striped');

                var episodeArray = [];
                var episodesCounter = 0;
                $.each(season.episodes, function (episodeid, episode) {
                    episodeArray[episodesCounter] = episode;
                    episodesCounter++;
                });

                $.each(episodeArray, function (episodeCounter, episode) {
                    var row = $('
');

                    var episodeImage = $('');
                    episodeImage.css('height', '50px');
                    episodeImage.css('width', '100px');
                    episodeImage.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(episode.thumbnail) + '&w=100&h=50');

                    var episodeThumb = $('').addClass('thumbnail');
                    episodeThumb.append(episodeImage);
                    episodeThumb.css('height', '50px');
                    episodeThumb.css('width', '100px');

                    var playButton = $('');
                    playButton.addClass('btn');
                    playButton.addClass('pull-right');
                    playButton.html('Play');
                    playButton.click(function() {
                        playItem(episode.file);
                    });

                    row.append($('').html(episodeThumb).width(100));
                    row.append($('').html('
' + episode.label + '
' + '
' + episode.plot + '
'));
                    row.append($('').append(playButton));
                    episodesTable.append(row);
                });

                var accordionToggle = $('').addClass('accordion-toggle').html('
Season ' + season.season + '
');
                accordionToggle.attr('data-toggle', 'collapse');
                accordionToggle.attr('data-parent', 'show-accordion');
                accordionToggle.attr('href', '#season-' + season.season);
                var accordionHeading = $('
').addClass('accordion-heading').append(accordionToggle);
                accordionHeading.css('cursor', 'pointer');
                var accordionBody = $('
').addClass('accordion-body collapse').append(episodesTable);
                if (seasonCounter == 0) {
                    accordionBody.addClass('in');
                }
                accordionBody.attr('id', 'season-' + season.season)
                var accordionGroup = $('
').addClass('accordion-group').append(accordionHeading);
                accordionGroup.append(accordionBody);
                accordion.append(accordionGroup);
            });
            $('#show-loader').hide();
            $('#show-details').show();
            $('#show-seasons').append(accordion);
        }
    });
}

var nowPlayingThumb = '';
function loadNowPlaying() {
    $.ajax({
        url: '/xbmc/NowPlaying',
        type: 'get',
        dataType: 'json',
        complete: function() {
            setTimeout(function () {
                loadNowPlaying();
            }, 1000);
        },
        success: function(data) {
            if (data == null) {
                $('#nowplaying').hide();
                return;
            }

            if (!$('#nowplaying').is(':visible')) {
                $('#nowplaying').fadeIn();
            }

            if (nowPlayingThumb != data.itemInfo.item.thumbnail) {
                var thumbnail = $('');
                thumbnail.attr('alt', data.itemInfo.item.label);

                var thumbContainer = $('#nowplaying .thumbnail');

                if (data.itemInfo.item.type == 'episode') {
                    thumbnail.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(data.itemInfo.item.thumbnail) + '&w=150&h=75');
                    thumbnail.css('height', '75px');
                    thumbnail.css('width', '150px');
                    thumbContainer.css('height', '75px');
                    thumbContainer.css('width', '150px');
                }
                if (data.itemInfo.item.type == 'movie') {
                    thumbnail.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(data.itemInfo.item.thumbnail) + '&w=100&h=150');
                    thumbnail.css('height', '150px');
                    thumbnail.css('width', '100px');
                    thumbContainer.css('height', '150px');
                    thumbContainer.css('width', '100px');
                }

                thumbContainer.html(thumbnail);

                nowPlayingThumb = data.itemInfo.item.thumbnail;

                $('#nowplaying').css({
                    'background' : 'url(/xbmc/GetThumb?thumb=' + encodeURIComponent(data.itemInfo.item.fanart) + '&w=1150&h=640&o=10) top center',
                    'background-size' : '100%;',
                    'background-position' : '50% 20%',
                    'margin-bottom' : '10px'
                });
                $('#nowplaying-fanart').addClass('trans');
            }

            var playPauseButton = $('[data-player-control=PlayPause]');
            var playPauseIcon = playPauseButton.find('i');
            playPauseIcon.removeClass();
            if (data.playerInfo.speed == 1) {
                playPauseIcon.addClass('icon-pause');
            } else {
                playPauseIcon.addClass('icon-play');
            }

            var setMuteButton = $('[data-player-control=SetMute]')
            var setMuteIcon = setMuteButton.find('i');
            setMuteIcon.removeClass();
            if (data.app.muted) {
                setMuteIcon.addClass('icon-volume-up');
            } else {
                setMuteIcon.addClass('icon-volume-off');
            }

            var playingTime = pad(data.playerInfo.time.hours, 2) + ':' + pad(data.playerInfo.time.minutes, 2) + ':' + pad(data.playerInfo.time.seconds, 2);
            var totalTime = pad(data.playerInfo.totaltime.hours, 2) + ':' + pad(data.playerInfo.totaltime.minutes, 2) + ':' + pad(data.playerInfo.totaltime.seconds, 2);
            var itemTime = $('#player-item-time');
            itemTime.html(playingTime + ' / ' + totalTime);

            var itemTitel = $('#player-item-title')
            var playingTitle = '';
            if (data.itemInfo.item.type == 'episode') {
                playingTitle = data.itemInfo.item.showtitle + ' - ' + data.itemInfo.item.season + 'x' + data.itemInfo.item.episode + ' ' + data.itemInfo.item.label
            }
            if (data.itemInfo.item.type == 'movie') {
                playingTitle = data.itemInfo.item.label + ' (' + data.itemInfo.item.year + ')';
            }
            itemTitel.html(playingTitle);

            var progressBar = $('#player-progressbar').find('.bar');
            progressBar.css('width', data.playerInfo.percentage + '%');

            $('[data-player-control]').attr('disabled', false);
        }
    });
}

function playItem(item) {
    $.get('/xbmc/PlayItem?item='+item);
}

function xbmcControl(action) {
    $.get('/xbmc/ControlPlayer?action='+action, function(data){
    });
}
function enablePlayerControls() {
    $('[data-player-control]').click(function () {
        var action = $(this).attr('data-player-control');
        $(this).attr('disabled', true);
        xbmcControl(action);
    });
}

function sendNotification(string) {
    $.post('/xbmc/Notify',{'text': string}, function(data) {
        notify('XBMC', 'Notification sent successfully', 'info');
    });
}

function xbmcClean(lib) {
    $.get('/xbmc/Clean?lib='+lib, function(data) {
        notify('XBMC', 'Library clean sent successfully', 'info');
    });
}

function xbmcScan(lib) {
    $.get('/xbmc/Scan?lib='+lib, function(data) {
        notify('XBMC', 'Library update sent successfully', 'info');
    });
}
<commit_msg>Episode label already contains season and episode number<commit_after>var lastMovieLoaded = 0;
var allMoviesLoaded = false;
var moviesLoading = false;
var movieRequest = null;
var movieLimit = 99999;
function loadMovies(options) {
    if ($('#movie-grid').attr('data-scroll-limit') !== 0) {
        movieLimit = parseInt($('#movie-grid').attr('data-scroll-limit'));
    }

    if (movieRequest != null) {
        movieRequest.abort();
        moviesLoading = false;
    }

    var sendData = {
        start: lastMovieLoaded,
        end: (lastMovieLoaded + movieLimit),
        search: '',
        sortorder: 'ascending',
        sortmethod: 'videotitle'
    };
    $.extend(sendData, options);

    if (allMoviesLoaded) return;
    if (moviesLoading) return;

    movieRequest = $.ajax({
        url: '/xbmc/GetMovies',
        type: 'get',
        data: sendData,
        beforeSend: function() {
            $('#movie-loader').show();
            moviesLoading = true;
        },
        dataType: 'json',
        success: function (data) {
            $('#movie-loader').hide();
            lastMovieLoaded += movieLimit;

            if (data == null) {;
                notify('Error','Cannot connect to XBMC','error');
                $('#xbmc-wake').show();
            }

            if (data.limits.end == data.limits.total) {
                allMoviesLoaded = true;
            }

            $.each(data.movies, function (i, movie) {
                var moviePicture = $('');
                moviePicture.css('height', '150px');
                moviePicture.css('width', '100px');
                moviePicture.attr('src', '/xbmc/GetThumb?thumb='+encodeURIComponent(movie.thumbnail)+'&w=100&h=150');

                var movieAnchor = $('');
                movieAnchor.addClass('thumbnail');
                movieAnchor.attr('href', 'javascript:void(0);');
                movieAnchor.css('height', '150px');
                movieAnchor.css('width', '100px');
                movieAnchor.append(moviePicture);

                movieAnchor.click(function () {
                    xbmcShowMovie(movie);
                });

                var movieItem = $('
').attr('title', movie.title);
                movieItem.attr('id', movie.title);
                movieItem.append(movieAnchor);
                movieItem.append($('
').addClass('movie-title').html(shortenText(movie.title, 12)));

                $('#movie-grid').append(movieItem);

                moviesLoading = false;
            });
        },
        error: function() {
            notify('Error','An error occurred','error');
        }
    });
}

function xbmcShowMovie(movie) {
    var modalMoviePicture = $('');
    modalMoviePicture.attr('src', '/xbmc/GetThumb?thumb='+encodeURIComponent(movie.thumbnail)+'&w=200&h=300');

    var modalMovieAnchor = $('
');
    modalMovieAnchor.addClass('thumbnail pull-left');
    modalMovieAnchor.css('margin-right', '20px')
    modalMovieAnchor.append(modalMoviePicture);

    var movieStudio = $('
').html(movie.studio);
    var movieGenre = $('
').html(movie.genre);
    var moviePlot = $('
').html(movie.plot);

    var movieRating = $('').raty({
        readOnly: true,
        score: (movie.rating / 2),
    });

    var modalMovieInfo = $('
');
    modalMovieInfo.append(modalMovieAnchor);
    modalMovieInfo.append(moviePlot);
    modalMovieInfo.append(movieGenre);
    modalMovieInfo.append(movieStudio);
    modalMovieInfo.append(movieRating);

    showModal(movie.title + ' (' + movie.year + ')',  modalMovieInfo, {
        'Play' : function () {
            playItem(movie.file);
            hideModal();
        }
    })
    $('.modal-fanart').css({
        'background' : '#ffffff url(/xbmc/GetThumb?thumb='+encodeURIComponent(movie.fanart)+'&w=675&h=400&o=10) top center no-repeat',
        'background-size' : '100%'
    });
}

var showSteps = 99999;
var lastShowLoaded = 0;
var allShowsLoaded = false;
var showsLoading = false;
var showRequest = null;
function loadXbmcShows(options) {
    if ($('#movie-grid').attr('data-scroll-limit') !== 0) {
        showSteps = parseInt($('#movie-grid').attr('data-scroll-limit'));
    }

    if (showRequest != null) {
        showRequest.abort();
        showsLoading = false;
    }

    var sendData = {
        start: lastShowLoaded,
        end: (lastShowLoaded + showSteps),
        search: ''
    };
    $.extend(sendData, options);

    if (allShowsLoaded) return;
    if (showsLoading) return;

    showRequest = $.ajax({
        url: '/xbmc/GetShows',
        type: 'get',
        dataType: 'json',
        data: sendData,
        beforeSend: function() {
            $('#show-loader').show();
            showsLoading = true;
        },
        success: function (data) {
            lastShowLoaded += showSteps;

            if (data == null) return;

            if (data.limits.end == data.limits.total) {
                allShowsLoaded = true;
            }

            if (data == null) return;
            $.each(data.tvshows, function (i, show) {
                var showPicture = $('');
                if ($('#show-grid').hasClass('banners')) {
                    showPicture.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(show.thumbnail) + '&h=80&w=500');
                    showPicture.css('height', '90px');
                    showPicture.css('width', '500px');
                } else {
                    showPicture.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(show.thumbnail) + '&h=150&w=100');
                    showPicture.css('height', '150px');
                    showPicture.css('width', '100px');
                }

                var showAnchor = $('');
                showAnchor.addClass('thumbnail');
                showAnchor.attr('href', 'javascript:void(0);');
                if ($('#show-grid').hasClass('banners')) {
                    showAnchor.css('height', '90px');
                    showAnchor.css('width', '500px');
                } else {
                    showAnchor.css('height', '150px');
                    showAnchor.css('width', '100px');
                }
                showAnchor.append(showPicture);
                showAnchor.click(function () {
                    loadXBMCShow(show);
                });

                var showItem = $('
');
                showItem.addClass('show-item');
                showItem.append(showAnchor);
                if ($('#show-grid').hasClass('banners')) {
                    showItem.append($('
').addClass('show-title').html(show.title));
                } else {
                    showItem.append($('
').addClass('show-title').html(shortenText(show.title, 12)));
                }

                $('#show-grid').append(showItem);
                $('#show-loader').hide();
                showsLoading = false;
            });
        }
    });
}

function xbmcShowEpisode(episode) {
    var modalshowPicture = $('');
    modalshowPicture.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(episode.thumbnail) + '&w=200&h=125');

    var modalshowAnchor = $('
');
    modalshowAnchor.addClass('thumbnail pull-left');
    modalshowAnchor.css('margin-right', '20px')
    modalshowAnchor.append(modalshowPicture);

    var modalPlot = $('
').html(episode.plot);

    var modalEpisodeInfo = $('
');
    modalEpisodeInfo.append(modalshowAnchor);
    modalEpisodeInfo.append(modalPlot);

    showModal(episode.label, modalEpisodeInfo, {
        'Play' : function () {
            playItem(episode.file);
            hideModal();
        }
    })

    $('.modal-fanart').css({
        'background' : '#ffffff url(/xbmc/GetThumb?thumb=' + encodeURIComponent(episode.fanart) + '&w=675&h=400&o=10) top center',
        'background-size' : '100%;'
    });
}

function loadXBMCShow(show) {
    $.ajax({
        url: '/xbmc/GetShow?tvshowid=' + show.tvshowid,
        type: 'get',
        dataType: 'json',
        beforeSend: function () {
            $('#show-seasons').empty();
            $('#show-grid').hide();
            $('#show-loader').show();
        },
        success: function (data) {
            $('#show-title').html(show.title)
            var accordion = $('
').addClass('accordion').attr('id', 'show-accordion');

            var seasonsArray = [];
            var seasonsCounter = 0;
            $.each(data.seasons, function(seasonNumber, episodes) {
                if (seasonsArray[seasonsCounter] == undefined) {
                    seasonsArray[seasonsCounter] = {};
                }
                seasonsArray[seasonsCounter].season = seasonNumber;
                seasonsArray[seasonsCounter].episodes = episodes;
                seasonsCounter ++;
            });

            $.each(seasonsArray, function(seasonCounter, season) {
                var episodesTable = $('
').addClass('accordion-inner');
                episodesTable.addClass('table');
                episodesTable.addClass('table-striped');

                var episodeArray = [];
                var episodesCounter = 0;
                $.each(season.episodes, function (episodeid, episode) {
                    episodeArray[episodesCounter] = episode;
                    episodesCounter++;
                });

                $.each(episodeArray, function (episodeCounter, episode) {
                    var row = $('
');

                    var episodeImage = $('');
                    episodeImage.css('height', '50px');
                    episodeImage.css('width', '100px');
                    episodeImage.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(episode.thumbnail) + '&w=100&h=50');

                    var episodeThumb = $('').addClass('thumbnail');
                    episodeThumb.append(episodeImage);
                    episodeThumb.css('height', '50px');
                    episodeThumb.css('width', '100px');

                    var playButton = $('');
                    playButton.addClass('btn');
                    playButton.addClass('pull-right');
                    playButton.html('Play');
                    playButton.click(function() {
                        playItem(episode.file);
                    });

                    row.append($('').html(episodeThumb).width(100));
                    row.append($('').html('
' + episode.label + '
' + '
' + episode.plot + '
'));
                    row.append($('').append(playButton));
                    episodesTable.append(row);
                });

                var accordionToggle = $('').addClass('accordion-toggle').html('
Season ' + season.season + '
');
                accordionToggle.attr('data-toggle', 'collapse');
                accordionToggle.attr('data-parent', 'show-accordion');
                accordionToggle.attr('href', '#season-' + season.season);
                var accordionHeading = $('
').addClass('accordion-heading').append(accordionToggle);
                accordionHeading.css('cursor', 'pointer');
                var accordionBody = $('
').addClass('accordion-body collapse').append(episodesTable);
                if (seasonCounter == 0) {
                    accordionBody.addClass('in');
                }
                accordionBody.attr('id', 'season-' + season.season)
                var accordionGroup = $('
').addClass('accordion-group').append(accordionHeading);
                accordionGroup.append(accordionBody);
                accordion.append(accordionGroup);
            });
            $('#show-loader').hide();
            $('#show-details').show();
            $('#show-seasons').append(accordion);
        }
    });
}

var nowPlayingThumb = '';
function loadNowPlaying() {
    $.ajax({
        url: '/xbmc/NowPlaying',
        type: 'get',
        dataType: 'json',
        complete: function() {
            setTimeout(function () {
                loadNowPlaying();
            }, 1000);
        },
        success: function(data) {
            if (data == null) {
                $('#nowplaying').hide();
                return;
            }

            if (!$('#nowplaying').is(':visible')) {
                $('#nowplaying').fadeIn();
            }

            if (nowPlayingThumb != data.itemInfo.item.thumbnail) {
                var thumbnail = $('');
                thumbnail.attr('alt', data.itemInfo.item.label);

                var thumbContainer = $('#nowplaying .thumbnail');

                if (data.itemInfo.item.type == 'episode') {
                    thumbnail.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(data.itemInfo.item.thumbnail) + '&w=150&h=75');
                    thumbnail.css('height', '75px');
                    thumbnail.css('width', '150px');
                    thumbContainer.css('height', '75px');
                    thumbContainer.css('width', '150px');
                }
                if (data.itemInfo.item.type == 'movie') {
                    thumbnail.attr('src', '/xbmc/GetThumb?thumb=' + encodeURIComponent(data.itemInfo.item.thumbnail) + '&w=100&h=150');
                    thumbnail.css('height', '150px');
                    thumbnail.css('width', '100px');
                    thumbContainer.css('height', '150px');
                    thumbContainer.css('width', '100px');
                }

                thumbContainer.html(thumbnail);

                nowPlayingThumb = data.itemInfo.item.thumbnail;

                $('#nowplaying').css({
                    'background' : 'url(/xbmc/GetThumb?thumb=' + encodeURIComponent(data.itemInfo.item.fanart) + '&w=1150&h=640&o=10) top center',
                    'background-size' : '100%;',
                    'background-position' : '50% 20%',
                    'margin-bottom' : '10px'
                });
                $('#nowplaying-fanart').addClass('trans');
            }

            var playPauseButton = $('[data-player-control=PlayPause]');
            var playPauseIcon = playPauseButton.find('i');
            playPauseIcon.removeClass();
            if (data.playerInfo.speed == 1) {
                playPauseIcon.addClass('icon-pause');
            } else {
                playPauseIcon.addClass('icon-play');
            }

            var setMuteButton = $('[data-player-control=SetMute]')
            var setMuteIcon = setMuteButton.find('i');
            setMuteIcon.removeClass();
            if (data.app.muted) {
                setMuteIcon.addClass('icon-volume-up');
            } else {
                setMuteIcon.addClass('icon-volume-off');
            }

            var playingTime = pad(data.playerInfo.time.hours, 2) + ':' + pad(data.playerInfo.time.minutes, 2) + ':' + pad(data.playerInfo.time.seconds, 2);
            var totalTime = pad(data.playerInfo.totaltime.hours, 2) + ':' + pad(data.playerInfo.totaltime.minutes, 2) + ':' + pad(data.playerInfo.totaltime.seconds, 2);
            var itemTime = $('#player-item-time');
            itemTime.html(playingTime + ' / ' + totalTime);

            var itemTitel = $('#player-item-title')
            var playingTitle = '';
            if (data.itemInfo.item.type == 'episode') {
                playingTitle = data.itemInfo.item.showtitle + ' ' + data.itemInfo.item.label
            }
            if (data.itemInfo.item.type == 'movie') {
                playingTitle = data.itemInfo.item.label + ' (' + data.itemInfo.item.year + ')';
            }
            itemTitel.html(playingTitle);

            var progressBar = $('#player-progressbar').find('.bar');
            progressBar.css('width', data.playerInfo.percentage + '%');

            $('[data-player-control]').attr('disabled', false);
        }
    });
}

function playItem(item) {
    $.get('/xbmc/PlayItem?item='+item);
}

function xbmcControl(action) {
    $.get('/xbmc/ControlPlayer?action='+action, function(data){
    });
}
function enablePlayerControls() {
    $('[data-player-control]').click(function () {
        var action = $(this).attr('data-player-control');
        $(this).attr('disabled', true);
        xbmcControl(action);
    });
}

function sendNotification(string) {
    $.post('/xbmc/Notify',{'text': string}, function(data) {
        notify('XBMC', 'Notification sent successfully', 'info');
    });
}

function xbmcClean(lib) {
    $.get('/xbmc/Clean?lib='+lib, function(data) {
        notify('XBMC', 'Library clean sent successfully', 'info');
    });
}

function xbmcScan(lib) {
    $.get('/xbmc/Scan?lib='+lib, function(data) {
        notify('XBMC', 'Library update sent successfully', 'info');
    });
}
<|endoftext|>