<commit_before>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {

        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.show();
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {

            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            this.setSearchInactive();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        }).on('click', e => {
            e.preventDefault();
            this.setSearchInactive();
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.clearInput();
                this.$inputField.blur();
                this.setSearchInactive();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.clearInput();
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;

        this.$searchResults.html('');

        results.map((result) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url });
            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<commit_msg>lol idk what i'm doing<commit_after>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        // eslint-disable-next-line no-console
        console.log('CTOR SRFSEARCH', element, options);

        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        // eslint-disable-next-line no-console
        console.log('opening inputfield', this.$inputField);
        this.$inputField.show();
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {

            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            this.setSearchInactive();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        }).on('click', e => {
            e.preventDefault();
            this.setSearchInactive();
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.clearInput();
                this.$inputField.blur();
                this.setSearchInactive();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.clearInput();
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;

        this.$searchResults.html('');

        results.map((result) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url });
            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<|endoftext|>


<commit_before>/*
* Youtube Embed Plugin
*
* @author Jonnas Fonini <contato@fonini.net>
* @version 0.1
* https://github.com/fonini/ckeditor-youtube-plugin
*/

'use strict';

( function() {

	CKEDITOR.plugins.add( 'youtube',
	{
		init: function( editor )
		{
			// Backport language
			editor.lang.youtube = {
				button : gettext('Embed YouTube Video'),
				title : gettext('Embed Youtube Video'),
				txtEmbed : gettext('Paste Embed Code Here'),
				txtUrl : gettext('Paste Youtube Video URL'),
				txtWidth : gettext('Width'),
				txtHeight : gettext('Height'),
				noCode : gettext('You must input an embed code or URL'),
				invalidEmbed : gettext('The embed code you\'ve entered doesn\'t appear to be valid'),
				invalidUrl : gettext('The URL you\'ve entered doesn\'t appear to be valid'),
				or : gettext('or')
			};

			editor.addCommand( 'youtube', new CKEDITOR.dialogCommand( 'youtube' ) );

			editor.ui.addButton( 'youtube',
			{
				label: editor.lang.youtube.button,
				command: 'youtube',
				icon: this.path + 'images/icon.png',
				toolbar: 'insert'
			});

			CKEDITOR.dialog.add( 'youtube', function ( instance )
			{
				return {
					title : editor.lang.youtube.title,
					minWidth : 550,
					minHeight : 200,
					contents :
						[{
							id : 'youtubePlugin',
							expand : true,
							elements :
								[{
									id : 'txtEmbed',
									type : 'textarea',
									label : editor.lang.youtube.txtEmbed,
									autofocus : 'autofocus',
									onChange : function ( api ){
										if ( this.getValue().length > 0 ){
											this.getDialog().getContentElement( 'youtubePlugin', 'txtUrl' ).disable();
										}
										else{
											this.getDialog().getContentElement( 'youtubePlugin', 'txtUrl' ).enable();
										}
									},
									validate : function() {
										if ( this.isEnabled() ){
											if ( !this.getValue() )
											{
												alert( editor.lang.youtube.noCode );
												return false;
											}
											else if (!(/https?\:\/\//.test(this.getValue()))) {
												alert( editor.lang.youtube.invalidEmbed );
												return false;
											}
										}
									}
								},
								{
									type : 'html',
									html : editor.lang.youtube.or + '
'
								},
								{
									type : 'hbox',
									widths : [ '70%', '15%', '15%' ],
									children :
									[
										{
											id : 'txtUrl',
											type : 'text',
											label : editor.lang.youtube.txtUrl,
											onChange : function ( api ){
												if ( this.getValue().length > 0 ){
													this.getDialog().getContentElement( 'youtubePlugin', 'txtEmbed' ).disable();
												}
												else {
													this.getDialog().getContentElement( 'youtubePlugin', 'txtEmbed' ).enable();
												}
											},
											validate : function() {
												if ( this.isEnabled() ){
													if ( !this.getValue() )
													{
														alert( editor.lang.youtube.noCode );
														return false;
													}
													else if (!(/https?\:\/\//.test(this.getValue()))) {
														alert( editor.lang.youtube.invalidUrl );
														return false;
													}
												}
											}
										},
										{
											type : 'text',
											id : 'txtWidth',
											width : '60px',
											label : editor.lang.youtube.txtWidth,
											default : '640'
										},
										{
											type : 'text',
											id : 'txtHeight',
											width : '60px',
											label : editor.lang.youtube.txtHeight,
											default : '360'
										}
									]
								}
							]
						}
					],
					onOk: function() {
						var content = '';

						if ( this.getContentElement( 'youtubePlugin', 'txtEmbed' ).isEnabled() )
						{
							content = this.getValueOf( 'youtubePlugin', 'txtEmbed' ).replace('http:', 'https:');
						}
						else {
							var url = this.getValueOf( 'youtubePlugin', 'txtUrl' );
							var width = this.getValueOf( 'youtubePlugin', 'txtWidth' );
							var height = this.getValueOf( 'youtubePlugin', 'txtHeight' );
							var urlSplit = url.split('?');
							var video = urlSplit.length && urlSplit[1] ? jQuery.parseQuerystring(urlSplit[1]).v : '';
							if(video) {
								url = 'https://www.youtube.com/embed/' + video + '/?feature=player_detailpage';
								content = '';
 							}
						}

						var instance = this.getParentEditor();
						instance.insertHtml( content );

						this.getContentElement( 'youtubePlugin', 'txtEmbed' ).enable();
						this.getContentElement( 'youtubePlugin', 'txtUrl' ).enable();
					}
				};
			});
		}
	});
})();
<commit_msg>bug 975868 - Old browsers will fail on default used as a property name.<commit_after>/*
* Youtube Embed Plugin
*
* @author Jonnas Fonini <contato@fonini.net>
* @version 0.1
* https://github.com/fonini/ckeditor-youtube-plugin
*/

'use strict';

( function() {

	CKEDITOR.plugins.add( 'youtube',
	{
		init: function( editor )
		{
			// Backport language
			editor.lang.youtube = {
				button : gettext('Embed YouTube Video'),
				title : gettext('Embed Youtube Video'),
				txtEmbed : gettext('Paste Embed Code Here'),
				txtUrl : gettext('Paste Youtube Video URL'),
				txtWidth : gettext('Width'),
				txtHeight : gettext('Height'),
				noCode : gettext('You must input an embed code or URL'),
				invalidEmbed : gettext('The embed code you\'ve entered doesn\'t appear to be valid'),
				invalidUrl : gettext('The URL you\'ve entered doesn\'t appear to be valid'),
				or : gettext('or')
			};

			editor.addCommand( 'youtube', new CKEDITOR.dialogCommand( 'youtube' ) );

			editor.ui.addButton( 'youtube',
			{
				label: editor.lang.youtube.button,
				command: 'youtube',
				icon: this.path + 'images/icon.png',
				toolbar: 'insert'
			});

			CKEDITOR.dialog.add( 'youtube', function ( instance )
			{
				return {
					title : editor.lang.youtube.title,
					minWidth : 550,
					minHeight : 200,
					contents :
						[{
							id : 'youtubePlugin',
							expand : true,
							elements :
								[{
									id : 'txtEmbed',
									type : 'textarea',
									label : editor.lang.youtube.txtEmbed,
									autofocus : 'autofocus',
									onChange : function ( api ){
										if ( this.getValue().length > 0 ){
											this.getDialog().getContentElement( 'youtubePlugin', 'txtUrl' ).disable();
										}
										else{
											this.getDialog().getContentElement( 'youtubePlugin', 'txtUrl' ).enable();
										}
									},
									validate : function() {
										if ( this.isEnabled() ){
											if ( !this.getValue() )
											{
												alert( editor.lang.youtube.noCode );
												return false;
											}
											else if (!(/https?\:\/\//.test(this.getValue()))) {
												alert( editor.lang.youtube.invalidEmbed );
												return false;
											}
										}
									}
								},
								{
									type : 'html',
									html : editor.lang.youtube.or + '
'
								},
								{
									type : 'hbox',
									widths : [ '70%', '15%', '15%' ],
									children :
									[
										{
											id : 'txtUrl',
											type : 'text',
											label : editor.lang.youtube.txtUrl,
											onChange : function ( api ){
												if ( this.getValue().length > 0 ){
													this.getDialog().getContentElement( 'youtubePlugin', 'txtEmbed' ).disable();
												}
												else {
													this.getDialog().getContentElement( 'youtubePlugin', 'txtEmbed' ).enable();
												}
											},
											validate : function() {
												if ( this.isEnabled() ){
													if ( !this.getValue() )
													{
														alert( editor.lang.youtube.noCode );
														return false;
													}
													else if (!(/https?\:\/\//.test(this.getValue()))) {
														alert( editor.lang.youtube.invalidUrl );
														return false;
													}
												}
											}
										},
										{
											type : 'text',
											id : 'txtWidth',
											width : '60px',
											label : editor.lang.youtube.txtWidth,
											'default' : '640'
										},
										{
											type : 'text',
											id : 'txtHeight',
											width : '60px',
											label : editor.lang.youtube.txtHeight,
											'default' : '360'
										}
									]
								}
							]
						}
					],
					onOk: function() {
						var content = '';

						if ( this.getContentElement( 'youtubePlugin', 'txtEmbed' ).isEnabled() )
						{
							content = this.getValueOf( 'youtubePlugin', 'txtEmbed' ).replace('http:', 'https:');
						}
						else {
							var url = this.getValueOf( 'youtubePlugin', 'txtUrl' );
							var width = this.getValueOf( 'youtubePlugin', 'txtWidth' );
							var height = this.getValueOf( 'youtubePlugin', 'txtHeight' );
							var urlSplit = url.split('?');
							var video = urlSplit.length && urlSplit[1] ? jQuery.parseQuerystring(urlSplit[1]).v : '';
							if(video) {
								url = 'https://www.youtube.com/embed/' + video + '/?feature=player_detailpage';
								content = '';
 							}
						}

						var instance = this.getParentEditor();
						instance.insertHtml( content );

						this.getContentElement( 'youtubePlugin', 'txtEmbed' ).enable();
						this.getContentElement( 'youtubePlugin', 'txtUrl' ).enable();
					}
				};
			});
		}
	});
})();
<|endoftext|>


<commit_before>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';
import {KEYCODES} from '../utils/fef-keycodes';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;

const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'mousedown.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {
            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            e.preventDefault();
            this.setSearchInactive();
            this.clearInput();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.$inputField.blur();
                this.setSearchInactive();
                this.clearInput();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                this.clearInput();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                this.clearInputIfAllowed();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();

        /**
         *  setTimeout fixes a different behavior in ie 11, which causes not being able to leave the input field.
         */
        setTimeout(() => {
            this.$searchResults.hide().html('').removeClass ('search__results--showed-results');
        }, 0);

    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    clearInputIfAllowed() {
        // clear input if data-keep-keyword-on-blur was not set
        if (this.$inputField.data('keep-keyword-on-blur') === undefined) {
            this.clearInput();
        }
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            const matchIndexName = item.name.toString().toLowerCase().indexOf(query);
            const matchIndexKey  = item.keymatches.toString().toLowerCase().indexOf(query);
            let matchIndex = Math.max(matchIndexName, matchIndexKey);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;
        const resultLength = results.length;

        this.$searchResults.html('');

        results.map((result, index) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url, 'data-referrer-track' : 'srg_override_mod1=search_suggestions&srg_override_mod2=search_result&srg_override_mod3=' + query + '&srg_override_mod4=' + resultLength + '-' + (index + 1)});

            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<commit_msg>set search inactive via event<commit_after>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';
import {KEYCODES} from '../utils/fef-keycodes';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;

const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'mousedown.search-destroyer';
const SET_SEARCH_INACTIVE_EVENT = 'srf:search:deactivate';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {
            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            e.preventDefault();
            this.setSearchInactive();
            this.clearInput();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });

        this.$element.on(SET_SEARCH_INACTIVE_EVENT, () => {
            this.setSearchInactive();
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.$inputField.blur();
                this.setSearchInactive();
                this.clearInput();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                this.clearInput();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                this.clearInputIfAllowed();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();

        /**
         *  setTimeout fixes a different behavior in ie 11, which causes not being able to leave the input field.
         */
        setTimeout(() => {
            this.$searchResults.hide().html('').removeClass ('search__results--showed-results');
        }, 0);

    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    clearInputIfAllowed() {
        // clear input if data-keep-keyword-on-blur was not set
        if (this.$inputField.data('keep-keyword-on-blur') === undefined) {
            this.clearInput();
        }
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            const matchIndexName = item.name.toString().toLowerCase().indexOf(query);
            const matchIndexKey  = item.keymatches.toString().toLowerCase().indexOf(query);
            let matchIndex = Math.max(matchIndexName, matchIndexKey);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;
        const resultLength = results.length;

        this.$searchResults.html('');

        results.map((result, index) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url, 'data-referrer-track' : 'srg_override_mod1=search_suggestions&srg_override_mod2=search_result&srg_override_mod3=' + query + '&srg_override_mod4=' + resultLength + '-' + (index + 1)});

            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<|endoftext|>


<commit_before>// IDLE HANDLING /////////////////////////////////////////////////////

window.idleTime = 0; // in seconds
window.isHidden = false;

var IDLE_POLL_TIME = 10;

var idlePoll = function() {
  window.idleTime += IDLE_POLL_TIME;
}

setInterval(idlePoll, IDLE_POLL_TIME*1000);

var idleReset = function () {
  // update immediately when the user comes back
  if(isIdle()) {
    window.idleTime = 0;
    $.each(window._onResumeFunctions, function(ind, f) {
      f();
    });
  }
  window.idleTime = 0;
};

// only reset idle on mouse move where the coordinates are actually different.
// some browsers send the event when not moving!
var _lastMouseX=-1, _lastMouseY=-1;
var idleMouseMove = function(e) {
  var dX = _lastMouseX-e.clientX;
  var dY = _lastMouseY-e.clientY;
  var deltaSquared = dX*dX + dY*dY;
  // only treat movements over 3 pixels as enough to reset us
  if (deltaSquared > 3*3) {
    _lastMouseX = e.clientX;
    _lastMouseY = e.clientY;
    idleReset();
  }
}

$('body').keypress(idleReset);
$('body').mousemove(idleMouseMove);

window.isIdle = function() {
  if (window.idleTime < window.REFRESH) {
    // if idle for less than the refresh time ignore 'hidden' state - likely initial page load in the background
    return false;
  }

  var hidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden);
  if (hidden) {
    // window hidden - force an idle state even if below the idle time limit
    return true;
  }

  // otherwise use the idle time limit
  return window.idleTime >= MAX_IDLE_TIME;
}

window._onResumeFunctions = [];

// add your function here if you want to be notified when the user
// resumes from being idle
window.addResumeFunction = function(f) {
  window._onResumeFunctions.push(f);
}
<commit_msg>update idle handling - prevents the status getting stuck showing 'idle' after hiding+showing the window<commit_after>// IDLE HANDLING /////////////////////////////////////////////////////

window.idleTime = 0; // in seconds
window._idleTimeLimit = MAX_IDLE_TIME;

var IDLE_POLL_TIME = 10;

var idlePoll = function() {
  window.idleTime += IDLE_POLL_TIME;

  var hidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden || false);
  if (hidden) {
    // window hidden - use the refresh time as the idle limit, rather than the max time
    window._idleTimeLimit = window.REFRESH;
  }
}

setInterval(idlePoll, IDLE_POLL_TIME*1000);

var idleReset = function () {
  // update immediately when the user comes back
  if(isIdle()) {
    window.idleTime = 0;
    $.each(window._onResumeFunctions, function(ind, f) {
      f();
    });
  }
  window.idleTime = 0;
  window._idleTimeLimit = MAX_IDLE_TIME;
};

// only reset idle on mouse move where the coordinates are actually different.
// some browsers send the event when not moving!
var _lastMouseX=-1, _lastMouseY=-1;
var idleMouseMove = function(e) {
  var dX = _lastMouseX-e.clientX;
  var dY = _lastMouseY-e.clientY;
  var deltaSquared = dX*dX + dY*dY;
  // only treat movements over 3 pixels as enough to reset us
  if (deltaSquared > 3*3) {
    _lastMouseX = e.clientX;
    _lastMouseY = e.clientY;
    idleReset();
  }
}

$('body').keypress(idleReset);
$('body').mousemove(idleMouseMove);

window.isIdle = function() {
  return window.idleTime >= window._idleTimeLimit;
}

window._onResumeFunctions = [];

// add your function here if you want to be notified when the user
// resumes from being idle
window.addResumeFunction = function(f) {
  window._onResumeFunctions.push(f);
}
<|endoftext|>


<commit_before>import axios from 'axios'

export function updateScore(amount){
  return {
    type:'UPDATE_SCORE',
    amount
  }
}

function setTotalScore(score){
  return {
    type:'SET_TOTAL_SCORE',
    score
  }
}

 //score actions
function requestInitialData(bool){
  return {
    type: 'GET_INITIAL_DATA',
    loading:bool
  }
}

function storeInitialData(user, score) {
  return {
    type: 'GET_INITIAL_DATA_SUCCESS',
    user,
    score
  }
}

export function fetchData(data){
  return (dispatch, getState) => {
    dispatch(requestInitialData(true))
    return axios.all([
        axios.get('/api/profile'),
        axios.get('/api/score/')
      ])
      .then(axios.spread(function (profile, score) {
        dispatch(storeInitialData(profile.data.results, score.data.results))

        // set total score
        let total = [];
        for (var i = 0; i < score.data.results.length; i++) {
          total.push(score.data.results[i].score);
        }
        let totalScore = total.reduce((a, b) => a + b, 0);
        dispatch(setTotalScore(totalScore))
      }))
  }
}<commit_msg>add require for polyfill to action creators<commit_after>require('es6-promise').polyfill();
import axios from 'axios'

export function updateScore(amount){
  return {
    type:'UPDATE_SCORE',
    amount
  }
}

function setTotalScore(score){
  return {
    type:'SET_TOTAL_SCORE',
    score
  }
}

 //score actions
function requestInitialData(bool){
  return {
    type: 'GET_INITIAL_DATA',
    loading:bool
  }
}

function storeInitialData(user, score) {
  return {
    type: 'GET_INITIAL_DATA_SUCCESS',
    user,
    score
  }
}

export function fetchData(data){
  return (dispatch, getState) => {
    dispatch(requestInitialData(true))
    return axios.all([
        axios.get('/api/profile'),
        axios.get('/api/score/')
      ])
      .then(axios.spread(function (profile, score) {
        dispatch(storeInitialData(profile.data.results, score.data.results))

        // set total score
        let total = [];
        for (var i = 0; i < score.data.results.length; i++) {
          total.push(score.data.results[i].score);
        }
        let totalScore = total.reduce((a, b) => a + b, 0);
        dispatch(setTotalScore(totalScore))
      }))
  }
}<|endoftext|>


<commit_before>window.AudioContext = window.AudioContext || window.webkitAudioContext;

var DB_THRESH = -20.0;
var FREQ_MIN = 60;
var FREQ_MAX = 800;
var CALIBRATE_TIME = 300;

var audioContext = null;
var analyzerNode = null;
var buf = new Float32Array(1024);
var pitch = null;

var musicBuffer = null;
var musicSourceNode = null;
var isPlayingMusic = false;

var buttonElem = document.querySelector('.playMusicButton').childNodes[0];
var rawFreqElem = document.querySelector('.rawFreq').childNodes[0];
var frequencyElem = document.querySelector('.frequency').childNodes[0];
var countdownElem = document.querySelector('.countdown').childNodes[0];
var calibrateElem = document.querySelector('.calibrate').childNodes[0];
var noteElem = document.querySelector('.note').childNodes[0];

var calibrateFreqs = [];
var calibrate = false;
var calibrateCountdown = CALIBRATE_TIME;
var calibratedFreq = null;

var refNote = 50;

var freqs = [];
for (var i = 0; i < 20; ++i) {
  freqs.push(0);
}

window.addEventListener('load', function() {
  audioContext = new AudioContext();
  pitch = new PitchAnalyzer(44100);
  loadMusic('./music/find_your_soul.mp3');
});

function toggleCalibrate() {
  getUserMedia({
    "audio": {
      "mandatory": {
        "googEchoCancellation": "false",
        "googAutoGainControl": "false",
        "googNoiseSuppression": "false",
        "googHighpassFilter": "false"
      },
      "optional": []
    },
  }, gotCalibrateStream);
}

function gotCalibrateStream(stream) {
  var mediaStreamSource = audioContext.createMediaStreamSource(stream);
  analyzerNode = audioContext.createAnalyser();
  mediaStreamSource.connect(analyzerNode);
  calibratePitch();
}

function calibratePitch() {
  calibrateCountdown--;
  countdownElem.textContent = calibrateCountdown;
  if (calibrateCountdown < 0) {
    calibrateCountdown = CALIBRATE_TIME;
    calibratedFreq = extractDominantFrequency(calibrateFreqs);
    countdownElem.textContent = CALIBRATE_TIME;
    calibrateElem.textContent = Math.round(calibratedFreq);
    return;
  }
  analyzerNode.getFloatTimeDomainData(buf);
  pitch.input(buf);
  pitch.process();
  var tone = pitch.findTone();

  if (tone && tone.db > DB_THRESH &&
      tone.freq > FREQ_MIN &&
      tone.freq < FREQ_MAX) {
    rawFreqElem.textContent = Math.round(tone.freq);
//    frequencyElem.textContent = Math.round(tone.freq);
    calibrateFreqs.push(tone.freq);
  } else {
    rawFreqElem.textContent = 0;
//    frequencyElem.textContent = 0;
  }

  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = window.webkitRequestAnimationFrame;
  }
  window.requestAnimationFrame(calibratePitch);
}

function toggleLiveInput() {
  getUserMedia({
    "audio": {
      "mandatory": {
        "googEchoCancellation": "false",
        "googAutoGainControl": "false",
        "googNoiseSuppression": "false",
        "googHighpassFilter": "false"
      },
      "optional": []
    },
  }, gotStream);
}

function getUserMedia(dictionary, callback) {
  try {
    navigator.getUserMedia = navigator.getUserMedia ||
                             navigator.webkitGetUserMedia ||
                             navigator.mozGetUserMedia;
    navigator.getUserMedia(dictionary, callback, error);
  } catch(e) {
    alert('getUserMedia threw exception :' + e);
  }
}

function gotStream(stream) {
  var mediaStreamSource = audioContext.createMediaStreamSource(stream);
  analyzerNode = audioContext.createAnalyser();
  mediaStreamSource.connect(analyzerNode);
  getPitch();
}

function getPitch() {
  analyzerNode.getFloatTimeDomainData(buf);
  pitch.input(buf);
  pitch.process();
  var tone = pitch.findTone();

  var freq = 0;
  if (tone &&
      tone.db > DB_THRESH &&
      tone.freq > FREQ_MIN &&
      tone.freq < FREQ_MAX) {
    freq = Math.round(tone.freq);
  }
  rawFreqElem.textContent = freq;
  freqs.push(freq);
  freqs.shift();
  var fundFreq = extractFundamentalFrequency(freqs);
  frequencyElem.textContent = Math.round(fundFreq);
  noteElem.textContent = getNoteFromFreq(refNote, calibratedFreq, fundFreq);

  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = window.webkitRequestAnimationFrame;
  }
  window.requestAnimationFrame(getPitch);
}

function error() {
  alert('Stream generation failed.');
}

function loadMusic(url) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';
  request.onload = function() {
    audioContext.decodeAudioData(request.response, function(buffer) {
      musicBuffer = buffer;
    });
  }
  request.send();
}

function togglePlayMusic() {
  if (isPlayingMusic) {
    musicSourceNode.stop(0);
    buttonElem.textContent = "Play";
  } else {
    musicSourceNode = audioContext.createBufferSource();
    musicSourceNode.buffer = musicBuffer;
    musicSourceNode.connect(audioContext.destination);
    musicSourceNode.start(0);
    buttonElem.textContent = "Stop";
  }
  isPlayingMusic = !isPlayingMusic;
}

<commit_msg>loop music :)<commit_after>window.AudioContext = window.AudioContext || window.webkitAudioContext;

