this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add list customization

uwx 9ed83a27 76459715

+664 -125
+33
lexicons/io/gitlab/kinklist/kinklist/list.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.gitlab.kinklist.kinklist.list", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "properties": { 11 + "kinkDefinitions": { 12 + "type": "array", 13 + "items": { 14 + "type": "ref", 15 + "ref": "io.gitlab.kinklist.kinklist.profile#kinkCategory" 16 + }, 17 + "description": "Array of kink category/section definitions" 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime", 22 + "description": "When this list was created" 23 + } 24 + }, 25 + "required": [ 26 + "kinkDefinitions", 27 + "createdAt" 28 + ] 29 + }, 30 + "description": "A shared kink list" 31 + } 32 + } 33 + }
+4
package.json
··· 17 17 "devDependencies": { 18 18 "@atcute/lex-cli": "^2.5.3", 19 19 "@preact/signals": "^2.8.0", 20 + "@rollup/pluginutils": "^5.3.0", 20 21 "@typelex/cli": "^0.4.0", 21 22 "@typelex/emitter": "^0.4.0", 22 23 "@types/masonry-layout": "^4.2.8", 24 + "comptime.ts": "^0.8.0", 23 25 "express": "^5.2.1", 26 + "magic-string": "^0.30.21", 24 27 "nocache": "^4.0.0", 25 28 "preact-portal": "^1.1.3", 26 29 "rolldown": "1.0.0-rc.4", ··· 40 43 "lz-string": "^1.5.0", 41 44 "masonic": "^4.1.0", 42 45 "preact": "^10.28.3", 46 + "preact-iso": "^2.11.1", 43 47 "qfs-compression": "^0.2.3", 44 48 "react-masonry-css": "^1.0.16", 45 49 "react-responsive-masonry": "^2.7.1",
+192
pnpm-lock.yaml
··· 50 50 preact: 51 51 specifier: ^10.28.3 52 52 version: 10.28.3 53 + preact-iso: 54 + specifier: ^2.11.1 55 + version: 2.11.1(preact-render-to-string@6.6.5(preact@10.28.3))(preact@10.28.3) 53 56 qfs-compression: 54 57 specifier: ^0.2.3 55 58 version: 0.2.3 ··· 69 72 '@preact/signals': 70 73 specifier: ^2.8.0 71 74 version: 2.8.0(preact@10.28.3) 75 + '@rollup/pluginutils': 76 + specifier: ^5.3.0 77 + version: 5.3.0 72 78 '@typelex/cli': 73 79 specifier: link:../../AppData/Local/pnpm/global/5/node_modules/@typelex/cli 74 80 version: link:../../AppData/Local/pnpm/global/5/node_modules/@typelex/cli ··· 78 84 '@types/masonry-layout': 79 85 specifier: ^4.2.8 80 86 version: 4.2.8 87 + comptime.ts: 88 + specifier: ^0.8.0 89 + version: 0.8.0 81 90 express: 82 91 specifier: ^5.2.1 83 92 version: 5.2.1 93 + magic-string: 94 + specifier: ^0.30.21 95 + version: 0.30.21 84 96 nocache: 85 97 specifier: ^4.0.0 86 98 version: 4.0.0 ··· 377 389 resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} 378 390 engines: {node: '>=18.0.0'} 379 391 392 + '@jridgewell/sourcemap-codec@1.5.5': 393 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 394 + 380 395 '@napi-rs/wasm-runtime@1.1.1': 381 396 resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} 382 397 ··· 531 546 532 547 '@rolldown/pluginutils@1.0.0-rc.4': 533 548 resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==} 549 + 550 + '@rollup/pluginutils@5.3.0': 551 + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} 552 + engines: {node: '>=14.0.0'} 553 + peerDependencies: 554 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 555 + peerDependenciesMeta: 556 + rollup: 557 + optional: true 534 558 535 559 '@sindresorhus/merge-streams@4.0.0': 536 560 resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} ··· 556 580 '@types/bun@1.3.9': 557 581 resolution: {integrity: sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw==} 558 582 583 + '@types/estree@1.0.8': 584 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 585 + 559 586 '@types/jquery@3.5.33': 560 587 resolution: {integrity: sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==} 561 588 ··· 580 607 ajv@8.17.1: 581 608 resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} 582 609 610 + ansi-align@3.0.1: 611 + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} 612 + 613 + ansi-regex@5.0.1: 614 + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 615 + engines: {node: '>=8'} 616 + 583 617 ansi-regex@6.2.2: 584 618 resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} 585 619 engines: {node: '>=12'} ··· 592 626 resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} 593 627 engines: {node: '>=18'} 594 628 629 + boxen@8.0.1: 630 + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} 631 + engines: {node: '>=18'} 632 + 595 633 braces@3.0.3: 596 634 resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 597 635 engines: {node: '>=8'} ··· 611 649 resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 612 650 engines: {node: '>= 0.4'} 613 651 652 + camelcase@8.0.0: 653 + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} 654 + engines: {node: '>=16'} 655 + 656 + chalk@5.6.2: 657 + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} 658 + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 659 + 614 660 change-case@5.4.4: 615 661 resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} 616 662 ··· 621 667 resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} 622 668 engines: {node: '>=18'} 623 669 670 + cli-boxes@3.0.0: 671 + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} 672 + engines: {node: '>=10'} 673 + 624 674 cli-width@4.1.0: 625 675 resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} 626 676 engines: {node: '>= 12'} ··· 628 678 cliui@9.0.1: 629 679 resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} 630 680 engines: {node: '>=20'} 681 + 682 + comptime.ts@0.8.0: 683 + resolution: {integrity: sha512-p0y99CqkIk1fjJJz9Hux+aNE/L4TmEVaBjavk4RWujgj3o2jtG3RlvV4hAj4zE2hjs5RyJbxUHe5NlgUrmbPjA==} 684 + hasBin: true 685 + peerDependencies: 686 + vite: ^6 || ^7 687 + peerDependenciesMeta: 688 + vite: 689 + optional: true 631 690 632 691 content-disposition@1.0.1: 633 692 resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} ··· 668 727 emoji-regex@10.6.0: 669 728 resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} 670 729 730 + emoji-regex@8.0.0: 731 + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 732 + 671 733 encodeurl@2.0.0: 672 734 resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} 673 735 engines: {node: '>= 0.8'} ··· 697 759 698 760 esm-env@1.2.2: 699 761 resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 762 + 763 + estree-walker@2.0.2: 764 + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 700 765 701 766 etag@1.8.1: 702 767 resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} ··· 806 871 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 807 872 engines: {node: '>=0.10.0'} 808 873 874 + is-fullwidth-code-point@3.0.0: 875 + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 876 + engines: {node: '>=8'} 877 + 809 878 is-glob@4.0.3: 810 879 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 811 880 engines: {node: '>=0.10.0'} ··· 846 915 lz-string@1.5.0: 847 916 resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} 848 917 hasBin: true 918 + 919 + magic-string@0.30.21: 920 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 849 921 850 922 masonic@4.1.0: 851 923 resolution: {integrity: sha512-3RNbAG5qLve7qNtGp1UM/u7vI39jO73ZFHDBAg3xl8AVh7A6Ikx7I7mBeC0NY0h1r1jJn2Wqeol1QMa09MQbyQ==} ··· 941 1013 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 942 1014 engines: {node: '>=8.6'} 943 1015 1016 + picomatch@4.0.3: 1017 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1018 + engines: {node: '>=12'} 1019 + 1020 + preact-iso@2.11.1: 1021 + resolution: {integrity: sha512-rLy0RmzP/hrDjnFdnEblxFgKtzUj4njkHrpGJBGS7S4QuYw1zv0lA38qsWpeAAB10JAz/hF2CsHrLen9ufCtbw==} 1022 + peerDependencies: 1023 + preact: '>=10 || >= 11.0.0-0' 1024 + preact-render-to-string: '>=6.4.0' 1025 + 944 1026 preact-portal@1.1.3: 945 1027 resolution: {integrity: sha512-rE0KG2b7ggIly4VVsSm7+WmQmG/EoUZzBOed2IbycyaFIArOvz+yab/8RBoDogA0JWZuTsbMTStR41Ghc+5m7Q==} 946 1028 peerDependencies: 947 1029 preact: '*' 1030 + 1031 + preact-render-to-string@6.6.5: 1032 + resolution: {integrity: sha512-O6MHzYNIKYaiSX3bOw0gGZfEbOmlIDtDfWwN1JJdc/T3ihzRT6tGGSEWE088dWrEDGa1u7101q+6fzQnO9XCPA==} 1033 + peerDependencies: 1034 + preact: '>=10 || >= 11.0.0-0' 948 1035 949 1036 preact@10.28.3: 950 1037 resolution: {integrity: sha512-tCmoRkPQLpBeWzpmbhryairGnhW9tKV6c6gr/w+RhoRoKEJwsjzipwp//1oCpGPOchvSLaAPlpcJi9MwMmoPyA==} ··· 1066 1153 resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} 1067 1154 engines: {node: '>= 0.8'} 1068 1155 1156 + string-width@4.2.3: 1157 + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1158 + engines: {node: '>=8'} 1159 + 1069 1160 string-width@7.2.0: 1070 1161 resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} 1071 1162 engines: {node: '>=18'} 1163 + 1164 + strip-ansi@6.0.1: 1165 + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1166 + engines: {node: '>=8'} 1072 1167 1073 1168 strip-ansi@7.1.2: 1074 1169 resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} ··· 1102 1197 tslib@2.8.1: 1103 1198 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1104 1199 1200 + type-fest@4.41.0: 1201 + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} 1202 + engines: {node: '>=16'} 1203 + 1105 1204 type-is@2.0.1: 1106 1205 resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} 1107 1206 engines: {node: '>= 0.6'} 1207 + 1208 + typescript@5.9.3: 1209 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 1210 + engines: {node: '>=14.17'} 1211 + hasBin: true 1108 1212 1109 1213 typescript@6.0.0-dev.20260213: 1110 1214 resolution: {integrity: sha512-zGIJwsX3OEsKIoEvXJzHRpt58fk370/T1N0GsSECAbcTlrsfUe2QQFbdKyKT3HyG2hFFyZg+Q0zYn6VLeahafg==} ··· 1145 1249 vscode-languageserver@9.0.1: 1146 1250 resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} 1147 1251 hasBin: true 1252 + 1253 + w@2.3.1: 1254 + resolution: {integrity: sha512-tsdkeVsK5xvi/zLQZimOcYshowsRJuX5mbdWZsmt+3FncCh/c82N7Ig6GSIJJSF0bjKUv7vfqtgryBr7thq4tA==} 1255 + 1256 + widest-line@5.0.0: 1257 + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} 1258 + engines: {node: '>=18'} 1148 1259 1149 1260 wrap-ansi@9.0.2: 1150 1261 resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} ··· 1518 1629 dependencies: 1519 1630 minipass: 7.1.2 1520 1631 1632 + '@jridgewell/sourcemap-codec@1.5.5': {} 1633 + 1521 1634 '@napi-rs/wasm-runtime@1.1.1': 1522 1635 dependencies: 1523 1636 '@emnapi/core': 1.8.1 ··· 1634 1747 1635 1748 '@rolldown/pluginutils@1.0.0-rc.4': {} 1636 1749 1750 + '@rollup/pluginutils@5.3.0': 1751 + dependencies: 1752 + '@types/estree': 1.0.8 1753 + estree-walker: 2.0.2 1754 + picomatch: 4.0.3 1755 + 1637 1756 '@sindresorhus/merge-streams@4.0.0': {} 1638 1757 1639 1758 '@standard-schema/spec@1.1.0': {} ··· 1656 1775 '@types/bun@1.3.9': 1657 1776 dependencies: 1658 1777 bun-types: 1.3.9 1778 + 1779 + '@types/estree@1.0.8': {} 1659 1780 1660 1781 '@types/jquery@3.5.33': 1661 1782 dependencies: ··· 1705 1826 json-schema-traverse: 1.0.0 1706 1827 require-from-string: 2.0.2 1707 1828 1829 + ansi-align@3.0.1: 1830 + dependencies: 1831 + string-width: 4.2.3 1832 + 1833 + ansi-regex@5.0.1: {} 1834 + 1708 1835 ansi-regex@6.2.2: {} 1709 1836 1710 1837 ansi-styles@6.2.3: {} ··· 1723 1850 transitivePeerDependencies: 1724 1851 - supports-color 1725 1852 1853 + boxen@8.0.1: 1854 + dependencies: 1855 + ansi-align: 3.0.1 1856 + camelcase: 8.0.0 1857 + chalk: 5.6.2 1858 + cli-boxes: 3.0.0 1859 + string-width: 7.2.0 1860 + type-fest: 4.41.0 1861 + widest-line: 5.0.0 1862 + wrap-ansi: 9.0.2 1863 + 1726 1864 braces@3.0.3: 1727 1865 dependencies: 1728 1866 fill-range: 7.1.1 ··· 1743 1881 call-bind-apply-helpers: 1.0.2 1744 1882 get-intrinsic: 1.3.0 1745 1883 1884 + camelcase@8.0.0: {} 1885 + 1886 + chalk@5.6.2: {} 1887 + 1746 1888 change-case@5.4.4: {} 1747 1889 1748 1890 chardet@2.1.1: {} 1749 1891 1750 1892 chownr@3.0.0: {} 1893 + 1894 + cli-boxes@3.0.0: {} 1751 1895 1752 1896 cli-width@4.1.0: {} 1753 1897 ··· 1757 1901 strip-ansi: 7.1.2 1758 1902 wrap-ansi: 9.0.2 1759 1903 1904 + comptime.ts@0.8.0: 1905 + dependencies: 1906 + boxen: 8.0.1 1907 + magic-string: 0.30.21 1908 + typescript: 5.9.3 1909 + w: 2.3.1 1910 + 1760 1911 content-disposition@1.0.1: {} 1761 1912 1762 1913 content-type@1.0.5: {} ··· 1781 1932 1782 1933 emoji-regex@10.6.0: {} 1783 1934 1935 + emoji-regex@8.0.0: {} 1936 + 1784 1937 encodeurl@2.0.0: {} 1785 1938 1786 1939 env-paths@3.0.0: {} ··· 1798 1951 escape-html@1.0.3: {} 1799 1952 1800 1953 esm-env@1.2.2: {} 1954 + 1955 + estree-walker@2.0.2: {} 1801 1956 1802 1957 etag@1.8.1: {} 1803 1958 ··· 1944 2099 1945 2100 is-extglob@2.1.1: {} 1946 2101 2102 + is-fullwidth-code-point@3.0.0: {} 2103 + 1947 2104 is-glob@4.0.3: 1948 2105 dependencies: 1949 2106 is-extglob: 2.1.1 ··· 1972 2129 '@atproto/syntax': 0.4.3 1973 2130 1974 2131 lz-string@1.5.0: {} 2132 + 2133 + magic-string@0.30.21: 2134 + dependencies: 2135 + '@jridgewell/sourcemap-codec': 1.5.5 1975 2136 1976 2137 masonic@4.1.0(react@19.2.4): 1977 2138 dependencies: ··· 2044 2205 picocolors@1.1.1: {} 2045 2206 2046 2207 picomatch@2.3.1: {} 2208 + 2209 + picomatch@4.0.3: {} 2210 + 2211 + preact-iso@2.11.1(preact-render-to-string@6.6.5(preact@10.28.3))(preact@10.28.3): 2212 + dependencies: 2213 + preact: 10.28.3 2214 + preact-render-to-string: 6.6.5(preact@10.28.3) 2047 2215 2048 2216 preact-portal@1.1.3(preact@10.28.3): 2049 2217 dependencies: 2050 2218 preact: 10.28.3 2051 2219 2220 + preact-render-to-string@6.6.5(preact@10.28.3): 2221 + dependencies: 2222 + preact: 10.28.3 2223 + 2052 2224 preact@10.28.3: {} 2053 2225 2054 2226 prettier@3.8.1: {} ··· 2194 2366 2195 2367 statuses@2.0.2: {} 2196 2368 2369 + string-width@4.2.3: 2370 + dependencies: 2371 + emoji-regex: 8.0.0 2372 + is-fullwidth-code-point: 3.0.0 2373 + strip-ansi: 6.0.1 2374 + 2197 2375 string-width@7.2.0: 2198 2376 dependencies: 2199 2377 emoji-regex: 10.6.0 2200 2378 get-east-asian-width: 1.4.0 2201 2379 strip-ansi: 7.1.2 2380 + 2381 + strip-ansi@6.0.1: 2382 + dependencies: 2383 + ansi-regex: 5.0.1 2202 2384 2203 2385 strip-ansi@7.1.2: 2204 2386 dependencies: ··· 2232 2414 2233 2415 tslib@2.8.1: {} 2234 2416 2417 + type-fest@4.41.0: {} 2418 + 2235 2419 type-is@2.0.1: 2236 2420 dependencies: 2237 2421 content-type: 1.0.5 2238 2422 media-typer: 1.1.0 2239 2423 mime-types: 3.0.2 2424 + 2425 + typescript@5.9.3: {} 2240 2426 2241 2427 typescript@6.0.0-dev.20260213: {} 2242 2428 ··· 2264 2450 vscode-languageserver@9.0.1: 2265 2451 dependencies: 2266 2452 vscode-languageserver-protocol: 3.17.5 2453 + 2454 + w@2.3.1: {} 2455 + 2456 + widest-line@5.0.0: 2457 + dependencies: 2458 + string-width: 7.2.0 2267 2459 2268 2460 wrap-ansi@9.0.2: 2269 2461 dependencies:
+17 -1
public/atproto/client.ts
··· 1 1 import type { KittyAgent } from "kitty-agent"; 2 - import { IoGitlabKinklistKinklistProfile } from "../lexicons"; 2 + import { IoGitlabKinklistKinklistList, IoGitlabKinklistKinklistProfile } from "../lexicons"; 3 3 import { Did } from "@atcute/lexicons"; 4 4 import { slingshotClient } from "./slingshot"; 5 + import { now as tidNow } from '@atcute/tid'; 5 6 6 7 export class KinklistClient { 7 8 constructor(private readonly loginState: { ··· 24 25 25 26 get user() { 26 27 return this.loginState; 28 + } 29 + 30 + async createList(list: Omit<IoGitlabKinklistKinklistList.Main, "createdAt" | "$type">) { 31 + const { uri, cid } = await this.agent.put({ 32 + collection: 'io.gitlab.kinklist.kinklist.list', 33 + repo: this.user.did, 34 + rkey: tidNow(), 35 + record: { 36 + $type: 'io.gitlab.kinklist.kinklist.list', 37 + ...list, 38 + createdAt: new Date().toISOString(), 39 + }, 40 + }); 41 + 42 + return { uri, cid }; 27 43 } 28 44 29 45 async createOrUpdateProfile(profile: Omit<IoGitlabKinklistKinklistProfile.Main, "createdAt" | "updatedAt" | "$type">) {
+1 -1
public/atproto/signed-in-user.ts
··· 2 2 import { KinklistClient } from "./client"; 3 3 import { computed, signal } from "@preact/signals"; 4 4 5 - import metadata from '../client-metadata4.json' with { type: 'json' }; 5 + import metadata from '../client-metadata5.json' with { type: 'json' }; 6 6 7 7 export const oauthClient = new StatefulPreactOAuthClient<KinklistClient>( 8 8 {
+2 -2
public/client-metadata4.json public/client-metadata5.json
··· 1 1 { 2 - "client_id": "https://kinklist.wisp.place/client-metadata4.json", 2 + "client_id": "https://kinklist.wisp.place/client-metadata5.json", 3 3 "client_name": "atpaste", 4 4 "client_uri": "https://kinklist.wisp.place/", 5 5 "redirect_uris": ["https://kinklist.wisp.place/oauth-redirect.html", "https://kinklist.github.io/oauth-redirect.html", "https://sites.wisp.place/did:plc:nmc77zslrwafxn75j66mep6o/kinklist/oauth-redirect.html"], 6 - "scope": "atproto repo:io.gitlab.kinklist.kinklist.profile rpc:app.bsky.actor.getProfile?aud=* rpc:app.bsky.graph.getFollowers?aud=* rpc:app.bsky.graph.getFollows?aud=*", 6 + "scope": "atproto repo:io.gitlab.kinklist.kinklist.profile rpc:app.bsky.actor.getProfile?aud=* rpc:app.bsky.graph.getFollowers?aud=* rpc:app.bsky.graph.getFollows?aud=* repo:io.gitlab.kinklist.kinklist.list", 7 7 "grant_types": ["authorization_code", "refresh_token"], 8 8 "response_types": ["code"], 9 9 "token_endpoint_auth_method": "none",
+1 -1
public/exporter.ts
··· 1 1 import { choiceOptions } from "./base"; 2 - import { KinkCategory, Kink } from "./main"; 2 + import type { KinkCategory, Kink } from "./parser"; 3 3 4 4 const IMGUR_CLIENT_ID = '9db53e5936cd02f'; 5 5
+2 -2
public/index.html
··· 45 45 detectColorScheme(); 46 46 </script> 47 47 48 - <link rel="stylesheet" href="style.css"> 48 + <link rel="stylesheet" href="/style.css"> 49 49 </head> 50 50 51 51 <body> 52 52 <div id="root"></div> 53 53 54 54 <!-- defer is used here so that DOMTools is loaded in time as it's async --> 55 - <script src="kinklist.js"></script> 55 + <script src="/kinklist.js"></script> 56 56 </body> 57 57 58 58 </html>
+4 -2
public/kinks.ts
··· 1 - export const kinkText = ` 1 + import { parseKinks } from "./parser" with { type: "comptime" }; 2 + 3 + export const defaultKinks = parseKinks(` 2 4 #Bodies 3 5 (Self, Partner) 4 6 * Skinny ··· 220 222 * Scratching ::: Love scratches, or any kind of sexually-oriented skin scratching that doesn't draw blood. 221 223 * Biting ::: Bites, typically on the neck, that don't draw blood. 222 224 * Cutting ::: Purposefully cutting the skin to draw blood, can refer to knife play but also applies to physical interactions that draw blood in general. 223 - `.trim(); 225 + `.trim());
+1
public/lexicons/index.ts
··· 3 3 export * as BlueMicrocosmLinksGetManyToManyCounts from "./types/blue/microcosm/links/getManyToManyCounts.js"; 4 4 export * as ComBadExampleIdentityResolveMiniDoc from "./types/com/bad-example/identity/resolveMiniDoc.js"; 5 5 export * as ComBadExampleRepoGetUriRecord from "./types/com/bad-example/repo/getUriRecord.js"; 6 + export * as IoGitlabKinklistKinklistList from "./types/io/gitlab/kinklist/kinklist/list.js"; 6 7 export * as IoGitlabKinklistKinklistProfile from "./types/io/gitlab/kinklist/kinklist/profile.js";
+37
public/lexicons/types/io/gitlab/kinklist/kinklist/list.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as IoGitlabKinklistKinklistProfile from "./profile.js"; 5 + 6 + const _mainSchema = /*#__PURE__*/ v.record( 7 + /*#__PURE__*/ v.tidString(), 8 + /*#__PURE__*/ v.object({ 9 + $type: /*#__PURE__*/ v.literal("io.gitlab.kinklist.kinklist.list"), 10 + /** 11 + * When this list was created 12 + */ 13 + createdAt: /*#__PURE__*/ v.datetimeString(), 14 + /** 15 + * Array of kink category/section definitions 16 + */ 17 + get kinkDefinitions() { 18 + return /*#__PURE__*/ v.array( 19 + IoGitlabKinklistKinklistProfile.kinkCategorySchema, 20 + ); 21 + }, 22 + }), 23 + ); 24 + 25 + type main$schematype = typeof _mainSchema; 26 + 27 + export interface mainSchema extends main$schematype {} 28 + 29 + export const mainSchema = _mainSchema as mainSchema; 30 + 31 + export interface Main extends v.InferInput<typeof mainSchema> {} 32 + 33 + declare module "@atcute/lexicons/ambient" { 34 + interface Records { 35 + "io.gitlab.kinklist.kinklist.list": mainSchema; 36 + } 37 + }
+162 -115
public/main.tsx
··· 1 - import { signal, computed, Signal } from "@preact/signals"; 1 + import { signal, computed, Signal, effect } from "@preact/signals"; 2 2 import { render } from "preact"; 3 3 import { useEffect, useMemo, useReducer, useState } from "preact/hooks"; 4 4 import { Masonry } from "masonic"; ··· 7 7 import { exportImage } from "./exporter"; 8 8 import { compress, decompress } from "qfs-compression"; 9 9 import { createPortal } from "preact/compat"; 10 - import { kinkText as kinkTextContent } from "./kinks"; 10 + import { defaultKinks } from "./kinks"; 11 11 import { oauthClient, savedHandle, user } from "./atproto/signed-in-user"; 12 - import { Did } from "@atcute/lexicons"; 12 + import { Blob, Did, LegacyBlob, parseResourceUri } from "@atcute/lexicons"; 13 13 import { getAllBacklinks, getBacklinks } from "./atproto/constellation"; 14 - import type {} from '@atcute/bluesky'; 14 + import type { AppBskyActorProfile } from '@atcute/bluesky'; 15 15 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 16 16 import { faSun } from "@fortawesome/free-solid-svg-icons/faSun"; 17 17 import { faMoon } from "@fortawesome/free-solid-svg-icons/faMoon"; 18 + import { LocationProvider, Route, Router, useLocation, useRoute } from "preact-iso/router"; 19 + import { ErrorBoundary } from "preact-iso/lazy"; 20 + import { slingshotClient } from "./atproto/slingshot"; 21 + import { IoGitlabKinklistKinklistList } from "./lexicons"; 22 + import { Kink, KinkCategory, parseKinks, unparseKinks } from "./parser"; 18 23 19 24 const root = document.querySelector("#root"); 20 25 21 - export interface Kink { 22 - name: string; 23 - description: string | undefined; 24 - } 25 - 26 - export interface KinkCategory { 27 - name: string; 28 - description: string | undefined; 29 - kinks: Kink[]; 30 - participants: string[]; 31 - } 32 - 33 - function parseKinks(kinkStr: string) { 34 - const kinkCode = kinkStr 35 - .split("\n") 36 - .map((e, i) => [e.trim(), i + 1] as const) 37 - .filter((e) => e[0]); 38 - 39 - const kinkCategories: KinkCategory[] = []; 40 - 41 - const kinksById: Kink[] = []; 42 - 43 - let curKinkCategory: KinkCategory | undefined; 44 - let curKinkId = 0; 45 - for (const [line, lineNum] of kinkCode) { 46 - if (line.startsWith("#")) { 47 - const [categoryName, categoryDesc] = sliceOnce(removeSymbols(line, "#"), ":::"); 48 - 49 - curKinkCategory = { 50 - name: categoryName, 51 - description: categoryDesc, 52 - kinks: [], 53 - participants: ["Unknown"], 54 - }; 55 - kinkCategories.push(curKinkCategory); 56 - } else if (line.startsWith("(") && line.endsWith(")")) { 57 - if (curKinkCategory === undefined) { 58 - throw new Error(`Encountered a participant definition before a kink type declaration (line ${lineNum})`); 59 - } 60 - curKinkCategory.participants = removeSymbols(line, "(", ")") 61 - .split(",") 62 - .map((e) => e.trim()); 63 - } else if (line.startsWith("*")) { 64 - if (curKinkCategory?.kinks === undefined) { 65 - throw new Error(`Encountered a kink definition before a kink type declaration (line ${lineNum})`); 66 - } 67 - 68 - const [kinkName, kinkDescription] = sliceOnce(removeSymbols(line, "*"), ":::"); 69 - const kink = { name: kinkName, description: kinkDescription }; 70 - curKinkCategory.kinks.push(kink); 71 - kinksById[curKinkId++] = kink; 72 - } 73 - } 74 - 75 - return { kinkCategories, kinksById }; 76 - } 77 - 78 - const kinkText = signal(localStorage.getItem("kink-text") ?? kinkTextContent); 79 - const kinkData = computed(() => parseKinks(kinkText.value)); 26 + const kinkData = signal(localStorage.getItem("kink-text") ? parseKinks(localStorage.getItem("kink-text")!) : defaultKinks); 80 27 81 28 /** 82 29 * Maps kink name -> participant -> choice (id string) ··· 116 63 ))} 117 64 </> 118 65 ); 119 - } 120 - 121 - function sliceOnce(str: string, char: string): [string, string?] { 122 - const index = str.indexOf(char); 123 - 124 - if (index !== -1) { 125 - const leftHalf = str.slice(0, index).trim(); 126 - const rightHalf = str.slice(index + char.length).trim(); 127 - return [leftHalf, rightHalf]; 128 - } 129 - 130 - return [str.trim(), undefined]; 131 - } 132 - 133 - function removeSymbols(str: string, symbolStart: string, symbolEnd: string | null = null): string { 134 - if (str.startsWith(symbolStart)) { 135 - str = str.slice(1); 136 - } 137 - 138 - if (symbolEnd !== null && str.endsWith(symbolEnd)) { 139 - str = str.slice(0, -1); 140 - } 141 - 142 - return str.trim(); 143 66 } 144 67 145 68 const base64Alphabet = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet ··· 385 308 return <TheKinkCategory kinkCategory={kinkCategory} />; 386 309 } 387 310 388 - function KinksSection() { 311 + function getBlobCid(blob?: Blob | LegacyBlob) { 312 + if (!blob) return undefined; 313 + 314 + if ('cid' in blob) { 315 + return blob.cid; 316 + } else { 317 + return blob.ref.$link; 318 + } 319 + } 320 + 321 + function KinksSection({ 322 + creatorDid, 323 + creatorProfile, 324 + createdOn 325 + }: { 326 + creatorDid: Did | undefined; 327 + creatorProfile: AppBskyActorProfile.Main | undefined; 328 + createdOn: Date | undefined; 329 + }) { 389 330 return ( 390 - <Masonry 391 - items={kinkData.value.kinkCategories} 392 - render={MasonryItem} 393 - columnWidth={460} 394 - maxColumnCount={8} 395 - overscanBy={10} 396 - /> 331 + <> 332 + {creatorProfile && creatorDid && <div class="card"> 333 + <div class="card-content"> 334 + <div class="media"> 335 + <div class="media-left"> 336 + <figure class="image is-64x64"> 337 + <img 338 + src={`https://cdn.bsky.app/img/avatar/plain/${creatorDid}/${getBlobCid(creatorProfile?.avatar)}@jpeg`} 339 + alt="Placeholder image" 340 + /> 341 + </figure> 342 + </div> 343 + <div class="media-content"> 344 + <p class="title is-4">List by {creatorProfile.displayName}</p> 345 + <p class="subtitle is-6">Created on {createdOn?.toLocaleString('en-US')}</p> 346 + </div> 347 + </div> 348 + </div> 349 + </div>} 350 + <Masonry 351 + key={kinkData.value.kinkCategories} // this dogshit component breaks if you don't do this 352 + items={kinkData.value.kinkCategories} 353 + render={MasonryItem} 354 + columnWidth={460} 355 + maxColumnCount={8} 356 + overscanBy={10} 357 + /> 358 + </> 397 359 ); 398 360 } 399 361 ··· 402 364 open, 403 365 onClose, 404 366 container, 367 + zIndex, 405 368 }: { 406 369 children: import("preact").ComponentChildren; 407 370 open: boolean; 408 371 onClose: () => void; 409 372 container: HTMLElement; 373 + zIndex?: number; 410 374 }) { 411 375 if (!container) return null; 412 376 413 377 return createPortal( 414 - <div class={`modal${open ? " is-active" : ""}`}> 378 + <div class={`modal${open ? " is-active" : ""}`} style={zIndex ? { zIndex: zIndex } : undefined}> 415 379 <div class="modal-background" /> 416 380 <div class="modal-content">{children}</div> 417 381 <button class="modal-close is-large" aria-label="close" onClick={onClose} /> ··· 421 385 } 422 386 423 387 const changelog = [ 388 + { 389 + version: "18 - February 19th 2026", 390 + changes: ["Added customized list sharing"] 391 + }, 424 392 { 425 393 version: "17 - February 18th 2026", 426 394 changes: ["Add Customize List", "Add friends section", "Reorganize navbar"], ··· 558 526 const kinkPlus2 = useMemo(() => getKinkAndParticipantByIndex(indexPlus2), [indexPlus2]); 559 527 const kinkPlus3 = useMemo(() => getKinkAndParticipantByIndex(indexPlus3), [indexPlus3]); 560 528 561 - function getChoice({category, kink, participant}: { category: KinkCategory, kink: Kink, participant: string }) { 529 + function getChoice({ category, kink, participant }: { category: KinkCategory, kink: Kink, participant: string }) { 562 530 return getSelectedKinkOrDefault(kink, participant).value; 563 531 } 564 532 ··· 993 961 ); 994 962 } 995 963 996 - async function getUserKinks<T extends { did: Did }>(users: T[]): Promise<{ users: { 997 - user: T, 998 - kinks: KinksArray 999 - }[], userCount: number }> { 964 + async function getUserKinks<T extends { did: Did }>(users: T[]): Promise<{ 965 + users: { 966 + user: T, 967 + kinks: KinksArray 968 + }[], userCount: number 969 + }> { 1000 970 if (!user.value) return { users: [], userCount: users.length }; 1001 971 1002 972 let userCount = users.length; ··· 1081 1051 }: { 1082 1052 onClose: () => void; 1083 1053 }) { 1084 - const [localKinkText, setLocalKinkText] = useState(kinkText.value); 1054 + const [localKinkText, setLocalKinkText] = useState(unparseKinks(kinkData.value.kinkCategories)); 1085 1055 const error = useMemo(() => { 1086 1056 try { 1087 1057 parseKinks(localKinkText); ··· 1091 1061 } 1092 1062 }, [localKinkText]); 1093 1063 1064 + const [publishedLink, setPublishedLink] = useState(""); 1065 + const [publishedModalOpen, setPublishedModalOpen] = useState(false); 1066 + const location = useLocation(); 1067 + 1094 1068 function save() { 1095 1069 localStorage.setItem("kink-text", localKinkText); 1096 - kinkText.value = localKinkText; 1070 + kinkData.value = parseKinks(localKinkText); 1097 1071 onClose(); 1098 1072 } 1099 1073 1100 1074 function resetToDefault() { 1101 1075 localStorage.removeItem("kink-text"); 1102 - setLocalKinkText(kinkTextContent); 1076 + setLocalKinkText(unparseKinks(defaultKinks.kinkCategories)); 1103 1077 } 1104 1078 1105 - // async function publish() { 1106 - // const success = await uploadKinks(); 1079 + async function publish() { 1080 + if (!user.value) { 1081 + return; 1082 + } 1107 1083 1108 - // if (success) { 1109 - // alert("Successfully uploaded kinks!"); 1110 - // } 1111 - // } 1084 + try { 1085 + const { uri } = await user.value.client.createList({ 1086 + kinkDefinitions: parseKinks(localKinkText).kinkCategories, 1087 + }); 1088 + 1089 + const result = parseResourceUri(uri); 1090 + if (!result.ok) { 1091 + throw new Error("Failed to parse URI: " + result.error); 1092 + } 1093 + 1094 + setPublishedLink(`${document.location.origin}/${result.value.repo}/lists/${result.value.rkey}`); 1095 + setPublishedModalOpen(true); 1096 + } catch (err) { 1097 + console.error(err); 1098 + alert("Failed to publish kink list: " + err); 1099 + } 1100 + } 1101 + 1102 + function closeAllModals() { 1103 + setPublishedModalOpen(false); 1104 + onClose(); 1105 + } 1106 + 1107 + function goto(url: string) { 1108 + closeAllModals(); 1109 + location.route(url); 1110 + } 1112 1111 1113 1112 return ( 1114 1113 <div class="box content"> ··· 1117 1116 <div class="field"> 1118 1117 <textarea class="textarea" placeholder={localKinkText} value={localKinkText} onChange={e => { 1119 1118 setLocalKinkText(e.currentTarget.value); 1120 - }} style={{'--bulma-textarea-min-height': '24em'}} /> 1119 + }} style={{ '--bulma-textarea-min-height': '24em' }} /> 1121 1120 </div> 1122 1121 {error && <div class="notification is-danger">{String(error)}</div>} 1123 1122 1124 1123 <div class="field is-grouped"> 1125 1124 <button class="button is-primary" disabled={error !== undefined} onClick={save}>Save</button> 1126 1125 <button class="button is-secondary" onClick={resetToDefault}>Reset to Default</button> 1126 + {user.value && <button class="button is-secondary" onClick={publish}>Share to ATProto</button>} 1127 1127 </div> 1128 - {/* {user.value && <button class="button is-primary" onClick={publish}>Publish to ATProto</button>} */} 1128 + 1129 + <Modal open={publishedModalOpen} onClose={() => setPublishedModalOpen(false)} container={document.body} zIndex={1000}> 1130 + <div class="box content"> 1131 + <h1 class="title">Successfully published!</h1> 1132 + <p>Your kink list is now available at <a href={publishedLink} onClick={() => goto(publishedLink)}>{publishedLink}</a></p> 1133 + </div> 1134 + </Modal> 1129 1135 </div> 1130 1136 ) 1131 1137 } ··· 1155 1161 const [friendsOpen, setFriendsOpen] = useState(false); 1156 1162 const [customizeListOpen, setCustomizeListOpen] = useState(false); 1157 1163 const [exportDropdownActive, setExportDropdownActive] = useState(false); 1164 + const location = useLocation(); 1158 1165 1159 1166 function exportImageCallback() { 1160 1167 const canvas = exportImage(kinkData.value.kinkCategories, kinkData.value.kinksById, (kink, participant) => getSelectedKinkOrDefault(kink, participant).value); ··· 1285 1292 {quizModeOpen && <QuizMode />} 1286 1293 </Modal> 1287 1294 1288 - <a class="navbar-item" id="customize-list" onClick={() => setCustomizeListOpen(true)}> 1295 + <a class="navbar-item" id="customize-list" onClick={() => { 1296 + setCustomizeListOpen(true); 1297 + location.route('/'); // remove any /:did/lists/:rkey from the URL and hide the card 1298 + }}> 1289 1299 Customize List 1290 1300 </a> 1291 1301 ··· 1341 1351 } 1342 1352 1343 1353 function Root() { 1354 + const { did, rkey } = useRoute().params; 1344 1355 const [changelogOpen, setChangelogOpen] = useState(false); 1345 1356 const [hasInitialSession, setHasInitialSession] = useState(false); 1357 + const [listProfileRecord, setListProfileRecord] = useState<AppBskyActorProfile.Main>(); 1358 + const [createdOn, setCreatedOn] = useState<Date>(); 1346 1359 1347 1360 useEffect(() => { 1348 1361 const hash = location.hash.slice(1); ··· 1421 1434 } 1422 1435 }, []); 1423 1436 1437 + useEffect(() => { 1438 + if (document.location.pathname.includes('/lists/')) { 1439 + Promise.all([ 1440 + slingshotClient.getRecord({ 1441 + collection: 'io.gitlab.kinklist.kinklist.list', 1442 + repo: did as Did, 1443 + rkey: rkey, 1444 + }), 1445 + slingshotClient.getRecord({ 1446 + collection: 'app.bsky.actor.profile', 1447 + repo: did as Did, 1448 + rkey: 'self', 1449 + }) 1450 + ]).then(([record, profile]) => { 1451 + kinkData.value = parseKinks(unparseKinks(record.value.kinkDefinitions)); 1452 + setListProfileRecord(profile.value); 1453 + setCreatedOn(new Date(record.value.createdAt)); 1454 + }) 1455 + } 1456 + }, [did, rkey]); 1457 + 1424 1458 return ( 1425 1459 <div id="root"> 1426 1460 <Navbar hasInitialSession={hasInitialSession} /> ··· 1432 1466 </nav> 1433 1467 1434 1468 <section class="section kinks-section"> 1435 - <KinksSection /> 1469 + <KinksSection creatorProfile={listProfileRecord} creatorDid={did as Did} createdOn={createdOn} /> 1436 1470 </section> 1437 1471 <footer class="footer"> 1438 1472 <div class="content has-text-centered"> ··· 1472 1506 ); 1473 1507 } 1474 1508 1475 - render(<Root />, root!); 1509 + function App() { 1510 + return ( 1511 + <LocationProvider> 1512 + <ErrorBoundary> 1513 + <Router> 1514 + <Route path="/:did/lists/:rkey" component={Root} /> 1515 + <Route default component={Root} /> 1516 + </Router> 1517 + </ErrorBoundary> 1518 + </LocationProvider> 1519 + ); 1520 + } 1521 + 1522 + render(<App />, root!); 1476 1523 function getKinksArray(): KinksArray { 1477 1524 return kinkData.value.kinkCategories.flatMap((category) => 1478 1525 category.kinks.flatMap((kink) =>
+104
public/parser.ts
··· 1 + export interface Kink { 2 + name: string; 3 + description?: string; 4 + } 5 + 6 + export interface KinkCategory { 7 + name: string; 8 + description?: string; 9 + kinks: Kink[]; 10 + participants: string[]; 11 + } 12 + 13 + function sliceOnce(str: string, char: string): [string, string?] { 14 + const index = str.indexOf(char); 15 + 16 + if (index !== -1) { 17 + const leftHalf = str.slice(0, index).trim(); 18 + const rightHalf = str.slice(index + char.length).trim(); 19 + return [leftHalf, rightHalf]; 20 + } 21 + 22 + return [str.trim(), undefined]; 23 + } 24 + 25 + function removeSymbols(str: string, symbolStart: string, symbolEnd: string | null = null): string { 26 + if (str.startsWith(symbolStart)) { 27 + str = str.slice(1); 28 + } 29 + 30 + if (symbolEnd !== null && str.endsWith(symbolEnd)) { 31 + str = str.slice(0, -1); 32 + } 33 + 34 + return str.trim(); 35 + } 36 + 37 + export function unparseKinks(kinkCategories: KinkCategory[]): string { 38 + let result: string[] = []; 39 + for (const category of kinkCategories) { 40 + result.push('# ', category.name); 41 + if (category.description) { 42 + result.push(' ::: ', category.description); 43 + } 44 + result.push('\n'); 45 + if (category.participants.length > 0) { 46 + result.push('(', category.participants.join(', '), ')\n'); 47 + } 48 + for (const kink of category.kinks) { 49 + result.push('* ', kink.name); 50 + if (kink.description) { 51 + result.push(' ::: ', kink.description); 52 + } 53 + result.push('\n'); 54 + } 55 + result.push('\n'); 56 + } 57 + 58 + return result.join(""); 59 + } 60 + 61 + export function parseKinks(kinkStr: string) { 62 + const kinkCode = kinkStr 63 + .split("\n") 64 + .map((e, i) => [e.trim(), i + 1] as const) 65 + .filter((e) => e[0]); 66 + 67 + const kinkCategories: KinkCategory[] = []; 68 + 69 + const kinksById: Kink[] = []; 70 + 71 + let curKinkCategory: KinkCategory | undefined; 72 + let curKinkId = 0; 73 + for (const [line, lineNum] of kinkCode) { 74 + if (line.startsWith("#")) { 75 + const [categoryName, categoryDesc] = sliceOnce(removeSymbols(line, "#"), ":::"); 76 + 77 + curKinkCategory = { 78 + name: categoryName, 79 + description: categoryDesc, 80 + kinks: [], 81 + participants: ["Unknown"], 82 + }; 83 + kinkCategories.push(curKinkCategory); 84 + } else if (line.startsWith("(") && line.endsWith(")")) { 85 + if (curKinkCategory === undefined) { 86 + throw new Error(`Encountered a participant definition before a kink type declaration (line ${lineNum})`); 87 + } 88 + curKinkCategory.participants = removeSymbols(line, "(", ")") 89 + .split(",") 90 + .map((e) => e.trim()); 91 + } else if (line.startsWith("*")) { 92 + if (curKinkCategory?.kinks === undefined) { 93 + throw new Error(`Encountered a kink definition before a kink type declaration (line ${lineNum})`); 94 + } 95 + 96 + const [kinkName, kinkDescription] = sliceOnce(removeSymbols(line, "*"), ":::"); 97 + const kink = { name: kinkName, description: kinkDescription }; 98 + curKinkCategory.kinks.push(kink); 99 + kinksById[curKinkId++] = kink; 100 + } 101 + } 102 + 103 + return { kinkCategories, kinksById }; 104 + }
+92 -1
rolldown.config.js
··· 1 1 // @ts-check 2 2 import { defineConfig } from 'rolldown'; 3 + import { getComptimeReplacements } from "comptime.ts/api"; 4 + import MagicString from "magic-string"; 5 + import { readFile } from "node:fs/promises"; 6 + import { createFilter } from '@rollup/pluginutils'; 7 + 8 + /** 9 + * @template T 10 + * @typedef {T | Promise<T>} MaybePromise 11 + */ 12 + 13 + /** 14 + * @typedef {(specifier: string, importer: string) => MaybePromise<string | null | undefined>} ModuleResolver 15 + */ 16 + 17 + /** 18 + * @typedef {Object} BaseConfig 19 + * @property {ModuleResolver} [resolver] 20 + */ 21 + 22 + /** 23 + * @typedef {BaseConfig} ConfigByConfig 24 + * @property {string} rootDir 25 + * @property {import('typescript').CompilerOptions} tsconfig 26 + * @property {never} [tsConfigPath] 27 + */ 28 + 29 + /** 30 + * @typedef {BaseConfig} ConfigByPath 31 + * @property {string} tsConfigPath 32 + * @property {never} [rootDir] 33 + */ 34 + 35 + /** 36 + * @typedef {BaseConfig} ConfigByImplicitConfig 37 + * @property {string} tsconfigPath 38 + * @property {never} [rootDir] 39 + * @property {never} [tsConfigPath] 40 + */ 41 + 42 + /** 43 + * @typedef {ConfigByConfig | ConfigByPath | ConfigByImplicitConfig} GetComptimeReplacementsOpts 44 + */ 45 + 46 + /** 47 + * @typedef {ReadonlyArray<string | RegExp> | string | RegExp | null} FilterPattern 48 + */ 49 + 50 + /** 51 + * @typedef {GetComptimeReplacementsOpts & { include?: FilterPattern; exclude?: FilterPattern; }} ComptimeVitePluginOpts 52 + */ 53 + 54 + /** 55 + * @param {ComptimeVitePluginOpts} [opts] 56 + * @returns {import('rolldown').Plugin} 57 + */ 58 + function comptime(opts) { 59 + const filter = createFilter(opts?.include, opts?.exclude); 60 + 61 + /** @type {((specifier: string, importer: string) => Promise<string | undefined>)} */ 62 + let resolver; 63 + /** @type {import('comptime.ts/api').Replacements} */ 64 + let replacements; 65 + 66 + return { 67 + name: "vite-plugin-comptime", 68 + async buildStart() { 69 + resolver = (specifier, importer) => this.resolve(specifier, importer).then(res => res?.id); 70 + 71 + replacements = await getComptimeReplacements({ ...opts, resolver, filter }); 72 + }, 73 + async load(id) { 74 + const replacement = replacements[id]; 75 + if (!replacement) return null; 76 + 77 + const mod = await readFile(id, "utf-8"); 78 + 79 + const s = new MagicString(mod); 80 + 81 + for (const r of replacement) { 82 + s.overwrite(r.start, r.end, r.replacement); 83 + } 84 + 85 + return { code: s.toString(), map: s.generateMap() }; 86 + }, 87 + }; 88 + } 3 89 4 90 export default defineConfig({ 5 91 input: 'public/main.tsx', ··· 9 95 format: 'iife', 10 96 generatedCode: { 11 97 preset: 'es5' 12 - } 98 + }, 99 + sourcemap: 'inline' 13 100 }, 101 + // Incompatible with watch mode 102 + // plugins: [comptime({ 103 + // tsconfigPath: 'public/tsconfig.json', 104 + // })], 14 105 platform: 'browser', 15 106 transform: { 16 107 jsx: {
+12
typelex/main.tsp
··· 1 1 import "@typelex/emitter"; 2 2 import "./externals.tsp"; 3 3 4 + namespace io.gitlab.kinklist.kinklist.list { 5 + /** A shared kink list */ 6 + @rec("tid") 7 + model Main { 8 + /** Array of kink category/section definitions */ 9 + @required kinkDefinitions: io.gitlab.kinklist.kinklist.profile.KinkCategory[]; 10 + 11 + /** When this list was created */ 12 + @required createdAt: datetime; 13 + } 14 + } 15 + 4 16 namespace io.gitlab.kinklist.kinklist.profile { 5 17 /** My kink list profile. */ 6 18 @rec("literal:self")