From 89dc4af965a638242f41208873752252ad5f63e4 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Mon, 13 Aug 2018 11:04:08 +0200 Subject: [PATCH 01/29] URSH: Add ursh plugin for indico URSH: URSH: - Add a new blueprint: /ursh URSH: - Add a template hook ('url-shortener-link') to enable short link generation --- ursh/MANIFEST.in | 4 ++ ursh/indico_ursh/__init__.py | 22 +++++++ ursh/indico_ursh/blueprint.py | 25 ++++++++ ursh/indico_ursh/client/index.js | 60 ++++++++++++++++++++ ursh/indico_ursh/controllers.py | 51 +++++++++++++++++ ursh/indico_ursh/plugin.py | 63 +++++++++++++++++++++ ursh/indico_ursh/templates/ursh_button.html | 5 ++ ursh/indico_ursh/util.py | 36 ++++++++++++ ursh/setup.py | 43 ++++++++++++++ ursh/webpack-bundles.json | 5 ++ 10 files changed, 314 insertions(+) create mode 100644 ursh/MANIFEST.in create mode 100644 ursh/indico_ursh/__init__.py create mode 100644 ursh/indico_ursh/blueprint.py create mode 100644 ursh/indico_ursh/client/index.js create mode 100644 ursh/indico_ursh/controllers.py create mode 100644 ursh/indico_ursh/plugin.py create mode 100644 ursh/indico_ursh/templates/ursh_button.html create mode 100644 ursh/indico_ursh/util.py create mode 100644 ursh/setup.py create mode 100644 ursh/webpack-bundles.json diff --git a/ursh/MANIFEST.in b/ursh/MANIFEST.in new file mode 100644 index 0000000..db61780 --- /dev/null +++ b/ursh/MANIFEST.in @@ -0,0 +1,4 @@ +graft indico_ursh/static +graft indico_ursh/translations + +global-exclude *.pyc __pycache__ .keep diff --git a/ursh/indico_ursh/__init__.py b/ursh/indico_ursh/__init__.py new file mode 100644 index 0000000..7e6aeec --- /dev/null +++ b/ursh/indico_ursh/__init__.py @@ -0,0 +1,22 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.util.i18n import make_bound_gettext + + +_ = make_bound_gettext('ursh') diff --git a/ursh/indico_ursh/blueprint.py b/ursh/indico_ursh/blueprint.py new file mode 100644 index 0000000..67d40ae --- /dev/null +++ b/ursh/indico_ursh/blueprint.py @@ -0,0 +1,25 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.core.plugins import IndicoPluginBlueprint + +from indico_ursh.controllers import RHShortenURL + + +blueprint = IndicoPluginBlueprint('ursh', 'indico_ursh') +blueprint.add_url_rule('/ursh', 'get_short_url', RHShortenURL, methods=('POST',)) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js new file mode 100644 index 0000000..9771580 --- /dev/null +++ b/ursh/indico_ursh/client/index.js @@ -0,0 +1,60 @@ +/* This file is part of Indico. + * Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). + * + * Indico is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Indico is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Indico; if not, see . + */ + +import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; + + +async function _makeUrshRequest(originalURL, triggerElement) { + const urshEndpoint = '/ursh'; + + let response; + try { + response = await indicoAxios.post(urshEndpoint, { + original_url: originalURL, + }); + } catch (error) { + handleAxiosError(error); + return; + } + + const data = response.data; + if (data.success) { + $(triggerElement).copyURLTooltip(data.msg).show(); + } else { + $(triggerElement).qtip({ + content: { + text: $T.gettext(`URL shortening service is unavailable: ${data.msg}`), + }, + hide: { + event: 'mouseleave', + fixed: true, + delay: 700, + }, + show: { + event: false, + ready: true, + } + }).show(); + } +} + + +$(document).on('click', '.ursh-get', (event) => { + event.preventDefault(); + const originalURL = $(event.target).attr('data-original-url'); + _makeUrshRequest(originalURL, event.target); +}); diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py new file mode 100644 index 0000000..3998784 --- /dev/null +++ b/ursh/indico_ursh/controllers.py @@ -0,0 +1,51 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +import posixpath + +from flask import jsonify, request +from werkzeug.exceptions import BadRequest +from werkzeug.urls import url_parse + +from indico.core.config import config +from indico.web.rh import RH + +from indico_ursh.util import request_short_url + + +class RHShortenURL(RH): + """Make a request to the URL shortening service""" + + @staticmethod + def _resolve_full_url(original_url): + if url_parse(original_url).host is not None: + return original_url + original_url = original_url.lstrip('/') + return posixpath.join(config.BASE_URL, original_url) + + @staticmethod + def _check_host(full_url): + if url_parse(full_url).host != url_parse(config.BASE_URL).host: + raise BadRequest('Invalid host for URL shortening service') + + def _process(self): + original_url = request.json.get('original_url') + full_url = self._resolve_full_url(original_url) + self._check_host(full_url) + short_url = request_short_url(full_url) + return jsonify(success=True, msg=short_url) diff --git a/ursh/indico_ursh/plugin.py b/ursh/indico_ursh/plugin.py new file mode 100644 index 0000000..f445921 --- /dev/null +++ b/ursh/indico_ursh/plugin.py @@ -0,0 +1,63 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from flask_pluginengine import render_plugin_template + +from indico.core.plugins import IndicoPlugin +from indico.web.forms.base import IndicoForm +from indico.web.views import WPBase + +from indico_ursh import _ +from indico_ursh.blueprint import blueprint +from wtforms.fields.core import StringField +from wtforms.fields.html5 import URLField +from wtforms.validators import DataRequired + + +class SettingsForm(IndicoForm): + api_key = StringField(_('API key'), [DataRequired()], + description=_('The API key to access the ursh service')) + api_host = URLField(_('API host'), [DataRequired()], + description=_('The ursh API host, providing the interface to generate short URLs')) + + +class UrshPlugin(IndicoPlugin): + """URL Shortener + + Provides a URL shortening service for indico assets, events, etc. + """ + + configurable = True + settings_form = SettingsForm + default_settings = { + 'api_key': '', + 'api_host': '', + } + + def init(self): + super(UrshPlugin, self).init() + self.template_hook('url-shortener-link', self._inject_ursh_button) + self.inject_bundle('main.js', WPBase) + + def get_blueprints(self): + return blueprint + + def _inject_ursh_button(self, target, element_type='a', element_class='', text='(short-url)', **kwargs): + if self.settings.get('api_key') and self.settings.get('api_host'): + return render_plugin_template('ursh_button.html', target=target, + element_type=element_type, element_class=element_class, text=text) diff --git a/ursh/indico_ursh/templates/ursh_button.html b/ursh/indico_ursh/templates/ursh_button.html new file mode 100644 index 0000000..f08c6c5 --- /dev/null +++ b/ursh/indico_ursh/templates/ursh_button.html @@ -0,0 +1,5 @@ +<{{ element_type }} class="ursh-get {{ element_class }}" + title="{% trans %}Obtain short URL{% endtrans %}" + data-original-url="{{ target }}"> + {{- text -}} + diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py new file mode 100644 index 0000000..b9e3a10 --- /dev/null +++ b/ursh/indico_ursh/util.py @@ -0,0 +1,36 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +import requests +from werkzeug.exceptions import ServiceUnavailable + + +def request_short_url(original_url): + from indico_ursh.plugin import UrshPlugin + api_key = UrshPlugin.settings.get('api_key') + api_host = UrshPlugin.settings.get('api_host') + + if not api_key or not api_host: + raise ServiceUnavailable('Not configured') + + headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)} + response = requests.post(api_host, data=dict(url=original_url, allow_reuse=True), headers=headers) + response.raise_for_status() + + data = response.json() + return data['short_url'] diff --git a/ursh/setup.py b/ursh/setup.py new file mode 100644 index 0000000..7cde8f5 --- /dev/null +++ b/ursh/setup.py @@ -0,0 +1,43 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from setuptools import find_packages, setup + + +setup( + name='indico-plugin-ursh', + version='3.0-dev', + description='URL shortening service for Indico', + url='https://github.com/indico/indico-plugins', + license='https://www.gnu.org/licenses/gpl-3.0.txt', + 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 :: GNU General Public License v3 or later (GPLv3+)', + 'Programming Language :: Python :: 2.7' + ], + entry_points={'indico.plugins': {'ursh = indico_ursh.plugin:UrshPlugin'}} +) diff --git a/ursh/webpack-bundles.json b/ursh/webpack-bundles.json new file mode 100644 index 0000000..5f28835 --- /dev/null +++ b/ursh/webpack-bundles.json @@ -0,0 +1,5 @@ +{ + "entry": { + "main": "./index.js" + } +} From 09539b83b2d991a2988dcdf951cf28eda48d7969 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Tue, 21 Aug 2018 17:06:23 +0200 Subject: [PATCH 02/29] URSH: Rename ursh controller return value: msg -> url --- ursh/indico_ursh/client/index.js | 19 +------------------ ursh/indico_ursh/controllers.py | 2 +- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 9771580..4eceab1 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -32,24 +32,7 @@ async function _makeUrshRequest(originalURL, triggerElement) { } const data = response.data; - if (data.success) { - $(triggerElement).copyURLTooltip(data.msg).show(); - } else { - $(triggerElement).qtip({ - content: { - text: $T.gettext(`URL shortening service is unavailable: ${data.msg}`), - }, - hide: { - event: 'mouseleave', - fixed: true, - delay: 700, - }, - show: { - event: false, - ready: true, - } - }).show(); - } + $(triggerElement).copyURLTooltip(data.url).show(); } diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py index 3998784..2bb50eb 100644 --- a/ursh/indico_ursh/controllers.py +++ b/ursh/indico_ursh/controllers.py @@ -48,4 +48,4 @@ class RHShortenURL(RH): full_url = self._resolve_full_url(original_url) self._check_host(full_url) short_url = request_short_url(full_url) - return jsonify(success=True, msg=short_url) + return jsonify(url=short_url) From eda2d0b43f8fd2285ad502ffe2140af4d535669d Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Wed, 29 Aug 2018 18:25:51 +0200 Subject: [PATCH 03/29] URSH: Add URL shortener page --- ursh/indico_ursh/blueprint.py | 5 +- ursh/indico_ursh/client/index.js | 67 +++++++++++++++++-- ursh/indico_ursh/controllers.py | 12 +++- ursh/indico_ursh/templates/url_shortener.html | 24 +++++++ ursh/indico_ursh/util.py | 3 +- ursh/indico_ursh/views.py | 25 +++++++ 6 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 ursh/indico_ursh/templates/url_shortener.html create mode 100644 ursh/indico_ursh/views.py diff --git a/ursh/indico_ursh/blueprint.py b/ursh/indico_ursh/blueprint.py index 67d40ae..8ce992c 100644 --- a/ursh/indico_ursh/blueprint.py +++ b/ursh/indico_ursh/blueprint.py @@ -18,8 +18,9 @@ from __future__ import unicode_literals from indico.core.plugins import IndicoPluginBlueprint -from indico_ursh.controllers import RHShortenURL +from indico_ursh.controllers import RHDisplayShortenURLPage, RHGetShortURL blueprint = IndicoPluginBlueprint('ursh', 'indico_ursh') -blueprint.add_url_rule('/ursh', 'get_short_url', RHShortenURL, methods=('POST',)) +blueprint.add_url_rule('/ursh', 'get_short_url', RHGetShortURL, methods=('POST',)) +blueprint.add_url_rule('/url-shortener', 'shorten_url', RHDisplayShortenURLPage) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 4eceab1..8be6c54 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -35,9 +35,66 @@ async function _makeUrshRequest(originalURL, triggerElement) { $(triggerElement).copyURLTooltip(data.url).show(); } +function _patchURL(url) { + if (url.startsWith(window.location.hostname)) { + return `${window.location.protocol}//${url}`; + } else { + return url; + } +} -$(document).on('click', '.ursh-get', (event) => { - event.preventDefault(); - const originalURL = $(event.target).attr('data-original-url'); - _makeUrshRequest(originalURL, event.target); -}); +function _validateURL(url) { + const re = RegExp(`^(${window.location.protocol}//)?${window.location.hostname}/`); + return re.test(url); +} + +function _getUrshInput() { + const $t = $T.domain('ursh'); + const input = $('#ursh-shorten-input'); + const originalURL = input.val().trim(); + + const tip = ((msg) => { + input.qtip({ + content: { + text: msg + }, + hide: { + event: 'mouseleave', + fixed: true, + delay: 700 + }, + show: { + event: false, + ready: true + } + }); + }); + + if (!originalURL) { + tip($t.gettext('Please fill in a URL to shorten')); + input.focus(); + return null; + } else if (!_validateURL(originalURL)) { + tip($t.gettext('This does not look like a valid URL')); + input.focus(); + return null; + } else { + const patchedURL = _patchURL(originalURL); + input.val(patchedURL); + return patchedURL; + } +} + +$(document) + .on('click', '#ursh-shorten-button', (evt) => { + evt.preventDefault(); + const originalURL = _getUrshInput(); + if (originalURL) { + _makeUrshRequest(originalURL, evt.target); + } + }) + .on('click', '.ursh-get', (evt) => { + evt.preventDefault(); + const originalURL = $(evt.target).attr('data-original-url'); + _makeUrshRequest(originalURL, evt.target); + }); diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py index 2bb50eb..254f7b2 100644 --- a/ursh/indico_ursh/controllers.py +++ b/ursh/indico_ursh/controllers.py @@ -26,14 +26,15 @@ from indico.core.config import config from indico.web.rh import RH from indico_ursh.util import request_short_url +from indico_ursh.views import WPShortenURLPage -class RHShortenURL(RH): +class RHGetShortURL(RH): """Make a request to the URL shortening service""" @staticmethod def _resolve_full_url(original_url): - if url_parse(original_url).host is not None: + if url_parse(original_url).host: return original_url original_url = original_url.lstrip('/') return posixpath.join(config.BASE_URL, original_url) @@ -49,3 +50,10 @@ class RHShortenURL(RH): self._check_host(full_url) short_url = request_short_url(full_url) return jsonify(url=short_url) + + +class RHDisplayShortenURLPage(RH): + """Provide a simple page, where users can submit a URL to be shortened""" + + def _process(self): + return WPShortenURLPage.render_template('url_shortener.html') diff --git a/ursh/indico_ursh/templates/url_shortener.html b/ursh/indico_ursh/templates/url_shortener.html new file mode 100644 index 0000000..4843c21 --- /dev/null +++ b/ursh/indico_ursh/templates/url_shortener.html @@ -0,0 +1,24 @@ +{% extends 'layout/base.html' %} + +{% block page_class %}fixed-width-standalone-text-page{% endblock %} + +{% block title -%} + {% trans %}URL Shortener{% endtrans %} +{%- endblock %} + +{% block content -%} +
+

+ {% trans -%} + In this page, you can generate short links for any indico URL.
+ Tip: you can also generate short links by using the quick-links in various places within indico! + {%- endtrans %} +

+
+ + +
+
+{%- endblock %} diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py index b9e3a10..105e13a 100644 --- a/ursh/indico_ursh/util.py +++ b/ursh/indico_ursh/util.py @@ -16,9 +16,10 @@ from __future__ import unicode_literals -import requests from werkzeug.exceptions import ServiceUnavailable +import requests + def request_short_url(original_url): from indico_ursh.plugin import UrshPlugin diff --git a/ursh/indico_ursh/views.py b/ursh/indico_ursh/views.py new file mode 100644 index 0000000..7287625 --- /dev/null +++ b/ursh/indico_ursh/views.py @@ -0,0 +1,25 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.core.plugins import WPJinjaMixinPlugin +from indico.web.views import WPDecorated + + +class WPShortenURLPage(WPJinjaMixinPlugin, WPDecorated): + def _getBody(self, params): + return self._getPageContent(params) From 5774e37686830f8dcd3eab649e9672fbc9441605 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Thu, 30 Aug 2018 17:42:00 +0200 Subject: [PATCH 04/29] URSH: Refactor ursh request function --- ursh/indico_ursh/client/index.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 8be6c54..67cfbfc 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -18,7 +18,7 @@ import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; -async function _makeUrshRequest(originalURL, triggerElement) { +async function _makeUrshRequest(originalURL, callback) { const urshEndpoint = '/ursh'; let response; @@ -32,7 +32,7 @@ async function _makeUrshRequest(originalURL, triggerElement) { } const data = response.data; - $(triggerElement).copyURLTooltip(data.url).show(); + callback(data.url); } function _patchURL(url) { @@ -90,11 +90,18 @@ $(document) evt.preventDefault(); const originalURL = _getUrshInput(); if (originalURL) { - _makeUrshRequest(originalURL, evt.target); + _makeUrshRequest(originalURL, (result) => { + const outputElement = $('#ursh-shorten-output'); + $('#ursh-shorten-response-form').slideDown(); + outputElement.val(result); + outputElement.select(); + }); } }) .on('click', '.ursh-get', (evt) => { evt.preventDefault(); const originalURL = $(evt.target).attr('data-original-url'); - _makeUrshRequest(originalURL, evt.target); + _makeUrshRequest(originalURL, (result) => { + $(evt.target).copyURLTooltip(result).show(); + }); }); From c39f6bd914e9cc2c9a3c3a1f60ef42a5d738cc15 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Thu, 30 Aug 2018 17:42:42 +0200 Subject: [PATCH 05/29] URSH: Replace qtip with input field --- ursh/indico_ursh/templates/url_shortener.html | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ursh/indico_ursh/templates/url_shortener.html b/ursh/indico_ursh/templates/url_shortener.html index 4843c21..fdb3a40 100644 --- a/ursh/indico_ursh/templates/url_shortener.html +++ b/ursh/indico_ursh/templates/url_shortener.html @@ -1,6 +1,6 @@ {% extends 'layout/base.html' %} -{% block page_class %}fixed-width-standalone-text-page{% endblock %} +{% block page_class %}fixed-width-standalone-page{% endblock %} {% block title -%} {% trans %}URL Shortener{% endtrans %} @@ -10,15 +10,26 @@

{% trans -%} - In this page, you can generate short links for any indico URL.
- Tip: you can also generate short links by using the quick-links in various places within indico! + In this page, you can generate short links for any Indico URL. + Just enter the URL below and click "Shorten". {%- endtrans %}

-
+
+ placeholder="{% trans %}Enter an Indico URL to shorten{% endtrans %}...">
+ +

+ {% trans -%} + Tip: you can also generate short links by using the quick-links in various places within Indico! + Just look for the icon. + {%- endtrans %} +

{%- endblock %} From d881e9e96ff65652c05bdd3cdc5249deafe915af Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Fri, 31 Aug 2018 10:58:43 +0200 Subject: [PATCH 06/29] URSH: Add copy-to-clipboard functionality --- ursh/indico_ursh/templates/url_shortener.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ursh/indico_ursh/templates/url_shortener.html b/ursh/indico_ursh/templates/url_shortener.html index fdb3a40..50c8905 100644 --- a/ursh/indico_ursh/templates/url_shortener.html +++ b/ursh/indico_ursh/templates/url_shortener.html @@ -23,7 +23,9 @@

{% trans -%} From e1100c9e34d6e1c13808595f12aafb399aa9caa8 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Fri, 31 Aug 2018 11:00:02 +0200 Subject: [PATCH 07/29] URSH: Remove callback from _makeUrshRequest --- ursh/indico_ursh/client/index.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 67cfbfc..ffc9d18 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -18,7 +18,7 @@ import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; -async function _makeUrshRequest(originalURL, callback) { +async function _makeUrshRequest(originalURL) { const urshEndpoint = '/ursh'; let response; @@ -31,8 +31,7 @@ async function _makeUrshRequest(originalURL, callback) { return; } - const data = response.data; - callback(data.url); + return response.data.url; } function _patchURL(url) { @@ -86,22 +85,20 @@ function _getUrshInput() { } $(document) - .on('click', '#ursh-shorten-button', (evt) => { + .on('click', '#ursh-shorten-button', async (evt) => { evt.preventDefault(); const originalURL = _getUrshInput(); if (originalURL) { - _makeUrshRequest(originalURL, (result) => { - const outputElement = $('#ursh-shorten-output'); - $('#ursh-shorten-response-form').slideDown(); - outputElement.val(result); - outputElement.select(); - }); + const result = await _makeUrshRequest(originalURL); + const outputElement = $('#ursh-shorten-output'); + $('#ursh-shorten-response-form').slideDown(); + outputElement.val(result); + outputElement.select(); } }) - .on('click', '.ursh-get', (evt) => { + .on('click', '.ursh-get', async (evt) => { evt.preventDefault(); const originalURL = $(evt.target).attr('data-original-url'); - _makeUrshRequest(originalURL, (result) => { - $(evt.target).copyURLTooltip(result).show(); - }); + const result = await _makeUrshRequest(originalURL); + $(evt.target).copyURLTooltip(result).show(); }); From 7f55721fa4b9da873e24a9ecac69cec3455d9d1a Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Fri, 31 Aug 2018 11:02:42 +0200 Subject: [PATCH 08/29] URSH: Increase page font size to 1.2em --- ursh/indico_ursh/templates/url_shortener.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ursh/indico_ursh/templates/url_shortener.html b/ursh/indico_ursh/templates/url_shortener.html index 50c8905..c65611f 100644 --- a/ursh/indico_ursh/templates/url_shortener.html +++ b/ursh/indico_ursh/templates/url_shortener.html @@ -7,7 +7,7 @@ {%- endblock %} {% block content -%} -

+

{% trans -%} In this page, you can generate short links for any Indico URL. From aa8bb6d8e80328b7d3278e81b2d4c105e433f64a Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Mon, 3 Sep 2018 15:20:40 +0200 Subject: [PATCH 09/29] URSH: Refactor index.js URSH: URSH: * Submit on Enter URSH: * Differentiate invalid URL from invalid host --- ursh/indico_ursh/client/index.js | 86 +++++++++++++++++++------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index ffc9d18..495c1ba 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -34,23 +34,34 @@ async function _makeUrshRequest(originalURL) { return response.data.url; } -function _patchURL(url) { - if (url.startsWith(window.location.hostname)) { - return `${window.location.protocol}//${url}`; - } else { - return url; +function _validateAndFormatURL(url) { + const $t = $T.domain('ursh'); + if (!url) { + throw $t.gettext('Please fill in a URL to shorten'); } -} -function _validateURL(url) { - const re = RegExp(`^(${window.location.protocol}//)?${window.location.hostname}/`); - return re.test(url); + // if protocol is missing, prepend it + if (url.startsWith(window.location.hostname)) { + url = `${window.location.protocol}//${url}`; + } + + let parsedURL; + try { + parsedURL = new URL(url); + } catch (err) { + throw $t.gettext('This does not look like a valid URL'); + } + + parsedURL.protocol = window.location.protocol; // patch protocol to match server + if (parsedURL.host !== window.location.host) { + throw $t.gettext('Invalid host: only Indico URLs are allowed'); + } + return parsedURL.href; } function _getUrshInput() { - const $t = $T.domain('ursh'); const input = $('#ursh-shorten-input'); - const originalURL = input.val().trim(); + const inputURL = input.val().trim(); const tip = ((msg) => { input.qtip({ @@ -69,36 +80,43 @@ function _getUrshInput() { }); }); - if (!originalURL) { - tip($t.gettext('Please fill in a URL to shorten')); - input.focus(); + try { + const formattedURL = _validateAndFormatURL(inputURL); + input.val(formattedURL); + return formattedURL; + } catch (err) { + tip(err); + input.focus().select(); return null; - } else if (!_validateURL(originalURL)) { - tip($t.gettext('This does not look like a valid URL')); - input.focus(); - return null; - } else { - const patchedURL = _patchURL(originalURL); - input.val(patchedURL); - return patchedURL; } } -$(document) - .on('click', '#ursh-shorten-button', async (evt) => { - evt.preventDefault(); - const originalURL = _getUrshInput(); - if (originalURL) { - const result = await _makeUrshRequest(originalURL); +async function _handleUrshPageInput(evt) { + evt.preventDefault(); + const originalURL = _getUrshInput(); + if (originalURL) { + const result = await _makeUrshRequest(originalURL); + if (result) { const outputElement = $('#ursh-shorten-output'); $('#ursh-shorten-response-form').slideDown(); outputElement.val(result); outputElement.select(); } + } +} + +async function _handleUrshClick(evt) { + evt.preventDefault(); + const originalURL = $(evt.target).attr('data-original-url'); + const result = await _makeUrshRequest(originalURL); + $(evt.target).copyURLTooltip(result); +} + +$(document) + .on('click', '#ursh-shorten-button', _handleUrshPageInput) + .on('keydown', '#ursh-shorten-input', (evt) => { + if (evt.key === 'Enter') { + _handleUrshPageInput(evt); + } }) - .on('click', '.ursh-get', async (evt) => { - evt.preventDefault(); - const originalURL = $(evt.target).attr('data-original-url'); - const result = await _makeUrshRequest(originalURL); - $(evt.target).copyURLTooltip(result).show(); - }); + .on('click', '.ursh-get', _handleUrshClick); From eb138ba83ba0cfc68c821a77b8225c8331b314f4 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Mon, 3 Sep 2018 17:05:13 +0200 Subject: [PATCH 10/29] URSH: Switch to URL polyfill --- ursh/indico_ursh/client/index.js | 5 +++-- ursh/package.json | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 ursh/package.json diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 495c1ba..05b4b0d 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -15,6 +15,7 @@ * along with Indico; if not, see . */ +import 'url-polyfill'; import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; @@ -41,8 +42,8 @@ function _validateAndFormatURL(url) { } // if protocol is missing, prepend it - if (url.startsWith(window.location.hostname)) { - url = `${window.location.protocol}//${url}`; + if (url.startsWith(location.hostname)) { + url = `${location.protocol}//${url}`; } let parsedURL; diff --git a/ursh/package.json b/ursh/package.json new file mode 100644 index 0000000..68e321b --- /dev/null +++ b/ursh/package.json @@ -0,0 +1,19 @@ +{ + "name": "indico-plugin-ursh", + "version": "1.0.0", + "description": "URL shortening service provider for Indico", + "main": "indico_ursh/client/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/indico/indico-plugins.git" + }, + "author": "Indico Team ", + "license": "GPL-3.0", + "bugs": { + "url": "https://github.com/indico/indico-plugins/issues" + }, + "homepage": "https://github.com/indico/indico-plugins#readme", + "dependencies": { + "url-polyfill": "^1.1.0" + } +} From 6af54aca8ab3751b6d0a449feda35bde31c8d946 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Mon, 3 Sep 2018 17:28:22 +0200 Subject: [PATCH 11/29] URSH: Use .hostname instead of .host URSH: URSH: Fixes issues with IE --- ursh/indico_ursh/client/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 05b4b0d..622bf9f 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -53,8 +53,8 @@ function _validateAndFormatURL(url) { throw $t.gettext('This does not look like a valid URL'); } - parsedURL.protocol = window.location.protocol; // patch protocol to match server - if (parsedURL.host !== window.location.host) { + parsedURL.protocol = location.protocol; // patch protocol to match server + if (parsedURL.hostname !== location.hostname) { throw $t.gettext('Invalid host: only Indico URLs are allowed'); } return parsedURL.href; From 6e47dc6d040d45cd2291d681fd1fa36df69f66a0 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Tue, 4 Sep 2018 15:16:15 +0200 Subject: [PATCH 12/29] URSH: Switch back to RegExp-based URL validation --- ursh/indico_ursh/client/index.js | 29 +++++++++++++++++------------ ursh/package.json | 19 ------------------- 2 files changed, 17 insertions(+), 31 deletions(-) delete mode 100644 ursh/package.json diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 622bf9f..d8f8135 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -15,7 +15,6 @@ * along with Indico; if not, see . */ -import 'url-polyfill'; import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; @@ -38,7 +37,7 @@ async function _makeUrshRequest(originalURL) { function _validateAndFormatURL(url) { const $t = $T.domain('ursh'); if (!url) { - throw $t.gettext('Please fill in a URL to shorten'); + throw Error($t.gettext('Please fill in a URL to shorten')); } // if protocol is missing, prepend it @@ -46,18 +45,24 @@ function _validateAndFormatURL(url) { url = `${location.protocol}//${url}`; } - let parsedURL; - try { - parsedURL = new URL(url); - } catch (err) { - throw $t.gettext('This does not look like a valid URL'); + // regular expression, because I.E. does not like the URL class + const re = RegExp(`^(.+:)//([^/]+)(/.*)?$`); + const urlTokens = url.match(re); + if (!urlTokens) { + throw Error($t.gettext('This does not look like a valid URL')); } - parsedURL.protocol = location.protocol; // patch protocol to match server - if (parsedURL.hostname !== location.hostname) { - throw $t.gettext('Invalid host: only Indico URLs are allowed'); + // extract tokens + let protocol = urlTokens[1]; + const hostname = urlTokens[2]; + const path = urlTokens[3] ? urlTokens[3] : '/'; + + protocol = location.protocol; // patch protocol to match server + if (hostname !== location.hostname) { + throw Error($t.gettext('Invalid host: only Indico URLs are allowed')); } - return parsedURL.href; + + return `${protocol}//${hostname}${path}`; } function _getUrshInput() { @@ -86,7 +91,7 @@ function _getUrshInput() { input.val(formattedURL); return formattedURL; } catch (err) { - tip(err); + tip(err.message); input.focus().select(); return null; } diff --git a/ursh/package.json b/ursh/package.json deleted file mode 100644 index 68e321b..0000000 --- a/ursh/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "indico-plugin-ursh", - "version": "1.0.0", - "description": "URL shortening service provider for Indico", - "main": "indico_ursh/client/index.js", - "repository": { - "type": "git", - "url": "git+https://github.com/indico/indico-plugins.git" - }, - "author": "Indico Team ", - "license": "GPL-3.0", - "bugs": { - "url": "https://github.com/indico/indico-plugins/issues" - }, - "homepage": "https://github.com/indico/indico-plugins#readme", - "dependencies": { - "url-polyfill": "^1.1.0" - } -} From 436d9342fe68cc1888cf13489be3b588537362fd Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Wed, 5 Sep 2018 14:08:14 +0200 Subject: [PATCH 13/29] URSH: Don't raise an exeption if ursh responds with 400 URSH: URSH: Allows the client to give a user-friendly diagnostic message --- ursh/indico_ursh/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py index 105e13a..3802839 100644 --- a/ursh/indico_ursh/util.py +++ b/ursh/indico_ursh/util.py @@ -30,8 +30,10 @@ def request_short_url(original_url): raise ServiceUnavailable('Not configured') headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)} + response = requests.post(api_host, data=dict(url=original_url, allow_reuse=True), headers=headers) - response.raise_for_status() + if response.status_code not in (200, 201, 400): + response.raise_for_status() data = response.json() - return data['short_url'] + return data.get('short_url') From a338ddbbdbc805ded6abba9ebbdfd8a46a08746c Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Wed, 5 Sep 2018 14:10:08 +0200 Subject: [PATCH 14/29] URSH: Fix regex, catch more invalid URLs --- ursh/indico_ursh/client/index.js | 51 ++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index d8f8135..67a0972 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -18,6 +18,23 @@ import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; +function _showTip(element, msg) { + element.qtip({ + content: { + text: msg + }, + hide: { + event: 'mouseleave', + fixed: true, + delay: 700 + }, + show: { + event: false, + ready: true + } + }); +} + async function _makeUrshRequest(originalURL) { const urshEndpoint = '/ursh'; @@ -46,7 +63,8 @@ function _validateAndFormatURL(url) { } // regular expression, because I.E. does not like the URL class - const re = RegExp(`^(.+:)//([^/]+)(/.*)?$`); + // provides minimal validation, leaving the serious job to the server + const re = RegExp(`^([\\d\\w]+:)//([^/ .]+(?:\\.[^/ .]+)*)(/.*)?$`); const urlTokens = url.match(re); if (!urlTokens) { throw Error($t.gettext('This does not look like a valid URL')); @@ -65,33 +83,16 @@ function _validateAndFormatURL(url) { return `${protocol}//${hostname}${path}`; } -function _getUrshInput() { - const input = $('#ursh-shorten-input'); +function _getUrshInput(input) { const inputURL = input.val().trim(); - - const tip = ((msg) => { - input.qtip({ - content: { - text: msg - }, - hide: { - event: 'mouseleave', - fixed: true, - delay: 700 - }, - show: { - event: false, - ready: true - } - }); - }); + input.val(inputURL); try { const formattedURL = _validateAndFormatURL(inputURL); input.val(formattedURL); return formattedURL; } catch (err) { - tip(err.message); + _showTip(input, err.message); input.focus().select(); return null; } @@ -99,7 +100,10 @@ function _getUrshInput() { async function _handleUrshPageInput(evt) { evt.preventDefault(); - const originalURL = _getUrshInput(); + const $t = $T.domain('ursh'); + + const input = $('#ursh-shorten-input'); + const originalURL = _getUrshInput(input); if (originalURL) { const result = await _makeUrshRequest(originalURL); if (result) { @@ -107,6 +111,9 @@ async function _handleUrshPageInput(evt) { $('#ursh-shorten-response-form').slideDown(); outputElement.val(result); outputElement.select(); + } else { + _showTip(input, $t.gettext('This does not look like a valid URL')); + input.focus().select(); } } } From 082d01dc3e93e8de1b61c4baf2b9f04c6238f46d Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Fri, 7 Sep 2018 11:20:03 +0200 Subject: [PATCH 15/29] URSH: Avoid jQuery in obtaining data attribute --- ursh/indico_ursh/client/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 67a0972..81fa9cc 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -120,7 +120,7 @@ async function _handleUrshPageInput(evt) { async function _handleUrshClick(evt) { evt.preventDefault(); - const originalURL = $(evt.target).attr('data-original-url'); + const originalURL = evt.target.dataset.originalUrl; const result = await _makeUrshRequest(originalURL); $(evt.target).copyURLTooltip(result); } From b2b9ac1b397df78e9506182195258e9b46ade45a Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Wed, 12 Sep 2018 18:10:27 +0200 Subject: [PATCH 16/29] URSH: Add custom shortcut creation via dropdown --- ursh/indico_ursh/blueprint.py | 6 +- ursh/indico_ursh/client/index.js | 30 ++++++++-- ursh/indico_ursh/controllers.py | 52 ++++++++++++++-- ursh/indico_ursh/plugin.py | 16 +++-- ursh/indico_ursh/templates/url_shortener.html | 37 ------------ ursh/indico_ursh/templates/ursh_button.html | 5 -- .../templates/ursh_custom_shortener_page.html | 59 +++++++++++++++++++ ursh/indico_ursh/templates/ursh_dropdown.html | 23 ++++++++ ursh/indico_ursh/templates/ursh_footer.html | 12 ++++ ursh/indico_ursh/templates/ursh_link.html | 32 ++++++++++ .../templates/ursh_shortener_page.html | 35 +++++++++++ ursh/indico_ursh/util.py | 33 ++++++++++- 12 files changed, 276 insertions(+), 64 deletions(-) delete mode 100644 ursh/indico_ursh/templates/url_shortener.html delete mode 100644 ursh/indico_ursh/templates/ursh_button.html create mode 100644 ursh/indico_ursh/templates/ursh_custom_shortener_page.html create mode 100644 ursh/indico_ursh/templates/ursh_dropdown.html create mode 100644 ursh/indico_ursh/templates/ursh_footer.html create mode 100644 ursh/indico_ursh/templates/ursh_link.html create mode 100644 ursh/indico_ursh/templates/ursh_shortener_page.html diff --git a/ursh/indico_ursh/blueprint.py b/ursh/indico_ursh/blueprint.py index 8ce992c..ffd6091 100644 --- a/ursh/indico_ursh/blueprint.py +++ b/ursh/indico_ursh/blueprint.py @@ -18,9 +18,11 @@ from __future__ import unicode_literals from indico.core.plugins import IndicoPluginBlueprint -from indico_ursh.controllers import RHDisplayShortenURLPage, RHGetShortURL +from indico_ursh.controllers import RHCustomShortURLPage, RHGetShortURL, RHShortURLPage blueprint = IndicoPluginBlueprint('ursh', 'indico_ursh') blueprint.add_url_rule('/ursh', 'get_short_url', RHGetShortURL, methods=('POST',)) -blueprint.add_url_rule('/url-shortener', 'shorten_url', RHDisplayShortenURLPage) +blueprint.add_url_rule('/url-shortener', 'shorten_url', RHShortURLPage) +blueprint.add_url_rule('/event//manage/short-url', 'shorten_event_url', RHCustomShortURLPage, + methods=('GET', 'POST')) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 81fa9cc..6e2bb6a 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -18,13 +18,15 @@ import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; -function _showTip(element, msg) { +const $t = $T.domain('ursh'); + +function _showTip(element, msg, hideEvent = 'unfocus') { element.qtip({ content: { text: msg }, hide: { - event: 'mouseleave', + event: hideEvent, fixed: true, delay: 700 }, @@ -52,7 +54,6 @@ async function _makeUrshRequest(originalURL) { } function _validateAndFormatURL(url) { - const $t = $T.domain('ursh'); if (!url) { throw Error($t.gettext('Please fill in a URL to shorten')); } @@ -100,7 +101,6 @@ function _getUrshInput(input) { async function _handleUrshPageInput(evt) { evt.preventDefault(); - const $t = $T.domain('ursh'); const input = $('#ursh-shorten-input'); const originalURL = _getUrshInput(input); @@ -122,7 +122,11 @@ async function _handleUrshClick(evt) { evt.preventDefault(); const originalURL = evt.target.dataset.originalUrl; const result = await _makeUrshRequest(originalURL); - $(evt.target).copyURLTooltip(result); + $(evt.target).copyURLTooltip(result, 'unfocus'); +} + +function _validateUrshCustomShortcut(shortcut) { + return shortcut.length >= 5; } $(document) @@ -132,4 +136,18 @@ $(document) _handleUrshPageInput(evt); } }) - .on('click', '.ursh-get', _handleUrshClick); + .on('click', '.ursh-get', _handleUrshClick) + .on('input', '#ursh-custom-shortcut-input', (evt) => { + const value = $(evt.target).val(); + $('#ursh-custom-shortcut-submit-button').prop('disabled', !_validateUrshCustomShortcut(value)) + }) + .on('mouseenter', '#ursh-custom-shortcut-submit-button', (evt) => { + if (evt.target.disabled) { + _showTip($(evt.target), $t.gettext('Please check that the shortcut is correct'), 'mouseleave'); + } + }); + +$(document).ready(() => { + // keep dropdown menu open when clicking on an entry + $('.ursh-dropdown').next('ul').find('li a').on('menu_select', () => true); +}); diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py index 254f7b2..b2159eb 100644 --- a/ursh/indico_ursh/controllers.py +++ b/ursh/indico_ursh/controllers.py @@ -18,15 +18,19 @@ from __future__ import unicode_literals import posixpath -from flask import jsonify, request -from werkzeug.exceptions import BadRequest -from werkzeug.urls import url_parse +from flask_pluginengine import render_plugin_template from indico.core.config import config +from indico.modules.events.management.controllers import RHManageEventBase from indico.web.rh import RH +from indico.web.util import jsonify, jsonify_template -from indico_ursh.util import request_short_url +from flask import jsonify, request +from indico_ursh import _ +from indico_ursh.util import register_shortcut, request_short_url, strip_end from indico_ursh.views import WPShortenURLPage +from werkzeug.exceptions import BadRequest +from werkzeug.urls import url_parse class RHGetShortURL(RH): @@ -52,8 +56,44 @@ class RHGetShortURL(RH): return jsonify(url=short_url) -class RHDisplayShortenURLPage(RH): +class RHShortURLPage(RH): """Provide a simple page, where users can submit a URL to be shortened""" def _process(self): - return WPShortenURLPage.render_template('url_shortener.html') + return WPShortenURLPage.render_template('ursh_shortener_page.html') + + +class RHCustomShortURLPage(RHManageEventBase): + """Provide a simple page, where users can submit a URL to be shortened""" + + def _make_absolute_url(self, url): + return posixpath.join(config.BASE_URL, url[1:]) if url.startswith('/') else url + + def _get_error_msg(self, response): + if response['status'] == 403: + return _('Shortcut already exists') + + def _process_args(self): + from indico_ursh.plugin import UrshPlugin + super(RHCustomShortURLPage, self)._process_args() + api_host = url_parse(UrshPlugin.settings.get('api_host')) + self.ursh_host = strip_end(api_host.to_url(), api_host.path[1:]) + + def _process_GET(self): + original_url = self._make_absolute_url(request.args['original_url']) + return WPShortenURLPage.render_template('ursh_custom_shortener_page.html', + event=self.event, + ursh_host=self.ursh_host, + original_url=original_url) + + def _process_POST(self): + original_url = self._make_absolute_url(request.args['original_url']) + shortcut = request.form['shortcut'] + result = register_shortcut(original_url, shortcut) + + error = result.get('error') + kwargs = {'success': True} if not error else {'success': False, 'msg': self._get_error_msg(result)} + + return jsonify_template('ursh_custom_shortener_page.html', render_plugin_template, + event=self.event, ursh_host=self.ursh_host, shortcut=shortcut, + original_url=original_url, **kwargs) diff --git a/ursh/indico_ursh/plugin.py b/ursh/indico_ursh/plugin.py index f445921..b934312 100644 --- a/ursh/indico_ursh/plugin.py +++ b/ursh/indico_ursh/plugin.py @@ -19,6 +19,7 @@ from __future__ import unicode_literals from flask_pluginengine import render_plugin_template from indico.core.plugins import IndicoPlugin +from indico.web.flask.util import url_for from indico.web.forms.base import IndicoForm from indico.web.views import WPBase @@ -51,13 +52,18 @@ class UrshPlugin(IndicoPlugin): def init(self): super(UrshPlugin, self).init() - self.template_hook('url-shortener-link', self._inject_ursh_button) + self.template_hook('url-shortener', self._inject_ursh_link) + self.template_hook('page-footer', self._inject_ursh_footer) self.inject_bundle('main.js', WPBase) def get_blueprints(self): return blueprint - def _inject_ursh_button(self, target, element_type='a', element_class='', text='(short-url)', **kwargs): - if self.settings.get('api_key') and self.settings.get('api_host'): - return render_plugin_template('ursh_button.html', target=target, - element_type=element_type, element_class=element_class, text=text) + def _inject_ursh_link(self, target=None, event=None, dropdown=False, element_class='', text='', **kwargs): + if self.settings.get('api_key') and self.settings.get('api_host') and (target or event): + return render_plugin_template('ursh_link.html', target=target, event=event, + dropdown=dropdown, element_class=element_class, text=text, **kwargs) + + def _inject_ursh_footer(self, **kwargs): + url = url_for('plugin_ursh.shorten_url') + return render_plugin_template('ursh_footer.html') diff --git a/ursh/indico_ursh/templates/url_shortener.html b/ursh/indico_ursh/templates/url_shortener.html deleted file mode 100644 index c65611f..0000000 --- a/ursh/indico_ursh/templates/url_shortener.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'layout/base.html' %} - -{% block page_class %}fixed-width-standalone-page{% endblock %} - -{% block title -%} - {% trans %}URL Shortener{% endtrans %} -{%- endblock %} - -{% block content -%} -

-

- {% trans -%} - In this page, you can generate short links for any Indico URL. - Just enter the URL below and click "Shorten". - {%- endtrans %} -

-
- - -
- -

- {% trans -%} - Tip: you can also generate short links by using the quick-links in various places within Indico! - Just look for the icon. - {%- endtrans %} -

-
-{%- endblock %} diff --git a/ursh/indico_ursh/templates/ursh_button.html b/ursh/indico_ursh/templates/ursh_button.html deleted file mode 100644 index f08c6c5..0000000 --- a/ursh/indico_ursh/templates/ursh_button.html +++ /dev/null @@ -1,5 +0,0 @@ -<{{ element_type }} class="ursh-get {{ element_class }}" - title="{% trans %}Obtain short URL{% endtrans %}" - data-original-url="{{ target }}"> - {{- text -}} - diff --git a/ursh/indico_ursh/templates/ursh_custom_shortener_page.html b/ursh/indico_ursh/templates/ursh_custom_shortener_page.html new file mode 100644 index 0000000..70e2870 --- /dev/null +++ b/ursh/indico_ursh/templates/ursh_custom_shortener_page.html @@ -0,0 +1,59 @@ +{% extends 'layout/base.html' %} + +{% block page_class %}fixed-width-standalone-text-page{% endblock %} + +{% block title -%} + {% trans %}Create custom shortcut{% endtrans %} +{%- endblock %} + +{% block content -%} + {% if success == False %} +
+
+ +
{% trans %}Custom URL creation failed:{% endtrans %} {{ msg }}
+
+
+ {% elif success == True %} +
+
+ +
{% trans %}Custom URL created successfully{% endtrans %}
+
+
+ {% endif %} +

{% trans %}Creating a custom shortcut for the following URL:{% endtrans %}

+
+ +
+

{% trans %}Please enter the desired shortcut below (at least 5 chars).{% endtrans %}

+
+
+ + + +
+
+ + {% if success == True %} + + {% endif %} +{%- endblock %} diff --git a/ursh/indico_ursh/templates/ursh_dropdown.html b/ursh/indico_ursh/templates/ursh_dropdown.html new file mode 100644 index 0000000..6a57f9e --- /dev/null +++ b/ursh/indico_ursh/templates/ursh_dropdown.html @@ -0,0 +1,23 @@ + + diff --git a/ursh/indico_ursh/templates/ursh_footer.html b/ursh/indico_ursh/templates/ursh_footer.html new file mode 100644 index 0000000..fdd36f5 --- /dev/null +++ b/ursh/indico_ursh/templates/ursh_footer.html @@ -0,0 +1,12 @@ +{% if url %} + {% trans %}URL Shortener{% endtrans %} +{% else %} + + {% trans %}URL Shortener{% endtrans %} + +{% endif %} diff --git a/ursh/indico_ursh/templates/ursh_link.html b/ursh/indico_ursh/templates/ursh_link.html new file mode 100644 index 0000000..5ad81fa --- /dev/null +++ b/ursh/indico_ursh/templates/ursh_link.html @@ -0,0 +1,32 @@ +{% set event_manager = event and event.can_manage(session.user) %} +{% set target = event.url if event else target %} +{% if dropdown or event_manager %} + + +{% else %} + + {{- text -}} + +{% endif %} diff --git a/ursh/indico_ursh/templates/ursh_shortener_page.html b/ursh/indico_ursh/templates/ursh_shortener_page.html new file mode 100644 index 0000000..e6e2287 --- /dev/null +++ b/ursh/indico_ursh/templates/ursh_shortener_page.html @@ -0,0 +1,35 @@ +{% extends 'layout/base.html' %} + +{% block page_class %}fixed-width-standalone-text-page{% endblock %} + +{% block title -%} + {% trans %}URL Shortener{% endtrans %} +{%- endblock %} + +{% block content -%} +

+ {% trans -%} + In this page, you can generate short links for any Indico URL. + Just enter the URL below and click "Shorten". + {%- endtrans %} +

+
+ + +
+ +

+ {% trans -%} + Tip: you can also generate short links by using the quick-links in various places within Indico! + Just look for the icon. + {%- endtrans %} +

+{%- endblock %} diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py index 3802839..c0c2a3b 100644 --- a/ursh/indico_ursh/util.py +++ b/ursh/indico_ursh/util.py @@ -16,12 +16,13 @@ from __future__ import unicode_literals -from werkzeug.exceptions import ServiceUnavailable +import posixpath import requests +from werkzeug.exceptions import ServiceUnavailable -def request_short_url(original_url): +def _get_settings(): from indico_ursh.plugin import UrshPlugin api_key = UrshPlugin.settings.get('api_key') api_host = UrshPlugin.settings.get('api_host') @@ -29,11 +30,37 @@ def request_short_url(original_url): if not api_key or not api_host: raise ServiceUnavailable('Not configured') + return api_key, api_host + + +def request_short_url(original_url): + api_key, api_host = _get_settings() headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)} response = requests.post(api_host, data=dict(url=original_url, allow_reuse=True), headers=headers) - if response.status_code not in (200, 201, 400): + if response.status_code not in (400, ): response.raise_for_status() data = response.json() return data.get('short_url') + + +def register_shortcut(original_url, shortcut): + api_key, api_host = _get_settings() + headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)} + + # ursh expects shortcut as a path argument + api_host = posixpath.join(api_host, shortcut) + + response = requests.put(api_host, data=dict(url=original_url), headers=headers) + if response.status_code not in (403, ): + response.raise_for_status() + + data = response.json() + return data + + +def strip_end(text, suffix): + if not text.endswith(suffix): + return text + return text[:len(text)-len(suffix)] From 4be6c7e81f5c974e4954b6045a3db8e2a3cfa178 Mon Sep 17 00:00:00 2001 From: Ergys Dona Date: Wed, 19 Sep 2018 22:15:32 +0200 Subject: [PATCH 17/29] URSH: Rename element_class to classes --- ursh/indico_ursh/templates/ursh_link.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ursh/indico_ursh/templates/ursh_link.html b/ursh/indico_ursh/templates/ursh_link.html index 5ad81fa..3f66771 100644 --- a/ursh/indico_ursh/templates/ursh_link.html +++ b/ursh/indico_ursh/templates/ursh_link.html @@ -1,7 +1,7 @@ {% set event_manager = event and event.can_manage(session.user) %} {% set target = event.url if event else target %} {% if dropdown or event_manager %} - {% else %} - {{- text -}} From 91393b9176d89179cb561ce05fbdd8e06a390dd0 Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Mon, 13 May 2019 16:56:10 +0200 Subject: [PATCH 18/29] URSH: Fix license, headers, isort, version --- ursh/indico_ursh/__init__.py | 19 +++++-------------- ursh/indico_ursh/blueprint.py | 19 +++++-------------- ursh/indico_ursh/client/index.js | 22 ++++++---------------- ursh/indico_ursh/controllers.py | 25 ++++++++----------------- ursh/indico_ursh/plugin.py | 25 ++++++++----------------- ursh/indico_ursh/util.py | 19 +++++-------------- ursh/indico_ursh/views.py | 19 +++++-------------- ursh/setup.py | 23 +++++++---------------- 8 files changed, 49 insertions(+), 122 deletions(-) diff --git a/ursh/indico_ursh/__init__.py b/ursh/indico_ursh/__init__.py index 7e6aeec..c753ada 100644 --- a/ursh/indico_ursh/__init__.py +++ b/ursh/indico_ursh/__init__.py @@ -1,18 +1,9 @@ -# This file is part of Indico. -# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# This file is part of the Indico plugins. +# Copyright (C) 2002 - 2019 CERN # -# Indico is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# Indico is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Indico; if not, see . +# 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 diff --git a/ursh/indico_ursh/blueprint.py b/ursh/indico_ursh/blueprint.py index ffd6091..c567622 100644 --- a/ursh/indico_ursh/blueprint.py +++ b/ursh/indico_ursh/blueprint.py @@ -1,18 +1,9 @@ -# This file is part of Indico. -# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# This file is part of the Indico plugins. +# Copyright (C) 2002 - 2019 CERN # -# Indico is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# Indico is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Indico; if not, see . +# 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 diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 6e2bb6a..5d84c8d 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -1,19 +1,9 @@ -/* This file is part of Indico. - * Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). - * - * Indico is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Indico is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Indico; if not, see . - */ +// This file is part of the Indico plugins. +// Copyright (C) 2002 - 2019 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 {handleAxiosError, indicoAxios} from 'indico/utils/axios'; diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py index b2159eb..1ac5f4e 100644 --- a/ursh/indico_ursh/controllers.py +++ b/ursh/indico_ursh/controllers.py @@ -1,36 +1,27 @@ -# This file is part of Indico. -# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# This file is part of the Indico plugins. +# Copyright (C) 2002 - 2019 CERN # -# Indico is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# Indico is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Indico; if not, see . +# 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 posixpath +from flask import jsonify, request from flask_pluginengine import render_plugin_template +from werkzeug.exceptions import BadRequest +from werkzeug.urls import url_parse from indico.core.config import config from indico.modules.events.management.controllers import RHManageEventBase from indico.web.rh import RH from indico.web.util import jsonify, jsonify_template -from flask import jsonify, request from indico_ursh import _ from indico_ursh.util import register_shortcut, request_short_url, strip_end from indico_ursh.views import WPShortenURLPage -from werkzeug.exceptions import BadRequest -from werkzeug.urls import url_parse class RHGetShortURL(RH): diff --git a/ursh/indico_ursh/plugin.py b/ursh/indico_ursh/plugin.py index b934312..d5cf08e 100644 --- a/ursh/indico_ursh/plugin.py +++ b/ursh/indico_ursh/plugin.py @@ -1,22 +1,16 @@ -# This file is part of Indico. -# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# This file is part of the Indico plugins. +# Copyright (C) 2002 - 2019 CERN # -# Indico is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# Indico is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Indico; if not, see . +# 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 render_plugin_template +from wtforms.fields.core import StringField +from wtforms.fields.html5 import URLField +from wtforms.validators import DataRequired from indico.core.plugins import IndicoPlugin from indico.web.flask.util import url_for @@ -25,9 +19,6 @@ from indico.web.views import WPBase from indico_ursh import _ from indico_ursh.blueprint import blueprint -from wtforms.fields.core import StringField -from wtforms.fields.html5 import URLField -from wtforms.validators import DataRequired class SettingsForm(IndicoForm): diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py index c0c2a3b..2280b74 100644 --- a/ursh/indico_ursh/util.py +++ b/ursh/indico_ursh/util.py @@ -1,18 +1,9 @@ -# This file is part of Indico. -# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# This file is part of the Indico plugins. +# Copyright (C) 2002 - 2019 CERN # -# Indico is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# Indico is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Indico; if not, see . +# 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 diff --git a/ursh/indico_ursh/views.py b/ursh/indico_ursh/views.py index 7287625..5e86be9 100644 --- a/ursh/indico_ursh/views.py +++ b/ursh/indico_ursh/views.py @@ -1,18 +1,9 @@ -# This file is part of Indico. -# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# This file is part of the Indico plugins. +# Copyright (C) 2002 - 2019 CERN # -# Indico is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# Indico is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Indico; if not, see . +# 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 diff --git a/ursh/setup.py b/ursh/setup.py index 7cde8f5..cb0fee5 100644 --- a/ursh/setup.py +++ b/ursh/setup.py @@ -1,18 +1,9 @@ -# This file is part of Indico. -# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# This file is part of the Indico plugins. +# Copyright (C) 2002 - 2019 CERN # -# Indico is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# Indico is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Indico; if not, see . +# 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 @@ -21,10 +12,10 @@ from setuptools import find_packages, setup setup( name='indico-plugin-ursh', - version='3.0-dev', + version='2.2-dev', description='URL shortening service for Indico', url='https://github.com/indico/indico-plugins', - license='https://www.gnu.org/licenses/gpl-3.0.txt', + license='MIT', author='Indico Team', author_email='indico-team@cern.ch', packages=find_packages(), From 43a3fa79aa665af53b57774d3c5e0a9d0761bddc Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Mon, 13 May 2019 17:01:24 +0200 Subject: [PATCH 19/29] URSH: Fix js issues --- ursh/indico_ursh/client/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 5d84c8d..9592a94 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -62,11 +62,10 @@ function _validateAndFormatURL(url) { } // extract tokens - let protocol = urlTokens[1]; const hostname = urlTokens[2]; const path = urlTokens[3] ? urlTokens[3] : '/'; - protocol = location.protocol; // patch protocol to match server + const protocol = location.protocol; // patch protocol to match server if (hostname !== location.hostname) { throw Error($t.gettext('Invalid host: only Indico URLs are allowed')); } @@ -129,7 +128,7 @@ $(document) .on('click', '.ursh-get', _handleUrshClick) .on('input', '#ursh-custom-shortcut-input', (evt) => { const value = $(evt.target).val(); - $('#ursh-custom-shortcut-submit-button').prop('disabled', !_validateUrshCustomShortcut(value)) + $('#ursh-custom-shortcut-submit-button').prop('disabled', !_validateUrshCustomShortcut(value)); }) .on('mouseenter', '#ursh-custom-shortcut-submit-button', (evt) => { if (evt.target.disabled) { From 796a7fd9e381a5a3b4d3a8eadef25afc6dc21612 Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Mon, 13 May 2019 17:50:59 +0200 Subject: [PATCH 20/29] URSH: Fix various issues --- ursh/indico_ursh/controllers.py | 22 ++++++++++------- ursh/indico_ursh/plugin.py | 2 -- .../templates/ursh_custom_shortener_page.html | 24 +++++++++---------- ursh/indico_ursh/templates/ursh_footer.html | 20 +++++++--------- .../templates/ursh_shortener_page.html | 1 + ursh/indico_ursh/util.py | 20 +++++++--------- ursh/indico_ursh/views.py | 4 ++-- 7 files changed, 45 insertions(+), 48 deletions(-) diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py index 1ac5f4e..e1c013d 100644 --- a/ursh/indico_ursh/controllers.py +++ b/ursh/indico_ursh/controllers.py @@ -60,31 +60,37 @@ class RHCustomShortURLPage(RHManageEventBase): def _make_absolute_url(self, url): return posixpath.join(config.BASE_URL, url[1:]) if url.startswith('/') else url - def _get_error_msg(self, response): - if response['status'] == 403: + def _get_error_msg(self, result): + if result['status'] == 409: return _('Shortcut already exists') + elif result['status'] == 400: + return _('Malformed shortcut') + return result['error'].get('description') def _process_args(self): from indico_ursh.plugin import UrshPlugin super(RHCustomShortURLPage, self)._process_args() api_host = url_parse(UrshPlugin.settings.get('api_host')) - self.ursh_host = strip_end(api_host.to_url(), api_host.path[1:]) + self.ursh_host = strip_end(api_host.to_url(), api_host.path[1:]).rstrip('/') + '/' def _process_GET(self): original_url = self._make_absolute_url(request.args['original_url']) return WPShortenURLPage.render_template('ursh_custom_shortener_page.html', event=self.event, ursh_host=self.ursh_host, - original_url=original_url) + original_url=original_url, + submitted=False) def _process_POST(self): original_url = self._make_absolute_url(request.args['original_url']) - shortcut = request.form['shortcut'] + shortcut = request.form['shortcut'].strip() result = register_shortcut(original_url, shortcut) - error = result.get('error') - kwargs = {'success': True} if not error else {'success': False, 'msg': self._get_error_msg(result)} + if result.get('error'): + kwargs = {'success': False, 'msg': self._get_error_msg(result)} + else: + kwargs = {'success': True, 'shorturl': result['short_url']} return jsonify_template('ursh_custom_shortener_page.html', render_plugin_template, event=self.event, ursh_host=self.ursh_host, shortcut=shortcut, - original_url=original_url, **kwargs) + original_url=original_url, submitted=True, **kwargs) diff --git a/ursh/indico_ursh/plugin.py b/ursh/indico_ursh/plugin.py index d5cf08e..37da0b8 100644 --- a/ursh/indico_ursh/plugin.py +++ b/ursh/indico_ursh/plugin.py @@ -13,7 +13,6 @@ from wtforms.fields.html5 import URLField from wtforms.validators import DataRequired from indico.core.plugins import IndicoPlugin -from indico.web.flask.util import url_for from indico.web.forms.base import IndicoForm from indico.web.views import WPBase @@ -56,5 +55,4 @@ class UrshPlugin(IndicoPlugin): dropdown=dropdown, element_class=element_class, text=text, **kwargs) def _inject_ursh_footer(self, **kwargs): - url = url_for('plugin_ursh.shorten_url') return render_plugin_template('ursh_footer.html') diff --git a/ursh/indico_ursh/templates/ursh_custom_shortener_page.html b/ursh/indico_ursh/templates/ursh_custom_shortener_page.html index 70e2870..30c4721 100644 --- a/ursh/indico_ursh/templates/ursh_custom_shortener_page.html +++ b/ursh/indico_ursh/templates/ursh_custom_shortener_page.html @@ -7,14 +7,14 @@ {%- endblock %} {% block content -%} - {% if success == False %} + {% if submitted and not success %}
{% trans %}Custom URL creation failed:{% endtrans %} {{ msg }}
- {% elif success == True %} + {% elif submitted and success %}
@@ -27,15 +27,16 @@

{% trans %}Please enter the desired shortcut below (at least 5 chars).{% endtrans %}

-
+
- + spellcheck="false" autocomplete="off" autocapitalize="off" + pattern="^[0-9a-zA-Z-]{5,}$" + {% if shortcut %}value="{{ shortcut }}"{% endif %} + placeholder="{% trans %}Enter a shortcut...{% endtrans %}"> @@ -43,16 +44,13 @@ - {% if success == True %} + {% if submitted and success and shorturl %} {% endif %} diff --git a/ursh/indico_ursh/templates/ursh_footer.html b/ursh/indico_ursh/templates/ursh_footer.html index fdd36f5..7732352 100644 --- a/ursh/indico_ursh/templates/ursh_footer.html +++ b/ursh/indico_ursh/templates/ursh_footer.html @@ -1,12 +1,8 @@ -{% if url %} - {% trans %}URL Shortener{% endtrans %} -{% else %} - - {% trans %}URL Shortener{% endtrans %} - -{% endif %} + + {% trans %}URL Shortener{% endtrans %} + diff --git a/ursh/indico_ursh/templates/ursh_shortener_page.html b/ursh/indico_ursh/templates/ursh_shortener_page.html index e6e2287..fc512b8 100644 --- a/ursh/indico_ursh/templates/ursh_shortener_page.html +++ b/ursh/indico_ursh/templates/ursh_shortener_page.html @@ -16,6 +16,7 @@
diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py index 2280b74..8484f6a 100644 --- a/ursh/indico_ursh/util.py +++ b/ursh/indico_ursh/util.py @@ -27,31 +27,29 @@ def _get_settings(): def request_short_url(original_url): api_key, api_host = _get_settings() headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)} + url = posixpath.join(api_host, 'urls/') - response = requests.post(api_host, data=dict(url=original_url, allow_reuse=True), headers=headers) - if response.status_code not in (400, ): + response = requests.post(url, data={'url': original_url, 'allow_reuse': True}, headers=headers) + if response.status_code not in (400,): response.raise_for_status() data = response.json() - return data.get('short_url') + return data['short_url'] def register_shortcut(original_url, shortcut): api_key, api_host = _get_settings() headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)} + url = posixpath.join(api_host, 'urls', shortcut) - # ursh expects shortcut as a path argument - api_host = posixpath.join(api_host, shortcut) - - response = requests.put(api_host, data=dict(url=original_url), headers=headers) - if response.status_code not in (403, ): + response = requests.put(url, data={'url': original_url, 'allow_reuse': True}, headers=headers) + if not (400 <= response.status_code < 500): response.raise_for_status() - data = response.json() - return data + return response.json() def strip_end(text, suffix): if not text.endswith(suffix): return text - return text[:len(text)-len(suffix)] + return text[:len(text) - len(suffix)] diff --git a/ursh/indico_ursh/views.py b/ursh/indico_ursh/views.py index 5e86be9..d47b840 100644 --- a/ursh/indico_ursh/views.py +++ b/ursh/indico_ursh/views.py @@ -12,5 +12,5 @@ from indico.web.views import WPDecorated class WPShortenURLPage(WPJinjaMixinPlugin, WPDecorated): - def _getBody(self, params): - return self._getPageContent(params) + def _get_body(self, params): + return self._get_page_content(params) From 2b831d7ef84e35aa7adbeea4d9af30e28fa3601b Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 20 Aug 2019 12:17:00 +0200 Subject: [PATCH 21/29] URSH: Clarify action titles The difference between "obtain" and "create" isn't really obvious --- ursh/indico_ursh/templates/ursh_dropdown.html | 4 ++-- ursh/indico_ursh/templates/ursh_link.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ursh/indico_ursh/templates/ursh_dropdown.html b/ursh/indico_ursh/templates/ursh_dropdown.html index 6a57f9e..5c6314f 100644 --- a/ursh/indico_ursh/templates/ursh_dropdown.html +++ b/ursh/indico_ursh/templates/ursh_dropdown.html @@ -4,7 +4,7 @@
  • - {{ trans }}Obtain short URL{{ end_trans }} + {{ trans }}Get short URL{{ end_trans }}
  • {%- if allow_create %} @@ -16,7 +16,7 @@ data-params='{"original_url": "{{ target }}"}' data-ajax-dialog data-hide-page-header> - {{ trans }}Create short URL{{ end_trans }} + {{ trans }}Create custom short URL{{ end_trans }} {% endif -%} diff --git a/ursh/indico_ursh/templates/ursh_link.html b/ursh/indico_ursh/templates/ursh_link.html index 3f66771..2d7617b 100644 --- a/ursh/indico_ursh/templates/ursh_link.html +++ b/ursh/indico_ursh/templates/ursh_link.html @@ -6,7 +6,7 @@

    {% trans -%} - Tip: you can also generate short links by using the quick-links in various places within Indico! - Just look for the icon. + Tip: You can also generate short links by using the quick-links in various + places within Indico! Just look for the icon. {%- endtrans %}

    {%- endblock %} From 0fac1a4ae84021f3c9a8a3045f7bf5e3103b198c Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 20 Aug 2019 14:36:30 +0200 Subject: [PATCH 27/29] URSH: Validate custom shortcuts There shouldn't be any risk even if someone sends garbage containing let's say `../`, but better to stay on the safe side than actually sending an API request containing something obviously incorrect. --- ursh/indico_ursh/controllers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py index e11cf67..44514b4 100644 --- a/ursh/indico_ursh/controllers.py +++ b/ursh/indico_ursh/controllers.py @@ -24,6 +24,9 @@ from indico_ursh.util import register_shortcut, request_short_url, strip_end from indico_ursh.views import WPShortenURLPage +CUSTOM_SHORTCUT_ALPHABET = frozenset('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-') + + class RHGetShortURL(RH): """Make a request to the URL shortening service""" @@ -84,6 +87,10 @@ class RHCustomShortURLPage(RHManageEventBase): def _process_POST(self): original_url = self._make_absolute_url(request.args['original_url']) shortcut = request.form['shortcut'].strip() + + if not (set(shortcut) <= CUSTOM_SHORTCUT_ALPHABET): + raise BadRequest('Invalid shortcut') + result = register_shortcut(original_url, shortcut, session.user) if result.get('error'): From 00d1dddfa7f32aa6b83da54650f1cb72305835e6 Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 20 Aug 2019 14:48:29 +0200 Subject: [PATCH 28/29] URSH: Bump version to 2.2 --- ursh/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ursh/setup.py b/ursh/setup.py index cb0fee5..cd5e45c 100644 --- a/ursh/setup.py +++ b/ursh/setup.py @@ -12,7 +12,7 @@ from setuptools import find_packages, setup setup( name='indico-plugin-ursh', - version='2.2-dev', + version='2.2', description='URL shortening service for Indico', url='https://github.com/indico/indico-plugins', license='MIT', From ef3d06e6378e313d6ea0a2fef6e01ade4967ed3d Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 20 Aug 2019 15:00:40 +0200 Subject: [PATCH 29/29] URSH: prettier-ify JS code --- ursh/indico_ursh/client/index.js | 200 ++++++++++++++++--------------- 1 file changed, 104 insertions(+), 96 deletions(-) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 9592a94..eaf0fab 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -5,138 +5,146 @@ // them and/or modify them under the terms of the MIT License; // see the LICENSE file for more details. -import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; +/* global $T:false */ +import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; const $t = $T.domain('ursh'); function _showTip(element, msg, hideEvent = 'unfocus') { - element.qtip({ - content: { - text: msg - }, - hide: { - event: hideEvent, - fixed: true, - delay: 700 - }, - show: { - event: false, - ready: true - } - }); + element.qtip({ + content: { + text: msg, + }, + hide: { + event: hideEvent, + fixed: true, + delay: 700, + }, + show: { + event: false, + ready: true, + }, + }); } async function _makeUrshRequest(originalURL) { - const urshEndpoint = '/ursh'; + const urshEndpoint = '/ursh'; - let response; - try { - response = await indicoAxios.post(urshEndpoint, { - original_url: originalURL, - }); - } catch (error) { - handleAxiosError(error); - return; - } + let response; + try { + response = await indicoAxios.post(urshEndpoint, { + original_url: originalURL, + }); + } catch (error) { + handleAxiosError(error); + return; + } - return response.data.url; + return response.data.url; } function _validateAndFormatURL(url) { - if (!url) { - throw Error($t.gettext('Please fill in a URL to shorten')); - } + if (!url) { + throw Error($t.gettext('Please fill in a URL to shorten')); + } - // if protocol is missing, prepend it - if (url.startsWith(location.hostname)) { - url = `${location.protocol}//${url}`; - } + // if protocol is missing, prepend it + if (url.startsWith(location.hostname)) { + url = `${location.protocol}//${url}`; + } - // regular expression, because I.E. does not like the URL class - // provides minimal validation, leaving the serious job to the server - const re = RegExp(`^([\\d\\w]+:)//([^/ .]+(?:\\.[^/ .]+)*)(/.*)?$`); - const urlTokens = url.match(re); - if (!urlTokens) { - throw Error($t.gettext('This does not look like a valid URL')); - } + // regular expression, because I.E. does not like the URL class + // provides minimal validation, leaving the serious job to the server + const re = RegExp(`^([\\d\\w]+:)//([^/ .]+(?:\\.[^/ .]+)*)(/.*)?$`); + const urlTokens = url.match(re); + if (!urlTokens) { + throw Error($t.gettext('This does not look like a valid URL')); + } - // extract tokens - const hostname = urlTokens[2]; - const path = urlTokens[3] ? urlTokens[3] : '/'; + // extract tokens + const hostname = urlTokens[2]; + const path = urlTokens[3] ? urlTokens[3] : '/'; - const protocol = location.protocol; // patch protocol to match server - if (hostname !== location.hostname) { - throw Error($t.gettext('Invalid host: only Indico URLs are allowed')); - } + const protocol = location.protocol; // patch protocol to match server + if (hostname !== location.hostname) { + throw Error($t.gettext('Invalid host: only Indico URLs are allowed')); + } - return `${protocol}//${hostname}${path}`; + return `${protocol}//${hostname}${path}`; } function _getUrshInput(input) { - const inputURL = input.val().trim(); - input.val(inputURL); + const inputURL = input.val().trim(); + input.val(inputURL); - try { - const formattedURL = _validateAndFormatURL(inputURL); - input.val(formattedURL); - return formattedURL; - } catch (err) { - _showTip(input, err.message); - input.focus().select(); - return null; - } + try { + const formattedURL = _validateAndFormatURL(inputURL); + input.val(formattedURL); + return formattedURL; + } catch (err) { + _showTip(input, err.message); + input.focus().select(); + return null; + } } async function _handleUrshPageInput(evt) { - evt.preventDefault(); + evt.preventDefault(); - const input = $('#ursh-shorten-input'); - const originalURL = _getUrshInput(input); - if (originalURL) { - const result = await _makeUrshRequest(originalURL); - if (result) { - const outputElement = $('#ursh-shorten-output'); - $('#ursh-shorten-response-form').slideDown(); - outputElement.val(result); - outputElement.select(); - } else { - _showTip(input, $t.gettext('This does not look like a valid URL')); - input.focus().select(); - } + const input = $('#ursh-shorten-input'); + const originalURL = _getUrshInput(input); + if (originalURL) { + const result = await _makeUrshRequest(originalURL); + if (result) { + const outputElement = $('#ursh-shorten-output'); + $('#ursh-shorten-response-form').slideDown(); + outputElement.val(result); + outputElement.select(); + } else { + _showTip(input, $t.gettext('This does not look like a valid URL')); + input.focus().select(); } + } } async function _handleUrshClick(evt) { - evt.preventDefault(); - const originalURL = evt.target.dataset.originalUrl; - const result = await _makeUrshRequest(originalURL); - $(evt.target).copyURLTooltip(result, 'unfocus'); + evt.preventDefault(); + const originalURL = evt.target.dataset.originalUrl; + const result = await _makeUrshRequest(originalURL); + $(evt.target).copyURLTooltip(result, 'unfocus'); } function _validateUrshCustomShortcut(shortcut) { - return shortcut.length >= 5; + return shortcut.length >= 5; } $(document) - .on('click', '#ursh-shorten-button', _handleUrshPageInput) - .on('keydown', '#ursh-shorten-input', (evt) => { - if (evt.key === 'Enter') { - _handleUrshPageInput(evt); - } - }) - .on('click', '.ursh-get', _handleUrshClick) - .on('input', '#ursh-custom-shortcut-input', (evt) => { - const value = $(evt.target).val(); - $('#ursh-custom-shortcut-submit-button').prop('disabled', !_validateUrshCustomShortcut(value)); - }) - .on('mouseenter', '#ursh-custom-shortcut-submit-button', (evt) => { - if (evt.target.disabled) { - _showTip($(evt.target), $t.gettext('Please check that the shortcut is correct'), 'mouseleave'); - } - }); + .on('click', '#ursh-shorten-button', _handleUrshPageInput) + .on('keydown', '#ursh-shorten-input', evt => { + if (evt.key === 'Enter') { + _handleUrshPageInput(evt); + } + }) + .on('click', '.ursh-get', _handleUrshClick) + .on('input', '#ursh-custom-shortcut-input', evt => { + const value = $(evt.target).val(); + $('#ursh-custom-shortcut-submit-button').prop('disabled', !_validateUrshCustomShortcut(value)); + }) + .on('mouseenter', '#ursh-custom-shortcut-submit-button', evt => { + if (evt.target.disabled) { + _showTip( + $(evt.target), + $t.gettext('Please check that the shortcut is correct'), + 'mouseleave' + ); + } + }); $(document).ready(() => { - // keep dropdown menu open when clicking on an entry - $('.ursh-dropdown').next('ul').find('li a').on('menu_select', () => true); + // keep dropdown menu open when clicking on an entry + $('.ursh-dropdown') + .next('ul') + .find('li a') + .on('menu_select', () => true); });