var DB_THRESH = -20.0;
var FREQ_MIN = 60;
var FREQ_MAX = 800;
var CALIBRATE_TIME = 300;

var audioContext = null;
var analyzerNode = null;
var buf = new Float32Array(1024);
var pitch = null;

var musicBuffer = null;
var musicSourceNode = null;
var isPlayingMusic = false;

var buttonElem = document.querySelector('.playMusicButton').childNodes[0];
var rawFreqElem = document.querySelector('.rawFreq').childNodes[0];
var frequencyElem = document.querySelector('.frequency').childNodes[0];
var countdownElem = document.querySelector('.countdown').childNodes[0];
var calibrateElem = document.querySelector('.calibrate').childNodes[0];
var noteElem = document.querySelector('.note').childNodes[0];

var calibrateFreqs = [];
var calibrate = false;
var calibrateCountdown = CALIBRATE_TIME;
var calibratedFreq = null;

var refNote = 50;

var freqs = [];
for (var i = 0; i < 20; ++i) {
  freqs.push(0);
}

window.addEventListener('load', function() {
  audioContext = new AudioContext();
  pitch = new PitchAnalyzer(44100);
  loadMusic('./music/find_your_soul.mp3');
});

function toggleCalibrate() {
  getUserMedia({
    "audio": {
      "mandatory": {
        "googEchoCancellation": "false",
        "googAutoGainControl": "false",
        "googNoiseSuppression": "false",
        "googHighpassFilter": "false"
      },
      "optional": []
    },
  }, gotCalibrateStream);
}

