Graphic : Add an icon for syndication page;

HTML    : Add an XML template for The Atom Syndication Format;
Python  : Add support for The Atom Syndication Format;
README  : Add screenshots;
XSLT    : Add an XSL Transformations stylesheet.
This commit is contained in:
Schimon Jehudah, Adv. 2024-08-27 14:26:41 +03:00
parent 3d9e5b2e02
commit 164b4d67d4
34 changed files with 578 additions and 18 deletions

View file

@ -15,6 +15,11 @@ types adc, dweb, ed2k, feed, ftp, gemini, geo, gopher, http, ipfs, irc, magnet,
mailto, monero, mms, news, sip, udp, xmpp and any scheme and type that you mailto, monero, mms, news, sip, udp, xmpp and any scheme and type that you
desire. desire.
## Screenshots
[<img alt="browse view" src="graphic/browse.png" width="200px"/>](screenshot/browse.png)
[<img alt="tags view" src="graphic/tag.png" width="200px"/>](screenshot/tag.png)
## Technicalities ## Technicalities
Blasta is a federated bookmarking system which is based on XMPP and stores Blasta is a federated bookmarking system which is based on XMPP and stores
@ -45,7 +50,6 @@ The connection to the Blasta system is made with XMPP accounts.
## Future features ## Future features
- ActivityPub; - ActivityPub;
- Atom Syndication Format;
- Federation; - Federation;
- Filters; - Filters;
- Pin; - Pin;

View file

@ -261,15 +261,12 @@ class HttpInstance:
def __init__(self, accounts, sessions): def __init__(self, accounts, sessions):
self.app = FastAPI() self.app = FastAPI()
#templates = Jinja2Templates(directory='xhtml/template') templates = Jinja2Templates(directory='template')
templates = Jinja2Templates(directory='xhtml')
self.app.mount('/data', StaticFiles(directory='data'), name='data') self.app.mount('/data', StaticFiles(directory='data'), name='data')
self.app.mount('/export', StaticFiles(directory='export'), name='export') self.app.mount('/export', StaticFiles(directory='export'), name='export')
self.app.mount('/graphic', StaticFiles(directory='graphic'), name='graphic') self.app.mount('/graphic', StaticFiles(directory='graphic'), name='graphic')
self.app.mount('/stylesheet', StaticFiles(directory='stylesheet'), name='stylesheet') self.app.mount('/stylesheet', StaticFiles(directory='stylesheet'), name='stylesheet')
#self.app.mount('/policy', StaticFiles(directory='xhtml/policy'), name='policy')
#self.app.mount('/xhtml', StaticFiles(directory='xhtml'), name='xhtml')
filename_configuration = 'configuration.toml' filename_configuration = 'configuration.toml'
data = Data.open_file_toml(filename_configuration) data = Data.open_file_toml(filename_configuration)
@ -721,13 +718,14 @@ class HttpInstance:
related_tags = None related_tags = None
tags_dict = None tags_dict = None
param_filetype = request.query_params.get('filetype', '') or None param_filetype = request.query_params.get('filetype', '') or None
param_mode = request.query_params.get('mode', '') or None
param_page = request.query_params.get('page', '') or None param_page = request.query_params.get('page', '') or None
param_protocol = request.query_params.get('protocol', '') or None param_protocol = request.query_params.get('protocol', '') or None
param_query = request.query_params.get('q', '') or None param_query = request.query_params.get('q', '') or None
if param_query: param_query = param_query.strip() if param_query: param_query = param_query.strip()
param_tags = request.query_params.get('tags', '') or None param_tags = request.query_params.get('tags', '') or None
param_tld = request.query_params.get('tld', '') or None param_tld = request.query_params.get('tld', '') or None
if param_page: if param_page and param_mode != 'feed':
try: try:
page = int(param_page) page = int(param_page)
page_next = page + 1 page_next = page + 1
@ -979,7 +977,6 @@ class HttpInstance:
message = 'Blasta system message » Please connect with your XMPP account to view this directory.' message = 'Blasta system message » Please connect with your XMPP account to view this directory.'
path = 'error' path = 'error'
return result_post(request, jabber_id, description, message, path) return result_post(request, jabber_id, description, message, path)
template_file = 'browse.xhtml'
template_dict = { template_dict = {
'request': request, 'request': request,
'description': description, 'description': description,
@ -999,6 +996,12 @@ class HttpInstance:
'start': start, 'start': start,
'syndicate': jid, 'syndicate': jid,
'tags' : tags_dict or related_tags or ''} 'tags' : tags_dict or related_tags or ''}
if param_mode == 'feed':
template_file = 'browse.atom'
response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xml"
else:
template_file = 'browse.xhtml'
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers["Content-Type"] = "application/xhtml+xml"
return response return response
@ -1031,14 +1034,15 @@ class HttpInstance:
async def root_main_get(request: Request, response : Response, page_type=None): async def root_main_get(request: Request, response : Response, page_type=None):
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request) jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
node_id = path = syndicate = page_type node_id = path = syndicate = page_type
param_filetype = request.query_params.get('filetype', '') or None
param_mode = request.query_params.get('mode', '') or None
param_page = request.query_params.get('page', '') or None
param_protocol = request.query_params.get('protocol', '') or None
param_query = request.query_params.get('q', '') or None param_query = request.query_params.get('q', '') or None
if param_query: param_query = param_query.strip() if param_query: param_query = param_query.strip()
param_page = request.query_params.get('page', '') or None
param_tags = request.query_params.get('tags', '') or None param_tags = request.query_params.get('tags', '') or None
param_tld = request.query_params.get('tld', '') or None param_tld = request.query_params.get('tld', '') or None
param_filetype = request.query_params.get('filetype', '') or None if param_page and param_mode != 'feed':
param_protocol = request.query_params.get('protocol', '') or None
if param_page:
try: try:
page = int(param_page) page = int(param_page)
page_next = page + 1 page_next = page + 1
@ -1154,6 +1158,12 @@ class HttpInstance:
'pubsub_jid' : jabber_id_pubsub, 'pubsub_jid' : jabber_id_pubsub,
'syndicate' : syndicate, 'syndicate' : syndicate,
'tags' : tags_dict} 'tags' : tags_dict}
if param_mode == 'feed':
template_file = 'browse.atom'
response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xml"
else:
template_file = 'browse.xhtml'
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers["Content-Type"] = "application/xhtml+xml"
return response return response

