diff --git a/_meta/meta.yaml b/_meta/meta.yaml index 9e7dda6..010d8b5 100644 --- a/_meta/meta.yaml +++ b/_meta/meta.yaml @@ -1,6 +1,4 @@ -extras: - indico-plugin-importer: importer - indico-plugin-importer-invenio: importer +extras: {} skip: - indico-plugin-livesync-debug diff --git a/_meta/setup.py b/_meta/setup.py index 7b33ec4..211187e 100644 --- a/_meta/setup.py +++ b/_meta/setup.py @@ -19,14 +19,11 @@ plugins_require = [ 'indico-plugin-piwik>=2.3,<2.4.dev0', 'indico-plugin-previewer-code>=1.0,<2.4.dev0', 'indico-plugin-previewer-jupyter>=1.0,<2.4.dev0', - 'indico-plugin-search>=2.3,<2.4.dev0', 'indico-plugin-storage-s3>=2.3,<2.4.dev0', 'indico-plugin-ursh>=2.3,<2.4.dev0', 'indico-plugin-vc-vidyo>=2.3,<2.4.dev0', ] -extras_require = { - 'importer': ['indico-plugin-importer-invenio>=2.2,<2.4.dev0', 'indico-plugin-importer>=2.2,<2.4.dev0'], -} +extras_require = {} # END GENERATED REQUIREMENTS diff --git a/importer/MANIFEST.in b/importer/MANIFEST.in deleted file mode 100644 index c998b51..0000000 --- a/importer/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -graft indico_importer/static -graft indico_importer/translations - -global-exclude *.pyc __pycache__ .keep diff --git a/importer/indico_importer/__init__.py b/importer/indico_importer/__init__.py deleted file mode 100644 index 55a1b99..0000000 --- a/importer/indico_importer/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from indico.util.i18n import make_bound_gettext - - -_ = make_bound_gettext('importer') -__all__ = ('ImporterSourcePluginBase', 'ImporterEngineBase') - - -from .base import ImporterSourcePluginBase, ImporterEngineBase # isort:skip diff --git a/importer/indico_importer/base.py b/importer/indico_importer/base.py deleted file mode 100644 index e9fbee3..0000000 --- a/importer/indico_importer/base.py +++ /dev/null @@ -1,67 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from flask_pluginengine import depends - -from indico.core.plugins import IndicoPlugin, PluginCategory - -from indico_importer.plugin import ImporterPlugin - - -@depends('importer') -class ImporterSourcePluginBase(IndicoPlugin): - """Base class for importer engine plugins""" - - importer_engine_classes = None - category = PluginCategory.importers - - def init(self): - super(ImporterSourcePluginBase, self).init() - for engine_class in self.importer_engine_classes: - importer_engine = engine_class() - ImporterPlugin.instance.register_importer_engine(importer_engine, self) - - -class ImporterEngineBase(object): - """Base class for data importers""" - - _id = '' - name = '' - - def import_data(self, query, size): - """Fetch and converts data from external source. - - :param query: A search phrase send to the importer. - :param size: Number of records to fetch from external source. - :return: List of dictionaries with the following format (all keys are optional) - [{"recordId" : idOfTheRecordInExternalSource, - "title": eventTitle, - "primaryAuthor": {"firstName": primaryAuthorFirstName, - "familyName": primaryAuthorFamilyName, - "affiliation": primaryAuthorAffiliation}, - "speaker": {"firstName": speakerFirstName, - "familyName": speakerFamilyName, - "affiliation": speakerAffiliation}, - "secondaryAuthor": {"firstName": secondaryAuthorFirstName, - "familyName": secondaryAuthorFamilyName, - "affiliation": secondaryAuthorAffiliation}, - "summary": eventSummary, - "meetingName": nameOfTheEventMeeting, - "materials": [{"name": nameOfTheLink, - "url": linkDestination}, - ...], - "reportNumbers": [reportNumber, ...] - "startDateTime": {"time" : eventStartTime, - "date" : eventStartDate}, - "endDateTime": {"time" : eventEndTime, - "date" : eventEndDate} - "place": eventPlace}, - ...] - """ - raise NotImplementedError diff --git a/importer/indico_importer/client/index.js b/importer/indico_importer/client/index.js deleted file mode 100644 index 077cf83..0000000 --- a/importer/indico_importer/client/index.js +++ /dev/null @@ -1,1717 +0,0 @@ -// This file is part of the Indico plugins. -// Copyright (C) 2002 - 2020 CERN -// -// The Indico plugins are free software; you can redistribute -// them and/or modify them under the terms of the MIT License; -// see the LICENSE file for more details. - -import './main.css'; - -(function() { - const $t = $T.domain('importer'); - - /** Namespace for importer utility functions and variables */ - const ImporterUtils = { - /** Possible extensions for resources */ - resourcesExtensionList: {pdf: 1, doc: 1, docx: 1, ppt: 1}, - - /** Maps importer name into report number system name */ - reportNumberSystems: {invenio: 'cds', dummy: 'Dummy'}, - - /** Short names of the months. */ - shortMonthsNames: [ - $t.gettext('Jan'), - $t.gettext('Feb'), - $t.gettext('Mar'), - $t.gettext('Apr'), - $t.gettext('May'), - $t.gettext('Jun'), - $t.gettext('Jul'), - $t.gettext('Aug'), - $t.gettext('Sep'), - $t.gettext('Oct'), - $t.gettext('Nov'), - $t.gettext('Dec'), - ], - /** - * Converts minutes to the hour string (format HH:MM). - * If minutes are greater than 1440 (24:00) value '00:00' is returned. - * @param minutes Integer containing number of minutes. - * @return hour string. - */ - minutesToTime(minutes) { - if (minutes <= 1440) { - return `${ - (minutes - (minutes % 60)) / 60 < 10 - ? `0${(minutes - (minutes % 60)) / 60}` - : (minutes - (minutes % 60)) / 60 - }:${minutes % 60 < 10 ? `0${minutes % 60}` : minutes % 60}`; - } else { - return '00:00'; - } - }, - - /** - * Standard sorting function comparing start times of events. - * @param a first event. - * @param b second event. - * @return true if the first event starts later than the second. If not false. - */ - compareStartTime(a, b) { - return a.startDate.time > b.startDate.time; - }, - - /** - * Returns array containing sorted keys of the dictionary. - * @param dict Dictionary to be sorted. - * @param sortFunc Function comparing keys of the dictionary. - * @return Array containg sorted keys. - */ - sortedKeys(dict, sortFunc) { - const array = []; - each(dict, function(item) { - array.push(item); - }); - return array.sort(sortFunc); - }, - - /** - * Checks if a dictionary contains empty person data. - */ - isPersonEmpty(person) { - return !person || (!person.firstName && !person.familyName); - }, - - /** - * Send a POST request to indico. - * @param url The url where to post the data. - * @param data The data to POST. - * @param onSuccess Called when the request succeeds. - * @param onError Called when an error is encountered while performing the request. - */ - _sendRequest(url, args, onSuccess, onError) { - $.ajax({ - // There's no synchronisation on the server side yet thus make sure requests are sent serially - async: false, - url, - method: 'POST', - data: args, - success(data, textStatus) { - if ($.isFunction(onSuccess)) { - onSuccess(data, textStatus); - } - }, - error(xhr) { - if ($.isFunction(onError)) { - onError({ - title: $t.gettext('Something went wrong'), - message: '{0} ({1})'.format(xhr.statusText.toLowerCase(), xhr.status), - report_url: null, - }); - } - }, - }); - }, - }; - - /** - * Imitates dictionary with keys ordered by the time element insertion. - */ - type( - 'QueueDict', - [], - { - /** - * Inserts new element to the dictionary. If element's value is null removes an element. - * @param key element's key - * @param value element's value - */ - set(key, value) { - let existed = false; - for (const i in this.keySequence) { - if (key == this.keySequence[i]) { - existed = true; - if (value !== null) { - this.keySequence[i] = value; - } else { - this.keySequence.splice(i, 1); - } - } - } - if (!existed) { - this.keySequence.push(key); - } - this.dict[key] = value; - }, - - /** - * Gets list of keys. The list is sorted by an element insertion time. - */ - getKeys() { - return this.keySequence; - }, - - /** - * Gets elements with the specified key. - */ - get(key) { - return this.dict[key]; - }, - - /** - * Gets list of values. The list is sorted by an element insertion time. - */ - getValues() { - const tmp = []; - for (const index in this.keySequence) { - tmp.push(this.get(this.keySequence[index])); - } - return tmp; - }, - - /** - * Returns number of elements in the dictionary. - */ - getLength() { - return this.keySequence.length; - }, - - /** - * Removes all elements from the dictionary - */ - clear() { - this.keySequence = []; - this.dict = {}; - }, - - /** - * Moves the key one position towards the begining of the key list. - */ - shiftTop(idx) { - if (idx > 0) { - const tmp = this.keySequence[idx]; - this.keySequence[idx] = this.keySequence[idx - 1]; - this.keySequence[idx - 1] = tmp; - } - }, - - /** - * Moves the key one position towards the end of the key list. - */ - shiftBottom(idx) { - if (idx < this.keySequence.length - 1) { - const tmp = this.keySequence[idx]; - this.keySequence[idx] = this.keySequence[idx + 1]; - this.keySequence[idx + 1] = tmp; - } - }, - }, - - function() { - this.keySequence = []; - this.dict = {}; - } - ); - - type( - 'ImportDialog', - ['ExclusivePopupWithButtons', 'PreLoadHandler'], - { - _preload: [ - /** Loads a list of importers from the server */ - function(hook) { - const self = this; - $.ajax({ - url: build_url(ImporterPlugin.urls.importers, {}), - type: 'GET', - dataType: 'json', - success(data) { - if (handleAjaxError(data)) { - return; - } - self.importers = data; - hook.set(true); - }, - }); - }, - ], - - /** - * Hides importer list and timetable list and shows information to type a new query. - */ - _hideLists() { - this.importerList.hide(); - this.timetableList.hide(); - this.emptySearchDiv.show(); - }, - - /** - * Shows importer list and timetable list and hides information to type a new query. - */ - _showLists() { - this.importerList.show(); - this.timetableList.refresh(); - this.timetableList.show(); - this.emptySearchDiv.hide(); - }, - - /** - * Draws the content of the dialog. - */ - drawContent() { - const self = this; - const search = function() { - self.importerList.search(query.dom.value, importFrom.dom.value, 20, [ - function() { - self._showLists(); - }, - ]); - }; - const searchButton = Html.input('button', {}, $t.gettext('search')); - searchButton.observeClick(search); - var importFrom = Html.select({}); - for (const importer in this.importers) - importFrom.append(Html.option({value: importer}, this.importers[importer])); - var query = Html.input('text', {}); - query.observeEvent('keypress', function(event) { - if (event.keyCode == 13) { - search(); - } - }); - - this.emptySearchDiv = new PresearchContainer(this.height, function() { - self._showLists(); - }); - - /** Enables insert button whether some elements are selected at both importer and timetable list */ - const _observeInsertButton = function() { - if ( - self.importerList.getSelectedList().getLength() > 0 && - self.timetableList.getSelection() - ) { - self.insertButton.disabledButtonWithTooltip('enable'); - } else { - self.insertButton.disabledButtonWithTooltip('disable'); - } - }; - this.importerList = new ImporterList( - [], - {height: this.height - 80, width: this.width / 2 - 20, cssFloat: 'left'}, - 'entryList', - 'entryListSelected', - true, - _observeInsertButton - ); - this.timetableList = new TableTreeList( - this.topTimetable, - {height: this.height - 80, width: this.width / 2 - 20, cssFloat: 'right'}, - 'treeList', - 'treeListDayName', - 'treeListEntry', - true, - _observeInsertButton - ); - return Html.div( - {}, - Html.div( - {className: 'importDialogHeader', style: {width: pixels(this.width * 0.9)}}, - query, - searchButton, - ' ', - $t.gettext('in'), - ' ', - importFrom - ), - this.emptySearchDiv.draw(), - this.importerList.draw(), - this.timetableList.draw() - ); - }, - - _getButtons() { - const self = this; - return [ - [ - $t.gettext('Proceed...'), - function() { - const destination = self.timetableList.getSelection(); - const entries = self.importerList.getSelectedList(); - const importer = self.importerList.getLastImporter(); - new ImporterDurationDialog( - entries, - destination, - self.confId, - self.timetable, - importer, - function(redirect) { - if (!redirect) { - self._hideLists(); - self.timetableList.clearSelection(); - self.importerList.clearSelection(); - self.emptySearchDiv.showAfterSearch(); - } - self.reloadPage = true; - } - ); - }, - ], - [ - $t.gettext('Close'), - function() { - self.close(); - }, - ], - ]; - }, - - draw() { - this.insertButton = this.buttons.eq(0); - this.insertButton.disabledButtonWithTooltip({ - tooltip: $t.gettext('Please select contributions to be added and their destination.'), - disabled: true, - }); - return this.ExclusivePopupWithButtons.prototype.draw.call(this, this.drawContent()); - }, - - _onClose(evt) { - if (this.reloadPage) { - location.reload(); - IndicoUI.Dialogs.Util.progress(); - } - return self.ExclusivePopupWithButtons.prototype._onClose.call(this, evt); - }, - }, - - /** - * Importer's main tab. Contains inputs for typing a query and select the importer type. - * After making a query imported entries are displayed at the left side of the dialog, while - * at the right side list of all entries in the event's timetable will be shown. User can add - * new contributions to the timetable's entry by simply selecting them and clicking at 'proceed' - * button. - * @param timetable Indico timetable object. If it's undefined constructor will try to fetch - * window.timetable object. - */ - function(timetable) { - const self = this; - this.ExclusivePopupWithButtons($t.gettext('Import Entries')); - this.timetable = timetable ? timetable : window.timetable; - this.topTimetable = this.timetable.parentTimetable - ? this.timetable.parentTimetable - : this.timetable; - this.confId = this.topTimetable.contextInfo.id; - this.height = document.body.clientHeight - 200; - this.width = document.body.clientWidth - 200; - this.PreLoadHandler(this._preload, function() { - self.open(); - }); - this.execute(); - } - ); - - type( - 'PresearchContainer', - [], - { - /** - * Shows a widget. - */ - show() { - this.contentDiv.dom.style.display = 'block'; - }, - - /** - * Hides a widget. - */ - hide() { - this.contentDiv.dom.style.display = 'none'; - }, - - /** - * Changes a content of the widget. It should be used after making a first successful import. - */ - showAfterSearch() { - this.firstSearch.dom.style.display = 'none'; - this.afterSearch.dom.style.display = 'inline'; - }, - - draw() { - this.firstSearch = Html.span( - {style: {display: 'inline'}}, - $t.gettext("Please type your search phrase and press 'search'.") - ); - const hereLink = Html.span({className: 'fake-link'}, $t.gettext('here')); - hereLink.observeClick(this.afterSearchAction); - this.afterSearch = Html.span( - {style: {display: 'none'}}, - $t.gettext( - 'Your entries were inserted successfully. Please specify a new query or click' - ), - ' ', - hereLink, - ' ', - $t.gettext('to see the previous results.') - ); - this.contentDiv = Html.div( - {className: 'presearchContainer', style: {height: pixels(this.height - 130)}}, - this.firstSearch, - this.afterSearch - ); - return this.contentDiv; - }, - }, - - /** - * A placeholder for importer and timetable list widgets. Contains user's tips about what to do right now. - * @param widget's height - * @param function executed afer clicking 'here' link. - */ - function(height, afterSearchAction) { - this.height = height; - this.afterSearchAction = afterSearchAction; - } - ); - - type( - 'ImporterDurationDialog', - ['ExclusivePopupWithButtons', 'PreLoadHandler'], - { - _preload: [ - /** - * Fetches the default start time of the first inserted contribution. - * Different requests are used for days, sessions and contributions. - */ - function(hook) { - const self = this; - // If the destination is a contribution, simply fetch the contribution's start time. - if (this.destination.entryType == 'Contribution') { - self.info.set('startTime', this.destination.startDate.time.substr(0, 5)); - hook.set(true); - } else { - let url; - if (this.destination.entryType == 'Day') { - url = build_url(ImporterPlugin.urls.day_end_date, { - importer_name: 'indico_importer', - confId: this.confId, - }); - } - if (this.destination.entryType == 'Session') { - url = build_url(ImporterPlugin.urls.block_end_date, { - importer_name: 'indico_importer', - confId: this.confId, - entry_id: this.destination.scheduleEntryId, - }); - } - $.ajax({ - url, - data: { - sessionId: this.destination.sessionId, - slotId: this.destination.sessionSlotId, - selectedDay: this.destination.startDate.date.replace(/-/g, '/'), - }, - success(data) { - self.info.set('startTime', data); - hook.set(true); - }, - }); - } - }, - ], - - drawContent() { - const durationField = Html.input( - 'text', - {}, - this.destination.contribDuration ? this.destination.contribDuration : 20 - ); - const timeField = Html.input('text', {}); - const redirectCheckbox = Html.input('checkbox', {}); - - this.parameterManager.add(durationField, 'unsigned_int', false); - this.parameterManager.add(timeField, 'time', false); - - $B(this.info.accessor('duration'), durationField); - $B(timeField, this.info.accessor('startTime')); - $B(this.info.accessor('redirect'), redirectCheckbox); - - return IndicoUtil.createFormFromMap([ - [$t.gettext('Duration time of every inserted contribution:'), durationField], - [$t.gettext('Start time of the first contribution:'), timeField], - [$t.gettext('Show me the destination:'), redirectCheckbox], - ]); - }, - - /* Redirect to the timetable containing the created entry. */ - _performRedirect() { - let fragment = this.destination.startDate.date.replace(/-/g, ''); - if (this.destination.entryType == 'Session') { - fragment += `.${this.destination.id}`; - } - location.hash = fragment; - location.reload(); // Reload needed since only the fragment is changed - }, - - _getUrl(eventId, day, destination) { - const params = { - confId: eventId, - }; - if (destination.entryType == 'Day') { - params.day = day; - return build_url(ImporterPlugin.urls.add_contrib, params); - } - if (destination.entryType == 'Session') { - params.day = day; - params.session_block_id = destination.sessionSlotId; - return build_url(ImporterPlugin.urls.add_contrib, params); - } - if (destination.entryType == 'Contribution') { - params.contrib_id = destination.contributionId; - return build_url(ImporterPlugin.urls.create_subcontrib_rest, params); - } - }, - - _personLinkData(entry) { - const authorType = { - none: 0, - primary: 1, - secondary: 2, - }; - const linkDataEntry = function(author, authorType, speaker) { - if (speaker === undefined) { - speaker = !!author.isSpeaker; - } - return $.extend( - { - authorType, - isSpeaker: speaker, - isSubmitter: false, - }, - author - ); - }; - const linkData = []; - if (!ImporterUtils.isPersonEmpty(entry.primaryAuthor)) { - linkData.push(linkDataEntry(entry.primaryAuthor, authorType.primary)); - } - if (!ImporterUtils.isPersonEmpty(entry.secondaryAuthor)) { - linkData.push(linkDataEntry(entry.secondaryAuthor, authorType.secondary)); - } - if (!ImporterUtils.isPersonEmpty(entry.speaker)) { - linkData.push(linkDataEntry(entry.speaker, authorType.none, true)); - } - - if (!this.timetable.eventInfo.isConference) { - // Only speakers are allowed in this type of event - const speakersLinkData = []; - $.each(linkData, function() { - if (this.isSpeaker) { - this.authorType = authorType.none; - speakersLinkData.push(this); - } - }); - return speakersLinkData; - } - return linkData; - }, - - _addContributionMaterial(title, link_url, eventId, contributionId, subContributionId) { - let requestUrl; - if (subContributionId !== undefined) { - requestUrl = build_url(ImporterPlugin.urls.add_link, { - confId: eventId, - contrib_id: contributionId, - subcontrib_id: subContributionId, - object_type: 'subcontribution', - }); - } else { - requestUrl = build_url(ImporterPlugin.urls.add_link, { - confId: eventId, - contrib_id: contributionId, - object_type: 'contribution', - }); - } - const params = { - csrf_token: $('#csrf-token').attr('content'), - link_url, - title, - folder: '__None', - acl: '[]', - }; - ImporterUtils._sendRequest(requestUrl, params); - }, - - _addReference(type, value, eventId, contributionId, subContributionId) { - let url; - if (subContributionId !== undefined) { - url = build_url(ImporterPlugin.urls.create_subcontrib_reference_rest, { - confId: eventId, - contrib_id: contributionId, - subcontrib_id: subContributionId, - }); - } else { - url = build_url(ImporterPlugin.urls.create_contrib_reference_rest, { - confId: eventId, - contrib_id: contributionId, - }); - } - const params = { - csrf_token: $('#csrf-token').attr('content'), - type, - value, - }; - ImporterUtils._sendRequest(url, params); - }, - - _getButtons() { - const self = this; - return [ - [ - $t.gettext('Insert'), - function() { - if (!self.parameterManager.check()) { - return; - } - // Converts string containing contribution's start date(HH:MM) into a number of minutes. - // Using parseFloat because parseInt('08') = 0. - let time = - parseFloat(self.info.get('startTime').split(':')[0]) * 60 + - parseFloat(self.info.get('startTime').split(':')[1]); - const duration = parseInt(self.info.get('duration')); - // If last contribution finishes before 24:00 - if (time + duration * self.entries.getLength() <= 1440) { - let hasError = false; - const killProgress = IndicoUI.Dialogs.Util.progress(); - const errorCallback = function(error) { - if (error) { - hasError = true; - showErrorDialog(error); - } - }; - const date = self.destination.startDate.date.replace(/-/g, '/'); - const args = []; - each(self.entries.getValues(), function(entry) { - entry = entry.getAll(); - const timeStr = ImporterUtils.minutesToTime(time); - const params = { - csrf_token: $('#csrf-token').attr('content'), - title: entry.title ? entry.title : 'Untitled', - description: entry.summary, - duration: [duration, 'minutes'], - references: '[]', - }; - if (self.destination.entryType == 'Contribution') { - $.extend(params, { - speakers: Json.write(self._personLinkData(entry)), - }); - } else { - $.extend(params, { - time: timeStr, - person_link_data: Json.write(self._personLinkData(entry)), - location_data: '{"address": "", "inheriting": true}', - type: '__None', - }); - } - const url = self._getUrl(self.confId, date, self.destination); - const successCallback = function(result) { - const materials = entry.materials || {}; - const reportNumbers = entry.reportNumbers || []; - const reportNumbersLabel = ImporterUtils.reportNumberSystems[self.importer]; - if (self.destination.entryType == 'Contribution') { - $.each(materials, function(title, url) { - self._addContributionMaterial( - title, - url, - result.event_id, - result.contribution_id, - result.id - ); - }); - $.each(reportNumbers, function(idx, number) { - self._addReference( - reportNumbersLabel, - number, - self.confId, - result.contribution_id, - result.id - ); - }); - } else { - const contribution = result.entries[0].entry; - $.each(materials, function(title, url) { - self._addContributionMaterial( - title, - url, - self.confId, - contribution.contributionId - ); - }); - $.each(reportNumbers, function(idx, number) { - self._addReference( - reportNumbersLabel, - number, - self.confId, - contribution.contributionId - ); - }); - } - }; - ImporterUtils._sendRequest(url, params, successCallback, errorCallback); - time += duration; - }); - if (self.successFunction) { - self.successFunction(self.info.get('redirect')); - } - if (self.info.get('redirect')) { - self._performRedirect(); - } - self.close(); - killProgress(); - } else { - new WarningPopup( - 'Warning', - 'Some contributions will end after 24:00. Please modify start time and duration.' - ).open(); - } - }, - ], - [ - $t.gettext('Cancel'), - function() { - self.close(); - }, - ], - ]; - }, - - draw() { - return this.ExclusivePopupWithButtons.prototype.draw.call(this, this.drawContent()); - }, - }, - - /** - * Dialog used to set the duration of the each contribution and the start time of the first on. - * @param entries List of imported entries - * @param destination Place into which entries will be inserted - * @param confId Id of the current conference - * @param timetable Indico timetable object of the current conference. - * @param importer Name of the used importer. - * @param successFunction Function executed after inserting events. - */ - function(entries, destination, confId, timetable, importer, successFunction) { - const self = this; - this.ExclusivePopupWithButtons($t.gettext('Adjust entries')); - this.confId = confId; - this.entries = entries; - this.destination = destination; - this.timetable = timetable; - this.successFunction = successFunction; - this.importer = importer; - this.parameterManager = new IndicoUtil.parameterManager(); - this.info = new WatchObject(); - this.PreLoadHandler(this._preload, function() { - self.open(); - }); - this.execute(); - } - ); - - type( - 'ImporterListWidget', - ['SelectableListWidget'], - { - /** - * Removes all entries from the list - */ - clearList() { - this.SelectableListWidget.prototype.clearList.call(this); - this.recordDivs = []; - }, - - /** - * Removes all selections. - */ - clearSelection() { - this.SelectableListWidget.prototype.clearSelection.call(this); - this.selectedObserver(this.selectedList); - }, - - /** - * Returns number of entries in the list. - */ - getLength() { - return this.recordDivs.length; - }, - - /** - * Returns the last query. - */ - getLastQuery() { - return this.lastSearchQuery; - }, - - /** - * Returns the name of the last used importer. - */ - getLastImporter() { - return this.lastSearchImporter; - }, - - /** - * Returns true if it's possible to import more entries, otherwise false. - */ - isMoreToImport() { - return this.moreToImport; - }, - - /** - * Base search method. Sends a query to the importer. - * @param query A query string send to the importer - * @param importer A name of the used importer. - * @param size Number of fetched objects. - * @param successFunction Method executed after successful request. - * @param callbacks List of methods executed after request (doesn't matter if successful). - */ - _searchBase(query, importer, size, successFunc, callbacks) { - const self = this; - $.ajax({ - // One more entry is fetched to be able to check if it's possible to fetch - // more entries in case of further requests. - url: build_url(ImporterPlugin.urls.import_data, { - importer_name: importer, - query, - size: size + 1, - }), - type: 'POST', - dataType: 'json', - complete: IndicoUI.Dialogs.Util.progress(), - success(data) { - if (handleAjaxError(data)) { - return; - } - successFunc(data.records); - _.each(callbacks, function(callback) { - callback(); - }); - }, - }); - // Saves last request data - this.lastSearchImporter = importer; - this.lastSearchQuery = query; - this.lastSearchSize = size; - }, - - /** - * Clears the list and inserts new imported entries. - * @param query A query string send to the importer - * @param importer A name of the used importer. - * @param size Number of fetched objects. - * @param callbacks List of methods executed after request (doesn't matter if successful). - */ - search(query, importer, size, callbacks) { - const self = this; - const successFunc = function(result) { - self.clearList(); - let importedRecords = 0; - self.moreToImport = false; - for (const record in result) { - // checks if it's possible to import more entries - if (size == importedRecords++) { - self.moreToImport = true; - break; - } - self.set(record, $O(result[record])); - } - }; - this._searchBase(query, importer, size, successFunc, callbacks); - }, - - /** - * Adds more entries to the current list. Uses previous query and importer. - * @param size Number of fetched objects. - * @param callbacks List of methods executed after request (doesn't matter if successful). - */ - append(size, callbacks) { - const self = this; - const lastLength = this.getLength(); - const successFunc = function(result) { - let importedRecords = 0; - self.moreToImport = false; - for (const record in result) { - // checks if it's possible to import more entries - if (lastLength + size == importedRecords) { - self.moreToImport = true; - break; - } - // Some entries are already in the list so we don't want to insert them twice. - // Entries with indexes greater or equal lastLength - 1 are not yet in the list. - if (lastLength - 1 < importedRecords) { - self.set(record, $O(result[record])); - } - ++importedRecords; - } - }; - this._searchBase( - this.getLastQuery(), - this.getLastImporter(), - this.getLength() + size, - successFunc, - callbacks - ); - }, - - /** - * Extracts person's name, surname and affiliation - */ - _getPersonString(person) { - return `${person.firstName} ${person.familyName}${ - person.affiliation ? ` (${person.affiliation})` : '' - }`; - }, - - /** - * Draws sequence number attached to the item div - */ - _drawSelectionIndex() { - const self = this; - const selectionIndex = Html.div({ - className: 'entryListIndex', - style: {display: 'none', cssFloat: 'left'}, - }); - // Removes standard mouse events to enable custom right click event - const stopMouseEvent = function(event) { - event.cancelBubble = true; - if (event.preventDefault) { - event.preventDefault(); - } else { - event.returnValue = false; - } - return false; - }; - selectionIndex.observeEvent('contextmenu', stopMouseEvent); - selectionIndex.observeEvent('mousedown', stopMouseEvent); - selectionIndex.observeEvent('click', stopMouseEvent); - selectionIndex.observeEvent('mouseup', function(event) { - // Preventing event propagation - event.cancelBubble = true; - if (event.which === null) { - /* IE case */ - var button = event.button == 1 ? 'left' : 'notLeft'; - } else { - /* All others */ - var button = event.which == 1 ? 'left' : 'notLeft'; - } - const idx = parseInt( - selectionIndex.dom.innerHTML.substr(0, selectionIndex.dom.innerHTML.length - 2) - 1 - ); - if (button == 'left') { - self.selectedList.shiftTop(idx); - } else { - self.selectedList.shiftBottom(idx); - } - self.observeSelection(self.selectedList); - }); - return selectionIndex; - }, - - /** - * Converts list of materials into a dictionary - */ - _convertMaterials(materials) { - const materialsDict = {}; - each(materials, function(mat) { - const key = mat.name || ''; - if (!materialsDict[key]) { - materialsDict[key] = []; - } - materialsDict[key].push(mat.url); - }); - return materialsDict; - }, - - /** - * Converts resource link into a name. - */ - _getResourceName(resource) { - const splittedName = resource.split('.'); - if (splittedName[splittedName.length - 1] in ImporterUtils.resourcesExtensionList) { - return splittedName[splittedName.length - 1]; - } else { - return 'resource'; - } - }, - - /** - * Draws a div containing entry's data. - */ - _drawItem(record) { - const self = this; - const recordDiv = Html.div({}); - const key = record.key; - record = record.get(); - // Empty fields are not displayed. - if (record.get('reportNumbers')) { - const reportNumber = Html.div({}, Html.em({}, $t.gettext('Report number(s)')), ':'); - each(record.get('reportNumbers'), function(id) { - reportNumber.append(` ${id}`); - }); - recordDiv.append(reportNumber); - } - if (record.get('title')) { - recordDiv.append( - Html.div({}, Html.em({}, $t.gettext('Title')), ': ', record.get('title')) - ); - } - if (record.get('meetingName')) { - recordDiv.append( - Html.div({}, Html.em({}, $t.gettext('Meeting')), ': ', record.get('meetingName')) - ); - } - // Speaker, primary and secondary authors are stored in dictionaries. Their property have to be checked. - if (!ImporterUtils.isPersonEmpty(record.get('primaryAuthor'))) { - recordDiv.append( - Html.div( - {}, - Html.em({}, $t.gettext('Primary author')), - ': ', - this._getPersonString(record.get('primaryAuthor')) - ) - ); - } - if (!ImporterUtils.isPersonEmpty(record.get('secondaryAuthor'))) { - recordDiv.append( - Html.div( - {}, - Html.em({}, $t.gettext('Secondary author')), - ': ', - this._getPersonString(record.get('secondaryAuthor')) - ) - ); - } - if (!ImporterUtils.isPersonEmpty(record.get('speaker'))) { - recordDiv.append( - Html.div( - {}, - Html.em({}, $t.gettext('Speaker')), - ': ', - this._getPersonString(record.get('speaker')) - ) - ); - } - if (record.get('summary')) { - const summary = record.get('summary'); - // If summary is too long it need to be truncated. - if (summary.length < 200) { - recordDiv.append(Html.div({}, Html.em({}, $t.gettext('Summary')), ': ', summary)); - } else { - const summaryBeg = Html.span({}, summary.substr(0, 200)); - const summaryEnd = Html.span({style: {display: 'none'}}, summary.substr(200)); - const showLink = Html.span({className: 'fake-link'}, $t.gettext(' (show all)')); - showLink.observeClick(function(evt) { - summaryEnd.dom.style.display = 'inline'; - showLink.dom.style.display = 'none'; - hideLink.dom.style.display = 'inline'; - // Preventing event propagation - evt.cancelBubble = true; - // Recalculating position of the selection number - self.observeSelection(self.selectedList); - }); - var hideLink = Html.span( - {className: 'fake-link', style: {display: 'none'}}, - $t.gettext(' (hide)') - ); - hideLink.observeClick(function(evt) { - summaryEnd.dom.style.display = 'none'; - showLink.dom.style.display = 'inline'; - hideLink.dom.style.display = 'none'; - // Preventing event propagation - evt.cancelBubble = true; - // Recalculating position of the selection number - self.observeSelection(self.selectedList); - }); - const sumamaryDiv = Html.div( - {}, - Html.em({}, $t.gettext('Summary')), - ': ', - summaryBeg, - showLink, - summaryEnd, - hideLink - ); - recordDiv.append(sumamaryDiv); - } - } - if (record.get('place')) { - recordDiv.append( - Html.div({}, Html.em({}, $t.gettext('Place')), ': ', record.get('place')) - ); - } - if (record.get('materials')) { - record.set('materials', this._convertMaterials(record.get('materials'))); - const materials = Html.div({}, Html.em({}, $t.gettext('Materials')), ':'); - for (const mat in record.get('materials')) { - var materialType = Html.div({}, mat ? `${mat}:` : ''); - each(record.get('materials')[mat], function(resource) { - const link = Html.a( - {href: resource, target: '_new'}, - self._getResourceName(resource) - ); - link.observeClick(function(evt) { - // Preventing event propagation - evt.cancelBubble = true; - }); - materialType.append(' '); - materialType.append(link); - }); - materials.append(materialType); - } - recordDiv.append(materials); - } - recordDiv.append(this._drawSelectionIndex()); - this.recordDivs[key] = recordDiv; - return recordDiv; - }, - - /** - * Observer function executed when selection is made. Draws a number next to the item div, which - * represents insertion sequence of entries. - */ - observeSelection(list) { - const self = this; - // Clears numbers next to the all divs - for (const entry in this.recordDivs) { - const record = this.recordDivs[entry]; - record.dom.lastChild.style.display = 'none'; - record.dom.lastChild.innerHTML = ''; - } - let seq = 1; - each(list.getKeys(), function(entry) { - const record = self.recordDivs[entry]; - record.dom.lastChild.style.display = 'block'; - record.dom.lastChild.style.top = pixels(-(record.dom.clientHeight + 23) / 2); - record.dom.lastChild.innerHTML = seq; - switch (seq) { - case 1: - record.dom.lastChild.innerHTML += $t.gettext('st'); - break; - case 2: - record.dom.lastChild.innerHTML += $t.gettext('nd'); - break; - case 3: - record.dom.lastChild.innerHTML += $t.gettext('rd'); - break; - default: - record.dom.lastChild.innerHTML += $t.gettext('th'); - break; - } - ++seq; - }); - }, - }, - - /** - * Widget containing a list of imported contributions. Supports multiple selections of results and - * keeps selection order. - * @param events List of events to be inserted during initialization. - * @param listStyle Css class name of the list. - * @param selectedStyle Css class name of a selected element. - * @param customObserver Function executed while selection is made. - */ - function(events, listStyle, selectedStyle, customObserver) { - const self = this; - // After selecting/deselecting an element two observers are executed. - // The first is a default one, used to keep selected elements order. - // The second one is a custom observer passed in the arguments list. - const observer = function(list) { - this.observeSelection(list); - if (customObserver) { - customObserver(list); - } - }; - this.SelectableListWidget(observer, false, listStyle, selectedStyle); - this.selectedList = new QueueDict(); - this.recordDivs = {}; - for (const record in events) { - this.set(record, $O(events[record])); - } - } - ); - - type( - 'ImporterList', - [], - { - /** - * Show the widget. - */ - show() { - this.contentDiv.dom.style.display = 'block'; - }, - - /** - * Hides the widget. - */ - hide() { - this.contentDiv.dom.style.display = 'none'; - }, - - /** - * Returns list of the selected entries. - */ - getSelectedList() { - return this.importerWidget.getSelectedList(); - }, - - /** - * Removes all entries from the selection list. - */ - clearSelection() { - this.importerWidget.clearSelection(); - }, - - /** - * Returns last used importer. - */ - getLastImporter() { - return this.importerWidget.getLastImporter(); - }, - - /** - * Changes widget's header depending on the number of results in the list. - */ - handleContent() { - if (this.descriptionDiv && this.emptyDescriptionDiv) { - if (this.importerWidget.getLength() === 0) { - this.descriptionDiv.dom.style.display = 'none'; - this.emptyDescriptionDiv.dom.style.display = 'block'; - this.moreEntriesDiv.dom.style.display = 'none'; - } else { - this.entriesCount.dom.innerHTML = $t - .ngettext( - 'One entry was found. ', - '{0} entries were found. ', - this.importerWidget.getLength() - ) - .format(this.importerWidget.getLength()); - this.descriptionDiv.dom.style.display = 'block'; - this.emptyDescriptionDiv.dom.style.display = 'none'; - if (this.importerWidget.isMoreToImport()) { - this.moreEntriesDiv.dom.style.display = 'block'; - } else { - this.moreEntriesDiv.dom.style.display = 'none'; - } - } - } - }, - - /** - * Adds handleContent method to the callback list. If callback list is empty, creates a new one - * containing only handleContent method. - * @return list with inserted handleContent method. - */ - _appendCallbacks(callbacks) { - const self = this; - if (callbacks) { - callbacks.push(function() { - self.handleContent(); - }); - } else { - callbacks = [ - function() { - self.handleContent(); - }, - ]; - } - return callbacks; - }, - - /** - * Calls search method from ImporterListWidget object. - */ - search(query, importer, size, callbacks) { - this.importerWidget.search(query, importer, size, this._appendCallbacks(callbacks)); - }, - - /** - * Calls append method from ImporterListWidget object. - */ - append(size, callbacks) { - this.importerWidget.append(size, this._appendCallbacks(callbacks)); - }, - - draw() { - const importerDiv = this._drawImporterDiv(); - this.contentDiv = Html.div( - {className: 'entryListContainer'}, - this._drawHeader(), - importerDiv - ); - - for (const style in this.style) { - this.contentDiv.setStyle(style, this.style[style]); - if (style == 'height') { - importerDiv.setStyle('height', this.style[style] - 76); // 76 = height of the header - } - } - - if (this.hidden) { - this.contentDiv.dom.style.display = 'none'; - } - - return this.contentDiv; - }, - - _drawHeader() { - this.entriesCount = Html.span({}, '0'); - this.descriptionDiv = Html.div( - {className: 'entryListDesctiption'}, - this.entriesCount, - $t.gettext('Please select the results you want to insert.') - ); - this.emptyDescriptionDiv = Html.div( - {className: 'entryListDesctiption'}, - $t.gettext('No results were found. Please change the search phrase.') - ); - return Html.div( - {}, - Html.div({className: 'entryListHeader'}, $t.gettext('Step 1: Search results:')), - this.descriptionDiv, - this.emptyDescriptionDiv - ); - }, - - _drawImporterDiv() { - const self = this; - this.moreEntriesDiv = Html.div( - { - className: 'fake-link', - style: { - paddingBottom: pixels(15), - textAlign: 'center', - clear: 'both', - marginTop: pixels(15), - }, - }, - $t.gettext('more results') - ); - this.moreEntriesDiv.observeClick(function() { - self.append(20); - }); - return Html.div( - {style: {overflow: 'auto'}}, - this.importerWidget.draw(), - this.moreEntriesDiv - ); - }, - }, - - /** - * Encapsulates ImporterListWidget. Adds a header depending on the number of entries in the least. - * Adds a button to fetch more entries from selected importer. - * @param events List of events to be inserted during initialization. - * @param style Dictionary of css styles applied to the div containing the list. IMPORTANT pass 'height' - * attribute as an integer not a string, because some further calculations will be made. - * @param listStyle Css class name of the list. - * @param selectedStyle Css class name of a selected element. - * @param hidden If true widget will not be displayed after being initialized. - * @param observer Function executed while selection is made. - */ - function(events, style, listStyle, selectedStyle, hidden, observer) { - this.importerWidget = new ImporterListWidget(events, listStyle, selectedStyle, observer); - this.style = style; - this.hidden = hidden; - } - ); - - type( - 'TimetableListWidget', - ['ListWidget'], - { - /** - * Highlights selected entry and calls an observer method. - */ - setSelection(selected, div) { - if (this.selectedDiv) { - this.selectedDiv.dom.style.fontWeight = 'normal'; - this.selectedDiv.dom.style.boxShadow = ''; - this.selectedDiv.dom.style.MozBoxShadow = ''; - } - if (this.selected != selected) { - this.selectedDiv = div; - this.selected = selected; - this.selectedDiv.dom.style.fontWeight = 'bold'; - this.selectedDiv.dom.style.boxShadow = '3px 3px 15px #000000'; - this.selectedDiv.dom.style.MozBoxShadow = '3px 3px 15px #000000'; - } else { - this.selected = null; - this.selectedDiv = null; - } - if (this.observeSelection) { - this.observeSelection(); - } - }, - - /** - * Deselects current entry. - */ - clearSelection() { - if (this.selectedDiv) { - this.selectedDiv.dom.style.backgroundColor = ''; - } - this.selected = null; - this.selectedDiv = null; - if (this.observeSelection) { - this.observeSelection(); - } - }, - - /** - * Returns selected entry - */ - getSelection() { - return this.selected; - }, - - /** - * Recursive function drawing timetable hierarchy. - * @param item Entry to be displayed - * @param level Recursion level. Used to set margins properly. - */ - _drawItem(item, level) { - const self = this; - level = level ? level : 0; - // entry is a Day - switch (item.entryType) { - case 'Contribution': - item.color = '#F8F2E8'; - item.textColor = '#000000'; - case 'Session': - var titleDiv = Html.div( - { - className: 'treeListEntry', - style: {backgroundColor: item.color, color: item.textColor}, - }, - item.title + - (item.startDate && item.endDate - ? ` (${item.startDate.time.substr(0, 5)} - ${item.endDate.time.substr(0, 5)})` - : '') - ); - var entries = ImporterUtils.sortedKeys(item.entries, ImporterUtils.compareStartTime); - break; - case 'Break': - if (this.displayBreaks) { - var titleDiv = Html.div( - { - className: 'treeListEntry', - style: {backgroundColor: item.color, color: item.textColor}, - }, - item.title + - (item.startDate && item.endDate - ? ` (${item.startDate.time.substr(0, 5)} - ${item.endDate.time.substr(0, 5)})` - : '') - ); - var entries = ImporterUtils.sortedKeys(item.entries, ImporterUtils.compareStartTime); - } else { - return null; - } - break; - case undefined: - item.entryType = 'Day'; - item.startDate = { - date: `${item.key.substr(0, 4)}-${item.key.substr(4, 2)}-${item.key.substr(6, 2)}`, - }; - item.color = '#FFFFFF'; - item.textColor = '#000000'; - var titleDiv = Html.div( - {className: 'treeListDayName'}, - `${item.key.substr(6, 2)} ${ - ImporterUtils.shortMonthsNames[parseFloat(item.key.substr(4, 2)) - 1] - } ${item.key.substr(0, 4)}` - ); - var entries = ImporterUtils.sortedKeys( - item.get().getAll(), - ImporterUtils.compareStartTime - ); - break; - } - titleDiv.observeClick(function() { - self.setSelection(item, titleDiv); - }); - const itemDiv = Html.div( - {style: {marginLeft: pixels(level * 20), clear: 'both', padding: pixels(5)}}, - titleDiv - ); - const entriesDiv = Html.div({style: {display: 'none'}}); - - // Draws subentries - for (const entry in entries) { - entriesDiv.append(this._drawItem(entries[entry], level + 1)); - } - - // If there are any subentries, draws buttons to show/hide them on demand. - if (entries.length) { - titleDiv.append(this._drawShowHideButtons(entriesDiv)); - itemDiv.append(entriesDiv); - } - - return itemDiv; - }, - - /** - * Attaches buttons to the dom object which hide/show it when clicked. - */ - _drawShowHideButtons(div) { - const showButton = Html.img({src: imageSrc('collapsd'), style: {display: 'block'}}); - const hideButton = Html.img({src: imageSrc('exploded'), style: {display: 'none'}}); - showButton.observeClick(function(evt) { - div.dom.style.display = 'block'; - showButton.dom.style.display = 'none'; - hideButton.dom.style.display = 'block'; - evt.cancelBubble = true; - }); - hideButton.observeClick(function(evt) { - div.dom.style.display = 'none'; - showButton.dom.style.display = 'block'; - hideButton.dom.style.display = 'none'; - evt.cancelBubble = true; - }); - return Html.div({className: 'expandButtonsDiv'}, showButton, hideButton); - }, - - /** - * Inserts entries from the timetable inside the widget. - */ - _insertFromTimetable() { - const self = this; - const timetableData = this.timetable.getData(); - each(this.timetable.sortedKeys, function(day) { - self.set(day, $O(timetableData[day])); - }); - }, - - /** - * Clears the list and inserts entries from the timetable inside the widget. - */ - refresh() { - this.clear(); - this._insertFromTimetable(); - }, - }, - - /** - * Draws event's timetable as a hierarchical expandable list. - * @param timetable Indico timetable object to be drawn - * @param listStyle Css class name of the list. - * @param dayStyle Css class name of day entries. - * @param eventStyle Css class name of session and contributions entries. - * @param observeSelection Funtcion executed after changing selection state. - * @param displayBreaks If true breaks will be displayed in the list. If false breaks are hidden. - */ - function(timetable, listStyle, dayStyle, eventStyle, observeSelection, displayBreaks) { - this.timetable = timetable; - this.displayBreaks = displayBreaks; - this.observeSelection = observeSelection; - const self = this; - this.ListWidget(listStyle); - this._insertFromTimetable(); - } - ); - - type( - 'TableTreeList', - [], - { - /** - * Show the widget. - */ - show() { - this.contentDiv.dom.style.display = 'block'; - }, - - /** - * Hides the widget - */ - hide() { - this.contentDiv.dom.style.display = 'none'; - }, - - /** - * Returns selected entry. TimetableListWidget method wrapper. - */ - getSelection() { - return this.timetableList.getSelection(); - }, - - /** - * Deselects current entry. TimetableListWidget method wrapper. - */ - clearSelection() { - return this.timetableList.clearSelection(); - }, - - /** - * Highlights selected entry and calls an observer method. TimetableListWidget method wrapper. - */ - setSelection(selected, div) { - return this.timetableList.setSelection(selected, div); - }, - - /** - * Clears the list and inserts entries from the timetable inside the widget. - */ - refresh() { - this.timetableList.refresh(); - }, - - draw() { - this.contentDiv = Html.div( - {className: 'treeListContainer'}, - Html.div({className: 'treeListHeader'}, $t.gettext('Step 2: Choose destination:')), - Html.div( - {className: 'treeListDescription'}, - $t.gettext('Please select the place in which the contributions will be inserted.') - ) - ); - const treeDiv = Html.div({style: {overflow: 'auto'}}, this.timetableList.draw()); - for (const style in this.style) { - this.contentDiv.setStyle(style, this.style[style]); - if (style == 'height') { - treeDiv.setStyle('height', this.style[style] - 76); - } - } - this.contentDiv.append(treeDiv); - if (this.hidden) { - this.contentDiv.dom.style.display = 'none'; - } - return this.contentDiv; - }, - }, - - /** - * Draws event's timetable as a hierarchical expandable list. - * @param timetable Indico timetable object to be drawn - * @param style Dictionary of css styles applied to the div containing the list. IMPORTANT pass 'height' - * attribute as an integer not a string, because some further calculations will be made. - * @param listStyle Css class name of the list. - * @param dayStyle Css class name of day entries. - * @param eventStyle Css class name of session and contributions entries. - * @param observer Funtcion executed after changing selection state. - */ - function(timetable, style, listStyle, dayStyle, eventStyle, hidden, observer) { - this.timetableList = new TimetableListWidget( - timetable, - listStyle, - dayStyle, - eventStyle, - observer - ); - this.style = style; - this.hidden = hidden; - } - ); - - $(function() { - $('#timetable').on('click', '.js-create-importer-dialog', function() { - const timetable = $(this).data('timetable'); - new ImportDialog(timetable); - }); - }); -})(); diff --git a/importer/indico_importer/client/main.css b/importer/indico_importer/client/main.css deleted file mode 100644 index 39e07b5..0000000 --- a/importer/indico_importer/client/main.css +++ /dev/null @@ -1,158 +0,0 @@ -/* This file is part of the Indico plugins. - * Copyright (C) 2002 - 2020 CERN - * - * The Indico plugins are free software; you can redistribute - * them and/or modify them under the terms of the MIT License; - * see the LICENSE file for more details. - */ - -ul.treeList { - list-style: none; -} - -div.treeListContainer { - background: #f8f8f8; - -moz-border-radius: 5%; - border-radius: 5%; - margin-top: 15px; - overflow: auto; -} - -div.treeListHeader { - color: #4e4c46; - font-family: 'Times New Roman', Verdana, Arial, sans-serif; - font-size: 20px; - height: 20px; - letter-spacing: 1px; - padding: 15px; - text-align: center; -} - -div.treeListDescription { - text-align: center; - color: #777; - font-size: 12px; - font-style: italic; - font-weight: normal; - padding: 7px 0; - height: 12px; -} - -.treeListDayName { - -moz-border-radius: 20px; - border-radius: 20px; - border: 1px solid #ccc; - padding: 10px; - background-color: #fff; - cursor: pointer; - float: left; -} - -.treeListEntry { - -moz-border-radius: 10px; - border-radius: 10px; - border: 1px solid #ccc; - padding: 7px; - width: 80%; - background-color: #fff; - cursor: pointer; -} - -div.entryListContainer { - background: #f8f8f8; - -moz-border-radius: 5%; - border-radius: 5%; - margin-top: 15px; - overflow: auto; -} - -div.entryListHeader { - color: #4e4c46; - font-family: 'Times New Roman', Verdana, Arial, sans-serif; - font-size: 20px; - height: 20px; - letter-spacing: 1px; - padding: 15px; - text-align: center; -} - -div.entryListDesctiption { - text-align: center; - color: #777; - font-size: 12px; - font-style: italic; - font-weight: normal; - padding: 7px 0; - height: 12px; -} - -ul.entryList li { - -moz-border-radius: 20px; - border-radius: 20px; - border: 1px solid #ccc; - padding: 7px; - width: 90%; - margin-bottom: 10px; - list-style: none; - background-color: #fff; - cursor: pointer; -} - -ul.entryList li div { - padding: 5px; -} - -ul.entryList em { - font-weight: bold; - font-style: normal; -} - -.entryListSelected { - background-color: #cdeb8b !important; - box-shadow: 3px 3px 5px #000; - -moz-box-shadow: 3px 3px 5px #000; - -webkit-box-shadow: 3px 3px 5px #000; -} - -div.importDialogHeader { - margin-left: auto; - margin-right: auto; - padding: 5px; - text-align: center; -} - -div.importDialogHeader input[type='text'] { - width: 35%; - min-width: 250px; - height: 20px; - font-size: 17px; -} - -.entryListIndex { - -moz-border-radius: 100% 100% 100% 100%; - border-radius: 100% 100% 100% 100%; - border: 1px solid #ccc; - left: -51px; - position: relative; - text-align: center; - width: 23px; -} - -div.expandButtonsDiv { - float: left; - left: -30px; - position: relative; - top: 2px; - width: 0; -} - -div.presearchContainer { - -moz-border-radius: 20px 20px 20px 20px; - border-radius: 20px 20px 20px 20px; - border: 1px solid #ccc; - color: #777; - font-size: 14px; - margin-top: 25px; - padding: 15px; - text-align: center; -} diff --git a/importer/indico_importer/controllers.py b/importer/indico_importer/controllers.py deleted file mode 100644 index d5d2b52..0000000 --- a/importer/indico_importer/controllers.py +++ /dev/null @@ -1,103 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from datetime import datetime - -from flask import jsonify, request -from flask_pluginengine import current_plugin -from pytz import timezone, utc - -from indico.core.db import db -from indico.modules.events.timetable.controllers import RHManageTimetableBase -from indico.modules.events.timetable.models.entries import TimetableEntry, TimetableEntryType -from indico.web.rh import RHProtected - - -class RHGetImporters(RHProtected): - def _process(self): - importers = {k: importer.name for k, (importer, _) in current_plugin.importer_engines.iteritems()} - return jsonify(importers) - - -class RHImportData(RHProtected): - def _process(self): - size = request.args.get('size', 10) - query = request.args.get('query') - importer, plugin = current_plugin.importer_engines.get(request.view_args['importer_name']) - with plugin.plugin_context(): - data = {'records': importer.import_data(query, size)} - return jsonify(data) - - -class RHEndTimeBase(RHManageTimetableBase): - """Base class for the importer operations""" - normalize_url_spec = { - 'locators': { - lambda self: self.event - }, - 'preserved_args': { - 'importer_name' - } - } - - @staticmethod - def _find_latest_end_dt(entries): - latest_dt = None - for entry in entries: - if latest_dt is None or entry.end_dt > latest_dt: - latest_dt = entry.end_dt - return latest_dt - - def _format_date(self, date): - return date.astimezone(timezone(self.event.timezone)).strftime('%H:%M') - - -class RHDayEndTime(RHEndTimeBase): - """Get the end_dt of the latest timetable entry or the event start_dt if no entry exist on that date""" - - def _process_args(self): - RHEndTimeBase._process_args(self) - self.date = self.event.tzinfo.localize(datetime.strptime(request.args['selectedDay'], '%Y/%m/%d')).date() - - def _process(self): - event_start_date = db.cast(TimetableEntry.start_dt.astimezone(self.event.tzinfo), db.Date) - entries = self.event.timetable_entries.filter(event_start_date == self.date) - latest_end_dt = self._find_latest_end_dt(entries) - if latest_end_dt is None: - event_start = self.event.start_dt - latest_end_dt = utc.localize(datetime.combine(self.date, event_start.time())) - return self._format_date(latest_end_dt) - - -class RHBlockEndTime(RHEndTimeBase): - """Return the end_dt of the latest timetable entry inside the block or the block start_dt if it is empty""" - - normalize_url_spec = { - 'locators': { - lambda self: self.timetable_entry - }, - 'preserved_args': { - 'importer_name' - } - } - - def _process_args(self): - RHEndTimeBase._process_args(self) - self.date = timezone(self.event.timezone).localize(datetime.strptime(request.args['selectedDay'], '%Y/%m/%d')) - self.timetable_entry = (self.event.timetable_entries - .filter_by(type=TimetableEntryType.SESSION_BLOCK, - id=request.view_args['entry_id']) - .first_or_404()) - - def _process(self): - entries = self.timetable_entry.children - latest_end_dt = self._find_latest_end_dt(entries) - if latest_end_dt is None: - latest_end_dt = self.timetable_entry.start_dt - return self._format_date(latest_end_dt) diff --git a/importer/indico_importer/converter.py b/importer/indico_importer/converter.py deleted file mode 100644 index f8b138c..0000000 --- a/importer/indico_importer/converter.py +++ /dev/null @@ -1,103 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - - -APPEND = object() - - -class RecordConverter(object): - """ - Converts a dictionary or list of dictionaries into another list of dictionaries. The goal - is to alter data fetched from connector class into a format that can be easily read by importer - plugin. The way dictionaries are converted depends on the 'conversion' variable. - - conversion = [ (sourceKey, destinationKey, conversionFuncion(optional), converter(optional))... ] - - It's a list tuples in which a single element represents a translation that will be made. Every - element of the list is a tuple that consists of from 1 to 4 entries. - - The first one is the key name in the source dictionary, the value that applies to this key will - be the subject of the translation. The second is the key in the destination dictionary at which - translated value will be put. If not specified its value will be equal the value of the first - element. If the second element is equal *append* and the converted element is a dictionary or a - list of dictionaries, destination dictionary will be updated by the converted element.Third, - optional, element is the function that will take the value from the source dictionary and return - the value which will be inserted into result dictionary. If the third element is empty - defaultConversionMethod will be called. Fourth, optional, element is a RecordConverter class - which will be executed with converted value as an argument. - """ - - conversion = [] - - @staticmethod - def default_conversion_method(attr): - """ - Method that will be used to convert an entry in dictionary unless other method is specified. - """ - return attr - - @classmethod - def convert(cls, record): - """ - Converts a single dictionary or list of dictionaries into converted list of dictionaries. - """ - if isinstance(record, list): - return [cls._convert(r) for r in record] - else: - return [cls._convert(record)] - - @classmethod - def _convert_internal(cls, record): - """ - Converts a single dictionary into converted dictionary or list of dictionaries into converted - list of dictionaries. Used while passing dictionaries to another converter. - """ - if isinstance(record, list): - return [cls._convert(r) for r in record] - else: - return cls._convert(record) - - @classmethod - def _convert(cls, record): - """ - Core method of the converter. Converts a single dictionary into another dictionary. - """ - if not record: - return {} - - converted_dict = {} - for field in cls.conversion: - key = field[0] - if len(field) >= 2 and field[1]: - converted_key = field[1] - else: - converted_key = key - if len(field) >= 3 and field[2]: - conversion_method = field[2] - else: - conversion_method = cls.default_conversion_method - if len(field) >= 4: - converter = field[3] - else: - converter = None - try: - value = conversion_method(record[key]) - except KeyError: - continue - if converter: - value = converter._convert_internal(value) - if converted_key is APPEND: - if isinstance(value, list): - for v in value: - converted_dict.update(v) - else: - converted_dict.update(value) - else: - converted_dict[converted_key] = value - return converted_dict diff --git a/importer/indico_importer/plugin.py b/importer/indico_importer/plugin.py deleted file mode 100644 index 4dc2be7..0000000 --- a/importer/indico_importer/plugin.py +++ /dev/null @@ -1,63 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from indico.core import signals -from indico.core.plugins import IndicoPlugin, IndicoPluginBlueprint, PluginCategory, plugin_url_rule_to_js -from indico.modules.events.timetable.views import WPManageTimetable -from indico.web.flask.util import url_rule_to_js - -from indico_importer import _ -from indico_importer.controllers import RHBlockEndTime, RHDayEndTime, RHGetImporters, RHImportData - - -class ImporterPlugin(IndicoPlugin): - """Importer - - Extends Indico for other plugins to import data from external sources to - the timetable. - """ - category = PluginCategory.importers - - def init(self): - super(ImporterPlugin, self).init() - self.inject_bundle('main.js', WPManageTimetable) - self.inject_bundle('main.css', WPManageTimetable) - self.connect(signals.event.timetable_buttons, self.get_timetable_buttons) - self.importer_engines = {} - - def get_blueprints(self): - return blueprint - - def get_timetable_buttons(self, *args, **kwargs): - if self.importer_engines: - yield _('Importer'), 'create-importer-dialog' - - def get_vars_js(self): - return {'urls': {'import_data': plugin_url_rule_to_js('importer.import_data'), - 'importers': plugin_url_rule_to_js('importer.importers'), - 'day_end_date': plugin_url_rule_to_js('importer.day_end_date'), - 'block_end_date': plugin_url_rule_to_js('importer.block_end_date'), - 'add_contrib': url_rule_to_js('timetable.add_contribution'), - 'create_subcontrib_rest': url_rule_to_js('contributions.create_subcontrib_rest'), - 'create_contrib_reference_rest': url_rule_to_js('contributions.create_contrib_reference_rest'), - 'create_subcontrib_reference_rest': url_rule_to_js('contributions' - '.create_subcontrib_reference_rest'), - 'add_link': url_rule_to_js('attachments.add_link')}} - - def register_importer_engine(self, importer_engine, plugin): - self.importer_engines[importer_engine._id] = (importer_engine, plugin) - - -blueprint = IndicoPluginBlueprint('importer', __name__) -blueprint.add_url_rule('/importers//search', 'import_data', RHImportData, methods=('POST',)) -blueprint.add_url_rule('/importers/', 'importers', RHGetImporters) - -blueprint.add_url_rule('/importers//event//day-end-date', 'day_end_date', RHDayEndTime) -blueprint.add_url_rule('/importers//event//entry//block-end-date', 'block_end_date', - RHBlockEndTime) diff --git a/importer/indico_importer/translations/fr_FR/LC_MESSAGES/messages-js.po b/importer/indico_importer/translations/fr_FR/LC_MESSAGES/messages-js.po deleted file mode 100644 index bfd5d99..0000000 --- a/importer/indico_importer/translations/fr_FR/LC_MESSAGES/messages-js.po +++ /dev/null @@ -1,228 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2017 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# -# Translators: -# Thomas Baron , 2015,2017 -msgid "" -msgstr "" -"Project-Id-Version: Indico\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-10-18 11:55+0200\n" -"PO-Revision-Date: 2017-10-27 14:56+0000\n" -"Last-Translator: Thomas Baron \n" -"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" -"Language: fr_FR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: indico_importer/static/js/importer.js:31 -msgid "Jan" -msgstr "Jan" - -#: indico_importer/static/js/importer.js:32 -msgid "Feb" -msgstr "Fév" - -#: indico_importer/static/js/importer.js:33 -msgid "Mar" -msgstr "Mar" - -#: indico_importer/static/js/importer.js:34 -msgid "Apr" -msgstr "Avr" - -#: indico_importer/static/js/importer.js:35 -msgid "May" -msgstr "Mai" - -#: indico_importer/static/js/importer.js:36 -msgid "Jun" -msgstr "Juin" - -#: indico_importer/static/js/importer.js:37 -msgid "Jul" -msgstr "Juil" - -#: indico_importer/static/js/importer.js:38 -msgid "Aug" -msgstr "Aoû" - -#: indico_importer/static/js/importer.js:39 -msgid "Sep" -msgstr "Sep" - -#: indico_importer/static/js/importer.js:40 -msgid "Oct" -msgstr "Oct" - -#: indico_importer/static/js/importer.js:41 -msgid "Nov" -msgstr "Nov" - -#: indico_importer/static/js/importer.js:42 -msgid "Dec" -msgstr "Déc" - -#: indico_importer/static/js/importer.js:111 -msgid "Something went wrong" -msgstr "Une erreur est survenue" - -#: indico_importer/static/js/importer.js:270 -msgid "search" -msgstr "rechercher" - -#: indico_importer/static/js/importer.js:301 -msgid "in" -msgstr "dans" - -#: indico_importer/static/js/importer.js:308 -msgid "Proceed..." -msgstr "Continuer..." - -#: indico_importer/static/js/importer.js:322 -msgid "Close" -msgstr "Fermer" - -#: indico_importer/static/js/importer.js:331 -msgid "Please select contributions to be added and their destination." -msgstr "Veuillez sélectionner les contributions à ajouter et leur destination." - -#: indico_importer/static/js/importer.js:357 -msgid "Import Entries" -msgstr "Importer les entrées" - -#: indico_importer/static/js/importer.js:397 -msgid "Please type your search phrase and press 'search'." -msgstr "Veuillez entrer votre phrase de recherche et appuyer sur 'rechercher'." - -#: indico_importer/static/js/importer.js:398 -msgid "here" -msgstr "ici" - -#: indico_importer/static/js/importer.js:402 -msgid "" -"Your entries were inserted successfully. Please specify a new query or click" -msgstr "Vos entrées ont été insérées. Veuillez indiquer une nouvelle recherche ou cliquer sur " - -#: indico_importer/static/js/importer.js:402 -msgid "to see the previous results." -msgstr "pour voir les résultats précédents." - -#: indico_importer/static/js/importer.js:477 -msgid "Duration time of every inserted contribution:" -msgstr "Durée de chaque contribution insérée: " - -#: indico_importer/static/js/importer.js:478 -msgid "Start time of the first contribution:" -msgstr "Heure de début de la première contribution: " - -#: indico_importer/static/js/importer.js:479 -msgid "Show me the destination:" -msgstr "Afficher la destination: " - -#: indico_importer/static/js/importer.js:604 -msgid "Insert" -msgstr "Insérer" - -#: indico_importer/static/js/importer.js:686 -msgid "Cancel" -msgstr "Annuler" - -#: indico_importer/static/js/importer.js:708 -msgid "Adjust entries" -msgstr "Ajuster les entrées" - -#: indico_importer/static/js/importer.js:944 -msgid "Report number(s)" -msgstr "Numéros de rapports" - -#: indico_importer/static/js/importer.js:951 -msgid "Title" -msgstr "Titre" - -#: indico_importer/static/js/importer.js:954 -msgid "Meeting" -msgstr "Réunion" - -#: indico_importer/static/js/importer.js:958 -msgid "Primary author" -msgstr "Auteur principal" - -#: indico_importer/static/js/importer.js:961 -msgid "Secondary author" -msgstr "Auteur secondaire" - -#: indico_importer/static/js/importer.js:964 -msgid "Speaker" -msgstr "Orateur" - -#: indico_importer/static/js/importer.js:970 -#: indico_importer/static/js/importer.js:994 -msgid "Summary" -msgstr "Récapitulatif" - -#: indico_importer/static/js/importer.js:974 -msgid " (show all)" -msgstr "(afficher tout)" - -#: indico_importer/static/js/importer.js:984 -msgid " (hide)" -msgstr "(cacher)" - -#: indico_importer/static/js/importer.js:999 -msgid "Place" -msgstr "Lieu" - -#: indico_importer/static/js/importer.js:1003 -msgid "Materials" -msgstr "Documents" - -#: indico_importer/static/js/importer.js:1044 -msgid "st" -msgstr "er" - -#: indico_importer/static/js/importer.js:1047 -msgid "nd" -msgstr "ème" - -#: indico_importer/static/js/importer.js:1050 -msgid "rd" -msgstr "ème" - -#: indico_importer/static/js/importer.js:1053 -msgid "th" -msgstr "ème" - -#: indico_importer/static/js/importer.js:1136 -msgid "One entry was found. " -msgid_plural "{0} entries were found. " -msgstr[0] "Une entrée trouvée." -msgstr[1] "{0} entrées trouvées." - -#: indico_importer/static/js/importer.js:1201 -msgid "Please select the results you want to insert." -msgstr "Veuillez sélectionner les résultats que vous souhaitez insérer." - -#: indico_importer/static/js/importer.js:1202 -msgid "No results were found. Please change the search phrase." -msgstr "Aucun résultat. Veuillez modifier la phrase de recherche." - -#: indico_importer/static/js/importer.js:1203 -msgid "Step 1: Search results:" -msgstr "Étape 1: Sélectionnez les entrées à insérer:" - -#: indico_importer/static/js/importer.js:1208 -msgid "more results" -msgstr "plus de résultats" - -#: indico_importer/static/js/importer.js:1445 -msgid "Step 2: Choose destination:" -msgstr "Étape 2: Choisissez la destination:" - -#: indico_importer/static/js/importer.js:1446 -msgid "Please select the place in which the contributions will be inserted." -msgstr "Veuillez sélectionner l'endroit où les contributions seront insérées." diff --git a/importer/indico_importer/translations/fr_FR/LC_MESSAGES/messages.po b/importer/indico_importer/translations/fr_FR/LC_MESSAGES/messages.po deleted file mode 100644 index df466d2..0000000 --- a/importer/indico_importer/translations/fr_FR/LC_MESSAGES/messages.po +++ /dev/null @@ -1,24 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2017 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# -# Translators: -# Thomas Baron , 2015 -msgid "" -msgstr "" -"Project-Id-Version: Indico\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-10-18 11:55+0200\n" -"PO-Revision-Date: 2017-09-23 20:55+0000\n" -"Last-Translator: Thomas Baron \n" -"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" -"Language: fr_FR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: indico_importer/plugin.py:48 -msgid "Importer" -msgstr "Importateur" diff --git a/importer/indico_importer/translations/messages-js.pot b/importer/indico_importer/translations/messages-js.pot deleted file mode 100644 index 0f354ef..0000000 --- a/importer/indico_importer/translations/messages-js.pot +++ /dev/null @@ -1,227 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2020 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2020. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-08-19 20:40+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.8.0\n" - -#: indico_importer/client/index.js:23 -msgid "Jan" -msgstr "" - -#: indico_importer/client/index.js:24 -msgid "Feb" -msgstr "" - -#: indico_importer/client/index.js:25 -msgid "Mar" -msgstr "" - -#: indico_importer/client/index.js:26 -msgid "Apr" -msgstr "" - -#: indico_importer/client/index.js:27 -msgid "May" -msgstr "" - -#: indico_importer/client/index.js:28 -msgid "Jun" -msgstr "" - -#: indico_importer/client/index.js:29 -msgid "Jul" -msgstr "" - -#: indico_importer/client/index.js:30 -msgid "Aug" -msgstr "" - -#: indico_importer/client/index.js:31 -msgid "Sep" -msgstr "" - -#: indico_importer/client/index.js:32 -msgid "Oct" -msgstr "" - -#: indico_importer/client/index.js:33 -msgid "Nov" -msgstr "" - -#: indico_importer/client/index.js:34 -msgid "Dec" -msgstr "" - -#: indico_importer/client/index.js:107 -msgid "Something went wrong" -msgstr "" - -#: indico_importer/client/index.js:270 -msgid "search" -msgstr "" - -#: indico_importer/client/index.js:321 -msgid "in" -msgstr "" - -#: indico_importer/client/index.js:335 -msgid "Proceed..." -msgstr "" - -#: indico_importer/client/index.js:359 -msgid "Close" -msgstr "" - -#: indico_importer/client/index.js:370 -msgid "Please select contributions to be added and their destination." -msgstr "" - -#: indico_importer/client/index.js:396 -msgid "Import Entries" -msgstr "" - -#: indico_importer/client/index.js:440 -msgid "Please type your search phrase and press 'search'." -msgstr "" - -#: indico_importer/client/index.js:442 -msgid "here" -msgstr "" - -#: indico_importer/client/index.js:446 -msgid "" -"Your entries were inserted successfully. Please specify a new query or " -"click" -msgstr "" - -#: indico_importer/client/index.js:452 -msgid "to see the previous results." -msgstr "" - -#: indico_importer/client/index.js:537 -msgid "Duration time of every inserted contribution:" -msgstr "" - -#: indico_importer/client/index.js:538 -msgid "Start time of the first contribution:" -msgstr "" - -#: indico_importer/client/index.js:539 -msgid "Show me the destination:" -msgstr "" - -#: indico_importer/client/index.js:668 -msgid "Insert" -msgstr "" - -#: indico_importer/client/index.js:777 -msgid "Cancel" -msgstr "" - -#: indico_importer/client/index.js:801 -msgid "Adjust entries" -msgstr "" - -#: indico_importer/client/index.js:1052 -msgid "Report number(s)" -msgstr "" - -#: indico_importer/client/index.js:1060 -msgid "Title" -msgstr "" - -#: indico_importer/client/index.js:1065 -msgid "Meeting" -msgstr "" - -#: indico_importer/client/index.js:1073 -msgid "Primary author" -msgstr "" - -#: indico_importer/client/index.js:1083 -msgid "Secondary author" -msgstr "" - -#: indico_importer/client/index.js:1093 -msgid "Speaker" -msgstr "" - -#: indico_importer/client/index.js:1103 indico_importer/client/index.js:1132 -msgid "Summary" -msgstr "" - -#: indico_importer/client/index.js:1107 -msgid " (show all)" -msgstr "" - -#: indico_importer/client/index.js:1119 -msgid " (hide)" -msgstr "" - -#: indico_importer/client/index.js:1144 -msgid "Place" -msgstr "" - -#: indico_importer/client/index.js:1149 -msgid "Materials" -msgstr "" - -#: indico_importer/client/index.js:1193 -msgid "st" -msgstr "" - -#: indico_importer/client/index.js:1196 -msgid "nd" -msgstr "" - -#: indico_importer/client/index.js:1199 -msgid "rd" -msgstr "" - -#: indico_importer/client/index.js:1202 -msgid "th" -msgstr "" - -#: indico_importer/client/index.js:1291 -msgid "One entry was found. " -msgid_plural "{0} entries were found. " -msgstr[0] "" -msgstr[1] "" - -#: indico_importer/client/index.js:1367 -msgid "Please select the results you want to insert." -msgstr "" - -#: indico_importer/client/index.js:1371 -msgid "No results were found. Please change the search phrase." -msgstr "" - -#: indico_importer/client/index.js:1375 -msgid "Step 1: Search results:" -msgstr "" - -#: indico_importer/client/index.js:1393 -msgid "more results" -msgstr "" - -#: indico_importer/client/index.js:1667 -msgid "Step 2: Choose destination:" -msgstr "" - -#: indico_importer/client/index.js:1670 -msgid "Please select the place in which the contributions will be inserted." -msgstr "" - diff --git a/importer/indico_importer/translations/messages.pot b/importer/indico_importer/translations/messages.pot deleted file mode 100644 index 1c4ecf6..0000000 --- a/importer/indico_importer/translations/messages.pot +++ /dev/null @@ -1,23 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2020 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2020. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-08-19 20:40+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.8.0\n" - -#: indico_importer/plugin.py:39 -msgid "Importer" -msgstr "" - diff --git a/importer/indico_importer/util.py b/importer/indico_importer/util.py deleted file mode 100644 index 96f8693..0000000 --- a/importer/indico_importer/util.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - - -def convert_dt_tuple(dt_tuple): - split_datetime = dt_tuple[0].split('T') - if len(split_datetime) > 1: - return {'date': dt_tuple[0].split('T')[0], - 'time': dt_tuple[0].split('T')[1]} - else: - return {'date': dt_tuple[0].split('T')[0], - 'time': '00:00'} diff --git a/importer/setup.py b/importer/setup.py deleted file mode 100644 index 59bf38f..0000000 --- a/importer/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from setuptools import find_packages, setup - - -setup( - name='indico-plugin-importer', - version='2.2', - description='Framework for importing timetable data from external sources into Indico', - url='https://github.com/indico/indico-plugins', - license='MIT', - author='Indico Team', - author_email='indico-team@cern.ch', - packages=find_packages(), - zip_safe=False, - include_package_data=True, - install_requires=[ - 'indico>=2.2.dev0' - ], - classifiers=[ - 'Environment :: Plugins', - 'Environment :: Web Environment', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2.7' - ], - entry_points={'indico.plugins': {'importer = indico_importer.plugin:ImporterPlugin'}} -) diff --git a/importer/webpack-bundles.json b/importer/webpack-bundles.json deleted file mode 100644 index 5f28835..0000000 --- a/importer/webpack-bundles.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": { - "main": "./index.js" - } -} diff --git a/importer_invenio/MANIFEST.in b/importer_invenio/MANIFEST.in deleted file mode 100644 index 9f1d4c4..0000000 --- a/importer_invenio/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -graft indico_importer_invenio/translations - -global-exclude *.pyc __pycache__ .keep diff --git a/importer_invenio/indico_importer_invenio/__init__.py b/importer_invenio/indico_importer_invenio/__init__.py deleted file mode 100644 index ef7b709..0000000 --- a/importer_invenio/indico_importer_invenio/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from indico.util.i18n import make_bound_gettext - - -_ = make_bound_gettext('importer_invenio') diff --git a/importer_invenio/indico_importer_invenio/connector.py b/importer_invenio/indico_importer_invenio/connector.py deleted file mode 100644 index 98d5915..0000000 --- a/importer_invenio/indico_importer_invenio/connector.py +++ /dev/null @@ -1,643 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -# flake8: noqa - -""" -Tools to connect to distant Invenio servers using Invenio APIs. - - -Example of use: - -from InvenioConnector import * -cds = InvenioConnector("http://cds.cern.ch") - -results = cds.search("higgs") - -for record in results: - print record["245__a"][0] - print record["520__b"][0] - for author in record["100__"]: - print author["a"][0], author["u"][0] - -FIXME: -- implement cache expiration -- exceptions handling -- parsing of -- better checking of input parameters -- improve behaviour when running locally - (perform_request_search *requiring* "req" object) -""" - -from __future__ import print_function - -import os -import re -import sys -import tempfile -import time -import urllib -import urllib2 -import xml.sax - -import requests -from requests.exceptions import ConnectionError, InvalidSchema, InvalidURL, MissingSchema, RequestException - - -MECHANIZE_CLIENTFORM_VERSION_CHANGE = (0, 2, 0) -try: - import mechanize - if mechanize.__version__ < MECHANIZE_CLIENTFORM_VERSION_CHANGE: - OLD_MECHANIZE_VERSION = True - import ClientForm - else: - OLD_MECHANIZE_VERSION = False - MECHANIZE_AVAILABLE = True -except ImportError: - MECHANIZE_AVAILABLE = False - -try: - # if we are running locally, we can optimize :-) - from invenio.config import CFG_SITE_URL, CFG_SITE_SECURE_URL, CFG_SITE_RECORD, CFG_CERN_SITE - from invenio.legacy.bibsched.bibtask import task_low_level_submission - from invenio.legacy.search_engine import perform_request_search, collection_restricted_p - from invenio.modules.formatter import format_records - from invenio.utils.url import make_user_agent_string - LOCAL_SITE_URLS = [CFG_SITE_URL, CFG_SITE_SECURE_URL] - CFG_USER_AGENT = make_user_agent_string("invenio_connector") -except ImportError: - LOCAL_SITE_URLS = None - CFG_CERN_SITE = 0 - CFG_USER_AGENT = "invenio_connector" - -CFG_CDS_URL = "http://cds.cern.ch/" - -class InvenioConnectorAuthError(Exception): - """ - This exception is called by InvenioConnector when authentication fails during - remote or local connections. - """ - def __init__(self, value): - """ - Set the internal "value" attribute to that of the passed "value" parameter. - @param value: an error string to display to the user. - @type value: string - """ - Exception.__init__(self) - self.value = value - def __str__(self): - """ - Return oneself as a string (actually, return the contents of self.value). - @return: representation of error - @rtype: string - """ - return str(self.value) - -class InvenioConnectorServerError(Exception): - """ - This exception is called by InvenioConnector when using it on a machine with no - Invenio installed and no remote server URL is given during instantiation. - """ - def __init__(self, value): - """ - Set the internal "value" attribute to that of the passed "value" parameter. - @param value: an error string to display to the user. - @type value: string - """ - Exception.__init__(self) - self.value = value - def __str__(self): - """ - Return oneself as a string (actually, return the contents of self.value). - @return: representation of error - @rtype: string - """ - return str(self.value) - -class InvenioConnector(object): - """ - Creates an connector to a server running Invenio - """ - - def __init__(self, url=None, user="", password="", login_method="Local", - local_import_path="invenio", insecure_login=False): - """ - Initialize a new instance of the server at given URL. - - If the server happens to be running on the local machine, the - access will be done directly using the Python APIs. In that case - you can choose from which base path to import the necessary file - specifying the local_import_path parameter. - - @param url: the url to which this instance will be connected. - Defaults to CFG_SITE_URL, if available. - @type url: string - @param user: the optional username for interacting with the Invenio - instance in an authenticated way. - @type user: string - @param password: the corresponding password. - @type password: string - @param login_method: the name of the login method the Invenio instance - is expecting for this user (in case there is more than one). - @type login_method: string - @param local_import_path: the base path from which the connector should - try to load the local connector, if available. Eg "invenio" will - lead to "import invenio.dbquery" - @type local_import_path: string - - @raise InvenioConnectorAuthError: if no secure URL is given for authentication - @raise InvenioConnectorServerError: if no URL is given on a machine without Invenio installed - """ - if url == None and LOCAL_SITE_URLS != None: - self.server_url = LOCAL_SITE_URLS[0] # Default to CFG_SITE_URL - elif url == None: - raise InvenioConnectorServerError("You do not seem to have Invenio installed and no remote URL is given") - else: - self.server_url = url - self._validate_server_url() - - self.local = LOCAL_SITE_URLS and self.server_url in LOCAL_SITE_URLS - self.cached_queries = {} - self.cached_records = {} - self.cached_baskets = {} - self.user = user - self.password = password - self.login_method = login_method - self.browser = None - if self.user: - if not insecure_login and not self.server_url.startswith('https://'): - raise InvenioConnectorAuthError("You have to use a secure URL (HTTPS) to login") - if MECHANIZE_AVAILABLE: - self._init_browser() - self._check_credentials() - else: - self.user = None - raise InvenioConnectorAuthError("The Python module Mechanize (and ClientForm" \ - " if Mechanize version < 0.2.0) must" \ - " be installed to perform authenticated requests.") - - def _init_browser(self): - """ - Ovveride this method with the appropriate way to prepare a logged in - browser. - """ - self.browser = mechanize.Browser() - self.browser.set_handle_robots(False) - self.browser.open(self.server_url + "/youraccount/login") - self.browser.select_form(nr=0) - try: - self.browser['nickname'] = self.user - self.browser['password'] = self.password - except: - self.browser['p_un'] = self.user - self.browser['p_pw'] = self.password - # Set login_method to be writable - self.browser.form.find_control('login_method').readonly = False - self.browser['login_method'] = self.login_method - self.browser.submit() - - def _check_credentials(self): - out = self.browser.response().read() - if not 'youraccount/logout' in out: - raise InvenioConnectorAuthError("It was not possible to successfully login with the provided credentials" + out) - - def search(self, read_cache=True, **kwparams): - """ - Returns records corresponding to the given search query. - - See docstring of invenio.legacy.search_engine.perform_request_search() - for an overview of available parameters. - - @raise InvenioConnectorAuthError: if authentication fails - """ - parse_results = False - of = kwparams.get('of', "") - if of == "": - parse_results = True - of = "xm" - kwparams['of'] = of - params = urllib.urlencode(kwparams, doseq=1) - - # Are we running locally? If so, better directly access the - # search engine directly - if self.local and of != 't': - # See if user tries to search any restricted collection - c = kwparams.get('c', "") - if c != "": - if type(c) is list: - colls = c - else: - colls = [c] - for collection in colls: - if collection_restricted_p(collection): - if self.user: - self._check_credentials() - continue - raise InvenioConnectorAuthError("You are trying to search a restricted collection. Please authenticate yourself.\n") - kwparams['of'] = 'id' - results = perform_request_search(**kwparams) - if of.lower() != 'id': - results = format_records(results, of) - else: - if params + str(parse_results) not in self.cached_queries or not read_cache: - if self.user: - results = self.browser.open(self.server_url + "/search?" + params) - else: - results = urllib2.urlopen(self.server_url + "/search?" + params) - if 'youraccount/login' in results.geturl(): - # Current user not able to search collection - raise InvenioConnectorAuthError("You are trying to search a restricted collection. Please authenticate yourself.\n") - else: - return self.cached_queries[params + str(parse_results)] - - if parse_results: - # FIXME: we should not try to parse if results is string - parsed_records = self._parse_results(results, self.cached_records) - self.cached_queries[params + str(parse_results)] = parsed_records - return parsed_records - else: - # pylint: disable=E1103 - # The whole point of the following code is to make sure we can - # handle two types of variable. - try: - res = results.read() - except AttributeError: - res = results - # pylint: enable=E1103 - - if of == "id": - try: - if type(res) is str: - # Transform to list - res = [int(recid.strip()) for recid in \ - res.strip("[]").split(",") if recid.strip() != ""] - res.reverse() - except (ValueError, AttributeError): - res = [] - self.cached_queries[params + str(parse_results)] = res - return self.cached_queries[params + str(parse_results)] - - def search_with_retry(self, sleeptime=3.0, retrycount=3, **params): - """ - This function performs a search given a dictionary of search(..) - parameters. It accounts for server timeouts as necessary and - will retry some number of times. - - @param sleeptime: number of seconds to sleep between retries - @type sleeptime: float - - @param retrycount: number of times to retry given search - @type retrycount: int - - @param params: search parameters - @type params: **kwds - - @rtype: list - @return: returns records in given format - """ - results = [] - count = 0 - while count < retrycount: - try: - results = self.search(**params) - break - except urllib2.URLError: - sys.stderr.write("Timeout while searching...Retrying\n") - time.sleep(sleeptime) - count += 1 - else: - sys.stderr.write("Aborting search after %d attempts.\n" % (retrycount,)) - return results - - def search_similar_records(self, recid): - """ - Returns the records similar to the given one - """ - return self.search(p="recid:" + str(recid), rm="wrd") - - def search_records_cited_by(self, recid): - """ - Returns records cited by the given one - """ - return self.search(p="recid:" + str(recid), rm="citation") - - def get_records_from_basket(self, bskid, group_basket=False, read_cache=True): - """ - Returns the records from the (public) basket with given bskid - """ - if bskid not in self.cached_baskets or not read_cache: - if self.user: - if group_basket: - group_basket = '&category=G' - else: - group_basket = '' - results = self.browser.open(self.server_url + \ - "/yourbaskets/display?of=xm&bskid=" + str(bskid) + group_basket) - else: - results = urllib2.urlopen(self.server_url + \ - "/yourbaskets/display_public?of=xm&bskid=" + str(bskid)) - else: - return self.cached_baskets[bskid] - - parsed_records = self._parse_results(results, self.cached_records) - self.cached_baskets[bskid] = parsed_records - return parsed_records - - def get_record(self, recid, read_cache=True): - """ - Returns the record with given recid - """ - if recid in self.cached_records or not read_cache: - return self.cached_records[recid] - else: - return self.search(p="recid:" + str(recid)) - - def upload_marcxml(self, marcxml, mode): - """ - Uploads a record to the server - - Parameters: - marcxml - *str* the XML to upload. - mode - *str* the mode to use for the upload. - "-i" insert new records - "-r" replace existing records - "-c" correct fields of records - "-a" append fields to records - "-ir" insert record or replace if it exists - """ - if mode not in ["-i", "-r", "-c", "-a", "-ir"]: - raise NameError, "Incorrect mode " + str(mode) - - # Are we running locally? If so, submit directly - if self.local: - (code, marcxml_filepath) = tempfile.mkstemp(prefix="upload_%s" % \ - time.strftime("%Y%m%d_%H%M%S_", - time.localtime())) - marcxml_file_d = os.fdopen(code, "w") - marcxml_file_d.write(marcxml) - marcxml_file_d.close() - return task_low_level_submission("bibupload", "", mode, marcxml_filepath) - else: - params = urllib.urlencode({'file': marcxml, - 'mode': mode}) - ## We don't use self.browser as batchuploader is protected by IP - opener = urllib2.build_opener() - opener.addheaders = [('User-Agent', CFG_USER_AGENT)] - return opener.open(self.server_url + "/batchuploader/robotupload", params,) - - def _parse_results(self, results, cached_records): - """ - Parses the given results (in MARCXML format). - - The given "cached_records" list is a pool of - already existing parsed records (in order to - avoid keeping several times the same records in memory) - """ - parser = xml.sax.make_parser() - handler = RecordsHandler(cached_records) - parser.setContentHandler(handler) - parser.parse(results) - return handler.records - - def _validate_server_url(self): - """Validates self.server_url""" - try: - request = requests.head(self.server_url) - if request.status_code >= 400: - raise InvenioConnectorServerError( - "Unexpected status code '%d' accessing URL: %s" - % (request.status_code, self.server_url)) - except (InvalidSchema, MissingSchema) as err: - raise InvenioConnectorServerError( - "Bad schema, expecting http:// or https://:\n %s" % (err,)) - except ConnectionError as err: - raise InvenioConnectorServerError( - "Couldn't establish connection to '%s':\n %s" - % (self.server_url, err)) - except InvalidURL as err: - raise InvenioConnectorServerError( - "Invalid URL '%s':\n %s" - % (self.server_url, err)) - except RequestException as err: - raise InvenioConnectorServerError( - "Unknown error connecting to '%s':\n %s" - % (self.server_url, err)) - - -class Record(dict): - """ - Represents a Invenio record - """ - def __init__(self, recid=None, marcxml=None, server_url=None): - #dict.__init__(self) - self.recid = recid - self.marcxml = "" - if marcxml is not None: - self.marcxml = marcxml - #self.record = {} - self.server_url = server_url - - def __setitem__(self, item, value): - tag, ind1, ind2, subcode = decompose_code(item) - if subcode is not None: - #if not dict.has_key(self, tag + ind1 + ind2): - # dict.__setitem__(self, tag + ind1 + ind2, []) - dict.__setitem__(self, tag + ind1 + ind2, [{subcode: [value]}]) - else: - dict.__setitem__(self, tag + ind1 + ind2, value) - - def __getitem__(self, item): - tag, ind1, ind2, subcode = decompose_code(item) - - datafields = dict.__getitem__(self, tag + ind1 + ind2) - if subcode is not None: - subfields = [] - for datafield in datafields: - if subcode in datafield: - subfields.extend(datafield[subcode]) - return subfields - else: - return datafields - - def __repr__(self): - return "Record(" + dict.__repr__(self) + ")" - - def __str__(self): - return self.marcxml - - def export(self, of="marcxml"): - """ - Returns the record in chosen format - """ - return self.marcxml - - def url(self): - """ - Returns the URL to this record. - Returns None if not known - """ - if self.server_url is not None and \ - self.recid is not None: - return self.server_url + "/"+ CFG_SITE_RECORD +"/" + str(self.recid) - else: - return None -if MECHANIZE_AVAILABLE: - class _SGMLParserFactory(mechanize.DefaultFactory): - """ - Black magic to be able to interact with CERN SSO forms. - """ - def __init__(self, i_want_broken_xhtml_support=False): - if OLD_MECHANIZE_VERSION: - forms_factory = mechanize.FormsFactory( - form_parser_class=ClientForm.XHTMLCompatibleFormParser) - else: - forms_factory = mechanize.FormsFactory( - form_parser_class=mechanize.XHTMLCompatibleFormParser) - mechanize.Factory.__init__( - self, - forms_factory=forms_factory, - links_factory=mechanize.LinksFactory(), - title_factory=mechanize.TitleFactory(), - response_type_finder=mechanize._html.ResponseTypeFinder( - allow_xhtml=i_want_broken_xhtml_support), - ) - - class CDSInvenioConnector(InvenioConnector): - def __init__(self, user="", password="", local_import_path="invenio"): - """ - This is a specialized InvenioConnector class suitable to connect - to the CERN Document Server (CDS), which uses centralized SSO. - """ - cds_url = CFG_CDS_URL - if user: - cds_url = cds_url.replace('http', 'https') - super(CDSInvenioConnector, self).__init__(cds_url, user, password, local_import_path=local_import_path) - - def _init_browser(self): - """ - @note: update this everytime the CERN SSO login form is refactored. - """ - self.browser = mechanize.Browser(factory=_SGMLParserFactory(i_want_broken_xhtml_support=True)) - self.browser.set_handle_robots(False) - self.browser.open(self.server_url) - self.browser.follow_link(text_regex="Sign in") - self.browser.select_form(nr=0) - self.browser.form['ctl00$ctl00$NICEMasterPageBodyContent$SiteContentPlaceholder$txtFormsLogin'] = self.user - self.browser.form['ctl00$ctl00$NICEMasterPageBodyContent$SiteContentPlaceholder$txtFormsPassword'] = self.password - self.browser.submit() - self.browser.select_form(nr=0) - self.browser.submit() - -class RecordsHandler(xml.sax.handler.ContentHandler): - "MARCXML Parser" - - def __init__(self, records): - """ - Parameters: - records - *dict* a dictionary with an already existing cache of records - """ - self.cached_records = records - self.records = [] - self.in_record = False - self.in_controlfield = False - self.in_datafield = False - self.in_subfield = False - self.cur_tag = None - self.cur_subfield = None - self.cur_controlfield = None - self.cur_datafield = None - self.cur_record = None - self.recid = 0 - self.buffer = "" - self.counts = 0 - - def startElement(self, name, attributes): - if name == "record": - self.cur_record = Record() - self.in_record = True - - elif name == "controlfield": - tag = attributes["tag"] - self.cur_datafield = "" - self.cur_tag = tag - self.cur_controlfield = [] - if tag not in self.cur_record: - self.cur_record[tag] = self.cur_controlfield - self.in_controlfield = True - - elif name == "datafield": - tag = attributes["tag"] - self.cur_tag = tag - ind1 = attributes["ind1"] - if ind1 == " ": ind1 = "_" - ind2 = attributes["ind2"] - if ind2 == " ": ind2 = "_" - if tag + ind1 + ind2 not in self.cur_record: - self.cur_record[tag + ind1 + ind2] = [] - self.cur_datafield = {} - self.cur_record[tag + ind1 + ind2].append(self.cur_datafield) - self.in_datafield = True - - elif name == "subfield": - subcode = attributes["code"] - if subcode not in self.cur_datafield: - self.cur_subfield = [] - self.cur_datafield[subcode] = self.cur_subfield - else: - self.cur_subfield = self.cur_datafield[subcode] - self.in_subfield = True - - def characters(self, data): - if self.in_subfield: - self.buffer += data - elif self.in_controlfield: - self.buffer += data - elif "Search-Engine-Total-Number-Of-Results:" in data: - print(data) - match_obj = re.search("\d+", data) - if match_obj: - print(int(match_obj.group())) - self.counts = int(match_obj.group()) - - def endElement(self, name): - if name == "record": - self.in_record = False - elif name == "controlfield": - if self.cur_tag == "001": - self.recid = int(self.buffer) - if self.recid in self.cached_records: - # Record has already been parsed, no need to add - pass - else: - # Add record to the global cache - self.cached_records[self.recid] = self.cur_record - # Add record to the ordered list of results - self.records.append(self.cached_records[self.recid]) - - self.cur_controlfield.append(self.buffer) - self.in_controlfield = False - self.buffer = "" - elif name == "datafield": - self.in_datafield = False - elif name == "subfield": - self.in_subfield = False - self.cur_subfield.append(self.buffer) - self.buffer = "" - - -def decompose_code(code): - """ - Decomposes a MARC "code" into tag, ind1, ind2, subcode - """ - code = "%-6s" % code - ind1 = code[3:4] - if ind1 == " ": ind1 = "_" - ind2 = code[4:5] - if ind2 == " ": ind2 = "_" - subcode = code[5:6] - if subcode == " ": subcode = None - return (code[0:3], ind1, ind2, subcode) diff --git a/importer_invenio/indico_importer_invenio/converters.py b/importer_invenio/indico_importer_invenio/converters.py deleted file mode 100644 index 5c55e90..0000000 --- a/importer_invenio/indico_importer_invenio/converters.py +++ /dev/null @@ -1,80 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from indico.util.string import strip_tags - -from indico_importer.converter import APPEND, RecordConverter -from indico_importer.util import convert_dt_tuple - - -class InvenioRecordConverterBase(RecordConverter): - """ - Base class of every Invenio record converter. Data in Invenio records is always stored - in the list (e.g. author's name is stored as ["Joe Doe"]), so defaultConversion method - simply takes the first element form a list. - """ - - @staticmethod - def default_conversion_method(attr): - return attr[0] - - -class InvenioAuthorConverter(InvenioRecordConverterBase): - """ - Converts author name, surname and affiliation. - """ - - conversion = [('a', 'firstName', lambda x: x[0].split(' ')[0]), - ('a', 'familyName', lambda x: ' '.join(x[0].split(' ')[1:])), - ('u', 'affiliation'), - ('e', APPEND, lambda x: {'isSpeaker': 'speaker' in x})] - - -class InvenioPlaceTimeConverter111(InvenioRecordConverterBase): - """ - Extracts event's place and start/end time. Used for entry 111 in MARC 21. - """ - - conversion = [('9', 'startDateTime', convert_dt_tuple), - ('z', 'endDateTime', convert_dt_tuple), - ('c', 'place')] - - -class InvenioPlaceTimeConverter518(InvenioRecordConverterBase): - """ - Extracts event's place and start/end time. Used for entry 518 in MARC 21. - """ - - conversion = [('d', 'startDateTime', convert_dt_tuple), - ('h', 'endDateTime', convert_dt_tuple), - ('r', 'place')] - - -class InvenioLinkConverter(InvenioRecordConverterBase): - """ - Extracts link to the event. - """ - - conversion = [('y', 'name'), - ('u', 'url')] - - -class InvenioRecordConverter(InvenioRecordConverterBase): - """ - Main converter class. Converts record from InvenioConverter in format readable by a plugin. - """ - - conversion = [('088', 'reportNumbers', lambda x: [number for number in x[0]['a'][0].split(' ') if number != '(Confidential)']), - ('100', 'primaryAuthor', lambda x: x[0] if 'Primary Author' in x[0].get('e', []) else {}, InvenioAuthorConverter), - ('100', 'speaker', lambda x: x[0] if 'Speaker' in x[0].get('e', []) else {}, InvenioAuthorConverter), - ('111', APPEND, None, InvenioPlaceTimeConverter111), - ('245', 'title', lambda x: x[0]['a'][0]), - ('518', APPEND, None, InvenioPlaceTimeConverter518), - ('520', 'summary', lambda x: strip_tags(x[0]['a'][0])), - ('700', 'secondaryAuthor', None, InvenioAuthorConverter), - ('61124', 'meetingName', lambda x: str(x[0]['a'][0])), - ('8564', 'materials', lambda x: x, InvenioLinkConverter)] diff --git a/importer_invenio/indico_importer_invenio/forms.py b/importer_invenio/indico_importer_invenio/forms.py deleted file mode 100644 index 0529a5e..0000000 --- a/importer_invenio/indico_importer_invenio/forms.py +++ /dev/null @@ -1,19 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from wtforms.fields.html5 import URLField -from wtforms.validators import URL - -from indico.web.forms.base import IndicoForm - -from indico_importer_invenio import _ - - -class SettingsForm(IndicoForm): - server_url = URLField(_("Invenio server URL"), validators=[URL()]) diff --git a/importer_invenio/indico_importer_invenio/importer.py b/importer_invenio/indico_importer_invenio/importer.py deleted file mode 100644 index 78aed0b..0000000 --- a/importer_invenio/indico_importer_invenio/importer.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from flask_pluginengine import current_plugin - -from indico_importer import ImporterEngineBase - -from .connector import InvenioConnector -from .converters import InvenioRecordConverter - - -class InvenioImporter(ImporterEngineBase): - """Fetches and converts data from CDS Invenio""" - - _id = 'invenio' - name = 'CDS Invenio' - - def import_data(self, query, size): - url = current_plugin.settings.get('server_url') - registers = InvenioConnector(url).search(p=query, rg=size) - return InvenioRecordConverter.convert(registers) diff --git a/importer_invenio/indico_importer_invenio/plugin.py b/importer_invenio/indico_importer_invenio/plugin.py deleted file mode 100644 index 5333b03..0000000 --- a/importer_invenio/indico_importer_invenio/plugin.py +++ /dev/null @@ -1,24 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from indico_importer import ImporterSourcePluginBase - -from .forms import SettingsForm -from .importer import InvenioImporter - - -class ImporterInvenioPlugin(ImporterSourcePluginBase): - """Importer for Invenio - - Adds Invenio importer to Indico timetable import sources. - """ - configurable = True - settings_form = SettingsForm - default_settings = {'server_url': ''} - importer_engine_classes = (InvenioImporter,) diff --git a/importer_invenio/indico_importer_invenio/translations/fr_FR/LC_MESSAGES/messages-js.po b/importer_invenio/indico_importer_invenio/translations/fr_FR/LC_MESSAGES/messages-js.po deleted file mode 100644 index 4769102..0000000 --- a/importer_invenio/indico_importer_invenio/translations/fr_FR/LC_MESSAGES/messages-js.po +++ /dev/null @@ -1,23 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# -# Translators: -# Thomas Baron , 2015 -msgid "" -msgstr "" -"Project-Id-Version: Indico\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-03-11 16:21+0100\n" -"PO-Revision-Date: 2015-03-12 09:51+0000\n" -"Last-Translator: Thomas Baron \n" -"Language-Team: French (France) (http://www.transifex.com/projects/p/indico/language/fr_FR/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" -"Language: fr_FR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -msgid "Indico" -msgstr "Indico" diff --git a/importer_invenio/indico_importer_invenio/translations/fr_FR/LC_MESSAGES/messages.po b/importer_invenio/indico_importer_invenio/translations/fr_FR/LC_MESSAGES/messages.po deleted file mode 100644 index 7d3b868..0000000 --- a/importer_invenio/indico_importer_invenio/translations/fr_FR/LC_MESSAGES/messages.po +++ /dev/null @@ -1,24 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2017 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# -# Translators: -# Thomas Baron , 2015 -msgid "" -msgstr "" -"Project-Id-Version: Indico\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-10-18 11:55+0200\n" -"PO-Revision-Date: 2017-09-23 20:55+0000\n" -"Last-Translator: Thomas Baron \n" -"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" -"Language: fr_FR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: indico_importer_invenio/forms.py:28 -msgid "Invenio server URL" -msgstr "URL du serveur Invenio" diff --git a/importer_invenio/indico_importer_invenio/translations/messages.pot b/importer_invenio/indico_importer_invenio/translations/messages.pot deleted file mode 100644 index 3a49e83..0000000 --- a/importer_invenio/indico_importer_invenio/translations/messages.pot +++ /dev/null @@ -1,23 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2020 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2020. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-08-19 20:40+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.8.0\n" - -#: indico_importer_invenio/forms.py:19 -msgid "Invenio server URL" -msgstr "" - diff --git a/importer_invenio/setup.py b/importer_invenio/setup.py deleted file mode 100644 index 25b404a..0000000 --- a/importer_invenio/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from setuptools import find_packages, setup - - -setup( - name='indico-plugin-importer-invenio', - version='2.2', - description='Invenio data source for the Indico Importer plugin', - url='https://github.com/indico/indico-plugins', - license='MIT', - author='Indico Team', - author_email='indico-team@cern.ch', - packages=find_packages(), - zip_safe=False, - include_package_data=True, - install_requires=[ - 'indico>=2.2.dev0', - 'indico-plugin-importer>=2.2' - ], - classifiers=[ - 'Environment :: Plugins', - 'Environment :: Web Environment', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2.7' - ], - entry_points={'indico.plugins': {'importer_invenio = indico_importer_invenio.plugin:ImporterInvenioPlugin'}} -) diff --git a/search/MANIFEST.in b/search/MANIFEST.in deleted file mode 100644 index dabb799..0000000 --- a/search/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -graft indico_search/static -graft indico_search/templates -graft indico_search/translations - -global-exclude *.pyc __pycache__ .keep diff --git a/search/indico_search/__init__.py b/search/indico_search/__init__.py deleted file mode 100644 index 0b57e03..0000000 --- a/search/indico_search/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from indico.util.i18n import make_bound_gettext - - -_ = make_bound_gettext('search') -__all__ = ('SearchPluginBase', 'SearchEngine', 'SearchForm') - -from .base import SearchPluginBase, SearchEngine # isort:skip -from .forms import SearchForm # isort:skip diff --git a/search/indico_search/base.py b/search/indico_search/base.py deleted file mode 100644 index 6559422..0000000 --- a/search/indico_search/base.py +++ /dev/null @@ -1,65 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from flask import session -from flask_pluginengine import depends - -from indico.core.plugins import IndicoPlugin, PluginCategory - -from indico_search.forms import SearchForm -from indico_search.plugin import SearchPlugin - - -@depends('search') -class SearchPluginBase(IndicoPlugin): - """Base class for search engine plugins""" - - #: the SearchEngine subclass to use - engine_class = None - #: the SearchForm subclass to use - search_form = SearchForm - category = PluginCategory.search - - def init(self): - super(SearchPluginBase, self).init() - SearchPlugin.instance.engine_plugin = self - - @property - def only_public(self): - """If the search engine only returns public events""" - return session.user is None - - def perform_search(self, values, obj=None, obj_type=None): - """Performs the search. - - For documentation on the parameters and return value, see - the documentation of the :class:`SearchEngine` class. - """ - return self.engine_class(values, obj, obj_type).process() - - -class SearchEngine(object): - """Base class for a search engine""" - - def __init__(self, values, obj, obj_type): - """ - :param values: the values sent by the user - :param obj: object to search in (a `Category` or `Conference`) - """ - self.values = values - self.obj = obj - self.obj_type = obj_type - self.user = session.user - - def process(self): - """Executes the search - - :return: an object that's passed directly to the result template. - if a flask response is returned, it is sent to the client - instead (useful to redirect to an external page) - """ - raise NotImplementedError diff --git a/search/indico_search/blueprint.py b/search/indico_search/blueprint.py deleted file mode 100644 index c81c463..0000000 --- a/search/indico_search/blueprint.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from indico.core.plugins import IndicoPluginBlueprint - -from indico_search.controllers import RHSearch, RHSearchCategoryTitles - - -blueprint = IndicoPluginBlueprint('search', 'indico_search') - -blueprint.add_url_rule('/search', 'search', RHSearch) -blueprint.add_url_rule('/category//search', 'search', RHSearch) -blueprint.add_url_rule('/event//search', 'search', RHSearch) - -blueprint.add_url_rule('/category/search-titles', 'category_names', RHSearchCategoryTitles) diff --git a/search/indico_search/client/index.js b/search/indico_search/client/index.js deleted file mode 100644 index 98c6c74..0000000 --- a/search/indico_search/client/index.js +++ /dev/null @@ -1,532 +0,0 @@ -// This file is part of the Indico plugins. -// Copyright (C) 2002 - 2020 CERN -// -// The Indico plugins are free software; you can redistribute -// them and/or modify them under the terms of the MIT License; -// see the LICENSE file for more details. - -import './main.scss'; - -(function(global) { - const $t = $T.domain('search'); - - type( - 'IntelligentSearchBox', - ['RealtimeTextBox'], - { - _highlight(elem, token) { - // Code for highlighting the matched part of the string - const idx = elem.toLowerCase().indexOf(token.toLowerCase()); - if (idx >= 0) { - const before = elem.slice(0, idx); - const after = elem.slice(idx + token.length, elem.length); - return [before, Html.span('highlight', elem.slice(idx, idx + token.length)), after]; - } - }, - - _truncateTitle(title) { - const max = 27; - if (title.length > max) { - return `${title.slice(0, max / 2)} ... ${title.slice(-max / 2)}`; - } else { - return title; - } - }, - - _showSuggestions(text, suggList, totalNumber) { - const self = this; - if (!this.suggestionBox) { - // Create the suggestion box only once - - // totalNumber will change during time - this.totalNumber = new WatchValue(totalNumber); - this.suggestionList = Html.ul(); - - const infoBox = Html.div('help', $t.gettext('Some category suggestions...')); - - // Create the suggestion box - this.suggestionBox = Html.div( - { - style: { - position: 'absolute', - top: pixels(23), - left: 0, - }, - className: 'suggestion-box', - }, - infoBox, - this.suggestionList, - $B(Html.div('other-results'), this.totalNumber, function(number) { - return number === 0 - ? '' - : `${number} ${$t.gettext('other results - please write more...')}`; - }) - ); - - this.container.append(this.suggestionBox); - } - - // Prepare regular expression for highlighting - const tokens = _.filter(escapeHTML(text).split(' '), function(t) { - return t !== ''; - }); - const tokenRE = new RegExp(`(${tokens.join('|')})`, 'gi'); - - // Bind the list to the DOM element - let counter = 0; - - $B(this.suggestionList, suggList, function(elem) { - const titleHtml = escapeHTML(self._truncateTitle(elem.title)).replace( - tokenRE, - '$1' - ); - const index = counter; - const title = Html.span('title'); - title.dom.innerHTML = titleHtml; - const pathText = Util.truncateCategPath(elem.path).join(' >> '); - const path = Html.div('path', pathText); - const liItem = Html.li({}, title, path); - - // Use mouse to control selector as well - - liItem.observeEvent('mouseover', function() { - if (self.selectorPos != index) { - self._clearSelector(); - self.selectorPos = index; - self._setSelector(); - } - }); - - liItem.observeClick(function() { - window.location = elem.url; - }); - - counter++; - return liItem; - }); - - this.suggestionList.observeEvent('mouseout', function() { - self._clearSelector(); - self.selectorPos = -1; - }); - - // Update - this.totalNumber.set(totalNumber); - }, - - _hideSuggestions() { - this.container.remove(this.suggestionBox); - this.suggestionBox = null; - this.selectorPos = -1; - this.suggestions = null; - }, - - _retrieveOptions(expression) { - const self = this; - this.querying = true; - - $.ajax({ - url: self.searchUrl, - type: 'GET', - data: { - term: expression, - }, - dataType: 'json', - complete() { - self.querying = false; - self.timeOfLastQuery = new Date().getTime(); - }, - success(data) { - if (handleAjaxError(data)) { - return; - } - - if (data.results.length) { - self.suggestions = data.results; - self._showSuggestions(expression, data.results, data.count - data.results.length); - } else { - self._hideSuggestions(); - } - - const currentText = trim(self.get()); - - // if the text changed meanwhile and it - // is still long enough - if (currentText != expression && currentText.length > 1) { - // request - self._textTyped(); - } else if (currentText.length <= 1) { - // if it is not long enough - self._hideSuggestions(); - } - }, - }); - }, - - _getTimeSinceLastQuery() { - const now = new Date(); - return now.getTime() - this.timeOfLastQuery; - }, - - _waitForRequestTime() { - const self = this; - if (!this.queuedRequest) { - // This should never happen... - return; - } - - if (this._getTimeSinceLastQuery() > 1000) { - this._textTyped(); - this.queuedRequest = false; - } else { - setTimeout(function() { - self._waitForRequestTime(); - }, 300); - } - }, - - /* - * Called each time a new character is typed - * strips white spaces, and calls for a request if needed - */ - _textTyped(key) { - const self = this; - const text = trim(this.get()); - - if (text.length > 1) { - // if we're not already querying and enough time has passed - // since the last request - if (!this.querying && this._getTimeSinceLastQuery() > 1000) { - this._retrieveOptions(text); - } else if (!this.queuedRequest) { - // otherwise, if we can't do a request right now - // and no other request is queued - this.queuedRequest = true; - - setTimeout(function() { - self._waitForRequestTime(); - }, 300); - } - } else if (this.suggestionBox) { - this._hideSuggestions(); - } - }, - - _openSelection(event) { - if (this.selectorPos >= 0) { - window.location = this.suggestions[this.selectorPos].url; - return false; - } - return true; - }, - - /* - * Move the selector (gray area) up or down - */ - _moveSelector(direction) { - if (this.suggestionBox) { - const suggNum = this.suggestionList.length.get(); - - if (this.selectorPos < 0) { - this.selectorPos = direction == 'down' ? 0 : suggNum - 1; - } else { - this._clearSelector(); - this.selectorPos += direction == 'up' ? -1 : 1; - - if (this.selectorPos >= suggNum) { - this.selectorPos = -1; - } else if (this.selectorPos < 0) { - this.selectorPos = -1; - } - } - } - - this._setSelector(); - }, - - _setSelector() { - if (this.selectorPos >= 0) { - this.suggestionList.item(this.selectorPos).dom.className = 'selected'; - } - }, - - _clearSelector() { - if (this.selectorPos >= 0) { - this.suggestionList.item(this.selectorPos).dom.className = ''; - } - }, - - isAnyItemSelected() { - return this.selectorPos > 0; - }, - }, - function(args, container, searchUrl) { - args.autocomplete = 'off'; - this.RealtimeTextBox(args); - this.selectorPos = -1; - this.querying = false; - this.container = container; - this.timeOfLastQuery = 0; - this.searchUrl = searchUrl; - - const self = this; - - this.observe(function(key, event) { - self._textTyped(key); - return true; - }); - - this.observeOtherKeys(function(text, key, event) { - if (key == 38 || key == 40) { - self._moveSelector(key == 38 ? 'up' : 'down'); - return false; - } else if (key == 27) { - self._hideSuggestions(); - return false; - } else if (key == 13) { - return self._openSelection(event); - } else { - return true; - } - }); - - $E(document.body).observeClick(function(event) { - // Close box if a click is done outside of it - - /* for some unknown reason, onclick is called on the submit button, - * as soon as the return key is pressed in any of the textfields. - * To make it even better, onclick is called before onkeyup, - * which forces us to do the last two checks. - */ - - if ( - self.suggestionBox && - !self.suggestionList.ancestorOf($E(eventTarget(event))) && - $E(eventTarget(event)) != self.input - ) { - self._hideSuggestions(); - } - }); - } - ); - - $.widget('indico.search_tag', { - options: { - categ_title: 'Home', - everywhere: true, - search_category_url: '#', - search_url: '#', - form: null, - }, - - _transition(title, no_check) { - const $tag = this.$tag; - - $tag - .fadeTo('fast', 0.3) - .find('.where') - .html(title); - - $tag - .fadeTo('fast', 0.5) - }, - - _create() { - const self = this; - - const tag_template = _.template( - '
' + - '
<%= categ_title %>
' + - '
x
' + - '
' - ); - - const $tag = (this.$tag = $( - tag_template({ - categ_title: this.options.everywhere - ? $t.gettext('Everywhere') - : this.options.categ_title, - }) - )); - - $(this.element).replaceWith($tag); - const $where = $('.where', $tag); - - if (this.options.everywhere) { - $tag.addClass('everywhere'); - $('.cross', this.$tag).hide(); - } else { - $tag.addClass('in-category'); - $('.cross', this.$tag).show(); - } - - $('.cross', $tag).on('click', function() { - self.search_everywhere(); - }); - - const $parent = $tag.parent(); - - $parent.on('mouseenter', '.search-tag.everywhere', function() { - self.show_categ(); - }); - $parent.on('mouseover', '.search-tag.everywhere', function(e) { - self.show_tip(e); - }); - $parent.on('mouseleave', '.search-tag.everywhere', function() { - $where.removeData('hasFocus'); - }); - $parent.on('click', '.search-tag.in-category-over', function() { - self.confirm_tip(); - }); - $parent.on('mouseleave', '.search-tag.in-category-over', function() { - self.back_to_everywhere(); - }); - }, - - confirm_tip() { - const $where = $('.where', this.$tag); - const $tag = this.$tag; - - $tag.qtip('destroy'); - $where.fadeOut('fast', function() { - $(this).fadeIn('fast'); - }); - - this.$tag - .addClass('in-category') - .removeClass('everywhere') - .removeClass('in-category-over'); - this.options.form.attr('action', this.options.search_category_url); - - $tag.animate( - 200, - 'swing', - function() { - $('.cross', $tag).fadeIn('fast'); - } - ); - }, - - search_everywhere() { - $('.cross', this.$tag).hide(); - this.$tag.addClass('everywhere').removeClass('in-category'); - this._transition($t.gettext('Everywhere'), true); - this.options.form.attr('action', this.options.search_url); - }, - - show_categ() { - const $tag = this.$tag; - const $where = $('.where', $tag); - const self = this; - $where.data('hasFocus', true); - setTimeout(function() { - if ($where.data('hasFocus')) { - self._transition(self.options.categ_title); - $tag.addClass('in-category-over'); - } - }, 200); - }, - - show_tip(event) { - this.$tag.qtip( - { - content: format($t.gettext('Click to search inside {title}'), { - title: this.options.categ_title, - }), - position: { - at: 'bottom center', - my: 'top center', - }, - show: { - event: event.type, - ready: true, - }, - }, - event - ); - }, - - back_to_everywhere() { - const $where = $('.where', this.$tag); - const self = this; - - this.$tag.removeClass('in-category-over'); - $where.removeData('hasFocus'); - setTimeout(function() { - if (!$where.data('hasFocus')) { - self._transition($t.gettext('Everywhere'), true); - self.$tag.addClass('everywhere'); - } - }, 200); - }, - }); - - global.categorySearchBox = function categorySearchBox(options) { - // expected options: categoryNamesUrl, searchUrl, searchCategoryUrl, categoryName, isRoot - const form = $('#category-search-form'); - const extra = form.find('.extra-options'); - const controls = form.find('.search-controls'); - let extraConfigured = false; - $('#category-search-expand').on('click', function() { - if (extra.is(':visible')) { - extra.slideUp('fast'); - } else { - extra.slideDown('fast'); - if (!extraConfigured) { - extraConfigured = true; - extra.css('display', 'table').position({ - of: controls, - my: 'right top', - at: 'right bottom', - }); - } - } - }); - - function verifyForm() { - const startDate = $('#search-start_date').val(); - const endDate = $('#search-end_date').val(); - return ( - (!startDate || Util.parseDateTime(startDate, IndicoDateTimeFormats.DefaultHourless)) && - (!endDate || Util.parseDateTime(endDate, IndicoDateTimeFormats.DefaultHourless)) - ); - } - - const intelligentSearchBox = new IntelligentSearchBox( - { - name: 'search-phrase', - id: 'search-phrase', - style: {backgroundColor: 'transparent', outline: 'none'}, - }, - $E('category-search-box'), - options.categoryNamesUrl - ); - - $E('search-phrase').replaceWith(intelligentSearchBox.draw()); - - $('.search-button').on('click', function() { - if (verifyForm()) { - $('#category-search-form').submit(); - } - }); - - $('#search-phrase').on('keypress', function(e) { - if (e.which == 13 && !intelligentSearchBox.isAnyItemSelected()) { - if (verifyForm()) { - $('#category-search-form').submit(); - } - } - }); - - if (!options.isRoot) { - $('#category-search-form .search-button').before($('
')); - $('.search-tag').search_tag({ - everywhere: true, - categ_title: options.categoryName, - form: $('#category-search-form'), - search_url: options.searchUrl, - search_category_url: options.searchCategoryUrl, - }); - } - }; -})(window); diff --git a/search/indico_search/client/main.scss b/search/indico_search/client/main.scss deleted file mode 100644 index fae692f..0000000 --- a/search/indico_search/client/main.scss +++ /dev/null @@ -1,281 +0,0 @@ -// This file is part of the Indico plugins. -// Copyright (C) 2002 - 2020 CERN -// -// The Indico plugins are free software; you can redistribute -// them and/or modify them under the terms of the MIT License; -// see the LICENSE file for more details. - -#search-advanced-help-tooltip { - display: none; - - code { - font-size: 1.1em; - } - - .field { - color: #8a8a45; - } - - .negation { - color: #b00; - } -} - -.search-container { - .search-public-warning { - float: right; - padding: 10px; - color: #f44; - } - - .search-banner { - float: right; - margin-top: 10px; - - & > span { - color: #777; - } - - img { - vertical-align: middle; - border: 0; - } - } - - .content { - form { - width: 400px; - - #search-phrase { - width: 300px; - height: 20px; - font-size: 17px; - vertical-align: middle; - } - - input[type='submit'] { - vertical-align: middle; - } - - .toggle-advanced-options-container { - padding-top: 4px; - } - - .advanced-options > table { - text-align: right; - } - } - } - - h1 { - font-size: 2em; - color: #b14300; - font-weight: normal; - margin: 0; - padding: 0; - } -} - -#category-search-form { - .search-box { - position: absolute; - right: 1.5em; - top: 50px; - border: none; - font-size: 13px; - } - - .search-button { - float: right; - border-left: 1px solid #003042; - width: 28px; - height: 21px; - line-height: 21px; - cursor: pointer; - background: #444; - text-align: center; - color: #fafafa; - } - - .search-controls { - width: auto; - border: 1px solid #003042; - height: 21px; - overflow: hidden; - background-color: white; - - input { - border: 0; - font-weight: normal; - color: #333; - } - } - - .search-field { - margin-left: 3px; - min-width: 150px; - width: auto; - height: 19px; - } - - .extra-options { - border: 1px solid #888; - border-radius: 6px; - min-width: 275px; - white-space: nowrap; - width: 100%; - display: none; - position: absolute; - top: 23px; - right: 0; - background-color: white; - overflow: visible; - - table { - width: auto; - } - - table td:first-child { - padding-left: 10px; - } - - input { - margin-left: 8px; - font-size: 12px; - width: 148px; - } - - select { - margin-left: 8px; - font-size: 12px; - width: 148px; - } - - td { - font-size: 12px; - color: #444; - } - - .label { - text-align: center; - padding: 5px; - border-radius: 6px 6px 0 0; - background-color: #ececec; - font-style: italic; - border-bottom: 1px solid #aaa; - } - } -} - -.search-tag { - max-width: 112px; - display: flex; - line-height: 13px; - vertical-align: middle; - padding: 2px 5px; - float: left; - -ms-user-select: none; - -o-user-select: none; - -webkit-user-select: none; - -moz-user-select: -moz-none; - user-select: none; - font-size: 11px; - overflow: hidden; - opacity: 0.6; - color: #000; - - .where { - max-width: 100px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - float: left; - } - - .cross { - font-weight: bold; - color: #566e89; - float: right; - cursor: pointer; - font-size: 10px; - margin-left: 4px; - line-height: 12px; - } - - &.everywhere { - border: 1px solid #566e89; - text-shadow: 0 1px 0 #666; - background-color: #aaa; - cursor: default; - } - - &.in-category { - border: 1px solid #566e89; - background-color: #8ea1b7; - text-shadow: 0 1px 0 #666; - cursor: default; - } - - &.in-category-over { - border: 1px solid #566e89; - background-color: #8ea1b7; - cursor: pointer; - } -} - -.suggestion-box { - border: 1px solid #888; - width: 220px; - overflow: hidden; - background-color: white; - - .help { - background-color: #fff; - color: #555; - font-size: 12px; - font-style: italic; - margin: 0 0 0; - padding: 5px; - } - - .other-results { - background-color: #fff; - color: #777; - font-size: 12px; - font-style: italic; - text-align: center; - margin-top: 5px; - margin-bottom: 3px; - } - - ul { - background-color: #fff; - padding: 3px; - margin: 0 0 0 0; - - li { - list-style-type: none; - padding: 3px; - border-top: 1px solid #ddd; - cursor: pointer; - - &.selected { - background-color: #ececec; - } - - .title { - color: #0b63a5; - font-size: 12px; - } - - .highlight { - font-weight: bold; - } - - .path { - color: #444; - font-size: 9px; - } - } - } -} diff --git a/search/indico_search/controllers.py b/search/indico_search/controllers.py deleted file mode 100644 index e0d945f..0000000 --- a/search/indico_search/controllers.py +++ /dev/null @@ -1,62 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from flask import jsonify, request -from flask_pluginengine import current_plugin -from sqlalchemy.orm import undefer -from werkzeug.wrappers import Response - -from indico.modules.categories import Category -from indico.modules.events import Event -from indico.web.rh import RH - -from indico_search.views import WPSearchCategory, WPSearchConference - - -class RHSearch(RH): - """Performs a search using the search engine plugin""" - - def _process_args(self): - if 'confId' in request.view_args: - self.obj = Event.get_or_404(request.view_args['confId'], is_deleted=False) - self.obj_type = 'event' - elif 'category_id' in request.view_args: - self.obj = Category.get_or_404(request.view_args['category_id'], is_deleted=False) - self.obj_type = 'category' if not self.obj.is_root else None - else: - self.obj = Category.get_root() - self.obj_type = None - - def _process(self): - with current_plugin.engine_plugin.plugin_context(): - form = current_plugin.search_form(formdata=request.args, prefix='search-', csrf_enabled=False) - result = None - if form.validate_on_submit(): - result = current_plugin.perform_search(form.data, self.obj, self.obj_type) - if isinstance(result, Response): # probably a redirect or a json response - return result - view_class = WPSearchConference if isinstance(self.obj, Event) else WPSearchCategory - return view_class.render_template('results.html', self.obj, only_public=current_plugin.only_public, - form=form, obj_type=self.obj_type, result=result) - - -class RHSearchCategoryTitles(RH): - """Searches for categories with matching titles""" - def _process(self): - query = (Category.query - .filter(Category.title_matches(request.args['term']), - ~Category.is_deleted) - .options(undefer('chain_titles')) - .order_by(Category.title)) - results = [{ - 'title': category.title, - 'path': category.chain_titles[1:-1], - 'url': unicode(category.url) - } for category in query.limit(7)] - return jsonify(success=True, results=results, count=query.count()) diff --git a/search/indico_search/forms.py b/search/indico_search/forms.py deleted file mode 100644 index c7264ed..0000000 --- a/search/indico_search/forms.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from flask import request -from wtforms.fields.core import SelectField, StringField -from wtforms.validators import Optional - -from indico.web.forms.base import IndicoForm -from indico.web.forms.fields import IndicoDateField - -from indico_search import _ - - -FIELD_CHOICES = [('', _('Anywhere')), - ('title', _('Title')), - ('abstract', _('Description/Abstract')), - ('author', _('Author/Speaker')), - ('affiliation', _('Affiliation')), - ('keyword', _('Keyword'))] - - -class SearchForm(IndicoForm): - phrase = StringField(_('Phrase')) - field = SelectField(_('Search in'), choices=FIELD_CHOICES, default='') - start_date = IndicoDateField('Start Date', [Optional()]) - end_date = IndicoDateField('End Date', [Optional()]) - - def is_submitted(self): - return 'search-phrase' in request.args diff --git a/search/indico_search/plugin.py b/search/indico_search/plugin.py deleted file mode 100644 index 855a272..0000000 --- a/search/indico_search/plugin.py +++ /dev/null @@ -1,64 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -import os - -from flask import g, request -from flask_pluginengine import plugins_loaded - -from indico.core.plugins import IndicoPlugin, PluginCategory -from indico.modules.events.layout import layout_settings -from indico.web.views import WPBase - -from indico_search.blueprint import blueprint -from indico_search.util import render_engine_or_search_template - - -class SearchPlugin(IndicoPlugin): - """Search - - Provides a base for search engine plugins. - """ - category = PluginCategory.search - _engine_plugin = None # the search engine plugin - - def init(self): - super(SearchPlugin, self).init() - self.connect(plugins_loaded, self._plugins_loaded, sender=self.app) - self.template_hook('conference-header-right-column', self._add_conference_search_box) - self.template_hook('page-header', self._add_category_search_box) - self.inject_bundle('main.js', WPBase) - self.inject_bundle('main.css', WPBase) - - def _plugins_loaded(self, sender, **kwargs): - if not self.engine_plugin and 'INDICO_DUMPING_URLS' not in os.environ: - raise RuntimeError('Search plugin active but no search engine plugin loaded') - - @property - def engine_plugin(self): - return self._engine_plugin - - @engine_plugin.setter - def engine_plugin(self, value): - if self._engine_plugin is not None: - raise RuntimeError('Another search engine plugin is active: {}'.format(self._engine_plugin.name)) - self._engine_plugin = value - - def get_blueprints(self): - return blueprint - - def _add_conference_search_box(self, event, **kwargs): - if layout_settings.get(event, 'is_searchable') and not g.get('static_site'): - form = self.engine_plugin.search_form(prefix='search-', csrf_enabled=False) - return render_engine_or_search_template('searchbox_conference.html', event=event, form=form) - - def _add_category_search_box(self, category, **kwargs): - if request.blueprint != 'plugin_search': - form = self.engine_plugin.search_form(prefix='search-', csrf_enabled=False) - return render_engine_or_search_template('searchbox_category.html', category=category, form=form) diff --git a/search/indico_search/templates/results.html b/search/indico_search/templates/results.html deleted file mode 100644 index e4a99ff..0000000 --- a/search/indico_search/templates/results.html +++ /dev/null @@ -1,99 +0,0 @@ -{% if obj_type == 'event' %} - {% extends 'events/display/conference/base.html' %} -{% else %} - {% extends 'layout/base.html' %} -{% endif %} - -{% from 'forms/_form.html' import form_header, form_footer %} - -{% block page_class %} - {{ super() if obj_type == 'event' else 'fixed-width-standalone-page' }} -{% endblock %} - -{% block title %} - {% trans %}Search{% endtrans %} - {% if obj_type == 'event' %} - {% trans %}Event{% endtrans %} - {% elif obj_type == 'category' %} - {% trans %}Category{% endtrans %} - {% endif %} -{% endblock %} - -{% block content %} -
- {% if only_public %} -
- {% trans %}Warning: since you are not logged in, only results from public events will appear.{% endtrans %} -
- {% endif %} - -
-
-
-
- Search powered by - {% block banner %}{% endblock %} -
- - {{ form_header(form, method='get', i_form=false, disable_if_locked=false) }} -
- {{ form.phrase() }} - - {% block tooltip %}{% endblock %} -
- - - - - {{ form_footer(form, i_form=false) }} -
-
- {% set errors = form.error_list %} - {% if errors %} -
    - {% for error in errors %} -
  • {{ error }}
  • - {% endfor %} -