function gotCalibrateStream(stream) {
  var mediaStreamSource = audioContext.createMediaStreamSource(stream);
  analyzerNode = audioContext.createAnalyser();
  mediaStreamSource.connect(analyzerNode);
  calibratePitch();
}

function calibratePitch() {
  calibrateCountdown--;
  countdownElem.textContent = calibrateCountdown;
  if (calibrateCountdown < 0) {
    calibrateCountdown = CALIBRATE_TIME;
    calibratedFreq = extractDominantFrequency(calibrateFreqs);
    countdownElem.textContent = CALIBRATE_TIME;
    calibrateElem.textContent = Math.round(calibratedFreq);
    return;
  }
  analyzerNode.getFloatTimeDomainData(buf);
  pitch.input(buf);
  pitch.process();
  var tone = pitch.findTone();

  if (tone && tone.db > DB_THRESH &&
      tone.freq > FREQ_MIN &&
      tone.freq < FREQ_MAX) {
    rawFreqElem.textContent = Math.round(tone.freq);
//    frequencyElem.textContent = Math.round(tone.freq);
    calibrateFreqs.push(tone.freq);
  } else {
    rawFreqElem.textContent = 0;
//    frequencyElem.textContent = 0;
  }

  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = window.webkitRequestAnimationFrame;
  }
  window.requestAnimationFrame(calibratePitch);
}

