<commit_before>import { Meteor } from 'meteor/meteor';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';

const Translations = new Meteor.Collection('translations');

Translations.schema = new SimpleSchema({
	status: {
		type: String,
		optional: true,
	},

	wordpressId: {
		type: Number,
		optional: true,
	},

	tenantId: {
		type: String,
		optional: true,
	},

	commenters: {
		type: Object,
		optional: true,
	},

	work: {
		type: Object,
		optional: true,
	},

	subwork: {
		type: Object,
		optional: true,
	},

	lineFrom: {
		type: Number,
		optional: true,
	},

	lineTo: {
		type: Number,
		optional: true,
	},

	lineLetter: {
		type: String,
		optional: true,
	},

	bookChapterUrl: {
		type: String,
		optional: true,
	},

	paragraphN: {
		type: Number,
		optional: true,
	},

	nLines: {
		type: Number,
		optional: true,
	},

	isAnnotation: {
		type: Boolean,
		optional: true,
	},

	discussionCommentsDisabled: {
		type: Boolean,
		optional: true,
	},

	created: {
		type: Date,
		optional: true,
	},

	updated: {
		type: Date,
		optional: true,
	},

	textValue: {
		type: Object,
		optional: true
	},

	textRawValue: {
		type: Object,
		optional: true
	},

	revisions: {
		type: [Object],
		optional: true,
	},

});

Translations.attachSchema(Translations.schema);

Translations.attachBehaviour('timestamable', {
	createdAt: 'created',
	createdBy: 'createdBy',
	updatedAt: 'updated',
	updatedBy: 'updatedBy'
});

export default Translations;
<commit_msg>fixed friendly slug issue<commit_after>import { Meteor } from 'meteor/meteor';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';

const Translations = new Meteor.Collection('translations');

Translations.schema = new SimpleSchema({
	status: {
		type: String,
		optional: true,
	},

	wordpressId: {
		type: Number,
		optional: true,
	},

	tenantId: {
		type: String,
		optional: true,
	},

	commenters: {
		type: Object,
		optional: true,
	},

	work: {
		type: Object,
		optional: true,
	},

	subwork: {
		type: Object,
		optional: true,
	},

	lineFrom: {
		type: Number,
		optional: true,
	},

	lineTo: {
		type: Number,
		optional: true,
	},

	lineLetter: {
		type: String,
		optional: true,
	},

	bookChapterUrl: {
		type: String,
		optional: true,
	},

	paragraphN: {
		type: Number,
		optional: true,
	},

	nLines: {
		type: Number,
		optional: true,
	},

	isAnnotation: {
		type: Boolean,
		optional: true,
	},

	discussionCommentsDisabled: {
		type: Boolean,
		optional: true,
	},

	created: {
		type: Date,
		optional: true,
	},

	updated: {
		type: Date,
		optional: true,
	},

	textValue: {
		type: Object,
		optional: true
	},

	textRawValue: {
		type: Object,
		optional: true
	},

	revisions: {
		type: [Object],
		optional: true,
	},

	'revisions.$._id': {
		type: String,
		optional: true,
	},

	'revisions.$.title': {
		type: String,
		optional: true,
	},

	'revisions.$.text': {
		type: String,
		optional: true,
	},

	'revisions.$.tenantId': {
		type: String,
		optional: true,
	},

	'revisions.$.originalDate': {
		type: Date,
		optional: true,
	},

	'revisions.friendlySlugs.slug.base': {
		type: String,
		optional: true,
	},

	'revisions.friendlySlugs.slug.index': {
		type: Number,
		optional: Number,
	},

	'revisions.$.slug': {
		type: String,
		optional: true,
	},

	'revisions.$.created': {
		type: Date,
		optional: true,
	},

	'revisions.$.createdBy': {
		type: String,
		optional: true,
	},

	'revisions.$.updated': {
		type: Date,
		optional: true,
	},

	'revisions.$.updatedBy': {
		type: String,
		optional: true,
	},

});

Translations.attachSchema(Translations.schema);

Translations.attachBehaviour('timestamable', {
	createdAt: 'created',
	createdBy: 'createdBy',
	updatedAt: 'updated',
	updatedBy: 'updatedBy'
});

export default Translations;
<|endoftext|>


<commit_before>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

(function() {

    var root = this;
    var _ = root._.noConflict();
    var $ = root.AmarajQuery;

    var LanguageSelectorController = function($scope, SubtitleStorage, SubtitleListFinder) {
        /**
         * This controller is responsible for the language and version selector
         * widget.  The widget allows the user to select a reference language and
         * version when translating subtitles into another language.
         *
         * The $scope contains 'languages' and 'versions'.  Each list is
         * represented in the UI as a <select> element.  The selected language or
         * version is stored on the $scope as a 'language' and 'version' model.
         *
         * Whenever the language selection is changed, we update the list of
         * versions.  When a new version is selected, we retrieve the subtitles
         * (either from memory or from the API via ajax) and display them in the
         * side panel.
         */

        $scope.languageSelectChanged = function(lang) {
            var vers, language;

            if (lang) {
                language = lang;
            } else {
                language = $scope.language;
            }

            if (!language) {
                return;
            }

            vers =_.sortBy(language.versions, function(item) {
                return item.version_no;
            });

            $scope.versions = vers.reverse();

            if (vers.length && vers.length > 0) {
                $scope.version = $scope.versions[0];
            }
        };
        $scope.setReferenceSubs = function(subtitlesXML) {
            if (!$scope.refSubList) {
                $scope.refSubList = SubtitleListFinder.get('reference-subtitle-set');
            }
            $scope.refSubList.scope.onSubtitlesFetched(subtitlesXML);
        };
        $scope.versionChanged = function(newVersion) {
            var subtitlesXML;

            if (!newVersion) {
                return;
            }
            subtitlesXML = newVersion.subtitlesXML;

            if (!subtitlesXML) {
                SubtitleStorage.getSubtitles($scope.language.code,
                                             newVersion.version_no,
                                             function(subtitlesXML) {
                    $scope.version.subtitlesXML = subtitlesXML;
                    $scope.setReferenceSubs(subtitlesXML);
                });
            } else {
                $scope.setReferenceSubs(subtitlesXML);
            }
        };

        SubtitleStorage.getLanguages(function(languages) {
            $scope.languages = languages;
            $scope.language = _.find(languages, function(item) {
                return item.editingLanguage;
            });
            $scope.languageSelectChanged($scope.language);
        });

        $scope.$watch('version', $scope.versionChanged);
    };
    var SaveSessionButtonController = function($scope, SubtitleListFinder) {

        $scope.cancel = function($event) {

            $event.preventDefault();

            // TODO: Need a redirect URL.
        };
        $scope.saveAndExit = function($event) {

            $event.preventDefault();

            $scope.saveSession().then(function() {
                if ($scope.status === 'saved') {

                    // TODO: Need a redirect URL.

                }
            });
        };
        $scope.saveSession = function() {
            if ($scope.status !== 'saving') {
                $scope.status = 'saving';

                var promise = SubtitleListFinder.get('working-subtitle-set').scope.saveSubtitles();

                promise.then(function onSuccess(response) {
                    $scope.status = 'saved';
                    $scope.$root.$emit('show-loading-modal', 'Subtitles saved! Redirecting…');
                    window.location = response['data']['site_url'];
                }, function onError() {
                    $scope.status = 'error';
                    window.alert('Sorry, there was an error...');
                });

                return promise;
            }
        };
        $scope.toggleSaveDropdown = function($event) {
            $scope.dropdownOpen = !$scope.dropdownOpen;
            $event.preventDefault();
        };

        $scope.$root.$on('work-done', function() {
            $scope.canSave = '';
            $scope.$digest();
        });
    };
    var SubtitleListController = function($scope, $timeout, SubtitleStorage) {
        /**
         * Responsible for everything that touches subtitles as a group,
         * souch as populating the list with actual data, removing subs,
         * adding subs.
         * @param $scope
         * @param SubtitleStorage
         * @constructor
         */

        $scope.addSubtitle = function(index, attrs, content, focus) {

            // Add the subtitle directly to the DFXP instance.
            var newSubtitle = $scope.parser.addSubtitle(index, attrs, content);

            // If we want to focus the subtitle after it's been added, set
            // the index here.
            if (focus) {
                $scope.focusIndex = $scope.parser.getSubtitleIndex(newSubtitle, $scope.parser.getSubtitles().get());

            // Otherwise, reset the focusIndex.
            } else {
                $scope.focusIndex = null;
            }

            // Update the subtitles on the list scope.
            $scope.updateParserSubtitles();
        };
        $scope.getSubtitleListHeight = function() {
            return $(window).height() - 359;
        };
        $scope.getSubtitles = function(languageCode, versionNumber) {
            // if this version has no default source translation language
            // it will be empty, in which case we want to wait for user
            // interaction to request a reference subtitle set.
            if (!languageCode || !versionNumber) {
                $scope.status = 'idle';
                return;
            }
            $scope.status = 'loading';
            $scope.items = SubtitleStorage.getSubtitles(languageCode, versionNumber, function(subtitlesXML) {
                $scope.onSubtitlesFetched(subtitlesXML);
            });
        };
        $scope.onSubtitlesFetched = function (dfxpXML) {
            /**
             * Once we have the dfxp from the server,
             * massage the data as a simpler object and set it on the
             * template. Angular will pick up the change (from the broadcast)
             * and will re-render the UI.
             * @param dfxpXML
             */

            this.dfxpWrapper = new root.AmaraDFXPParser();
            this.dfxpWrapper.init(dfxpXML);

            $scope.parser = this.dfxpWrapper;
            $scope.subtitles = $scope.parser.getSubtitles().get();

            $scope.status = 'ready';

            // When we have subtitles for an editable set, tell the kids.
            $timeout(function() {
                if ($scope.isEditable) {
                    $scope.$broadcast('subtitlesFetched');
                }
            });
        };
        $scope.removeSubtitle = function(subtitle) {
            $scope.parser.removeSubtitle(subtitle);
            $scope.updateParserSubtitles();
        };
        $scope.saveSubtitles = function() {
            $scope.status = 'saving';
            return SubtitleStorage.saveSubtitles($scope.videoID,
                                          $scope.languageCode,
                                          $scope.parser.xmlToString(true, true));
        };
        $scope.setLanguageCode = function(languageCode) {
            $scope.languageCode = languageCode;
        };
        $scope.setVideoID = function(videoID) {
            $scope.videoID = videoID;
        };
        $scope.updateParserSubtitles = function() {
            $scope.subtitles = $scope.parser.getSubtitles().get();
        };

        $scope.$watch($scope.getSubtitleListHeight, function(newHeight) {
            $($('div.subtitles').height(newHeight));
        });

        window.onresize = function() {
            $scope.$digest();
        };
    };
    var SubtitleListHelperController = function($scope) {

        $scope.isEditingAny = false;

        $scope.$root.$on('editing', function() {
            $scope.isEditingAny = true;
            $scope.$digest();
        });
        $scope.$root.$on('editingDone', function() {
            $scope.isEditingAny = false;
            $scope.$digest();
        });
    };
    var SubtitleListItemController = function($scope) {
        /**
         * Responsible for actions on one subtitle: editing, selecting.
         * @param $scope
         * @constructor
         */

        var initialText;

        $scope.empty = false;
        $scope.isEditing = false;
        $scope.showStartTime = $scope.parser.startTimeFromNode($scope.subtitle) > 0;

        $scope.finishEditingMode = function(newValue) {

            $scope.isEditing = false;

            // Tell the root scope that we're no longer editing, now.
            $scope.$root.$emit('editingDone');

            var content = $scope.parser.content($scope.subtitle, newValue);

            if (content !== initialText) {
                $scope.$root.$emit('work-done');
            }
        };
        $scope.getSubtitleIndex = function() {
            return $scope.parser.getSubtitleIndex($scope.subtitle, $scope.subtitles);
        };
        $scope.startEditingMode = function() {

            initialText = $scope.parser.content($scope.subtitle);

            $scope.isEditing = true;

            // Tell the root scope that we're editing, now.
            $scope.$root.$emit('editing');

            return initialText;
        };

        $scope.$on('subtitlesFetched', function() {
            // When subtitles are first retrieved, we need to set up the amarasubtitle
            // on the video and bind to this scope.
            //
            // This will happen on the video controller. Just throw an event stating that
            // we're ready.

            $scope.$root.$emit('subtitleReady', $scope);
        });
    };

    root.LanguageSelectorController = LanguageSelectorController;
    root.SaveSessionButtonController = SaveSessionButtonController;
    root.SubtitleListController = SubtitleListController;
    root.SubtitleListHelperController = SubtitleListHelperController;
    root.SubtitleListItemController = SubtitleListItemController;

}).call(this);
<commit_msg>Move redirection out of saveSession<commit_after>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

