handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs

refactor: some cleaning up

mary.my.id 15ff3a61 8b0b04fd

verified
+119 -78
+1
package.json
··· 21 21 "valibot": "1.0.0-beta.2" 22 22 }, 23 23 "devDependencies": { 24 + "@tailwindcss/forms": "^0.5.9", 24 25 "@types/node": "^22.8.2", 25 26 "autoprefixer": "^10.4.20", 26 27 "prettier": "^3.3.3",
+19
pnpm-lock.yaml
··· 45 45 specifier: 1.0.0-beta.2 46 46 version: 1.0.0-beta.2(typescript@5.7.0-beta) 47 47 devDependencies: 48 + '@tailwindcss/forms': 49 + specifier: ^0.5.9 50 + version: 0.5.9(tailwindcss@3.4.14) 48 51 '@types/node': 49 52 specifier: ^22.8.2 50 53 version: 22.8.2 ··· 657 660 cpu: [x64] 658 661 os: [win32] 659 662 663 + '@tailwindcss/forms@0.5.9': 664 + resolution: {integrity: sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==} 665 + peerDependencies: 666 + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20' 667 + 660 668 '@types/babel__core@7.20.5': 661 669 resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 662 670 ··· 1024 1032 engines: {node: '>=10.0.0'} 1025 1033 hasBin: true 1026 1034 1035 + mini-svg-data-uri@1.4.4: 1036 + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} 1037 + hasBin: true 1038 + 1027 1039 miniflare@3.20241022.0: 1028 1040 resolution: {integrity: sha512-x9Fbq1Hmz1f0osIT9Qmj78iX4UpCP2EqlZnA/tzj/3+I49vc3Kq0fNqSSKplcdf6HlCHdL3fOBicmreQF4BUUQ==} 1029 1041 engines: {node: '>=16.13'} ··· 1965 1977 '@rollup/rollup-win32-x64-msvc@4.24.2': 1966 1978 optional: true 1967 1979 1980 + '@tailwindcss/forms@0.5.9(tailwindcss@3.4.14)': 1981 + dependencies: 1982 + mini-svg-data-uri: 1.4.4 1983 + tailwindcss: 3.4.14 1984 + 1968 1985 '@types/babel__core@7.20.5': 1969 1986 dependencies: 1970 1987 '@babel/parser': 7.26.1 ··· 2337 2354 picomatch: 2.3.1 2338 2355 2339 2356 mime@3.0.0: {} 2357 + 2358 + mini-svg-data-uri@1.4.4: {} 2340 2359 2341 2360 miniflare@3.20241022.0: 2342 2361 dependencies:
+15
src/api/queries/plc.ts
··· 1 + import * as v from 'valibot'; 2 + 3 + import { At } from '@atcute/client/lexicons'; 4 + 5 + import { plcLogEntries } from '../types/plc'; 6 + 7 + export const getPlcAuditLogs = async ({ did, signal }: { did: At.DID; signal?: AbortSignal }) => { 8 + const response = await fetch(`https://plc.directory/${did}/log/audit`, { signal }); 9 + if (!response.ok) { 10 + throw new Error(`got resposne ${response.status}`); 11 + } 12 + 13 + const json = await response.json(); 14 + return v.parse(plcLogEntries, json); 15 + };
+64
src/api/types/plc.ts
··· 1 + import * as v from 'valibot'; 2 + 3 + import { didKeyString, didString, handleString, serviceUrlString } from './strings'; 4 + 5 + export const legacyGenesisOp = v.object({ 6 + type: v.literal('create'), 7 + signingKey: didKeyString, 8 + recoveryKey: didKeyString, 9 + handle: handleString, 10 + service: serviceUrlString, 11 + prev: v.null(), 12 + sig: v.string(), 13 + }); 14 + export type PlcLegacyGenesisOp = v.InferOutput<typeof legacyGenesisOp>; 15 + 16 + export const tombstoneOp = v.object({ 17 + type: v.literal('plc_tombstone'), 18 + prev: v.string(), 19 + sig: v.string(), 20 + }); 21 + export type PlcTombstoneOp = v.InferOutput<typeof tombstoneOp>; 22 + 23 + export const service = v.object({ 24 + type: v.string(), 25 + endpoint: v.pipe(v.string(), v.url()), 26 + }); 27 + export type Service = v.InferOutput<typeof service>; 28 + 29 + const updateOp = v.object({ 30 + type: v.literal('plc_operation'), 31 + rotationKeys: v.array(didKeyString), 32 + verificationMethods: v.record(v.string(), didKeyString), 33 + alsoKnownAs: v.array(v.pipe(v.string(), v.url())), 34 + services: v.record( 35 + v.string(), 36 + v.object({ 37 + type: v.string(), 38 + endpoint: v.pipe(v.string(), v.url()), 39 + }), 40 + ), 41 + prev: v.nullable(v.string()), 42 + sig: v.string(), 43 + }); 44 + export type PlcUpdateOp = v.InferOutput<typeof updateOp>; 45 + 46 + export const plcOperation = v.union([legacyGenesisOp, tombstoneOp, updateOp]); 47 + export type PlcOperation = v.InferOutput<typeof plcOperation>; 48 + 49 + export const plcLogEntry = v.object({ 50 + did: didString, 51 + cid: v.string(), 52 + operation: plcOperation, 53 + nullified: v.boolean(), 54 + createdAt: v.pipe( 55 + v.string(), 56 + v.check((dateString) => { 57 + const date = new Date(dateString); 58 + return !Number.isNaN(date.getTime()); 59 + }), 60 + ), 61 + }); 62 + export type PlcLogEntry = v.InferOutput<typeof plcLogEntry>; 63 + 64 + export const plcLogEntries = v.array(plcLogEntry);
+5
src/api/types/strings.ts
··· 19 19 ); 20 20 }, 'must be a valid service url'), 21 21 ); 22 + 23 + export const didKeyString = v.pipe( 24 + v.string(), 25 + v.check((str) => str.startsWith('did:key:')), 26 + );
+2 -2
src/views/blob/blob-export.tsx
··· 260 260 required 261 261 pattern={DID_OR_HANDLE_RE.source} 262 262 placeholder="paul.bsky.social" 263 - class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline" 263 + class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0" 264 264 /> 265 265 </label> 266 266 ··· 280 280 input.setCustomValidity(''); 281 281 } 282 282 }} 283 - class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline" 283 + class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0" 284 284 /> 285 285 </label> 286 286
+1 -1
src/views/identity/did-lookup.tsx
··· 73 73 pattern={DID_OR_HANDLE_RE.source} 74 74 placeholder="paul.bsky.social" 75 75 value={params.q ?? ''} 76 - class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline" 76 + class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0" 77 77 /> 78 78 </label> 79 79
+7 -73
src/views/identity/plc-oplogs.tsx
··· 1 1 import { createSignal, JSX, Match, onCleanup, Switch } from 'solid-js'; 2 - import * as v from 'valibot'; 3 2 4 3 import { At } from '@atcute/client/lexicons'; 5 4 6 5 import { resolveHandleViaAppView } from '~/api/queries/handle'; 7 - import { didString, handleString, serviceUrlString } from '~/api/types/strings'; 6 + import { PlcLogEntry, Service } from '~/api/types/plc'; 8 7 import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings'; 9 8 9 + import { getPlcAuditLogs } from '~/api/queries/plc'; 10 10 import { useTitle } from '~/lib/navigation/router'; 11 11 import { dequal } from '~/lib/utils/dequal'; 12 12 import { createQuery } from '~/lib/utils/query'; ··· 15 15 import CircularProgressView from '~/components/circular-progress-view'; 16 16 import DiffTable from '~/components/diff-table'; 17 17 import ErrorView from '~/components/error-view'; 18 + 19 + import CheckIcon from '~/components/ic-icons/baseline-check'; 18 20 import ContentCopyIcon from '~/components/ic-icons/baseline-content-copy'; 19 - import CheckIcon from '~/components/ic-icons/baseline-check'; 20 21 21 22 const PlcOperationLogPage = () => { 22 23 const [params, setParams] = useSearchParams({ ··· 37 38 throw new Error(`${did} is not plc`); 38 39 } 39 40 40 - const response = await fetch(`https://plc.directory/${did}/log/audit`); 41 - if (!response.ok) { 42 - throw new Error(`got resposne ${response.status}`); 43 - } 44 - 45 - const json = await response.json(); 46 - return v.parse(plcLogEntries, json); 41 + const logs = await getPlcAuditLogs({ did, signal }); 42 + return logs; 47 43 }, 48 44 ); 49 45 ··· 84 80 pattern={DID_OR_HANDLE_RE.source} 85 81 placeholder="paul.bsky.social" 86 82 value={params.q ?? ''} 87 - class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline" 83 + class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0" 88 84 /> 89 85 </label> 90 86 ··· 711 707 712 708 return history; 713 709 }; 714 - 715 - const didKeyString = v.pipe( 716 - v.string(), 717 - v.check((str) => str.startsWith('did:key:')), 718 - ); 719 - 720 - const legacyGenesisOp = v.object({ 721 - type: v.literal('create'), 722 - signingKey: didKeyString, 723 - recoveryKey: didKeyString, 724 - handle: handleString, 725 - service: serviceUrlString, 726 - prev: v.null(), 727 - sig: v.string(), 728 - }); 729 - 730 - const tombstoneOp = v.object({ 731 - type: v.literal('plc_tombstone'), 732 - prev: v.string(), 733 - sig: v.string(), 734 - }); 735 - 736 - const service = v.object({ 737 - type: v.string(), 738 - endpoint: v.pipe(v.string(), v.url()), 739 - }); 740 - type Service = v.InferOutput<typeof service>; 741 - 742 - const plcOp = v.object({ 743 - type: v.literal('plc_operation'), 744 - rotationKeys: v.array(didKeyString), 745 - verificationMethods: v.record(v.string(), didKeyString), 746 - alsoKnownAs: v.array(v.pipe(v.string(), v.url())), 747 - services: v.record( 748 - v.string(), 749 - v.object({ 750 - type: v.string(), 751 - endpoint: v.pipe(v.string(), v.url()), 752 - }), 753 - ), 754 - prev: v.nullable(v.string()), 755 - sig: v.string(), 756 - }); 757 - 758 - const plcOperation = v.union([legacyGenesisOp, tombstoneOp, plcOp]); 759 - 760 - const plcLogEntry = v.object({ 761 - did: didString, 762 - cid: v.string(), 763 - operation: plcOperation, 764 - nullified: v.boolean(), 765 - createdAt: v.pipe( 766 - v.string(), 767 - v.check((dateString) => { 768 - const date = new Date(dateString); 769 - return !Number.isNaN(date.getTime()); 770 - }), 771 - ), 772 - }); 773 - type PlcLogEntry = v.InferOutput<typeof plcLogEntry>; 774 - 775 - const plcLogEntries = v.array(plcLogEntry); 776 710 777 711 function findLastMatching<T, S extends T>( 778 712 arr: T[],
+2 -2
src/views/repository/repo-export.tsx
··· 186 186 required 187 187 pattern={DID_OR_HANDLE_RE.source} 188 188 placeholder="paul.bsky.social" 189 - class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline" 189 + class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0" 190 190 /> 191 191 </label> 192 192 ··· 206 206 input.setCustomValidity(''); 207 207 } 208 208 }} 209 - class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline" 209 + class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0" 210 210 /> 211 211 </label> 212 212
+3
tailwind.config.js
··· 1 1 import plugin from 'tailwindcss/plugin'; 2 2 3 + import forms from '@tailwindcss/forms'; 4 + 3 5 /** @type {import('tailwindcss').Config} */ 4 6 export default { 5 7 content: ['./src/**/*.tsx'], ··· 16 18 hoverOnlyWhenSupported: true, 17 19 }, 18 20 plugins: [ 21 + forms(), 19 22 plugin(({ addVariant, addUtilities }) => { 20 23 addVariant('modal', '&:modal'); 21 24 addVariant('focus-within', '&:has(:focus-visible)');