Add OPML support;

Set a new default node (Thank you roughnecks);
Improve CSS, JS, XSLT;
Neglect external libraries to produce syndications.
This commit is contained in:
Schimon Jehudah, Adv. 2024-07-12 15:39:17 +03:00
parent e07ff6e838
commit d1f1edbaca
8 changed files with 519 additions and 185 deletions

View file

@ -5,8 +5,8 @@ pass = "" # Password.
# A default node, when no arguments are set. # A default node, when no arguments are set.
[default] [default]
pubsub = "blog.jmp.chat" # Jabber ID. pubsub = "pubsub.woodpeckersnest.space" # Jabber ID.
nodeid = "urn:xmpp:microblog:0" # Node ID. nodeid = "PlanetJabber" # Node ID.
# Settings # Settings
[settings] [settings]

View file

@ -7,6 +7,17 @@ body {
background: #000; background: #000;
} }
code, pre {
overflow: auto;
display: inline-block;
max-height: 500px;
max-width: 100%; }
img, video {
width: auto;
height: auto;
}
h1#title, h2#subtitle, #actions, #references { h1#title, h2#subtitle, #actions, #references {
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
@ -64,6 +75,7 @@ h1#title, h2#subtitle, #actions, #references {
width: 20%; width: 20%;
} }
#articles #journal ol,
#articles #journal ul { #articles #journal ul {
/* height: 500px; */ /* height: 500px; */
line-height: 160%; line-height: 160%;
@ -74,24 +86,25 @@ h1#title, h2#subtitle, #actions, #references {
font-weight: bold; font-weight: bold;
} }
#articles > ul > li > div > p.content { #articles div.content {
font-size: 120%; font-size: 120%;
line-height: 30px; line-height: 200%;
margin: auto; margin: auto;
padding-left: 2%; padding-left: 2%;
padding-right: 10%; padding-right: 10%;
/* text-align: justify; */ /* text-align: justify; */
word-wrap: break-word;
} }
#articles > ul > li > div.entry { #articles div.entry {
padding-bottom: 0.67em; padding-bottom: 0.67em;
} }
#articles > ul > li > div.entry h1 { #articles div.entry h1 {
font-size: 2vw; font-size: 2vw;
} }
#articles > ul > li > div.entry h2 { #articles div.entry h2 {
font-size: 1.5vw; font-size: 1.5vw;
} }
@ -138,7 +151,7 @@ h1#title, h2#subtitle, #actions, #references {
text-decoration: underline; text-decoration: underline;
} }
@media (max-width: 950px) { @media (max-width: 1525px) {
#articles { #articles {
display: unset; display: unset;
} }

View file

