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