Compare commits

...

3 Commits

6 changed files with 236 additions and 49 deletions

View File

@ -37,8 +37,18 @@ This ends up being specially useful for communities, in order to follow the work
## Customize the generated HTML
Each community has its own identity, so you should be able to easily customize the look and feel
of the generated page. To do so, before publishing you can edit the `templates/default.handlebars` file.
of the generated page. To do so, before publishing you can edit one of the existing templates in the `templates` folder.
After you should "precompile" that file using the following command:
After, you should "precompile" that file using the following command:
> \$ npm run template -- templates/default.handlebars -f templates/default.precompiled.js
If the template name you are using is different from `default`, you should change the following 2 lines in `index.js`:
```javascript
import template from './templates/default.precompiled'
```
```javascript
let template = Handlebars.templates['default']
```

View File

@ -2,11 +2,27 @@ import Parser from 'rss-parser'
import { Feed } from 'feed'
import Handlebars from 'handlebars/runtime'
import template from './templates/default.precompiled'
import * as striptags from 'striptags'
addEventListener('scheduled', event => {
event.waitUntil(handleScheduled(event))
/**
* Extra Handlerbars template helpers
*/
Handlebars.registerHelper('isRowElemN', function(index, rowItems, n, options) {
return index % rowItems == n ? options.fn(this) : options.inverse(this)
})
/**
* Handle CRON jobs
* Where information is gathered and HTML and RSS is generated.
*/
addEventListener('scheduled', event => {
event.waitUntil(handleScheduled())
})
/**
* Serve the existing generated elements.
* CACHE is used to speed up the operation.
*/
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
@ -55,10 +71,8 @@ async function handleRequest(request) {
* Fetch all source feeds and generate the aggregated content
*
* TODO implement a faster way to fetch several sources
*
* @param {*} event
*/
async function handleScheduled(event) {
async function handleScheduled() {
let feeds = FEEDS.split(',')
let content = []
let sources = []
@ -167,6 +181,15 @@ function createFeed(items) {
function createHTML(items, sources) {
console.log(`[createHTML] building the HTML document`)
let template = Handlebars.templates['default']
let dateFormatter = new Intl.DateTimeFormat('pt-PT', { timeZone: 'UTC' })
for (let item of items) {
item.description = striptags(item.content).substring(0, 250) + '[...]'
item.formattedDate = item.pubDate
? dateFormatter.format(new Date(item.pubDate))
: ''
}
return template({
items: items,
sources: sources,

93
package-lock.json generated
View File

@ -11,7 +11,8 @@
"dependencies": {
"feed": "^4.2.2",
"handlebars": "^4.7.7",
"rss-parser": "^3.13.0"
"rss-parser": "^3.13.0",
"striptags": "3.2.0"
},
"devDependencies": {
"prettier": "^1.18.2",
@ -31,9 +32,9 @@
}
},
"node_modules/@cloudflare/workerd-darwin-64": {
"version": "1.20230518.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230518.0.tgz",
"integrity": "sha512-reApIf2/do6GjLlajU6LbRYh8gm/XcaRtzGbF8jo5IzyDSsdStmfNuvq7qssZXG92219Yp1kuTgR9+D1GGZGbg==",
"version": "1.20230717.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230717.0.tgz",
"integrity": "sha512-NVwwAYEIJmXGnQRnrCig2i4XAYFwPPFD+324fvQGWqUyfvBXKDYF3Jkw92ZginwdojYU1W+2l2qP7t6JE9Sw0g==",
"cpu": [
"x64"
],
@ -47,9 +48,9 @@
}
},
"node_modules/@cloudflare/workerd-darwin-arm64": {
"version": "1.20230518.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230518.0.tgz",
"integrity": "sha512-1l+xdbmPddqb2YIHd1YJ3YG/Fl1nhayzcxfL30xfNS89zJn9Xn3JomM0XMD4mk0d5GruBP3q8BQZ1Uo4rRLF3A==",
"version": "1.20230717.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230717.0.tgz",
"integrity": "sha512-toK0AadC35j0AiaXPYUq7yrAwPJR+5GLLXOXguHq5DHPqzzgD5pB9e4zlIKD+b09FAAEB6lO/k/eJSMYF/lfzA==",
"cpu": [
"arm64"
],
@ -63,9 +64,9 @@
}
},
"node_modules/@cloudflare/workerd-linux-64": {
"version": "1.20230518.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230518.0.tgz",
"integrity": "sha512-/pfR+YBpMOPr2cAlwjtInil0hRZjD8KX9LqK9JkfkEiaBH8CYhnJQcOdNHZI+3OjcY09JnQtEVC5xC4nbW7Bvw==",
"version": "1.20230717.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230717.0.tgz",
"integrity": "sha512-nxpfVzMkNLNa4hOvZ+Y7JHY13UkRf7or5tcQCRtfJ5sEhtCVGdfsOosQGnzw9G+045JUk0EKEY+gqNPRnt5u2w==",
"cpu": [
"x64"
],
@ -79,9 +80,9 @@
}
},
"node_modules/@cloudflare/workerd-linux-arm64": {
"version": "1.20230518.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230518.0.tgz",
"integrity": "sha512-q3HQvn3J4uEkE0cfDAGG8zqzSZrD47cavB/Tzv4mNutqwg6B4wL3ifjtGeB55tnP2K2KL0GVmX4tObcvpUF4BA==",
"version": "1.20230717.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230717.0.tgz",
"integrity": "sha512-jWVSJmOP0bMJ23xNONqP5x6TEDS9h/4nr0d0gULMpkfh6W9qNZNLpF8urNanXOqTM9p6rX55aBvzXsHs8Jctqw==",
"cpu": [
"arm64"
],
@ -95,9 +96,9 @@
}
},
"node_modules/@cloudflare/workerd-windows-64": {
"version": "1.20230518.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230518.0.tgz",
"integrity": "sha512-vNEHKS5gKKduNOBYtQjcBopAmFT1iScuPWMZa2nJboSjOB9I/5oiVsUpSyk5Y2ARyrohXNz0y8D7p87YzTASWw==",
"version": "1.20230717.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230717.0.tgz",
"integrity": "sha512-y9Ys8j22LMHaMdy31OyaV7Qz1Xca8MhzpBlpX4Ay6saECXYP1DZHHwtcW8pBqBU2zyGfEkErBQhyH130SSHFJg==",
"cpu": [
"x64"
],
@ -717,9 +718,9 @@
"dev": true
},
"node_modules/acorn": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
"integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@ -969,9 +970,9 @@
]
},
"node_modules/better-sqlite3": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.4.0.tgz",
"integrity": "sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.5.0.tgz",
"integrity": "sha512-vbPcv/Hx5WYdyNg/NbcfyaBZyv9s/NVbxb7yCeC5Bq1pVocNxeL2tZmSu3Rlm4IEOTjYdGyzWQgyx0OSdORBzw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@ -1762,9 +1763,9 @@
}
},
"node_modules/detect-libc": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
"dev": true,
"engines": {
"node": ">=8"
@ -3491,9 +3492,9 @@
}
},
"node_modules/miniflare": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.0.2.tgz",
"integrity": "sha512-tSwmK+JPwHsV2KR7/dSZFGtTF/2M30OShjPDY7e5lAxyGE8SkHqXn/ckjg2TVltc9B8rXCSffMnCfYW1pH7R4A==",
"version": "3.20230717.0",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20230717.0.tgz",
"integrity": "sha512-S1L3QKPEHAf7n+66b/JpWdsIusz/tlgHRnNcDztz0PfeDkWlfNPxDzrnMqv/cHm9MjzXqhz2XqFkWqZSfZJk5Q==",
"dev": true,
"dependencies": {
"acorn": "^8.8.0",
@ -3504,10 +3505,11 @@
"glob-to-regexp": "^0.4.1",
"http-cache-semantics": "^4.1.0",
"kleur": "^4.1.5",
"set-cookie-parser": "^2.6.0",
"source-map-support": "0.5.21",
"stoppable": "^1.1.0",
"undici": "^5.13.0",
"workerd": "^1.20230512.0",
"workerd": "1.20230717.0",
"ws": "^8.11.0",
"youch": "^3.2.2",
"zod": "^3.20.6"
@ -4614,9 +4616,9 @@
}
},
"node_modules/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@ -4637,6 +4639,12 @@
"randombytes": "^2.1.0"
}
},
"node_modules/set-cookie-parser": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==",
"dev": true
},
"node_modules/set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@ -5296,6 +5304,11 @@
"node": ">=0.10.0"
}
},
"node_modules/striptags": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz",
"integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw=="
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@ -6183,9 +6196,9 @@
}
},
"node_modules/workerd": {
"version": "1.20230518.0",
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230518.0.tgz",
"integrity": "sha512-VNmK0zoNZXrwEEx77O/oQDVUzzyDjf5kKKK8bty+FmKCd5EQJCpqi8NlRKWLGMyyYrKm86MFz0kAsreTEs7HHA==",
"version": "1.20230717.0",
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230717.0.tgz",
"integrity": "sha512-MTKF2u3pfqfpGlyaYe9BfZm8IoUwhQNe2vMWECYtv6Z8cGPJujVxcRe27ZT4l/G5lRijnnVeP3Ns3mMS9fjMsw==",
"dev": true,
"hasInstallScript": true,
"bin": {
@ -6195,11 +6208,11 @@
"node": ">=16"
},
"optionalDependencies": {
"@cloudflare/workerd-darwin-64": "1.20230518.0",
"@cloudflare/workerd-darwin-arm64": "1.20230518.0",
"@cloudflare/workerd-linux-64": "1.20230518.0",
"@cloudflare/workerd-linux-arm64": "1.20230518.0",
"@cloudflare/workerd-windows-64": "1.20230518.0"
"@cloudflare/workerd-darwin-64": "1.20230717.0",
"@cloudflare/workerd-darwin-arm64": "1.20230717.0",
"@cloudflare/workerd-linux-64": "1.20230717.0",
"@cloudflare/workerd-linux-arm64": "1.20230717.0",
"@cloudflare/workerd-windows-64": "1.20230717.0"
}
},
"node_modules/wrangler": {

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "worker-planet",
"version": "1.0.0",
"version": "1.1.0",
"description": "Generate a single feed of content from multiple RSS/Atom sources. Runs on Cloudflare Workers.",
"main": "./index.js",
"scripts": {
@ -23,6 +23,7 @@
"dependencies": {
"feed": "^4.2.2",
"handlebars": "^4.7.7",
"rss-parser": "^3.13.0"
"rss-parser": "^3.13.0",
"striptags": "3.2.0"
}
}

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{page_title}}</title>
<meta property="og:title" content="{{page_title}}" />
<meta property="og:description" content="{{page_description}}" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-hashes' 'sha256-2drSom6Fplo+OFvNUVKnb+wZyrq6v1lOEkVaexi/Fw4=' 'sha256-JeTyInHTbNelTT9AfPH8MBCkB4sU4yMQjoaIQNeLIZ8='; style-src https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css;" />
<link rel="alternate" type="application/rss+xml" title="Planet RSS feed" href="/rss" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
</head>
<body>
<nav class="container">
<ul>
<li></li>
</ul>
<ul>
<li><a href="/rss">RSS</a></li>
<li><a href="https://github.com/dethos/worker-planet">Code</a></li>
<li><a href="#open" onclick="document.getElementById('sources').setAttribute('open', '');">Sources</a></li>
</ul>
</nav>
<main class="container-fluid">
<div class="grid">
<div class="container">
<hgroup>
<h1>{{page_title}}</h1>
<h3>{{page_description}}</h3>
</hgroup>
</div>
</div>
{{#each items}}
{{#isRowElemN @index 4 0 }}
<div class="grid">
{{/isRowElemN}}
<article>
<hgroup>
<h3>{{title}}</h2>
<small>{{formattedDate}}</small>
</hgroup>
<p>
{{{description}}}
</p>
<p>
<a href="{{link}}">More</a>
</p>
</article>
{{#isRowElemN @index 4 3}}
</div>
{{/isRowElemN}}
{{/each}}
</main>
<footer>
<div class="container">
<p>
All rights belong to the original authors. Powered by <a href="https://github.com/dethos/worker-planet">worker-planet</a>.
</p>
</div>
</footer>
<dialog id="sources">
<article>
<header>
<a href="#close" aria-label="Close" class="close" onclick="document.getElementById('sources').removeAttribute('open');"></a>
Sources
</header>
<p>The content of this page is fetched from the following sources:</p>
<p>
<ul>
{{#each sources}}
<li><a href="{{link}}">{{name}}</a></li>
{{/each}}
</ul>
</p>
</article>
</dialog>
</body>
</html>

View File

@ -0,0 +1,61 @@
var Handlebars = require("handlebars/runtime"); var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['headlines'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
};
return ((stack1 = (lookupProperty(helpers,"isRowElemN")||(depth0 && lookupProperty(depth0,"isRowElemN"))||alias2).call(alias1,(data && lookupProperty(data,"index")),4,0,{"name":"isRowElemN","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":35,"column":8},"end":{"line":37,"column":23}}})) != null ? stack1 : "")
+ " <article>\n <hgroup>\n <h3>"
+ alias4(((helper = (helper = lookupProperty(helpers,"title") || (depth0 != null ? lookupProperty(depth0,"title") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data,"loc":{"start":{"line":40,"column":18},"end":{"line":40,"column":27}}}) : helper)))
+ "</h2>\n <small>"
+ alias4(((helper = (helper = lookupProperty(helpers,"formattedDate") || (depth0 != null ? lookupProperty(depth0,"formattedDate") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"formattedDate","hash":{},"data":data,"loc":{"start":{"line":41,"column":21},"end":{"line":41,"column":38}}}) : helper)))
+ "</small>\n </hgroup>\n <p>\n "
+ ((stack1 = ((helper = (helper = lookupProperty(helpers,"description") || (depth0 != null ? lookupProperty(depth0,"description") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"description","hash":{},"data":data,"loc":{"start":{"line":44,"column":14},"end":{"line":44,"column":31}}}) : helper))) != null ? stack1 : "")
+ "\n </p>\n <p>\n <a href=\""
+ alias4(((helper = (helper = lookupProperty(helpers,"link") || (depth0 != null ? lookupProperty(depth0,"link") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"link","hash":{},"data":data,"loc":{"start":{"line":47,"column":23},"end":{"line":47,"column":31}}}) : helper)))
+ "\">More</a>\n </p>\n </article>\n"
+ ((stack1 = (lookupProperty(helpers,"isRowElemN")||(depth0 && lookupProperty(depth0,"isRowElemN"))||alias2).call(alias1,(data && lookupProperty(data,"index")),4,3,{"name":"isRowElemN","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":50,"column":8},"end":{"line":52,"column":23}}})) != null ? stack1 : "");
},"2":function(container,depth0,helpers,partials,data) {
return " <div class=\"grid\">\n";
},"4":function(container,depth0,helpers,partials,data) {
return " </div>\n";
},"6":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
};
return " <li><a href=\""
+ alias4(((helper = (helper = lookupProperty(helpers,"link") || (depth0 != null ? lookupProperty(depth0,"link") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"link","hash":{},"data":data,"loc":{"start":{"line":72,"column":27},"end":{"line":72,"column":35}}}) : helper)))
+ "\">"
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":72,"column":37},"end":{"line":72,"column":45}}}) : helper)))
+ "</a></li>\n";
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
};
return "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>"
+ alias4(((helper = (helper = lookupProperty(helpers,"page_title") || (depth0 != null ? lookupProperty(depth0,"page_title") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"page_title","hash":{},"data":data,"loc":{"start":{"line":7,"column":11},"end":{"line":7,"column":25}}}) : helper)))
+ "</title>\n <meta property=\"og:title\" content=\""
+ alias4(((helper = (helper = lookupProperty(helpers,"page_title") || (depth0 != null ? lookupProperty(depth0,"page_title") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"page_title","hash":{},"data":data,"loc":{"start":{"line":8,"column":39},"end":{"line":8,"column":53}}}) : helper)))
+ "\" />\n <meta property=\"og:description\" content=\""
+ alias4(((helper = (helper = lookupProperty(helpers,"page_description") || (depth0 != null ? lookupProperty(depth0,"page_description") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"page_description","hash":{},"data":data,"loc":{"start":{"line":9,"column":45},"end":{"line":9,"column":65}}}) : helper)))
+ "\" />\n <meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'self' 'unsafe-hashes' 'sha256-2drSom6Fplo+OFvNUVKnb+wZyrq6v1lOEkVaexi/Fw4=' 'sha256-JeTyInHTbNelTT9AfPH8MBCkB4sU4yMQjoaIQNeLIZ8='; style-src https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css;\" />\n <link rel=\"alternate\" type=\"application/rss+xml\" title=\"Planet RSS feed\" href=\"/rss\" />\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css\">\n </head>\n <body>\n <nav class=\"container\">\n <ul>\n <li></li>\n </ul>\n <ul>\n <li><a href=\"/rss\">RSS</a></li>\n <li><a href=\"https://github.com/dethos/worker-planet\">Code</a></li>\n <li><a href=\"#open\" onclick=\"document.getElementById('sources').setAttribute('open', '');\">Sources</a></li>\n </ul>\n </nav>\n <main class=\"container-fluid\">\n <div class=\"grid\">\n <div class=\"container\">\n <hgroup>\n <h1>"
+ alias4(((helper = (helper = lookupProperty(helpers,"page_title") || (depth0 != null ? lookupProperty(depth0,"page_title") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"page_title","hash":{},"data":data,"loc":{"start":{"line":29,"column":16},"end":{"line":29,"column":30}}}) : helper)))
+ "</h1>\n <h3>"
+ alias4(((helper = (helper = lookupProperty(helpers,"page_description") || (depth0 != null ? lookupProperty(depth0,"page_description") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"page_description","hash":{},"data":data,"loc":{"start":{"line":30,"column":16},"end":{"line":30,"column":36}}}) : helper)))
+ "</h3>\n </hgroup>\n </div>\n </div>\n"
+ ((stack1 = lookupProperty(helpers,"each").call(alias1,(depth0 != null ? lookupProperty(depth0,"items") : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":34,"column":6},"end":{"line":53,"column":15}}})) != null ? stack1 : "")
+ " </main>\n <footer>\n <div class=\"container\">\n <p>\n All rights belong to the original authors. Powered by <a href=\"https://github.com/dethos/worker-planet\">worker-planet</a>.\n </p>\n </div>\n </footer>\n <dialog id=\"sources\">\n <article>\n <header>\n <a href=\"#close\" aria-label=\"Close\" class=\"close\" onclick=\"document.getElementById('sources').removeAttribute('open');\"></a>\n Sources\n </header>\n <p>The content of this page is fetched from the following sources:</p>\n <p>\n <ul>\n"
+ ((stack1 = lookupProperty(helpers,"each").call(alias1,(depth0 != null ? lookupProperty(depth0,"sources") : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":71,"column":12},"end":{"line":73,"column":21}}})) != null ? stack1 : "")
+ " </ul>\n </p>\n </article>\n </dialog>\n </body>\n</html>";
},"useData":true});