├── package.json
├── .gitignore
├── ui-behaviour-tests.js
├── ui-style.css
├── README.md
├── identity.svg
├── ui-behaviour.js
├── index.html
├── ui-test-_access.js
└── ui-test-_misc.js
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "test": "olsk-spec"
4 | },
5 | "devDependencies": {
6 | "OLSKSpec": "olsk/OLSKSpec"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # NODE
2 | node_modules
3 | package-lock.json
4 |
5 | # BUILD
6 | **/__*/*
7 | .static
8 |
9 | # OS
10 | **/*.DS_Store
11 |
12 | # LOCAL
13 | .env*
14 | !.env-sample
15 |
--------------------------------------------------------------------------------
/ui-behaviour-tests.js:
--------------------------------------------------------------------------------
1 | const { throws, rejects, deepEqual } = require('assert');
2 |
3 | const mod = require('./ui-behaviour.js');
4 |
5 | describe('APRVitrineRandomAnchor', function test_APRVitrineRandomAnchor() {
6 |
7 | it('returns string', function () {
8 | deepEqual(mod.APRVitrineRandomAnchor(), 'random');
9 | });
10 |
11 | });
12 |
13 | describe('APRVitrineRefreshSeconds', function test_APRVitrineRefreshSeconds() {
14 |
15 | it('returns string', function () {
16 | deepEqual(mod.APRVitrineRefreshSeconds(), 3);
17 | });
18 |
19 | });
20 |
--------------------------------------------------------------------------------
/ui-style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: sans-serif;
3 | font-size: 10pt;
4 |
5 | --APRTextColor: #555;
6 | --APRLinkColor: black;
7 | --APRPadding: 10px;
8 | }
9 |
10 | html {
11 | height: 100%;
12 | }
13 |
14 | body {
15 | display: flex;
16 | height: 100%;
17 | padding: 0;
18 |
19 | margin: 0;
20 |
21 | background: #f4f4f4;
22 | color: var(--APRTextColor);
23 | }
24 |
25 | a {
26 | color: var(--APRLinkColor);
27 | }
28 |
29 | hr {
30 | border: 1px solid white;
31 | }
32 |
33 | .APRBox {
34 | flex-grow: 1;
35 | padding: var(--APRPadding);
36 | border-radius: 2px;
37 |
38 | margin: var(--APRPadding);
39 |
40 | background: #ddd;
41 |
42 | background-image: url(identity.svg);
43 | background-size: 42px;
44 | background-repeat: no-repeat;
45 | background-position: bottom 10px right 10px;
46 | }
47 |
48 | .APRVitrine p {
49 | max-width: 400px;
50 | }
51 |
52 | .APRVitrineList {
53 | padding-left: calc(var(--APRPadding) * 3);
54 | }
55 |
56 | .APRRandomTargetName {
57 | font-weight: bold;
58 | }
59 |
60 | .APRRandomTargetBlurb {
61 | margin: 0;
62 | }
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # [Doorless App Ring](https://ring.0data.app)
4 |
5 | _A webring for web apps_
6 |
7 | This appring supports [doorless](https://rosano.hmm.garden/01evv3hq1ak4b6ng1jzppx5n2j) projects that are functional without signing into an account.
8 |
9 | https://ring.0data.app
10 |
11 | It is not necessary for it to be [0data](https://0data.app) or a [Progressive web app](https://en.wikipedia.org/wiki/Progressive_web_application), but these would be ideal. Doorless is another way of saying 'someone can show up and start using it immediately'.
12 |
13 | Inspired by [XXIIVV/webring](https://github.com/XXIIVV/webring) and [indiewebring](https://indieweb.org/indiewebring).
14 |
15 | ## Add your project(s) to the ring
16 |
17 | 1. Link to the appring from your project's homepage or inside the app itself (see below for example code that you can copy and modify).
18 | 2. Add your project URL to [index.html](https://github.com/0dataapp/lap/edit/master/index.html)
19 | 3. Submit a Pull Request with the location of the appring link in your project.
20 |
21 | ### Link via text
22 |
23 | ```html
24 | Part of the Appring
25 | ```
26 |
27 | ### Link via image
28 |
29 | ```html
30 |
31 | ```
32 |
33 | ### Link via image and text
34 |
35 | ```html
36 |
Part of the Appring
37 | ```
38 |
39 | ## ❤️
40 |
41 | Help me keep creating projects that are public, accessible for free, and open-source.
42 |
43 |
44 |
45 | ## Questions
46 |
47 | Feel free to reach out on [Mastodon](https://rosano.ca/mastodon) or [Bluesky](https://rosano.ca/bluesky).
48 |
--------------------------------------------------------------------------------
/identity.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
31 |
--------------------------------------------------------------------------------
/ui-behaviour.js:
--------------------------------------------------------------------------------
1 | const uRandomElement = function () {
2 | const array = [].concat(...arguments);
3 | return array[Date.now() % array.length];
4 | };
5 |
6 | const mod = {
7 |
8 | OLSKControllerRoutes () {
9 | return {
10 | APRVitrineRoute: {
11 | OLSKRoutePath: '/',
12 | OLSKRouteMethod: 'get',
13 | OLSKRouteFunction (req, res, next) {
14 | return res.render(require('path').join(__dirname, 'index.html'));
15 | },
16 | },
17 | APRRandomRoute: {
18 | OLSKRoutePath: '/#' + mod.APRVitrineRandomAnchor(),
19 | OLSKRouteMethod: 'get',
20 | OLSKRouteFunction (req, res, next) {
21 | return res.render(require('path').join(__dirname, 'index.html'));
22 | },
23 | },
24 | };
25 | },
26 |
27 | APRVitrineRandomAnchor () {
28 | return 'random';
29 | },
30 |
31 | APRVitrineRefreshSeconds () {
32 | return 3;
33 | },
34 |
35 | // DATA
36 |
37 | DataProjects () {
38 | return Array.from(mod._APRVitrine.querySelectorAll('li a')).map(function (e) {
39 | return {
40 | APRProjectName: e.innerHTML,
41 | APRProjectURL: e.getAttribute('href'),
42 | APRProjectBlurb: e.getAttribute('title'),
43 | };
44 | });
45 | },
46 |
47 | // MESSAGE
48 |
49 | WindowHashDidChange () {
50 | mod.SetupRandom();
51 | },
52 |
53 | // SETUP
54 |
55 | _SetupMethods () {
56 | return Object.keys(mod).filter(function (e) {
57 | return e.match(/^Setup/);
58 | });
59 | },
60 |
61 | SetupElements () {
62 | mod._APRVitrine = document.querySelector('.APRVitrine');
63 | mod._APRRandom = document.querySelector('.APRRandom');
64 | },
65 |
66 | SetupRandom () {
67 | const isRandom = window.location.hash.replace(/^#+/, '').trim() === mod.APRVitrineRandomAnchor();
68 |
69 | document.body.removeChild(isRandom ? mod._APRVitrine : mod._APRRandom);
70 | document.body.appendChild(isRandom ? mod._APRRandom : mod._APRVitrine);
71 |
72 | if (!isRandom) {
73 | return;
74 | }
75 |
76 | const item = uRandomElement(mod.DataProjects());
77 |
78 | document.querySelector('.APRRandomTargetDomain').innerText = (new URL(item.APRProjectURL)).hostname;
79 | document.querySelector('.APRRandomTargetName').innerText = item.APRProjectName;
80 | document.querySelector('.APRRandomTargetBlurb').innerText = item.APRProjectBlurb;
81 |
82 | const meta = document.createElement('meta');
83 | meta.httpEquiv = 'refresh';
84 | meta.content = `${ mod.APRVitrineRefreshSeconds() }; url=${ item.APRProjectURL }`;
85 | mod._APRRandom.appendChild(meta);
86 | },
87 |
88 | SetupWindowHashChange() {
89 | window.addEventListener('hashchange', mod.WindowHashDidChange, false);
90 | },
91 |
92 | // LIFECYCLE
93 |
94 | LifecyclePageDidLoad () {
95 | return mod._SetupMethods().forEach(function (e) {
96 | return mod[e]();
97 | });
98 | },
99 |
100 | };
101 |
102 | if (typeof module !== 'undefined') {
103 | module.exports = mod;
104 | }
105 |
106 | const APRVitrineBehaviour = mod;
107 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This webring supports doorless projects that function without signing into an account. The ring welcomes hand-crafted tools, apps, or websites where 'someone can show up and start using it immediately'.
36 | 37 |To add your project(s) to the ring, link to here from your project and submit a Pull Request.
38 | 39 | Random 40 | 41 | | 42 | 43 | More info 44 | 45 |50 | Doorless App Ring 51 | — 52 | A webring for web apps 53 |
54 | 55 |58 | Redirecting to 59 | 60 |