View file

@ -0,0 +1,6 @@
<svg width="50" height="50" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="25" height="25" fill="#fff" />
<rect x="25" y="0" width="25" height="25" fill="#d9541e" />
<rect x="0" y="25" width="25" height="25" fill="#CC5D15" />
<rect x="25" y="25" width="25" height="25" fill="#d3d2d2" />
</svg>

After

Width:  |  Height:  |  Size: 316 B

BIN
screenshot/browse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

BIN
screenshot/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

491
stylesheet/stylesheet.xsl Normal file
View file

@ -0,0 +1,491 @@
<?xml version='1.0' encoding='UTF-8' ?>
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:xml='http://www.w3.org/XML/1998/namespace'
xmlns:media='http://search.yahoo.com/mrss/'
xmlns:georss='http://www.georss.org/georss'
xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#'
xmlns:atom10='http://www.w3.org/2005/Atom'
xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:output
method = 'html'
indent = 'yes'
omit-xml-decleration='no' />
<!-- set page metadata -->
<xsl:template name='metadata'>
<xsl:param name='name'/>
<xsl:param name='content'/>
<xsl:if test='$content and not($content="")'>
<xsl:element name='meta'>
<xsl:attribute name='name'>
<xsl:value-of select='$name'/>
</xsl:attribute>
<xsl:attribute name='content'>
<xsl:value-of select='$content'/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template match='/atom:feed'>
<!-- index right-to-left language codes -->
<!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
<xsl:variable name='rtl'
select='@xml: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='atom:subtitle' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"generator"' />
<xsl:with-param name='content' select='"Blasta"' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"mimetype"' />
<xsl:with-param name='content' select='"application/atom+xml"' />
</xsl:call-template>
<title>
<xsl:value-of select='atom:title'/>
</title>
<link rel='stylesheet' type='text/css' media='screen' href='/stylesheet/stylesheet.css'/>
<link rel='icon' type='image/svg+xml' href='/graphic/blasta.svg'/>
</head>
<body>
<div id='header'>
<!-- feed title -->
<h1>
<img src='/graphic/blasta_syndicate.svg'/>
&#8202;
<xsl:value-of select='atom:title'/>
</h1>
<dl id='navigation'>
<dd>
<img src='/graphic/blasta_syndicate.svg'/>
</dd>
<dd>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link'/>
</xsl:attribute>
<xsl:attribute name='id'>follow</xsl:attribute>
<xsl:attribute name='title'>Subscribe to updates with a news reader software.</xsl:attribute>
Follow
</xsl:element>
</dd>
<dd>
<a id='subtome' title='Subscribe via SubToMe.'>
<xsl:attribute name='href'>
javascript:location.href='https://www.subtome.com/#/subscribe?feeds='+location.href;
</xsl:attribute>
<xsl:attribute name='onclick'>
(
function(btn){
var z=document.createElement('script');
document.subtomeBtn=btn;
z.src='https://www.subtome.com/load.js';document.body.appendChild(z);
}
)(this);
return false;
</xsl:attribute>
SubToMe
</a>
</dd>
<dd>
<a href='/help/about/xmpp'
title='Of the benefits of XMPP.'>
XMPP
</a>
</dd>
<dd>
<a href='https://xmpp.link/#syndication@conference.movim.eu?join'
title='Syndictaion and PubSub.'>
Groupchat
</a>
</dd>
<dd>
<a href='/help/feeds'
title='Of the benefits of syndication feed.'
id='aboutfeeds'>
Help
</a>
</dd>
</dl>
</div>
<xsl:if test='count(atom:entry) &gt; 1'>
<div>
<h3>
<img src='/graphic/syndicate.svg' height='22' width='22'/>
&#8202;
<xsl:value-of select='atom:subtitle'/>
</h3>
<!-- xsl:for-each select='atom:entry[position() &lt;21]' -->
<ul>
<xsl:for-each select='atom:entry[not(position() &gt; 20)]'>
<li>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:text>#blasta-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(atom:title) &gt; 0'>
<xsl:value-of select='atom:title'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</li>
</xsl:for-each>
</ul>
</div>
</xsl:if>
<div id='main'>
<!-- feed entry -->
<dl>
<dd>
<article class='title'>
<h4 class='title'>
<a href='/help/feeds'>
This is an automated Blasta Syndication Feed
</a>
</h4>
<h5 class='related'>
&#8202;
<img alt='💡'
height='16'
src='/graphic/xmpp.svg'
width='16' />
&#8202;
<a href='/help/about/xmpp/pubsub'>XMPP</a>
&#8202;
<img alt='⚛'
class='enlarge'
height='16'
src='/graphic/syndicate.svg'
width='16' />
&#8202;
<a href='/help/syndication'>Syndication</a>
</h5>
<p>
<b>Congratulations!</b> You have reached to the final
frontier that the XML technology has to offer
to-date, and as of yet!
</p>
<p>
This is an automated Blasta Syndication Feed
which you can utilize to gracefully receive
autmatic Blasta updates.
</p>
<p>
<a href='/help/syndication#software'>Click
here</a> for a selection of software and pick
the ones that would fit to you best!
</p>
<div class='details'>
<a href='?tags=filetype:atom'>filetype:atom </a>
<a href='?tags=filetype:rss'>filetype:rss </a>
<a href='?tags=filetype:xml'>filetype:xml </a>
<a href='?tags=software:akregator'>software:akregator </a>
<a href='?tags=software:fraidycat'>software:fraidycat </a>
<a href='?tags=software:liferea'>software:liferea </a>
<a href='?tags=software:otter-browser'>software:otter-browser </a>
<a href='?tags=software:raven-reader'>software:raven-reader </a>
<a href='?tags=niche:syndication'>niche:syndication</a>
<h5 class='updated'>2024-08-22T17:09:04+03:00</h5>
<h5 class='author'>
<a href='/jid/sch@pimux.de'>sch@pimux.de</a>
</h5>
</div>
</article>
</dd>
<xsl:choose>
<xsl:when test='atom:entry'>
<xsl:for-each select='atom:entry[not(position() >20)]'>
<dd>
<article class='title'>
<!-- entry title -->
<h4 class='title'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:choose>
<xsl:when test='atom:link[@rel="self"]'>
<xsl:value-of select='atom:link[@rel="self"]/@href'/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select='atom:link/@href'/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name='id'>
<xsl:text>blasta-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(atom:title) &gt; 0'>
<xsl:value-of select='atom:title'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</h4>
<h5 class='related'>
💡️
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="alternate" and @type="x-scheme-handler/xmpp"]/@href'/>
</xsl:attribute>
PubSub
</xsl:element>
&#8203;&#8202;
💬
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="related" and @type="text/html"]/@href'/>
</xsl:attribute>
People
</xsl:element>
</h5>
<!-- entry content -->
<p class='summary'>
<xsl:value-of select='atom:summary'/>
</p>
<!-- entry tags -->
<xsl:if test='atom:category/@term'>
<div class='details'>
<xsl:for-each select='atom:category'>
<xsl:element name='a'>
<xsl:attribute name='href'>
?tags=<xsl:value-of select='@term'/>
</xsl:attribute>
<xsl:value-of select='@term'/>
</xsl:element>
&#8202;
</xsl:for-each>
<h5 class='date'>
<!-- entry date -->
<xsl:choose>
<xsl:when test='atom:updated'>
<xsl:attribute name='class'>
<xsl:text>updated</xsl:text>
</xsl:attribute>
<xsl:value-of select='atom:updated'/>
</xsl:when>
<xsl:when test='atom:published'>
<xsl:attribute name='class'>
<xsl:text>published</xsl:text>
</xsl:attribute>
<xsl:value-of select='atom:published'/>
</xsl:when>
<xsl:otherwise>
<h4 class='warning atom1 published'></h4>
</xsl:otherwise>
</xsl:choose>
</h5>
<!-- entry author -->
<h5 class='author'>
<xsl:choose>
<xsl:when test='atom:author/atom:email'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:text>mailto:</xsl:text>
<xsl:value-of select='atom:author/atom:email'/>
</xsl:attribute>
<xsl:attribute name='title'>
<xsl:text>Send an Email to </xsl:text>
<xsl:value-of select='atom:author/atom:email'/>
</xsl:attribute>
<xsl:value-of select='atom:author/atom:name'/>
</xsl:element>
</xsl:when>
<xsl:when test='atom:author/atom:uri'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:author/atom:uri'/>
</xsl:attribute>
<xsl:attribute name='title'>
<xsl:value-of select='atom:author/atom:summary'/>
</xsl:attribute>
<xsl:value-of select='atom:author/atom:name'/>
</xsl:element>
</xsl:when>
<xsl:when test='atom:author/atom:name'>
<xsl:text>By </xsl:text>
<xsl:value-of select='atom:author/atom:name'/>
</xsl:when>
</xsl:choose>
</h5>
</div>
</xsl:if>
</article>
<!-- entry id -->
<xsl:if test='not(atom:id)'>
<div class='warning atom1 id'>No entry ID</div>
</xsl:if>
</dd>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<dd>
<div class='entry'>
<h3 class='title'>
<a href='javascript:alert("Please check that the mother PubSub node is populated with content.")'>
No content
</a>
</h3>
<h4>This entry is empty</h4>
<div class='content'>Please check that the mother PubSub node is populated with content.</div>
</div>
</dd>
</xsl:otherwise>
</xsl:choose>
<dd>
<article class='title'>
<h4 class='title'>
<a href='/help/feeds'>
About Blasta Feeds
</a>
</h4>
<h5 class='related'>
&#8202;
<img alt='💡'
height='16'
src='/graphic/xmpp.svg'
width='16' />
&#8202;
<a href='/help/about/xmpp/pubsub'>XMPP</a>
&#8202;
<img alt='⚛'
class='enlarge'
height='16'
src='/graphic/syndicate.svg'
width='16' />
&#8202;
<a href='/help/syndication'>Syndication</a>
</h5>
<p>
This is a concise introduction to the syndication
technology. <b>Please, read it.</b>
</p>
<p>
This an Atom document which can also be viewed
with a Syndication Feed Reader (also referred to
as News Reader or RSS Reader) which provides
automated news updates and notifications on
desktop and mobile.
<a href='/help/syndication#software'>Click
here</a> for a selection of software and pick
the ones that would fit to you best!
</p>
<p>
This ASF (Atom Syndication Format) document is
conveyed as an XHTML document. This document was
produced by an XSLT <a
href='stylesheet/stylesheet.xsl'>stylesheet</a>.
</p>
<p>
XSLT is a powerful technology which transforms
XML documents into HTML, JSON, PDF, Text, XHTML,
and (modified) XML documents;
<a href='https://www.w3.org/Style/XSL/'>Learn
more</a> about The Extensible Stylesheet
Language Family (XSL).
</p>
<div class='details'>
<a href='?tags=brand:atompub'>brand:atompub </a>
<a href='?tags=brand:atomsub'>brand:atomsub </a>
<a href='?tags=brand:pubsub'>brand:pubsub </a>
<a href='?tags=code:xslt'>code:xslt </a>
<a href='?tags=filetype:atom'>filetype:atom </a>
<a href='?tags=filetype:rss'>filetype:rss </a>
<a href='?tags=filetype:xml'>filetype:xml </a>
<a href='?tags=software:libervia'>software:libervia </a>
<a href='?tags=software:movim'>software:movim </a>
<a href='?tags=software:reeder'>software:reeder </a>
<a href='?tags=niche:syndication'>niche:syndication</a>
<h5 class='updated'>2024-08-22T17:09:04+03:00</h5>
<h5 class='author'>
<a href='/jid/sch@pimux.de'>sch@pimux.de</a>
</h5>
</div>
</article>
</dd>
</dl>
</div>
<div id='footer'>
<dl>
<dd>
<img src='/graphic/blasta.svg' alt='logo'/>
<a href='/'>
Blasta
</a>
</dd>
<dd>
<a href='https://joinjabber.org'
title='An Inclusive Space On The Jabber Network.'>
JoinJabber
</a>
</dd>
<dd>
<a href='https://libervia.org'
title='The Universal Communication Ecosystem.'>
Libervia
</a>
</dd>
<dd>
<a href='https://join.movim.eu'
title='The Social Platform Shaped For Your Community.'>
Movim
</a>
</dd>
<dd>
<a href='https://git.xmpp-it.net/sch/Rivista'
title='A Journal Publisher And Browser For XMPP.'>
Rivista
</a>
</dd>
<dd>
<a href='https://github.com/SeveFP/Reeder'
title='An XMPP-Based Feed Reader.'>
Reeder
</a>
</dd>
<dd>
<a href='https://modernxmpp.org'
title='A Project To Improve The Quality Of User-To-User Messaging Applications That Use XMPP.'>
Modern
</a>
</dd>
<dd>
<a href='https://xmpp.org'
title='The Universal Messaging Standard.'>
XMPP
</a>
</dd>
<dd>
<a href='https://xmpp.org/extensions/xep-0060.html'
title='XEP-0060: Publish-Subscribe.'>
PubSub
</a>
</dd>
</dl>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