@ -2,10 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime import datetime
from dateutil import parser
from fastapi import FastAPI, Request, Response from fastapi import FastAPI, Request, Response
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import feedgenerator
import json import json
from slixmpp import ClientXMPP from slixmpp import ClientXMPP
from slixmpp.exceptions import IqError, IqTimeout from slixmpp.exceptions import IqError, IqTimeout
@ -57,12 +57,11 @@ async def view_pubsub(request: Request):
if pubsub and node and item_id: if pubsub and node and item_id:
iq = await get_node_item(pubsub, node, item_id) iq = await get_node_item(pubsub, node, item_id)
if iq: if iq:
link = 'xmpp:{pubsub}?;node={node};item={item}'.format( link = form_an_item_link(pubsub, node, item_id)
pubsub=pubsub, node=node, item=item_id) xml_atom = generate_atom(iq, link)
xml_atom = generate_rfc_4287(iq, link)
iq = await get_node_items(pubsub, node) iq = await get_node_items(pubsub, node)
if iq: if iq:
generate_json(iq, node) generate_json(iq)
else: else:
operator = get_configuration('settings')['operator'] operator = get_configuration('settings')['operator']
json_data = [{'title' : 'Error retrieving items list.', json_data = [{'title' : 'Error retrieving items list.',
@ -79,7 +78,8 @@ async def view_pubsub(request: Request):
else: else:
text = 'Please check that PubSub node and item are valid and accessible.' text = 'Please check that PubSub node and item are valid and accessible.'
xml_atom = error_message(text) xml_atom = error_message(text)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
# try: # try:
# iq = await get_node_items(pubsub, node) # iq = await get_node_items(pubsub, node)
@ -94,32 +94,37 @@ async def view_pubsub(request: Request):
elif pubsub and node: elif pubsub and node:
iq = await get_node_items(pubsub, node) iq = await get_node_items(pubsub, node)
if iq: if iq:
link = form_a_link(pubsub, node) link = form_a_node_link(pubsub, node)
xml_atom = generate_rfc_4287(iq, link) xml_atom = generate_atom(iq, link)
else: else:
text = 'Please check that PubSub node is valid and accessible.' text = 'Please check that PubSub node is valid and accessible.'
xml_atom = error_message(text) xml_atom = error_message(text)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
elif pubsub: elif pubsub:
iq = await get_nodes(pubsub) iq = await get_nodes(pubsub)
if iq: if iq:
link = 'xmpp:{pubsub}'.format(pubsub=pubsub) link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
result = pubsub_to_opml(iq) xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml.xsl')
else: else:
text = 'Please check that PubSub Jabber ID is valid and accessible.' text = 'Please check that PubSub Jabber ID is valid and accessible.'
xml_atom = error_message(text) xml_atom = error_message(text)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
elif node: elif node:
text = 'PubSub parameter is missing.' text = 'PubSub parameter is missing.'
xml_atom = error_message(text) xml_atom = error_message(text)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
# else: # else:
# result = ('Mandatory parameter PubSub and ' # result = ('Mandatory parameter PubSub and '
# 'optional parameter Node are missing.') # 'optional parameter Node are missing.')
else: else:
text = 'The given domain {} is not allowed.'.format(pubsub) text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = error_message(text) xml_atom = error_message(text)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
default = get_configuration('default') default = get_configuration('default')
if not result: if not result:
if default['pubsub'] and default['nodeid']: if default['pubsub'] and default['nodeid']:
@ -127,20 +132,23 @@ async def view_pubsub(request: Request):
pubsub = default['pubsub'] pubsub = default['pubsub']
node = default['nodeid'] node = default['nodeid']
iq = await get_node_items(pubsub, node) iq = await get_node_items(pubsub, node)
link = form_a_link(pubsub, node) link = form_a_node_link(pubsub, node)
xml_atom = generate_rfc_4287(iq, link) xml_atom = generate_rfc_4287(iq, link)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
elif not settings['service']: elif not settings['service']:
pubsub = default['pubsub'] pubsub = default['pubsub']
node = default['nodeid'] node = default['nodeid']
iq = await get_node_items(pubsub, node) iq = await get_node_items(pubsub, node)
link = form_a_link(pubsub, node) link = form_a_node_link(pubsub, node)
xml_atom = generate_rfc_4287(iq, link) xml_atom = generate_rfc_4287(iq, link)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
else: else:
text = 'Please contact the administrator and ask him to set default PubSub and Node ID.' text = 'Please contact the administrator and ask him to set default PubSub and Node ID.'
xml_atom = error_message(text) xml_atom = error_message(text)
result = append_stylesheet(xml_atom) result = append_stylesheet(
xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom')
response = Response(content=result, media_type="application/xml") response = Response(content=result, media_type="application/xml")
return response return response
@ -166,104 +174,125 @@ async def get_node_items(pubsub, node):
async def get_nodes(pubsub): async def get_nodes(pubsub):
try: try:
await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5) iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5)
return iq return iq
except (IqError, IqTimeout) as e: except (IqError, IqTimeout) as e:
print(e) print(e)
def form_a_link(pubsub, node): def form_a_node_link(pubsub, node):
link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node) link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node)
return link return link
def form_an_item_link(pubsub, node, item_id):
link = 'xmpp:{pubsub}?;node={node};item={item}'.format(
pubsub=pubsub, node=node, item=item_id)
return link
def error_message(text): def error_message(text):
"""Error message in RFC 4287: The Atom Syndication Format.""" """Error message in RFC 4287: The Atom Syndication Format."""
feed = feedgenerator.Atom1Feed( title = 'StreamBurner'
subtitle = 'XMPP Journal Publisher'
description = ('This is a syndication feed generated with XMPP Journal ' description = ('This is a syndication feed generated with XMPP Journal '
'Publisher (XJP), which conveys XEP-0060: Publish-' 'Publisher, which conveys XEP-0060: Publish-Subscribe '
'Subscribe nodes to standard RFC 4287: The Atom ' 'nodes to standard RFC 4287: The Atom Syndication Format.')
'Syndication Format.'), language = 'en'
language = 'en', feed = ET.Element("feed")
link = '', feed.set('xmlns', 'http://www.w3.org/2005/Atom')
subtitle = 'XMPP Journal Publisher', ET.SubElement(feed, 'title', {'type': 'text'}).text = title
title = 'StreamBurner') ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle
namespace = '{http://www.w3.org/2005/Atom}' ET.SubElement(feed, 'author', {'name':'XMPP Journal Publisher','email':'xjp@schimon.i2p'})
feed_url = 'gemini://schimon.i2p/' ET.SubElement(feed, 'generator', {
# create entry 'uri': 'https://git.xmpp-it.net/sch/XMPPJournalPublisher',
feed.add_item( 'version': '0.1'}).text = 'XMPP Journal Publisher (XJP)'
description = text, ET.SubElement(feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
# enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None, entry = ET.SubElement(feed, 'entry')
link = '', ET.SubElement(entry, 'title').text = 'Error'
# pubdate = updated, ET.SubElement(entry, 'id').text = 'xjp-error'
title = 'Error', ET.SubElement(entry, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
# unique_id = '' ET.SubElement(entry, 'published').text = datetime.datetime.now(datetime.UTC).isoformat()
) # ET.SubElement(entry, 'summary', {'type': summary_type_text}).text = summary_text
xml_atom = feed.writeString('utf-8') ET.SubElement(entry, 'content', {'type': 'text'}).text = text
xml_atom_extended = append_element( return ET.tostring(feed, encoding='unicode')
xml_atom,
'generator',
'XMPP Journal Publisher (XJP)')
return xml_atom_extended
def generate_rfc_4287(iq, link): # generate_rfc_4287
"""Convert XEP-0060: Publish-Subscribe to RFC 4287: The Atom Syndication Format.""" def generate_atom(iq, link):
feed = feedgenerator.Atom1Feed( """Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items."""
description = ('This is a syndication feed generated with PubSub To ' pubsub = iq['from'].bare
'Atom, which conveys XEP-0060: Publish-Subscribe nodes ' node = iq['pubsub']['items']['node']
'to standard RFC 4287: The Atom Syndication Format.'), title = node
language = iq['pubsub']['items']['lang'], link = link
link = link, # link = form_a_node_link(pubsub, node)
subtitle = 'XMPP PubSub Syndication Feed', subtitle = 'XMPP PubSub Syndication Feed'
title = iq['pubsub']['items']['node']) description = ('This is a syndication feed generated with XMPP Journal '
# See also iq['pubsub']['items']['substanzas'] 'Publisher, which conveys XEP-0060: Publish-Subscribe '
entries = iq['pubsub']['items'] 'nodes to standard RFC 4287: The Atom Syndication Format.')
for entry in entries: language = iq['pubsub']['items']['lang']
item = entry['payload'] items = iq['pubsub']['items']
feed = ET.Element("feed")
feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(feed, 'title', {'type': 'text'}).text = title
ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(feed, 'link', {'rel': 'self', 'href': link})
ET.SubElement(feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/XMPPJournalPublisher',
'version': '0.1'}).text = 'XMPP Journal Publisher (XJP)'
ET.SubElement(feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
for item in items:
item_id = item['id']
item_payload = item['payload']
namespace = '{http://www.w3.org/2005/Atom}' namespace = '{http://www.w3.org/2005/Atom}'
title = item.find(namespace + 'title') title = item_payload.find(namespace + 'title')
title = None if title == None else title.text title_text = None if title == None else title.text
updated = item.find(namespace + 'updated') # link = item_payload.find(namespace + 'link')
updated = None if updated == None else updated.text # link_href = '' if link == None else link.attrib['href']
published = item.find(namespace + 'published') link_href = form_an_item_link(pubsub, node, item_id)
published = None if published == None else published.text if not title_text or not link_href: continue
if not updated and not published: updated = datetime.datetime.utcnow().isoformat() content = item_payload.find(namespace + 'content')
content = item.find(namespace + 'content') content_text = 'No content' if content == None else content.text
content = 'No content' if content == None else content.text if content.attrib:
link = item.find(namespace + 'link') content_type = content.attrib['type'] if 'type' in content.attrib else 'text'
link = '' if link == None else link.attrib['href'] content_type_text = 'html' if 'html' in content_type else 'text'
author = item.find(namespace + 'author') published = item_payload.find(namespace + 'published')
published_text = None if published == None else published.text
if published: published_dt = parser.parse(published_text)
updated = item_payload.find(namespace + 'updated')
updated_text = None if updated == None else updated.text
author = item_payload.find(namespace + 'author')
if author and author.attrib: print(author.attrib) if author and author.attrib: print(author.attrib)
author = 'None' if author == None else author.text author_text = 'None' if author == None else author.text
# create entry identifier = item_payload.find(namespace + 'id')
feed.add_item( if identifier and identifier.attrib: print(identifier.attrib)
description = content, identifier_text = 'None' if identifier == None else identifier.text
# enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None, entry = ET.SubElement(feed, 'entry')
link = link, ET.SubElement(entry, 'title').text = title_text
pubdate = published or updated, ET.SubElement(entry, 'link', {'href': link_href})
title = title, ET.SubElement(entry, 'id').text = identifier_text
unique_id = link) ET.SubElement(entry, 'updated').text = updated_text
xml_atom = feed.writeString('utf-8') ET.SubElement(entry, 'published').text = published_text
xml_atom_extended = append_element( # ET.SubElement(entry, 'summary', {'type': summary_type_text}).text = summary_text
xml_atom, ET.SubElement(entry, 'content', {'type': content_type_text}).text = content_text
'generator', return ET.tostring(feed, encoding='unicode')
'XMPP Journal Publisher (XJP)')
return xml_atom_extended
def generate_json(iq, node): def generate_json(iq):
"""Create a JSON file from node items.""" """Create a JSON file from node items."""
json_data = [] json_data = []
pubsub = iq['from'].bare
node = iq['pubsub']['items']['node']
entries = iq['pubsub']['items'] entries = iq['pubsub']['items']
for entry in entries: for entry in entries:
item = entry['payload'] item_id = entry['id']
item_payload = entry['payload']
namespace = '{http://www.w3.org/2005/Atom}' namespace = '{http://www.w3.org/2005/Atom}'
title = item.find(namespace + 'title') title = item_payload.find(namespace + 'title')
title = None if title == None else title.text title_text = None if title == None else title.text
# updated = item.find(namespace + 'updated') # updated = item.find(namespace + 'updated')
# updated = None if updated == None else updated.text # updated = None if updated == None else updated.text
# if updated: updated = datetime.datetime.fromisoformat(updated) # if updated: updated = datetime.datetime.fromisoformat(updated)
link = item.find(namespace + 'link') link_href = form_an_item_link(pubsub, node, item_id)
link = '' if link == None else link.attrib['href'] # link = item.find(namespace + 'link')
json_data_entry = {'title' : title, # link_href = '' if link == None else link.attrib['href']
'link' : link} json_data_entry = {'title' : title_text,
'link' : link_href}
json_data.append(json_data_entry) json_data.append(json_data_entry)
if len(json_data) > 6: break if len(json_data) > 6: break
filename = 'data/{}.json'.format(node) filename = 'data/{}.json'.format(node)
@ -286,15 +315,40 @@ def append_element(xml_data, element, text):
"""Patch function to append XSLT reference to XML""" """Patch function to append XSLT reference to XML"""
"""Why is not this a built-in function of ElementTree or LXML""" """Why is not this a built-in function of ElementTree or LXML"""
def append_stylesheet(xml_data): def append_stylesheet(xml_data, filename, namespace=None):
# Register namespace in order to avoide ns0: # Register namespace in order to avoide ns0:
ET.register_namespace("", "http://www.w3.org/2005/Atom") if namespace: ET.register_namespace("", namespace)
# Load XML from string # Load XML from string
tree = ET.fromstring(xml_data) tree = ET.fromstring(xml_data)
# The following direction removes the XML declaration # The following direction removes the XML declaration
xml_data_no_declaration = ET.tostring(tree, encoding='unicode') xml_data_without_a_declaration = ET.tostring(tree, encoding='unicode')
# Add XML declaration and stylesheet # Add XML declaration and stylesheet
xml_data_declaration = ('<?xml version="1.0" encoding="utf-8"?>' xml_data_declaration = (
'<?xml-stylesheet type="text/xsl" href="xsl/stylesheet.xsl"?>' + '<?xml version="1.0" encoding="utf-8"?>'
xml_data_no_declaration) '<?xml-stylesheet type="text/xsl" href="xsl/{}"?>'.format(filename) +
xml_data_without_a_declaration)
return xml_data_declaration return xml_data_declaration
def generate_opml(iq):
pubsub = iq['from'].bare
items = iq['disco_items']['items']
opml = ET.Element("opml")
opml.set("version", "1.0")
head = ET.SubElement(opml, "head")
ET.SubElement(head, "title").text = pubsub
ET.SubElement(head, "description").text = (
"PubSub Nodes of {}").format(pubsub)
ET.SubElement(head, "generator").text = "XMPP Journal Publisher (XJP)"
ET.SubElement(head, "urlPublic").text = (
"https://git.xmpp-it.net/sch/XMPPJournalPublisher")
time_stamp = datetime.datetime.now(datetime.UTC).isoformat()
ET.SubElement(head, "dateCreated").text = time_stamp
ET.SubElement(head, "dateModified").text = time_stamp
body = ET.SubElement(opml, "body")
for item in items:
pubsub, node, title = item
uri = form_a_node_link(pubsub, node)
outline = ET.SubElement(body, "outline")
outline.set("text", title or node)
outline.set("xmlUrl", uri)
return ET.tostring(opml, encoding='unicode')

View file

@ -1,10 +1,11 @@
window.onload = async function(){ window.onload = async function(){
// Fix button follow
let follow = document.querySelector('#follow');
//let feedUrl = location.href.replace(/^https?:/, 'feed:');
let locationHref = new URL(location.href); let locationHref = new URL(location.href);
let node = locationHref.searchParams.get('node') let node = locationHref.searchParams.get('node')
let pubsub = locationHref.searchParams.get('pubsub') let pubsub = locationHref.searchParams.get('pubsub')
// Fix button follow
let follow = document.querySelector('#follow');
if (follow) {
//let feedUrl = location.href.replace(/^https?:/, 'feed:');
let feedUrl = `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`; let feedUrl = `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`;
follow.href = feedUrl; follow.href = feedUrl;
follow.addEventListener ('click', function() { follow.addEventListener ('click', function() {
@ -12,17 +13,22 @@ window.onload = async function(){
}); });
// Fix button subtome // Fix button subtome
document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds=' + feedUrl; document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds=' + feedUrl;
}
// Convert ISO8601 To UTC // Convert ISO8601 To UTC
for (let element of document.querySelectorAll('#articles > ul > li > div > h4, #feed > #header > h2#subtitle.date')) { for (let element of document.querySelectorAll(
'#articles > ul > li > div > h4.published,' +
'#articles > ul > li > div > h4.updated, ' +
'#feed > #header > h2#subtitle.date')) {
let timeStamp = new Date(element.textContent); let timeStamp = new Date(element.textContent);
element.textContent = timeStamp.toUTCString(); element.textContent = timeStamp.toUTCString();
} }
// Parse Markdown // Parse Markdown
for (let element of document.querySelectorAll('#articles > ul > li > div > p')) { for (let element of document.querySelectorAll('#articles > ul > li > div > p[type="text"]')) {
let markDown = element.textContent let markDown = element.textContent
element.innerHTML = marked.parse(markDown); element.innerHTML = marked.parse(markDown);
} }
// Build a journal list // Build a journal list
if (node) {
itemsList = await openJson(node) itemsList = await openJson(node)
if (itemsList && locationHref.searchParams.get('item')) { if (itemsList && locationHref.searchParams.get('item')) {
node = locationHref.searchParams.get('node') node = locationHref.searchParams.get('node')
@ -35,7 +41,7 @@ window.onload = async function(){
let elementH4 = document.createElement('h4'); let elementH4 = document.createElement('h4');
elementH4.textContent = node; elementH4.textContent = node;
elementDiv.appendChild(elementH4); elementDiv.appendChild(elementH4);
let elementUl = document.createElement('ul'); let elementUl = document.createElement('ol');
elementDiv.appendChild(elementUl); elementDiv.appendChild(elementUl);
for (let item of itemsList) { for (let item of itemsList) {
let elementLi = document.createElement('li'); let elementLi = document.createElement('li');
@ -56,7 +62,9 @@ window.onload = async function(){
{'text' : 'Subscribe with a News Reader.', {'text' : 'Subscribe with a News Reader.',
'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`}, 'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`},
{'text' : 'Browse the journal.', {'text' : 'Browse the journal.',
'href' : `atom?pubsub=${pubsub}&node=${node}`} 'href' : `atom?pubsub=${pubsub}&node=${node}`},
{'text' : 'Browse the portal.',
'href' : `atom?pubsub=${pubsub}`}
] ]
for (let link of links) { for (let link of links) {
let elementLi = document.createElement('li'); let elementLi = document.createElement('li');
@ -70,15 +78,22 @@ window.onload = async function(){
// document.querySelector('#feed').appendChild(elementDiv); // This would result in a combination of Title, Article, and Journal // document.querySelector('#feed').appendChild(elementDiv); // This would result in a combination of Title, Article, and Journal
document.querySelector('#articles').appendChild(elementDiv); document.querySelector('#articles').appendChild(elementDiv);
} }
}
// Convert URI xmpp: to URI http: links. // Convert URI xmpp: to URI http: links.
for (let xmppLink of document.querySelectorAll('a[href^="xmpp:"]')) { for (let xmppLink of document.querySelectorAll('#articles > ul a[href^="xmpp:"], ol a[href^="xmpp:"]')) {
xmppUri = new URL(xmppLink); xmppUri = new URL(xmppLink);
let parameters = xmppUri.search.split(';'); let parameters = xmppUri.search.split(';');
try {
try { try {
let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1]; let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1];
let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1]; let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1];
let pubsub = xmppUri.pathname; let pubsub = xmppUri.pathname;
xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}` xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}`
} catch {
let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1];
let pubsub = xmppUri.pathname;
xmppLink.href = `atom?pubsub=${pubsub}&node=${node}`
}
} catch (err) { } catch (err) {
console.warn(err) console.warn(err)
} }

View file

@ -8,22 +8,15 @@ element <meta/> inside of html element </head>
--> -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method = 'html'
<xsl:output
method = 'html'
indent = 'yes' indent = 'yes'
omit-xml-decleration='no' /> omit-xml-decleration='no' />
<!-- Atom 1.0 Syndication Format --> <!-- Atom 1.0 Syndication Format -->
<xsl:include href='atom_as_xhtml.xsl'/> <xsl:include href='atom_as_xhtml.xsl'/>
<!-- extract filename from given url string --> <!-- extract filename from given url string -->
<xsl:include href='extract-filename.xsl'/> <xsl:include href='extract-filename.xsl'/>
<!-- set page metadata --> <!-- set page metadata -->
<xsl:include href='metadata.xsl'/> <xsl:include href='metadata.xsl'/>
<!-- transform filesize from given length string --> <!-- transform filesize from given length string -->
<xsl:include href='transform-filesize.xsl'/> <xsl:include href='transform-filesize.xsl'/>
</xsl:stylesheet> </xsl:stylesheet>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!-- <!--
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head> element <meta/> inside of html element </head>
@ -46,9 +46,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:call-template> </xsl:call-template>
<title> <title>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:title and not(atom:title="")'> <xsl:when test='atom:title and not(atom:title="") and count(atom:entry) &gt; 1'>
<xsl:value-of select='atom:title'/> <xsl:value-of select='atom:title'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:entry'>
<xsl:value-of select='atom:entry/atom:title'/>
</xsl:when>
<xsl:otherwise>StreamBurner</xsl:otherwise> <xsl:otherwise>StreamBurner</xsl:otherwise>
</xsl:choose> </xsl:choose>
</title> </title>
@ -282,7 +285,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<!-- entry content --> <!-- entry content -->
<!-- entry summary of GitLab Atom Syndication Feeds --> <!-- entry summary of GitLab Atom Syndication Feeds -->
<xsl:if test='atom:content or atom:summary'> <xsl:if test='atom:content or atom:summary'>
<p class='content'> <div class='content'>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:summary[contains(@type,"text")]'> <xsl:when test='atom:summary[contains(@type,"text")]'>
<xsl:attribute name='type'> <xsl:attribute name='type'>
@ -290,6 +293,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
<xsl:value-of select='atom:summary'/> <xsl:value-of select='atom:summary'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:summary[contains(@type,"html")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:summary/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:summary' disable-output-escaping='yes'/>
</xsl:when>
<xsl:when test='atom:summary[contains(@type,"base64")]'> <xsl:when test='atom:summary[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media --> <!-- TODO add xsl:template to handle inline media -->
</xsl:when> </xsl:when>
@ -299,6 +308,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
<xsl:value-of select='atom:content'/> <xsl:value-of select='atom:content'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:content[contains(@type,"html")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:content/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:content' disable-output-escaping='yes'/>
</xsl:when>
<xsl:when test='atom:content[contains(@type,"base64")]'> <xsl:when test='atom:content[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media --> <!-- TODO add xsl:template to handle inline media -->
</xsl:when> </xsl:when>
@ -313,7 +328,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:choose> </xsl:choose>
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</p> </div>
</xsl:if> </xsl:if>
<!-- entry enclosure --> <!-- entry enclosure -->
<xsl:if test='atom:link[contains(@rel,"enclosure")]'> <xsl:if test='atom:link[contains(@rel,"enclosure")]'>

18
xsl/opml.xsl Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head>
-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method = 'html'
indent = 'yes'
omit-xml-decleration='no' />
<!-- Atom 1.0 Syndication Format -->
<xsl:include href='opml_as_xhtml.xsl'/>
<!-- set page metadata -->
<xsl:include href='metadata.xsl'/>
</xsl:stylesheet>

226
xsl/opml_as_xhtml.xsl Normal file
View file

@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head>
-->
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:xml='http://www.w3.org/XML/1998/namespace'>
<!-- Outline Processor Markup Language 1.0 -->
<xsl:output
media-type='text/x-opml' />
<xsl:template match='/opml'>
<!-- index right-to-left language codes -->
<!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
<xsl:variable name='rtl'
select='lang[
contains(self::node(),"ar") or
contains(self::node(),"fa") or
contains(self::node(),"he") or
contains(self::node(),"ji") or
contains(self::node(),"ku") or
contains(self::node(),"ur") or
contains(self::node(),"yi")]'/>
<html>
<head>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"description"' />
<xsl:with-param name='content' select='subtitle' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"generator"' />
<xsl:with-param name='content' select='StreamBurner' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"mimetype"' />
<xsl:with-param name='content' select='"text/x-opml"' />
</xsl:call-template>
<title>
<xsl:choose>
<xsl:when test='title and not(title="")'>
<xsl:value-of select='title'/>
</xsl:when>
<xsl:otherwise>StreamBurner</xsl:otherwise>
</xsl:choose>
</title>
<!-- TODO media='print' -->
<link href='/css/stylesheet.css' rel='stylesheet' type='text/css' media='screen'/>
<link rel='icon' type='image/svg+xml' href='/graphic/xmpp.svg'/>
<!-- whether language code is of direction right-to-left -->
<xsl:if test='$rtl'>
<link id='semitic' href='/css/stylesheet-rtl.css' rel='stylesheet' type='text/css' />
</xsl:if>
<script type='text/javascript' src='/script/marked.min.js'/>
<script type='text/javascript' src='/script/postprocess.js'/>
</head>
<body>
<div id='actions'>
<a href='https://git.xmpp-it.net/sch/PubSubToAtom'
title='About PubSub To Atom.'>
About
</a>
<a href='https://aboutfeeds.com/'
title='Of the benefits of syndication feed.'
id='aboutfeeds'>
Feeds
</a>
<a href='https://xmpp.org/about/technology-overview/'
title='Of the benefits of XMPP.'>
XMPP
</a>
<a href='https://join.jabber.network/#syndication@conference.movim.eu?join'
title='Syndictaion and PubSub.'>
Groupchat
</a>
</div>
<div id='feed'>
<div id='header'>
<!-- feed title -->
<h1 id='title'>
<xsl:choose>
<xsl:when test='//head/title and not(//head/title="") and count(//outline) &gt; 1'>
<xsl:value-of select='//head/title'/>
</xsl:when>
<xsl:when test='//outline'>
<xsl:value-of select='//outline/@text'/>
</xsl:when>
<xsl:otherwise>
StreamBurner OPML Feed
</xsl:otherwise>
</xsl:choose>
</h1>
<!-- feed subtitle -->
<h2 id='subtitle'>
<xsl:choose>
<xsl:when test='//head/description and not(//head/description="") and count(//outline) &gt; 1'>
<xsl:value-of select='//head/description'/>
</xsl:when>
<xsl:otherwise>
Outline Processor Markup Language
</xsl:otherwise>
</xsl:choose>
</h2>
</div>
<xsl:if test='count(//outline) &gt; 1'>
<div id='menu'>
<h3>Subscriptions</h3>
<!-- xsl:for-each select='outline[position() &lt;21]' -->
<ul>
<xsl:for-each select='//outline[not(position() &gt; 20)]'>
<li>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:text>#stremburner-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(@text) &gt; 0'>
<xsl:value-of select='@text'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</li>
</xsl:for-each>
</ul>
</div>
</xsl:if>
<div id='articles'>
<!-- opml outline -->
<xsl:choose>
<xsl:when test='//outline'>
<ul>
<xsl:for-each select='//outline[not(position() &gt; 20)]'>
<li>
<div class='entry'>
<!-- outline title -->
<h3 class='title'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='@xmlUrl'/>
</xsl:attribute>
<xsl:attribute name='title'>
<xsl:value-of select='@text'/>
</xsl:attribute>
<xsl:attribute name='id'>
<xsl:text>stremburner-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(@text) &gt; 0'>
<xsl:value-of select='@text'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</h3>
<!-- entry content -->
<h4>
<xsl:value-of select='@text'/>
</h4>
<p class='content'>
<xsl:value-of select='@xmlUrl'/>
</p>
</div>
</li>
</xsl:for-each>
</ul>
</xsl:when>
<xsl:otherwise>
<div class='notice no-entry'></div>
</xsl:otherwise>
</xsl:choose>
</div>
</div>
<div id='references'>
<a href='https://joinjabber.org/'
title='An Inclusive Space On The Jabber Network.'>
JoinJabber
</a>
<a href='https://libervia.org/'
title='The Universal Communication Ecosystem.'>
Libervia
</a>
<a href='https://join.movim.eu/'
title='The Social Platform Shaped For Your Community.'>
Movim
</a>
<a href='https://modernxmpp.org/'
title='A Project To Improve The Quality Of User-To-User Messaging Applications That Use Xmpp.'>
Modern
</a>
<a href='https://xmpp.org/'
title='The Universal Messaging Standard.'>
XMPP
</a>
<a href='https://xmpp.org/extensions/xep-0060.html'
title='XEP-0060: Publish-Subscribe.'>
PubSub
</a>
</div>
<!-- note -->
<p id='note'>
<i>
This is an
<b title ='Outline Processor Markup Language'>OPML</b>
document which is conveyed as an HTML document; This
document includes a list of subscriptionsis and is
intended to be imported to a syndication feed reader
which provides automated notifications on desktop and
mobile. <span id="selection-link">Click here</span> for
a selection of software and pick the ones that would fit
you best!
</i>
</p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>