···1+/**
2+ * GENERATED CODE - DO NOT MODIFY
3+ */
4+import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon"
5+import { CID } from "npm:multiformats/cid"
6+import { validate as _validate } from '../../../../lexicons.ts'
7+import {
8+ type $Typed,
9+ is$typed as _is$typed,
10+ type OmitKey,
11+} from '../../../../util.ts'
12+import type * as SocialGrainActorDefs from '../actor/defs.ts'
13+import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts'
14+import type * as AppBskyActorDefs from '../../../app/bsky/actor/defs.ts'
15+import type * as ComAtprotoModerationDefs from '../../../com/atproto/moderation/defs.ts'
16+17+const is$typed = _is$typed,
18+ validate = _validate
19+const id = 'social.grain.labeler.defs'
20+21+export interface LabelerView {
22+ $type?: 'social.grain.labeler.defs#labelerView'
23+ uri: string
24+ cid: string
25+ creator: SocialGrainActorDefs.ProfileView
26+ favoriteCount?: number
27+ viewer?: LabelerViewerState
28+ indexedAt: string
29+ labels?: ComAtprotoLabelDefs.Label[]
30+}
31+32+const hashLabelerView = 'labelerView'
33+34+export function isLabelerView<V>(v: V) {
35+ return is$typed(v, id, hashLabelerView)
36+}
37+38+export function validateLabelerView<V>(v: V) {
39+ return validate<LabelerView & V>(v, id, hashLabelerView)
40+}
41+42+export interface LabelerViewDetailed {
43+ $type?: 'social.grain.labeler.defs#labelerViewDetailed'
44+ uri: string
45+ cid: string
46+ creator: AppBskyActorDefs.ProfileView
47+ policies: SocialGrainActorDefs.LabelerPolicies
48+ favoriteCount?: number
49+ viewer?: LabelerViewerState
50+ indexedAt: string
51+ labels?: ComAtprotoLabelDefs.Label[]
52+ /** The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. */
53+ reasonTypes?: ComAtprotoModerationDefs.ReasonType[]
54+ /** The set of subject types (account, record, etc) this service accepts reports on. */
55+ subjectTypes?: ComAtprotoModerationDefs.SubjectType[]
56+ /** Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. */
57+ subjectCollections?: string[]
58+}
59+60+const hashLabelerViewDetailed = 'labelerViewDetailed'
61+62+export function isLabelerViewDetailed<V>(v: V) {
63+ return is$typed(v, id, hashLabelerViewDetailed)
64+}
65+66+export function validateLabelerViewDetailed<V>(v: V) {
67+ return validate<LabelerViewDetailed & V>(v, id, hashLabelerViewDetailed)
68+}
69+70+export interface LabelerViewerState {
71+ $type?: 'social.grain.labeler.defs#labelerViewerState'
72+ like?: string
73+}
74+75+const hashLabelerViewerState = 'labelerViewerState'
76+77+export function isLabelerViewerState<V>(v: V) {
78+ return is$typed(v, id, hashLabelerViewerState)
79+}
80+81+export function validateLabelerViewerState<V>(v: V) {
82+ return validate<LabelerViewerState & V>(v, id, hashLabelerViewerState)
83+}
84+85+export interface LabelerPolicies {
86+ $type?: 'social.grain.labeler.defs#labelerPolicies'
87+ /** The label values which this labeler publishes. May include global or custom labels. */
88+ labelValues: ComAtprotoLabelDefs.LabelValue[]
89+ /** Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. */
90+ labelValueDefinitions?: ComAtprotoLabelDefs.LabelValueDefinition[]
91+}
92+93+const hashLabelerPolicies = 'labelerPolicies'
94+95+export function isLabelerPolicies<V>(v: V) {
96+ return is$typed(v, id, hashLabelerPolicies)
97+}
98+99+export function validateLabelerPolicies<V>(v: V) {
100+ return validate<LabelerPolicies & V>(v, id, hashLabelerPolicies)
101+}
···1+/**
2+ * GENERATED CODE - DO NOT MODIFY
3+ */
4+import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon"
5+import { CID } from "npm:multiformats/cid"
6+import { validate as _validate } from '../../../../lexicons.ts'
7+import {
8+ type $Typed,
9+ is$typed as _is$typed,
10+ type OmitKey,
11+} from '../../../../util.ts'
12+import type * as AppBskyLabelerDefs from '../../../app/bsky/labeler/defs.ts'
13+import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts'
14+import type * as ComAtprotoModerationDefs from '../../../com/atproto/moderation/defs.ts'
15+16+const is$typed = _is$typed,
17+ validate = _validate
18+const id = 'social.grain.labeler.service'
19+20+export interface Record {
21+ $type: 'social.grain.labeler.service'
22+ policies: AppBskyLabelerDefs.LabelerPolicies
23+ labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string }
24+ createdAt: string
25+ /** The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. */
26+ reasonTypes?: ComAtprotoModerationDefs.ReasonType[]
27+ /** The set of subject types (account, record, etc) this service accepts reports on. */
28+ subjectTypes?: ComAtprotoModerationDefs.SubjectType[]
29+ /** Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. */
30+ subjectCollections?: string[]
31+ [k: string]: unknown
32+}
33+34+const hashRecord = 'main'
35+36+export function isRecord<V>(v: V) {
37+ return is$typed(v, id, hashRecord)
38+}
39+40+export function validateRecord<V>(v: V) {
41+ return validate<Record & V>(v, id, hashRecord, true)
42+}
···1+{
2+ "lexicon": 1,
3+ "id": "social.grain.labeler.service",
4+ "defs": {
5+ "main": {
6+ "type": "record",
7+ "description": "A declaration of the existence of labeler service.",
8+ "key": "literal:self",
9+ "record": {
10+ "type": "object",
11+ "required": ["policies", "createdAt"],
12+ "properties": {
13+ "policies": {
14+ "type": "ref",
15+ "ref": "app.bsky.labeler.defs#labelerPolicies"
16+ },
17+ "labels": {
18+ "type": "union",
19+ "refs": ["com.atproto.label.defs#selfLabels"]
20+ },
21+ "createdAt": { "type": "string", "format": "datetime" },
22+ "reasonTypes": {
23+ "description": "The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed.",
24+ "type": "array",
25+ "items": {
26+ "type": "ref",
27+ "ref": "com.atproto.moderation.defs#reasonType"
28+ }
29+ },
30+ "subjectTypes": {
31+ "description": "The set of subject types (account, record, etc) this service accepts reports on.",
32+ "type": "array",
33+ "items": {
34+ "type": "ref",
35+ "ref": "com.atproto.moderation.defs#subjectType"
36+ }
37+ },
38+ "subjectCollections": {
39+ "type": "array",
40+ "description": "Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type.",
41+ "items": { "type": "string", "format": "nsid" }
42+ }
43+ }
44+ }
45+ }
46+ }
47+}
···1+/**
2+ * GENERATED CODE - DO NOT MODIFY
3+ */
4+import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon"
5+import { CID } from "npm:multiformats/cid"
6+import { validate as _validate } from '../../../../lexicons.ts'
7+import {
8+ type $Typed,
9+ is$typed as _is$typed,
10+ type OmitKey,
11+} from '../../../../util.ts'
12+13+const is$typed = _is$typed,
14+ validate = _validate
15+const id = 'com.atproto.label.defs'
16+17+/** Metadata tag on an atproto resource (eg, repo or record). */
18+export interface Label {
19+ $type?: 'com.atproto.label.defs#label'
20+ /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */
21+ cid?: string
22+ /** Timestamp when this label was created. */
23+ cts: string
24+ /** Timestamp at which this label expires (no longer applies). */
25+ exp?: string
26+ /** If true, this is a negation label, overwriting a previous label. */
27+ neg?: boolean
28+ /** Signature of dag-cbor encoded label. */
29+ sig?: Uint8Array
30+ /** DID of the actor who created this label. */
31+ src: string
32+ /** AT URI of the record, repository (account), or other resource that this label applies to. */
33+ uri: string
34+ /** The short string name of the value or type of this label. */
35+ val: string
36+ /** The AT Protocol version of the label object. */
37+ ver?: number
38+}
39+40+const hashLabel = 'label'
41+42+export function isLabel<V>(v: V) {
43+ return is$typed(v, id, hashLabel)
44+}
45+46+export function validateLabel<V>(v: V) {
47+ return validate<Label & V>(v, id, hashLabel)
48+}
49+50+/** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */
51+export interface SelfLabel {
52+ $type?: 'com.atproto.label.defs#selfLabel'
53+ /** The short string name of the value or type of this label. */
54+ val: string
55+}
56+57+const hashSelfLabel = 'selfLabel'
58+59+export function isSelfLabel<V>(v: V) {
60+ return is$typed(v, id, hashSelfLabel)
61+}
62+63+export function validateSelfLabel<V>(v: V) {
64+ return validate<SelfLabel & V>(v, id, hashSelfLabel)
65+}
66+67+export type LabelValue =
68+ | '!hide'
69+ | '!no-promote'
70+ | '!warn'
71+ | '!no-unauthenticated'
72+ | 'dmca-violation'
73+ | 'doxxing'
74+ | 'porn'
75+ | 'sexual'
76+ | 'nudity'
77+ | 'nsfl'
78+ | 'gore'
79+ | (string & {})
80+81+/** Metadata tags on an atproto record, published by the author within the record. */
82+export interface SelfLabels {
83+ $type?: 'com.atproto.label.defs#selfLabels'
84+ values: SelfLabel[]
85+}
86+87+const hashSelfLabels = 'selfLabels'
88+89+export function isSelfLabels<V>(v: V) {
90+ return is$typed(v, id, hashSelfLabels)
91+}
92+93+export function validateSelfLabels<V>(v: V) {
94+ return validate<SelfLabels & V>(v, id, hashSelfLabels)
95+}
96+97+/** Declares a label value and its expected interpretations and behaviors. */
98+export interface LabelValueDefinition {
99+ $type?: 'com.atproto.label.defs#labelValueDefinition'
100+ /** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */
101+ blurs: 'content' | 'media' | 'none' | (string & {})
102+ locales: LabelValueDefinitionStrings[]
103+ /** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */
104+ severity: 'inform' | 'alert' | 'none' | (string & {})
105+ /** Does the user need to have adult content enabled in order to configure this label? */
106+ adultOnly?: boolean
107+ /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */
108+ identifier: string
109+ /** The default setting for this label. */
110+ defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {})
111+}
112+113+const hashLabelValueDefinition = 'labelValueDefinition'
114+115+export function isLabelValueDefinition<V>(v: V) {
116+ return is$typed(v, id, hashLabelValueDefinition)
117+}
118+119+export function validateLabelValueDefinition<V>(v: V) {
120+ return validate<LabelValueDefinition & V>(v, id, hashLabelValueDefinition)
121+}
122+123+/** Strings which describe the label in the UI, localized into a specific language. */
124+export interface LabelValueDefinitionStrings {
125+ $type?: 'com.atproto.label.defs#labelValueDefinitionStrings'
126+ /** The code of the language these strings are written in. */
127+ lang: string
128+ /** A short human-readable name for the label. */
129+ name: string
130+ /** A longer description of what the label means and why it might be applied. */
131+ description: string
132+}
133+134+const hashLabelValueDefinitionStrings = 'labelValueDefinitionStrings'
135+136+export function isLabelValueDefinitionStrings<V>(v: V) {
137+ return is$typed(v, id, hashLabelValueDefinitionStrings)
138+}
139+140+export function validateLabelValueDefinitionStrings<V>(v: V) {
141+ return validate<LabelValueDefinitionStrings & V>(
142+ v,
143+ id,
144+ hashLabelValueDefinitionStrings,
145+ )
146+}
···1+{
2+ "lexicon": 1,
3+ "id": "com.atproto.label.defs",
4+ "defs": {
5+ "label": {
6+ "type": "object",
7+ "required": [
8+ "src",
9+ "uri",
10+ "val",
11+ "cts"
12+ ],
13+ "properties": {
14+ "cid": {
15+ "type": "string",
16+ "format": "cid",
17+ "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to."
18+ },
19+ "cts": {
20+ "type": "string",
21+ "format": "datetime",
22+ "description": "Timestamp when this label was created."
23+ },
24+ "exp": {
25+ "type": "string",
26+ "format": "datetime",
27+ "description": "Timestamp at which this label expires (no longer applies)."
28+ },
29+ "neg": {
30+ "type": "boolean",
31+ "description": "If true, this is a negation label, overwriting a previous label."
32+ },
33+ "sig": {
34+ "type": "bytes",
35+ "description": "Signature of dag-cbor encoded label."
36+ },
37+ "src": {
38+ "type": "string",
39+ "format": "did",
40+ "description": "DID of the actor who created this label."
41+ },
42+ "uri": {
43+ "type": "string",
44+ "format": "uri",
45+ "description": "AT URI of the record, repository (account), or other resource that this label applies to."
46+ },
47+ "val": {
48+ "type": "string",
49+ "maxLength": 128,
50+ "description": "The short string name of the value or type of this label."
51+ },
52+ "ver": {
53+ "type": "integer",
54+ "description": "The AT Protocol version of the label object."
55+ }
56+ },
57+ "description": "Metadata tag on an atproto resource (eg, repo or record)."
58+ },
59+ "selfLabel": {
60+ "type": "object",
61+ "required": [
62+ "val"
63+ ],
64+ "properties": {
65+ "val": {
66+ "type": "string",
67+ "maxLength": 128,
68+ "description": "The short string name of the value or type of this label."
69+ }
70+ },
71+ "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel."
72+ },
73+ "labelValue": {
74+ "type": "string",
75+ "knownValues": [
76+ "!hide",
77+ "!no-promote",
78+ "!warn",
79+ "!no-unauthenticated",
80+ "dmca-violation",
81+ "doxxing",
82+ "porn",
83+ "sexual",
84+ "nudity",
85+ "nsfl",
86+ "gore"
87+ ]
88+ },
89+ "selfLabels": {
90+ "type": "object",
91+ "required": [
92+ "values"
93+ ],
94+ "properties": {
95+ "values": {
96+ "type": "array",
97+ "items": {
98+ "ref": "#selfLabel",
99+ "type": "ref"
100+ },
101+ "maxLength": 10
102+ }
103+ },
104+ "description": "Metadata tags on an atproto record, published by the author within the record."
105+ },
106+ "labelValueDefinition": {
107+ "type": "object",
108+ "required": [
109+ "identifier",
110+ "severity",
111+ "blurs",
112+ "locales"
113+ ],
114+ "properties": {
115+ "blurs": {
116+ "type": "string",
117+ "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
118+ "knownValues": [
119+ "content",
120+ "media",
121+ "none"
122+ ]
123+ },
124+ "locales": {
125+ "type": "array",
126+ "items": {
127+ "ref": "#labelValueDefinitionStrings",
128+ "type": "ref"
129+ }
130+ },
131+ "severity": {
132+ "type": "string",
133+ "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
134+ "knownValues": [
135+ "inform",
136+ "alert",
137+ "none"
138+ ]
139+ },
140+ "adultOnly": {
141+ "type": "boolean",
142+ "description": "Does the user need to have adult content enabled in order to configure this label?"
143+ },
144+ "identifier": {
145+ "type": "string",
146+ "maxLength": 100,
147+ "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
148+ "maxGraphemes": 100
149+ },
150+ "defaultSetting": {
151+ "type": "string",
152+ "default": "warn",
153+ "description": "The default setting for this label.",
154+ "knownValues": [
155+ "ignore",
156+ "warn",
157+ "hide"
158+ ]
159+ }
160+ },
161+ "description": "Declares a label value and its expected interpretations and behaviors."
162+ },
163+ "labelValueDefinitionStrings": {
164+ "type": "object",
165+ "required": [
166+ "lang",
167+ "name",
168+ "description"
169+ ],
170+ "properties": {
171+ "lang": {
172+ "type": "string",
173+ "format": "language",
174+ "description": "The code of the language these strings are written in."
175+ },
176+ "name": {
177+ "type": "string",
178+ "maxLength": 640,
179+ "description": "A short human-readable name for the label.",
180+ "maxGraphemes": 64
181+ },
182+ "description": {
183+ "type": "string",
184+ "maxLength": 100000,
185+ "description": "A longer description of what the label means and why it might be applied.",
186+ "maxGraphemes": 10000
187+ }
188+ },
189+ "description": "Strings which describe the label in the UI, localized into a specific language."
190+ }
191+ }
192+}
···1+import { BffContext, RouteHandler } from "@bigmoves/bff";
2+import { ComponentChildren } from "preact";
3+import { Breadcrumb } from "../components/Breadcrumb.tsx";
4+import { State } from "../state.ts";
5+6+export const handler: RouteHandler = (
7+ _req,
8+ _params,
9+ ctx: BffContext<State>,
10+) => {
11+ ctx.state.meta = [
12+ { title: "Community Guidelines — Grain" },
13+ ];
14+ return ctx.render(
15+ <div className="px-4 py-4">
16+ <Breadcrumb
17+ items={[{ label: "support", href: "/support" }, {
18+ label: "community guidelines",
19+ }]}
20+ />
21+ <h1 className="text-3xl font-bold mb-6 text-zinc-900 dark:text-white">
22+ Community Guidelines
23+ </h1>
24+ <Section title="About Grain Social">
25+ <p>
26+ Grain Social is a photo-sharing service built on the AT Protocol.
27+ These guidelines apply specifically to Grain Social. While the
28+ protocol is decentralized and supports many independent services, our
29+ focus is on fostering a respectful, creative, and safe experience
30+ within our app.
31+ </p>
32+ </Section>
33+34+ <Section title="Our Principles">
35+ <ul className="list-disc pl-5 space-y-1">
36+ <li>
37+ <strong>User choice</strong>: We are committed to empowering users
38+ with control over where their data is stored, how their content is
39+ moderated, and which algorithms power their feeds (hopefully more
40+ options soon!).
41+ </li>
42+ <li>
43+ <strong>Welcoming space</strong>: We aim to build a friendly,
44+ inclusive environment where people enjoy sharing and discovering
45+ photos.
46+ </li>
47+ <li>
48+ <strong>Evolving standards</strong>: Our policies will adapt over
49+ time based on your feedback and the needs of the community.
50+ </li>
51+ </ul>
52+ </Section>
53+54+ <Section title="What’s Not Allowed">
55+ <p>
56+ Don't use Grain Social to break the law, harm others, or disrupt the
57+ network. Specifically, do not:
58+ </p>
59+ <ul className="list-disc pl-5 space-y-1">
60+ <li>Promote hate groups or terrorism</li>
61+ <li>
62+ Share child sexual abuse material or any sexual content involving
63+ minors
64+ </li>
65+ <li>Engage in trafficking, exploitation, or predatory behavior</li>
66+ <li>Trade illegal goods or substances</li>
67+ <li>Share private personal info without consent</li>
68+ <li>Hack, phish, scam, or impersonate others</li>
69+ <li>Spam, abuse automation, or manipulate engagement</li>
70+ <li>Violate copyrights or trademarks</li>
71+ <li>Spread false or misleading election info</li>
72+ <li>
73+ Evade moderation actions (e.g., ban evasion) by creating new
74+ accounts
75+ </li>
76+ </ul>
77+ </Section>
78+79+ <Section title="Respect Others">
80+ <p>We expect respectful conduct. This includes avoiding:</p>
81+ <ul className="list-disc pl-5 space-y-1">
82+ <li>Harassment, bullying, or targeted abuse</li>
83+ <li>Hate speech or extremist content</li>
84+ <li>Threats of violence or glorification of harm</li>
85+ <li>Promotion of self-harm or suicide</li>
86+ <li>Graphic violence or non-consensual sexual content</li>
87+ <li>Misleading impersonation of individuals or organizations</li>
88+ </ul>
89+ </Section>
90+91+ <Section title="Reporting Violations">
92+ <p>
93+ Help us keep the community safe. You can report photos, galleries, or
94+ accounts directly through the app (soon!) or by contacting us at{" "}
95+ <a
96+ href="mailto:support@grain.social"
97+ className="text-sky-500 underline hover:underline"
98+ >
99+ support@grain.social
100+ </a>
101+ . Our moderation team will review and take action where needed.
102+ Reports may consider off-platform context when relevant.
103+ </p>
104+ </Section>
105+ </div>,
106+ );
107+};
108+109+type SectionProps = {
110+ title: string;
111+ children: ComponentChildren;
112+};
113+114+const Section = ({ title, children }: SectionProps) => (
115+ <section className="mb-8">
116+ <h2 className="text-xl font-bold mb-2 text-zinc-800 dark:text-zinc-100">
117+ {title}
118+ </h2>
119+ <div className="space-y-2 text-zinc-700 dark:text-zinc-300">
120+ {children}
121+ </div>
122+ </section>
123+);
+30
src/routes/dialogs.tsx
···9import { CreateAccountDialog } from "../components/CreateAccountDialog.tsx";
10import { GalleryCreateEditDialog } from "../components/GalleryCreateEditDialog.tsx";
11import { GallerySortDialog } from "../components/GallerySortDialog.tsx";
012import { PhotoAltDialog } from "../components/PhotoAltDialog.tsx";
13import { PhotoDialog } from "../components/PhotoDialog.tsx";
14import { PhotoSelectDialog } from "../components/PhotoSelectDialog.tsx";
15import { ProfileDialog } from "../components/ProfileDialog.tsx";
16import { getActorPhotos, getActorProfile } from "../lib/actor.ts";
17import { getGallery, getGalleryItemsAndPhotos } from "../lib/gallery.ts";
018import { photoToView } from "../lib/photo.ts";
19import type { State } from "../state.ts";
20···162) => {
163 return ctx.html(<CreateAccountDialog />);
164};
0000000000000000000000000000
···9import { CreateAccountDialog } from "../components/CreateAccountDialog.tsx";
10import { GalleryCreateEditDialog } from "../components/GalleryCreateEditDialog.tsx";
11import { GallerySortDialog } from "../components/GallerySortDialog.tsx";
12+import { LabelDefinitionDialog } from "../components/LabelDefinitionDialog.tsx";
13import { PhotoAltDialog } from "../components/PhotoAltDialog.tsx";
14import { PhotoDialog } from "../components/PhotoDialog.tsx";
15import { PhotoSelectDialog } from "../components/PhotoSelectDialog.tsx";
16import { ProfileDialog } from "../components/ProfileDialog.tsx";
17import { getActorPhotos, getActorProfile } from "../lib/actor.ts";
18import { getGallery, getGalleryItemsAndPhotos } from "../lib/gallery.ts";
19+import { atprotoLabelValueDefinitions } from "../lib/moderation.ts";
20import { photoToView } from "../lib/photo.ts";
21import type { State } from "../state.ts";
22···164) => {
165 return ctx.html(<CreateAccountDialog />);
166};
167+168+export const labelValueDefinition: RouteHandler = async (
169+ _req,
170+ params,
171+ ctx: BffContext<State>,
172+) => {
173+ const src = params.src;
174+ const val = params.val;
175+ const labelerDeinitionsMap = await ctx.getLabelerDefinitions();
176+ if (!labelerDeinitionsMap) return ctx.next();
177+ const labelValueDefinitions = labelerDeinitionsMap[src]
178+ ?.labelValueDefinitions;
179+ if (!labelValueDefinitions) return ctx.next();
180+181+ let valDef = labelValueDefinitions.find((def) => def.identifier === val);
182+ if (!valDef && typeof val === "string") {
183+ valDef = atprotoLabelValueDefinitions[val];
184+ }
185+186+ if (!valDef) return ctx.next();
187+ const labelerAtpData = await ctx.didResolver.resolveAtprotoData(src);
188+ return ctx.html(
189+ <LabelDefinitionDialog
190+ labelByHandle={labelerAtpData?.handle}
191+ labelValueDefinition={valDef}
192+ />,
193+ );
194+};
+5-1
src/routes/explore.tsx
···5import { Input } from "@bigmoves/bff/components";
6import { ComponentChildren } from "preact";
7import { ActorAvatar } from "../components/ActorAvatar.tsx";
08import { profileToView } from "../lib/actor.ts";
9import { getPageMeta } from "../meta.ts";
10import type { State } from "../state.ts";
···85 {profileViews.map((profile) => (
86 <li key={profile.did}>
87 <a class="flex items-center" href={`/profile/${profile.handle}`}>
88- <ActorAvatar profile={profile} size={32} class="mr-2" />
00089 <div class="flex flex-col">
90 <div class="font-semibold">
91 {profile.displayName || profile.handle}