- {% endif %} - {% if form.validate_on_submit() %} - {% block results %}{% endblock %} - {% endif %} -
-
- - - {% block scripts %}{% endblock %} -{% endblock %} - diff --git a/search/indico_search/templates/searchbox_category.html b/search/indico_search/templates/searchbox_category.html deleted file mode 100644 index 0c75aa7..0000000 --- a/search/indico_search/templates/searchbox_category.html +++ /dev/null @@ -1,47 +0,0 @@ -{% from 'forms/_form.html' import form_header, form_footer %} - -{{ form_header(form, method='get', action=url_for_plugin('search.search'), id='category-search-form', i_form=false, - disable_if_locked=false) }} - -{{ form_footer(form, i_form=false) }} - - diff --git a/search/indico_search/templates/searchbox_conference.html b/search/indico_search/templates/searchbox_conference.html deleted file mode 100644 index 6c175bf..0000000 --- a/search/indico_search/templates/searchbox_conference.html +++ /dev/null @@ -1,12 +0,0 @@ -{% from 'forms/_form.html' import form_header, form_footer %} - -{{ form_header(form, method='get', action=url_for_plugin('search.search', event), i_form=false, - disable_if_locked=false) }} -
-
- {{ form.phrase(id='conference-search-phrase', placeholder=_("Search...")) }} - - {% block extra_fields %}{% endblock %} -
-
-{{ form_footer(form, i_form=false) }} diff --git a/search/indico_search/translations/fr_FR/LC_MESSAGES/messages-js.po b/search/indico_search/translations/fr_FR/LC_MESSAGES/messages-js.po deleted file mode 100644 index d0acb18..0000000 --- a/search/indico_search/translations/fr_FR/LC_MESSAGES/messages-js.po +++ /dev/null @@ -1,41 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2017 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# -# Translators: -# Thomas Baron , 2015,2017 -msgid "" -msgstr "" -"Project-Id-Version: Indico\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-10-18 11:55+0200\n" -"PO-Revision-Date: 2017-09-23 20:55+0000\n" -"Last-Translator: Thomas Baron \n" -"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" -"Language: fr_FR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: indico_search/static/js/search.js:50 -msgid "Some category suggestions..." -msgstr "Quelques suggestions de catégories..." - -#: indico_search/static/js/search.js:64 -msgid "other results - please write more..." -msgstr "autres résultats - veuillez entrer plus de texte..." - -#: indico_search/static/js/search.js:345 indico_search/static/js/search.js:405 -#: indico_search/static/js/search.js:444 -msgid "Everywhere" -msgstr "Partout" - -#: indico_search/static/js/search.js:424 -msgid "Click to search inside {title}" -msgstr "Veuillez cliquer pour chercher dans {title}" - -#: indico_search/templates/searchbox_conference.html:6 -msgid "Search..." -msgstr "Chercher..." diff --git a/search/indico_search/translations/fr_FR/LC_MESSAGES/messages.po b/search/indico_search/translations/fr_FR/LC_MESSAGES/messages.po deleted file mode 100644 index a4e9791..0000000 --- a/search/indico_search/translations/fr_FR/LC_MESSAGES/messages.po +++ /dev/null @@ -1,88 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2017 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# -# Translators: -# Thomas Baron , 2015,2017 -msgid "" -msgstr "" -"Project-Id-Version: Indico\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-10-18 11:55+0200\n" -"PO-Revision-Date: 2017-09-23 20:55+0000\n" -"Last-Translator: Thomas Baron \n" -"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" -"Language: fr_FR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: indico_search/forms.py:29 -msgid "Anywhere" -msgstr "Partout" - -#: indico_search/forms.py:30 -msgid "Title" -msgstr "Titre" - -#: indico_search/forms.py:31 -msgid "Description/Abstract" -msgstr "Description/résumé" - -#: indico_search/forms.py:32 -msgid "Author/Speaker" -msgstr "Auteur/orateur" - -#: indico_search/forms.py:33 -msgid "Affiliation" -msgstr "Affiliation" - -#: indico_search/forms.py:34 -msgid "Keyword" -msgstr "Mots-clés" - -#: indico_search/forms.py:38 -msgid "Phrase" -msgstr "Phrase" - -#: indico_search/forms.py:39 -msgid "Search in" -msgstr "Chercher dans" - -#: indico_search/templates/results.html:14 -#: indico_search/templates/results.html:41 -msgid "Search" -msgstr "Chercher" - -#: indico_search/templates/results.html:16 -msgid "Event" -msgstr "Événement" - -#: indico_search/templates/results.html:18 -msgid "Category" -msgstr "Categorie" - -#: indico_search/templates/results.html:26 -msgid "" -"Warning: since you are not logged in, only results from public events will " -"appear." -msgstr "Attention: puisque vous n'êtes pas authentifié, seuls des résultats publics seront affichés." - -#: indico_search/templates/results.html:47 -#: indico_search/templates/results.html:49 -msgid "Show advanced options" -msgstr "Afficher les options avancées" - -#: indico_search/templates/results.html:48 -msgid "Hide advanced options" -msgstr "Cacher les options avancées" - -#: indico_search/templates/searchbox_category.html:13 -msgid "Advanced options" -msgstr "Options avancées" - -#: indico_search/templates/searchbox_conference.html:6 -msgid "Search..." -msgstr "Rechercher..." diff --git a/search/indico_search/translations/messages-js.pot b/search/indico_search/translations/messages-js.pot deleted file mode 100644 index 0297721..0000000 --- a/search/indico_search/translations/messages-js.pot +++ /dev/null @@ -1,36 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2020 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2020. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-08-19 20:40+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.8.0\n" - -#: indico_search/client/index.js:45 -msgid "Some category suggestions..." -msgstr "" - -#: indico_search/client/index.js:347 indico_search/client/index.js:413 -#: indico_search/client/index.js:457 -msgid "Everywhere" -msgstr "" - -#: indico_search/client/index.js:433 -msgid "Click to search inside {title}" -msgstr "" - -#: indico_search/templates/searchbox_conference.html:7 -msgid "Search..." -msgstr "" - diff --git a/search/indico_search/translations/messages.pot b/search/indico_search/translations/messages.pot deleted file mode 100644 index b9bb7db..0000000 --- a/search/indico_search/translations/messages.pot +++ /dev/null @@ -1,87 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2020 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2020. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-08-19 20:40+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.8.0\n" - -#: indico_search/forms.py:20 -msgid "Anywhere" -msgstr "" - -#: indico_search/forms.py:21 -msgid "Title" -msgstr "" - -#: indico_search/forms.py:22 -msgid "Description/Abstract" -msgstr "" - -#: indico_search/forms.py:23 -msgid "Author/Speaker" -msgstr "" - -#: indico_search/forms.py:24 -msgid "Affiliation" -msgstr "" - -#: indico_search/forms.py:25 -msgid "Keyword" -msgstr "" - -#: indico_search/forms.py:29 -msgid "Phrase" -msgstr "" - -#: indico_search/forms.py:30 -msgid "Search in" -msgstr "" - -#: indico_search/templates/results.html:14 -#: indico_search/templates/results.html:41 -msgid "Search" -msgstr "" - -#: indico_search/templates/results.html:16 -msgid "Event" -msgstr "" - -#: indico_search/templates/results.html:18 -msgid "Category" -msgstr "" - -#: indico_search/templates/results.html:26 -msgid "" -"Warning: since you are not logged in, only results from public events " -"will appear." -msgstr "" - -#: indico_search/templates/results.html:47 -#: indico_search/templates/results.html:49 -msgid "Show advanced options" -msgstr "" - -#: indico_search/templates/results.html:48 -msgid "Hide advanced options" -msgstr "" - -#: indico_search/templates/searchbox_category.html:14 -msgid "Advanced options" -msgstr "" - -#: indico_search/templates/searchbox_conference.html:7 -msgid "Search..." -msgstr "" - diff --git a/search/indico_search/util.py b/search/indico_search/util.py deleted file mode 100644 index 9901f6c..0000000 --- a/search/indico_search/util.py +++ /dev/null @@ -1,28 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from flask_pluginengine import current_plugin, render_plugin_template - - -def render_engine_or_search_template(template_name, **context): - """Renders a template from the engine plugin or the search plugin - - If the template is available in the engine plugin, it's taken - from there, otherwise the template from this plugin is used. - - :param template_name: name of the template - :param context: the variables that should be available in the - context of the template. - """ - from indico_search.plugin import SearchPlugin - assert current_plugin == SearchPlugin.instance - - templates = ('{}:{}'.format(SearchPlugin.instance.engine_plugin.name, template_name), - template_name) - return render_plugin_template(templates, **context) diff --git a/search/indico_search/views.py b/search/indico_search/views.py deleted file mode 100644 index 109bb86..0000000 --- a/search/indico_search/views.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from indico.core.plugins import WPJinjaMixinPlugin -from indico.modules.categories.views import WPCategory -from indico.modules.events.views import WPConferenceDisplayBase - - -class WPSearchCategory(WPJinjaMixinPlugin, WPCategory): - pass - - -class WPSearchConference(WPJinjaMixinPlugin, WPConferenceDisplayBase): - pass diff --git a/search/setup.py b/search/setup.py deleted file mode 100644 index 48b8b6c..0000000 --- a/search/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -# This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN -# -# The Indico plugins are free software; you can redistribute -# them and/or modify them under the terms of the MIT License; -# see the LICENSE file for more details. - -from __future__ import unicode_literals - -from setuptools import find_packages, setup - - -setup( - name='indico-plugin-search', - version='2.3', - description='Framework for searching content in Indico', - url='https://github.com/indico/indico-plugins', - license='MIT', - author='Indico Team', - author_email='indico-team@cern.ch', - packages=find_packages(), - zip_safe=False, - include_package_data=True, - install_requires=[ - 'indico>=2.3.dev0' - ], - classifiers=[ - 'Environment :: Plugins', - 'Environment :: Web Environment', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2.7' - ], - entry_points={'indico.plugins': {'search = indico_search.plugin:SearchPlugin'}} -) diff --git a/search/webpack-bundles.json b/search/webpack-bundles.json deleted file mode 100644 index 5f28835..0000000 --- a/search/webpack-bundles.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": { - "main": "./index.js" - } -}