49
template/browse.atom Normal file
View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="stylesheet/stylesheet.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<generator uri="https://git.xmpp-it.net/sch/Blasta" version="0.1">Blasta</generator>
<icon>/graphic/blasta.svg</icon>
<link href="{{request.url}}"
rel="self"
type="text/html" />
<link href="{{request.url.__str__().replace('?mode=feed', '')}}"
rel="alternate"
type="text/html" />
<link href="xmpp:{{pubsub_jid}}?pubsub;action=subscribe;node={{node_id}}"
rel="alternate"
type="x-scheme-handler/xmpp" />
<logo>/graphic/blasta.svg</logo>
<subtitle type="text">{{description}}</subtitle>
<title type="text">Blasta / {{path}}</title>
<updated>{{entries[0]['updated']}}</updated>
{% if entries %}
{% for entry in entries %}
<entry>
<author>
<name>{{entry['name']}}</name>
<uri>/jid/{{entry['jid']}}</uri>
<uri>xmpp:{{entry['jid']}}</uri>
<xmpp>{{entry['jid']}}</xmpp>
</author>
<id>{{entry['url_hash']}}</id>
<link href="{{entry['link']}}"
rel="alternate" />
<link href="xmpp:{{pubsub_jid}}?pubsub;action=subscribe;node=hash:{{entry['url_hash']}}"
rel="alternate"
type="x-scheme-handler/xmpp" />
<link href="/url/{{entry['url_hash']}}"
rel="related"
type="text/html" />
<published>{{entry['published']}}</published>
<summary type="text">{{entry['summary']}}</summary>
<title>{{entry['title']}}</title>
<updated>{{entry['updated']}}</updated>
{% if entry['tags'] | length > 0 %}
{% for tag in entry['tags'] %}
<category term="{{tag}}" />
{% endfor %}
{% endif %}
</entry>
{% endfor %}
{% endif %}
</feed>