function toggleLiveInput() {
  getUserMedia({
    "audio": {
      "mandatory": {
        "googEchoCancellation": "false",
        "googAutoGainControl": "false",
        "googNoiseSuppression": "false",
        "googHighpassFilter": "false"
      },
      "optional": []
    },
  }, gotStream);
}

function getUserMedia(dictionary, callback) {
  try {
    navigator.getUserMedia = navigator.getUserMedia ||
                             navigator.webkitGetUserMedia ||
                             navigator.mozGetUserMedia;
    navigator.getUserMedia(dictionary, callback, error);
  } catch(e) {
    alert('getUserMedia threw exception :' + e);
  }
}

function gotStream(stream) {
  var mediaStreamSource = audioContext.createMediaStreamSource(stream);
  analyzerNode = audioContext.createAnalyser();
  mediaStreamSource.connect(analyzerNode);
  getPitch();
}

function getPitch() {
  analyzerNode.getFloatTimeDomainData(buf);
  pitch.input(buf);
  pitch.process();
  var tone = pitch.findTone();

  var freq = 0;
  if (tone &&
      tone.db > DB_THRESH &&
      tone.freq > FREQ_MIN &&
      tone.freq < FREQ_MAX) {
    freq = Math.round(tone.freq);
  }
  rawFreqElem.textContent = freq;
  freqs.push(freq);
  freqs.shift();
  var fundFreq = extractFundamentalFrequency(freqs);
  frequencyElem.textContent = Math.round(fundFreq);
  noteElem.textContent = getNoteFromFreq(refNote, calibratedFreq, fundFreq);

  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = window.webkitRequestAnimationFrame;
  }
  window.requestAnimationFrame(getPitch);
}

