on master help sign in
Merge remote-tracking branch 'upstream/master'
d8ea4f8Gaeel Bradshaw-Rodriguez
.github/ISSUE_TEMPLATE/name-of-website.mdnew file+9
@@ -0,0 +1,9 @@
1+---
2+name: name.of.website
3+about: Describe this issue template's purpose here.
4+
5+---
6+
7+```
8+Position of webring icon on site: ex: footer, header
9+```
404.htmlnew file+18
@@ -0,0 +1,18 @@
1+---
2+permalink: /404.html
3+---
4+<!DOCTYPE html>
5+<html lang="en">
6+<head>
7+ <meta charset="utf-8">
8+ <title>Loading..</title>
9+</head>
10+<body style='background:black'>
11+ <script>
12+ const parts = window.location.pathname.split("/")
13+ const target = parts.slice(-1);
14+ parts.splice(-1)
15+ document.location.href = parts.join("/")+"/#"+target
16+ </script>
17+</body>
18+</html>
README.md+47−8
@@ -1,25 +1,64 @@
1# Webring
1# Webring
2
2
3−This [webring](http://wiki.xxiivv.com/webring) is an attempt to inspire artists and developers to create and maintain their own website and share traffic among each other.
3+This [webring](https://wiki.xxiivv.com/webring) is an attempt to inspire artists & developers to create and maintain their own website and share traffic among each other. The webring's aim is to share hand-crafted websites such as **diaries, wikis & portfolios**. To add yourself to the ring, submit an [edit](https://github.com/XXIIVV/webring/edit/master/scripts/sites.js) to this repository.
4−
5−The webring's aim is to share personal websites such as diaries, wikis, bookmark lists, portfolios. To add yourself to the ring, submit a [pull request](https://github.com/XXIIVV/webring/edit/master/index.html) to this repository.
6
4
7## Join the webring
5## Join the webring
8
6
9−Add your website url to the [index.html](https://github.com/XXIIVV/webring/edit/master/index.html), and add the webring icon to your website html. Alternatively, if you your website has a dark background, use `icon.white.svg`. If your website is complaning about *https*, go ahead and host the icon yourself.
10−
11```
7```
12−<a href='http://webring.xxiivv.com/#random' target='_blank'><img src='http://webring.xxiivv.com/icon.black.svg'/></a>
8+<a href='https://webring.xxiivv.com/#random' target='_blank'><img src='https://webring.xxiivv.com/icon.black.svg'/></a>
13```
9```
14
10
11+- Add the webring icon to your website html.
12+- Add your website information to the [sites.js](https://github.com/XXIIVV/webring/edit/master/scripts/sites.js) file. The `url` key is required, but you can also include `title`, `type`, `author`, `contact`, `rss`, and `feed`.
13+- Submit a Pull Request with **the location of the webring icon** on your site.
14+
15+Alternatively, if you your website has a dark background, use `icon.white.svg`. If your website is complaining about *https*, go ahead and host the icon yourself. **Single page websites, and websites acting only as portals to other social platforms, will be rejected**.
16+
15### Circular Linking
17### Circular Linking
16
18
17Instead of linking to the directory, you can also link to the next link in the ring by adding parts of your site or domain in the hash of the request url:
19Instead of linking to the directory, you can also link to the next link in the ring by adding parts of your site or domain in the hash of the request url:
18
20
19```
21```
20−<a href='http://webring.xxiivv.com/#wiki.xxiivv' target='_blank'><img src='http://webring.xxiivv.com/icon.black.svg'/></a>
22+<a href='https://webring.xxiivv.com/#wiki.xxiivv' target='_blank' rel="noopener noreferrer"><img src='https://webring.xxiivv.com/icon.black.svg'/></a>
23+```
24+
25+### Joining the hallway
26+
27+[The Hallway](https://webring.xxiivv.com/hallway.html) is a decentralized forum using [twtxt](https://twtxt.readthedocs.io/en/stable/user/twtxtfile.html) feeds.
28+
29+To join, create a `.txt` file on your site, and add its URL to [your webring entry](https://github.com/XXIIVV/Webring/blob/master/scripts/sites.js) as `feed:`. The content of the file should be a dateISO string, a tab(or 3 spaces) and a message:
30+
31+```
32+2016-02-04T13:30:00+01:00 You can really go crazy here! ┐(゚∀゚)┌
33+2016-02-03T23:05:00+01:00 @<example http://example.org/twtxt.txt> welcome to twtxt!
34+2016-02-01T11:00:00+01:00 This is just another example.
35+2015-12-12T12:00:00+01:00 Fiat lux!
36+```
37+
38+This file needs to be accessible via CORS, so if you're self-hosting, please make sure to allow the `webring.xxiivv.com` origin.
39+Here's an example of how to do so with Nginx:
40+
41+```
42+location / {
43+ index index.html;
44+ if ($request_method = 'GET') {
45+ add_header 'Access-Control-Allow-Origin' 'https://webring.xxiivv.com';
46+ add_header 'Access-Control-Allow-Methods' 'GET';
47+ add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
48+ add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
49+ }
50+}
21```
51```
22
52
53+You don't need to allow all origins nor allow any other methods rather than GET, doing so can harm the security of your website, please read about [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) if you intend to do anything more complicated.
54+
55+## API
56+
57+This repository does not contain mature API capabilities, but there are a couple ways to request a list of sites and other information currently in the webring.
58+
59+- Request [sites.js on xxiivv](https://webring.xxiivv.com/scripts/sites.js) or [sites.js on github](https://raw.githubusercontent.com/XXIIVV/webring/master/scripts/sites.js) and parse it. An example can be seen [here](https://gist.github.com/ckipp01/2ab7ac42e2837b4359efeb76eb49bb54).
60+- If you'd like a already parsed list, [webring-checker.now.sh/sites](https://webring-checker.now.sh/sites) will return an json array of the site objects in the webring. See [webring-checker.now.sh](https://webring-checker.now.sh) for more info on what else is available.
61+
23## Need Help?
62## Need Help?
24
63
25−The ring master is [@neauoire](http://twitter.com/neauoire), but any member of the network is also welcome to join this repository as a collaborator to help manage new links and Pull Requests. Read more [here](http://wiki.xxiivv.com/webring).
64+The ring is managed by [@neauoire](https://twitter.com/neauoire), but any member of the network is also welcome to join this repository as a collaborator to help manage new links and Pull Requests. Read more [here](https://wiki.xxiivv.com/webring).
hallway.htmlnew file+19
@@ -0,0 +1,19 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+<head>
4+ <meta charset="utf-8">
5+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6+ <link rel="stylesheet" type="text/css" href="links/fonts.css"/>
7+ <link rel="stylesheet" type="text/css" href="links/main.css"/>
8+ <script src="scripts/hallway.js"></script>
9+ <script src="scripts/sites.js"></script>
10+ <title>Hallway</title>
11+</head>
12+<body>
13+ <script>
14+ const hallway = new Hallway(sites);
15+ hallway.install(document.body);
16+ hallway.start();
17+ </script>
18+</body>
19+</html>
index.html+7−28
@@ -3,39 +3,18 @@
3<head>
3<head>
4 <meta charset="utf-8">
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6+ <link rel="stylesheet" type="text/css" href="links/fonts.css"/>
7+ <link rel="stylesheet" type="text/css" href="links/main.css"/>
6− <script src="portal.js"></script>
8+ <script src="scripts/portal.js"></script>
9+ <script src="scripts/sites.js"></script>
10+ <script src="scripts/opml.js"></script>
7 <title>Webring</title>
11 <title>Webring</title>
8</head>
12</head>
9<body>
13<body>
10 <script>
14 <script>
11− // protocole://url.domain.ext/
12− var sites = [
13− "https://wiki.xxiivv.com/",
14− "http://estevancarlos.com/",
15− "https://electro.pizza/",
16− "http://xla.wiki/",
17− "https://monochromatic.co/",
18− "https://joshavanier.github.io/",
19− "http://kaemura.com/",
20− "https://liamcooke.com/",
21− "https://electricgecko.de/",
22− "https://wichniow.ski/",
23− "https://hraew.autophagy.io/",
24− "https://anxl.faith",
25− "https://xvw.github.io",
26− "https://heracl.es",
27− "https://felipecortez.net",
28− "http://luminghao.com",
29− "https://spaceshipsin.space/"
30− // Don't forget the comma!
31− ]
32− var portal = new Portal(sites);
15+ const portal = new Portal(sites);
16+ portal.install(document.body);
33 portal.start();
17 portal.start();
34 </script>
18 </script>
35− <style>
36− body { background:#eee; padding:30px; }
37− body a { color:#000; display: inline-block; padding:0px 5px; line-height: 20px}
38− body a:hover { color:#eee; background:#000; text-decoration: none; }
39− </style>
40</body>
19</body>
41</html>
20</html>
links/fonts.cssnew file+15
@@ -0,0 +1,15 @@
1+/* Input Mono */
2+
3+@font-face {
4+ font-family: 'input_mono_regular';
5+ src: url('../media/fonts/input_mono_regular.ttf') format('truetype');
6+ font-weight: normal;
7+ font-style: normal;
8+}
9+
10+@font-face {
11+ font-family: 'input_mono_medium';
12+ src: url('../media/fonts/input_mono_medium.ttf') format('truetype');
13+ font-weight: normal;
14+ font-style: normal;
15+}
links/main.cssnew file+62
@@ -0,0 +1,62 @@
1+* { margin:0;padding:0;border:0;outline:0;text-decoration:none;font-weight:inherit;font-style:inherit;color:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;list-style:none;border-collapse:collapse;border-spacing:0; -webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
2+
3+body { background:#f4f4f4; padding:0px; margin:0px; }
4+body div { display:block; background: #111;min-height: calc(100vh - 60px);width: calc(100vw - 60px);margin: 15px;color: #333;padding: 15px;font-family:'input_mono_regular', 'Monospaced','Courier New',courier;font-size:11px; position: relative; border-radius: 2px; color:white;}
5+body div ul { max-width: 800px; border-bottom: 2px solid white;padding-bottom: 15px;margin-bottom:15px; overflow: hidden;}
6+body div p { display:block; padding:0px 5px; line-height: 20px; max-width: 600px}
7+body div p a { text-decoration: underline;}
8+body div p a:hover { text-decoration: underline; }
9+body div p b { font-weight: normal; font-family: 'input_mono_medium' }
10+body div p.readme { max-width:500px; }
11+
12+/* Hallway */
13+
14+body #hallway #entries { width: calc(100vw - 300px) !important; }
15+body #hallway #sidebar { position: absolute;top: 0px;right: 0px;padding: 0px 15px;width: 200px; }
16+
17+body #hallway #sidebar > * { border-bottom: 0px; line-height: 16px; padding-bottom: 0px }
18+body #hallway #sidebar > * li { text-transform: lowercase; }
19+body #hallway #sidebar > * li.selected { background: white; color:black; }
20+body #hallway #sidebar > * li:hover { cursor: pointer; }
21+body #hallway #sidebar > * li span.right { color:#555; float:right; }
22+body #hallway #sidebar > * li span.right:after { content: " msgs"; }
23+body #hallway #sidebar #channels li:before { content:'/'; }
24+body #hallway #sidebar #users li:before { content:'@'; }
25+
26+body #hallway .entry { line-height: 16px; display: block; position: relative; padding-left:100px; }
27+body #hallway .entry .date { color: #555;display: block;float:right; margin-left:20px; padding-bottom: 16px;}
28+body #hallway .entry .author { font-family: 'input_mono_medium'; position: absolute;left: 0px; }
29+body #hallway .entry .body { display: block;border-bottom: 1px solid #333;padding-bottom: 10px;margin-bottom: 10px; }
30+body #hallway .entry .body a { text-decoration: underline; }
31+body #hallway .entry .body .channel { display: inline-block;border-radius: 1px;font-family: 'input_mono_medium';color: #777; }
32+body #hallway .entry .body .user { display: inline-block;border-radius: 1px;font-family: 'input_mono_medium';color: #777; }
33+body #hallway .entry.highlight .author { color:#555; }
34+body #hallway .entry:hover .author { color:#ccc; }
35+body #hallway .entry:hover .date { color:#ccc; }
36+body #hallway .entry:hover .body { color:#ccc; border-bottom-color: #555 }
37+body #hallway p { padding-left: 60px;background-image: url(../icon.white.svg);background-size: 40px;background-repeat: no-repeat;background-position: 0px 0px; }
38+body #hallway ul { max-width: 100%; }
39+body #hallway ul li:last-child { border-bottom:0px; }
40+
41+/* Portal */
42+
43+body #portal #icon { display: block;width: 30px;height: 30px;background-image: url(../icon.white.svg);background-size: cover;background-repeat: no-repeat;position: absolute;bottom: 15px;right:15px;background-position: center;padding:0px }
44+body #portal ul { columns:3; }
45+body #portal ul li { display:block; padding-left:5px; text-overflow: ellipsis; text-transform: lowercase;}
46+body #portal ul li.wiki::after { content:"<wiki>"; color:#555; }
47+body #portal ul li.blog::after { content:"<blog>"; color:#555; }
48+body #portal ul li a { line-height: 20px; padding:0px 5px; text-decoration: none;}
49+body #portal ul li a:visited { color:#ccc; text-decoration: none;}
50+body #portal ul li a:hover { text-decoration: underline; }
51+
52+@media (max-width: 730px) {
53+ body #portal ul li span.counter { display: none }
54+}
55+@media (max-width: 630px) {
56+ body #portal ul { columns: 2 !important; }
57+ body #portal ul li span.counter { display: none }
58+}
59+@media (max-width: 530px) {
60+ body #portal ul { columns: 1 !important; }
61+ body #portal ul li span.counter { display: inline }
62+}
media/fonts/input_mono_medium.ttfnew file

Binary file not shown.

media/fonts/input_mono_regular.ttfnew file

Binary file not shown.

portal.jsdeleted−74
@@ -1,74 +0,0 @@
1−function Portal(sites)
2−{
3− this.el = document.createElement("pre");
4− this.sites = sites;
5−
6− this.install = function()
7− {
8− document.body.appendChild(this.el);
9− }
10−
11− this.start = function()
12− {
13− this.install();
14− this.el.innerHTML = window.location.hash ? this.redirect() : this.directory();
15− }
16−
17− this.directory = function()
18− {
19− var html = ""
20−
21− for(id in this.sites){
22− var site = this.sites[id]
23− html += `${id}) <a href='${site}'>${site}</a>\n`
24− }
25− html += `\n<a href='#random' onClick="portal.reload('random')">Random</a> | <a href='https://github.com/XXIIVV/webring'>Information</a>`
26−
27− return html
28− }
29−
30− this.reload = function()
31− {
32− setTimeout(()=>{ window.location.reload() },500)
33− }
34−
35− this.location = function()
36− {
37− return window.location.hash.replace("#","").trim();
38− }
39−
40− this.locate = function()
41− {
42− var hash = this.location();
43−
44− if(hash == "random"){
45− return Math.floor(Math.random()*this.sites.length)
46− }
47−
48− for(var id in this.sites){
49− var site = this.sites[id];
50− if(site.indexOf(hash) >-1){
51− return parseInt(id)
52− }
53− }
54− return -1
55− }
56−
57− this.next = function(loc)
58− {
59− return loc == this.sites.length-1 ? this.sites[0] : this.sites[loc+1];
60− }
61−
62− this.redirect = function()
63− {
64− var location = this.locate();
65− var target = this.next(location);
66−
67− var html = ""
68−
69− html = `Redirecting to ${target}
70− <meta http-equiv="refresh" content="3; url=${target}">
71−<a href='' onClick="window.location.reload()">Directory</a> | <a href='#${target}' onClick="portal.reload('random')">Skip</a> | <a href='#random' onClick="portal.reload('random')">Random</a> | <a href='https://github.com/XXIIVV/webring'>Information</a>`
72− return html
73− }
74−}
scripts/hallway.jsnew file+192
@@ -0,0 +1,192 @@
1+'use strict'
2+
3+function Hallway (sites) {
4+ const feeds = {}
5+ this.sites = sites
6+ this.el = document.createElement('div')
7+ this.el.id = 'hallway'
8+ this.filter = window.location.hash.replace('#', '').trim()
9+ this.cache = null
10+
11+ this.install = function (host) {
12+ host.appendChild(this.el)
13+ this.findFeeds()
14+ }
15+
16+ this.start = function () {
17+ this.el.innerHTML = 'hello.'
18+ this.fetchFeeds()
19+ }
20+
21+ this.refresh = function (feeds = this.cache) {
22+ const entries = this.findEntries(feeds)
23+ const channels = this.findChannels(entries)
24+ const users = this.findUsers(entries)
25+
26+ this.el.innerHTML = `
27+ <ul id='entries'>
28+ ${entries.filter((val, id) => { return !(this.filter && val.author !== this.filter && val.channel !== this.filter) }).filter((val, id) => { return id < 50 }).reduce((acc, val, id) => { return acc + this.templateEntry(val) + '\n' }, '')}
29+ </ul>
30+ <div id='sidebar'>
31+ <ul id='channels'>
32+ <li onclick='filter("")' class='${hallway.filter === '' ? 'selected' : ''}'><a href='#'>hallway <span class='right'>${entries.length}</span></a></li>
33+ ${Object.keys(channels).reduce((acc, val, id) => { return acc + `<li onclick='filter("${val}")' class='${hallway.filter === val ? 'selected' : ''}'><a href='#${val}'>${val} <span class='right'>${channels[val]}</span></a></li>\n` }, '')}
34+ </ul>
35+ <ul id='users'>
36+ ${Object.keys(users).reduce((acc, val, id) => { return acc + `<li onclick='filter("${val}")' class='${hallway.filter === val ? 'selected' : ''}' href='#${val}'>${val} <span class='right'>${users[val]}</span></li>\n` }, '')}
37+ </ul>
38+ </div>
39+ <p>The <b>Hallway</b> is a decentralized forum, to join the conversation, simply create yourself a <a href="https://twtxt.readthedocs.io/en/stable/user/twtxtfile.html">twtxt</a> feed and <a href="https://github.com/XXIIVV/Webring/">add it</a> to your entry in the <a href="index.html">webring</a>.</p>`
40+
41+ if (feeds) {
42+ this.cache = feeds
43+ }
44+ }
45+
46+ // Entries
47+
48+ this.findChannels = function (entries) {
49+ const channels = {}
50+ for (const id in entries) {
51+ const entry = entries[id]
52+ if (!entry.channel) { continue }
53+ channels[entry.channel] = channels[entry.channel] ? channels[entry.channel] + 1 : 1
54+ }
55+ return channels
56+ }
57+
58+ this.findUsers = function (entries) {
59+ const users = {}
60+ for (const id in entries) {
61+ const entry = entries[id]
62+ users[entry.author] = users[entry.author] ? users[entry.author] + 1 : 1
63+ }
64+ return users
65+ }
66+
67+ this.findEntries = function (feeds) {
68+ const a = []
69+ for (const id in feeds) {
70+ for (const i in feeds[id].content) {
71+ a.push(feeds[id].content[i])
72+ }
73+ }
74+ return a.sort(compare)
75+ }
76+
77+ this.templateEntry = function (entry) {
78+ const words = entry.body.split(' ')
79+ const users = []
80+ const channels = []
81+ const tags = []
82+ const links = []
83+
84+ // Channels
85+ for (const id in words) {
86+ if (words[id].substr(0, 1) === '@') { users.push(words[id]) }
87+ if (words[id].substr(0, 1) === '/') { channels.push(words[id]) }
88+ if (words[id].substr(0, 1) === '#') { tags.push(words[id]) }
89+ if (words[id].substr(0, 4) === 'http') { links.push(words[id]) }
90+ }
91+
92+ for (const id in users) { entry.body = entry.body.replace(users[id], `<span class='user'>${users[id]}</span>`) }
93+ for (const id in channels) { entry.body = entry.body.replace(channels[id], `<span class='channel'>${channels[id]}</span>`) }
94+ for (const id in tags) { entry.body = entry.body.replace(tags[id], `<span class='tag'>${tags[id]}</span>`) }
95+ for (const id in links) { entry.body = entry.body.replace(links[id], `<a href='${links[id]}'>${links[id].replace('https://', '').replace('http://', '')}</a>`) }
96+
97+ const filter = window.location.hash.substr(1).replace(/\+/g, ' ').toLowerCase()
98+ const highlight = filter === entry.author.toLowerCase()
99+ const origin = feeds[entry.author].path
100+
101+ return `<li class='entry ${highlight ? 'highlight' : ''}'><span class='date'>${timeAgo(Date.parse(entry.date))}</span> <a class='author' href='${origin}' target='_blank'>${entry.author}</a> <span class='body'>${entry.body}</span></li>`
102+ }
103+
104+ // Feeds
105+
106+ this.findFeeds = function () {
107+ console.log('Finding feeds..')
108+ for (const id in sites) {
109+ const site = sites[id]
110+ if (site.feed && site.author) {
111+ feeds[site.author] = { path: site.feed }
112+ }
113+ }
114+ console.log(`Found ${Object.keys(feeds).length} feeds.`)
115+ }
116+
117+ this.fetchFeeds = function () {
118+ console.log(`Fetching ${Object.keys(feeds).length} feeds..`)
119+ for (const id in feeds) {
120+ this.fetchFeed(id, feeds[id])
121+ }
122+ }
123+
124+ this.fetchFeed = function (id, feed) {
125+ console.log(`Fetching ${id}(${feed.path})..`)
126+ fetch(feed.path, { cache: 'no-store' }).then(x => x.text()).then((content) => {
127+ feeds[id].content = parseFeed(id, content)
128+ this.refresh(feeds)
129+ }).catch((err) => {
130+ console.warn(`${id}`, err)
131+ })
132+ }
133+
134+ // Utils
135+
136+ function compare (a, b) {
137+ return a.offset < b.offset ? -1 : a.offset > b.offset ? 1 : 0
138+ }
139+
140+ function parseFeed (author, feed) {
141+ const lines = feed.split('\n')
142+ const entries = []
143+ for (const id in lines) {
144+ const line = lines[id].trim()
145+ if (line === '') { continue }
146+ const parts = line.replace(' ', '\t').split('\t')
147+ const date = parts[0].trim()
148+ const body = escapeHtml(parts[1].trim()).trim()
149+ const channel = body.substr(0, 1) === '/' ? body.split(' ')[0].substr(1).toLowerCase() : body.substr(0, 1) === '@' ? 'veranda' : 'lobby'
150+ const offset = new Date() - new Date(date)
151+ entries.push({ date, body, author, offset, channel })
152+ }
153+ return entries
154+ }
155+
156+ function timeAgo (dateParam) {
157+ const date = typeof dateParam === 'object' ? dateParam : new Date(dateParam)
158+ const DAY_IN_MS = 86400000 // 24 * 60 * 60 * 1000
159+ const today = new Date()
160+ const yesterday = new Date(today - DAY_IN_MS)
161+ const seconds = Math.round((today - date) / 1000)
162+ const minutes = Math.round(seconds / 60)
163+ const isToday = today.toDateString() === date.toDateString()
164+ const isYesterday = yesterday.toDateString() === date.toDateString()
165+
166+ if (seconds < 5) {
167+ return 'just now'
168+ } else if (seconds < 60) {
169+ return `${seconds} seconds ago`
170+ } else if (seconds < 90) {
171+ return 'a minute ago'
172+ } else if (minutes < 60) {
173+ return `${minutes} minutes ago`
174+ } else if (isToday) {
175+ return `${Math.floor(minutes / 60)} hours ago`
176+ } else if (isYesterday) {
177+ return 'yesterday'
178+ }
179+
180+ return `${Math.floor(minutes / 1440)} days ago`
181+ }
182+
183+ function escapeHtml (unsafe) {
184+ return unsafe.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;')
185+ }
186+}
187+
188+function filter (name) {
189+ window.location.hash = name
190+ hallway.filter = window.location.hash.replace('#', '').trim()
191+ hallway.refresh()
192+}
scripts/opml.jsnew file+11
@@ -0,0 +1,11 @@
1+let outlines = sites.filter((site) => !!site.rss).map((site) => {
2+ return `<outline type="rss" xmlUrl="${site.rss}" title="${site.title}"/>`
3+})
4+
5+let template = `<?xml version="1.0" encoding="UTF-8"?><opml version="1.1"><head></head><body>${outlines.join('')}</body></opml>`
6+
7+window.onload = function () {
8+ let a = document.getElementById('opml')
9+ a.setAttribute('href', 'data:text/plain;charset=utf-8,' + window.encodeURIComponent(template))
10+ a.setAttribute('download', 'webring.opml')
11+}
scripts/portal.jsnew file+69
@@ -0,0 +1,69 @@
1+'use strict'
2+
3+function Portal (sites) {
4+ this.el = document.createElement('div')
5+ this.el.id = 'portal'
6+ this.sites = sites
7+
8+ // Templates
9+
10+ function _readme () {
11+ return `<p class='readme'>This webring is an attempt to inspire artists & developers to build their own website and share traffic among each other. The ring welcomes personalized websites such as <b>diaries, wikis & portfolios</b>.</p><p>To add yourself to the ring, submit a <a href='https://github.com/XXIIVV/webring/edit/master/scripts/sites.js' target='_blank'>Pull Request</a>.<br />If you found a broken link, please <a href='https://github.com/XXIIVV/webring/issues/new' target='_blank'>report it</a>.</p>`
12+ }
13+
14+ function _buttons () {
15+ return `<p class='buttons'><a href='#random' onClick="portal.reload('random')">Random</a> | <a href='https://github.com/XXIIVV/webring'>Information</a> <a id='icon' href='#random' onClick="portal.reload('random')"></a> | <a href="hallway.html">The Hallway</a> | <a id="opml">OPML</a></p>`
16+ }
17+
18+ function _directory (sites) {
19+ return `
20+ <ul>${sites.reduce((acc, site, id) => { return `${acc}<li class='${site.type}'><span class='counter'>${id})</span><a href='${site.url}'>${_name(site)}</a></li>` }, '')}</ul>\n${_readme()}${_buttons()}`
21+ }
22+
23+ function _name (site) {
24+ return site.title ? site.title : `${site.url.split('//')[1]}`
25+ }
26+
27+ function _redirect (site) {
28+ return `<p>Redirecting to <a href="${site.url}">${site.url}</a></p><meta http-equiv="refresh" content="3; url=${site.url}">
29+ <p class='buttons'><a href='#' onClick="portal.reload('')">Directory</a> | <a href='#${site.url}' onClick="portal.reload('random')">Skip</a> | <a href='#random' onClick="portal.reload('random')">Random</a> | <a href='https://github.com/XXIIVV/webring'>Information</a> <a id='icon' href='#random' onClick="portal.reload('random')"></a></p>`
30+ }
31+
32+ //
33+
34+ this.install = function (host) {
35+ host.appendChild(this.el)
36+ }
37+
38+ this.start = function () {
39+ this.el.innerHTML = window.location.hash && window.location.hash.length > 4 ? _redirect(this.next()) : _directory(this.sites)
40+ }
41+
42+ this.reload = function () {
43+ setTimeout(() => { window.location.reload() }, 500)
44+ }
45+
46+ this.navigate = function (target) {
47+ setTimeout(() => { window.location.href = target }, 3000)
48+ }
49+
50+ this.locate = function () {
51+ const hash = window.location.hash.replace('#', '').trim()
52+
53+ if (hash == 'random') {
54+ return Math.floor(Math.random() * this.sites.length)
55+ }
56+
57+ for (const id in this.sites) {
58+ const site = this.sites[id]
59+ if (site.url.indexOf(hash) > -1) {
60+ return parseInt(id)
61+ }
62+ }
63+ return -1
64+ }
65+
66+ this.next = function (loc = this.locate()) {
67+ return loc == this.sites.length - 1 ? this.sites[0] : this.sites[loc + 1]
68+ }
69+}
scripts/sites.jsnew file+91
@@ -0,0 +1,91 @@
1+'use strict'
2+
3+// Don't forget the comma! No trailing slashes
4+// protocol://url.domain.ext
5+
6+const sites = [
7+ { url: 'https://wiki.xxiivv.com', title: 'xxiivv', type: 'wiki', author: 'neauoire', contact: 'aliceffekt@gmail.com', rss: 'https://wiki.xxiivv.com/links/rss.xml', feed: 'https://wiki.xxiivv.com/twtxt.txt' },
8+ { url: 'http://estevancarlos.com' },
9+ { url: 'https://electro.pizza', title: 'electro pizza', type: 'blog', author: 'rho', rss: 'https://electro.pizza/feed.xml', feed: 'https://electro.pizza/twtxt.txt' },
10+ { url: 'https://ianjbattaglia.co' },
11+ { url: 'https://avanier.now.sh/wiki', title: 'Ath', type: 'wiki', author: 'Joshavanier', contact: 'joshavanier@protonmail.com' },
12+ { url: 'http://kaemura.com' },
13+ { url: 'https://liamcooke.com' },
14+ { url: 'https://electricgecko.de' },
15+ { url: 'https://wichniow.ski' },
16+ { url: 'https://hraew.autophagy.io' },
17+ { url: 'http://evenunto.net' },
18+ { url: 'https://anxl.faith' },
19+ { url: 'https://xvw.github.io', title: 'xvw.github.io', type: 'blog', author: 'xvw', contact: 'xaviervdw@gmail.com', rss: 'https://xvw.github.io/atom.xml' },
20+ { url: 'https://heracl.es' },
21+ { url: 'https://felipecortez.net' },
22+ { url: 'http://luminghao.com' },
23+ { url: 'https://turelio.github.io' },
24+ { url: 'https://spaceshipsin.space', title: 'spaceshipsin.space', type: 'hybrid', author: 'gaeel', contact: 'gaeel@spaceshipsin.space', feed: 'https://spaceshipsin.space/twtxts/hallway.txt' },
25+ { url: 'https://log.lectronice.com', title: 'lectrolog.', type: 'blog', author: 'lectronice', contact: 'i.love.spam@lectronice.com', feed: 'https://lectronice.com/hallway/twtxt.txt' },
26+ { url: 'https://craze.co.uk' },
27+ { url: 'https://shaneckel.com' },
28+ { url: 'https://cblgh.org', title: 'cblgh.org', author: 'cblgh', contact: 'cabal://cblgh.org', feed: 'https://cblgh.org/twtxt.txt' },
29+ { url: 'https://ellugar.co', title: 'ellugar', type: 'wiki', author: 'afk', contact: 'afk@ellugar.co', rss: 'http://feeds.ellugar.co/ellugar-logs' },
30+ { url: 'http://hur.bet' },
31+ { url: 'http://chigby.org' },
32+ { url: 'https://longest.voyage' },
33+ { url: 'https://palomakop.tv' },
34+ { url: 'https://travisshears.com' },
35+ { url: 'https://v-os.ca' },
36+ { url: 'http://secretinternet.club' },
37+ { url: 'https://jmandel.xyz' },
38+ { url: 'https://systems.ws' },
39+ { url: 'https://jamesin.space' },
40+ { url: 'https://nathanwentworth.co' },
41+ { url: 'https://eswat.ca' },
42+ { url: 'https://uonai.space' },
43+ { url: 'http://controls.ee' },
44+ { url: 'https://wasin.io' },
45+ { url: 'https://inns.studio' },
46+ { url: 'http://kokorobot.ca' },
47+ { url: 'https://ameyama.com', title: '雨山', type: 'blog', author: 'jrc03c', contact: 'josh@ameyama.com', rss: 'https://ameyama.com/blog/rss.xml' },
48+ { url: 'https://wake.st', title: 'wake.st', author: 'wakest', contact: '@liaizon@wake.st', feed: 'https://wake.st/twtxt.txt' },
49+ { url: 'https://xarene.la' },
50+ { url: 'https://alex.zyzhang.me' },
51+ { url: 'http://bildwissenschaft.vortok.info' },
52+ { url: 'https://jakofranko.github.com' },
53+ { url: 'https://aeriform.io' },
54+ { url: 'http://blog.lucasdidthis.com' },
55+ { url: 'http://npisanti.com', title: 'npisanti.com', author: 'npisanti', contact: 'nicola@npisanti.com', rss: 'http://npisanti.com/journal/rss.xml' },
56+ { url: 'https://underscorediscovery.ca' },
57+ { url: 'https://drisc.io', title: 'drisc.io', type: 'wiki', author: 'drisc', contact: 'cory@drisc.io', feed: 'https://drisc.io/hallway/twtxt.txt' },
58+ { url: 'https://neufv.systems', author: 'neufv', feed: 'https://neufv.systems/twtxt.txt' },
59+ { url: 'https://ricky.codes' },
60+ { url: 'https://maxdeviant.com' },
61+ { url: 'https://tynandebold.com' },
62+ { url: 'http://gytis.co' },
63+ { url: 'https://nomand.co' },
64+ { url: 'http://memoriata.com' },
65+ { url: 'https://mmm.s-ol.nu' },
66+ { url: 'https://chad.is' },
67+ { url: 'https://smidgeo.com/bots' },
68+ { url: 'https://iko.soy' },
69+ { url: 'http://atelieroilandsugar.com' },
70+ { url: 'https://magoz.is' },
71+ { url: 'https://szymonkaliski.com' },
72+ { url: 'https://phse.net', title: 'phse.net', type: 'blog', author: 'setphen', rss: 'https://phse.net/post/index.xml', feed: 'https://phse.net/twtxt/merv.txt' },
73+ { url: 'https://rosano.ca' },
74+ { url: 'https://soyboysky.github.io' },
75+ { url: 'https://gndclouds.cc' },
76+ { url: 'https://xuv.be' },
77+ { url: 'https://dsdshcym.github.io' },
78+ { url: 'https://wiki.chronica.xyz', title: 'chronica', type: 'wiki', author: 'ckipp', contact: 'ckipp@pm.me', feed: 'https://chronica.xyz/hallway.txt' },
79+ { url: 'https://boffosocko.com' },
80+ { url: 'https://hex22.org', title: 'hex22', author: 'kodedninja', contact: 'karamanhunor@pm.me', feed: 'https://t.seed.hex22.org/twtxt.txt' },
81+ { url: 'https://patrikarvidsson.com' },
82+ { url: 'https://sophieleetmaa.com' },
83+ { url: 'https://xinniw.github.io' },
84+ { url: 'https://mboxed.github.io/forida' },
85+ { url: 'https://letters.vexingworkshop.com' },
86+ { url: 'https://tom.org.nz/about' },
87+ { url: 'https://teknari.com' },
88+ { url: 'https://colectivo-de-livecoders.gitlab.io/', title: 'Colectivo de Livecoders', type: 'blog', author: 'clic', contact: 'https://t.me/clic_laplata' },
89+ { url: 'https://www.madewithtea.com', title: 'madewithtea.com', type: 'blog' },
90+ { url: 'https://amorris.ca', title: 'amorris', author: 'amorris', type: 'blog', feed: 'https://feed.amorris.ca/hallway.txt' }
91+]