// ==UserScript==
// @name Newspaper
// @namespace org.openuserjs.sjehuda.newspaper
// @description Native Feed Viewer. Render syndication web feeds (supports ActivityStreams, Atom, JSON, RDF and RSS)
// @homepageURL https://sjehuda.github.io/newspaper.html
// @supportURL https://openuserjs.org/scripts/sjehuda/Newspaper/issues
// @copyright 2023, Schimon Jehudah (http://schimon.i2p)
// @license MIT; https://opensource.org/licenses/MIT
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=
// @include *
// @version 23.05.10
// @run-at document-start
// ==/UserScript==
/*
TODO
1) "Lazy" Content media loader
https://www.merixstudio.com/blog/lazy-loading-pure-javascript/
https://css-tricks.com/the-complete-guide-to-lazy-loading-images/
2) Dark mode
body { color: WhiteSmoke; background: #333; }
a { color: WhiteSmoke; }
#top-navigation-button { background: '#555'; border: '2px solid WhiteSmoke'; }
Example: https://alligator.io/feed.xml (Color schemes)
Note: Waiting for Falkon https://bugs.kde.org/show_bug.cgi?id=468046
3) Parse XML using DOMParser
parser = new DOMParser();
xmlDoc = parser.parseFromString(text,"text/xml");
4) Use JSON to store and apply inline CSS style per element
5) Replace <xsl:text>#newspaper-oujs-</xsl:text> by <xsl:text>#</xsl:text><xsl:value-of select="title"/>
6) Add instructions to
navigator.userAgent.toLowerCase().includes('firefox')
https://openuserjs.org/scripts/sjehuda/Newspaper#gecko
NOTE
1) <p class="explanation">This is a podcast RSS feed generated by <a href="https://castos.com/seriously-simple-podcasting/">Seriously Simple Podcasting</a>. It is meant for consumption by podcast feed readers using the URL in the address bar.</p>
Design HTML as JSON
NOTE Handling attributes
"attributes": {
"style" : {
"direction" : "language"
}
}
feedItems = {
"div": "feed",
"attributes": {
"style" : "language",
},
"children" : {
"div": "title",
"div": "subtitle",
"div": "links-bar",
"div": "entry", // multiple alike
"attributes": {
"style" : "language",
},
"children" : {
"div" : "title",
"attributes": {
"href" : ["url", "id"],
"id" : "id",
},
"div" : ["authors", "author"],
"div" : ["date_published", "date_modified"],
"div" : ["content_html", "content_text"],
"div" : "image",
"div" : "tags",
},
}
};
FIXME
1) Event listeners don't work on every page, yet.
This is due to document.contentType (read only) determined as xml (Mainstream issue)
*/
const
namespace = 'org.openuserjs.sjehuda.newspaper',
// This news feed is brought to you by Streamburner News Reader
defaultSubtitle = 'News feed rendered by Streamburner',
rtlLocales = ['ar', 'fa', 'he', 'ji', 'ku', 'ur', 'yi'],
atomRules = {
"feedLanguage" : "feed", // @xml:lang
"feedTitlePage" : "feed > title",
"feedSubtitle" : "feed > subtitle",
"feedDate" : "updated",
"feedItem" : "entry",
"feedItemTitle" : "title",
"feedItemLink" : "link",
"feedItemDate" : "updated",
"feedItemContent" : "content"
},
rdfRules = {
"feedLanguage" : "channel > language", // TODO
"feedTitlePage" : "channel > title",
"feedSubtitle" : "channel > description",
"feedDate" : "date",
"feedItem" : "item",
"feedItemTitle" : "title",
"feedItemLink" : "link",
"feedItemDate" : "date",
"feedItemContent" : "description"
},
rssRules = {
"feedLanguage" : "channel > language",
"feedTitlePage" : "channel > title",
"feedSubtitle" : "channel > description",
"feedDate" : "lastBuildDate",
"feedItem" : "item",
"feedItemTitle" : "title",
"feedItemLink" : "link",
"feedItemDate" : "pubDate",
"feedItemContent" : "description"
},
banner =`<svg width="256" height="100" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="77.1180071%" y1="12.3268731%" x2="3.36110907%" y2="118.781335%" id="a"><stop stop-color="#D446FF" offset="0%"/><stop stop-color="#A0D8FF" offset="100%"/></linearGradient><linearGradient x1="50.7818321%" y1="-17.173918%" x2="76.3448843%" y2="77.2144178%" id="b"><stop stop-color="#3C3C3C" offset="0%"/><stop stop-color="#191919" offset="100%"/></linearGradient><linearGradient x1="148.794275%" y1="-26.5643443%" x2="-21.1415871%" y2="99.3029307%" id="c"><stop stop-color="#D446FF" offset="0%"/><stop stop-color="#A0D8FF" offset="100%"/></linearGradient><linearGradient x1="41.8083357%" y1="20.866645%" x2="95.5956597%" y2="-8.31097281%" id="d"><stop stop-color="#FFF" offset="0%"/><stop stop-color="#DADADA" offset="100%"/></linearGradient><linearGradient x1="52.2801818%" y1="70.5577815%" x2="2.53678786%" y2="8.97706744%" id="e"><stop stop-color="#FFF" offset="0%"/><stop stop-color="#DADADA" offset="100%"/></linearGradient><linearGradient x1="98.684398%" y1="12.9995489%" x2="35.2678133%" y2="40.863838%" id="f"><stop stop-color="#D0D0D0" offset="0%"/><stop stop-color="#FFF" offset="100%"/></linearGradient><linearGradient x1="34.2841787%" y1="31.6476155%" x2="-40.2132134%" y2="123.398162%" id="g"><stop stop-color="#FFDC68" offset="0%"/><stop stop-color="#CE4300" offset="100%"/></linearGradient><linearGradient x1="95.7086811%" y1="2.33776624%" x2="-10.5474304%" y2="34.7418529%" id="h"><stop stop-color="#FFDC68" offset="0%"/><stop stop-color="#CE4300" offset="100%"/></linearGradient><linearGradient x1="55.2222258%" y1="39.6484627%" x2="-63.5655829%" y2="222.055577%" id="i"><stop stop-color="#FFDC68" offset="0%"/><stop stop-color="#CE4300" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path fill="#252525" fill-rule="nonzero" d="M68.3766216 33.24450169V46.6414437h17.1215335v4.3309176H68.3766216v18.9693703h-4.9083597V28.91359256h23.7622535v4.33090913H68.3766216zm31.2822046-4.92645583h5.5724368L119.81199 69.3461849h-4.966111l-4.157673-11.9244357H93.7687843l-4.1576731 11.9244357H85.078099l14.5807272-41.02813904Zm2.5696738 4.7928662L95.241299 53.1197155h13.945526L102.2285 33.11091206Zm20.879509-4.1973195h4.90836V65.6397065h18.420795v4.3020251h-23.329155zm27.650382 0h4.908369V69.9417316h-4.908369zm48.41257-.4253905c2.714031 0 5.245206.48121024 7.593526 1.44363071 2.367565.9431758 4.407905 2.28094667 6.121021 4.01331259 1.732366 1.73235454 3.089381 3.89780774 4.071046 6.49635944.981671 2.5792957 1.472507 5.4184455 1.472507 8.5174493 0 4.1961709-.837308 7.9303791-2.511923 11.2026246-1.655364 3.2529952-3.965184 5.7745455-6.929458 7.5646511-2.945012 1.770861-6.284623 2.6562914-10.018831 2.6562914-2.463811 0-4.82175-.43309-7.073818-1.2992702-2.252074-.8854361-4.282792-2.1654616-6.092154-3.8400765-1.80935-1.6746149-3.252981-3.8593184-4.330892-6.5541105-1.077922-2.7140367-1.616884-5.7649203-1.616884-9.1526508 0-3.1759995.490836-6.0825226 1.472507-8.7195693.981676-2.6370411 2.329072-4.8506144 4.042188-6.64072001 1.713115-1.8093616 3.753455-3.20487781 6.12102-4.18654862 2.367559-1.00091547 4.927607-1.50137321 7.680145-1.50137321Zm-.259854 4.30203363c-1.751611 0-3.435857.32722172-5.052738.98166514-1.616863.65445477-3.108617 1.61688087-4.475261 2.88727847-1.347399 1.2511471-2.434941 2.9450124-3.262626 5.0815957-.808429 2.117333-1.212643 4.5137703-1.212643 7.1893121 0 2.560051.375344 4.9276129 1.126034 7.1026855.750689 2.1558337 1.770858 3.9651924 3.060506 5.4280763 1.289648 1.4628896 2.810277 2.5985489 4.561887 3.406978 1.770867.8084405 3.666844 1.2126607 5.687931 1.2126607 1.366643 0 2.704411-.2021115 4.013304-.6063346 1.328148-.4042174 2.598552-1.0394161 3.811209-1.9055963 1.212647-.8854361 2.271313-1.9633529 3.176-3.2337505.923925-1.2703975 1.655367-2.8391469 2.194326-4.7062481.558203-1.867107.837304-3.9170723.837304-6.149896 0-2.2713186-.288726-4.3501538-.86618-6.2365054-.577453-1.8863516-1.347393-3.4647261-2.309819-4.7351237-.943176-1.2704032-2.049963-2.3386948-3.32036-3.2048749-1.251159-.88543618-2.550435-1.52063777-3.897828-1.90560483-1.328143-.40421172-2.685158-.60631758-4.071046-.60631758Zm23.397966-3.87664313h6.207638l22.347481 34.32967654V28.91359256h4.908369V69.9417316h-6.20763l-22.34749-34.3874106v34.3874106h-4.908368V28.91359256z"/><path fill="url(#a)" fill-rule="nonzero" d="M174.265411 4.50913925h6.14987L163.062778 24.0848526l-6.178763.4042146z" transform="translate(0 25)"/><path fill="url(#b)" fill-rule="nonzero" d="M162.552309 23.4815553 181.00198 44.933981h-6.323132l-18.305302-21.0482111z" transform="translate(0 25)"/><g transform="translate(0 25)"><ellipse stroke="#A3A3A3" stroke-width=".5" fill="url(#c)" fill-rule="nonzero" cx="26.8134179" cy="26.3507547" rx="26.805321" ry="26.3202814"/><path d="M9.37216085 52.0819111S17.7489335 22.7696899 50.3566889 26.4969018c0 0-20.723434-12.3020208-40.84161386 4.2828996-5.94153114 13.7625736-.14291419 21.3021097-.14291419 21.3021097Z" fill="url(#d)"/><path d="M11.8660211 48.3743096s8.7405326-21.0730284 32.0717549-21.4433648c-17.7350232 4.3205976.0290967 19.6378374.0290967 19.6378374s-14.0287234 12.7111189-32.1008516 1.8055274Z" fill="url(#e)"/><path d="M6.99959641 32.2202162S24.4574437 13.0165918 50.527832 26.2845469c-.4655559-3.607941-3.5497731-7.6814378-9.0780884-8.8452977-1.2220448-.5819257-10.1837464-14.43182348-29.4455641-6.459393 5.9256811.461387 8.4682656 1.291426 8.612541 1.9203488C10.369106 11.1390373 7.33148608 25.722572 6.99959641 32.2202162Z" fill="url(#f)"/><path d="M32.03706 15.9299212s4.4665322-1.5996384 5.0482196 3.1577417c-4.4042039.4155129-5.0482196-3.1577417-5.0482196-3.1577417Z" fill="#202020"/><path d="M9.2551104 52.1772666s7.6361082-24.9093105 33.1663186-25.8730411c0 0-20.7166787-3.7798413-35.34817197 19.3977044-.08112197 3.2790716 2.18185337 6.4753367 2.18185337 6.4753367Z" fill="url(#g)"/><path d="M12.0256021 10.9822724s6.3677384.3703449 8.6411954 1.9339868c.9492333-.1952797 1.7552122-.8451998 1.892222-1.7303694-.8145463-.337105-8.2702464-.9442903-10.5334174-.2036174Z" fill="url(#h)"/><path d="M7.05701562 32.2533371s7.97220928-9.5753699 24.06662038-10.719313c0 0-21.9094907.9901814-23.7487686 7.1768481-.5113449 1.4876671-.31785178 3.5424649-.31785178 3.5424649Z" fill="url(#i)"/></g></g></svg>`,
htmlDonate =
`<div class="about-newspaper" id="about-donate">
<div><big><b>Donations</b></big></div>
<div>No, thank you. Yet, I do appreciate your concern.</div>
<div><big><b>Here are some things you can do</b></big></div>
<div>In no particular order …</div>
<ul>
<li>Talk with your friends about the benefits of RSS (i.e. Web Feeds). That would be a good table talk.</li>
<li>Use an RSS reader even if it’s not Newspaper. (There are a bunch of good ones!)</li>
<li>Teach other people to use RSS readers. Blog about RSS readers. And about other open web technologies and apps.</li>
<li>Write a blog instead of posting to “social networks”. (You can always re-post to those places if you want to extend your reach.) <a href="https://micro.blog/">Micro.blog</a> is one good place to get going, and it’s not the only one.</li>
<li>Contact <a href="http://unicode.org/pending/proposals.html"><u>unicode.org</u></a> and promote the initiative for the <a href="https://github.com/vhf/unicode-syndication-proposal"><u>Proposal to Include Web Syndication Symbol</u></a>.</li>
<li>Donate to charities that promote literacy.</li>
<li>Tell other people about cool blogs and feeds you’ve found.</li>
<li>Support independent podcast apps and desktop programs.</li>
<li>Support your local library.</li>
<li>Be bold and do your best work.</li>
<li>Support indie developers. Even though software like Falkon, Newspaper, postmarketOS etc. are free, software are most definitely not free to make, and it costs time and effort to keep improving them. It’s worth it.</li>
<li>Finally: report bugs and make feature requests on our Issues tracker. We also need testers, writers, and, especially, people who are willing to talk things over. Most of software development is just making decisions, and we appreciate all the help we can get!.</li>
<li>Or: skip helping us, and, instead, help people who need help more than we do. Those people should not be hard to find.</li>
<li>Buy a meal to a person in need, or, even better, get a job for him or her.</li>
<li>Establish a family, or if you already are a parent, bring a new healthy child to the world.</li>
<li>Get more ideas from <a href="https://github.com/Ranchero-Software/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown#here-are-some-things-you-can-do">Ranchero-Software/NetNewsWire</a>.</li>
</ul>
<div>If you happen to visit in the Middle East, reach me out and we can meet for a café or tea.</div>
<div>Sharing is caring, and is exactly what makes us humans. It's "all of us for all of us" or we're on our own.</div>
<div>Schimon</div>
<div><small><i>(The technology behind ActivityPub and RSS is) "The very simple technology that Big Corps, Fortune 500, Mozilla et al. are jealously trying to oppress and hide from you", (because it unleashes the embodiment of what open web should be, a truely free-speech-driven web. The problem, for them, is that if true openness would flourish, it might have the "dire" potential, at least for them, to put many of them off the market). -- Alex James Anderson</i></small></div>
</div>`,
htmlAbout =
`<div class="about-newspaper" id="about-feed">
<div><small><i>"The technology that Big Corps, Fortune 500 and Mozilla et al. don't want you to know about". -- Alex James Anderson</i></small></div>
<hr></hr>
<div class="content" id="about-toc">
<b>Table of contents</b>
<a href="#intro">Web syndication news feed</a>
<a href="#feeds">Recommended feeds for start</a>
<a href="#software">Recommended feed readers</a>
<a href="#services">Recommended online services</a>
<a href="#learn">Learn more about web feeds</a>
<a href="#plea">An appeal from the author</a>
</div>
<div><big>📰 <b>Web syndication news feed</b></big></div>
<div class="content" id="intro">
<div>Syndication feed is a mean for content and media publishers to reach a wider audience easily. It allows you to receive information directly without the going from site to site.</div>
<div>Essentially, a feed embodies a function that allows “Feed Readers” to access multiple websites, automatically looking for new contents and then posting the information about new contents and updates to another website, mobile app or desktop software at your office.</div>
<div>Feeds provide a simple way to keep up with the latest news, events, package and delivery status information posted on different websites such as news sites, music sites, content sites (aka “social networks”), torrent indexers, podcasts and <a href="#feeds"><u>more</u></a>; all, in one single spot.</div>
<div>In the hope you find this program useful; and I do hope you’d enjoy and get the most out of this program!</div>
<div>Read more on: <a href="http://rss.userland.com/howUseRSS">How You Can Use RSS</a> and <a href="http://rss.userland.com/whyImportant">Why is RSS Important?</a></div>
</div>
<!-- div><big>📗 <b>Recommended feeds</b></big></div -->
<div><big>{ } <b><rss> is everywhere</b></big></div>
<div class="content" id="feeds">
<div>This is a list of feeds that should get you started with your news reader <a href="#software"><u>app or software</u></a>.</div>
<div class="category">
<div>Art, Literature & Nature</div>
<a href="https://4columns.org/feed">4Columns</a>
<a href="https://www.brainyquote.com/link/quotebr.rss">Quotes</a>
<a href="https://darksitefinder.com/feed/">Dark Site Finder</a>
<a href="https://tpb.party/rss/new/601">E-books (TPB)</a>
<a href="https://freedif.org/blog.atom">Freedif</a>
<a href="https://ifers.forumotion.com/feed/?type=atom">IFERS</a>
<a href="https://librivox.org/feed/">LibriVox</a>
<a href="https://www.music-scores.com/blog/feed/">Music Scores Blog</a>
<a href="https://www.nioc.eu/rss">Nioc Photos</a>
<a href="https://roaring.earth/feed/">Roaring Earth</a>
<a href="https://www.rockandice.com/feed/">Rock and Ice Magazine</a>
<a href="https://sacred-texts.com/rss/new.xml">ISTA - Internet Sacred Text Archive</a>
<a href="https://zenfolio.com/feed">Ron Reyes Photography</a>
</div>
<div class="category">
<div>Blogroll</div>
<a href="https://nerdy.dev/rss.xml">Adam Argyle</a>
<a href="https://arantius.com/feed.rss">arantius.com</a>
<a href="https://nu.federati.net/api/statuses/user_timeline/16.as">GeniusMusing (@geniusmusing)</a>
<a href="https://nu.federati.net/api/statuses/user_timeline/2.as">LinuxWalt (@lnxw48a1)</a>
<a href="https://problogger.com/feed/">ProBlogger</a>
<a href="https://singpolyma.net/feed/action_stream/?full">Stephen Paul Weber</a>
<a href="http://thedarnedestthing.com/atom.xml">the darnedest thing</a>
<a href="https://geniusmusing.com/feed/rss">The Random Thoughts of GeniusMusing</a>
<a href="https://unixsheikh.com/feed.rss">unixsheikh.com</a>
<a href="https://willnorris.com/atom.xml">willnorris.com</a>
</div>
<div class="category">
<div>Comic</div>
<a href="https://abstrusegoose.com/atomfeed.xml">Abstruse Goose</a>
<a href="https://www.basicinstructions.net/basic-instructions?format=rss">Basic Instructions</a>
<a href="https://dieselsweeties.com/ds-unifeed.xml">Diesel Sweeties</a>
<a href="https://pbfcomics.com/feed/">The Perry Bible Fellowship</a>
<a href="https://toothpastefordinner.com/rss/rss.php">Toothpaste For Dinner</a>
<a href="https://revive.thecomicseries.com/rss/">Revive</a>
<a href="https://xkcd.com/atom.xml">xkcd</a>
</div>
<div class="category">
<div>Cybersecurity, IT & Privacy</div>
<a href="https://www.bleepingcomputer.com/feed/">Bleeping Computer</a>
<a href="https://www.comparitech.com/feed/">Comparitech</a>
<a href="https://cyberscoop.com/feed/">CyberScoop</a>
<a href="https://dataoverhaulers.com/feed/">Data Overhaulers</a>
<a href="https://decrypt.fail/feed/">decrypt[.]fail</a>
<a href="https://www.hackread.com/feed/">HackRead</a>
<a href="https://nakedsecurity.sophos.com/feed/">Naked Security</a>
<a href="https://newatlas.com/index.rss">New Atlas</a>
<a href="https://reclaimthenet.org/feed/">Reclaim The Net</a>
<a href="https://restoreprivacy.com/feed/">Restore Privacy</a>
<a href="https://fosstodon.org/@RTP.rss">(RTP) Privacy and Tech Tips</a>
<a href="https://www.schneier.com/feed/">Schneier on Security</a>
<a href="https://securityintelligence.com/feed/">Security Intelligence</a>
<a href="https://takebackourtech.org/rss/">Take Back Our Tech</a>
<a href="https://torrentfreak.com/feed/">TorrentFreak</a>
<a href="https://venturebeat.com/feed/">VentureBeat</a>
</div>
<div class="category">
<div>DIY (3D Modeling & Printing, Architecture, Crafting and Electronics)</div>
<a href="http://dangerousprototypes.com/blog/feed/">Dangerous Prototypes</a>
<a href="https://designoptimal.com/feed/">DesignOptimal</a>
<a href="https://www.elementalchile.cl/en/feed/">Elemental</a>
<a href="https://www.familyhandyman.com/feed/">Family Handyman</a>
<a href="https://mansfield-devine.com/speculatrix/feed/">Machina Speculatrix</a>
<a href="http://moderntoil.com/?feed=rss2">Modern Toil</a>
<a href="https://n-o-d-e.net/rss/rss.xml">N O D E</a>
<a href="https://www.open-electronics.org/feed/atom/">Open Electronics</a>
<a href="https://www.opensourceecology.org/feed/">Open Source Ecology</a>
<a href="https://tpb.party/rss/new/605">Physibles (TPB)</a>
<a href="https://smartbuilds.io/feed/">SmartBuilds</a>
<a href="http://yorik.uncreated.net/feed">Yorik's blog</a>
</div>
<div class="category">
<div>Family, Leisure & Travel</div>
<a href="https://www.baldandbeards.com/feed/">Bald & Beards</a>
<a href="https://blastaloud.com/feed/atom/">BlastAloud</a>
<a href="https://dailyurbanista.com/feed/atom/">Daily Urbanista</a>
<a href="https://divinelifestyle.com/feed/">Divine Lifestyle</a>
<a href="https://expertvagabond.com/feed/">Expert Vagabond</a>
<a href="https://www.girlschase.com/rss.xml">Girls Chase</a>
<a href="https://latest-fashion-tips.com/feed/">Latest Fashion Tips</a>
<a href="https://www.mom-on-a-mission.blog/all-posts?format=rss">Mom on a Mission</a>
<a href="https://rebelliousdevelopment.com/feed">Rebellious Development</a>
<a href="https://www.artofmanliness.com/feed/">The Art of Manliness</a>
<a href="https://thebaldbrothers.com/feed/">The Bald Brothers</a>
<a href="https://theeverygirl.com/feed/">The Everygirl</a>
<a href="https://thefrugalgirls.com/feed">The Frugal Girls</a>
</div>
<div class="category">
<div>Government, Politics & World</div>
<a href="https://www.blacklistednews.com/rss.php">BlackListed News</a>
<a href="https://www.cryptogon.com/?feed=atom">cryptogon</a>
<a href="https://mastodon.social/@Cryptome.rss">Cryptome</a>
<a href="https://fakeologist.com/feed/">Fakeologist</a>
<a href="https://www.elegislation.gov.hk/verified-chapters!en.rss.xml">List of Verified Legislation (Hong Kong e-Legislation)</a>
<a href="https://off-guardian.org/feed/">OffGuardian</a>
<a href="https://www.presstv.ir/rss.xml">Press TV</a>
<a href="https://www.rt.com/rss/">RT (Russia Today)</a>
<a href="https://seymourhersh.substack.com/feed">Seymour Hersh</a>
<a href="https://strategicinvestment.com/page/str/rssfeed.xml">Strategic Investment</a>
<a href="https://summit.news/feed/">Summit News</a>
<a href="https://www.thegatewaypundit.com/feed/">The Gateway Pundit</a>
<a href="http://themostimportantnews.com/feed">The Most Important News</a>
<a href="https://unlimitedhangout.com/feed/">Unlimited Hangout</a>
</div>
<div class="category">
<div>Health, Nutrition & Recipes</div>
<a href="https://www.101cookbooks.com/feed">101 Cookbooks</a>
<a href="https://www.asweetpeachef.com/feed/">A Sweet Pea Chef</a>
<a href="https://www.annalenashearthbeat.com/feed/">Annalena's Heart(h)beat</a>
<a href="https://askannamoseley.com/feed/">Ask Anna</a>
<a href="https://cooknourishbliss.com/feed/">Cook Nourish Bliss</a>
<a href="https://www.easycookingwithmolly.com/feed/">Easy Cooking with Molly</a>
<a href="http://www.easypeasyjapanesey.com/blogeasypeasyjapanesey?format=rss">Easy Peasy Japanesey</a>
<a href="http://foodly.com/feed/">Foodly</a>
<a href="https://freezedryguy.com/feed/">Freeze Dry Guy</a>
<a href="https://www.healthyandnaturalworld.com/feed/">Healthy and Natural World</a>
<a href="https://www.jamieoliver.com/feed/">Jamie Oliver</a>
<a href="https://juicing-for-health.com/feed">Juicing for Health</a>
<a href="https://www.loveandlemons.com/feed/">Love & Lemons</a>
<a href="https://nutritionaustralia.org/category/recipes/feed/">Nutrition Australia</a>
<a href="https://pinchofyum.com/feed">Pinch of Yum</a>
<a href="https://plantbasedwithamy.com/feed/">Plant Based with Amy</a>
<a href="https://punchdrink.com/feed/">PUNCH</a>
<a href="https://recipeswitholiveoil.com/feed/">Recipes With Olive Oil</a>
<a href="https://www.sheknows.com/food-and-recipes/feed/">SheKnows</a>
<a href="https://steptohealth.com/feed/">Step To Health</a>
<a href="https://thegreenloot.com/feed/">The Green Loot</a>
<a href="https://theprettybee.com/feed/">The Pretty Bee</a>
<a href="https://traditionalcookingschool.com/feed/">Traditional Cooking School</a>
<a href="https://wonderfulcook.com/feed/">Wonderful Cook</a>
</div>
<div class="category">
<div>Music, Scores & Sound</div>
<a href="https://320kbpshouse.net/feed">320KBPSHOUSE</a>
<a href="https://www.free-scores.com/rss/fluxrss-uk.xml">Free-scores.com</a>
<a href="https://www.frostclick.com/wp/index.php/feed/">FrostClick</a>
<a href="https://intmusic.net/feed">IntMusic</a>
<a href="https://itopmusicx.com/feed/">iTOPMUSICX</a>
<a href="https://losslessclub.com/atom.php">LosslessClub</a>
<a href="https://nfodb.ru/rss.php">MP3 NFO Database</a>
<a href="https://tpb.party/rss/new/101">Music (TPB)</a>
<a href="https://musicrider.org/feed/">Music Rider</a>
<a href="https://losslessalbums.club/rss.xml">New lossless albums</a>
<a href="https://rss.ngfiles.com/latestsubmissions.xml">Newgrounds</a>
<a href="https://rss.ngfiles.com/weeklyaudiotop5.xml">Newgrounds (Weekly Top 5)</a>
<a href="https://phish.in/feeds/rss">Phish.in</a>
<a href="https://secondhandsongs.com/rss/new.xml">Second Hand Songs</a>
</div>
<div class="category">
<div>Podcasts & Radio</div>
<a href="https://files.manager-tools.com/files/public/feeds/career_tools_podcasts.xml">Career Tools</a>
<a href="https://fakeologist.com/blog/category/audio/fakeologistshow/feed/">Fakeologist Show</a>
<a href="http://hackerpublicradio.org/hpr_spx_rss.php">Hacker Public Radio</a>
<a href="https://www.iceagefarmer.com/feed/">Ice Age Farmer</a>
<a href="https://jameshfetzer.org/feed/">James H. Fetzer</a>
<a href="http://larkenrose.com/?format=feed">Larken Rose</a>
<a href="https://files.manager-tools.com/files/public/feeds/manager-tools-podcasts.xml">Manager Tools</a>
<a href="https://mediamonarchy.com/feed/podcast/">Media Monarchy</a>
<a href="https://feed.podbean.com/ediviney/feed.xml">Midwest Vegan Radio</a>
<a href="http://www.opensourcetruth.com/feed/">Open Source Truth</a>
<a href="https://optoutpod.com/index.xml">Opt Out</a>
<a href="https://www.pine64.org/feed/mp3/">PineTalk</a>
<a href="https://cast.postmarketos.org/feed.rss">postmarketOS</a>
<a href="https://redice.tv/rss/radio-3fourteen">Radio 3Fourteen</a>
<a href="https://www.reallibertymedia.com/category/podcasts/feed/?redirect=no">Real Liberty Media</a>
<a href="https://redice.tv/rss/red-ice-radio">Red Ice Radio</a>
<a href="http://revolutionradio.org/feed/">Revolution Radio</a>
<a href="https://feed.podbean.com/rightonradio/feed.xml">Right on Radio</a>
<a href="https://roaring.earth/category/podcast/feed/">Roaring Earth</a>
<a href="https://speakfreeradio.com/feed/">Speak Free Radio</a>
<a href="https://www.corbettreport.com/feed/">The Corbett Report</a>
<a href="https://www.thehighersidechats.com/feed/">The Higherside Chats</a>
</div>
<div class="category">
<div>Shopping (Product Reviews)</div>
<a href="https://www.geartaker.com/feed/">Gear Taker</a>
<a href="https://liliputing.com/feed/">Liliputing</a>
<a href="https://www.megabites.com.ph/feed/">MegaBites</a>
<a href="https://www.sheknows.com/feed/">SheKnows</a>
</div>
<div class="category">
<div>Social Action (Activism)</div>
<a href="https://campaignforliberty.org/feed/">Campaign for Liberty</a>
<a href="https://fluoridealert.org/feed/">Fluoride Action Network</a>
<a href="https://www.geoengineeringwatch.org/feed/atom/">Geoengineering Watch</a>
<a href="https://stop5g.cz/us/feed/">Stop 5G</a>
</div>
<div class="category">
<div>Software, Guides & Technology</div>
<a href="https://www.comparitech.com/feed/">Comparitech</a>
<a href="https://ddos-guard.net/rss">DDoS-GUARD</a>
<a href="https://www.dedoimedo.com/rss_feed.xml">Dedoimedo</a>
<a href="https://distrowatch.com/news/dw.xml">DistroWatch</a>
<a href="https://www.evilsocket.net/atom.xml">evilsocket</a>
<a href="https://www.gamingonlinux.com/article_rss.php">GamingOnLinux</a>
<a href="https://www.ghacks.net/feed/">gHacks</a>
<a href="https://guides.lw1.at/index.xml">guides.lw1.at</a>
<a href="https://i2p.rocks/blog/feeds/all.atom.xml">i2p.rocks</a>
<a href="https://planet.jabber.org/atom.xml">Jabber World</a>
<a href="https://linuxgameconsortium.com/feed/">Linux Game Consortium</a>
<a href="https://linmob.net/feed.xml">LINux on MOBile</a>
<a href="https://www.mfitzp.com/feeds/all.atom.xml">Martin Fitzpatrick
</a>
<a href="https://rfidresearchgroup.com/feed/">RFID Research Group</a>
<a href="https://singpolyma.net/feed/">Singpolyma</a>
<a href="https://sourceforge.net/blog/feed/">SourceForge Community Blog</a>
<a href="https://tuxphones.com/rss/">TuxPhones</a>
</div>
<div class="category">
<div>Software Project Updates</div>
<a href="https://artixlinux.org/feed.php">Artix Linux</a>
<a href="https://news.agpt.co/feed/">Auto-GPT</a>
<a href="https://www.falkon.org/atom.xml">Falkon Browser</a>
<a href="https://f-droid.org/feed.xml">F-Droid Store</a>
<a href="https://sourceforge.net/p/freedos/news/feed.rss">FreeDOS</a>
<a href="https://blog.funkwhale.audio/feeds/all.atom.xml">Funkwhale</a>
<a href="https://blogs.gnome.org/shell-dev/">GNOME Shell & Mutter</a>
<a href="https://blog.gtk.org/feed/">GTK Development Blog</a>
<a href="https://leafletjs.com/atom.xml">Leaflet Dev Blog</a>
<a href="https://libreboot.org/feed.xml">Libreboot</a>
<a href="https://mobile.nixos.org/index.xml">Mobile NixOS</a>
<a href="https://blogs.gnome.org/thaller/feed/">NetworkManager</a>
<a href="https://otter-browser.org/feed/">Otter Browser</a>
<a href="https://obsproject.com/blog/rss">Open Broadcaster Software</a>
<a href="https://joinpeertube.org/rss-en.xml">PeerTube</a>
<a href="https://www.pine64.org/feed/">PINE64</a>
<a href="https://postmarketos.org/blog/feed.atom">postmarketOS</a>
<a href="https://plausible.io/blog/feed.xml">Plausible Analytics</a>
<a href="https://www.qemu.org/feed.xml">QEMU</a>
<a href="https://reactos.org/index.xml">ReactOS</a>
<a href="https://blog.replicant.us/feed/">Replicant</a>
<a href="https://servo.org/blog/feed.xml">Servo</a>
<a href="https://spidermonkey.dev/feed.xml">SpiderMonkey</a>
<a href="https://translatelocally.com/rss/">Translate Locally</a>
<a href="https://typo3.org/rss">TYPO3</a>
<a href="https://ubports.com/blog/ubports-news-1/feed">UBports</a>
<a href="https://www.uzbl.org/atom.xml">Uzbl Browser</a>
</div>
<div class="category">
<div>Standards & Protocols</div>
<a href="https://gemini.circumlunar.space/news/atom.xml">Gemini Project</a>
<a href="https://gopher.zone/index.xml">Highway to the Gopher Zone</a>
<a href="https://geti2p.net/en/feed/blog/atom">I2P Blog</a>
<a href="https://blog.ipfs.io/index.xml">IPFS Blog & News</a>
<a href="https://oxen.io/feed/atom">Oxen (Session & Lokinet)</a>
<a href="https://blog.torproject.org/feed.xml">Tor Project</a>
<a href="https://www.w3.org/blog/news/feed/atom">W3C</a>
<a href="https://xmpp.org/feeds/all.atom.xml">XMPP Blog</a>
<a href="https://yggdrasil-network.github.io/feed.xml">Yggdrasil Network</a>
</div>
<div class="category">
<div>Syndication & XML</div>
<a href="https://www.dublincore.org/index.xml">Dublin Core</a>
<a href="https://www.jsonfeed.org/feed.xml">JSON Feed</a>
<a href="https://microformats.org/feed">Microformats</a>
<a href="https://openrss.org/rss">Open RSS</a>
<a href="http://opml.org/?format=opml">OPML</a>
<a href="http://feeds.rssboard.org/rssboard">RSS Advisory Board</a>
<a href="https://blog.saxonica.com/atom.xml">Saxonica</a>
<a href="http://docs.subtome.com/feed.xml">SubToMe</a>
<a href="https://sword.cottagelabs.com/feed/">SWORD</a>
<a href="http://rss.userland.com/xml/rss.xml">UserLand RSS Central</a>
<a href="http://xml.coverpages.org/covernews.xml">XML Cover Pages</a>
<a href="https://www.xml.com/feed/all/">XML.com</a>
</div>
<div class="category">
<div>Torrents</div>
<a href="https://bangumi.moe/rss/latest">Bangumi Moe</a>
<a href="https://eztv.re/ezrss.xml">EZTV</a>
<a href="https://www.limetorrents.lol/rss/">Lime Torrents</a>
<a href="https://nyaa.si/?page=rss">Nyaa</a>
<a href="http://rutor.info/rss.php">RUTOR</a>
<a href="https://tpb.party/rss">The Pirate Bay</a>
<a href="https://www.torrentdownload.info/feed_latest">Torrent Download</a>
</div>
</div>
<div><big>💿 <b>Recommended feed readers</b></big></div>
<div class="content" id="software">
<div>This is a list of mobile apps, desktop software and online services for you to choose from; this list includes news readers, podcast managers, torrent clients and web browsers which support web feeds.</div>
<div id="filter">
<span class="filter" id="torrent">BitTorrent</span>
<span class="filter" id="news">News</span>
<span class="filter" id="music">Podcast</span>
<span class="filter" id="web">Web Browser</span>
</div>
<div class="category">
<div>Desktop</div>
<div class="subcategory">
<div>Linux</div>
<a class="news" href="https://apps.kde.org/akregator/">Akregator</a>
<a class="music" href="https://amarok.kde.org/">Amarok</a>
<a class="web" href="https://brave.com/">Brave</a>
<a class="torrent" href="https://deluge-torrent.org/">Deluge</a>
<a class="news" href="https://github.com/jeena/FeedTheMonkey">Feed The Monkey</a>
<a class="news recom" href="https://leechcraft.org/">LeechCraft</a>
<a class="news recom" href="https://lzone.de/liferea/">Liferea</a>
<a class="news recom" href="https://gitlab.com/news-flash/news_flash_gtk/">NewsFlash</a>
<a class="web" href="https://otter-browser.org/">Otter Browser</a>
<a class="torrent" href="https://www.qbittorrent.org/">qBittorrent</a>
<a class="news" href="https://quiterss.org/">QuiteRSS</a>
<a class="news" href="https://github.com/martinrotter/rssguard">RSS Guard</a>
<a class="news" href="http://www.rssowl.org/">RSSOwl</a>
<a class="music" href="https://wiki.gnome.org/Apps/Rhythmbox">Rhythmbox</a>
<a class="music" href="https://strawberrymusicplayer.org/">Strawberry Music Player</a>
<a class="news" href="https://www.open-tickr.net/">TICKR</a>
<a class="torrent recom" href="https://www.tribler.org/">Tribler</a>
<a class="web" href="https://vivaldi.com/features/feed-reader/">Vivaldi</a>
</div>
<div class="subcategory">
<div>macOS</div>
<a class="music" href="https://amarok.kde.org/">Amarok</a>
<a class="web" href="https://brave.com/">Brave</a>
<a class="torrent" href="https://deluge-torrent.org/">Deluge</a>
<a class="news recom" href="https://leechcraft.org/">LeechCraft</a>
<a class="news recom" href="https://netnewswire.com/">NetNewsWire</a>
<a class="web" href="https://otter-browser.org/">Otter Browser</a>
<a class="torrent" href="https://www.qbittorrent.org/">qBittorrent</a>
<a class="news" href="https://quiterss.org/">QuiteRSS</a>
<a class="news" href="https://github.com/martinrotter/rssguard">RSS Guard</a>
<a class="news" href="http://www.rssowl.org/">RSSOwl</a>
<a class="music" href="https://strawberrymusicplayer.org/">Strawberry Music Player</a>
<a class="torrent recom" href="https://www.tribler.org/">Tribler</a>
<a class="web" href="https://vivaldi.com/features/feed-reader/">Vivaldi</a>
</div>
<div class="subcategory">
<div>Windows</div>
<a class="music" href="https://amarok.kde.org/">Amarok</a>
<a class="web" href="https://brave.com/">Brave</a>
<a class="torrent" href="https://deluge-torrent.org/">Deluge</a>
<a class="web" href="http://kmeleonbrowser.org/">K-Meleon</a>
<a class="news recom" href="https://leechcraft.org/">LeechCraft</a>
<a class="web" href="https://otter-browser.org/">Otter Browser</a>
<a class="torrent" href="https://www.qbittorrent.org/">qBittorrent</a>
<a class="news" href="https://quiterss.org/">QuiteRSS</a>
<a class="news" href="http://rssbandit.org/">RSS Bandit</a>
<a class="news" href="https://github.com/martinrotter/rssguard">RSS Guard</a>
<a class="news" href="http://www.rssowl.org/">RSSOwl</a>
<a class="news" href="http://sharpreader.net/">SharpReader</a>
<a class="music" href="https://strawberrymusicplayer.org/">Strawberry Music Player</a>
<a class="torrent recom" href="https://www.tribler.org/">Tribler</a>
<a class="web" href="https://vivaldi.com/features/feed-reader/">Vivaldi</a>
</div>
</div>
<div class="category">
<div>Mobile</div>
<div class="subcategory">
<div>Android</div>
<a class="web" href="https://brave.com/">Brave</a>
<a class="news" href="https://f-droid.org/en/packages/com.nononsenseapps.feeder/">Feeder</a>
<a class="torrent" href="https://f-droid.org/en/packages/org.proninyaroslav.libretorrent/">LibreTorrent</a>
<a class="music" href="https://f-droid.org/en/packages/io.gitlab.listentogether">ListenTogether</a>
<a class="news" href="https://f-droid.org/en/packages/co.appreactor.news/">News</a>
<a class="news" href="https://f-droid.org/en/packages/com.nunti/">Nunti</a>
<a class="music" href="https://f-droid.org/en/packages/com.podverse.fdroid/">Podverse</a>
<a class="news" href="https://f-droid.org/en/packages/me.ash.reader/">Read You</a>
<a class="news recom" href="https://f-droid.org/en/packages/com.aerotoad.thud/">Thud</a>
<a class="web" href="https://vivaldi.com/features/feed-reader/">Vivaldi</a>
</div>
<div class="subcategory">
<div>GerdaOS / KaiOS</div>
<a class="news" href="https://store.bananahackers.net/#feedolin">feedolin</a>
<a class="music" href="https://store.bananahackers.net/#FoxCastLite">FoxCast Lite</a>
<a class="music" href="https://store.bananahackers.net/#Mica">Mica</a>
<a class="music" href="https://store.bananahackers.net/#PodKast">PodKast</a>
<a class="music" href="https://store.bananahackers.net/#podlp">PodLP</a>
<a class="news" href="https://store.bananahackers.net/#n4no.com.rss-reader">RSS Reader</a>
</div>
<div class="subcategory">
<div>iOS</div>
<a class="web" href="https://brave.com/">Brave</a>
<a class="news" href="https://netnewswire.com/">NetNewsWire</a>
<a class="music" href="https://github.com/guumeyer/Podcast">Podcast</a>
<a class="music" href="https://github.com/rafaelclaycon/PodcastApp">PodcastApp</a>
</div>
<div class="subcategory">
<div>postmarketOS</div>
<a class="news" href="https://apps.kde.org/alligator/">Alligator</a>
<a class="news" href="https://gfeeds.gabmus.org/">Feeds</a>
</div>
<div class="subcategory">
<div>Sailfish OS</div>
<a class="news" href="https://github.com/mkiol/kaktus">Kaktus</a>
<a class="news" href="https://github.com/donaggio/harbour-feedhaven">Feed Haven</a>
<a class="news" href="http://gitlab.unique-conception.org/apps-4-sailfish/feed-me">Feed Me</a>
<a class="news" href="https://github.com/walokra/haikala">Haikala</a>
<a class="news" href="https://github.com/pycage/tidings">Tidings</a>
</div>
<div class="subcategory">
<div>Tizen</div>
<a class="news" href="https://github.com/CESARBR/tizenreader">Tizen Reader</a>
</div>
<div class="subcategory">
<div>Ubuntu Touch</div>
<a class="music" href="https://open-store.io/app/com.mikeasoft.podbird">Podbird</a>
<a class="music" href="https://open-store.io/app/soy.iko.podphoenix">Podphoenix</a>
<a class="news" href="https://open-store.io/app/rssreader.florisluiten">RSSreader</a>
<a class="news" href="https://open-store.io/app/simplestrss.kazord">SimplestRSS</a>
<a class="news" href="https://open-store.io/app/darkeye.ursses">uRsses</a>
</div>
</div>
<div class="category">
<div>Terminal</div>
<a class="news" href="https://codezen.org/canto-ng/">Canto</a>
<a class="news" href="https://newsboat.org/">Newsboat</a>
<a class="news" href="https://sr.ht/~ghost08/photon/">Photon</a>
<a class="news" href="https://codemadness.org/sfeed_curses-ui.html">Sfeed</a>
</div>
<div class="category">
<div>Web (self hosted)</div>
<a class="news" href="https://feedbin.com/">Feedbin</a>
<a class="news" href="http://feedonfeeds.com/">Feeds on Feeds</a>
<a class="news" href="https://freshrss.org/">FreshRSS</a>
<a class="news" href="https://miniflux.app/">Miniflux</a>
<a class="news" href="https://offog.org/code/rawdog/">rawdog</a>
<a class="news" href="https://github.com/acavalin/rrss">RRSS</a>
<a class="news" href="https://tt-rss.org/">Tiny Tiny RSS</a>
</div>
<div class="category">
<div>Web (service)</div>
<a class="news" href="https://feedbin.com/">Feedbin</a>
<a class="news" href="https://feeder.co/">Feeder</a>
<a class="news" href="https://feedly.com/">Feedly</a>
<a class="news" href="https://goodnews.click/">Good News</a>
<a class="news" href="https://www.inoreader.com/">Inoreader</a>
<a class="news" href="http://www.netvibes.com/en">Netvibes</a>
<a class="news" href="https://newsblur.com/">NewsBlur</a>
<a class="news" href="https://www.reedah.com/">Reedah</a>
<a class="news" href="https://theoldreader.com/">The Old Reader</a>
</div>
</div>
<div><big>📢 <b>Speak your mind!</b></big></div>
<div class="content">
<div>Do you want to start a syndication-enabled podcast? The following blog and podcast services provide access to syndication.</div>
<div id="services">
<div class="category">
<div>Decentralized (ActivityPub)</div>
<a href="https://diaspora.fediverse.observer/list">diaspora*</a>
<a class="recom" href="https://funkwhale.fediverse.observer/list">Funkwhale</a>
<a href="https://instances.social/">Mastodon</a>
<a class="recom" href="https://instances.joinpeertube.org/">PeerTube</a>
<div><i>Decentralized services are <u>very and mostly</u> encouraged. Use one account to communicate with all services and platforms.</i></div>
</div>
<div class="category">
<div>No-charge services</div>
<a href="https://castos.com/">Castos</a>
<a href="https://feedpress.com/">FeedPress</a>
<a class="recom" href="https://noblogs.org/">NoBlogs</a>
<a href="https://podbean.com/">PodBean</a>
<a href="https://rawvoice.com/">RawVoice</a>
<a href="https://wordpress.com/">WordPress</a>
<div><i>Free of monetary charge services are generally <u>not</u> encouraged.</i></div>
</div>
<div class="category">
<div>Pay services</div>
<a href="https://www.cloudaccess.net/">CloudAccess</a>
<a href="https://wordpress.com/">Hostinger</a>
<a class="recom" href="https://micro.blog/">Micro.blog</a>
<a class="recom" href="https://monocles.eu/more/">monocles</a>
<a href="https://www.rochen.com/">Rochen</a>
<a href="https://www.strato.de/">STRATO</a>
</div>
<div class="category">
<div>Self host</div>
<a href=""></a>
</div>
</div>
<div>🔖 Recommended service provider.</div>
<div>You are, however, encouraged to host <u>your own</u> server connected to the I2P network.</div>
<div>Best ways to make your files available are via BitTorrent and IPFS. PeerTube supports BitTorrent by default.</div>
</div>
<div><big>📚 <b>Learn more about syndication</b></big></div>
<div class="content" id="learn">
<div>This is a short history and reference guide to web feeds.</div>
<div><b>Newspaper (2021)</b></div>
<div>Newspaper is a Userscript that renders web feeds to web pages with the sole use of Javascript. <a href="https://openuserjs.org/scripts/sjehuda/Newspaper">Continue reading…</a></div>
<div><b>JSON Feed (2017)</b></div>
<div>The JSON Feed format is a pragmatic syndication format, like RSS and Atom, but with one big difference: it’s JSON instead of XML. <a href="https://www.jsonfeed.org/version/1.1/">Continue reading…</a></div>
<div><b>StreamBurner (2013)</b></div>
<div>StreamBurner is a set of XSLT stylesheets that allow the conversion of web feeds into printable and readable web pages. <a href="https://gitgud.io/sjehuda/streamburner">Continue reading…</a></div>
<div><b>Activity Streams (2008)</b></div>
<div>An extension to the Atom feed format to express what people are doing around web. The stream in ActivityStreams is a feed of related activities for a given person or social object.
<a href="https://wiki.activitystrea.ms/w/page/1359261/FrontPage">Continue reading…</a></div>
<div><b>h-entry and hAtom 0.1 (2006)</b></div>
<div>h-entry is a simple, open format for episodic or datestamped content on the web. h-entry is often used with content intended to be syndicated, e.g. blog posts. h-entry is one of several open microformat standards suitable for embedding data in HTML. <a href="http://microformats.org/wiki/h-entry">Continue reading…</a></div>
<div><b>Atom and AtomPub (2003)</b></div>
<div>Atom is the name of an XML-based Web content and metadata syndication format, and an application-level protocol for publishing and editing Web resources belonging to periodically updated websites. <a href="https://web.archive.org/web/20120105062737if_/http://www.atomenabled.org:80/developers/syndication/#whatIsAtom">Continue reading…</a></div>
<div><b>OPML (2000)</b></div>
<div>OPML is a text-based format designed to store and exchange outlines with attributes. It has been around since the early 2000s, and is widely used in the RSS world to exchange subscription lists. It is an established standard for interop among outliners. <a href="http://opml.org/spec2.opml">Continue reading…</a></div>
<div><b>RSS (1999)</b></div>
<div>RSS is a Web content syndication format. Its name is an acronym for Really Simple Syndication. RSS is a dialect of XML. <a href="https://www.rssboard.org/rss-specification#whatIsRss">Continue reading…</a></div>
<div><b>XSL and XSLT (1998)</b></div>
<div>XSL (Extensible Stylesheet Language) is designed for use as part of XSL, which is a stylesheet language for XML that has document manipulation capabilities beyond styling. <a href="http://xml.coverpages.org/xsl.html">Continue reading…</a></div>
</div>
<div><big>✒️ <b>An appeal from the author</b></big></div>
<div class="content" id="plea">
<div>Greetings,</div>
<div>My name is Schimon Jehudah, I’m an Attorney at Law and Crypto Analyst, and author of StreamBurner News Reader (also “Newspaper” Userscript).</div>
<div>For many years, the technology generally known as RSS has been serving me and the companies I’ve been working with (financial houses and law firms) in both corporate and individual life.</div>
<div>Since its inception, advertising and media companies have been working together to eliminate this technology; mostly, by paying off software companies (namely, web browser makers) to neglect and even remove support for this technology.</div>
<div>This vital technology, which exists for over 20 years, is being oppressed for over a decade, particularly by advertising companies, news publishers, western governments and data mining websites (aka “social networks”) who want to turn more control of data flow to them and much less control to individuals, like you.</div>
<div>I advise you to share this technology with your friends and any acquaintance of yours, while at the same time, boycott media and news outlets that refuse to provide web feeds.</div>
<div><b><i>I <u>don’t</u> ask you for financial support nor monetary donations, I only ask YOU to share this technology with people you know.</i></b></div>
<div>Respectfully,</div>
<div>Schimon Jehudah, Adv.</div>
</div>
<div><big>📜 <b>Postscript</b></big></div>
<div class="content" id="postscript">
<div>This program was made by a person who is a Lawyer and has no “formal” technical trainings nor technical qualifications, so-called, neither in CSS, ECMA (JavaScript), HTML nor XSLT technologies.</div>
<div>Since a Lawyer can make this program from scratch in 28 the days (4 hours each day), then ask yourselves “Why do web browser makers look for excuses to actively ignore this important web technology, if not because of payoffs?”</div>
</div>
<div>
StreamBurner project and source code:
<a href="https://gitgud.io/sjehuda/streamburner">https://gitgud.io/sjehuda/streamburner</a>
</div>
</div>`,
htmlBar =
`<div id="links-bar">
<a id="follow" title="Subscribe to get the latest updates and news">Follow</a>
<a class="cursor-pointer" id="subtome" title="Subscribe via SubToMe">SubToMe</a>
<a id="donate-newspaper" title="Learn how you can support">Support</a>
<a class="cursor-help" id="about-newspaper" title="Learn about syndication feed">Help</a>
</div>`,
htmlEmpty =
`<div class="notice no-entry" id="empty-feed">
<div><big><b>This news feed is empty</b></big></div>
<br/>
<div>We advise you to contact the web administrators and Ask them to maintain "Atom" and "JSON" feeds.</div>
<br/>
<!-- div>You may also address them to <a href="https://aboutfeeds.com">aboutfeeds.com</a>.</div>
<br/ -->
<hr/>
<br/>
<div>Below is a contact link with possible emails. Please use it only in case there is no contact address or form on this website.</div>
<br/>
</div>`,
cssFileBar = `
#links-bar {
margin: auto;
outline: .1em solid;
width: 100%;
/* max-width: 70%; */
direction: ltr;
text-align: center;
position: fixed;
top: 0;
left: 0;
right: 0;
color: WhiteSmoke;
background: #555; /* #eee */
/* white-space: nowrap; */
z-index: 1; }
#links-bar > a {
color: WhiteSmoke;
font-style: italic; }
#links-bar > a:nth-child(1) {
/* border-left-style: unset; */
border-right-style: solid;
/* border-radius: 2em; */
color: #555;
background: WhiteSmoke;
font-weight: bold; }`,
cssFileLTR = `
html, body {
padding: 0;
margin: 0;
overflow-x: hidden; }
body {
background: WhiteSmoke;
color: #333;
hyphens: auto; }
#feed {
/*
width: 98%;
margin: auto;
position: relative;
overflow-x: hidden;
*/
margin: 0 -1em 1em -1em;
margin-bottom: 1em;
padding: 1em 1em 0 1em; }
#feed a {
color: #333;
display: inline-block; }
.content * {
max-width: 96%;
object-fit: contain;
height: auto; }
#logo {
display: inline-block;
float: left;
overflow: hidden;
position: relative;
height: 60px;
width: 60px;
margin-right: 9;
padding-top: 12; }
#logo > a > img {
margin: auto;
max-width: 100%;
position: absolute;
width: 5em;
bottom: 0;
right: 0;
left: 0;
top: 0; }
#title { /* TODO tag </title-page> */
font-variant: small-caps;
text-align: center;
font-weight: bold;
font-size: 3em;
margin-bottom: 0;
overflow: hidden;
-webkit-line-clamp: 2;
margin: 0; }
#title .empty:before {
font-variant: small-caps;
content: 'Streamburner News Dashboard';
text-align: center; }
#subtitle {
border-top: 1px solid;
width: 90%;
margin: auto;
overflow: hidden;
-webkit-line-clamp: 2;
white-space: wrap;
text-align: center;
font-variant: all-small-caps;
font-weight: normal;
font-size: 1.5em; }
.container {
display: flex; }
#links-bar {
display: block;
margin: auto;
margin-bottom: 1em;
margin-top: 1em;
width: 90%;
text-align: center;
direction: ltr;
/* font-size: 90%; */ }
#links-bar > a {
padding: 10px;
text-decoration: none;
/* font-size: 70%; */
user-select: none;
outline: none;
min-width: 12%;
padding: 6px;
margin: 6px; }
#links-bar > a:nth-child(1) {
cursor: pointer;
background: lavender; /* honeydew */
border-color: grey;
border-left-style: solid;
border-radius: 2em; /* 40% */
/*
border-top-left-radius: 2em;
border-bottom-left-radius: 2em;
*/ }
.cursor-pointer {
cursor: pointer; }
.cursor-help {
cursor: help; }
#toc {
margin-left: 3%;
padding: 5px; }
#toc:before {
content: 'Latest Headlines';
/* font-size: 76%; */
font-weight: bold; }
#toc > a:first-child {
margin-top: 1em; }
#toc > a {
text-decoration: none;
/* font-size: 66%; */
display: block;
outline: none;
padding: 5px 0;
margin-left: 1%; }
#toc > a:hover {
text-decoration: underline; }
#toc > a:visited {
text-decoration: line-through; }
.about-newspaper { /* overlay */
font-style: initial;
position: fixed;
display: none;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: WhiteSmoke;
background-color: #555;
/* background-color: #ffff9b; */
z-index: 2;
overflow-y: auto;
text-align: left; /* justify */
direction: ltr;
padding: 5%;
line-height: 1.8;
font-size: 110%;
cursor: unset; }
.about-newspaper div {
margin-bottom: 1em; }
.about-newspaper a,
.category > a,
.subcategory > a {
text-decoration: none;
color: WhiteSmoke; }
.about-newspaper a:hover,
.category > a:hover,
.subcategory > a:hover {
text-decoration: underline; }
#services > a:after,
.category > a:after,
.subcategory > a:after {
content: ', '; }
#services > a:last-child:after,
.category > a:last-child:after,
.subcategory > a:last-child:after {
content: '.'; }
#filter {
/* margin-right: auto;
margin-left: auto; */
margin-top: 25px; }
.filter {
font-weight: bold;
outline: none;
user-select: none;
border-bottom: 2px solid WhiteSmoke;
background: WhiteSmoke;
color: #555;
border-radius: 5%;
margin-right: 15px;
padding: 5px;
width: 10%;
cursor: pointer; }
.hide {
display: none; }
.grey {
background: inherit;
color: inherit; }
/*
.recom {
filter: drop-shadow(2px 4px 6px pink); }
*/
.recom:before {
font-size: 80%;
content : '🔖 '; }
.category > div:first-child {
font-size: 110%;
font-weight: bold;
margin-top: 25px; }
.subcategory > div {
font-weight: bold;
/* text-decoration: underline; */ }
#postscript {
font-style: italic; }
/*
#feeds > .category > a:link {
text-decoration: none; }
*/
#feeds > .category > a:before {
font-size: 80%;
content: '🏷️ '; } /* 🧧 🔗 */
#articles {
justify-content: space-between;
max-width: 90%;
margin: 0 auto;
padding: 10px 0; }
#articles > * {
margin: .5em;
white-space: normal;
vertical-align: top;
margin-bottom: 50px; }
.entry {
/*border-bottom: inset;
border-bottom: groove; */
margin-left: auto;
margin-right: auto;
overflow: auto;
line-height: 1.6;
/* font-size: 85%; */
/* overflow-x: hidden; */
max-width: 98%;
/* outline: auto; */
outline: none;
padding: 4px; }
.entry:last-child {
border-bottom: unset; }
.entry:hover {
/* background: #f8f9fa; */
/* outline: none; */ }
.entry > a {
white-space: normal; }
.decor {
/* border-top: inset; */
/* border-top: groove;
width: 30%; */
/* padding-right: 30%;
padding-left: 30%; */
margin-right: 30% !important;
margin-left: 30% !important;
text-align: center;
/* text-decoration: overline; */
user-select: none; }
.decor:after {
/* content: '∽ ✦ ∼' */
content: '· · ✦ · ·'; } /* ✦ ✧ ۞ ⍟ ⍣ ✹ ✸ ✴ ✶ ✵ ✷ */
.title {
cursor: pointer;
display: inline-block;
font-size: 150%;
font-weight: bold;
text-decoration: underline;
overflow-wrap: anywhere;
/* overflow: visible;
text-overflow: ellipsis; */
font-variant: small-caps; }
/*
.title > a {
text-decoration: none; }
.title > a:hover {
text-decoration: underline; }
*/
.geolocation > a {
text-decoration: none;
padding-left: 6px; }
.author {
font-size: 75%;
margin: 0 auto 0 auto; }
.author:before {
content: 'By '; }
.author:after {
content: ' / '; }
.published, .updated {
/* font-size: 75%; */
margin: 0 auto 0 auto;
direction: ltr; }
.content {
margin: 15px auto 15px 1%;
inline-size: 95%;
text-indent: 3px; }
.content[type='text'] {
font-family: monospace; }
code {
color: WhiteSmoke;
background: #555;
overflow: auto;
display: inline-flex;
max-height: 300px;
border-radius: 4px; }
.enclosure {
background: #eee;
border: 1px solid GrayText;
border-radius: 4px;
clear: both;
color: #525c66;
cursor: help;
direction: ltr;
font-size: .8em;
margin: 5px auto 15px 1%;
padding: 15px;
vertical-align: middle;
/* border: 1px solid #aaa; */
border-radius: .5em;
max-width: 100%;
border-left: double;
padding: 1em; }
.enclosure > span:after {
content: ' (Document file) '; }
.enclosure > span[icon]:after {
content: '📄️';
margin: 3px; }
.enclosure > span.executable:after{
content: ' (Executable file) '; }
.enclosure > span[icon='executable']:after {
content: '📦️';
margin: 3px; }
.enclosure > span.image:after {
content: ' (Image file) '; }
.enclosure > span[icon='image']:after {
content: '🖼️';
margin: 3px; }
.enclosure > span.audio:after {
content: ' (Audio file) '; }
.enclosure > span[icon='audio']:after{
content: '🎼️';
margin: 3px; }
.enclosure > span.video:after {
content: ' (Video file) '; }
.enclosure > span[icon='video']:after {
content: '📽️';
margin:3px; }
.notice {
text-align: center;
display: block;
font-size: 130%;
font-weight: lighter;
font-variant-caps: all-small-caps;
color: FireBrick; }
.warning {
display: block;
font-size: 85%; /* 60 */
font-weight: bold;
color: DarkRed; }
.atom1.author:after {
content: 'Atom 1.0 Warning: Element </author> is missing'; }
.atom1.id:after {
content: 'Atom 1.0 Warning: Element </id> is missing'; }
.atom1.link:after {
content: 'Atom 1.0 Warning: Element </link> is missing'; }
.atom1.published:after {
content: 'Atom 1.0 Warning: Element </published> is missing'; }
.atom1.title:after {
content: 'Atom 1.0 Warning: Element </title> is missing'; }
.rss2.description:after {
content: 'RSS 2.0 Warning: Element </description> is missing'; }
.rss2.link:after {
content: 'RSS 2.0 Warning: Element </link> is missing'; }
.rss2.title:after {
content: 'RSS 2.0 Warning: Element </title> is missing'; }
abbr,acronym {
border-bottom: 1px dotted #c30; }
dt {
font-weight: bold; }
#about-toc {
display: grid;
/* border-bottom: inset;
text-align: right;
width: 70%;
margin-left: 30%; */ }
.about-newspaper > .content {
margin-bottom: 3em; }
#info-square {
position: fixed;
margin: auto;
bottom: 0;
right: 0;
left: 0;
/* top: 33px; */
padding: 3px;
color: WhiteSmoke;
background: #555;
/* border-radius: 50px;
width: 50%;
font-size: 70%; */
font-style: italic;
/* justify-content: center; */
align-items: center;
display: flex;
text-overflow: ellipsis;
overflow: hidden;
/* white-space: pre; in case we have html tags */
white-space: nowrap; }
#info-square > * {
color: WhiteSmoke;
margin-left : 0.5em;
margin-right : 0.5em; }
#top-navigation-button {
/* set position */
position : fixed;
bottom : 10px;
right : 20px;
z-index : 1;
/* set appearance */
background : WhiteSmoke;
border : 2px solid #555;
border-radius : 50px;
/* margin : 10px; */
min-width : 30px;
min-height : 30px;
font-size : 20px;
/* opacity : 0.3; */
/* center character */
justify-content : center;
align-items : center;
display : flex;
/* disable selection marks */
outline : none;
user-select : none;
cursor : default; }
#email-link {
text-decoration : overline;
outline : none; }
#feed-info {
font-size : 70%;
margin-bottom : 35px;
text-align : center; }
#feed-banner {
outline: none;
display: table;
margin: auto; }`,
cssFileRTL = `
html, body {
text-align: right; }
#feed {
direction: rtl; }
#logo {
float: right;
margin-left: 9; }
.geolocation > a {
padding-right: 6px; }
.image {
float: right;
margin-left: 40px;
margin-right: auto; }`;
var
cssFileBase;
const gmXmlhttpRequest = typeof GM_xmlhttpRequest === 'function' ? GM_xmlhttpRequest : GM.xmlHttpRequest;
(function checkContentType() {
let myPromise = new Promise(function(myResolve, myReject) {
let request = new XMLHttpRequest();
//request.overrideMimeType('text/plain');
//request.responseType = 'text'; // ms-stream also works but both don't make a difference
request.open('GET', document.documentURI);
//request.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
//request.setRequestHeader('Content-Type', 'text/plain');
request.onload = function() {
if (document.URL.startsWith('file:') ||
request.status == 200) {
myResolve(request);
}
else {
myReject("File not Found");
}
};
request.send();
/* gmXmlhttpRequest({
method: 'GET',
url: document.documentURI,
headers: {
"Content-Type": "text/plain",
"Accept": "text/plain"
},
onprogress: function(request) {
request.responseType = 'text';
},
onload: function(request) {
request.overrideMimeType = 'text/plain';
if (document.URL.startsWith('file:') ||
request.status == 200) {
myResolve(request);
}
else {
myReject("File not Found");
}
},
onerror: function(request) {
myReject('File not Found')
}
}) */
});
myPromise.then(
function(request) {
if (request.response.toLowerCase().includes('<?xml-stylesheet')) {
// Apparently, this program doesn't influence server stylesheet
// This if statement is useful to save CPU and RAM resources.
// NOTE We can remove it using DOMParser.
return; // exit
}
// errorPage is a good idea to promote Falkon
//setTimeout(function(){renderFeed(request.response)}, 1500); // timeout for testing
try {
if (JSON.parse(request.response)) {
let jsonFile = JSON.parse(request.response);
if (jsonFile.version) {
if (jsonFile.version.toLowerCase().includes('jsonfeed.org')) {
pageLoader();
//setTimeout(function(){renderJSONFeed(jsonFile)}, 1500);
renderJSONFeed(jsonFile);
extensionLoader(
jsonFile.feed_url,
jsonFile.items[0].date_published,
// jsonFile.items[0].date_modified,
'JSON Feed'
);
}
} else
if (jsonFile.generator) {
if (jsonFile.generator.toLowerCase().includes('statusnet') || // TODO Case insensitive
jsonFile.generator.toLowerCase().includes('gnu social')) {
pageLoader();
renderActivityStream(jsonFile);
extensionLoader(
null,
jsonFile.items[0].published,
'ActivityStream'
);
}
}
// TODO ActivityStream
// https://nu.federati.net/api/statuses/show/3424682.json
}
} catch {
let domParser = new DOMParser();
let xmlFile = domParser.parseFromString(request.response.trim(), 'text/xml');
if (xmlFile) {
/*
// TODO Configuration to override existing stylesheet
if (override) {
if (xmlFile.firstChild.nodeName == "xml-stylesheet") {
console.log(xmlFile.firstChild)
xmlFile.firstChild.remove();
}
} else {
return; // exit
}
*/
/*
// Remove node of type comment
// Because of this code below
if (xmlFile.childNodes[0] == xmlFile.querySelector('feed')) {
while (xmlFile.firstChild.nodeName == '#comment') {
xmlFile.firstChild.remove(); // xmlFile.childNodes[0]
}
}
*/
/*
// Remove all nodes of type comment
nodeIterator = xmlDoc.createNodeIterator(
xmlDoc, // Starting node, usually the document body
NodeFilter.SHOW_ALL, // NodeFilter to show all node types
null,
false
);
let currentNode;
// Loop through each node in the node iterator
while (currentNode = nodeIterator.nextNode()) {
// Do something with each node
console.log(currentNode.nodeName);
}
*/
// TODO Check by namespace xmlns
// https://web.resource.org/rss/1.0/schema.rdf
// <feed xmlns="http://www.w3.org/2005/Atom">
// xmlFile.getElementsByTagNameNS('http://www.w3.org/2005/Atom','feed')
if (xmlFile.firstElementChild == xmlFile.querySelector('feed')) {
pageLoader();
renderXML(xmlFile, atomRules);
extensionLoader(
// NOTE This && is an if statement
xmlFile.querySelector('feed > link') && xmlFile.querySelector('feed > link').href,
getDateXML(xmlFile, atomRules),
'Atom' // Atom Web Feed 1.0
);
} else
// Netscape RSS 0.91 <!DOCTYPE rss SYSTEM "http://my.netscape.com/publish/formats/rss-0.91.dtd">
// Userland RSS 0.91 <rss version="0.91">
// RSS 0.92 <rss version="0.92">
// RSS 0.93 <rss version="0.93">
// RSS 0.94 <rss version="0.94">
// RSS 2.0 <rss version="2.0">
if (xmlFile.firstElementChild == xmlFile.querySelector('rss')) {
pageLoader();
renderXML(xmlFile, rssRules);
extensionLoader(
// NOTE This && is an if statement
xmlFile.querySelector('channel > link') && xmlFile.querySelector('channel > link').href,
// FIXME
// https://www.elegislation.gov.hk/verified-chapters!en.rss.xml
getDateXML(xmlFile, rssRules),
'RSS' // RSS Web Feed 2.0
);
} else
// TODO Check xmlns
// RSS 0.90 <rdf:RDF xmlns="http://my.netscape.com/rdf/simple/0.9/">
// RSS 1.0 <rdf:RDF xmlns="http://purl.org/rss/1.0/">
// NOTE firstElementChild test page https://web.resource.org/rss/1.0/
// xmlFile.getElementsByTagNameNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'RDF');
if (xmlFile.firstElementChild == xmlFile.getElementsByTagName("rdf:RDF")[0]) {
pageLoader();
renderXML(xmlFile, rdfRules);
extensionLoader(
null,
getDateXML(xmlFile, rdfRules),
'RDF Vocabulary' // RSS Web Feed 1.0
);
}
// TODO
// This appears to be a not usuable Atom feed
// Either make it usable or invalidate it
// https://dbpedia.org/data/Searx.atom
}
}
// Information of request
//console.info(xmlFile);
//console.info(document);
//console.info(`all headers: ${request.getAllResponseHeaders()}`);
//console.info(`content-type header: ${request.getResponseHeader('content-type')}`);
//console.info(`content-type document: ${document.contentType}`);
}
);
})();
/*
Test code (attempting to modify content type):
console.info(document);
request = new XMLHttpRequest();
// "false" is only used for this test
request.open('GET', document.documentURI, false);
request.overrideMimeType('text/plain');
request.setRequestHeader('content-type', 'text/plain');
request.send();
//console.info(request.response);
console.info(`all headers: ${request.getAllResponseHeaders()}`);
console.info(`content-type header:
${request.getResponseHeader('content-type')}`);
console.info(`content-type document: ${document.contentType}`);
*/
/*
(function checkContentType() {
fetch(
document.documentURI,
{
method: 'GET',
headers: {
"Content-Type" : "text/plain",
},
}
)
.then((response) => {
console.info(response.headers.get('content-type'))
//console.info(response.arrayBuffer())
return response.arrayBuffer();
})
.then((data) => {
let decoder = new TextDecoder(document.characterSet);
let text = decoder.decode(data);
domParser = domParser = new DOMParser();
try {
if (JSON.parse(text)) {
jsonFile = JSON.parse(text);
if (jsonFile.version) {
if (jsonFile.version.toLowerCase().includes('jsonfeed.org')) {
renderJSONFeed(jsonFile);
extensionLoader(jsonFile.feed_url); // , jsonFile, 'JSON'
}
} else
if (jsonFile.generator) {
if (jsonFile.generator.toLowerCase().includes('statusnet') || // TODO Case insensitive
jsonFile.generator.toLowerCase().includes('gnu social')) {
renderActivityStream(jsonFile);
extensionLoader(); // null, jsonFile, 'ActivityStream'
}
}
}
} catch {
if (domParser.parseFromString(text, 'text/xml')) {
xmlFile = domParser.parseFromString(text, 'text/xml');
// errorPage is a good idea to promote Falkon
if (xmlFile.querySelector('feed')) {
renderXML(xmlFile, atomRules);
extensionLoader(xmlFile.querySelector('feed > link').href); // , xmlFile, 'Atom'
} else
if (xmlFile.querySelector('rss')) {
renderXML(xmlFile, rssRules);
extensionLoader(xmlFile.querySelector('channel > link').href); // , xmlFile, 'RSS'
}
}
}
});
})();
*/
function renderActivityStream(jsonFile) {
let newDocument = createPage();
newDocument.title = jsonFile.title;
if (jsonFile.language) {
newDocument
.documentElement
.setAttribute('lang', jsonFile.language);
}
let feed = newDocument.createElement('div');
feed.id = 'feed';
let title = newDocument.createElement('div');
if (jsonFile.title) {
title.textContent = jsonFile.title;
} else {
title.textContent = 'Streamburner News Reader';
}
title.id = 'title';
feed.append(title);
let subtitle = newDocument.createElement('div');
if (jsonFile.description) {
subtitle.textContent = jsonFile.description;
} else {
subtitle.textContent = defaultSubtitle;
}
subtitle.id = 'subtitle';
feed.append(subtitle);
let toc = newDocument.createElement('div');
toc.id = 'toc';
feed.append(toc);
let articles = newDocument.createElement('div');
articles.id = 'articles';
feed.append(articles);
if (jsonFile.items.length) {
for (const item of jsonFile.items) {
//for (let i = 0; i < jsonFile.items.length; i++) {
let index = jsonFile.items.indexOf(item) + 1;
let titleToc = newDocument.createElement('a');
// /questions/5002111/how-to-strip-html-tags-from-string-in-javascript
titleToc.textContent =
item
.content
.replace(/(<([^>]+)>)/gi, "")
.substring(0, 50) + '…';
//titleToc.textContent = item.actor.portablecontacts_net.preferredUsername;
titleToc.href = `#newspaper-oujs-${index}`;
toc.append(titleToc);
let entry = newDocument.createElement('div');
entry.className = 'entry';
let link = newDocument.createElement('a');
link.textContent =
item
.actor
.portablecontacts_net.preferredUsername;
link.href = item.url;
//link.id = item.id;
link.id = `newspaper-oujs-${index}`;
title = newDocument.createElement('div');
title.className = 'title';
title.append(link);
entry.append(title);
let date = newDocument.createElement('div');
date.className = 'published';
date.textContent = item.published;
entry.append(date);
if (item.image) {
let image = newDocument.createElement('img');
image.src = item.actor.image;
entry.append(image);
}
let text = newDocument.createElement('div');
text.className = 'content';
text.innerHTML = item.content;
entry.append(text);
articles.append(entry);
if (index > 19) {
break;
}
}
} else {
toc.remove();
articles
.insertAdjacentHTML('beforeend', htmlEmpty);
}
newDocument.body.append(feed);
newDocument = checkContentEmptiness(newDocument);
placeNewDocument(newDocument);
}
function renderJSONFeed(jsonFile) {
let feedMap = {
"title": "title",
"subtitle": "description",
"language" : "language",
"item": [{
"title" : "title",
"url" : ["url", "id"],
"content" : ["content_html", "content_text"],
"image" : "image",
"date" : ["date_published", "date_modified"],
"authors" : ["authors", "author"],
"tags" : "tags",
"language" : "language",
"id" : "id",
}],
"attachments": [{
"url" : "url",
"mime_type" : "mime_type",
"title" : "title",
"size_in_bytes" : "size_in_bytes",
"duration_in_seconds" : "duration_in_seconds"
}],
"homepage": "home_page_url",
"version": "version",
"url": "feed_url"
};
let newDocument = createPage();
/*
newDocument = domParser.parseFromString('<html></html>', 'text/html');
elements = ['html', 'head', 'body'];
//for (const element of elements) {
for (let i = 1; i < elements.length; i++) {
element = newDocument.createElement(elements[i]);
newDocument.documentElement.append(element);
}
*/
newDocument.title = jsonFile.title;
if (jsonFile.language) {
newDocument
.documentElement
.setAttribute('lang', jsonFile.language);
}
let feed = newDocument.createElement('div');
feed.id = 'feed';
let title = newDocument.createElement('div');
if (jsonFile.title) {
title.textContent = jsonFile.title;
} else {
title.textContent = 'Streamburner News Reader';
}
title.id = 'title';
feed.append(title);
let subtitle = newDocument.createElement('div');
if (jsonFile.description) {
subtitle.textContent = jsonFile.description;
} else {
subtitle.textContent = defaultSubtitle;
}
subtitle.id = 'subtitle';
feed.append(subtitle);
let toc = newDocument.createElement('div');
toc.id = 'toc';
feed.append(toc);
let articles = newDocument.createElement('div');
articles.id = 'articles';
feed.append(articles);
/* FIXME
These couple of for-loops don't work
Failing part: jsonFile.cellOfArray
Uncaught (in promise) TypeError: Cannot read property '0' of undefined
tags = ['title', 'description'];
for (const tag of tags) {
element = newDocument.createElement('div');
element.textContent = jsonFile.tag;
element.id = tag;
feed.append(element);
}
elements = ['title', 'description'];
for (let i = 0; i < elements.length; i++) {
element = newDocument.createElement('div');
element.textContent = jsonFile.elements[i];
element.id = elements[i];
feed.append(element);
}
*/
if (jsonFile.items.length) {
for (const item of jsonFile.items) {
//for (let i = 0; i < jsonFile.items.length; i++) {
let index = jsonFile.items.indexOf(item) + 1;
let titleToc = newDocument.createElement('a');
if (item.title) {
titleToc.textContent = item.title;
} else if (item.content_text) {
titleToc.textContent = item.content_text;
} else if (item.content_html) {
titleToc.textContent =
item
.content_html
.replace(/(<([^>]+)>)/gi, "");
} else {
titleToc.textContent = '*** No Title ***';
}
if (titleToc.textContent.length > 50) {
titleToc.textContent =
titleToc
.textContent
.substring(0, 50) + '…';
}
titleToc.href = `#newspaper-oujs-${index}`;
toc.append(titleToc);
let entry = newDocument.createElement('div');
entry.className = 'entry';
let link = newDocument.createElement('a');
if (item.title) {
link.textContent = item.title;
} else {
link.textContent = 'No Title';
}
link.href = item.url;
//link.id = item.id;
link.id = `newspaper-oujs-${index}`;
title = newDocument.createElement('div');
title.className = 'title';
title.append(link);
entry.append(title);
let date = newDocument.createElement('div');
date.className = 'published';
date.textContent = item.date_published; // date_modified
entry.append(date);
// TODO Set it as enclosure unless content is not html (i.e. is text)
if (item.image) {
let image = newDocument.createElement('img');
image.src = item.image;
entry.append(image);
}
let text = newDocument.createElement('div');
text.className = 'content';
text.innerHTML = item.content_html; // content_text
entry.append(text);
articles.append(entry);
if (index > 19) {
break;
}
}
} else {
toc.remove();
articles.insertAdjacentHTML('beforeend', htmlEmpty);
}
newDocument.body.append(feed);
newDocument = checkContentEmptiness(newDocument);
placeNewDocument(newDocument);
}
function renderXML(xmlFile, xmlRules) {
let newDocument = createPage();
newDocument.title =
xmlFile
.querySelector(xmlRules.feedTitlePage)
.textContent;
if (xmlFile.querySelector(xmlRules.feedLanguage)) {
let language;
if (xmlFile.querySelector(xmlRules.feedLanguage).getAttribute('xml:lang')) { // Atom
language =
xmlFile
.querySelector(xmlRules.feedLanguage)
.getAttribute('xml:lang');
} else {
language =
xmlFile
.querySelector(xmlRules.feedLanguage)
.textContent;
}
newDocument.documentElement.setAttribute('lang', language);
}
let feed = newDocument.createElement('div');
feed.id = 'feed';
let title = newDocument.createElement('div');
if (xmlFile.querySelector(xmlRules.feedTitlePage)) {
title.textContent =
xmlFile
.querySelector(xmlRules.feedTitlePage)
.textContent;
} else {
title.textContent = 'Streamburner News Reader';
}
title.id = 'title';
feed.append(title);
let subtitle = newDocument.createElement('div');
if (xmlFile.querySelector(xmlRules.feedSubtitle)) {
subtitle.textContent =
xmlFile
.querySelector(xmlRules.feedSubtitle)
.textContent
.replace(/(<([^>]+)>)/gi, "");
} else {
subtitle.textContent = defaultSubtitle;
}
subtitle.id = 'subtitle';
feed.append(subtitle);
let toc = newDocument.createElement('div');
toc.id = 'toc';
feed.append(toc);
let articles = newDocument.createElement('div');
articles.id = 'articles';
feed.append(articles);
if (xmlFile.querySelectorAll(xmlRules.feedItem).length) {
for (const item of xmlFile.querySelectorAll(xmlRules.feedItem)) {
let index = Array.from(xmlFile.querySelectorAll(xmlRules.feedItem)).indexOf(item) + 1;
let titleToc = newDocument.createElement('a');
// /questions/5002111/how-to-strip-html-tags-from-string-in-javascript
if (item.querySelector(xmlRules.feedItemTitle)) { // FIXME there are two of the same
if (item.querySelector(xmlRules.feedItemTitle).textContent) {
titleToc.textContent =
item
.querySelector(xmlRules.feedItemTitle)
.textContent;
}
titleToc.textContent =
titleToc
.textContent
.replace(/(<([^>]+)>)/gi, "");
} else
if (item.querySelector(xmlRules.feedItemContent)) {
if (item.querySelector(xmlRules.feedItemContent).textContent) {
titleToc.textContent =
item
.querySelector(xmlRules.feedItemContent)
.textContent
.replace(/(<([^>]+)>)/gi, "");
}
} else {
titleToc.textContent = '*** No Title ***';
}
if (titleToc.textContent.length > 50) {
titleToc.textContent =
titleToc
.textContent
.substring(0, 50) + '…';
}
titleToc.href = `#newspaper-oujs-${index}`;
toc.append(titleToc);
let entry = newDocument.createElement('div');
entry.className = 'entry';
let link = newDocument.createElement('a');
link.id = `newspaper-oujs-${index}`;
if (item.querySelector(xmlRules.feedItemTitle)) { // FIXME there are two of the same
if (item.querySelector(xmlRules.feedItemTitle).textContent) {
link.textContent =
item
.querySelector(xmlRules.feedItemTitle)
.textContent
.replace(/(<([^>]+)>)/gi, "");
}
} else {
link.textContent = 'No Title';
}
if (item.querySelector(xmlRules.feedItemLink).textContent.length) {
// rss
link.href =
item
.querySelector(xmlRules.feedItemLink)
.textContent;
} else {
// atom
link.href =
item
.querySelector(xmlRules.feedItemLink)
.getAttribute('href');
}
title = newDocument.createElement('div');
title.className = 'title';
title.append(link);
entry.append(title);
if (item.querySelector(xmlRules.feedItemDate)) {
let date = newDocument.createElement('div');
date.className = 'published';
date.textContent =
item
.querySelector(xmlRules.feedItemDate)
.textContent;
entry.append(date);
}
if (item.querySelector(xmlRules.feedItemContent)) {
let text = newDocument.createElement('div');
text.className = 'content';
text.innerHTML =
item
.querySelector(xmlRules.feedItemContent)
.textContent;
entry.append(text);
}
articles.append(entry);
if (index > 19) {
break;
}
}
}
newDocument.body.append(feed);
newDocument = checkContentEmptiness(newDocument);
placeNewDocument(newDocument);
}
function createPage() {
let domParser = new DOMParser();
let newDocument =
domParser
.parseFromString('', 'text/html');
return newDocument;
}
function checkContentEmptiness(newDocument) {
if (newDocument.getElementsByClassName('entry').length == 1) {
newDocument.getElementById('toc').remove();
// NOTE https://dbpedia.org/data/Searx.atom
if (!newDocument.getElementById('articles').outerText) {
newDocument.getElementsByClassName('entry')[0].remove();
// Should removed data be added to the htmlEmpty message?
// newDocument.getElementsByClassName('entry')[0].outerHTML;
newDocument.getElementById('articles')
.insertAdjacentHTML('beforeend', htmlEmpty);
}
} else
if (newDocument.getElementsByClassName('entry').length == 0) {
newDocument.getElementById('toc').remove();
newDocument.getElementById('articles')
.insertAdjacentHTML('beforeend', htmlEmpty);
}
return newDocument;
}
// Possible solution for the document.contentType issue
// /questions/40201137/i-need-to-read-a-text-file-from-a-javascript
// https://openuserjs.org/garage/Loading_functions_after_document_is_replaced_by_new_document
function placeNewDocument(newDocument) {
// Failed attempt to force web browser to treat file as HTML
var meta = newDocument.createElement('meta');
meta.setAttribute('http-equiv', 'Content-Type');
meta.setAttribute('content', 'text/html; charset=utf-8');
// Append the <meta> element to the <head> element
newDocument.head.appendChild(meta);
// Failed attempt to force web browser to treat file as HTML
newDocument = purgeStylesheets(newDocument); // FIXME
newDocument = setCssStylesheet(newDocument); // FIXME
//var newDoc = document.adoptNode(newDoc.documentElement, true);
let insertDocument =
document
.importNode(
newDocument
.documentElement,
true
);
let removeDocument =
document
.documentElement;
document
.replaceChild(
insertDocument,
removeDocument
);
}
// TODO
// The following events don't work on some pages: https://momi.ca/feed.xml
// Perhaps, confine them to some type of window.onload = (event) => { CODE }
function extensionLoader(url, date, type) {
// /questions/381744/is-there-anyway-to-change-the-content-type-of-an-xml-document-in-the-xml-docume
// /questions/23034283/is-it-possible-to-use-htmls-queryselector-to-select-by-xlink-attribute-in-an
//purgeStylesheets(); // FIXME
//setCssStylesheet(); // FIXME
scrollDown();
decorateEntry();
linksBar();
follow(url);
subToMe(url);
setBanner();
feedInfo(date, type);
formatDate();
aboutPage();
donatePage();
settleFilters();
statusBar();
mailTo();
//geckoHelp();
setNonceUponCSP();
}
// FIXME https://lw1.at/en/postfeed.xml
function getDateXML(xmlFile, xmlRules) {
if (xmlFile.querySelector(xmlRules.feedDate)) {
date = xmlFile.querySelector(xmlRules.feedDate).textContent;
} else
if (xmlFile.querySelector(xmlRules.feedItemDate)) {
date = xmlFile.querySelector(xmlRules.feedItemDate).textContent;
} else {
return null;
}
return date;
}
function setBanner() {
let feedBanner = document.createElement('a');
feedBanner.href = 'https://www.falkon.org/?ref=newspaper';
feedBanner.id = 'feed-banner';
document.body.append(feedBanner);
feedBanner.insertAdjacentHTML('beforeend', banner);
}
function feedInfo(date, type) {
let feedInfo = document.createElement('div');
feedInfo.id = 'feed-info';
// TODO Add "generated by" and also add to meta
if (date) {
feedInfo.innerHTML = `${type} syndication updated at <span class="published">${date}</span>`;
} else {
feedInfo.innerHTML = `${type} syndication</span>`;
}
document.body.append(feedInfo);
}
function decorateEntry() {
for (const entry of document.querySelectorAll('.entry')) {
let decor = document.createElement('div');
decor.className = 'decor';
entry.parentNode.insertBefore(decor, entry.nextSibling);
}
}
function formatDate() {
let elements = ['.published', '.updated'];
for (let i = 0; i < elements.length; i++) {
for (const element of document.querySelectorAll(elements[i])) {
let date = new Date(element.textContent);
//element.textContent = date.toDateString();
element.textContent = date.toLocaleString();
}
}
}
// FIXME
// Blocked due to server policy
// https://archlinux.org/feeds/packages/
// https://www.openstreetmap.org/traces/rss
function setCssStylesheet(document) {
let cssStylesheet = document.createElement('style');
document.head.append(cssStylesheet);
//stylesheet.setAttribute('crossorigin', 'anonymous');
cssStylesheet.type = 'text/css';
cssStylesheet.id = namespace;
// TODO
// Set direction by the letters of the initial set of words
// if no language specified.
if (rtlLocales.includes(document.documentElement.getAttribute('lang'))) {
cssFileBase = cssFileLTR + cssFileRTL;
} else {
cssFileBase = cssFileLTR;
}
cssStylesheet.textContent = cssFileBase;
//cssStylesheet.setAttribute('unsafe-hashes', null);
return document; // FIXME
}
function setNonceUponCSP() {
window.addEventListener("securitypolicyviolation", (e) => {
//let message = e.originalPolicy;
//messageTruncated = message.substring(message.indexOf("'nonce-") + 7);
//let nonceValue = messageTruncated.substring(0, messageTruncated.indexOf("'"));
let nonceValue = e.originalPolicy.match(/'nonce-(.*?)'/)[1];
cssStylesheet = document.getElementById(namespace);
cssStylesheet.setAttribute('nonce', nonceValue);
}, { passive : true, });
}
// NOTE
// https://momi.ca/feed.xml
// https://momi.ca/css/base.css
// https://momi.ca/css/dark.css
function purgeStylesheets(document) {
for (const style of document.querySelectorAll('link[rel="stylesheet"]')) {
style.remove();
}
return document; // FIXME
}
function follow(url) {
let flw = document.querySelector('#follow');
if (url) {
flw.href = `feed:${url}`;
} else {
flw.href = `feed:${location.href}`;
}
/*
flw.addEventListener ("click", function() {
if (url) {
location.href = `feed:${url}`;
} else {
location.href = `feed:${location.href}`;
}
});
*/
}
function subToMe(url) {
let stm = document.querySelector('#subtome');
//stm.href = `https://www.subtome.com/#/subscribe?feeds=${url}`;
stm.addEventListener ("click", function() {
(function(btn){
let z = document.createElement("script");
document.subtomeBtn = btn;
z.src = "https://www.subtome.com/load.js";
document.body.appendChild(z);
})(stm);
return false;
});
}
function linksBar() {
let links = document.createElement('div');
links.innerHTML = htmlBar;
let subtitle = document.querySelector('#subtitle');
subtitle.parentNode.insertBefore(links, subtitle.nextSibling);
}
function aboutPage() {
document.body.insertAdjacentHTML('beforeend', htmlAbout);
let btn = document.querySelector('#about-newspaper');
btn.addEventListener ("click", function() {
document
.querySelector('#about-feed')
.style
.display = 'block';
});
// freedos at sourceforge won't allow inline ondblclick
// ondblclick="this.style.display = "none""
let dia = document.querySelector('#about-feed');
dia.addEventListener ("dblclick", function() {
document
.querySelector('#about-feed')
.style
.display = 'none';
});
}
function donatePage() {
document.body.insertAdjacentHTML('beforeend', htmlDonate);
let btn = document.querySelector('#donate-newspaper');
btn.addEventListener ("click", function() {
document
.querySelector('#about-donate')
.style
.display = 'block';
});
// freedos at sourceforge won't allow inline ondblclick
// ondblclick="this.style.display = "none""
let dia = document.querySelector('#about-donate');
dia.addEventListener ("dblclick", function() {
document
.querySelector('#about-donate')
.style
.display = 'none';
});
}
function emptyPage() {
document
.querySelector('#articles')
.insertAdjacentHTML('beforeend', htmlEmpty);
}
function settleFilters() {
// Hide all links to software that are not of news
for (const element of document.querySelectorAll('#software a')) {
//if (element.className != 'news') {
if (!element.className.includes('news')) {
element.classList.toggle('hide');
}
}
// Create toggle mechanism
for (const element of document.querySelectorAll('.filter')) { // #filter > span
if (element.id != 'news') {
element.classList.toggle('grey');
}
element.addEventListener ("click", function() {
element.classList.toggle('grey');
for (const span of document.querySelectorAll(`.${element.id}`)) {
span.classList.toggle('hide');
}
});
}
}
function statusBar() {
// Display entry title in status bar
for (const element of document.querySelectorAll('.entry')) {
element.addEventListener ("mouseover", function() {
infoSquare(`${this.querySelector('.title > a').textContent}`);
});
}
// Prepare links to be used with status bar
for (const element of document.querySelectorAll('#links-bar > a')) {
element.addEventListener ("mouseover", function() {
//infoSquare(this.title);
if (this.title) {
this.setAttribute('info', this.title);
this.removeAttribute('title');
}
//infoSquare(`<b>Info: </b> ${this.getAttribute('info')}`);
infoSquare(`${this.getAttribute('info')}`);
});
}
// Remove status bar
for (const element of document.querySelectorAll('#links-bar')) {
//for (const element of document.querySelectorAll('#links-bar > a')) {
element.addEventListener ("mouseleave", function() { // mouseout
if (document.querySelector('#info-square')) {
document.querySelector('#info-square').remove();
}
});
}
}
function mailTo() {
// Add link with emails
if (document.querySelector('#empty-feed')) {
let ele, eml, hyl;
ele = document.querySelector('#empty-feed');
eml = document.createElement('a');
eml.id = 'email-link';
eml.textContent = 'Contact Webmaster';
let una = ['admin', 'contact', 'form', 'hello',
'hi', 'info', 'office', 'pr', 'press',
'support', 'web', 'webmaster',];
hyl = `${una[0]}@${location.hostname},`
for (let i = 1; i < una.length; i++) {
hyl += `${una[i]}@${location.hostname},`;
}
//hyl = hyl.slice(0. -1);
eml.href = `mailto:?subject=Web Feeds for ${location.hostname}&body=Hello,%0D%0A%0D%0AThis is an automated message.%0D%0A%0D%0APlease add Web Feeds (aka RSS) to your website, so that people can easily receive updates from your website.%0D%0A%0D%0AIf you do not know what a Web Feed is or how you can benefit from it, visit aboutfeeds.com to read about it.%0D%0A%0D%0AKind regards,%0D%0A&bcc=${hyl}`
ele.append(eml);
}
}
function scrollDown() {
// Create toolbar and a button when scrolling down
document.addEventListener ("scroll", function() {
const cssStylesheet = document.getElementById(namespace);
if (document.querySelector('#links-bar')) {
document.querySelector('#links-bar').style.display = 'auto';
}
if (window.pageYOffset > 200) {
cssStylesheet.textContent = cssFileBase + cssFileBar;
topButton();
} else {
cssStylesheet.textContent = cssFileBase;
document.querySelector('#links-bar').removeAttribute('style');
let elements = ['#top-navigation-button']; // '#close-bar'
for (let i = 0; i < elements.length; i++) {
if (document.querySelector(elements[i])) {
document.querySelector(elements[i]).remove();
}
}
}
});
}
/*
window.addEventListener ("hashchange", function() {
document.querySelector('#links-bar').style.display = 'none';
});
*/
/*
window.addEventListener ("scroll", function() {
const elementTitle = document.querySelector('#title');
const elementStyle = document.getElementById(namespace);
if (isInViewport(elementTitle)) {
elementStyle.textContent = stylesheetBase;
} else {
elementStyle.textContent = stylesheetBase + stylesheetBar;
}
});
*/
/*
// FIXME This is a safer fashion to do this task,
// specifically against server policy.
// NOTE Element is created, albeit without type="text/css",
// but no change applied, not even with !important
window.addEventListener ("scroll", function() {
const elementTitle = document.querySelector('#subtitle');
let elementStyle = document.querySelector('#bar');
if (isInViewport(elementTitle) && elementStyle) {
if (elementStyle) {
elementStyle.remove();
}
} else if (!elementStyle) {
elementStyle = document.createElement('style');
document.head.append(elementStyle);
elementStyle.textContent = stylesheetBar;
elementStyle.type = 'text/css';
elementStyle.id = 'bar';
}
});
*/
function imageData() {
/* TODO handle loading of images by saving bandwidth
document.body.addEventListener ("mouseover", function(e) {
if (e.target && e.target.nodeName == "IMG" && !e.target.src) {
console.log('DELEGATED');
source = e.target.getAttribute('src-data');
e.target.removeAttribute('src-data');
e.target.src = source;
}
});
for (const image of document.querySelectorAll('img')) {
image.addEventListener('mouseover', loadImage(image));
//image.onmouseover = () => {
// image.removeEventListener('mouseover',loadImage(image));
//}
}
*/
/*
for (const image of document.querySelectorAll('img')) {
image.addEventListener('focus',event => {
//image.addEventListener('mouseover',event => {
if (image.getAttribute('src-data')) {
//toggleAttribute(image);
source = image.getAttribute('src-data');
image.removeAttribute('src-data');
image.src = source;
}
}, {passive: true});
image.onmouseover = () => {
if (image.getAttribute('src-data')) {
//toggleAttribute(image);
source = image.getAttribute('src-data');
image.removeAttribute('src-data');
image.src = source;
}
};
}
*/
}
// Create status bar
function infoSquare(text) {
if (document.querySelector('#info-square')) {
document.querySelector('#info-square').remove();
}
let inf = document.createElement('div');
inf.id = 'info-square';
inf.innerHTML = text; // codeberg feeds
//inf.textContent = text;
document.body.append(inf);
}
function topButton() {
if (document.querySelector('#top-navigation-button')) return;
let nav = document.createElement('div');
nav.id = 'top-navigation-button';
nav.innerHTML = '^'; // Better than ⬆️
// behaviour
nav.onclick = () => {
window.scrollTo({ top: 0 });
};
document.body.append(nav);
}
// /questions/123999/how-can-i-tell-if-a-dom-element-is-visible-in-the-current-viewport
// https://www.javascripttutorial.net/dom/css/check-if-an-element-is-visible-in-the-viewport/
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
function loadImage(image) {
//image.removeEventListener('mouseover',loadImage(image));
console.log('onmouseover');
let source = image.getAttribute('src-data');
image.removeAttribute('src-data');
image.src = source;
}
/* TODO handle loading of images by saving bandwidth
function toggleAttribute(image) {
source = image.getAttribute('src-data');
image.removeAttribute('src-data');
image.src = source;
}
*/
/* TODO handle loading of images by saving bandwidth
for (const image of document.querySelectorAll('img')) {
//source = image.src;
//image.removeAttribute('src');
//image.setAttribute('src-data', source);
image.setAttribute('src-data', image.src);
image.removeAttribute('src');
}
*/
/* TODO remove or parse <aside> or refer to XSLT to parse it as HTML
// https://web.dev/feed.xml
// /questions/9848465/js-remove-a-tag-without-deleting-content
for (const aside of document.querySelectorAll('aside')) {
aside.remove();
}
*/
function queryByXPath(queries) {
let result, i = 0;
do {
result = document.evaluate(
queries[i], document,
null, XPathResult.STRING_TYPE);
i = i + 1;
result = result.stringValue;
} while (!result && i < queries.length);
return result;
}
function pageLoader() {
let
newDocument, insertDocument, removeDocument;
// /questions/6464592/how-to-align-entire-html-body-to-the-center
const
loadPage = `
<html>
<head>
<title>Newspaper</title>
<style>
html, body {
height: 100%; }
html {
display: table;
margin: auto; }
body {
display: table-cell;
vertical-align:middle;
background-color: #f1f1f1;
font-family: "Helvetica Neue", Helvetica,Arial, sans-serif;
cursor:default;
user-select: none;
max-height: 100%;
max-width: 100%; }
div {
font-size: 6vw;
font-weight: bold; }
</style>
</head>
<body>
<div id="loader">📰 Newspaper</div>
</body>
</html>`,
domParser = new DOMParser();
newDocument = domParser.parseFromString(loadPage, 'text/html');
insertDocument = document.importNode(newDocument.documentElement, true);
removeDocument = document.documentElement;
document.replaceChild(insertDocument, removeDocument);
}