xrforge/manyfold/usr/src/app/public/view/element/janus-script-rss.js

160 lines
4.6 KiB
JavaScript
Raw Normal View History

// https://hyperborea.org/tech-tips/fediverse-feeds/
room.registerElement('rss', {
index: 0,
columns: 2,
rows: 1,
maxitems: 6,
items: [],
emoji: "💬",
borderspacing: 20,
padding: 20,
cell_col: "#222",
cell_radius: 15,
cell_col_font: "#FDF",
images: true,
css: "",
cellwidth: 290,
cellheight: 220,
overflow: true,
cssDefault(){
return `
.paragraphcontainer{
}
table{
border-spacing: ${this.borderspacing}px;
table-layout: fixed;
}
a{
color: ${this.cell_col_font};
}
td > div,
th > div,
.grid > div{
vertical-align:top;
padding: ${this.padding}px;
${this.overflow ? `min-height: ${this.cellheight}px;` : `height: ${this.cellheight}px !important` };
width: ${this.cellwidth}px !important;
background: ${this.cell_col};
border-radius: ${this.cell_radius}px;
color: ${this.cell_col_font};
${this.overflow ? 'overflow: hidden !important' : ''}
}
.light{
font-size:12px;
font-face: monospace;
opacity:0.66;
}
.grid {
-webkit-column-count: ${this.columns};
-webkit-column-gap: ${this.borderspacing}px;
-moz-column-count: ${this.columns};
-moz-column-gap: ${this.borderspacing}px;
column-count: ${this.columns};
column-gap: ${this.borderspacing}px;
}
.grid > div{
margin-bottom: ${this.borderspacing}px;
}
h1{
font-size:16px;
font-weight:bold;
}
`
},
createChildren(){
this.paragraph = this.children.find( (el) => el.tag == 'PARAGRAPH' )
if( !this.paragraph ) throw 'dialog-rss: error need paragraph child'
if( !this.url ) throw 'dialog-rss: error need url'
this.fetchRSS()
},
fetchRSS(){
try {
let url = new URL(this.url)
fetch(this.url)
.then( res => res.text() )
.then( xml => {
this.items = this.rssToHTML( xml, url.origin )
this.render()
})
}catch(e){
console.error("dialog-rss: invalid URL: "+this.url)
}
},
render(){
this.index = this.index % this.items.length // to be sure
let maxCells = this.columns > 1 && this.rows > 1 ? this.columns * this.rows : 99999
if( maxCells > this.maxitems ) maxCells = this.maxitems
const css = this.cssDefault() + this.css
const cssgridAvailable = 'grid' in document.documentElement.style;
let html = `<style>.paragraphcontainer { background:transparent; } ${css} </style>`
html += cssgridAvailable ? this.getCSSgrid(maxCells) : this.getTable()
this.paragraph.text = html
this.paragraph.visible = true
},
getCSSgrid: function(maxCells){
let i = 0
let html = `<div class="grid">`
while( this.items[ this.index + i ] && i < maxCells ){
let content = this.items[ this.index + i ]
html += `<div>${content}</div>`
i++
}
html += `</div>`
return html
},
getTable: function(maxCells){
let i = 0
let html = `<table><tr>`
while( this.items[ this.index + i ] && i < maxCells ){
let content = this.items[ this.index + i ]
if( i > 0 && (i % this.columns) == 0 ) html += `</tr><tr>`
html += `<td><div>${content}</div></td>`
i++
}
html += `</tr></table>`
return html
},
rssToHTML(xmlString, baseUrl){
const doc = new DOMParser().parseFromString(xmlString, "text/xml");
return [...doc.querySelectorAll("item")].map(item => {
const title = String(
item.querySelector("title")?.textContent ||
item.querySelector("pubDate")?.textContent ||
doc.querySelector("title")?.textContent ||
""
).replace(/ \+.*/,'')
const server = doc.querySelector("link")?.textContent || baseUrl.replace(/.*\/\//,'')
const link = item.querySelector("link")?.textContent || "#";
let desc = item.querySelector("description")?.textContent || "";
// Fix relative paths
desc = desc.replace(/(src|href)="\/([^"]+)"/g, `$1="${baseUrl}/$2"`);
// Extract media:content or media:thumbnail URLs
const media = [
...item.getElementsByTagNameNS("*", "content"),
...item.getElementsByTagNameNS("*", "thumbnail")
]
.filter( (m) => m.getAttribute("url")?.match(/\.(png|jpg|jpeg|gif)/gi) )
.map(m => `\n<img src="${m.getAttribute("url")}" width="100%"/>`).join("");
return `<h1>${this.emoji}${baseUrl.replace(/.*\/\//,'')}</h1>
<span class="light">${title}</span>
${desc}
<br>
${ this.images ? media : ''}
`
})
},
})