function error() {
  alert('Stream generation failed.');
}

function loadMusic(url) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';
  request.onload = function() {
    audioContext.decodeAudioData(request.response, function(buffer) {
      musicBuffer = buffer;
    });
  }
  request.send();
}

function togglePlayMusic() {
  if (isPlayingMusic) {
    musicSourceNode.stop(0);
    buttonElem.textContent = "Play";
  } else {
    musicSourceNode = audioContext.createBufferSource();
    musicSourceNode.buffer = musicBuffer;
    musicSourceNode.loop = true;
    musicSourceNode.connect(audioContext.destination);
    musicSourceNode.start(0);
    buttonElem.textContent = "Stop";
  }
  isPlayingMusic = !isPlayingMusic;
}

<|endoftext|>


<commit_before>// brush: "bash-script" aliases: []

//	This file is part of the "jQuery.Syntax" project, and is licensed under the GNU AGPLv3.
//	Copyright 2010 Samuel Williams. All rights reserved.
//	See <jquery.syntax.js> for licensing details.

Syntax.register('bash-script', function(brush) {
	var keywords = ["break", "case", "continue", "do", "done", "elif", "else", "eq", "fi", "for", "function", "ge", "gt", "if", "in", "le", "lt", "ne", "return", "then", "until", "while"];

	brush.push(keywords, {klass: 'keyword'});

	var operators = ["&", "|", ">", "<", "=", "`", "--"];
	brush.push(operators, {klass: 'operator'});

	brush.push({
		pattern: /^\s*((?:\S+=\S+\s+)*)\s*(\S+)\s+((?:.*?)?)$/gmi,
		matches: Syntax.extractMatches(
			{klass: 'env'},
			{klass: 'function'},
			{klass: 'remainder', allow: '*'}
		)
	});

	brush.push({
		pattern: /(?:\||&&)\s+(\S+)/g,
		matches: Syntax.extractMatches(
			{klass: 'function', only: ['remainder']}
		)
	});

	brush.push({
		pattern: /\$\w+/g,
		klass: 'variable'
	})

	brush.push({pattern: /\s\-+\w+/g, klass: 'option'})

	brush.push(Syntax.lib.perlStyleComment);
	brush.push(jQuery.extend({}, Syntax.lib.singleQuotedString, {allow: '*'}));
	brush.push(jQuery.extend({}, Syntax.lib.doubleQuotedString, {allow: '*'}));

	brush.push(Syntax.lib.webLink);
});

