# HG changeset patch # User Emmanuel Gil Peyrot # Date 1307156567 -7200 # Node ID f62b5c395a48a435ee4afb60da193fcd658d0996 Initial commit. diff --git a/atom.js b/atom.js new file mode 100644 --- /dev/null +++ b/atom.js @@ -0,0 +1,109 @@ +'use strict'; + +parsers[ns.atom] = function(id, xml) { + var toDate = function(atom) { + if (!atom) + return new Date(); + + var last = atom.getChild('updated'); + if (!last) { + last = atom.getChild('published'); + if (!last) + return new Date(); + } + + // var d = new Date(last); // FIXME: don't work in obsolete browsers + var d = new Date(); + d.set8601(last); + + return d; + }; + + var toHTML = function(atom, date) { + var article = document.createElementNS(ns.xhtml, 'article'); + + article.setAttributeNS(ns.idq, 'id', id); + var d8601 = date.to8601(); + article.setAttributeNS(ns.idq, 'date', d8601); + + var aside = document.createElementNS(ns.xhtml, 'aside'); + article.appendChild(aside); + + var atomTitle = atom.getChild('title'); + if (atomTitle) { + var title = document.createElementNS(ns.xhtml, 'h2'); + title.appendChild(document.createTextNode(atomTitle)); + article.appendChild(title); + } + + var footer = document.createElementNS(ns.xhtml, 'footer'); + + var atomAuthor = atom.getChild('author', ns.atom); + if (atomAuthor) { + var atomName = atomAuthor.getChild('name'); + + var atomURI = atomAuthor.getChild('uri'); + var cite = document.createElementNS(ns.xhtml, 'cite'); + if (atomURI) { + var a = document.createElementNS(ns.xhtml, 'a'); + a.href = atomURI; + var atomJID = new JID; + atomJID.uri = atomURI; + + a.appendChild(document.createTextNode(atomName? atomName: atomJID.bare)); + cite.appendChild(a); + + var img = document.createElementNS(ns.xhtml, 'img'); + img.src = 'http://linkmauve.fr/avatar/' + atomJID.bare; + aside.appendChild(img); + } else + cite.appendChild(document.createTextNode(atomName)); + + footer.appendChild(document.createTextNode('By ')); + footer.appendChild(cite); + + var atomEmail = atomAuthor.getChild('email'); + if (atomEmail) { + footer.appendChild(document.createTextNode(' (')); + var a = document.createElementNS(ns.xhtml, 'a'); + a.href = atomEmail; + a.appendChild(document.createTextNode('email')); + footer.appendChild(a); + footer.appendChild(document.createTextNode(')')); + } + + article.appendChild(footer); + } + + footer.innerHTML += ', '; + + var atomSummary = atom.getChild('summary'); + if (atomSummary) { + var p = document.createElementNS(ns.xhtml, 'p'); + p.appendChild(document.createTextNode(atomSummary)); + article.appendChild(p); + } + + var atomLinks = atom.getChildren('link'); + for (var i in atomLinks) { + var atomLink = atomLinks[i]; + + if (atomLink['@rel'] !== 'replies') + continue; + + if (atomLink['@title'] !== 'comments') + continue; + + var href = new JID; + href.uri = atomLink['@href']; + + article.innerHTML += 'Comments !'; + } + + return article; + }; + + this.xml = xml; + this.date = toDate(xml); + this.html = toHTML(xml, this.date); +} diff --git a/blog.js b/blog.js new file mode 100644 --- /dev/null +++ b/blog.js @@ -0,0 +1,243 @@ +//'use strict'; + +const BOSH_SERVICE = 'http://linkmauve.fr/http-bind/'; +var conn = null; +var jid = 'blog@linkmauve.fr'; // FIXME: Strophe should accept anonymous connections. +var password = 'blog'; +var service = 'psgxs.linkmauve.fr'; +var node = 'blog'; +var re = true; + +var params = (function() { + var vars = {}; + var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split(';'); + + for(var i = 0; i < hashes.length; i++) { + var s = hashes[i].indexOf('='); + var key = hashes[i].substring(0, s); + var value = hashes[i].substring(s+1); + vars[key] = value; + } + + return vars; +})(); + +if (params.jid) + service = params.jid; + +if (params.node) + node = params.node; + +var logs = document.getElementById('log'); +if (params.debug) + logs.parentNode.hidden = false; + +var received = {}; +var messages = document.getElementById('messages'); + +function log(msg, error) { + var p = document.createElementNS(ns.xhtml, 'p'); + p.appendChild(document.createTextNode(msg)); + + if (!error) + p.setAttributeNS(null, 'class', 'error'); + + logs.appendChild(p); +} + +function rawInput(data) { + log('RECV: ' + data, 1); +} + +function rawOutput(data) { + log('SENT: ' + data, 1); +} + +var html = function(name, id) { + return received[name][id].html; +} + +var date = function(name, id) { + return received[name][id].date; +} + +var updateMessage = function(name, id) { + var divs = messages.getElementsByTagNameNS(ns.xhtml, 'div'); + var container = null; + + for (var i in divs) { + var div = divs[i] + if (typeof div != 'object') + continue; + + if (div.getAttributeNS(ns.idq, 'jid') === name) { + container = div; + break; + } + } + + if (!container) { + var container = document.createElementNS(ns.xhtml, 'div'); + container.setAttributeNS(ns.idq, 'jid', name); + messages.appendChild(container); + } + + var articles = container.getElementsByTagNameNS(ns.xhtml, 'article'); + for (var i in articles) { + var article = articles[i]; + if (typeof article != 'object') + continue; + + if (article.getAttributeNS(ns.idq, 'id') === id) { + container.replaceChild(html(name, id), article); + return; + } + } + + var article = html(name, id); + + if (!container.firstChild) + container.appendChild(article); + else { + var d = date(name, id); + var toInsert; + for (var i in articles) { + var a = articles[i]; + if (typeof a != 'object') + continue; + + var ad = new Date(); + ad.set8601(a.getAttributeNS(ns.idq, 'date')); + + if (ad < d) { + toInsert = a; + break; + } + } + + if (toInsert) + container.insertBefore(article, toInsert); + else + container.appendChild(article); + } +} + +var convert = function(id, xml) { + var ns = xml['@xmlns']; + if (ns in parsers) + return new parsers[ns](id, xml); + return new parsers[''](id, xml); +}; + +var parsePubSubEvent = function(stanza) { + var e = {}; + + e.service = stanza.getAttribute('from'); + + var pubsub = stanza.getChild('event', ns.pse); + if (!pubsub) { + pubsub = stanza.getChild('pubsub', ns.ps); + if (!pubsub) + return; + } + e.ns = pubsub.getAttribute('xmlns'); + + var items = pubsub.getChild('items', e.ns); + if (!items) + return; + + e.node = items.getAttribute('node'); + items = items.getChildren('item', e.ns); + if (!items) + return; + + e.name = e.service + '/' + e.node; + + e.items = {}; + for (var i in items) { + var item = items[i]; + if (!item.getAttribute) + continue; + + var pl = item.getChild(); + if (!pl) + continue; + + var id = item.getAttribute('id'); + + e.items[id] = pl; + } + + return e; +} + +var onMessages = function(stanza) { + conn.addHandler(onMessages, null, 'message', null, null, null); + + log('message'); + stanza = xml2json(stanza); + var e = parsePubSubEvent(stanza); + + if (!received[e.name]) + received[e.name] = {}; + + for (var id in e.items) { + received[e.name][id] = convert(id, e.items[id]); + updateMessage(e.name, id); + } +} + +var onInfo = function(stanza) { + log('info'); + /*var query = stanza.getElementsByTagNameNS(ns.info, 'query')[0]; + var x = query.getElementsByTagNameNS(ns.data, 'x')[0]; + var form = forms.parse(x);*/ +} + +var onSubscribed = function(stanza) { + log('subscribed'); + var type = stanza.getAttribute('type'); + if (type !== 'result') { + messages.innerHTML = 'Error, impossible to retrieve messages.'; + conn.disconnect(); + } +} + +function onConnect(status) { + if (status == Strophe.Status.CONNECTING) { + log('Strophe is connecting.'); + } else if (status == Strophe.Status.CONNFAIL) { + log('Strophe failed to connect.'); + } else if (status == Strophe.Status.AUTHENTICATING) { + log('Strophe is authenticating.'); + } else if (status == Strophe.Status.DISCONNECTING) { + log('Strophe is disconnecting.'); + } else if (status == Strophe.Status.DISCONNECTED) { + log('Strophe is disconnected.'); + if (re) + conn.connect(jid, password, onConnect); + } else if (status == Strophe.Status.CONNECTED) { + log('Strophe is connected.'); + conn.addHandler(onMessages, null, 'message', null, null, null); + conn.send($pres().tree()); + conn.pubsub.subscribe(jid, service, node, undefined, onMessages, onSubscribed); + if (params.no === 'server') { + conn.pubsub.items(jid, service, node, onMessages); + conn.pubsub.info(jid, service, node, onInfo); + } + } else + log('Strophe is '+status+'.'); +} + +window.addEventListener('load', function () { + conn = new Strophe.Connection(BOSH_SERVICE); + conn.rawInput = rawInput; + conn.rawOutput = rawOutput; + conn.connect(jid, password, onConnect); +}, false); + +window.addEventListener('unload', function (e) { + re = false; + conn.disconnect(); + e.preventDefault(); +}, false); diff --git a/configuration.js b/configuration.js new file mode 100644 --- /dev/null +++ b/configuration.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Emmanuel Gil Peyrot + * + * This file is the source code of an XMPP avatar retriever. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +'use strict'; + +var config = exports || {}; + +// The JID and password of the account used. +config.jid = 'blog@linkmauve.fr'; +config.password = 'blog'; + +// The JID of the node displayed if no arguments are given. +config.defaultNode = 'psgxs.linkmauve.fr/blog'; + +// Root of the webservice, and Atom HTTP service. +config.webRoot = '/blog/'; +config.atomRoot = '/atom/'; + +// These are the host and the port on which the web service will +// listen. If you want IPv4 connection only, instead of both IPv4 and +// IPv6, replace '::' by '0.0.0.0'. If you want a port < 1024, you +// have to start it as root, use a proxy or redirect it using a +// firewall like iptables. +config.webHost = '::'; +config.webPort = 8033; + +// MIME types used to serve files. Normally only index.xhtml would have +// to be served by node, but if you are lazy you can serve the entire +// directory and those are there for you. +config.types = { + xhtml: 'application/xhtml+xml', + js: 'application/ecmascript', + css: 'text/css', + svg: 'image/svg+xml' +} diff --git a/date.js b/date.js new file mode 100644 --- /dev/null +++ b/date.js @@ -0,0 +1,75 @@ +Date.prototype.set8601 = function (string) { + var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" + + "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"; + var d = string.match(new RegExp(regexp)); + + var offset = 0; + var date = new Date(d[1], 0, 1); + + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[5]); } + if (d[7]) { date.setHours(d[7]); } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } + if (d[14]) { + offset = (Number(d[16]) * 60) + Number(d[17]); + offset *= ((d[15] == '-') ? 1 : -1); + } + + offset -= date.getTimezoneOffset(); + var time = (Number(date) + (offset * 60 * 1000)); + this.setTime(Number(time)); + + return this; +} + +Date.prototype.to8601 = function(){ + function pad(n){return n<10 ? '0'+n : n} + return this.getUTCFullYear()+'-' + + pad(this.getUTCMonth()+1)+'-' + + pad(this.getUTCDate())+'T' + + pad(this.getUTCHours())+':' + + pad(this.getUTCMinutes())+':' + + pad(this.getUTCSeconds())+'Z'; +} + +Date.prototype.getRelative = function(){ + const s = 1000, m = s*60, h = m*60, d = h*24, y = d*365, M = y/12, + ref = Date.now(), + input = this.getTime(), + delta = ref - input, + year = Math.round((delta/y)*10)/10, + month = Math.round((delta/M)*10)/10, + day = Math.round((delta/d)*10)/10, + hour = Math.round((delta/h)*10)/10, + minute = Math.round((delta/m)*10)/10; + + if (year == 1) + return "a year ago"; + if (year > 1) + return Math.round(year)+" years ago"; + + if (month == 1) + return "a month ago"; + if (month > 1) + return Math.round(month)+" months ago"; + + if (day == 1) + return "today"; + if (day > 1) + return Math.round(day)+" days ago"; + + if (hour == 1) + return "an hour ago"; + if (hour > 1) + return Math.round(hour)+" hours ago"; + + if (minute == 1) + return "a minute ago"; + if (minute > 1) + return Math.round(minute)+" minutes ago"; + + return "just now"; +}; diff --git a/forms.js b/forms.js new file mode 100644 --- /dev/null +++ b/forms.js @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2010 Emmanuel Gil Peyrot + * + * This file is part of PSĜS, a PubSub server written in JavaScript. + * + * PSĜS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. + * + * PSĜS 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PSĜS. If not, see . + */ + +'use strict'; + +var xmpp = require('xmpp') + +var parseBoolean = function(b) { + if (b == 'true' || b == 'True' || b == 'TRUE' || b == '1') + return true; + return false; +} + +exports.build = function(type, desc, content, labels, title, instructions) { + var x = xmpp.stanza('x', {xmlns: 'jabber:x:data', type: type}); + + if (desc._TITLE) + x.c('title').t(desc._TITLE).up(); + else if (title) + x.c('title').t(title).up(); + + if (desc._INSTRUCTIONS) + x.c('instructions').t(desc._INSTRUCTIONS).up(); + else if (instructions) + x.c('instructions').t(instructions).up(); + + if (content == 'default') { + content = {}; + for (var i in desc) + content[i] = desc[i].value; + } + + for (var i in desc) { + if (i[0] == '_') + continue; + + var fieldAttr = {'var': i}; + + if (labels) { + if (desc[i].type) + fieldAttr.type = desc[i].type; + if (desc[i].label) + fieldAttr.label = desc[i].label; + } + x.c('field', fieldAttr); + + if (labels && + (desc[i].type == 'list-multi' || + desc[i].type == 'list-single')) { + for (var j in desc[i].options) { + var optAttr = {}; + if (desc[i].options[j].label) + optAttr.label = desc[i].options[j].label; + x.c('option', optAttr).c('value').t(j).up().up(); + } + } + + if (i == 'FORM_TYPE') + x.c('value').t(desc[i].value).up(); + else if (typeof content[i] != 'undefined') { + var md = content[i]; + if (desc[i].type == 'jid-multi' || + desc[i].type == 'list-multi' || + desc[i].type == 'text-multi') { + for (var j=0; j + + + + + Eldonilo blog + +