View file

@ -11,8 +11,8 @@
<link rel="stylesheet" type="text/css" media="screen" <link rel="stylesheet" type="text/css" media="screen"
href="/stylesheet/stylesheet.css" /> href="/stylesheet/stylesheet.css" />
<link rel="alternate" type="application/atom+xml" <link rel="alternate" type="application/atom+xml"
title="Follow updates on /{{syndicate}}{% if param_tags %} for Tag: #{{param_tags}}{% endif %}{% if param_url %} for URL: {{param_url}}{% endif %}{% if param_hash %} for hash: {{param_hash}}{% endif %}" title="Follow updates on /{% if jid %}jid/{% endif %}{{syndicate}}{% if param_tags %} for Tag: #{{param_tags}}{% endif %}{% if param_url %} for URL: {{param_url}}{% endif %}{% if param_hash %} for hash: {{param_hash}}{% endif %}"
href="/{{syndicate}}?mode=feed{% if param_tags %}&amp;tags={{param_tags}}{% endif %}{% if param_url %}&amp;url={{param_url}}{% endif %}{% if param_hash %}&amp;hash={{param_hash}}{% endif %}" /> href="/{% if jid %}jid/{% endif %}{{syndicate}}?mode=feed{% if param_tags %}&amp;tags={{param_tags}}{% endif %}{% if param_url %}&amp;url={{param_url}}{% endif %}{% if param_hash %}&amp;hash={{param_hash}}{% endif %}" />
<link rel="alternate" type="application/atom+xml" <link rel="alternate" type="application/atom+xml"
title="Subscribe to PubSub /{{syndicate}}{% if param_tags %} for Tag: #{{param_tags}}{% endif %}{% if param_url %} for URL: {{param_url}}{% endif %}{% if param_hash %} for hash: {{param_hash}}{% endif %}" title="Subscribe to PubSub /{{syndicate}}{% if param_tags %} for Tag: #{{param_tags}}{% endif %}{% if param_url %} for URL: {{param_url}}{% endif %}{% if param_hash %} for hash: {{param_hash}}{% endif %}"
href="xmpp:{{pubsub_jid}}?pubsub;action=subscribe;node={{node_id}}" /> href="xmpp:{{pubsub_jid}}?pubsub;action=subscribe;node={{node_id}}" />
@ -227,7 +227,7 @@
&#8203;&#8202; &#8203;&#8202;
and and
<img alt="⚛" class="enlarge" src="/graphic/syndicate.svg" width="16" height="16"/> <img alt="⚛" class="enlarge" src="/graphic/syndicate.svg" width="16" height="16"/>
<a href="/{{syndicate}}?mode=feed{% if param_tags %}&amp;tags={{param_tags}}{% endif %}{% if param_url %}&amp;url={{param_url}}{% endif %}{% if param_hash %}&amp;hash={{param_hash}}{% endif %}"> <a href="/{% if jid %}jid/{% endif %}{{syndicate}}?mode=feed{% if param_tags %}&amp;tags={{param_tags}}{% endif %}{% if param_url %}&amp;url={{param_url}}{% endif %}{% if param_hash %}&amp;hash={{param_hash}}{% endif %}">
RSS RSS
<!-- img src="/graphic/atom.svg" width="36" height="14" alt="Atom"/ --> <!-- img src="/graphic/atom.svg" width="36" height="14" alt="Atom"/ -->
</a> </a>