Version 0.1.2
This commit is contained in:
parent
cb8c201b95
commit
d32b0243d8
5 changed files with 114 additions and 25 deletions
|
@ -11,7 +11,7 @@ import commonjs from '@rollup/plugin-commonjs';
|
||||||
import replace from '@rollup/plugin-replace';
|
import replace from '@rollup/plugin-replace';
|
||||||
import postcss from 'rollup-plugin-postcss';
|
import postcss from 'rollup-plugin-postcss';
|
||||||
import alias from 'rollup-plugin-alias';
|
import alias from 'rollup-plugin-alias';
|
||||||
import _dotenv from 'dotenv/config';
|
// import _dotenv from 'dotenv/config';
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -1,8 +1,69 @@
|
||||||
import browser from 'webextension-polyfill';
|
import browser from 'webextension-polyfill';
|
||||||
|
|
||||||
|
let awaitingResponse = false
|
||||||
|
|
||||||
|
const onFeedDownloaded = (req: XMLHttpRequest) => {
|
||||||
|
return async () => {
|
||||||
|
awaitingResponse = false
|
||||||
|
if (req.status >= 400) {
|
||||||
|
console.error(
|
||||||
|
`Could not load URL feed: ${req.responseURL}: ` +
|
||||||
|
`${req.status}: ${req.statusText}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tab] = await browser.tabs.query({
|
||||||
|
active: true,
|
||||||
|
currentWindow: true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!tab || tab.id === -1)
|
||||||
|
return
|
||||||
|
|
||||||
|
await browser.tabs.sendMessage(
|
||||||
|
tab.id, {
|
||||||
|
type: 'renderFeed',
|
||||||
|
document: req.responseText
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderFeed = (url: string) => {
|
||||||
|
awaitingResponse = true
|
||||||
|
const req = new XMLHttpRequest()
|
||||||
|
req.onload = onFeedDownloaded(req)
|
||||||
|
req.open('GET', url)
|
||||||
|
req.responseType = 'text'
|
||||||
|
req.send()
|
||||||
|
}
|
||||||
|
|
||||||
browser.webNavigation.onCompleted.addListener(
|
browser.webNavigation.onCompleted.addListener(
|
||||||
async (event: {tabId: string;}) => {
|
async (event: {tabId: Number}) => {
|
||||||
const { tabId } = event
|
const { tabId } = event
|
||||||
await browser.tabs.sendMessage(tabId, {type: 'renderFeed'})
|
await browser.tabs.sendMessage(tabId, {type: 'renderFeed'})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
browser.webRequest.onHeadersReceived.addListener(
|
||||||
|
async (event: {
|
||||||
|
url: string,
|
||||||
|
responseHeaders: Array<{name: string, value: string}>
|
||||||
|
}) => {
|
||||||
|
if (awaitingResponse)
|
||||||
|
return
|
||||||
|
|
||||||
|
const {url, responseHeaders} = event
|
||||||
|
const contentType = responseHeaders.find(
|
||||||
|
h => h.name.toLowerCase() === 'content-type'
|
||||||
|
)?.value || ''
|
||||||
|
|
||||||
|
if (contentType.startsWith('application/rss+xml'))
|
||||||
|
renderFeed(url)
|
||||||
|
},
|
||||||
|
{urls: ['<all_urls>']},
|
||||||
|
['blocking', 'responseHeaders']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
43
src/main.ts
43
src/main.ts
|
@ -103,9 +103,9 @@ const parseFeed = (channel: Element) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFeedRoot = () => {
|
const getFeedRoot = (): HTMLElement | null => {
|
||||||
const xmlDoc = document.documentElement
|
const xmlDoc = document.documentElement
|
||||||
if (xmlDoc.tagName === 'rss')
|
if (xmlDoc.tagName.toLowerCase() === 'rss')
|
||||||
return xmlDoc
|
return xmlDoc
|
||||||
|
|
||||||
// Chrome-based browsers may wrap the XML into an HTML view
|
// Chrome-based browsers may wrap the XML into an HTML view
|
||||||
|
@ -116,28 +116,31 @@ const getFeedRoot = () => {
|
||||||
// For some ugly reasons, some RSS feeds are rendered inside of a <pre> in a normal HTML DOM
|
// For some ugly reasons, some RSS feeds are rendered inside of a <pre> in a normal HTML DOM
|
||||||
const preElements = document.getElementsByTagName('pre')
|
const preElements = document.getElementsByTagName('pre')
|
||||||
if (preElements.length !== 1)
|
if (preElements.length !== 1)
|
||||||
return
|
return null
|
||||||
|
|
||||||
const text = preElements[0].innerText
|
return preElements[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const textToDOM = (text: string) => {
|
||||||
const parser = new DOMParser()
|
const parser = new DOMParser()
|
||||||
let innerXmlDoc = null
|
let xmlDoc = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
innerXmlDoc = parser.parseFromString(text, 'text/xml')
|
xmlDoc = parser.parseFromString(text, 'text/xml')
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
|
|
||||||
if (!innerXmlDoc)
|
if (!xmlDoc)
|
||||||
return
|
return
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const root = innerXmlDoc.documentElement
|
const root = xmlDoc.documentElement
|
||||||
if (root.tagName === 'rss')
|
if (root.tagName.toLowerCase() === 'rss')
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderFeed = () => {
|
const renderFeed = (text: string) => {
|
||||||
const xmlDoc = getFeedRoot()
|
const xmlDoc = text?.length ? textToDOM(text) : getFeedRoot()
|
||||||
if (!xmlDoc)
|
if (!xmlDoc)
|
||||||
// Not an RSS feed
|
// Not an RSS feed
|
||||||
return
|
return
|
||||||
|
@ -154,7 +157,7 @@ const extractFeedUrl = () => {
|
||||||
const links = Array.from(document.getElementsByTagName('link'))
|
const links = Array.from(document.getElementsByTagName('link'))
|
||||||
.filter((link) =>
|
.filter((link) =>
|
||||||
link.getAttribute('rel') === 'alternate' &&
|
link.getAttribute('rel') === 'alternate' &&
|
||||||
link.getAttribute('type') === 'application/rss+xml'
|
link.getAttribute('type')?.startsWith('application/rss+xml')
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!links.length)
|
if (!links.length)
|
||||||
|
@ -171,10 +174,20 @@ const extractFeedUrl = () => {
|
||||||
return link.length ? link : null
|
return link.length ? link : null
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener(async (message: {type: Object}) => {
|
browser.runtime.onMessage.addListener(
|
||||||
|
async (
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
url: string,
|
||||||
|
document: string,
|
||||||
|
}
|
||||||
|
) => {
|
||||||
if (message.type === 'renderFeed')
|
if (message.type === 'renderFeed')
|
||||||
return renderFeed()
|
return renderFeed(message.document)
|
||||||
if (message.type === 'extractFeedUrl')
|
if (message.type === 'extractFeedUrl')
|
||||||
return extractFeedUrl()
|
return extractFeedUrl()
|
||||||
})
|
|
||||||
|
console.warn(`Received unknown message type: ${message.type}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "RSS Viewer",
|
"name": "RSS Viewer",
|
||||||
"description": "An easy way to render RSS feeds directly in your browser",
|
"description": "An easy way to render RSS feeds directly in your browser",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_title": "Feed Viewer",
|
"default_title": "Feed Viewer",
|
||||||
|
@ -26,6 +26,11 @@
|
||||||
"viewer/index.html"
|
"viewer/index.html"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab", "storage", "<all_urls>", "webNavigation"
|
"activeTab",
|
||||||
|
"storage",
|
||||||
|
"<all_urls>",
|
||||||
|
"webNavigation",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,10 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.feed {
|
||||||
|
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
box-shadow: 1px 1px 1px 1px #b7b7b7;
|
box-shadow: 1px 1px 1px 1px #b7b7b7;
|
||||||
|
@ -153,9 +157,15 @@ main {
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
font-family: sans-serif;
|
|
||||||
|
.description {
|
||||||
|
max-width: 45em;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue