+5
.changeset/spotty-buckets-jump.md
+5
.changeset/spotty-buckets-jump.md
-3
README.md
-3
README.md
+1
lefthook.yml
+1
lefthook.yml
+2
src/commands/init.ts
+2
src/commands/init.ts
+3
-6
src/constants.ts
+3
-6
src/constants.ts
···
24
24
],
25
25
},
26
26
{
27
-
value: "vanilla",
27
+
value: "Vanilla",
28
28
label: "Vanilla",
29
-
disabled: true,
30
-
hint: "coming soon",
31
29
variants: [
32
30
{
33
-
value: "javascript",
34
-
label: "JavaScript (With OAuth)",
35
-
docs: "https://github.com/bluesky-social/cookbook/tree/main/vanillajs-oauth-web-app",
31
+
value: "vanilla",
32
+
label: "JavaScript",
36
33
},
37
34
],
38
35
},
+35
templates/vanilla/README.md
+35
templates/vanilla/README.md
···
1
+
# HTML + JavaScript + Vite + AT Protocol
2
+
3
+
This is a minimal project with just enough to get you going with developing an AT Protocol web application using the public APIs.
4
+
5
+
## Resources
6
+
7
+
- [Introduction to the AT Protocol](https://atproto.com/articles/atproto-ethos)
8
+
- [Bluesky Developer Documentation](https://docs.bsky.app)
9
+
- [@atproto/lex documentation](https://github.com/bluesky-social/atproto/tree/HEAD/packages/lex/lex#quick-start)
10
+
11
+
## Getting Started
12
+
13
+
You'll need to install some lexicons before starting development. In your terminal, run:
14
+
15
+
```bash
16
+
npx @atproto/lex install app.bsky.actor.getProfile
17
+
```
18
+
19
+
Then generate the TypeScript files. You can change the destination to where these files are generated but remember to update your ``.gitignore`` file so you don't commit them.
20
+
21
+
```bash
22
+
npx @atproto/lex build --out ./src/__generated__
23
+
```
24
+
25
+
Next, install the package dependencies:
26
+
27
+
```bash
28
+
npm install
29
+
```
30
+
31
+
Finally, run the development server:
32
+
33
+
```bash
34
+
npm run dev
35
+
```
+20
templates/vanilla/package.json
+20
templates/vanilla/package.json
···
1
+
{
2
+
"name": "vanilla",
3
+
"private": true,
4
+
"version": "0.0.0",
5
+
"type": "module",
6
+
"scripts": {
7
+
"dev": "vite",
8
+
"build": "vite build",
9
+
"preview": "vite preview",
10
+
"update-lexicons": "lex install --update --save",
11
+
"postinstall": "lex install --ci",
12
+
"prebuild": "lex build --out ./src/__generated__"
13
+
},
14
+
"dependencies": {
15
+
"@atproto/lex": "^0.0.16"
16
+
},
17
+
"devDependencies": {
18
+
"vite": "^7.3.1"
19
+
}
20
+
}
+93
templates/vanilla/src/main.js
+93
templates/vanilla/src/main.js
···
1
+
import "./style.css";
2
+
import { Client } from "@atproto/lex";
3
+
4
+
import * as app from "./__generated__/app.js";
5
+
6
+
const client = new Client("https://public.api.bsky.app");
7
+
8
+
function escapeHtml(str) {
9
+
if (!str) return "";
10
+
return str
11
+
.replaceAll("&", "&")
12
+
.replaceAll("<", "<")
13
+
.replaceAll(">", ">")
14
+
.replaceAll('"', """)
15
+
.replaceAll("'", "'");
16
+
}
17
+
18
+
function formatStat(stat) {
19
+
if (!stat || typeof stat !== "number") {
20
+
return;
21
+
}
22
+
23
+
return new Intl.NumberFormat(window.navigator.language).format(stat);
24
+
}
25
+
26
+
document.addEventListener("DOMContentLoaded", async () => {
27
+
const root = document.querySelector("#app");
28
+
29
+
root.innerHTML = `<p>Loading profile...</p>`;
30
+
31
+
// https://atproto.com/specs/did
32
+
const people = [
33
+
"did:plc:ragtjsm2j2vknwkz3zp4oxrd",
34
+
"did:plc:w4xbfzo7kqfes5zb7r6qv3rw",
35
+
"did:plc:vc7f4oafdgxsihk4cry2xpze",
36
+
"did:plc:6i6n57nrkq6xavqbdo6bvkqr",
37
+
"did:plc:fip3nyk6tjo3senpq4ei2cxw",
38
+
"did:plc:yfvwmnlztr4dwkb7hwz55r2g",
39
+
];
40
+
41
+
const randomPerson = Math.floor(Math.random() * people.length);
42
+
43
+
const {
44
+
avatar,
45
+
displayName,
46
+
handle,
47
+
followersCount,
48
+
followsCount,
49
+
description,
50
+
postsCount,
51
+
} = await client.call(app.bsky.actor.getProfile, {
52
+
actor: people[randomPerson],
53
+
});
54
+
55
+
root.innerHTML = `
56
+
<article class="profile">
57
+
<header class="header">
58
+
<img
59
+
src="${escapeHtml(avatar)}"
60
+
alt=""
61
+
height="100"
62
+
width="100"
63
+
class="avatar"
64
+
/>
65
+
<div>
66
+
<h2>${escapeHtml(displayName)}</h2>
67
+
<p>${escapeHtml(handle)}</p>
68
+
</div>
69
+
</header>
70
+
<section class="stats">
71
+
<p>
72
+
<span class="number">
73
+
${formatStat(followersCount)}
74
+
</span>
75
+
followers
76
+
</p>
77
+
<p>
78
+
<span class="number">
79
+
${formatStat(followsCount)}
80
+
</span>
81
+
following
82
+
</p>
83
+
<p>
84
+
<span class="number">
85
+
${formatStat(postsCount)}
86
+
</span>
87
+
posts
88
+
</p>
89
+
</section>
90
+
<footer>${escapeHtml(description)}</footer>
91
+
</article>
92
+
`;
93
+
});
+128
templates/vanilla/src/style.css
+128
templates/vanilla/src/style.css
···
1
+
@import "https://unpkg.com/open-props/colors.min.css";
2
+
@import "https://unpkg.com/open-props/shadows.min.css";
3
+
4
+
@layer reset {
5
+
*,
6
+
*::before,
7
+
*::after {
8
+
box-sizing: border-box;
9
+
}
10
+
11
+
* {
12
+
margin: 0;
13
+
padding: 0;
14
+
}
15
+
16
+
html {
17
+
-webkit-font-smoothing: antialiased;
18
+
text-rendering: optimizespeed;
19
+
text-size-adjust: none;
20
+
tab-size: 2;
21
+
scrollbar-gutter: stable;
22
+
interpolate-size: allow-keywords;
23
+
line-height: 1.5;
24
+
height: 100%;
25
+
background-color: #f8f9fa;
26
+
}
27
+
28
+
body {
29
+
margin: 0;
30
+
/* https://systemfontstack.com */
31
+
font-family:
32
+
Menlo,
33
+
Consolas,
34
+
Monaco,
35
+
Adwaita Mono,
36
+
Liberation Mono,
37
+
Lucida Console,
38
+
monospace;
39
+
font-synthesis: none;
40
+
41
+
height: 100%;
42
+
display: flex;
43
+
justify-content: center;
44
+
align-items: center;
45
+
}
46
+
47
+
ul[role="list"],
48
+
ol[role="list"] {
49
+
list-style: none;
50
+
padding: 0;
51
+
}
52
+
53
+
::marker {
54
+
line-height: 0;
55
+
}
56
+
57
+
:focus-visible {
58
+
outline-offset: 2px;
59
+
}
60
+
61
+
@media (prefers-reduced-motion: no-preference) {
62
+
html:focus-within {
63
+
scroll-behavior: smooth;
64
+
}
65
+
}
66
+
67
+
a {
68
+
color: inherit;
69
+
text-underline-offset: 0.2ex;
70
+
}
71
+
72
+
h1,
73
+
h2,
74
+
h3,
75
+
h4 {
76
+
text-wrap: balance;
77
+
}
78
+
79
+
a[href] {
80
+
-webkit-tap-highlight-color: transparent;
81
+
}
82
+
83
+
p,
84
+
h1,
85
+
h2,
86
+
h3,
87
+
h4,
88
+
h5,
89
+
h6 {
90
+
overflow-wrap: break-word;
91
+
}
92
+
93
+
p {
94
+
text-wrap: pretty;
95
+
}
96
+
}
97
+
98
+
.avatar {
99
+
border-radius: 100%;
100
+
border: 1px solid grey;
101
+
}
102
+
103
+
.header {
104
+
display: flex;
105
+
align-items: center;
106
+
gap: 1rem;
107
+
}
108
+
109
+
.number {
110
+
font-weight: bold;
111
+
}
112
+
113
+
.stats {
114
+
display: flex;
115
+
gap: 0.5rem;
116
+
}
117
+
118
+
.profile {
119
+
width: 80ch;
120
+
display: flex;
121
+
flex-direction: column;
122
+
gap: 1rem;
123
+
background-color: #ffffff;
124
+
border-radius: 0.5rem;
125
+
padding: 2rem;
126
+
box-shadow: var(--shadow-1);
127
+
border: 1px solid var(--stone-0);
128
+
}
History
1 round
0 comments
besaid.zone
submitted
#0
4 commits
expand
collapse
feat: add vanilla javascript template
fix: remove space from vanilla template source folder
chore(templates): try a different escapeHtml implementation
chore: changeset
2/2 success
expand
collapse
expand 0 comments
closed without merging