working on super simple offline behavior
This commit is contained in:
parent
9fb14910d2
commit
932293850f
7 changed files with 357 additions and 0 deletions
135
frontend/feed.js
Normal file
135
frontend/feed.js
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
if (navigator.serviceWorker) {
|
||||||
|
navigator.serviceWorker.register('./sw.js')
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function(){
|
||||||
|
function updateTimestamps() {
|
||||||
|
$(".permalink time").each(function() {
|
||||||
|
var absolute = $(this).attr('datetime');
|
||||||
|
var formatted = moment.utc(absolute).fromNow();
|
||||||
|
$(this).text(formatted);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickOlderLink(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
$.get(this.href, function(result) {
|
||||||
|
var $newElements = $("article,.pager", $(result));
|
||||||
|
$(".pager").replaceWith($newElements);
|
||||||
|
$newElements.each(function () {
|
||||||
|
twttr.widgets.load(this);
|
||||||
|
});
|
||||||
|
attachListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitMicropubForm(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
var button = this;
|
||||||
|
var form = $(button).closest('form');
|
||||||
|
var replyArea = form.parent();
|
||||||
|
var endpoint = form.attr('action');
|
||||||
|
var responseArea = $('.micropub-response', replyArea);
|
||||||
|
var formData = form.serializeArray();
|
||||||
|
formData.push({name: button.name, value: button.value});
|
||||||
|
|
||||||
|
$.post(
|
||||||
|
endpoint,
|
||||||
|
formData,
|
||||||
|
function(result) {
|
||||||
|
if (Math.floor(result.code / 100) == 2) {
|
||||||
|
responseArea.html('<a target="_blank" href="' + result.location + '">Success!</a>');
|
||||||
|
$("textarea", form).val("");
|
||||||
|
|
||||||
|
if (button.value === 'rsvp-yes') {
|
||||||
|
$(".rsvps", form).html('✓ Going');
|
||||||
|
} else if (button.value === 'rsvp-maybe') {
|
||||||
|
$(".rsvps", form).html('? Interested');
|
||||||
|
} else if (button.value === 'rsvp-no') {
|
||||||
|
$(".rsvps", form).html('✗ Not Going');
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
responseArea.html('Failure');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'json'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
responseArea.html('Posting…');
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachListeners() {
|
||||||
|
$("#older-link").off('click').click(clickOlderLink);
|
||||||
|
$(".micropub-form button[type='submit']").off('click').click(submitMicropubForm);
|
||||||
|
|
||||||
|
// Post by ctrl/cmd + enter in the text area
|
||||||
|
$(".micropub-form textarea.content").keyup(function(e) {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && (e.keyCode == 13 || e.keyCode == 10)) {
|
||||||
|
var button = $(e.target).closest('form').find('button[value=reply]');
|
||||||
|
button[0].click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".micropub-form .content").focus(function () {
|
||||||
|
$(this).animate({ height: "4em" }, 200);
|
||||||
|
var $target = $(evt.target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function clickUnfoldLink(evt) {
|
||||||
|
$('#fold').after($('#fold').children())
|
||||||
|
$('#unfold-link').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function foldNewEntries(entries) {
|
||||||
|
$('#fold').prepend(entries.join('\n'));
|
||||||
|
attachListeners();
|
||||||
|
$('#unfold-link').text($('#fold>article:not(.reply-context)').length + " New Posts");
|
||||||
|
$('#unfold-link').off('click').click(clickUnfoldLink);
|
||||||
|
$('#unfold-link').show();
|
||||||
|
|
||||||
|
// load twitter embeds
|
||||||
|
twttr.widgets.load($('#fold').get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// topic will be user:id or feed:id
|
||||||
|
function webSocketSubscribe(topic) {
|
||||||
|
if ('WebSocket' in window) {
|
||||||
|
var ws = new WebSocket(window.location.origin
|
||||||
|
.replace(/http:\/\//, 'ws://')
|
||||||
|
.replace(/https:\/\//, 'wss://')
|
||||||
|
+ '/_updates');
|
||||||
|
|
||||||
|
ws.onopen = function(event) {
|
||||||
|
// send the topic
|
||||||
|
console.log('subscribing to topic: ' + topic);
|
||||||
|
ws.send(topic);
|
||||||
|
};
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
foldNewEntries(data.entries);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachListeners();
|
||||||
|
|
||||||
|
$(document).on("keypress", function(e) {
|
||||||
|
if (e.which === 46) {
|
||||||
|
clickUnfoldLink();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (WS_TOPIC) {
|
||||||
|
webSocketSubscribe(WS_TOPIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimestamps();
|
||||||
|
window.setInterval(updateTimestamps, 60 * 1000);
|
||||||
|
|
||||||
|
});
|
70
frontend/indieconfig.js
Normal file
70
frontend/indieconfig.js
Normal file
|
@ -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);
|
||||||
|
};
|
||||||
|
}());
|
16
frontend/manifest.json
Normal file
16
frontend/manifest.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "Woodwind",
|
||||||
|
"short_name": "Woodwind",
|
||||||
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#9b6137",
|
||||||
|
"background_color": "#9b6137",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/logo.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
33
frontend/package.json
Normal file
33
frontend/package.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "woodwind-fe",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/kylewm/woodwind.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"indieweb",
|
||||||
|
"reader"
|
||||||
|
],
|
||||||
|
"author": "Kyle Mahan",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/kylewm/woodwind/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/kylewm/woodwind#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel": "^6.5.2",
|
||||||
|
"babel-cli": "^6.9.0",
|
||||||
|
"babel-preset-es2015": "^6.9.0",
|
||||||
|
"mocha": "^2.5.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jquery": "^2.2.4",
|
||||||
|
"moment": "^2.13.0"
|
||||||
|
}
|
||||||
|
}
|
31
frontend/sw.js
Normal file
31
frontend/sw.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
var version = 'v2';
|
||||||
|
|
||||||
|
this.addEventListener('install', function (event) {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(version).then(function (cache) {
|
||||||
|
return cache.addAll([
|
||||||
|
'/static/logo.png',
|
||||||
|
'/static/style.css',
|
||||||
|
'/offline',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
this.addEventListener('fetch', function (event) {
|
||||||
|
console.log('caught fetch: ' + event)
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request)
|
||||||
|
.then(function (response) {
|
||||||
|
console.log('cache got response: ' + response)
|
||||||
|
return response || fetch(event.request);
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
console.log('fetch got response: ' + response)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
return caches.match('/offline')
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
63
frontend/webaction.js
Normal file
63
frontend/webaction.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}());
|
9
woodwind/templates/offline.jinja2
Normal file
9
woodwind/templates/offline.jinja2
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.jinja2" %}
|
||||||
|
|
||||||
|
{% block login %}{% endblock login %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
Offline, and it feels so good
|
||||||
|
|
||||||
|
{% endblock body %}
|
Loading…
Add table
Add a link
Reference in a new issue