<commit_msg>Fixed bash-script that highlighting for a single command with no trailing space or options.<commit_after>// brush: "bash-script" aliases: []

//	This file is part of the "jQuery.Syntax" project, and is licensed under the GNU AGPLv3.
//	Copyright 2010 Samuel Williams. All rights reserved.
//	See <jquery.syntax.js> for licensing details.

Syntax.register('bash-script', function(brush) {
	var keywords = ["break", "case", "continue", "do", "done", "elif", "else", "eq", "fi", "for", "function", "ge", "gt", "if", "in", "le", "lt", "ne", "return", "then", "until", "while"];

	brush.push(keywords, {klass: 'keyword'});

	var operators = ["&", "|", ">", "<", "=", "`", "--"];
	brush.push(operators, {klass: 'operator'});

	brush.push({
		pattern: /^\s*((?:\S+=\S+\s+)*)\s*(\S+)((?:\s+.*?)?)$/gmi,
		matches: Syntax.extractMatches(
			{klass: 'env'},
			{klass: 'function'},
			{klass: 'remainder', allow: '*'}
		)
	});

	brush.push({
		pattern: /(?:\||&&)\s+(\S+)/g,
		matches: Syntax.extractMatches(
			{klass: 'function', only: ['remainder']}
		)
	});

	brush.push({
		pattern: /\$\w+/g,
		klass: 'variable'
	})

	brush.push({pattern: /\s\-+\w+/g, klass: 'option'})

	brush.push(Syntax.lib.perlStyleComment);
	brush.push(jQuery.extend({}, Syntax.lib.singleQuotedString, {allow: '*'}));
	brush.push(jQuery.extend({}, Syntax.lib.doubleQuotedString, {allow: '*'}));

	brush.push(Syntax.lib.webLink);
});

<|endoftext|>


<commit_before>/*! imagediff.js 1.0.1
  * (c) 2011 Carl Sutherland, Humble Software Development
  * imagediff.js is freely distributable under the MIT license.
  * Thanks to Jacob Thornton for the node/amd integration bits.
  * For details and documentation:
  * https://github.com/HumbleSoftware/js-imagediff
  */