(function() {

    var root = this;
    var _ = root._.noConflict();
    var $ = root.AmarajQuery;

    var LanguageSelectorController = function($scope, SubtitleStorage, SubtitleListFinder) {
        /**
         * This controller is responsible for the language and version selector
         * widget.  The widget allows the user to select a reference language and
         * version when translating subtitles into another language.
         *
         * The $scope contains 'languages' and 'versions'.  Each list is
         * represented in the UI as a <select> element.  The selected language or
         * version is stored on the $scope as a 'language' and 'version' model.
         *
         * Whenever the language selection is changed, we update the list of
         * versions.  When a new version is selected, we retrieve the subtitles
         * (either from memory or from the API via ajax) and display them in the
         * side panel.
         */

        $scope.languageSelectChanged = function(lang) {
            var vers, language;

            if (lang) {
                language = lang;
            } else {
                language = $scope.language;
            }

            if (!language) {
                return;
            }

            vers =_.sortBy(language.versions, function(item) {
                return item.version_no;
            });

            $scope.versions = vers.reverse();

            if (vers.length && vers.length > 0) {
                $scope.version = $scope.versions[0];
            }
        };
        $scope.setReferenceSubs = function(subtitlesXML) {
            if (!$scope.refSubList) {
                $scope.refSubList = SubtitleListFinder.get('reference-subtitle-set');
            }
            $scope.refSubList.scope.onSubtitlesFetched(subtitlesXML);
        };
        $scope.versionChanged = function(newVersion) {
            var subtitlesXML;

            if (!newVersion) {
                return;
            }
            subtitlesXML = newVersion.subtitlesXML;

            if (!subtitlesXML) {
                SubtitleStorage.getSubtitles($scope.language.code,
                                             newVersion.version_no,
                                             function(subtitlesXML) {
                    $scope.version.subtitlesXML = subtitlesXML;
                    $scope.setReferenceSubs(subtitlesXML);
                });
            } else {
                $scope.setReferenceSubs(subtitlesXML);
            }
        };

        SubtitleStorage.getLanguages(function(languages) {
            $scope.languages = languages;
            $scope.language = _.find(languages, function(item) {
                return item.editingLanguage;
            });
            $scope.languageSelectChanged($scope.language);
        });

        $scope.$watch('version', $scope.versionChanged);
    };
    var SaveSessionButtonController = function($scope, SubtitleListFinder) {

        $scope.cancel = function($event) {

            $event.preventDefault();

            // TODO: Need a redirect URL.
        };
        $scope.saveAndExit = function($event) {

            $event.preventDefault();

            $scope.saveSession().then(function(response) {
                if ($scope.status === 'saved') {

                    $scope.$root.$emit('show-loading-modal', 'Subtitles saved! Redirecting…');
                    window.location = response['data']['site_url'];

                } else if ($scope.status === 'error') {
                    window.alert('Sorry, there was an error...');
                }
            });
        };
        $scope.saveSession = function() {
            if ($scope.status !== 'saving') {
                $scope.status = 'saving';

                var promise = SubtitleListFinder.get('working-subtitle-set').scope.saveSubtitles();

                promise.then(function onSuccess(response) {
                    $scope.status = 'saved';
                }, function onError() {
                    $scope.status = 'error';
                });

                return promise;
            }
        };
        $scope.toggleSaveDropdown = function($event) {
            $scope.dropdownOpen = !$scope.dropdownOpen;
            $event.preventDefault();
        };

        $scope.$root.$on('work-done', function() {
            $scope.canSave = '';
            $scope.$digest();
        });
    };
    var SubtitleListController = function($scope, $timeout, SubtitleStorage) {
        /**
         * Responsible for everything that touches subtitles as a group,
         * souch as populating the list with actual data, removing subs,
         * adding subs.
         * @param $scope
         * @param SubtitleStorage
         * @constructor
         */

        $scope.addSubtitle = function(index, attrs, content, focus) {

            // Add the subtitle directly to the DFXP instance.
            var newSubtitle = $scope.parser.addSubtitle(index, attrs, content);

            // If we want to focus the subtitle after it's been added, set
            // the index here.
            if (focus) {
                $scope.focusIndex = $scope.parser.getSubtitleIndex(newSubtitle, $scope.parser.getSubtitles().get());

            // Otherwise, reset the focusIndex.
            } else {
                $scope.focusIndex = null;
            }

            // Update the subtitles on the list scope.
            $scope.updateParserSubtitles();
        };
        $scope.getSubtitleListHeight = function() {
            return $(window).height() - 359;
        };
        $scope.getSubtitles = function(languageCode, versionNumber) {
            // if this version has no default source translation language
            // it will be empty, in which case we want to wait for user
            // interaction to request a reference subtitle set.
            if (!languageCode || !versionNumber) {
                $scope.status = 'idle';
                return;
            }
            $scope.status = 'loading';
            $scope.items = SubtitleStorage.getSubtitles(languageCode, versionNumber, function(subtitlesXML) {
                $scope.onSubtitlesFetched(subtitlesXML);
            });
        };
        $scope.onSubtitlesFetched = function (dfxpXML) {
            /**
             * Once we have the dfxp from the server,
             * massage the data as a simpler object and set it on the
             * template. Angular will pick up the change (from the broadcast)
             * and will re-render the UI.
             * @param dfxpXML
             */

            this.dfxpWrapper = new root.AmaraDFXPParser();
            this.dfxpWrapper.init(dfxpXML);

            $scope.parser = this.dfxpWrapper;
            $scope.subtitles = $scope.parser.getSubtitles().get();

            $scope.status = 'ready';

            // When we have subtitles for an editable set, tell the kids.
            $timeout(function() {
                if ($scope.isEditable) {
                    $scope.$broadcast('subtitlesFetched');
                }
            });
        };
        $scope.removeSubtitle = function(subtitle) {
            $scope.parser.removeSubtitle(subtitle);
            $scope.updateParserSubtitles();
        };
        $scope.saveSubtitles = function() {
            $scope.status = 'saving';
            return SubtitleStorage.saveSubtitles($scope.videoID,
                                          $scope.languageCode,
                                          $scope.parser.xmlToString(true, true));
        };
        $scope.setLanguageCode = function(languageCode) {
            $scope.languageCode = languageCode;
        };
        $scope.setVideoID = function(videoID) {
            $scope.videoID = videoID;
        };
        $scope.updateParserSubtitles = function() {
            $scope.subtitles = $scope.parser.getSubtitles().get();
        };

        $scope.$watch($scope.getSubtitleListHeight, function(newHeight) {
            $($('div.subtitles').height(newHeight));
        });

        window.onresize = function() {
            $scope.$digest();
        };
    };
    var SubtitleListHelperController = function($scope) {

        $scope.isEditingAny = false;

        $scope.$root.$on('editing', function() {
            $scope.isEditingAny = true;
            $scope.$digest();
        });
        $scope.$root.$on('editingDone', function() {
            $scope.isEditingAny = false;
            $scope.$digest();
        });
    };
    var SubtitleListItemController = function($scope) {
        /**
         * Responsible for actions on one subtitle: editing, selecting.
         * @param $scope
         * @constructor
         */

        var initialText;

        $scope.empty = false;
        $scope.isEditing = false;
        $scope.showStartTime = $scope.parser.startTimeFromNode($scope.subtitle) > 0;

        $scope.finishEditingMode = function(newValue) {

            $scope.isEditing = false;

            // Tell the root scope that we're no longer editing, now.
            $scope.$root.$emit('editingDone');

            var content = $scope.parser.content($scope.subtitle, newValue);

            if (content !== initialText) {
                $scope.$root.$emit('work-done');
            }
        };
        $scope.getSubtitleIndex = function() {
            return $scope.parser.getSubtitleIndex($scope.subtitle, $scope.subtitles);
        };
        $scope.startEditingMode = function() {

            initialText = $scope.parser.content($scope.subtitle);

            $scope.isEditing = true;

            // Tell the root scope that we're editing, now.
            $scope.$root.$emit('editing');

            return initialText;
        };

        $scope.$on('subtitlesFetched', function() {
            // When subtitles are first retrieved, we need to set up the amarasubtitle
            // on the video and bind to this scope.
            //
            // This will happen on the video controller. Just throw an event stating that
            // we're ready.

            $scope.$root.$emit('subtitleReady', $scope);
        });
    };

    root.LanguageSelectorController = LanguageSelectorController;
    root.SaveSessionButtonController = SaveSessionButtonController;
    root.SubtitleListController = SubtitleListController;
    root.SubtitleListHelperController = SubtitleListHelperController;
    root.SubtitleListItemController = SubtitleListItemController;

}).call(this);
<|endoftext|>


