旧版: v25.03.27 - 2025-03-27 - Update links to the journal "Dig Deeper".
Add various of links to useful software and webrings.
新版: v25.03.30 - 2025-03-30 - Added automatic discovery of syndicated feeds, similarly to Greasemonkey software Black Belt.
- @@ -16,7 +16,7 @@
- // @match *://*/*
- // @exclude *?streamburner=0*
- // @exclude *&streamburner=0*
-// @version 25.03.27
- +// @version 25.03.30
- // @run-at document-start
- // @grant GM.setValue
- // @grant GM.getValue
- @@ -86,6 +86,7 @@
- defaultSubtitle = 'News feed rendered with Streamburner',
- defaultAbout = 'No description was provided.',
- rtlLocales = ['ar', 'fa', 'he', 'ji', 'ku', 'ur', 'yi'],
- + svgGraphics = '<?xml version="1.0"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px" viewBox="0 0 256 256"><defs><linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="syndication"><stop offset="0.0" stop-color="#E3702D"/><stop offset="0.1071" stop-color="#EA7D31"/><stop offset="0.3503" stop-color="#F69537"/><stop offset="0.5" stop-color="#FB9E3A"/><stop offset="0.7016" stop-color="#EA7C31"/><stop offset="0.8866" stop-color="#DE642B"/><stop offset="1.0" stop-color="#D95B29"/></linearGradient></defs><rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15"/><rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/><rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#syndication)"/><circle cx="68" cy="189" r="24" fill="#FFF"/><path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/><path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/></svg>',
- atomRules = {
- "feedLanguage" : "feed", // NOTE @xml:lang
- "feedTitlePage" : "feed > title",
- @@ -185,6 +186,20 @@
- <h1>Settings</h1>
- <p>Settings are saved instantly upon click.</p>
- <p>Reload for changes to take affect.</p>
- + <h2>Browsing</h2>
- + <dl>
- + <dt>
- + <span>Automation</span>
- + </dt>
- + <dd>
- + <p>
- + <span>
- + <input type="checkbox" name="auto-discovery" id="auto-discovery" />
- + <label for="auto-discovery">Enable auto-discovery</label>
- + </span>
- + </p>
- + </dd>
- + </dl>
- <h2>Appearance</h2>
- <dl>
- <dt>
- @@ -2721,7 +2736,7 @@
- <p>When applying advertisements as an integrated part of a revenue model of a content production business so to speak, there is a conflict of interest between generating quality content to generating revenue;</p>
- <p>Which means that sooner or later a content producer will inevitably publish content that is either aberrational, defective, deficient, false, faulty, flawed, inadequate, wanting or even the lack thereof, which turns the content producer to a so called (i.e. false) content producer.</p>
- </li>
- <li>The conflict of interest extends to the censorship of contents that all or some advertisers do not want to be published (i.e. this is the the <i>lack</i> of content as written above).</li>
- + <li>The conflict of interest extends to the censorship of contents that all or some advertisers do not want to be published (i.e. this is the <i>lack</i> of content as written above).</li>
- </ol>
- <h3>Saying That Content Depends On Revenue Is Like Saying That Speech Depends On Revenue</h3>
- <p>It is as if you would not be able to greet or say "Good day!" to your mother, father or mate unless you generate revenue by doing so or display an advertisement while doing so.</p>
- @@ -4027,7 +4042,7 @@
- overflow-x: hidden; }
-
- body {
- background: #f4ffce; /* #f5f5f5; */
- + background: #f5f5f5; /* #f4ffce; */
- color: #333;
- hyphens: auto;
- /* font-family: serif; */ }
- @@ -5070,6 +5085,7 @@
-
- // Read settings
- if (gmGetValue) {
- + autoDiscovery = await getAutoDiscovery();
- contentMode = await getContentMode();
- enableIcon = await getIconMode();
- enableEnclosure = await getEnclosureMode();
- @@ -5095,23 +5111,45 @@
- return; // exit
- }
- */
- let xmlFile;
- let domParser = new DOMParser();
- xmlFile = domParser.parseFromString(request.response.trim(), 'text/xml');
-// orgFile = domParser.parseFromString(request.response, 'text/xml');
-// /questions/6334119/check-for-xml-errors-using-javascript
-// console.error(orgFile.documentElement.nodeName == 'parsererror' ? 'error while parsing' : orgFile.documentElement.nodeName);
- let wellFormedMessage = xmlFile.getElementsByTagName('parsererror').length ?
- (new XMLSerializer()).serializeToString(xmlFile) : 'This XML is well-formed.';
- if (wellFormedMessage != 'This XML is well-formed.') {
- console.warn('This XML is not-well-formed.');
- } else {
- wellFormed = true;
- +
- + let jsonFile, xmlFile;
- + let isHtml = false, isJson = false, isXml = false;
- + let requestResponse = request.response
- + try { // Check whether JSON.
- + // TODO Check ActivityStream/JSON Feed/RSS-in-JSON
- + jsonFile = JSON.parse(requestResponse);
- + isJson = true;
- + } catch { // Check whether XML syndication or otherwise.
- + let domParser = new DOMParser();
- + let htmlFile = domParser.parseFromString(requestResponse, 'text/html');
- + if (htmlFile.documentElement.tagName == 'HTML' &&
- + htmlFile.doctype && htmlFile.doctype.name == 'html') {
- + //if (htmlFile.body && htmlFile.body.childNodes.length > 0) {
- + isHtml = true;
- + } else {
- + // TODO Check whether Atom/OPML/RDF/RSS/SMF etc.
- + xmlFile = domParser.parseFromString(requestResponse.trim(), 'text/xml');
- + isXml = true;
- + }
- +
- }
-
- // TODO Preference to respect or override stylesheet
- // TODO Ignore all stylesheets if all are CSS
- if (xmlFile) {
- + if (isXml) { // Attempt to parse XML.
- +
- + // TODO Preference to respect or override stylesheet
- + // TODO Ignore all stylesheets if all are CSS
- +
- + // orgFile = domParser.parseFromString(request.response, 'text/xml');
- + // /questions/6334119/check-for-xml-errors-using-javascript
- + // console.error(orgFile.documentElement.nodeName == 'parsererror' ? 'error while parsing' : orgFile.documentElement.nodeName);
- +
- + // Check validity.
- + let wellFormedMessage = xmlFile.getElementsByTagName('parsererror').length ?
- + (new XMLSerializer()).serializeToString(xmlFile) : 'This XML is well-formed.';
- + if (wellFormedMessage != 'This XML is well-formed.') {
- + console.warn('This XML is not-well-formed.');
- + } else {
- + wellFormed = true;
- + }
- +
-
- //let xmlStylesheet;
- for (childNode of xmlFile.childNodes) {
- @@ -5164,112 +5202,112 @@
- switch (xmlFile.firstElementChild) {
- // <feed xmlns="http://www.w3.org/2005/Atom">
- // xmlFile.getElementsByTagNameNS('http://www.w3.org/2005/Atom','feed')
- case xmlFile.querySelector('feed'):
- pageLoader();
- newDocument = renderXML(xmlFile, atomRules);
- //aboutInfo(xmlFile, rdfRules);
- newDocument = feedInfoXML(
- newDocument,
- xmlFile,
- atomRules,
- 'Atom Syndication Format 1.0'); // Atom Syndication Feed 1.0
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- break;
- // 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">
- case xmlFile.querySelector('rss'):
- pageLoader();
- newDocument = renderXML(xmlFile, rssRules);
- //aboutInfo(xmlFile, rdfRules);
- // FIXME https://www.elegislation.gov.hk/verified-chapters!en.rss.xml
- if (rssVersion = xmlFile.firstElementChild.getAttribute('version')) {
- newDocument = feedInfoXML(
- newDocument,
- xmlFile,
- rssRules,
- `RSS ${rssVersion}`);
- } else {
-
- + case xmlFile.querySelector('feed'):
- + pageLoader();
- + newDocument = renderXML(xmlFile, atomRules);
- + //aboutInfo(xmlFile, rdfRules);
- +
- newDocument = feedInfoXML(
- newDocument,
- xmlFile,
- rssRules,
- `RSS`); // RSS Syndication Feed 2.0
- }
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- break;
- // TODO Check by namespace xmlns
- // https://yaxim.org/doap/yaxim.rdf.xml
- // https://wiki.gnome.org/action/rss_rc/Home?action=rss_rc&unique=1&ddiffs=1
- // https://web.resource.org/rss/1.0/schema.rdf
- // 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');
- case xmlFile.getElementsByTagName("rdf:RDF")[0]: // RDF Vocabulary
- switch (xmlFile.firstElementChild.getAttribute('xmlns')) {
- case 'http://my.netscape.com/rdf/simple/0.9/':
- case 'https://web.resource.org/rss/1.0/': // TODO TEST
- case 'http://purl.org/rss/1.0/':
- + atomRules,
- + 'Atom Syndication Format 1.0'); // Atom Syndication Feed 1.0
- + newDocument = await preProcess(newDocument);
- + placeNewDocument(newDocument);
- + await postProcess();
- + break;
- + // 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">
- + case xmlFile.querySelector('rss'):
- pageLoader();
- newDocument = renderXML(xmlFile, rdfRules);
- + newDocument = renderXML(xmlFile, rssRules);
- //aboutInfo(xmlFile, rdfRules);
- + // FIXME https://www.elegislation.gov.hk/verified-chapters!en.rss.xml
- + if (rssVersion = xmlFile.firstElementChild.getAttribute('version')) {
- + newDocument = feedInfoXML(
- + newDocument,
- + xmlFile,
- + rssRules,
- + `RSS ${rssVersion}`);
- + } else {
- + newDocument = feedInfoXML(
- + newDocument,
- + xmlFile,
- + rssRules,
- + `RSS`); // RSS Syndication Feed 2.0
- + }
- + newDocument = await preProcess(newDocument);
- + placeNewDocument(newDocument);
- + await postProcess();
- + break;
- + // TODO Check by namespace xmlns
- + // https://yaxim.org/doap/yaxim.rdf.xml
- + // https://wiki.gnome.org/action/rss_rc/Home?action=rss_rc&unique=1&ddiffs=1
- + // https://web.resource.org/rss/1.0/schema.rdf
- + // 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');
- + case xmlFile.getElementsByTagName("rdf:RDF")[0]: // RDF Vocabulary
- switch (xmlFile.firstElementChild.getAttribute('xmlns')) {
- case 'https://web.resource.org/rss/1.0/':
- case 'http://purl.org/rss/1.0/':
- newDocument = feedInfoXML(
- newDocument,
- xmlFile,
- rdfRules,
- 'RSS 1.0');
- break;
- case 'http://my.netscape.com/rdf/simple/0.9/':
- newDocument = feedInfoXML(
- newDocument,
- xmlFile,
- rdfRules,
- 'RSS 0.9');
- break;
- + case 'https://web.resource.org/rss/1.0/': // TODO TEST
- + case 'http://purl.org/rss/1.0/':
- + pageLoader();
- + newDocument = renderXML(xmlFile, rdfRules);
- + //aboutInfo(xmlFile, rdfRules);
- + switch (xmlFile.firstElementChild.getAttribute('xmlns')) {
- + case 'https://web.resource.org/rss/1.0/':
- + case 'http://purl.org/rss/1.0/':
- + newDocument = feedInfoXML(
- + newDocument,
- + xmlFile,
- + rdfRules,
- + 'RSS 1.0');
- + break;
- + case 'http://my.netscape.com/rdf/simple/0.9/':
- + newDocument = feedInfoXML(
- + newDocument,
- + xmlFile,
- + rdfRules,
- + 'RSS 0.9');
- + break;
- + }
- + newDocument = await preProcess(newDocument);
- + placeNewDocument(newDocument);
- + await postProcess();
- +
- }
- + break;
- + case xmlFile.querySelector('opml'):
- + pageLoader();
- + // dateCreated http://source.scripting.com/?format=opml&streamburner=0
- + newDocument = renderOPML(xmlFile, opmlRules);
- + //aboutInfo(xmlFile, rdfRules);
- + newDocument = feedInfoXML(
- + newDocument,
- + xmlFile,
- + opmlRules,
- + 'OPML Outline'); // OPML
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- }
- break;
- case xmlFile.querySelector('opml'):
- pageLoader();
- // dateCreated http://source.scripting.com/?format=opml&streamburner=0
- newDocument = renderOPML(xmlFile, opmlRules);
- //aboutInfo(xmlFile, rdfRules);
- newDocument = feedInfoXML(
- newDocument,
- xmlFile,
- opmlRules,
- 'OPML Outline'); // OPML
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- break;
- case xmlFile.getElementsByTagName("smf:xml-feed")[0]:
- pageLoader();
- newDocument = renderXML(xmlFile, smfRules);
- //aboutInfo(xmlFile, rdfRules);
- newDocument = feedInfoXML(
- newDocument,
- xmlFile,
- smfRules,
- 'Simple Machines Forum'); // SMF
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- break;
- + break;
- + case xmlFile.getElementsByTagName("smf:xml-feed")[0]:
- + pageLoader();
- + newDocument = renderXML(xmlFile, smfRules);
- + //aboutInfo(xmlFile, rdfRules);
- + newDocument = feedInfoXML(
- + newDocument,
- + xmlFile,
- + smfRules,
- + 'Simple Machines Forum'); // SMF
- + newDocument = await preProcess(newDocument);
- + placeNewDocument(newDocument);
- + await postProcess();
- + break;
- }
- // FIXME
- // This appears to be a not usuable Atom feed
- @@ -5291,75 +5329,420 @@
- // errorPage is a good idea to promote Falkon
- //setTimeout(function(){renderFeed(request.response)}, 1500); // timeout for testing
-
- try {
- if (JSON.parse(request.response)) {
- wellFormed = true;
- let jsonFile = JSON.parse(request.response);
- if (jsonFile.version) {
- // FIXME TODO Handle empty feed https://jblevins.org/index.json
- if (jsonFile.version.toLowerCase().includes('jsonfeed.org')) {
- pageLoader();
- //setTimeout(function(){renderJSONFeed(jsonFile)}, 1500);
- newDocument = renderJSONFeed(jsonFile);
- newDocument = feedInfoJSON(
- newDocument,
- jsonFile,
- jsonFile.home_page_url,
- jsonFile.generator,
- jsonFile.version,
- jsonFile.items[0].date_published,
- 'JSON Feed');
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- + else
- + if (isJson) { // Attempt to parse JSON.
- + wellFormed = true;
- + if (jsonFile.version) {
- + // FIXME TODO Handle empty feed https://jblevins.org/index.json
- + if (jsonFile.version.toLowerCase().includes('jsonfeed.org')) {
- + pageLoader();
- + //setTimeout(function(){renderJSONFeed(jsonFile)}, 1500);
- + newDocument = renderJSONFeed(jsonFile);
- + newDocument = feedInfoJSON(
- + newDocument,
- + jsonFile,
- + jsonFile.home_page_url,
- + jsonFile.generator,
- + jsonFile.version,
- + jsonFile.items[0].date_published,
- + 'JSON Feed');
- + newDocument = await preProcess(newDocument);
- + placeNewDocument(newDocument);
- + await postProcess();
- + }
- + } else
- + if (jsonFile.generator) {
- + if (jsonFile.generator.toLowerCase().includes('statusnet') || // TODO Case insensitive
- + jsonFile.generator.toLowerCase().includes('gnu social')) {
- + pageLoader();
- + newDocument = renderActivityStream(jsonFile);
- + newDocument = feedInfoJSON(
- + newDocument,
- + jsonFile,
- + //jsonFile.items[0].id, // NOTE Not good. This is done so that infoSquare will load
- + location.protocol + '//' + location.hostname,
- + jsonFile.generator,
- + jsonFile.generator,
- + jsonFile.items[0].published,
- + 'ActivityStream');
- + newDocument = await preProcess(newDocument);
- + placeNewDocument(newDocument);
- + await postProcess();
- + }
- + } else
- + if (jsonFile.rss) {
- + if (jsonFile.rss.version.toLowerCase().includes('2.0')) {
- + pageLoader();
- + newDocument = renderRssInJson(jsonFile.rss.channel);
- + newDocument = feedInfoJSON(
- + newDocument,
- + jsonFile,
- + jsonFile.rss.channel.link,
- + jsonFile.rss.channel.generator,
- + jsonFile.rss.version,
- + jsonFile.rss.channel.pubDate,
- + 'RSS-in-JSON');
- + newDocument = await preProcess(newDocument);
- + placeNewDocument(newDocument);
- + await postProcess();
- + }
- + }
- + // FIXME Parse ActivityStream entry
- + // https://nu.federati.net/api/statuses/show/3424682.json
- + } else
- + if (autoDiscovery) { // When all else fail, scan for possible syndication feeds.
- +
- + let
- + hrefs = [],
- + links = [],
- + mediaTypes = {
- + "feed" : {
- + "name" : "Follow",
- + "alternate" : [
- + "atom", "rss", "rdf", "stream",
- + "feed", "feed+json"], // json
- + "extension" : [
- + "atom", "atom.php", "atom.xml",
- + "rss", "rss.php", "rss.xml",
- + "rdf", "rdf.php", "rdf.xml",
- + "feed.xml", "feed.json", "rss.json"],
- + "path" : ["/feed/atom", "/feed/atom/",
- + "/feed", "/feed/", "app.php/feed"],
- + "uri" : ["feed", "news"]
- + },
- + "podcast" : {
- + "name" : "Podcast",
- + "uri" : ["itpc"],
- + "extension" : ["podcast.xml"]
- +
- }
- } else
- if (jsonFile.generator) {
- if (jsonFile.generator.toLowerCase().includes('statusnet') || // TODO Case insensitive
- jsonFile.generator.toLowerCase().includes('gnu social')) {
- pageLoader();
- newDocument = renderActivityStream(jsonFile);
- newDocument = feedInfoJSON(
- newDocument,
- jsonFile,
- //jsonFile.items[0].id, // NOTE Not good. This is done so that infoSquare will load
- location.protocol + '//' + location.hostname,
- jsonFile.generator,
- jsonFile.generator,
- jsonFile.items[0].published,
- 'ActivityStream');
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- + }
- +
- + let objectKeys = Object.keys(mediaTypes);
- +
- + for (let i = 0; i < objectKeys.length; i++) {
- + let
- + results = [],
- + name = mediaTypes[objectKeys[i]].name;
- + array = mediaTypes[objectKeys[i]];
- +
- + if (array.alternate) {
- + let results_set = extractRel(array.alternate)
- + if (results_set) {
- + for (let result of results_set) {
- + results.push(result)
- + }
- }
- } else
- if (jsonFile.rss) {
- if (jsonFile.rss.version.toLowerCase().includes('2.0')) {
- pageLoader();
- newDocument = renderRssInJson(jsonFile.rss.channel);
- newDocument = feedInfoJSON(
- newDocument,
- jsonFile,
- jsonFile.rss.channel.link,
- jsonFile.rss.channel.generator,
- jsonFile.rss.version,
- jsonFile.rss.channel.pubDate,
- 'RSS-in-JSON');
- newDocument = await preProcess(newDocument);
- placeNewDocument(newDocument);
- await postProcess();
- + }
- +
- + if (array.extension) {
- + let results_set = extractFile(array.extension)
- + if (results_set) {
- + for (let result of results_set) {
- + results.push(result)
- + }
- + }
- + }
- +
- + if (array.uri) {
- + let results_set = extractURI(array.uri)
- + if (results_set) {
- + for (let result of results_set) {
- + results.push(result)
- + }
- + }
- + }
- +
- + for (result of results) {
- + let aElement = document.createElement('a');
- + //aElement.textContent = '> ' + name;
- + //aElement.textContent = name;
- + //aElement.mouseenter = () => { aElement.style.marginLeft = '2em'; }
- + //aElement.onmouseout = () => { aElement.style.marginLeft = 'unset'; }
- + //aElement.onmouseover = () => { aElement.style.textDecoration = 'underline'; }
- + //aElement.onmouseleave = () => { aElement.style.textDecoration = 'none'; }
- + if (result.startsWith('/')) {
- + aElement.textContent = location.origin + result;
- + } else
- + if (result.startsWith('./')) {
- + aElement.textContent = location.origin + result.replace('.', '');
- + } else {
- + aElement.textContent = result;
- + }
- + aElement.href = result;
- + aElement.style.all = 'unset';
- + aElement.style.color = '#eee';
- + //aElement.style.display = 'block';
- + //aElement.style.font = 'caption';
- + aElement.style.fontFamily = 'system-ui';
- + //aElement.style.fontSize = '3em'; // 13px 20px
- + //aElement.style.cursor = 'pointer';
- + for (let i = 0; i < aElement.style.length; i++) {
- + aElement.style.setProperty(
- + aElement.style[i],
- + aElement.style.getPropertyValue(aElement.style[i]),
- + 'important'
- + );
- + }
- + if (hrefs.indexOf(result) == -1) {
- + hrefs.push(result);
- + links.push(aElement);
- + }
- + try {
- + GM.registerMenuCommand(name, () => openUrl(result));
- + } catch (err) {
- + console.warn('Please check that your Userscript manager has GM.registerMenuCommand API.')
- + console.error(err)
- }
- }
- // FIXME Parse ActivityStream entry
- // https://nu.federati.net/api/statuses/show/3424682.json
- }
- } catch {
- // Not JSON
- +
- + if (links.length) {
- + let listElement = document.createElement('div');
- + listElement.id = namespace + '-list';
- + //listElement.style.all = 'unset';
- + listElement.style.fontFamily = 'system-ui';
- + listElement.style.background = 'black';
- + listElement.style.color = '#eee';
- + listElement.style.fontSize = '1.5em';
- + listElement.style.lineHeight = '1em';
- + // Fixed position
- + listElement.style.position = 'fixed';
- + // Display on top of all elements
- + listElement.style.zIndex = 2147483647;
- + // Force direction Lieft-to-Right
- + listElement.style.direction = 'ltr';
- + // Start hidden
- + listElement.style.display = 'none';
- + // Create a scroll bar
- + listElement.style.overflow = 'scroll';
- + //listElement.style.scrollbarWidth = 'none';
- + // Truncate text
- + listElement.style.textOverflow = 'ellipsis';
- + listElement.style.whiteSpace = 'nowrap';
- + // Occupy whole display
- + listElement.style.bottom = 0;
- + listElement.style.right = 0;
- + listElement.style.left = 0;
- + listElement.style.top = 0;
- +
- + let titleElement = document.createElement('h1');
- + titleElement.textContent = `Greasemonkey Newspaper`;
- + titleElement.style.all = 'unset';
- + titleElement.style.fontFamily = 'system-ui';
- + titleElement.style.fontWeight = 'bold';
- + //titleElement.style.textAlign = 'center';
- + //titleElement.style.fontSize = '3em';
- + titleElement.style.display = 'block';
- + titleElement.style.margin = '1em';
- + titleElement.style.color = '#eee';
- + listElement.append(titleElement);
- +
- + let textElement = document.createElement('span');
- + textElement.innerHTML = `Source auto-discovery has detected ${links.length} syndication news feeds. <u>Click to return</u>.<hr/>`;
- + textElement.style.all = 'unset';
- + textElement.style.fontFamily = 'system-ui';
- + textElement.style.display = 'block';
- + textElement.style.margin = '1em';
- + textElement.style.color = '#eee';
- + returnElemeny = textElement.querySelector('u');
- + returnElemeny.onclick = () => { listElement.style.display = 'none'; }
- + returnElemeny.style.cursor = 'pointer';
- + listElement.append(textElement);
- +
- + //links.forEach(link => listElement.append(link));
- +
- + for (linkElement of links) {
- + let entryElement = document.createElement('div');
- + let bulletElement = document.createElement('span');
- + entryElement.append(bulletElement);
- + entryElement.append(linkElement);
- + entryElement.onmouseover = () => { bulletElement.textContent = ' => '; }
- + entryElement.onmouseleave = () => { bulletElement.textContent = ''; }
- + entryElement.style.margin = '1em';
- + entryElement.style.cursor = 'pointer';
- + listElement.append(entryElement);
- + }
- +
- + //let buttonElement = document.createElement('button');
- + //buttonElement.textContent = 'Close';
- + let exitElement = document.createElement('span');
- + exitElement.style.all = 'unset';
- + exitElement.style.fontFamily = 'system-ui';
- + exitElement.style.textDecoration = 'none';
- + //exitElement.style.fontStyle = 'italic';
- + exitElement.style.cursor = 'pointer';
- + exitElement.style.display = 'block';
- + exitElement.style.margin = '1em';
- + exitElement.textContent = 'Return';
- + exitElement.onclick = () => { listElement.style.display = 'none'; }
- + listElement.append(exitElement);
- +
- + document.body.append(listElement);
- +
- + //let number = 1;
- + let iconElement = document.createElement('div');
- + iconElement.id = namespace + '-icon';
- + iconElement.innerHTML = svgGraphics;
- + iconElement.ondblclick = () => { iconElement.remove(); }
- + iconElement.onclick = () => { listElement.style.display = 'block'; }
- +
- + iconElement.style.all = 'unset';
- + //iconElement.style.left = 0; // '5px'
- + iconElement.style.right = 0; // '5px'
- + iconElement.style.bottom = 0; // '5px'
- + iconElement.style.margin = '1em';
- + iconElement.style.display = 'block';
- + iconElement.style.position = 'fixed';
- + iconElement.style.zIndex = 2147483646;
- + //iconElement.style.height = '32px';
- + //iconElement.style.width = '32px';
- + svgElement = iconElement.querySelector('svg');
- + svgElement.style.height = '32px';
- + svgElement.style.width = '32px';
- + svgElement.style.filter = 'drop-shadow(1px 1px 1px orange)';
- + // Set !important
- + for (let i = 0; i < iconElement.style.length; i++) {
- + iconElement.style.setProperty(
- + iconElement.style[i],
- + iconElement.style.getPropertyValue(iconElement.style[i]),
- + 'important'
- + );
- + }
- + //iconElement.append("索 This site offers syndicated contents:") // 📰 ⚛
- + //links.forEach(link => iconElement.append(link));
- + //iconElement.append(closeButton(iconElement));
- + document.body.append(iconElement);
- + }
- }
- }
- );
- }
-
- +function extractFile(array) {
- + let i = 0, links = [];
- + for (let i = 0; i < array.length; i++) {
- + // FIXME Mainstream to support ends-with
- + // fn:ends-with appears to be missing in some engines
- + query = [
- + `//a[contains(@href, ".${array[i]}")]/@href`,
- + `//a[contains(@download, ".${array[i]}")]/@download`];
- +// `//a[ends-with(@href, ".${array[i]}")]/@href`
- +// `//a[ends-with(text(), ".${array[i]}")]/@href`
- + results = executeQuery(query, 'xpath');
- + if (results.length) {
- + for (result of results) {
- + protocol = location.protocol
- + hostname = location.hostname
- + //console.log(result)
- + switch (true) {
- +
- + case (result.startsWith('/')):
- + result = protocol + '//' + hostname + result;
- + break;
- +
- + case (!result.includes(':')):
- + result = protocol + '//' + hostname + '/' + result;
- + break;
- +
- + //case (result.startsWith('http')):
- + //break;
- + }
- +
- + //console.log(result)
- + let url = new URL(result);
- + let bol = url.pathname.endsWith(array[i-1]);
- + if (bol) {
- + links.push(result);
- + }
- + }
- + }
- + }
- + if (links.length) { return links; };
- +}
- +
- +function extractRel(array) {
- + let i = 0, links = [];
- + for (let i = 0; i < array.length; i++) {
- + query = [
- + // Also rel="feed". See https://miranda-ng.org/
- + `//link[@rel="alternate"\
- + and contains(@type, "${array[i]}")\
- + ]/@href`];
- + results = executeQuery(query, 'xpath');
- + if (results.length) {
- + for (result of results) {
- + links.push(result);
- + }
- + }
- + }
- + if (links.length) { return links; };
- +}
- +
- +function extractURI(array) {
- + let i = 0, links = [];
- + for (i of array) {
- + query = [
- + `//a[starts-with(@href, "${i}:")]/@href`];
- + results = executeQuery(query, 'xpath');
- + if (results.length) {
- + for (result of results) {
- + let url = new URL(result);
- + let bol = url.protocol.match(i);
- + if (bol) {
- + links.push(result);
- + }
- + }
- + }
- + }
- + if (links.length) { return links; };
- +}
- +
- +function executeQuery(queries, method) {
- + let i = 0, links = [], nodes;
- + for (query of queries) {
- + switch (method) {
- + case 'css':
- + result = document.querySelector(queries[i]);
- + //if (result) {result = result.href};
- + if (result) {return result.href};
- + break;
- +
- + case 'xpath':
- + // NOTE This may cause 404 error.
- + // Use getPathTo()
- + // https://stackoverflow.com/questions/2631820/how-do-i-ensure-saved-click-coordinates-can-be-reload-to-the-same-place-even-if/2631931#2631931
- + /*
- + xhtmlFile = new XMLSerializer().serializeToString(document).toLowerCase()
- + //xhtmlFile = '<html>'+document.documentElement.innerHTML.toLowerCase()+'</html>'
- + domParser = new DOMParser();
- + xhtmlFile = domParser.parseFromString(xhtmlFile, 'text/html');
- + result = document.evaluate(
- + queries[i], xhtmlFile, null, XPathResult.STRING_TYPE);
- + */
- +
- + /*
- + result = document.evaluate(
- + queries[i], document, null, XPathResult.STRING_TYPE);
- + //if (result) {result = result.stringValue};
- + if (result) {return result.stringValue};
- + */
- +
- + nodes = document.evaluate(
- + query, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
- + if (nodes) {
- + let node = nodes.iterateNext();
- + while (node) {
- + // Add the link to the array
- + links.push(node.nodeValue);
- + // Get the next node
- + node = nodes.iterateNext();
- + }
- + }
- + return links
- + }
- + }
- +}
- +
- /*
-
- Test code (attempting to modify content type):
- @@ -6628,6 +7011,10 @@
- return await GM.getValue('filter-whitelist', false)
- }
-
- +async function getAutoDiscovery() {
- + return await GM.getValue('auto-discovery', true)
- +}
- +
- async function getKeywordsBlacklist() {
- // No one cares.
- // Be sure you protect your civil liberties, instead of concentrating on gang wars.
- @@ -7078,7 +7465,7 @@
- 'title' : 'Instant messaging as free and open as it should be.',
- },
- { // recurse
- 'text' : 'The The Recurse Center Webring',
- + 'text' : 'The Recurse Center Webring',
- 'link' : 'https://webring.recurse.com',
- 'title' : '',
- },
- @@ -7877,7 +8264,9 @@
- }
-
- // Set values
- let pageSettings = document.querySelector('#page-settings')
- + let pageSettings = document.querySelector('#page-settings');
- +
- + pageSettings.querySelector('#auto-discovery').value = autoDiscovery;
- pageSettings.querySelector('#content-mode').value = contentMode;
- pageSettings.querySelector('#filter-whitelist').value = filterWhitelist;
- pageSettings.querySelector('#filter-blacklist').value = filterBlacklist;