(function (name, definition) {
  var root = this;
  if (typeof module != 'undefined') {
    module.exports = definition();
  } else if (typeof define == 'function' && typeof define.amd == 'object') {
    define(definition);
  } else {
    root[name] = definition(root, name);
  }
})('imagediff', function (root, name) {

  var
    TYPE_ARRAY        = '[object Array]',
    TYPE_CANVAS       = '[object HTMLCanvasElement]',
    TYPE_CONTEXT      = '[object CanvasRenderingContext2D]',
    TYPE_IMAGE        = '[object HTMLImageElement]',
    TYPE_IMAGE_DATA   = '[object ImageData]',

    canvas            = getCanvas(),
    context           = canvas.getContext('2d'),
    previous          = root[name],
    imagediff, jasmine;

  // Creation
  function getCanvas () {
    var
      canvas = document.createElement('canvas');
    return canvas;
  }
  function getImageData (width, height) {
    canvas.width = width;
    canvas.height = height;
    context.clearRect(0, 0, width, height);
    return context.createImageData(width, height);
  }

  // Type Checking
  function isImage (object) {
    return isType(object, TYPE_IMAGE);
  }
  function isCanvas (object) {
    return isType(object, TYPE_CANVAS);
  }
  function isContext (object) {
    return isType(object, TYPE_CONTEXT);
  }
  function isImageData (object) {
    return isType(object, TYPE_IMAGE_DATA);
  }
  function isImageType (object) {
    return (
      isImage(object) ||
      isCanvas(object) ||
      isContext(object) ||
      isImageData(object)
    );
  }
  function isType (object, type) {
    return typeof (object) === 'object' && Object.prototype.toString.apply(object) === type;
  }

  // Type Conversion
  function copyImageData (imageData) {
    var
      height = imageData.height,
      width = imageData.width;
    canvas.width = width;
    canvas.height = height;
    context.putImageData(imageData, 0, 0);
    return context.getImageData(0, 0, width, height);
  }
  function toImageData (object) {
    if (isImage(object)) { return toImageDataFromImage(object); }
    if (isCanvas(object)) { return toImageDataFromCanvas(object); }
    if (isContext(object)) { return toImageDataFromContext(object); }
    if (isImageData(object)) { return object; }
  }
  function toImageDataFromImage (image) {
    var
      height = image.height,
      width = image.width;
    canvas.width = width;
    canvas.height = height;
    context.clearRect(0, 0, width, height);
    context.drawImage(image, 0, 0);
    return context.getImageData(0, 0, width, height);
  }
  function toImageDataFromCanvas (canvas) {
    var
      height = canvas.height,
      width = canvas.width,
      context = canvas.getContext('2d');
    return context.getImageData(0, 0, width, height);
  }
  function toImageDataFromContext (context) {
    var
      canvas = context.canvas,
      height = canvas.height,
      width = canvas.width;
    return context.getImageData(0, 0, width, height);
  }

  // ImageData Equality Operators
  function equalWidth (a, b) {
    return a.width === b.width;
  }
  function equalHeight (a, b) {
    return a.height === b.height;
  }
  function equalDimensions (a, b) {
    return equalHeight(a, b) && equalWidth(a, b);
  }
  function equal (a, b) {

    var
      aData   = a.data,
      bData   = b.data,
      length  = aData.length,
      i;

    if (!equalDimensions(a, b)) return false;
    for (i = length; i--;) if (aData[i] !== bData[i]) return false;

    return true;
  }

  // Diff
  function diff (a, b) {
    return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b);
  }
  function diffEqual (a, b) {

    var
      height  = a.height,
      width   = a.width,
      c       = getImageData(width, height), // c = a - b
      aData   = a.data,
      bData   = b.data,
      cData   = c.data,
      length  = cData.length,
      row, column,
      i, j, k, v;

    for (i = 0; i < length; i += 4) {
      cData[i] = Math.abs(aData[i] - bData[i]);
      cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
      cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
      cData[i+3] = Math.abs(255 - aData[i+3] - bData[i+3]);
    }

    return c;
  }
  function diffUnequal (a, b) {

    var
      height  = Math.max(a.height, b.height),
      width   = Math.max(a.width, b.width),
      c       = getImageData(width, height), // c = a - b
      aData   = a.data,
      bData   = b.data,
      cData   = c.data,
      rowOffset,
      columnOffset,
      row, column,
      i, j, k, v;

    for (i = cData.length - 1; i > 0; i = i - 4) {
      cData[i] = 255;
    }

    // Add First Image
    offsets(a);
    for (row = a.height; row--;){
      for (column = a.width; column--;) {
        i = 4 * ((row + rowOffset) * width + (column + columnOffset));
        j = 4 * (row * a.width + column);
        cData[i+0] = aData[j+0]; // r
        cData[i+1] = aData[j+1]; // g
        cData[i+2] = aData[j+2]; // b
        // cData[i+3] = aData[j+3]; // a
      }
    }

    // Subtract Second Image
    offsets(b);
    for (row = b.height; row--;){
      for (column = b.width; column--;) {
        i = 4 * ((row + rowOffset) * width + (column + columnOffset));
        j = 4 * (row * b.width + column);
        cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
        cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
        cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
      }
    }

    // Helpers
    function offsets (imageData) {
      rowOffset = Math.floor((height - imageData.height) / 2);
      columnOffset = Math.floor((width - imageData.width) / 2);
    }

    return c;
  }

  // Validation
  function checkType () {
    var i;
    for (i = 0; i < arguments.length; i++) {
      if (!isImageType(arguments[i])) {
        throw {
          name : 'ImageTypeError',
          message : 'Submitted object was not an image.'
        };
      }
    }
  }

  // Jasmine Matchers
  function get (element, content) {
    element = document.createElement(element);
    if (element && content) {
      element.innerHTML = content;
    }
    return element;
  }
  jasmine = {

    toBeImageData : function () {
      return imagediff.isImageData(this.actual);
    },

    toImageDiffEqual : function (expected) {

      this.message = function() {

        var
          div     = get('div'),
          a       = get('div', '
Actual:
'),
          b       = get('div', '
Expected:
'),
          c       = get('div', '
Diff:
'),
          diff    = imagediff.diff(this.actual, expected),
          canvas  = getCanvas(),
          context;

        canvas.height = diff.height;
        canvas.width  = diff.width;

        context = canvas.getContext('2d');
        context.putImageData(diff, 0, 0);

        a.appendChild(this.actual);
        b.appendChild(expected);
        c.appendChild(canvas);

        div.appendChild(a);
        div.appendChild(b);
        div.appendChild(c);

        return [
          div,
          "Expected not to be equal."
        ];
      };

      return imagediff.equal(this.actual, expected);
    }
  };

  // Definition
  imagediff = {

    createCanvas : getCanvas,
    createImageData : getImageData,

    isImage : isImage,
    isCanvas : isCanvas,
    isContext : isContext,
    isImageData : isImageData,
    isImageType : isImageType,

    toImageData : function (object) {
      checkType(object);
      if (isImageData(object)) { return copyImageData(object); }
      return toImageData(object);
    },

    equal : function (a, b) {
      checkType(a, b);
      a = toImageData(a);
      b = toImageData(b);
      return equal(a, b);
    },
    diff : function (a, b) {
      checkType(a, b);
      a = toImageData(a);
      b = toImageData(b);
      return diff(a, b);
    },

    jasmine : jasmine,

    // Compatibility
    noConflict : function () {
      root[name] = previous;
      return imagediff;
    }
  };

  return imagediff;
});
<commit_msg>Added optional dimensions to createCanvas.<commit_after>/*! imagediff.js 1.0.1
  * (c) 2011 Carl Sutherland, Humble Software Development
  * imagediff.js is freely distributable under the MIT license.
  * Thanks to Jacob Thornton for the node/amd integration bits.
  * For details and documentation:
  * https://github.com/HumbleSoftware/js-imagediff
  */
(function (name, definition) {
  var root = this;
  if (typeof module != 'undefined') {
    module.exports = definition();
  } else if (typeof define == 'function' && typeof define.amd == 'object') {
    define(definition);
  } else {
    root[name] = definition(root, name);
  }
})('imagediff', function (root, name) {

  var
    TYPE_ARRAY        = '[object Array]',
    TYPE_CANVAS       = '[object HTMLCanvasElement]',
    TYPE_CONTEXT      = '[object CanvasRenderingContext2D]',
    TYPE_IMAGE        = '[object HTMLImageElement]',
    TYPE_IMAGE_DATA   = '[object ImageData]',

    canvas            = getCanvas(),
    context           = canvas.getContext('2d'),
    previous          = root[name],
    imagediff, jasmine;

  // Creation
  function getCanvas (width, height) {
    var
      canvas = document.createElement('canvas');
    if (width) canvas.width = width;
    if (height) canvas.height = height;
    return canvas;
  }
  function getImageData (width, height) {
    canvas.width = width;
    canvas.height = height;
    context.clearRect(0, 0, width, height);
    return context.createImageData(width, height);
  }

  // Type Checking
  function isImage (object) {
    return isType(object, TYPE_IMAGE);
  }
  function isCanvas (object) {
    return isType(object, TYPE_CANVAS);
  }
  function isContext (object) {
    return isType(object, TYPE_CONTEXT);
  }
  function isImageData (object) {
    return isType(object, TYPE_IMAGE_DATA);
  }
  function isImageType (object) {
    return (
      isImage(object) ||
      isCanvas(object) ||
      isContext(object) ||
      isImageData(object)
    );
  }
  function isType (object, type) {
    return typeof (object) === 'object' && Object.prototype.toString.apply(object) === type;
  }

  // Type Conversion
  function copyImageData (imageData) {
    var
      height = imageData.height,
      width = imageData.width;
    canvas.width = width;
    canvas.height = height;
    context.putImageData(imageData, 0, 0);
    return context.getImageData(0, 0, width, height);
  }
  function toImageData (object) {
    if (isImage(object)) { return toImageDataFromImage(object); }
    if (isCanvas(object)) { return toImageDataFromCanvas(object); }
    if (isContext(object)) { return toImageDataFromContext(object); }
    if (isImageData(object)) { return object; }
  }
  function toImageDataFromImage (image) {
    var
      height = image.height,
      width = image.width;
    canvas.width = width;
    canvas.height = height;
    context.clearRect(0, 0, width, height);
    context.drawImage(image, 0, 0);
    return context.getImageData(0, 0, width, height);
  }
  function toImageDataFromCanvas (canvas) {
    var
      height = canvas.height,
      width = canvas.width,
      context = canvas.getContext('2d');
    return context.getImageData(0, 0, width, height);
  }
  function toImageDataFromContext (context) {
    var
      canvas = context.canvas,
      height = canvas.height,
      width = canvas.width;
    return context.getImageData(0, 0, width, height);
  }

  // ImageData Equality Operators
  function equalWidth (a, b) {
    return a.width === b.width;
  }
  function equalHeight (a, b) {
    return a.height === b.height;
  }
  function equalDimensions (a, b) {
    return equalHeight(a, b) && equalWidth(a, b);
  }
  function equal (a, b) {

    var
      aData   = a.data,
      bData   = b.data,
      length  = aData.length,
      i;

    if (!equalDimensions(a, b)) return false;
    for (i = length; i--;) if (aData[i] !== bData[i]) return false;

    return true;
  }

  // Diff
  function diff (a, b) {
    return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b);
  }
  function diffEqual (a, b) {

    var
      height  = a.height,
      width   = a.width,
      c       = getImageData(width, height), // c = a - b
      aData   = a.data,
      bData   = b.data,
      cData   = c.data,
      length  = cData.length,
      row, column,
      i, j, k, v;

    for (i = 0; i < length; i += 4) {
      cData[i] = Math.abs(aData[i] - bData[i]);
      cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
      cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
      cData[i+3] = Math.abs(255 - aData[i+3] - bData[i+3]);
    }

    return c;
  }
  function diffUnequal (a, b) {

    var
      height  = Math.max(a.height, b.height),
      width   = Math.max(a.width, b.width),
      c       = getImageData(width, height), // c = a - b
      aData   = a.data,
      bData   = b.data,
      cData   = c.data,
      rowOffset,
      columnOffset,
      row, column,
      i, j, k, v;

    for (i = cData.length - 1; i > 0; i = i - 4) {
      cData[i] = 255;
    }

    // Add First Image
    offsets(a);
    for (row = a.height; row--;){
      for (column = a.width; column--;) {
        i = 4 * ((row + rowOffset) * width + (column + columnOffset));
        j = 4 * (row * a.width + column);
        cData[i+0] = aData[j+0]; // r
        cData[i+1] = aData[j+1]; // g
        cData[i+2] = aData[j+2]; // b
        // cData[i+3] = aData[j+3]; // a
      }
    }

    // Subtract Second Image
    offsets(b);
    for (row = b.height; row--;){
      for (column = b.width; column--;) {
        i = 4 * ((row + rowOffset) * width + (column + columnOffset));
        j = 4 * (row * b.width + column);
        cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
        cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
        cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
      }
    }

    // Helpers
    function offsets (imageData) {
      rowOffset = Math.floor((height - imageData.height) / 2);
      columnOffset = Math.floor((width - imageData.width) / 2);
    }

    return c;
  }

  // Validation
  function checkType () {
    var i;
    for (i = 0; i < arguments.length; i++) {
      if (!isImageType(arguments[i])) {
        throw {
          name : 'ImageTypeError',
          message : 'Submitted object was not an image.'
        };
      }
    }
  }

  // Jasmine Matchers
  function get (element, content) {
    element = document.createElement(element);
    if (element && content) {
      element.innerHTML = content;
    }
    return element;
  }
  jasmine = {

    toBeImageData : function () {
      return imagediff.isImageData(this.actual);
    },

    toImageDiffEqual : function (expected) {

      this.message = function() {

        var
          div     = get('div'),
          a       = get('div', '
Actual:
'),
          b       = get('div', '
Expected:
'),
          c       = get('div', '
Diff:
'),
          diff    = imagediff.diff(this.actual, expected),
          canvas  = getCanvas(),
          context;

        canvas.height = diff.height;
        canvas.width  = diff.width;

        context = canvas.getContext('2d');
        context.putImageData(diff, 0, 0);

        a.appendChild(this.actual);
        b.appendChild(expected);
        c.appendChild(canvas);

        div.appendChild(a);
        div.appendChild(b);
        div.appendChild(c);

        return [
          div,
          "Expected not to be equal."
        ];
      };

      return imagediff.equal(this.actual, expected);
    }
  };

  // Definition
  imagediff = {

    createCanvas : getCanvas,
    createImageData : getImageData,

    isImage : isImage,
    isCanvas : isCanvas,
    isContext : isContext,
    isImageData : isImageData,
    isImageType : isImageType,

    toImageData : function (object) {
      checkType(object);
      if (isImageData(object)) { return copyImageData(object); }
      return toImageData(object);
    },

    equal : function (a, b) {
      checkType(a, b);
      a = toImageData(a);
      b = toImageData(b);
      return equal(a, b);
    },
    diff : function (a, b) {
      checkType(a, b);
      a = toImageData(a);
      b = toImageData(b);
      return diff(a, b);
    },

    jasmine : jasmine,

    // Compatibility
    noConflict : function () {
      root[name] = previous;
      return imagediff;
    }
  };

  return imagediff;
});
<|endoftext|>