astro-atmosphere-comments 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ ---
2
+ // The comment box: "Comment as @handle", the text box (with @mention
3
+ // suggestions), the quote preview, a character count and Post. Its behaviour
4
+ // is in lib/client/composer.ts and its styles in atmosphere.css. Used under
5
+ // posts by AtmosphereComments (and cloned for reply boxes), and in the reply
6
+ // and quote overlay of AtmosphereReactions; not meant to be used on its own.
7
+ //
8
+ // Props:
9
+ // id prefix for the box's ids, unique on the page
10
+ // messages, lang the wording (see lib/i18n.ts)
11
+ // signOut show a Sign out button (default false)
12
+ // cancel show a Cancel button that closes an overlay (default false)
13
+ import { translate, translateHtml } from "../lib/i18n";
14
+ import { escapeHtml } from "../lib/translate";
15
+
16
+ interface Props {
17
+ id: string;
18
+ messages: Record<string, string>;
19
+ lang: string;
20
+ signOut?: boolean;
21
+ cancel?: boolean;
22
+ }
23
+
24
+ const { id, messages, lang, signOut = false, cancel = false } = Astro.props;
25
+ const t = (key: string) => translate(messages, lang, key);
26
+ const th = (key: string, html: Record<string, string>) => translateHtml(messages, lang, key, html);
27
+ ---
28
+
29
+ <form class="atmo__composer" data-composer>
30
+ <p class="atmo__context" data-context hidden>
31
+ <span data-context-text></span>
32
+ <button type="button" class="atmo__link-button" data-cancel-context>{t("composer.cancel")}</button>
33
+ </p>
34
+ <label for={`${id}-text`} class="atmo__as">
35
+ <span class="atmo__avatar atmo__avatar--me" data-me-avatar aria-hidden="true"></span>
36
+ <span set:html={th("composer.commentAs", { verb: `<span data-verb>${escapeHtml(t("composer.comment"))}</span>`, handle: "<strong data-handle></strong>" })} />
37
+ </label>
38
+ <div class="atmo__field">
39
+ <textarea
40
+ id={`${id}-text`}
41
+ name="text"
42
+ rows="4"
43
+ required
44
+ aria-autocomplete="list"
45
+ aria-controls={`${id}-suggestions`}
46
+ aria-expanded="false"></textarea>
47
+ <ul class="atmo__suggestions" id={`${id}-suggestions`} role="listbox" aria-label={t("composer.mentions")} data-suggestions hidden></ul>
48
+ </div>
49
+ <figure class="atmo__preview" data-preview hidden>
50
+ <figcaption>{t("composer.previewCaption")}</figcaption>
51
+ <div class="atmo__preview-post">
52
+ <span class="atmo__avatar" data-preview-avatar aria-hidden="true"></span>
53
+ <div class="atmo__preview-body">
54
+ <p class="atmo__preview-author"><strong data-preview-name></strong> <span data-preview-handle></span> <span>· {t("composer.now")}</span></p>
55
+ <p class="atmo__preview-text" data-preview-text></p>
56
+ <div class="atmo__quoted">
57
+ <p class="atmo__preview-author">
58
+ <span class="atmo__avatar atmo__avatar--small" data-quoted-avatar aria-hidden="true"></span>
59
+ <strong data-quoted-name></strong> <span data-quoted-handle></span>
60
+ </p>
61
+ <p class="atmo__preview-text" data-quoted-text></p>
62
+ <div class="atmo__card" data-card hidden>
63
+ <img data-card-image alt="" hidden />
64
+ <div>
65
+ <strong data-card-title></strong>
66
+ <span data-card-description></span>
67
+ <span class="atmo__card-domain" data-card-domain></span>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </figure>
74
+ <div class="atmo__row atmo__row--end">
75
+ <span class="atmo__count" data-count aria-live="polite"></span>
76
+ {signOut && <button type="button" class="atmo__link-button" data-sign-out>{t("composer.signOut")}</button>}
77
+ {cancel && <button type="button" class="atmo__link-button" data-close>{t("composer.cancel")}</button>}
78
+ <button type="submit" class="atmo__button" data-submit>{t("composer.postComment")}</button>
79
+ </div>
80
+ </form>
@@ -0,0 +1,218 @@
1
+ ---
2
+ // The sign-in form: a handle box (which suggests matching accounts, see
3
+ // lib/client/handle-suggestions.ts) and a "Sign in with Atmosphere" button.
4
+ // Used under posts by AtmosphereComments and in the sign-in overlay of
5
+ // AtmosphereReactions; not meant to be used on its own.
6
+ //
7
+ // Props:
8
+ // id prefix for the form's ids, unique on the page
9
+ // returnTo where the reader comes back to after signing in
10
+ // messages, lang the wording (see lib/i18n.ts)
11
+ // heading the form's heading, instead of "signIn.heading"
12
+ import logo from "../assets/union.svg?url";
13
+ import settings from "../lib/settings";
14
+ import { translate, translateHtml } from "../lib/i18n";
15
+ import { escapeHtml } from "../lib/translate";
16
+
17
+ interface Props {
18
+ id: string;
19
+ returnTo: string;
20
+ messages: Record<string, string>;
21
+ lang: string;
22
+ heading?: string;
23
+ }
24
+
25
+ const { id, returnTo, messages, lang, heading } = Astro.props;
26
+ const { base } = settings;
27
+ const t = (key: string) => translate(messages, lang, key);
28
+ const th = (key: string, html: Record<string, string>) => translateHtml(messages, lang, key, html);
29
+ const link = (href: string, text: string) => `<a href="${escapeHtml(href)}">${escapeHtml(text)}</a>`;
30
+ ---
31
+
32
+ <form class="atmo__login" id={`${id}-login`} method="post" action={`${base}/login`} data-login-form>
33
+ <label for={`${id}-handle`}>{heading ?? t("signIn.heading")}</label>
34
+ <p
35
+ class="atmo__hint"
36
+ set:html={th("signIn.anyAccount", {
37
+ atmosphereAccount: link("https://atmosphereaccount.com", t("signIn.atmosphereAccount")),
38
+ bluesky: link("https://bsky.app", "Bluesky"),
39
+ })}
40
+ />
41
+ <div class="atmo__row">
42
+ <span class="atmo__handle">
43
+ <span class="atmo__logo" style={`--atmo-logo: url("${logo}")`} aria-hidden="true"></span>
44
+ <input
45
+ id={`${id}-handle`}
46
+ name="handle"
47
+ type="text"
48
+ inputmode="url"
49
+ data-1p-ignore
50
+ data-lpignore="true"
51
+ data-bwignore
52
+ data-form-type="other"
53
+ autocapitalize="none"
54
+ spellcheck="false"
55
+ placeholder="alice.bsky.social"
56
+ required
57
+ />
58
+ <ul
59
+ class="atmo__suggestions"
60
+ id={`${id}-handle-suggestions`}
61
+ role="listbox"
62
+ aria-label={t("signIn.matchingAccounts")}
63
+ data-handle-suggestions
64
+ hidden
65
+ ></ul>
66
+ </span>
67
+ <input type="hidden" name="returnTo" value={returnTo} data-return-to />
68
+ <button type="submit" class="atmo__button atmo__button--sign-in">
69
+ <span class="atmo__logo" style={`--atmo-logo: url("${logo}")`} aria-hidden="true"></span>
70
+ {t("signIn.button")}
71
+ </button>
72
+ </div>
73
+ <p
74
+ class="atmo__hint"
75
+ set:html={th("signIn.privacy", { createAccount: link("https://atmosphereaccount.com", t("signIn.createAccount")) })}
76
+ />
77
+ </form>
78
+
79
+ <style>
80
+ .atmo__login {
81
+ display: grid;
82
+ gap: 8px;
83
+ max-width: 560px;
84
+ margin-top: 14px;
85
+ }
86
+
87
+ [hidden] {
88
+ display: none !important;
89
+ }
90
+
91
+ label {
92
+ font-weight: 700;
93
+ }
94
+
95
+ .atmo__hint {
96
+ margin: 0;
97
+ color: var(--atmo-muted, inherit);
98
+ font-size: 0.9em;
99
+ }
100
+
101
+ .atmo__row {
102
+ display: flex;
103
+ flex-wrap: wrap;
104
+ align-items: center;
105
+ gap: 10px;
106
+ }
107
+
108
+ /* The Atmosphere Account logo inside the handle box, on the left, and at
109
+ the start of the sign-in button. It is single-colour, so it is drawn in
110
+ the muted text colour in the box and the button's text colour on it. */
111
+ .atmo__handle {
112
+ position: relative;
113
+ display: flex;
114
+ flex: 1 1 220px;
115
+ min-width: 0;
116
+ }
117
+
118
+ .atmo__handle .atmo__logo {
119
+ position: absolute;
120
+ top: 50%;
121
+ left: 11px;
122
+ width: 20px;
123
+ height: 20px;
124
+ transform: translateY(-50%);
125
+ background-color: var(--atmo-muted, currentColor);
126
+ mask: var(--atmo-logo) center / contain no-repeat;
127
+ pointer-events: none;
128
+ }
129
+
130
+ input[type="text"] {
131
+ flex: 1 1 220px;
132
+ min-width: 0;
133
+ padding: 10px 12px 10px 40px;
134
+ border: 1px solid var(--atmo-border, #8886);
135
+ border-radius: var(--atmo-radius, 10px);
136
+ background: var(--atmo-field-bg, transparent);
137
+ color: inherit;
138
+ font: inherit;
139
+ }
140
+
141
+ .atmo__button {
142
+ display: inline-flex;
143
+ align-items: center;
144
+ gap: 8px;
145
+ padding: 10px 16px;
146
+ border: 0;
147
+ border-radius: var(--atmo-radius, 10px);
148
+ background: var(--atmo-accent, #1185fe);
149
+ color: var(--atmo-on-accent, #fff);
150
+ font: inherit;
151
+ font-weight: 700;
152
+ cursor: pointer;
153
+ }
154
+
155
+ .atmo__button .atmo__logo {
156
+ flex: none;
157
+ width: 20px;
158
+ height: 20px;
159
+ background-color: currentColor;
160
+ mask: var(--atmo-logo) center / contain no-repeat;
161
+ }
162
+
163
+ /* Matching accounts, under the handle box and as wide as it. */
164
+ .atmo__suggestions {
165
+ position: absolute;
166
+ z-index: 5;
167
+ top: calc(100% + 4px);
168
+ left: 0;
169
+ width: 100%;
170
+ margin: 0;
171
+ padding: 4px;
172
+ list-style: none;
173
+ border: 1px solid var(--atmo-border, #8886);
174
+ border-radius: var(--atmo-radius, 10px);
175
+ background: var(--atmo-menu-bg, Canvas);
176
+ box-shadow: 0 12px 30px rgb(0 0 0 / 0.25);
177
+ }
178
+
179
+ .atmo__suggestions :global(li) {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 8px;
183
+ padding: 7px 9px;
184
+ border-radius: calc(var(--atmo-radius, 10px) - 4px);
185
+ cursor: pointer;
186
+ }
187
+
188
+ .atmo__suggestions :global(li[aria-selected="true"]),
189
+ .atmo__suggestions :global(li:hover) {
190
+ background: color-mix(in srgb, var(--atmo-accent, #1185fe) 14%, transparent);
191
+ }
192
+
193
+ .atmo__suggestions :global(li span:last-child) {
194
+ overflow: hidden;
195
+ color: var(--atmo-muted, inherit);
196
+ text-overflow: ellipsis;
197
+ white-space: nowrap;
198
+ }
199
+
200
+ .atmo__suggestions :global(.atmo__avatar) {
201
+ display: grid;
202
+ flex: none;
203
+ place-items: center;
204
+ width: 26px;
205
+ height: 26px;
206
+ overflow: hidden;
207
+ border-radius: 50%;
208
+ background: var(--atmo-border, #8886);
209
+ font-size: 11px;
210
+ font-weight: 700;
211
+ }
212
+
213
+ .atmo__suggestions :global(.atmo__avatar img) {
214
+ width: 100%;
215
+ height: 100%;
216
+ object-fit: cover;
217
+ }
218
+ </style>