From 8d0d6da450124b5bdd6ce42fe76c8e980afc1a3e Mon Sep 17 00:00:00 2001 From: Kyle Mahan Date: Sun, 8 Feb 2015 11:56:27 -0800 Subject: [PATCH] add user settings; support for various reply mechanisms --- woodwind/models.py | 20 +++++ woodwind/static/indieconfig.js | 70 +++++++++++++++ woodwind/static/style.css | 23 ++++- woodwind/static/style.css.map | 2 +- woodwind/static/style.scss | 31 ++++++- woodwind/static/webaction.js | 63 ++++++++++++++ woodwind/templates/_reply.jinja2 | 34 ++++++++ woodwind/templates/feed.jinja2 | 34 +++----- woodwind/templates/login.jinja2 | 7 -- woodwind/templates/settings.jinja2 | 134 +++++++++++++++++++++++------ woodwind/views.py | 39 +++++++-- 11 files changed, 386 insertions(+), 71 deletions(-) create mode 100644 woodwind/static/indieconfig.js create mode 100644 woodwind/static/webaction.js create mode 100644 woodwind/templates/_reply.jinja2 delete mode 100644 woodwind/templates/login.jinja2 diff --git a/woodwind/models.py b/woodwind/models.py index 297264b..d7ef6bc 100644 --- a/woodwind/models.py +++ b/woodwind/models.py @@ -1,4 +1,5 @@ import bleach +import json from .extensions import db @@ -12,6 +13,24 @@ bleach.ALLOWED_ATTRIBUTES.update({ }) +class JsonType(db.TypeDecorator): + """Represents an immutable structure as a json-encoded string. + http://docs.sqlalchemy.org/en/rel_0_9/core/types.html#marshal-json-strings + """ + impl = db.Text + + def process_bind_param(self, value, dialect): + if value is not None: + value = json.dumps(value) + + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value + + users_to_feeds = db.Table( 'users_to_feeds', db.Model.metadata, db.Column('user_id', db.Integer, db.ForeignKey('user.id'), index=True), @@ -24,6 +43,7 @@ class User(db.Model): domain = db.Column(db.String(256)) micropub_endpoint = db.Column(db.String(512)) access_token = db.Column(db.String(512)) + settings = db.Column(JsonType) # Flask-Login integration def is_authenticated(self): diff --git a/woodwind/static/indieconfig.js b/woodwind/static/indieconfig.js new file mode 100644 index 0000000..dedce86 --- /dev/null +++ b/woodwind/static/indieconfig.js @@ -0,0 +1,70 @@ +/*jslint browser: true, plusplus: true, vars: true, indent: 2 */ +window.loadIndieConfig = (function () { + 'use strict'; + + // Indie-Config Loading script + // by Pelle Wessman, voxpelli.com + // MIT-licensed + // http://indiewebcamp.com/indie-config + + var config, configFrame, configTimeout, + callbacks = [], + handleConfig, parseConfig; + + // When the configuration has been loaded – deregister all loading mechanics and call all callbacks + handleConfig = function () { + config = config || {}; + + configFrame.parentNode.removeChild(configFrame); + configFrame = undefined; + + window.removeEventListener('message', parseConfig); + + clearTimeout(configTimeout); + + while (callbacks[0]) { + callbacks.shift()(config); + } + }; + + // When we receive a message, check if the source is right and try to parse it + parseConfig = function (message) { + var correctSource = (configFrame && message.source === configFrame.contentWindow); + + if (correctSource && config === undefined) { + try { + config = JSON.parse(message.data); + } catch (ignore) {} + + handleConfig(); + } + }; + + return function (callback) { + // If the config is already loaded, call callback right away + if (config) { + callback(config); + return; + } + + // Otherwise add the callback to the queue + callbacks.push(callback); + + // Are we already trying to load the Indie-Config, then wait + if (configFrame) { + return; + } + + // Create the iframe that will load the Indie-Config + configFrame = document.createElement('iframe'); + configFrame.src = 'web+action:load'; + document.getElementsByTagName('body')[0].appendChild(configFrame); + configFrame.style.display = 'none'; + + // Listen for messages so we will catch the Indie-Config message + window.addEventListener('message', parseConfig); + + // And if no such Indie-Config message has been loaded in a while, abort the loading + configTimeout = setTimeout(handleConfig, 3000); + }; +}()); diff --git a/woodwind/static/style.css b/woodwind/static/style.css index 616b108..8e24e34 100644 --- a/woodwind/static/style.css +++ b/woodwind/static/style.css @@ -415,11 +415,32 @@ article { label { font-weight: bold; - display: block; } + display: inline-block; + margin-bottom: 5px; + max-width: 100%; } textarea, input[type="text"], input[type="url"] { width: 100%; margin: 0.25em 0; } + textarea.input-25, input[type="text"].input-25, input[type="url"].input-25 { + width: 23%; } + textarea.input-50, input[type="text"].input-50, input[type="url"].input-50 { + width: 48%; } + textarea.input-75, input[type="text"].input-75, input[type="url"].input-75 { + width: 73%; } + +.reply-area { + text-align: center; } + .reply-area a { + display: inline-block; + padding: 0.2em; + border: 1px solid #687D77; + border-radius: 4px; + background-color: #ECEBF0; + text-decoration: none; + color: #484A47; + min-width: 50px; + text-align: center; } button { padding: 0.25em; } diff --git a/woodwind/static/style.css.map b/woodwind/static/style.css.map index 2c84d0c..b0f74fb 100644 --- a/woodwind/static/style.css.map +++ b/woodwind/static/style.css.map @@ -1,6 +1,6 @@ { "version": 3, -"mappings": "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", +"mappings": "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", "sources": ["normalize.scss","style.scss"], "names": [], "file": "style.css" diff --git a/woodwind/static/style.scss b/woodwind/static/style.scss index ff1894c..87fb9bc 100644 --- a/woodwind/static/style.scss +++ b/woodwind/static/style.scss @@ -92,12 +92,39 @@ article { label { font-weight: bold; - display: block; + display: inline-block; + margin-bottom: 5px; + max-width: 100%; } textarea, input[type="text"], input[type="url"] { width: 100%; - margin: 0.25em 0 ; + margin: 0.25em 0; + + &.input-25 { + width: 23%; + } + &.input-50 { + width: 48%; + } + &.input-75 { + width: 73%; + } +} + +.reply-area { + text-align: center; + a { + display: inline-block; + padding: 0.2em; + border: 1px solid $sirocco; + border-radius: 4px; + background-color: $athens-gray; + text-decoration: none; + color: $lunar-green; + min-width: 50px; + text-align: center; + } } button { diff --git a/woodwind/static/webaction.js b/woodwind/static/webaction.js new file mode 100644 index 0000000..262ad91 --- /dev/null +++ b/woodwind/static/webaction.js @@ -0,0 +1,63 @@ +/*jslint browser: true, plusplus: true, vars: true, indent: 2 */ +(function () { + 'use strict'; + + var loadingClassRegexp = /(^|\s)indieconfig-loading(\s|$)/; + + var doTheAction = function (indieConfig) { + var href, action, anchors; + + // Don't block the tag anymore as the queued action is now handled + this.className = this.className.replace(loadingClassRegexp, ' '); + + // Pick the correct endpoint for the correct action + action = this.getAttribute('do'); + href = indieConfig[action]; + + // If no endpoint is found, try the URL of the first a-tag within it + if (!href) { + anchors = this.getElementsByTagName('a'); + if (anchors[0]) { + href = anchors[0].href; + } + } + + // We have found an endpoint! + if (href) { + //Resolve a relative target + var target = document.createElement('a'); + target.href = this.getAttribute('with'); + target = target.href; + + // Insert the target into the endpoint + href = href.replace('{url}', encodeURIComponent(target || window.location.href)); + + // And redirect to it + window.open( href, '_blank'); + } + }; + + // Event handler for a click on an indie-action tag + var handleTheAction = function (e) { + // Prevent the default of eg. any a-tag fallback within the indie-action tag + e.preventDefault(); + + // Make sure this tag hasn't already been queued for the indieconfig-load + if (!loadingClassRegexp.test(this.className)) { + this.className += ' indieconfig-loading'; + // Set "doTheAction" to be called when the indie-config has been loaded + window.loadIndieConfig(doTheAction.bind(this)); + } + }; + + // Once the page is loased add click event listeners to all indie-action tags + window.addEventListener('DOMContentLoaded', function () { + var actions = document.querySelectorAll('indie-action'), + i, + length = actions.length; + + for (i = 0; i < length; i++) { + actions[i].addEventListener('click', handleTheAction); + } + }); +}()); diff --git a/woodwind/templates/_reply.jinja2 b/woodwind/templates/_reply.jinja2 new file mode 100644 index 0000000..13b0fa4 --- /dev/null +++ b/woodwind/templates/_reply.jinja2 @@ -0,0 +1,34 @@ +{% set settings = current_user.settings or {} %} +{% set replyMethod = settings.get('reply-method') %} + +{% if replyMethod == 'micropub' and current_user.micropub_endpoint %} +
+ + + + + +
+ + + +
+ + + + + + +
+ +{% elif replyMethod == 'indie-config' %} + {% for action in settings.get('indie-config-actions', []) %} + + {{ action | capitalize }} + + {% endfor %} +{% elif replyMethod == 'action-urls' %} + {% for action, url in settings.get('action-urls', []) %} + {{ action | capitalize }} + {% endfor %} +{% endif %} diff --git a/woodwind/templates/feed.jinja2 b/woodwind/templates/feed.jinja2 index a4014e0..fca5dbd 100644 --- a/woodwind/templates/feed.jinja2 +++ b/woodwind/templates/feed.jinja2 @@ -1,6 +1,13 @@ {% extends "base.jinja2" %} {% block head %} - + + + {% if current_user and current_user.settings + and current_user.settings.get('reply-method') == 'indie-config' %} + + + {% endif %} + {% endblock head %} {% block header %} @@ -34,28 +41,9 @@ {% endif %} {% endfor %} diff --git a/woodwind/templates/login.jinja2 b/woodwind/templates/login.jinja2 deleted file mode 100644 index 4e20abb..0000000 --- a/woodwind/templates/login.jinja2 +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.jinja2" %} -{% block body %} -
- - -
-{% endblock %} diff --git a/woodwind/templates/settings.jinja2 b/woodwind/templates/settings.jinja2 index 329b491..ad8704d 100644 --- a/woodwind/templates/settings.jinja2 +++ b/woodwind/templates/settings.jinja2 @@ -2,45 +2,123 @@ {% block body %}
- +
+ - {% if current_user.micropub_endpoint or current_user.access_token %} - - - +
+ {% set reply_method = settings.get('reply-method') %} - - - +

Reply Mechanism

+

+ + + Each post will have a Like and Reply button that will post content to your site directly via micropub. See Micropub for details. +

-
- - -
+

+ + + Clicking Like or Reply will invoke your web+action handler if registered. See indie-config for details. +

-
- - -
+

+ + + Configure Woodwind with your preferred web action handlers. The placeholder {url} will be replaced with the permalink URL of each entry. +

+
-{% else %} -
- - -
- {% endif %} + - +
+

Micropub

+

+ Configure micropub credentials. +

+ {% if current_user.micropub_endpoint or current_user.access_token %} + + +

+ Reauthorize Micropub +

+

+ Revoke Credentials +

+ {% else %} +

+ Authorize Micropub +

+ {% endif %} +
+ +
+

Indie-Config

+

+ Select indie-config actions. +

+ {% set selectedActions = settings.get('indie-config-actions', []) %} - + {% for action in ['like', 'favorite', 'reply', 'repost', 'bookmark'] %} +
+ {% endfor %} +

+
+ +
+

Action URLs

+
+ {% set actionUrls = settings.get('action-urls', {}) %} + {% for action, url in actionUrls %} + + + {% endfor %} + + +
+ +
+ + + {% endblock body %} diff --git a/woodwind/views.py b/woodwind/views.py index d7c3f05..6ac6543 100644 --- a/woodwind/views.py +++ b/woodwind/views.py @@ -43,10 +43,31 @@ def feeds(): return flask.render_template('feeds.jinja2', feeds=sorted_feeds) -@views.route('/settings') +@views.route('/settings', methods=['GET', 'POST']) @flask_login.login_required def settings(): - return flask.render_template('settings.jinja2') + settings = flask_login.current_user.settings or {} + if flask.request.method == 'GET': + return flask.render_template('settings.jinja2', settings=settings) + + settings = dict(settings) + reply_method = flask.request.form.get('reply-method') + settings['reply-method'] = reply_method + + if reply_method == 'micropub': + pass + elif reply_method == 'indie-config': + settings['indie-config-actions'] = flask.request.form.getlist( + 'indie-config-action') + elif reply_method == 'action-urls': + zipped = zip( + flask.request.form.getlist('action'), + flask.request.form.getlist('action-url')) + settings['action-urls'] = [[k, v] for k, v in zipped if k and v] + + flask_login.current_user.settings = settings + db.session.commit() + return flask.render_template('settings.jinja2', settings=settings) @views.route('/update_feed') @@ -103,7 +124,7 @@ def login(): def login_callback(resp): if not resp.me: flask.flash(cgi.escape('Login error: ' + resp.error)) - return flask.redirect(flask.url_for('.login')) + return flask.redirect(flask.url_for('.index')) if resp.error: flask.flash(cgi.escape('Warning: ' + resp.error)) @@ -121,12 +142,12 @@ def login_callback(resp): return flask.redirect(resp.next_url or flask.url_for('.index')) -@views.route('/authorize', methods=['POST']) +@views.route('/authorize') @flask_login.login_required def authorize(): return micropub.authorize( me=flask_login.current_user.url, - next_url=flask.request.form.get('next'), + next_url=flask.request.args.get('next'), scope='post') @@ -135,13 +156,13 @@ def authorize(): def micropub_callback(resp): if not resp.me or resp.error: flask.flash(cgi.escape('Authorize error: ' + resp.error)) - return flask.redirect(flask.url_for('.login')) + return flask.redirect(flask.url_for('.index')) domain = urllib.parse.urlparse(resp.me).netloc user = load_user(domain) if not user: flask.flash(cgi.escape('Unknown user for domain: ' + domain)) - return flask.redirect(flask.url_for('.login')) + return flask.redirect(flask.url_for('.index')) user.micropub_endpoint = resp.micropub_endpoint user.access_token = resp.access_token @@ -149,13 +170,13 @@ def micropub_callback(resp): return flask.redirect(resp.next_url or flask.url_for('.index')) -@views.route('/deauthorize', methods=['POST']) +@views.route('/deauthorize') @flask_login.login_required def deauthorize(): flask_login.current_user.micropub_endpoint = None flask_login.current_user.access_token = None db.session.commit() - return flask.redirect(flask.request.form.get('next') + return flask.redirect(flask.request.args.get('next') or flask.url_for('.index'))