<commit_before>/*! @license ©2015 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
// jQuery widget for Triple Pattern Fragments query execution

(function ($) {
  // Query UI main entry point, which mimics the jQuery UI widget interface:
  // - $(element).queryui(options) initializes the widget
  // - $(element).queryui('option', [key], [value]) gets or sets one or all options
  $.fn.queryui = function (operation, option, value) {
    // Shift parameters if no operation given
    if (typeof operation !== 'string')
      value = option, option = operation, operation = 'init';

    // Apply the operation to all elements; if one element yields a value, stop and return it
    var result = this;
    for (var i = 0; i < this.length && result === this; i++) {
      var $element = $(this[i]), queryui = $element.data('queryui');
      switch (operation) {
        // initialize the element as a Query UI
        case 'init':
          if (!queryui) {
            $element.data('queryui', queryui = new LdfQueryUI($element, option));
            queryui._create();
          }
          break;
        // set an option of a Query UI
        case 'option':
          if (!queryui) throw new Error('Query UI not activated on this element');
          // retrieve all options
          if (option === undefined)     result = queryui.options;
          // retrieve a specific option
          else if (value === undefined) result = queryui.options[value];
          // set a specific option
          else queryui._setOption(option, value);
          break;
      }
    }
    return result;
  };

  // Creates a new Query UI interface for the given element
  function LdfQueryUI($element, options) {
    this.element = $element;
    this.options = $.extend({}, this.options, options);
  }

  $.extend(LdfQueryUI.prototype, {
    // Default widget options
    options: {
      datasources: [],
      queries: [],
      prefixes: [],
    },

    // Initializes the widget
    _create: function () {
      var self = this,
          options = this.options,
          $element = this.element,
          $log = this.$log = $('.log', $element),
          $stop = this.$stop = $('.stop', $element),
          $start = this.$start = $('.start', $element),
          $query = this.$query = $('.queryText', $element),
          $queries = this.$queries = $('.query', $element),
          $results = this.$results = $('.results', $element),
          $datasources = this.$datasources = $('.datasources', $element);

      // Replace non-existing elements by an empty text box
      if (!$datasources.length) $datasources = this.$datasources = $('<select>');

      // When a datasource is selected, load the corresponding query set
      $datasources.chosen({
        create_option: true, persistent_create_option: true,
        skip_no_results: true, search_contains: true, display_selected_options: false,
        placeholder_text: ' ', create_option_text: 'Add datasource',
      });
      $datasources.change(function () { self._setOption('selectedDatasources', $datasources.val()); });

      // When a query is selected, load it into the editor
      $query.edited = $query.val() !== '';
      $query.change(function () { options.query = $query.val(); $query.edited = true; });
      $queries.chosen({ skip_no_results: true, placeholder_text: ' ' });
      $queries.change(function (query) {
        if (query = $queries.val())
          $query.val(options.query = query).edited = false;
      });

      // Set up starting and stopping
      $start.click(this._execute.bind(this));
      $stop.click(this._stopExecution.bind(this));

      // Add log lines to the log element
      var logger = this._logger = new ldf.Logger();
      ldf.Logger.setLevel('info');
      logger._print = function (items) { appendText($log, items.slice(2).join(' ').trim() + '\n'); };

      // Apply all options
      for (var key in options)
        this._setOption(key, options[key], true);
    },

    // Sets a specific widget option
    _setOption: function (key, value, initialize) {
      var options = this.options;
      if (!initialize && options[key] === value) return;
      options[key] = value;

      // Apply the chosen option
      var self = this, $datasources = this.$datasources, $queries = this.$queries;
      switch (key) {
      // Set the datasources available for querying
      case 'datasources':
        // Create options for each datasource
        $datasources.empty().append((value || []).map(function (datasource, index) {
          return $('<option>', { text: datasource.name, value: datasource.url });
        }));
        // Restore selected datasources
        this._setOption('selectedDatasources', options.selectedDatasources, true);
        break;
      // Set the datasources to query
      case 'selectedDatasources':
        // Choose the first available datasource if none was chosen
        var $options = $datasources.children();
        if (initialize && !(value && value.length) && $options.length)
          options[key] = value = [$options.val()];
        // Select chosen datasources that were already in the list
        var selected = toHash(value);
        $options.each(function (index) {
          var $option = $(this), url = $(this).val();
          $option.attr('selected', url in selected);
          selected[url] = true;
        });
        // Add and select chosen datasources that were not in the list yet
        $datasources.append($.map(selected, function (exists, url) {
          return exists ? null : $('<option>', { text: url, value: url, selected: true });
        })).trigger('chosen:updated');
        // Update the query set
        this._loadQueries(value);
        break;
      // Set the query
      case 'query':
        this.$query.val(value).change();
        $queries.children().each(function () { $(this).attr('selected', $(this).val() === value); });
        $queries.trigger('chosen:updated');
        break;
      // Set the list of all possible queries
      case 'queries':
        this._loadQueries(options.selectedDatasources);
        break;
      // Set the list of selectable queries
      case 'relevantQueries':
        $queries.empty().append($('<option>'), (value || []).map(function (query) {
          return $('<option>', { text: query.name, value: query.sparql,
                                 selected: self.options.query === query.sparql });
        })).trigger('chosen:updated').change();
        // Load the first query if the current query was not edited and not in the list
        if (!this.$query.edited && (!this.$query.val() || this.$query.val() !== $queries.val()))
          value[0] && this._setOption('query', value[0].sparql);
        break;
      // Load settings from a JSON resource
      case 'settings':
        $.getJSON(value, function (settings) {
          for (var key in settings)
            self._setOption(key, settings[key]);
        });
        break;
      }
    },

    // Load queries relevant for the given datasources
    _loadQueries: function (datasources) {
      datasources = toHash(datasources);
      var queries = (this.options.queries || []).filter(function (query, index) {
        query.id = index;
        // Include the query if it indicates no datasources,
        // or if it is relevant for at least one datasource
        return !query.datasources || !query.datasources.length ||
               query.datasources.some(function (d) { return d in datasources; });
      });

      // Load the set of queries if it is different from the current set
      var querySetId = queries.map(function (q) { return q.id; }).join();
      if (this._querySetId !== querySetId) {
        this._querySetId = querySetId;
        this._setOption('relevantQueries', queries);
        this.element.trigger('changeQuerySet');
      }
    },

    // Starts query execution
    _execute: function () {
     var datasources = this.$datasources.val();
      if (!datasources || !datasources.length)
        return alert('Please choose a datasource to execute the query.');

      // Clear results and log, and scroll page to the results
      var $results = this.$results, $log = this.$log;
      $('html,body').animate({ scrollTop: this.$start.offset().top });
      this.$stop.show();
      this.$start.hide();
      $log.empty();
      $results.empty();

      // Create a client to fetch the fragments through HTTP
      var config = { prefixes: this.options.prefixes, logger: this._logger };
      config.fragmentsClient = new ldf.FragmentsClient(datasources, config);

      // Create the iterator to solve the query
      var resultsIterator;
      try { resultsIterator = new ldf.SparqlIterator(this.$query.val(), config); }
      catch (error) { return this._stopExecution(error); }
      this._resultsIterator = resultsIterator;
      resultsIterator.on('end', $.proxy(this._stopExecution, this));
      resultsIterator.on('error', $.proxy(this._stopExecution, this));

      // Read the iterator's results, and write them depending on the query type
      switch (resultsIterator.queryType) {
        // For SELECT queries, write a JSON array representation of the rows
        case 'SELECT':
          var resultCount = 0;
          resultsIterator.on('data', function (row) {
            resultCount++;
            var lines = [];
            $.each(row, function (k, v) { if (v !== undefined) lines.push(k + ': ' + v); });
            appendText($results, lines.join('\n'), '\n\n');
          });
          resultsIterator.on('end', function () {
            resultCount || appendText($results, '(This query has no results.)');
          });
          break;
        // For CONSTRUCT and DESCRIBE queries, write a Turtle representation of all results
        case 'CONSTRUCT':
        case 'DESCRIBE':
          var writer = new N3.Writer({ write: function (chunk, encoding, done) {
            appendText($results, chunk), done && done();
          }}, config);
          resultsIterator.on('data', function (triple) { writer.addTriple(triple); })
                         .on('end',  function () { writer.end(); });
          break;
        // For ASK queries, write whether an answer exists
        case 'ASK':
          resultsIterator.on('data', function (exists) { appendText($results, exists); });
          break;
        default:
          appendText($log, 'Unsupported query type: ' + resultsIterator.queryType);
      }
    },

    // Stops query execution
    _stopExecution: function (error) {
      this.$stop.hide();
      this.$start.show();
      this._resultsIterator && this._resultsIterator.removeAllListeners();
      ldf.HttpClient.abortAll && ldf.HttpClient.abortAll();
      error && error.message && this.$results.text(error.message);
    },
  });

  // Appends text to the given element
  function appendText($element) {
    for (var i = 1, l = arguments.length; i < l; i++)
      $element.append((arguments[i] + '').replace(/(<)|(>)|(&)|(https?:\/\/[^\s<>]+)/g, escape));
    $element.scrollTop(1E10);
  }

  // Escapes special HTML characters and convert URLs into links
  function escape(match, lt, gt, amp, url) {
    return lt && '<' || gt && '>' || amp && '&' ||
           $('<a>', { href: url, target: '_blank', text: url })[0].outerHTML;
  }

  // Converts the array to a hash with the elements as keys
  function toHash(array) {
    var hash = {}, length = array ? array.length : 0;
    for (var i = 0; i < length; i++)
      hash[array[i]] = false;
    return hash;
  }
})(jQuery);
<commit_msg>Show the default query if selected.<commit_after>/*! @license ©2015 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
// jQuery widget for Triple Pattern Fragments query execution

(function ($) {
  // Query UI main entry point, which mimics the jQuery UI widget interface:
  // - $(element).queryui(options) initializes the widget
  // - $(element).queryui('option', [key], [value]) gets or sets one or all options
  $.fn.queryui = function (operation, option, value) {
    // Shift parameters if no operation given
    if (typeof operation !== 'string')
      value = option, option = operation, operation = 'init';

    // Apply the operation to all elements; if one element yields a value, stop and return it
    var result = this;
    for (var i = 0; i < this.length && result === this; i++) {
      var $element = $(this[i]), queryui = $element.data('queryui');
      switch (operation) {
        // initialize the element as a Query UI
        case 'init':
          if (!queryui) {
            $element.data('queryui', queryui = new LdfQueryUI($element, option));
            queryui._create();
          }
          break;
        // set an option of a Query UI
        case 'option':
          if (!queryui) throw new Error('Query UI not activated on this element');
          // retrieve all options
          if (option === undefined)     result = queryui.options;
          // retrieve a specific option
          else if (value === undefined) result = queryui.options[value];
          // set a specific option
          else queryui._setOption(option, value);
          break;
      }
    }
    return result;
  };

  // Creates a new Query UI interface for the given element
  function LdfQueryUI($element, options) {
    this.element = $element;
    this.options = $.extend({}, this.options, options);
  }

  $.extend(LdfQueryUI.prototype, {
    // Default widget options
    options: {
      datasources: [],
      queries: [],
      prefixes: [],
    },

    // Initializes the widget
    _create: function () {
      var self = this,
          options = this.options,
          $element = this.element,
          $log = this.$log = $('.log', $element),
          $stop = this.$stop = $('.stop', $element),
          $start = this.$start = $('.start', $element),
          $query = this.$query = $('.queryText', $element),
          $queries = this.$queries = $('.query', $element),
          $results = this.$results = $('.results', $element),
          $datasources = this.$datasources = $('.datasources', $element);

      // Replace non-existing elements by an empty text box
      if (!$datasources.length) $datasources = this.$datasources = $('<select>');

      // When a datasource is selected, load the corresponding query set
      $datasources.chosen({
        create_option: true, persistent_create_option: true,
        skip_no_results: true, search_contains: true, display_selected_options: false,
        placeholder_text: ' ', create_option_text: 'Add datasource',
      });
      $datasources.change(function () { self._setOption('selectedDatasources', $datasources.val()); });

      // When a query is selected, load it into the editor
      $query.edited = $query.val() !== '';
      $query.change(function () { options.query = $query.val(); $query.edited = true; });
      $queries.chosen({ skip_no_results: true, placeholder_text: ' ' });
      $queries.change(function (query) {
        if (query = $queries.val())
          $query.val(options.query = query).edited = false;
      });

      // Set up starting and stopping
      $start.click(this._execute.bind(this));
      $stop.click(this._stopExecution.bind(this));

      // Add log lines to the log element
      var logger = this._logger = new ldf.Logger();
      ldf.Logger.setLevel('info');
      logger._print = function (items) { appendText($log, items.slice(2).join(' ').trim() + '\n'); };

      // Apply all options
      for (var key in options)
        this._setOption(key, options[key], true);
    },

    // Sets a specific widget option
    _setOption: function (key, value, initialize) {
      var options = this.options;
      if (!initialize && options[key] === value) return;
      options[key] = value;

      // Apply the chosen option
      var self = this, $datasources = this.$datasources, $queries = this.$queries;
      switch (key) {
      // Set the datasources available for querying
      case 'datasources':
        // Create options for each datasource
        $datasources.empty().append((value || []).map(function (datasource, index) {
          return $('<option>', { text: datasource.name, value: datasource.url });
        }));
        // Restore selected datasources
        this._setOption('selectedDatasources', options.selectedDatasources, true);
        break;
      // Set the datasources to query
      case 'selectedDatasources':
        // Choose the first available datasource if none was chosen
        var $options = $datasources.children();
        if (initialize && !(value && value.length) && $options.length)
          options[key] = value = [$options.val()];
        // Select chosen datasources that were already in the list
        var selected = toHash(value);
        $options.each(function (index) {
          var $option = $(this), url = $(this).val();
          $option.attr('selected', url in selected);
          selected[url] = true;
        });
        // Add and select chosen datasources that were not in the list yet
        $datasources.append($.map(selected, function (exists, url) {
          return exists ? null : $('<option>', { text: url, value: url, selected: true });
        })).trigger('chosen:updated');
        // Update the query set
        this._loadQueries(value);
        break;
      // Set the query
      case 'query':
        this.$query.val(value).change();
        break;
      // Set the list of all possible queries
      case 'queries':
        this._loadQueries(options.selectedDatasources);
        break;
      // Set the list of selectable queries
      case 'relevantQueries':
        value = value || [];
        // Load the first selectable query if the current query was not edited and not in the list
        if (!this.$query.edited && !value.some(function (v) { return v.sparql === options.query; }))
          value[0] && this._setOption('query', value[0].sparql);
        // Update the selectable query list
        $queries.empty().append($('<option>'), value.map(function (query) {
          return $('<option>', { text: query.name, value: query.sparql,
                                 selected: options.query === query.sparql });
        })).trigger('chosen:updated').change();
        break;
      // Load settings from a JSON resource
      case 'settings':
        $.getJSON(value, function (settings) {
          for (var key in settings)
            self._setOption(key, settings[key]);
        });
        break;
      }
    },

    // Load queries relevant for the given datasources
    _loadQueries: function (datasources) {
      datasources = toHash(datasources);
      var queries = (this.options.queries || []).filter(function (query, index) {
        query.id = index;
        // Include the query if it indicates no datasources,
        // or if it is relevant for at least one datasource
        return !query.datasources || !query.datasources.length ||
               query.datasources.some(function (d) { return d in datasources; });
      });

      // Load the set of queries if it is different from the current set
      var querySetId = queries.map(function (q) { return q.id; }).join();
      if (this._querySetId !== querySetId) {
        this._querySetId = querySetId;
        this._setOption('relevantQueries', queries);
        this.element.trigger('changeQuerySet');
      }
    },

    // Starts query execution
    _execute: function () {
     var datasources = this.$datasources.val();
      if (!datasources || !datasources.length)
        return alert('Please choose a datasource to execute the query.');

      // Clear results and log, and scroll page to the results
      var $results = this.$results, $log = this.$log;
      $('html,body').animate({ scrollTop: this.$start.offset().top });
      this.$stop.show();
      this.$start.hide();
      $log.empty();
      $results.empty();

      // Create a client to fetch the fragments through HTTP
      var config = { prefixes: this.options.prefixes, logger: this._logger };
      config.fragmentsClient = new ldf.FragmentsClient(datasources, config);

      // Create the iterator to solve the query
      var resultsIterator;
      try { resultsIterator = new ldf.SparqlIterator(this.$query.val(), config); }
      catch (error) { return this._stopExecution(error); }
      this._resultsIterator = resultsIterator;
      resultsIterator.on('end', $.proxy(this._stopExecution, this));
      resultsIterator.on('error', $.proxy(this._stopExecution, this));

      // Read the iterator's results, and write them depending on the query type
      switch (resultsIterator.queryType) {
        // For SELECT queries, write a JSON array representation of the rows
        case 'SELECT':
          var resultCount = 0;
          resultsIterator.on('data', function (row) {
            resultCount++;
            var lines = [];
            $.each(row, function (k, v) { if (v !== undefined) lines.push(k + ': ' + v); });
            appendText($results, lines.join('\n'), '\n\n');
          });
          resultsIterator.on('end', function () {
            resultCount || appendText($results, '(This query has no results.)');
          });
          break;
        // For CONSTRUCT and DESCRIBE queries, write a Turtle representation of all results
        case 'CONSTRUCT':
        case 'DESCRIBE':
          var writer = new N3.Writer({ write: function (chunk, encoding, done) {
            appendText($results, chunk), done && done();
          }}, config);
          resultsIterator.on('data', function (triple) { writer.addTriple(triple); })
                         .on('end',  function () { writer.end(); });
          break;
        // For ASK queries, write whether an answer exists
        case 'ASK':
          resultsIterator.on('data', function (exists) { appendText($results, exists); });
          break;
        default:
          appendText($log, 'Unsupported query type: ' + resultsIterator.queryType);
      }
    },

    // Stops query execution
    _stopExecution: function (error) {
      this.$stop.hide();
      this.$start.show();
      this._resultsIterator && this._resultsIterator.removeAllListeners();
      ldf.HttpClient.abortAll && ldf.HttpClient.abortAll();
      error && error.message && this.$results.text(error.message);
    },
  });

  // Appends text to the given element
  function appendText($element) {
    for (var i = 1, l = arguments.length; i < l; i++)
      $element.append((arguments[i] + '').replace(/(<)|(>)|(&)|(https?:\/\/[^\s<>]+)/g, escape));
    $element.scrollTop(1E10);
  }

  // Escapes special HTML characters and convert URLs into links
  function escape(match, lt, gt, amp, url) {
    return lt && '<' || gt && '>' || amp && '&' ||
           $('<a>', { href: url, target: '_blank', text: url })[0].outerHTML;
  }

  // Converts the array to a hash with the elements as keys
  function toHash(array) {
    var hash = {}, length = array ? array.length : 0;
    for (var i = 0; i < length; i++)
      hash[array[i]] = false;
    return hash;
  }
})(jQuery);
<|endoftext|>


<commit_before>var request = require('supertest');
var app = require('./mb-logging-api-server');

request(app)
  .get('/')
  .expect(200)
  .end(function(error) {
    if (error) throw error;
    console.log('Done');
  });<commit_msg>Adjust test to use Mocha - sexy<commit_after>var request = require('supertest');
var app = require('./mb-logging-api-server');

describe('Requests to the root (/api) path', function() {

  it('Returns a 200 status code', function(done) {

    request(app)
      .get('/api')
      .expect(200)
      .end(function(error) {
        if (error) throw error;
        done();
      });
    });

});
<|endoftext|>


<commit_before>// @flow
import * as t from "babel-types"
import { parse } from "messageformat-parser"
import { parseExpression } from "babylon"
import generate from "babel-generator"
import plurals from "make-plural"
import R from "ramda"

import type { CatalogType } from "./formats/types"

const isString = s => typeof s === "string"

export function compile(message: string) {
  function processTokens(tokens) {
    if (!tokens.filter(token => !isString(token)).length) {
      return tokens.join("")
    }

    return t.arrayExpression(
      tokens.map(token => {
        if (isString(token)) {
          return t.stringLiteral(token)

          // # in plural case
        } else if (token.type === "octothorpe") {
          return t.stringLiteral("#")

          // simple argument
        } else if (token.type === "argument") {
          return t.callExpression(arg, [t.stringLiteral(token.arg)])

          // argument with custom format (date, number)
        } else if (token.type === "function") {
          const params = [
            t.stringLiteral(token.arg),
            t.stringLiteral(token.key)
          ]

          const format = token.params[0]
          if (format) {
            params.push(t.stringLiteral(format))
          }
          return t.callExpression(arg, params)
        }

        // complex argument with cases
        const formatProps = []

        if (token.offset) {
          formatProps.push(
            t.objectProperty(
              t.identifier("offset"),
              t.numericLiteral(parseInt(token.offset))
            )
          )
        }

        token.cases.forEach(item => {
          const inlineTokens = processTokens(item.tokens)
          formatProps.push(
            t.objectProperty(
              t.identifier(item.key),
              isString(inlineTokens)
                ? t.stringLiteral(inlineTokens)
                : inlineTokens
            )
          )
        })

        const params = [
          t.stringLiteral(token.arg),
          t.stringLiteral(token.type),
          t.objectExpression(formatProps)
        ]

        return t.callExpression(arg, params)
      })
    )
  }

  const arg = t.identifier("a")

  const tokens = parse(message)
  const ast = processTokens(tokens)

  if (isString(ast)) return t.stringLiteral(ast)

  return t.functionExpression(
    null,
    [arg],
    t.blockStatement([t.returnStatement(ast)])
  )
}

export function createCompiledCatalog(locale: string, messages: CatalogType) {
  const [language] = locale.split("_")
  const pluralRules = plurals[language]

  const compiledMessages = R.keys(messages).map(key => {
    const translation = messages[key]
    return t.objectProperty(t.stringLiteral(key), compile(translation || key))
  })

  const languageData = [
    t.objectProperty(
      t.stringLiteral("plurals"),
      parseExpression(pluralRules.toString())
    )
  ]

  const ast = t.expressionStatement(
    t.assignmentExpression(
      "=",
      t.memberExpression(t.identifier("module"), t.identifier("exports")),
      t.objectExpression([
        // language data
        t.objectProperty(
          t.identifier("languageData"),
          t.objectExpression(languageData)
        ),
        // messages
        t.objectProperty(
          t.identifier("messages"),
          t.objectExpression(compiledMessages)
        )
      ])
    )
  )

  return generate(ast, {
    minified: true
  }).code
}
<commit_msg>fix: use babel7 packages<commit_after>// @flow
import * as t from "@babel/types"
import { parse } from "messageformat-parser"
import { parseExpression } from "babylon"
import generate from "@babel/generator"
import plurals from "make-plural"
import R from "ramda"

import type { CatalogType } from "./formats/types"

const isString = s => typeof s === "string"

export function compile(message: string) {
  function processTokens(tokens) {
    if (!tokens.filter(token => !isString(token)).length) {
      return tokens.join("")
    }

    return t.arrayExpression(
      tokens.map(token => {
        if (isString(token)) {
          return t.stringLiteral(token)

          // # in plural case
        } else if (token.type === "octothorpe") {
          return t.stringLiteral("#")

          // simple argument
        } else if (token.type === "argument") {
          return t.callExpression(arg, [t.stringLiteral(token.arg)])

          // argument with custom format (date, number)
        } else if (token.type === "function") {
          const params = [
            t.stringLiteral(token.arg),
            t.stringLiteral(token.key)
          ]

          const format = token.params[0]
          if (format) {
            params.push(t.stringLiteral(format))
          }
          return t.callExpression(arg, params)
        }

        // complex argument with cases
        const formatProps = []

        if (token.offset) {
          formatProps.push(
            t.objectProperty(
              t.identifier("offset"),
              t.numericLiteral(parseInt(token.offset))
            )
          )
        }

        token.cases.forEach(item => {
          const inlineTokens = processTokens(item.tokens)
          formatProps.push(
            t.objectProperty(
              t.identifier(item.key),
              isString(inlineTokens)
                ? t.stringLiteral(inlineTokens)
                : inlineTokens
            )
          )
        })

        const params = [
          t.stringLiteral(token.arg),
          t.stringLiteral(token.type),
          t.objectExpression(formatProps)
        ]

        return t.callExpression(arg, params)
      })
    )
  }

  const arg = t.identifier("a")

  const tokens = parse(message)
  const ast = processTokens(tokens)

  if (isString(ast)) return t.stringLiteral(ast)

  return t.functionExpression(
    null,
    [arg],
    t.blockStatement([t.returnStatement(ast)])
  )
}

export function createCompiledCatalog(locale: string, messages: CatalogType) {
  const [language] = locale.split("_")
  const pluralRules = plurals[language]

  const compiledMessages = R.keys(messages).map(key => {
    const translation = messages[key]
    return t.objectProperty(t.stringLiteral(key), compile(translation || key))
  })

  const languageData = [
    t.objectProperty(
      t.stringLiteral("plurals"),
      parseExpression(pluralRules.toString())
    )
  ]

  const ast = t.expressionStatement(
    t.assignmentExpression(
      "=",
      t.memberExpression(t.identifier("module"), t.identifier("exports")),
      t.objectExpression([
        // language data
        t.objectProperty(
          t.identifier("languageData"),
          t.objectExpression(languageData)
        ),
        // messages
        t.objectProperty(
          t.identifier("messages"),
          t.objectExpression(compiledMessages)
        )
      ])
    )
  )

  return generate(ast, {
    minified: true
  }).code
}
<|endoftext|>


<commit_before>var TCM = TCM || {};

(function($) {

    // Filtering, autocomplete, and fake placeholder text for manage and results pages

    var formOptionsFilter = function(context_sel, data_attr, trigger_sel, target_sel) {
        var context = $(context_sel),
        trigger = context.find(trigger_sel);
        if (context.length && trigger.is("select")) {
            var target = context.find(target_sel),
            allopts = target.find("option").clone();

            var doFilter = function() {
                var key = trigger.find("option:selected").data(data_attr);
                target.empty();
                allopts.each(function() {
                    if ($(this).data(data_attr) === key) {
                        var newopt = $(this).clone();
                        newopt.appendTo(target);
                    }
                });
            };

            doFilter();

            trigger.change(doFilter);
        }
    },

    filtering = function() {
        var formActions = $('#filter .form-actions').hide(),
            input = $('#filter .visual .filter-group input[type="checkbox"]').each(function() {
                $(this).data('originallyChecked', $(this).is(':checked'));
            }),
            textbox = $('#filter .textual #text-filter'),
            typedText = textbox.val(),
            placeholder = textbox.attr('placeholder'),
            suggestionList = $('#filter .textual .suggest').hide(),
            keywordGroups = $('#filter .visual .filter-group.keyword'),
            notKeywordGroups = $('#filter .visual .filter-group:not(.keyword)'),
            selected,
            removeFakePlaceholder = function() {
                if (textbox.val().indexOf(placeholder) !== -1) {
                    textbox.val(null);
                }
                textbox.removeClass('placeholder');
            },
            updateFormActions = function() {
                if (input.filter(function() { return $(this).data('state') === 'changed'; }).length) {
                    formActions.fadeIn('fast');
                    $('.managelist').addClass('expired');
                } else {
                    formActions.fadeOut('fast');
                    $('.managelist').removeClass('expired');
                }
            };

        formActions.find('.reset').click(function() {
            formActions.fadeOut('fast');
            $('.managelist').removeClass('expired');
            input.each(function() {
                $(this).data('state', null);
                $(this).attr('checked', $(this).data('originallyChecked'));
            });
            return false;
        });

        input.live('change', function() {
            if ($(this).data('originallyChecked') !== $(this).is(':checked')) {
                $(this).data('state', 'changed');
            } else {
                $(this).data('state', null);
            }
            updateFormActions();
        });

        textbox.keyup(function(event) {
            if ($(this).val() !== typedText && $(this).val() !== placeholder) {
                typedText = $(this).val();
                suggestionList.empty();
                if ($(this).val().length) {
                    var relevantFilters = notKeywordGroups.find('input[type="checkbox"]:not(:checked)').parent('li').filter(function() {
                            return $(this).children('label').html().toLowerCase().indexOf(typedText.toLowerCase()) !== -1;
                        });
                    relevantFilters.each(function() {
                        var typedIndex = $(this).children('label').html().toLowerCase().indexOf(typedText.toLowerCase()),
                            preText = $(this).children('label').html().substring(0, typedIndex),
                            postText = $(this).children('label').html().substring(typedIndex + typedText.length),
                            type = $(this).children('input').attr('name'),
                            id = $(this).children('input').attr('id'),
                            newHTML = '
' + preText + '' + typedText + '' + postText + ' [' + type + ']
';
                        suggestionList.append(newHTML);
                    });
                    keywordGroups.each(function() {
                        var type = $(this).children('h5').html(),
                            name = $(this).data('name'),
                            keywordHTML = '
' + typedText + ' [' + type + ']
';
                        if ($(this).find('input[type="checkbox"]:checked').length) {
                            if ($(this).find('input[type="checkbox"][value^="^"][value$="$"]:checked').length === $(this).find('input[type="checkbox"]:checked').length) {
                                if (!($(this).find('input[type="checkbox"][value="' + typedText + '"]:checked').length) && typedText.indexOf('^') === 0 && typedText.lastIndexOf('$') === typedText.length - 1) {
                                    suggestionList.append(keywordHTML);
                                }
                            }
                        } else {
                            suggestionList.append(keywordHTML);
                        }
                    });
                    suggestionList.find('li:first-child a').addClass('selected');
                }
            }
        }).keydown(function(event) {
            if (textbox.hasClass('placeholder')) {
                if (!event.metaKey && event.keyCode !== 16 && event.keyCode !== 17 && event.keyCode !== 18 && event.keyCode !== 20 && event.keyCode !== 27) {
                    removeFakePlaceholder();
                }
            } else {
                if (event.keyCode === 38) {
                    event.preventDefault();
                    if (!suggestionList.find('.selected').parent().is(':first-child')) {
                        suggestionList.find('.selected').removeClass('selected').parent().prev().children('a').addClass('selected');
                    }
                    return false;
                }
                if (event.keyCode === 40) {
                    event.preventDefault();
                    if (!suggestionList.find('.selected').parent().is(':last-child')) {
                        suggestionList.find('.selected').removeClass('selected').parent().next().children('a').addClass('selected');
                    }
                    return false;
                }
                if (event.keyCode === 13) {
                    event.preventDefault();
                    if (textbox.val() === '' && $('.managelist').hasClass('expired')) {
                        formActions.find('button[type="submit"]').click();
                    } else {
                        suggestionList.find('.selected').click();
                        suggestionList.show();
                    }
                    return false;
                }
                if (event.keyCode === 9) {
                    var thisFilterName = input.filter('#' + suggestionList.find('.selected').data('id')).siblings('label').html();
                    if (thisFilterName && textbox.val() !== thisFilterName) {
                        event.preventDefault();
                        textbox.val(thisFilterName);
                        return false;
                    } else {
                        if (suggestionList.find('.selected').length) {
                            event.preventDefault();
                            suggestionList.find('.selected').click();
                            suggestionList.show();
                            return false;
                        }
                    }
                }
                return true;
            }
        }).click(function() {
            if (textbox.hasClass('placeholder')) {
                removeFakePlaceholder();
            }
        }).focus(function() {
            suggestionList.show();
            textbox.data('clicked', false);
            if (textbox.val().length === 0 && textbox.hasClass('placeholder')) {
                textbox.val(placeholder);
                textbox.get(0).setSelectionRange(0, 0);
            }
        }).blur(function() {
            function hideList() {
                if (textbox.data('clicked') !== true) {
                    suggestionList.hide();
                }
            }
            removeFakePlaceholder();
            window.setTimeout(hideList, 150);
        }).addClass('placeholder').focus();

        suggestionList.hover(function() {
            selected = $(this).find('.selected').removeClass('selected');
        }, function() {
            selected.addClass('selected');
        }).find('a').live({
            mousedown: function() {
                textbox.data('clicked', true);
            },
            click: function() {
                if ($(this).data('class') === 'keyword') {
                    var name = $(this).data('name'),
                        thisGroup = keywordGroups.filter(function() {
                            return $(this).data('name') === name;
                        }),
                        existingKeyword = thisGroup.find('input[type="checkbox"][value="' + typedText + '"][name="' + name + '"]'),
                        index = thisGroup.find('li').length + 1,
                        newHTML =
                            '
' +
                                '<input type="checkbox" name="' + name + '" value="' + typedText + '" id="id-' + name + '-' + index + '" checked="checked" data-state="changed" data-originallyChecked="false">' +
                                '' + typedText + '' +
                            '
';
                    if (existingKeyword.length) {
                        existingKeyword.attr('checked', 'checked');
                        if (existingKeyword.data('originallyChecked') !== existingKeyword.is(':checked')) {
                            existingKeyword.data('state', 'changed');
                        }
                    } else {
                        thisGroup.removeClass('empty').children('ul').append(newHTML);
                        input = input.add('#id-' + name + '-' + index);
                    }
                } else {
                    var thisFilter = input.filter('#' + $(this).data('id')).attr('checked', 'checked');
                    if (thisFilter.data('originallyChecked') !== thisFilter.is(':checked')) {
                        thisFilter.data('state', 'changed');
                    }
                }
                updateFormActions();
                textbox.data('clicked', false).val(null);
                typedText = null;
                suggestionList.empty().hide();
                return false;
            }
        });
    },

    listDetails = function() {
        $('#listcontent .items .item.details').live(
            'click',
            function(event) {
                if ($(event.target).is("button, a")) {
                    return;
                }
                var item = $(this),
                content = item.find('.content'),
                url = item.data('details-url');
                if (url && !content.hasClass('loaded')) {
                    content.css('min-height', '4.854em').addClass('loaded');
                    content.loadingOverlay();
                    $.get(url,
                          function(data) {
                              content.loadingOverlay('remove');
                              content.html(data.html);
                          });
                }
            });
    },

    manageActionsAjax = function() {
        $('.manage button[name^=action-]').live(
            'click',
            function() {
                var button = $(this),
                form = button.closest('form'),
                token = form.find('input[name=csrfmiddlewaretoken]'),
                url = form.attr('action'),
                method = form.attr('method'),
                replace = button.closest('.action-ajax-replace'),
                success = function(data) {
                    var replacement = $(data.html);
                    replace.replaceWith(replacement);
                    replacement.find('.details').html5accordion('.summary');
                    replace.loadingOverlay('remove');
                },
                data = {};
                data[button.attr('name')] = button.val();
                data['csrfmiddlewaretoken'] = token.val();
                replace.loadingOverlay();
                $.ajax(url, {
                           type: method,
                           data: data,
                           success: success
                       });
                return false;
            }
        );
    };

    $(function() {
        filtering();
        listDetails();
        manageActionsAjax();
        $('.details:not(html)').html5accordion('.summary');
        $('#messages').messages({handleAjax: true});
        $('input[placeholder], textarea[placeholder]').placeholder();
        $('input:not([type=radio], [type=checkbox]), textarea').blur(
            function() {
                $(this).addClass('hadfocus');
            }
        );
        $('#filter .toggle a').click(
            function() {
                $('#filter .visual').toggleClass('compact').toggleClass('expanded');
                return false;
            }
        );
        $('.subnav .findertoggle').click(function() {
            $(this).add('.subnav .finder').toggleClass('expanded').toggleClass('compact');
            return false;
        });
        formOptionsFilter("#addsuite", "product-id", "#id_product", "#id_cases");
        formOptionsFilter("#addrun", "product-id", "#id_test_cycle", "#id_suites");
        $('.subnav').html5finder( {
            loading: true,
            ellipsis: true,
            horizontalScroll: true,
            scrollContainer: '.finder.expanded',
            headerSelector: '.listordering',
            sectionSelector: '.col',
            sectionContentSelector: '.colcontent',
            sectionClasses: [
                'products',
                'cycles',
                'runs',
                'cases',
                'results'
            ],
            sectionItemSelectors: [
                'input[name="product"]',
                'input[name="testcycle"]',
                'input[name="testrun"]',
                'input[name="testcase"]',
                'input[name="testresult"]'
            ]
        });
        $('.selectruns').html5finder( {
            loading: true,
            ellipsis: true,
            headerSelector: '.listordering',
            sectionSelector: '.col',
            sectionContentSelector: '.colcontent',
            sectionClasses: [
                'products',
                'cycles',
                'runs'
            ],
            sectionItemSelectors: [
                'input[name="product"]',
                'input[name="testcycle"]',
                'input[name="testrun"]'
            ],
            callback: function() {
                $('.selectruns + .environment').slideUp('fast');
            },
            lastChildCallback: function(choice) {
                var environments = $('.selectruns + .environment').css('min-height', '169px').slideDown('fast'),
                    ajaxUrl = $(choice).data("sub-url");
                environments.loadingOverlay();
                $.get(ajaxUrl, function(data) {
                    environments.loadingOverlay('remove');
                    environments.html(data.html);
                });
            }
        });
        $('.selectruns + .environment.empty').hide();
    });

    $(window).load(function() {
        $('#listcontent .items').find('.title, .product, .cycle, .run').ellipsis(true, 300);
        if ($('.manage').length && window.location.hash) {
            var hash = window.location.hash;
            $(hash).children('.summary').click();
        }
    });

})(jQuery);
<commit_msg>JS filtering UI improvements (textbox maintains keyboard focus)<commit_after>var TCM = TCM || {};

(function($) {

    // Filtering, autocomplete, and fake placeholder text for manage and results pages

    var formOptionsFilter = function(context_sel, data_attr, trigger_sel, target_sel) {
        var context = $(context_sel),
        trigger = context.find(trigger_sel);
        if (context.length && trigger.is("select")) {
            var target = context.find(target_sel),
            allopts = target.find("option").clone();

            var doFilter = function() {
                var key = trigger.find("option:selected").data(data_attr);
                target.empty();
                allopts.each(function() {
                    if ($(this).data(data_attr) === key) {
                        var newopt = $(this).clone();
                        newopt.appendTo(target);
                    }
                });
            };

            doFilter();

            trigger.change(doFilter);
        }
    },

    filtering = function() {
        var keycodes = {
            SPACE: 32,
            ENTER: 13,
            TAB: 9,
            ESC: 27,
            BACKSPACE: 8,
            SHIFT: 16,
            CTRL: 17,
            ALT: 18,
            CAPS: 20,
            LEFT: 37,
            UP: 38,
            RIGHT: 39,
            DOWN: 40
        },
        formActions = $('#filter .form-actions').hide(),
        toggle = $('#filter .toggle a'),
        input = $('#filter .visual .filter-group input[type="checkbox"]').each(function() {
            $(this).data('originallyChecked', $(this).is(':checked'));
        }),
        textbox = $('#filter .textual #text-filter'),
        typedText,
        placeholder = textbox.attr('placeholder'),
        suggestionList = $('#filter .textual .suggest').hide(),
        keywordGroups = $('#filter .visual .filter-group.keyword'),
        notKeywordGroups = $('#filter .visual .filter-group:not(.keyword)'),
        selected,
        removeFakePlaceholder = function() {
            if (textbox.val().indexOf(placeholder) !== -1) {
                textbox.val(null);
            }
            textbox.removeClass('placeholder');
        },
        updateFormActions = function() {
            if (input.filter(function() { return $(this).data('state') === 'changed'; }).length) {
                formActions.fadeIn('fast');
                $('.managelist').addClass('expired');
            } else {
                formActions.fadeOut('fast');
                $('.managelist').removeClass('expired');
            }
        },
        updateSuggestions = function() {
            typedText = textbox.val();
            suggestionList.empty();
            if (textbox.val().length) {
                var relevantFilters = notKeywordGroups.find('input[type="checkbox"]:not(:checked)').parent('li').filter(function() {
                        return $(this).children('label').html().toLowerCase().indexOf(typedText.toLowerCase()) !== -1;
                    });
                relevantFilters.each(function() {
                    var typedIndex = $(this).children('label').html().toLowerCase().indexOf(typedText.toLowerCase()),
                        preText = $(this).children('label').html().substring(0, typedIndex),
                        postText = $(this).children('label').html().substring(typedIndex + typedText.length),
                        type = $(this).children('input').attr('name'),
                        id = $(this).children('input').attr('id'),
                        newHTML = '
' + preText + '' + typedText + '' + postText + ' [' + type + ']
';
                    suggestionList.append(newHTML);
                });
                keywordGroups.each(function() {
                    var type = $(this).children('h5').html(),
                        name = $(this).data('name'),
                        keywordHTML = '
' + typedText + ' [' + type + ']
';
                    if ($(this).find('input[type="checkbox"]:checked').length) {
                        if ($(this).find('input[type="checkbox"][value^="^"][value$="$"]:checked').length === $(this).find('input[type="checkbox"]:checked').length) {
                            if (!($(this).find('input[type="checkbox"][value="' + typedText + '"]:checked').length) && typedText.indexOf('^') === 0 && typedText.lastIndexOf('$') === typedText.length - 1) {
                                suggestionList.append(keywordHTML);
                            }
                        }
                    } else {
                        suggestionList.append(keywordHTML);
                    }
                });
                suggestionList.find('li:first-child a').addClass('selected');
            }
        };

        toggle.click(function() {
            $('#filter .visual').toggleClass('compact').toggleClass('expanded');
            return false;
        });

        formActions.find('.reset').click(function() {
            formActions.fadeOut('fast');
            $('.managelist').removeClass('expired');
            input.each(function() {
                $(this).data('state', null);
                $(this).prop('checked', $(this).data('originallyChecked'));
            });
            textbox.focus();
            suggestionList.hide();
            return false;
        });

        input.live('change', function() {
            if ($(this).data('originallyChecked') !== $(this).is(':checked')) {
                $(this).data('state', 'changed');
            } else {
                $(this).data('state', null);
            }
            textbox.focus();
            suggestionList.hide();
            updateFormActions();
        });

        textbox.keyup(function(event) {
            if ($(this).val() !== typedText && $(this).val() !== placeholder) {
                updateSuggestions();
            }
        }).keydown(function(event) {
            if (textbox.hasClass('placeholder')) {
                if (!event.metaKey && event.keyCode !== keycodes.SHIFT && event.keyCode !== keycodes.CTRL && event.keyCode !== keycodes.ALT && event.keyCode !== keycodes.CAPS && event.keyCode !== keycodes.ESC) {
                    removeFakePlaceholder();
                }
            }
            if (suggestionList.is(':visible')) {
                if (event.keyCode === keycodes.UP) {
                    event.preventDefault();
                    if (!suggestionList.find('.selected').parent().is(':first-child')) {
                        suggestionList.find('.selected').removeClass('selected').parent().prev().children('a').addClass('selected');
                    }
                    return false;
                }
                if (event.keyCode === keycodes.DOWN) {
                    event.preventDefault();
                    if (!suggestionList.find('.selected').parent().is(':last-child')) {
                        suggestionList.find('.selected').removeClass('selected').parent().next().children('a').addClass('selected');
                    }
                    return false;
                }
                if (event.keyCode === keycodes.ENTER) {
                    event.preventDefault();
                    if (textbox.val() === '' && $('.managelist').hasClass('expired')) {
                        formActions.find('button[type="submit"]').click();
                    } else {
                        suggestionList.find('.selected').click();
                        suggestionList.show();
                    }
                    return false;
                }
                if (event.keyCode === keycodes.TAB) {
                    var thisFilterName = input.filter('#' + suggestionList.find('.selected').data('id')).siblings('label').html();
                    if (thisFilterName && textbox.val() !== thisFilterName) {
                        event.preventDefault();
                        textbox.val(thisFilterName);
                        return false;
                    } else {
                        if (suggestionList.find('.selected').length) {
                            event.preventDefault();
                            suggestionList.find('.selected').click();
                            return false;
                        }
                    }
                }
                if (event.keyCode === keycodes.ESC) {
                    event.preventDefault();
                    suggestionList.hide();
                    return false;
                }
                return true;
            } else {
                if (event.keyCode === keycodes.TAB) {
                    event.preventDefault();
                }
                suggestionList.show();
                updateSuggestions();
            }
        }).click(function() {
            if (textbox.hasClass('placeholder')) {
                removeFakePlaceholder();
            }
        }).focus(function() {
            textbox.data('clicked', false);
        }).blur(function() {
            function hideList() {
                if (textbox.data('clicked') !== true) {
                    suggestionList.hide();
                    textbox.data('clicked', false);
                }
            }
            removeFakePlaceholder();
            window.setTimeout(hideList, 150);
        }).addClass('placeholder').focus().val(placeholder).get(0).setSelectionRange(0, 0);

        suggestionList.hover(function() {
            selected = $(this).find('.selected').removeClass('selected');
        }, function() {
            selected.addClass('selected');
        }).find('a').live({
            mousedown: function() {
                textbox.data('clicked', true);
            },
            click: function() {
                if ($(this).data('class') === 'keyword') {
                    var name = $(this).data('name'),
                        thisGroup = keywordGroups.filter(function() {
                            return $(this).data('name') === name;
                        }),
                        existingKeyword = thisGroup.find('input[type="checkbox"][value="' + typedText + '"][name="' + name + '"]'),
                        index = thisGroup.find('li').length + 1,
                        newHTML =
                            '
' +
                                '<input type="checkbox" name="' + name + '" value="' + typedText + '" id="id-' + name + '-' + index + '">' +
                                '' + typedText + '' +
                            '
';
                    if (existingKeyword.length) {
                        existingKeyword.prop('checked', true);
                        if (existingKeyword.data('originallyChecked') !== existingKeyword.is(':checked')) {
                            existingKeyword.data('state', 'changed');
                        }
                    } else {
                        thisGroup.removeClass('empty').children('ul').append(newHTML);
                        $('#id-' + name + '-' + index).data('state', 'changed').data('originallyChecked', false).prop('checked', true);
                        input = input.add('#id-' + name + '-' + index);
                    }
                } else {
                    var thisFilter = input.filter('#' + $(this).data('id')).prop('checked', true);
                    if (thisFilter.data('originallyChecked') !== thisFilter.is(':checked')) {
                        thisFilter.data('state', 'changed');
                    }
                }
                updateFormActions();
                textbox.val(null);
                typedText = null;
                suggestionList.empty().hide();
                return false;
            }
        });
    },

    listDetails = function() {
        $('#listcontent .items .item.details').live(
            'click',
            function(event) {
                if ($(event.target).is("button, a")) {
                    return;
                }
                var item = $(this),
                content = item.find('.content'),
                url = item.data('details-url');
                if (url && !content.hasClass('loaded')) {
                    content.css('min-height', '4.854em').addClass('loaded');
                    content.loadingOverlay();
                    $.get(url,
                          function(data) {
                              content.loadingOverlay('remove');
                              content.html(data.html);
                          });
                }
            });
    },

    manageActionsAjax = function() {
        $('.manage button[name^=action-]').live(
            'click',
            function() {
                var button = $(this),
                form = button.closest('form'),
                token = form.find('input[name=csrfmiddlewaretoken]'),
                url = form.attr('action'),
                method = form.attr('method'),
                replace = button.closest('.action-ajax-replace'),
                success = function(data) {
                    var replacement = $(data.html);
                    replace.replaceWith(replacement);
                    replacement.find('.details').html5accordion('.summary');
                    replace.loadingOverlay('remove');
                },
                data = {};
                data[button.attr('name')] = button.val();
                data['csrfmiddlewaretoken'] = token.val();
                replace.loadingOverlay();
                $.ajax(url, {
                           type: method,
                           data: data,
                           success: success
                       });
                return false;
            }
        );
    };

    $(function() {
        filtering();
        listDetails();
        manageActionsAjax();
        $('.details:not(html)').html5accordion('.summary');
        $('#messages').messages({handleAjax: true});
        $('input[placeholder], textarea[placeholder]').placeholder();
        $('input:not([type=radio], [type=checkbox]), textarea').blur(
            function() {
                $(this).addClass('hadfocus');
            }
        );
        $('#filter .toggle a').click(
            function() {
                $('#filter .visual').toggleClass('compact').toggleClass('expanded');
                return false;
            }
        );
        $('.subnav .findertoggle').click(function() {
            $(this).add('.subnav .finder').toggleClass('expanded').toggleClass('compact');
            return false;
        });
        formOptionsFilter("#addsuite", "product-id", "#id_product", "#id_cases");
        formOptionsFilter("#addrun", "product-id", "#id_test_cycle", "#id_suites");
        $('.subnav').html5finder( {
            loading: true,
            ellipsis: true,
            horizontalScroll: true,
            scrollContainer: '.finder.expanded',
            headerSelector: '.listordering',
            sectionSelector: '.col',
            sectionContentSelector: '.colcontent',
            sectionClasses: [
                'products',
                'cycles',
                'runs',
                'cases',
                'results'
            ],
            sectionItemSelectors: [
                'input[name="product"]',
                'input[name="testcycle"]',
                'input[name="testrun"]',
                'input[name="testcase"]',
                'input[name="testresult"]'
            ]
        });
        $('.selectruns').html5finder( {
            loading: true,
            ellipsis: true,
            headerSelector: '.listordering',
            sectionSelector: '.col',
            sectionContentSelector: '.colcontent',
            sectionClasses: [
                'products',
                'cycles',
                'runs'
            ],
            sectionItemSelectors: [
                'input[name="product"]',
                'input[name="testcycle"]',
                'input[name="testrun"]'
            ],
            callback: function() {
                $('.selectruns + .environment').slideUp('fast');
            },
            lastChildCallback: function(choice) {
                var environments = $('.selectruns + .environment').css('min-height', '169px').slideDown('fast'),
                    ajaxUrl = $(choice).data("sub-url");
                environments.loadingOverlay();
                $.get(ajaxUrl, function(data) {
                    environments.loadingOverlay('remove');
                    environments.html(data.html);
                });
            }
        });
        $('.selectruns + .environment.empty').hide();
    });

    $(window).load(function() {
        $('#listcontent .items').find('.title, .product, .cycle, .run').ellipsis(true, 300);
        if ($('.manage').length && window.location.hash) {
            var hash = window.location.hash;
            $(hash).children('.summary').click();
        }
    });

})(jQuery);
<|endoftext|>