159 lines
4.6 KiB
JavaScript
159 lines
4.6 KiB
JavaScript
// 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 : ''}
|
|
`
|
|
})
|
|
},
|
|
|
|
})
|