fedipod-server 0.21.0 → 0.28.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.
Files changed (332) hide show
  1. package/README.md +5 -4
  2. package/dist/claims.d.ts +3 -1
  3. package/dist/claims.js +20 -7
  4. package/dist/components/context.jsonld +6 -0
  5. package/dist/handler.d.ts +29 -1
  6. package/dist/handler.js +89 -4
  7. package/dist/handler.jsonld +36 -1
  8. package/lib/client/c2s.mjs +100 -3
  9. package/lib/client/masto/accounts.mjs +44 -4
  10. package/lib/client/masto/body.mjs +90 -3
  11. package/lib/client/masto/index.mjs +5 -1
  12. package/lib/client/masto/instance.mjs +11 -3
  13. package/lib/client/masto/media.mjs +3 -57
  14. package/lib/client/masto/oauth.mjs +66 -9
  15. package/lib/client/masto/render.mjs +62 -5
  16. package/lib/client/masto/statuses.mjs +25 -1
  17. package/lib/client/masto/timelines.mjs +128 -6
  18. package/lib/connections/tagfeed.mjs +42 -5
  19. package/lib/core/as2.mjs +36 -5
  20. package/lib/core/contexts/index.mjs +14 -0
  21. package/lib/core/contexts/litepub-0.1.json +60 -0
  22. package/lib/core/contexts/map.json +3 -1
  23. package/lib/core/contexts/quotes.json +18 -0
  24. package/lib/core/graphview.mjs +41 -3
  25. package/lib/core/intake/activities.mjs +136 -2
  26. package/lib/core/intake/activity.mjs +79 -1
  27. package/lib/core/intake/group.mjs +37 -3
  28. package/lib/core/intake/index.mjs +32 -2
  29. package/lib/core/intake/notes.mjs +60 -3
  30. package/lib/core/lease.mjs +12 -2
  31. package/lib/core/profile-page.mjs +104 -0
  32. package/lib/core/publisher/collections.mjs +6 -1
  33. package/lib/core/publisher/index.mjs +35 -17
  34. package/lib/core/publisher/notes.mjs +104 -3
  35. package/lib/core/shapes/activitystreams.ttl +28 -0
  36. package/lib/core/shapes/index.mjs +26 -1
  37. package/lib/core/wire-quotes.mjs +71 -0
  38. package/lib/core/wire.mjs +59 -103
  39. package/lib/device/admin/routes/lifecycle.mjs +9 -0
  40. package/lib/device/admin/static.mjs +4 -1
  41. package/lib/device/admin/surface.mjs +26 -2
  42. package/lib/device/certs.mjs +18 -2
  43. package/lib/device/cli/commands/setup.mjs +6 -2
  44. package/lib/device/cli/commands/state.mjs +3 -1
  45. package/lib/device/gateway-move.mjs +58 -0
  46. package/lib/device/migrate.mjs +3 -1
  47. package/lib/device/setup.mjs +67 -7
  48. package/lib/gateway/front-core.mjs +307 -36
  49. package/lib/gateway/gateway-core.mjs +15 -6
  50. package/lib/gateway/notices.mjs +84 -0
  51. package/lib/gateway/quiet.mjs +184 -0
  52. package/lib/pod/root.mjs +6 -3
  53. package/lib/pod/state.mjs +8 -7
  54. package/lib/pod/transport.mjs +5 -1
  55. package/lib/session/README.md +123 -0
  56. package/lib/session/demo.html +94 -0
  57. package/lib/session/fedi-account.mjs +346 -0
  58. package/lib/session/fedi-login.mjs +123 -0
  59. package/lib/session/oidc-session.mjs +233 -0
  60. package/lib/session/package.json +41 -0
  61. package/package.json +1 -1
  62. package/run-agent.mjs +28 -0
  63. package/vendor/gate.cjs +6 -3
  64. package/web/admin/bar.css +66 -0
  65. package/web/admin/bar.js +38 -0
  66. package/web/admin/client/index.html +31 -3
  67. package/web/admin/client-pick.js +79 -0
  68. package/web/admin/gateway.js +22 -0
  69. package/web/admin/index.html +54 -13
  70. package/web/admin/notices-bar.js +90 -0
  71. package/web/admin/oauth-signin.mjs +37 -0
  72. package/web/admin/record.js +13 -1
  73. package/web/admin/setup/index.html +31 -3
  74. package/web/admin/tokens.css +15 -3
  75. package/web/admin/upkeep.js +4 -5
  76. package/web/app/README.md +14 -10
  77. package/web/app/admin-facade.mjs +51 -12
  78. package/web/app/agent.mjs +157 -19
  79. package/web/app/authorize_interaction.html +56 -0
  80. package/web/app/authorize_interaction.js +69 -0
  81. package/web/app/boot.mjs +233 -95
  82. package/web/app/dist/boot.js +477 -569
  83. package/web/app/dist/boot.js.map +4 -4
  84. package/web/app/dist/sw.js +1861 -532
  85. package/web/app/dist/sw.js.map +4 -4
  86. package/web/app/gateway-move.mjs +60 -0
  87. package/web/app/index.html +40 -49
  88. package/web/app/keys-browser.mjs +12 -15
  89. package/web/app/keystore.mjs +19 -36
  90. package/web/app/oidc-session.mjs +5 -188
  91. package/web/app/signup.mjs +172 -132
  92. package/web/app/sw-src.mjs +1 -1
  93. package/web/app/update.js +66 -0
  94. package/web/front/admin.html +5 -3
  95. package/web/front/new-account.html +2 -2
  96. package/web/front/notices.html +69 -0
  97. package/web/front/notices.js +107 -0
  98. package/web/front/run.html +15 -8
  99. package/web/front/run.js +124 -36
  100. package/web/icons/forum.svg +11 -0
  101. package/web/icons/gateway.svg +14 -0
  102. package/web/icons/local.svg +11 -0
  103. package/web/app/index.html~ +0 -152
  104. package/web/app/site/_headers +0 -9
  105. package/web/app/site/_redirects +0 -22
  106. package/web/app/site/admin/actors.js +0 -145
  107. package/web/app/site/admin/bar.css +0 -60
  108. package/web/app/site/admin/bar.js +0 -61
  109. package/web/app/site/admin/client/client.js +0 -66
  110. package/web/app/site/admin/client/index.html +0 -43
  111. package/web/app/site/admin/common.js +0 -23
  112. package/web/app/site/admin/connections.js +0 -112
  113. package/web/app/site/admin/gateway.js +0 -111
  114. package/web/app/site/admin/group.js +0 -258
  115. package/web/app/site/admin/index.html +0 -564
  116. package/web/app/site/admin/record.js +0 -378
  117. package/web/app/site/admin/setup/index.html +0 -215
  118. package/web/app/site/admin/setup/setup.js +0 -326
  119. package/web/app/site/admin/tokens.css +0 -56
  120. package/web/app/site/admin/upkeep.js +0 -178
  121. package/web/app/site/admin/window.css +0 -55
  122. package/web/app/site/admin/window.js +0 -136
  123. package/web/app/site/app/404.html +0 -40
  124. package/web/app/site/app/_headers +0 -2
  125. package/web/app/site/app/apple-touch-icon.png +0 -0
  126. package/web/app/site/app/assets/ICONS-DIPJeU0C.js +0 -2
  127. package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +0 -1
  128. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  129. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  130. package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
  131. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +0 -2
  132. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +0 -1
  133. package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  134. package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
  135. package/web/app/site/app/assets/chunk-aKtaBQYM.js +0 -1
  136. package/web/app/site/app/assets/compose-CFEx7wb4.js +0 -2
  137. package/web/app/site/app/assets/compose-CFEx7wb4.js.map +0 -1
  138. package/web/app/site/app/assets/compose-ZJN5xSb4.js +0 -35
  139. package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +0 -1
  140. package/web/app/site/app/assets/debug.module-YFQgwnIr.js +0 -52
  141. package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +0 -1
  142. package/web/app/site/app/assets/dom-p1YcJ5Rw.js +0 -2
  143. package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +0 -1
  144. package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  145. package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  146. package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  147. package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  148. package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  149. package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +0 -1
  150. package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  151. package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  152. package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  153. package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  154. package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  155. package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  156. package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  157. package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  158. package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  159. package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  160. package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +0 -1
  161. package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +0 -1
  162. package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  163. package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +0 -1
  164. package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  165. package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  166. package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  167. package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  168. package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +0 -1
  169. package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  170. package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +0 -1
  171. package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  172. package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  173. package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  174. package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  175. package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  176. package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  177. package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  178. package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +0 -1
  179. package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +0 -1
  180. package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +0 -1
  181. package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  182. package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  183. package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  184. package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  185. package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +0 -1
  186. package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +0 -1
  187. package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  188. package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  189. package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +0 -1
  190. package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  191. package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  192. package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  193. package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  194. package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +0 -1
  195. package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  196. package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  197. package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +0 -1
  198. package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +0 -1
  199. package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +0 -1
  200. package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  201. package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  202. package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  203. package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +0 -1
  204. package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  205. package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +0 -1
  206. package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  207. package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +0 -1
  208. package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  209. package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  210. package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  211. package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +0 -1
  212. package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +0 -1
  213. package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  214. package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +0 -1
  215. package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +0 -1
  216. package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +0 -1
  217. package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +0 -1
  218. package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  219. package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  220. package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  221. package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  222. package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  223. package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +0 -1
  224. package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +0 -1
  225. package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  226. package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  227. package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  228. package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +0 -1
  229. package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +0 -1
  230. package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  231. package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +0 -1
  232. package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +0 -1
  233. package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +0 -1
  234. package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +0 -1
  235. package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +0 -1
  236. package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +0 -1
  237. package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +0 -1
  238. package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +0 -1
  239. package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +0 -1
  240. package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  241. package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  242. package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  243. package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  244. package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  245. package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +0 -1
  246. package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  247. package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +0 -1
  248. package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  249. package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  250. package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +0 -1
  251. package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  252. package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  253. package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  254. package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  255. package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  256. package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  257. package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  258. package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +0 -1
  259. package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  260. package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +0 -1
  261. package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +0 -1
  262. package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  263. package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  264. package/web/app/site/app/assets/instances-BNPptnn-.json +0 -333
  265. package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +0 -1
  266. package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  267. package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  268. package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +0 -1
  269. package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  270. package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  271. package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +0 -1
  272. package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +0 -1
  273. package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +0 -1
  274. package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  275. package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  276. package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +0 -1
  277. package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +0 -1
  278. package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +0 -1
  279. package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  280. package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +0 -1
  281. package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  282. package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  283. package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +0 -1
  284. package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  285. package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +0 -1
  286. package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  287. package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  288. package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  289. package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  290. package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  291. package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +0 -1
  292. package/web/app/site/app/assets/locales/tok-CfheRbCu.js +0 -1
  293. package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +0 -1
  294. package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  295. package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +0 -1
  296. package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +0 -1
  297. package/web/app/site/app/assets/main-BdqNbG-a.js +0 -26
  298. package/web/app/site/app/assets/main-BdqNbG-a.js.map +0 -1
  299. package/web/app/site/app/assets/mock-home-DGhR__He.js +0 -2
  300. package/web/app/site/app/assets/mock-home-DGhR__He.js.map +0 -1
  301. package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
  302. package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  303. package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  304. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +0 -2
  305. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  306. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +0 -20
  307. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  308. package/web/app/site/app/assets/style-B1iLPTPR.css +0 -2
  309. package/web/app/site/app/assets/temml-DuyIQMei.js +0 -97
  310. package/web/app/site/app/assets/temml-DuyIQMei.js.map +0 -1
  311. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  312. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  313. package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
  314. package/web/app/site/app/assets/year-in-posts-DJv_star.js +0 -3
  315. package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +0 -1
  316. package/web/app/site/app/compose/index.html +0 -20
  317. package/web/app/site/app/favicon.ico +0 -0
  318. package/web/app/site/app/index.html +0 -73
  319. package/web/app/site/app/logo-192.png +0 -0
  320. package/web/app/site/app/logo-512.png +0 -0
  321. package/web/app/site/app/logo-badge-72.png +0 -0
  322. package/web/app/site/app/logo-maskable-512.png +0 -0
  323. package/web/app/site/app/logo-monochrome-512.png +0 -0
  324. package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
  325. package/web/app/site/app/manifest.webmanifest +0 -1
  326. package/web/app/site/app/og-image-2.jpg +0 -0
  327. package/web/app/site/app/og-image.png +0 -0
  328. package/web/app/site/app/robots.txt +0 -2
  329. package/web/app/site/app/version.json +0 -1
  330. package/web/app/site/boot.js +0 -34277
  331. package/web/app/site/index.html +0 -251
  332. package/web/app/site/sw.js +0 -71331
@@ -62,6 +62,49 @@ export async function handle(api, ctx) {
62
62
  const following = (api.agent.tagfeed?.config().tags || []).includes(name);
63
63
  return send(200, api.tagObject(name, following, req));
64
64
  }
65
+ // --- trending tags: counted off the notes already mirrored ---
66
+ //
67
+ // A single-actor instance has no firehose to measure, so "trending" here can
68
+ // only mean what the tag feed has actually brought in — which is the honest
69
+ // answer, and the one that makes a client's Explore surface show something
70
+ // instead of an empty list. `/api/v1/trends` is Mastodon's older name for the
71
+ // same thing.
72
+ if (pathname === '/api/v1/trends/tags' || pathname === '/api/v1/trends') {
73
+ const DAY = 86400000;
74
+ const today = Math.floor(Date.now() / DAY) * DAY;
75
+ // tag -> day -> { uses, accounts }. Accounts are counted distinctly, which
76
+ // is what the number means in Mastodon: one person posting nine times is
77
+ // nine uses by one account, and a client shows the second number.
78
+ const seen = new Map();
79
+ for (const st of api.store.getStatuses()) {
80
+ if (st.kind !== 'tag' || !st.tag) continue;
81
+ const at = Date.parse(st.published || '');
82
+ if (!Number.isFinite(at) || at < today - 6 * DAY) continue;
83
+ const day = Math.floor(at / DAY) * DAY;
84
+ if (!seen.has(st.tag)) seen.set(st.tag, new Map());
85
+ const days = seen.get(st.tag);
86
+ if (!days.has(day)) days.set(day, { uses: 0, accounts: new Set() });
87
+ const rec = days.get(day);
88
+ rec.uses++;
89
+ if (st.actor) rec.accounts.add(st.actor);
90
+ }
91
+ const followed = new Set(api.agent.tagfeed?.config().tags || []);
92
+ const ranked = [...seen.entries()].map(([name, days]) => {
93
+ // Seven entries, newest first, gaps filled with zeroes — a client draws
94
+ // the sparkline straight off this and a short array draws a short line.
95
+ const history = [];
96
+ for (let i = 0; i < 7; i++) {
97
+ const rec = days.get(today - i * DAY);
98
+ history.push({ day: String(Math.floor((today - i * DAY) / 1000)),
99
+ uses: String(rec ? rec.uses : 0), accounts: String(rec ? rec.accounts.size : 0) });
100
+ }
101
+ return { total: history.reduce((n, h) => n + Number(h.uses), 0),
102
+ tag: api.tagObject(name, followed.has(name), req, history) };
103
+ }).sort((a, b) => b.total - a.total);
104
+ const limit = Math.min(Math.max(Number(url.searchParams.get('limit')) || 10, 1), 40);
105
+ return send(200, ranked.slice(0, limit).map(x => x.tag));
106
+ }
107
+
65
108
  const mTagTl = pathname.match(/^\/api\/v1\/timelines\/tag\/([^/]+)$/);
66
109
  if (mTagTl && req.method === 'GET') {
67
110
  const name = decodeURIComponent(mTagTl[1]).replace(/^#/, '').toLowerCase();
@@ -208,7 +251,83 @@ export async function handle(api, ctx) {
208
251
  })));
209
252
  }
210
253
 
254
+ // The direct timeline: every direct post, newest first, paged like the rest.
255
+ if (pathname === '/api/v1/timelines/direct' && req.method === 'GET') {
256
+ const all = api.store.getStatuses();
257
+ const items = all.filter(s => s.direct || s.visibility === 'direct');
258
+ const { items: page, headers } = api.page(items, url);
259
+ return send(200, page.map(s => api.status(s, { all })), headers);
260
+ }
261
+
262
+ // One notification by id — what a push notification tap opens — and the
263
+ // two ways of being rid of them.
264
+ if (pathname === '/api/v1/notifications/clear' && req.method === 'POST') {
265
+ api.store.removeNotifications(() => true);
266
+ return send(200, {});
267
+ }
268
+ const mNotif = /^\/api\/v1\/notifications\/([A-Za-z0-9_-]+)(\/dismiss)?$/.exec(pathname);
269
+ if (mNotif) {
270
+ const n = api.store.getNotifications().find(x => x.id === mNotif[1]);
271
+ if (!n) return send(404, { error: 'Record not found' });
272
+ if (mNotif[2] && req.method === 'POST') { api.store.removeNotifications(x => x.id === n.id); return send(200, {}); }
273
+ if (!mNotif[2] && req.method === 'GET') return send(200, api.notification(n));
274
+ }
275
+
276
+ // Grouped notifications, the shape a client reads from API version 2 on.
277
+ // One group per notification: the client does its own gathering of
278
+ // favourites and boosts on a post, and every field it joins on — the
279
+ // account and status ids — is here.
280
+ const mGroup = /^\/api\/v2\/notifications\/([A-Za-z0-9_-]+)(\/accounts|\/dismiss)?$/.exec(pathname);
281
+ if (mGroup) {
282
+ const n = api.store.getNotifications().find(x => x.id === mGroup[1]);
283
+ if (!n) return send(404, { error: 'Record not found' });
284
+ if (mGroup[2] === '/dismiss' && req.method === 'POST') { api.store.removeNotifications(x => x.id === n.id); return send(200, {}); }
285
+ if (mGroup[2] === '/accounts' && req.method === 'GET') return send(200, [api.account(n.actor)]);
286
+ if (!mGroup[2] && req.method === 'GET') return send(200, groupedNotifications(api, [n]));
287
+ }
288
+ if (pathname === '/api/v2/notifications') {
289
+ const { page, headers } = notificationPage(api, ctx);
290
+ return send(200, groupedNotifications(api, page), headers);
291
+ }
292
+
211
293
  if (pathname === '/api/v1/notifications') {
294
+ const { page, headers } = notificationPage(api, ctx);
295
+ return send(200, page.map((n) => api.notification(n)), headers);
296
+ }
297
+
298
+ // Search: @user@host → webfinger resolve; URL → actor or note ingest;
299
+ // plain text → local mirror + actor-cache scan.
300
+ if (pathname === '/api/v2/search' || pathname === '/api/v1/search') {
301
+ return searchHandler(api, ctx);
302
+ }
303
+ return false;
304
+ }
305
+
306
+ function groupedNotifications(api, page) {
307
+ const accounts = new Map();
308
+ const statuses = new Map();
309
+ const groups = page.map((n) => {
310
+ const acct = api.account(n.actor);
311
+ accounts.set(acct.id, acct);
312
+ const s = n.noteId && api.store.getStatuses().find(x => x.noteId === n.noteId);
313
+ let statusId = null;
314
+ if (s) { const j = api.status(s); statuses.set(j.id, j); statusId = j.id; }
315
+ return {
316
+ group_key: n.id, notifications_count: 1, type: api.notificationType(n.type),
317
+ most_recent_notification_id: n.id, page_min_id: n.id, page_max_id: n.id,
318
+ latest_page_notification_at: n.at, sample_account_ids: [acct.id],
319
+ ...(statusId ? { status_id: statusId } : {}),
320
+ ...(n.type === 'reaction' ? { emoji: n.emoji, ...(n.url ? { emoji_url: n.url } : {}) } : {}),
321
+ };
322
+ });
323
+ return { accounts: [...accounts.values()], statuses: [...statuses.values()], notification_groups: groups };
324
+ }
325
+
326
+ // The slice of notifications a request asks for, and the Link header that
327
+ // pages it. Shared by the flat list and the grouped one.
328
+ function notificationPage(api, ctx) {
329
+ const { req, pathname, url } = ctx;
330
+ {
212
331
  const limit = Math.min(Number(url.searchParams.get('limit')) || 30, 60);
213
332
  const q = url.searchParams;
214
333
  // Newest first, which is what every id rule below assumes.
@@ -261,17 +380,20 @@ export async function handle(api, ctx) {
261
380
  for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
262
381
  return u.href;
263
382
  };
264
- return send(200, page.map((n) => api.notification(n)), {
383
+ return { page, headers: {
265
384
  link: `<${link({ max_id: page[page.length - 1].id })}>; rel="next", `
266
385
  + `<${link({ min_id: page[0].id })}>; rel="prev"`,
267
- });
386
+ } };
268
387
  }
269
- return send(200, []);
388
+ return { page: [], headers: undefined };
270
389
  }
390
+ }
271
391
 
272
- // Search: @user@host → webfinger resolve; URL → actor or note ingest;
273
- // plain text → local mirror + actor-cache scan.
274
- if (pathname === '/api/v2/search' || pathname === '/api/v1/search') {
392
+ // Search: @user@host → webfinger resolve; URL → actor or note ingest;
393
+ // plain text → local mirror + actor-cache scan.
394
+ async function searchHandler(api, ctx) {
395
+ const { req, res, pathname, url, send } = ctx; // eslint-disable-line no-unused-vars
396
+ {
275
397
  const q = String(url.searchParams.get('q') || '').trim();
276
398
  const type = url.searchParams.get('type');
277
399
  const out = { accounts: [], statuses: [], hashtags: [] };
@@ -12,13 +12,36 @@ import { isContentType, authorOf } from '../core/intake/index.mjs';
12
12
  // and a bound where there was none.
13
13
  const MAX_TIMELINE_BYTES = 2 * 1024 * 1024;
14
14
 
15
+ // Two tags from each of six subjects, so a new account reads something on the
16
+ // first morning without having followed anybody. Deliberately even: a busy
17
+ // subject and a quiet one get the same number of places, and the sweep below
18
+ // makes sure the busy ones cannot take the quiet ones' turns as well.
15
19
  const DEFAULTS = {
16
20
  instance: 'https://mastodon.social',
17
- tags: ['solidproject', 'linkeddata', 'rdf'],
21
+ tags: [
22
+ 'fediverse', 'activitypub', // the network this account is on
23
+ 'opensource', 'foss', // how the software gets made
24
+ 'solidproject', 'linkeddata', // where the data lives
25
+ 'privacy', 'digitalrights', // what is at stake
26
+ 'indieweb', 'smallweb', // publishing for yourself
27
+ 'commons', 'platformcooperativism', // owning it together
28
+ ],
18
29
  intervalMin: 15,
19
30
  };
20
31
  const PER_TAG = 20; // statuses requested per tag per sweep
21
- const MAX_NEW_PER_SWEEP = 12; // dereference budget per sweep — stay light
32
+ const MAX_NEW_PER_SWEEP = 8; // dereference budget per sweep — stay light
33
+ // …and no one tag may take more than this much of it. Without a per-tag share
34
+ // the budget was spent in list order: the first two or three busy tags used it
35
+ // all and everything below them never appeared once, however long you waited.
36
+ const MAX_NEW_PER_TAG = 2;
37
+ // Only this many tags are polled in any one sweep, taken in turn. A sweep holds
38
+ // its writes until it finishes — so on the browser build, where the agent lives
39
+ // in a service worker the browser is free to kill whenever it likes, a sweep
40
+ // that runs long enough to be killed contributes NOTHING. Twelve tags was long
41
+ // enough: a new account could open the app repeatedly and still see an empty
42
+ // timeline. Four finishes. The list is still covered, three sweeps at a time,
43
+ // and a sweep now costs the gateway less than the three-tag one it replaced.
44
+ const TAGS_PER_SWEEP = 4;
22
45
  const MAX_TAG_ENTRIES = 200; // oldest tag entries pruned beyond this
23
46
  // The instance here is a stranger's server we poll on our own schedule, and it
24
47
  // has no other way to tell us to stop.
@@ -31,6 +54,9 @@ export class TagFeed {
31
54
  Object.assign(this, { store, intake, log, fetcher });
32
55
  this.lastSweep = null;
33
56
  this.lastAdded = 0;
57
+ // Where this sweep starts in the tag list. It moves on by one each time, so
58
+ // when the budget does run out it is a different tag that goes without.
59
+ this.cursor = 0;
34
60
  }
35
61
 
36
62
  config() { return { ...DEFAULTS, ...this.store.read('tagfeed.json', {}) }; }
@@ -91,6 +117,11 @@ export class TagFeed {
91
117
  const known = new Set(this.store.getStatuses().map(s => s.noteId));
92
118
  let budget = MAX_NEW_PER_SWEEP;
93
119
  let added = 0;
120
+ // A different handful every time, taken in turn, so no tag is permanently
121
+ // first and none is permanently last.
122
+ const start = this.cursor % tags.length;
123
+ this.cursor = (start + TAGS_PER_SWEEP) % tags.length;
124
+ const order = [...tags, ...tags].slice(start, start + Math.min(TAGS_PER_SWEEP, tags.length));
94
125
  // The store's 300ms debounce cannot coalesce a sweep that awaits between
95
126
  // items — every note costs a fetch to somebody else's server, which outlives
96
127
  // the timer — so statuses.json and actors.json, the two largest documents,
@@ -99,7 +130,8 @@ export class TagFeed {
99
130
  // acctfeed has done it this way since 2026-08-03; this is the same fix.
100
131
  this.store.hold?.();
101
132
  try {
102
- for (const tag of tags) {
133
+ for (const tag of order) {
134
+ let perTag = MAX_NEW_PER_TAG;
103
135
  let list;
104
136
  try {
105
137
  const { safeFetch, retryAfterMs, readCapped } = await import('../shared/safefetch.mjs');
@@ -129,7 +161,8 @@ export class TagFeed {
129
161
  for (const st of Array.isArray(list) ? list : []) {
130
162
  const noteId = st?.uri;
131
163
  if (!noteId || known.has(noteId) || this.store.isBlocked(noteId)) continue;
132
- if (budget-- <= 0) break;
164
+ if (perTag <= 0 || budget <= 0) break;
165
+ perTag--; budget--;
133
166
  const note = await this.intake.fetchAP(noteId).catch(() => null);
134
167
  if (!note || note.id !== noteId || !isContentType(note.type)) continue;
135
168
  // The author is only known once the note is dereferenced, and this
@@ -159,6 +192,10 @@ export class TagFeed {
159
192
  known.add(noteId);
160
193
  added++;
161
194
  }
195
+ // Nothing left to take, so stop asking. Every remaining tag would be a
196
+ // request whose answer is thrown away — and in the browser build every
197
+ // one of those goes out through the gateway.
198
+ if (budget <= 0) break;
162
199
  }
163
200
  const all = this.store.getStatuses();
164
201
  const tagged = all.filter(s => s.kind === 'tag');
@@ -170,6 +207,6 @@ export class TagFeed {
170
207
  this.store.release?.();
171
208
  }
172
209
  this.lastAdded = added;
173
- if (added) this.log(`tagfeed: +${added} from #${tags.join(' #')}`);
210
+ if (added) this.log(`tagfeed: +${added} from #${order.join(' #')}`);
174
211
  }
175
212
  }
package/lib/core/as2.mjs CHANGED
@@ -19,7 +19,7 @@
19
19
  // common shape: one that skipped it would not be read as JSON-LD, and would
20
20
  // produce no graph, so nothing could be validated against it.
21
21
  //
22
- // CONTEXTS ARE NEVER FETCHED. The loader serves the fifteen the fediverse
22
+ // CONTEXTS ARE NEVER FETCHED. The loader serves the ones the fediverse
23
23
  // actually uses and refuses every other URL. That refusal is the point: this
24
24
  // code runs on documents a stranger wrote, in the same path the SSRF guards
25
25
  // protect, and expanding one would otherwise mean dereferencing a URL of their
@@ -50,8 +50,14 @@ export class AS2Error extends Error {
50
50
  * the map throws rather than being fetched, so a document cannot make this
51
51
  * process reach out to an address its sender picked.
52
52
  */
53
+ // Pleroma and Akkoma name their own copy of the litepub schema, one URL per
54
+ // instance. Every copy is the same document, held here under the litepub
55
+ // namespace, so a per-instance URL names that entry.
56
+ const LITEPUB = 'http://litepub.social/ns';
57
+ export const heldKey = (url) => (typeof url === 'string' && /\/schemas\/litepub-0\.1\.jsonld$/u.test(url) ? LITEPUB : url);
58
+
53
59
  export function contextLoader(url) {
54
- const doc = CONTEXTS[url];
60
+ const doc = CONTEXTS[heldKey(url)];
55
61
  if (!doc) {
56
62
  return Promise.reject(new AS2Error(`context not held here: ${url}`, { context: url }));
57
63
  }
@@ -120,7 +126,7 @@ export async function parseAS2(raw) {
120
126
  */
121
127
  export function groundContext(input) {
122
128
  const ctx = input['@context'];
123
- const held = (c) => typeof c === 'string' ? Object.hasOwn(CONTEXTS, c) : (c && typeof c === 'object');
129
+ const held = (c) => typeof c === 'string' ? Object.hasOwn(CONTEXTS, heldKey(c)) : (c && typeof c === 'object');
124
130
  let kept;
125
131
  if (ctx === undefined || ctx === null) kept = [];
126
132
  else if (Array.isArray(ctx)) kept = ctx.filter(held);
@@ -143,6 +149,31 @@ export function groundContext(input) {
143
149
  * `doc` and `graph` are null only when the bytes are not JSON at all, which is
144
150
  * what the plain read would have concluded too.
145
151
  */
152
+ /**
153
+ * What a `null` in the document meant, carried onto the reading of it.
154
+ *
155
+ * ActivityPub §6.3.1: in an Update, a key set to `null` says REMOVE that
156
+ * property. JSON-LD has no way to carry that — expansion drops a null outright
157
+ * — so a partial update that takes something away arrives through the graph
158
+ * saying nothing at all, and is indistinguishable from an Update that never
159
+ * mentioned the property. The document plainly said it, so it is put back.
160
+ *
161
+ * Only nulls, only on the activity and its object, and only where the reading
162
+ * does not already have the key: nothing here overrides what the graph carried.
163
+ */
164
+ function carryRemovals(input, view) {
165
+ if (!input || typeof input !== 'object' || !view || typeof view !== 'object') return view;
166
+ for (const [key, value] of Object.entries(input)) {
167
+ if (value === null) {
168
+ if (!(key in view)) view[key] = null;
169
+ } else if (key === 'object' && typeof value === 'object' && !Array.isArray(value)
170
+ && view.object && typeof view.object === 'object' && !Array.isArray(view.object)) {
171
+ carryRemovals(value, view.object);
172
+ }
173
+ }
174
+ return view;
175
+ }
176
+
146
177
  export async function readLenient(raw) {
147
178
  // The document's own node is the one its `id` names. Handed to the view
148
179
  // as the root, so a document that points at itself — every actor does,
@@ -153,7 +184,7 @@ export async function readLenient(raw) {
153
184
  const root = typeof own === 'string' && /^https?:\/\//u.test(own) ? own : null;
154
185
  try {
155
186
  const { doc, graph } = await parseAS2(raw);
156
- return { doc, graph, view: graphView(graph, { root }), degraded: null };
187
+ return { doc, graph, view: carryRemovals(input, graphView(graph, { root })), degraded: null };
157
188
  } catch (e) {
158
189
  if (!input || typeof input !== 'object') return { doc: null, graph: null, view: null, degraded: e.message };
159
190
  // Grounded, so a graph exists for every document that is JSON at all.
@@ -164,7 +195,7 @@ export async function readLenient(raw) {
164
195
  // what they read, so rewriting it here silently changed what they posted.
165
196
  try {
166
197
  const { graph } = await parseAS2(groundContext(input));
167
- return { doc: input, graph, view: graphView(graph, { root }), degraded: e.message };
198
+ return { doc: input, graph, view: carryRemovals(input, graphView(graph, { root })), degraded: e.message };
168
199
  } catch (inner) {
169
200
  // Grounding names only held contexts, so reaching here means the document
170
201
  // is malformed JSON-LD rather than unfetchable. Hand back what a plain
@@ -28,6 +28,18 @@ import ctx13 from './miscellany.json' with { type: 'json' };
28
28
  // Not from fedify: the W3C Web Annotation context, which a client such as
29
29
  // dokieli names on the annotations it posts to the outbox.
30
30
  import ctx14 from './anno.json' with { type: 'json' };
31
+ // Not from fedify either. Pleroma and Akkoma name a copy of the litepub schema
32
+ // served by each instance itself, at /schemas/litepub-0.1.jsonld; every copy
33
+ // is the same document, and this is one of them. It is what declares
34
+ // EmojiReact, and the older quote spellings quoteUrl and quoteUri. Held under
35
+ // the litepub namespace, since no one URL names it; groundContext maps a
36
+ // per-instance URL onto this entry.
37
+ import ctx15 from './litepub-0.1.json' with { type: 'json' };
38
+ // The quote terms as Mastodon declares them inline on a post: FEP-044f's
39
+ // quote, quoteAuthorization, QuoteRequest and QuoteAuthorization, and
40
+ // Misskey's _misskey_quote. No document at the FEP's URL serves a context;
41
+ // the key is what lets the terms be named when reading.
42
+ import ctx16 from './quotes.json' with { type: 'json' };
31
43
 
32
44
  /** URL → the context document itself. Nothing outside this map is ever resolved. */
33
45
  export const CONTEXTS = {
@@ -46,4 +58,6 @@ export const CONTEXTS = {
46
58
  'http://joinmastodon.org/ns': ctx12,
47
59
  'https://purl.archive.org/miscellany': ctx13,
48
60
  'http://www.w3.org/ns/anno.jsonld': ctx14,
61
+ 'http://litepub.social/ns': ctx15,
62
+ 'https://w3id.org/fep/044f': ctx16,
49
63
  };
@@ -0,0 +1,60 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/ns/activitystreams",
4
+ "https://w3id.org/security/v1",
5
+ {
6
+ "Emoji": "toot:Emoji",
7
+ "Hashtag": "as:Hashtag",
8
+ "PropertyValue": "schema:PropertyValue",
9
+ "atomUri": "ostatus:atomUri",
10
+ "conversation": {
11
+ "@id": "ostatus:conversation",
12
+ "@type": "@id"
13
+ },
14
+ "discoverable": "toot:discoverable",
15
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
16
+ "capabilities": "litepub:capabilities",
17
+ "ostatus": "http://ostatus.org#",
18
+ "schema": "http://schema.org#",
19
+ "toot": "http://joinmastodon.org/ns#",
20
+ "misskey": "https://misskey-hub.net/ns#",
21
+ "fedibird": "http://fedibird.com/ns#",
22
+ "sharkey": "https://joinsharkey.org/ns#",
23
+ "value": "schema:value",
24
+ "sensitive": "as:sensitive",
25
+ "litepub": "http://litepub.social/ns#",
26
+ "invisible": "litepub:invisible",
27
+ "directMessage": "litepub:directMessage",
28
+ "listMessage": {
29
+ "@id": "litepub:listMessage",
30
+ "@type": "@id"
31
+ },
32
+ "quoteUrl": "as:quoteUrl",
33
+ "quoteUri": "fedibird:quoteUri",
34
+ "oauthRegistrationEndpoint": {
35
+ "@id": "litepub:oauthRegistrationEndpoint",
36
+ "@type": "@id"
37
+ },
38
+ "EmojiReact": "litepub:EmojiReact",
39
+ "ChatMessage": "litepub:ChatMessage",
40
+ "alsoKnownAs": {
41
+ "@id": "as:alsoKnownAs",
42
+ "@type": "@id"
43
+ },
44
+ "vcard": "http://www.w3.org/2006/vcard/ns#",
45
+ "formerRepresentations": "litepub:formerRepresentations",
46
+ "contentMap": {
47
+ "@id": "as:content",
48
+ "@container": "@language"
49
+ },
50
+ "featured": {
51
+ "@id": "toot:featured",
52
+ "@type": "@id"
53
+ },
54
+ "backgroundUrl": {
55
+ "@id": "sharkey:backgroundUrl",
56
+ "@type": "@id"
57
+ }
58
+ }
59
+ ]
60
+ }
@@ -13,5 +13,7 @@
13
13
  "https://join-lemmy.org/context.json": "join-lemmy.json",
14
14
  "http://joinmastodon.org/ns": "joinmastodon.json",
15
15
  "https://purl.archive.org/miscellany": "miscellany.json",
16
- "http://www.w3.org/ns/anno.jsonld": "anno.json"
16
+ "http://www.w3.org/ns/anno.jsonld": "anno.json",
17
+ "http://litepub.social/ns": "litepub-0.1.json",
18
+ "https://w3id.org/fep/044f": "quotes.json"
17
19
  }
@@ -0,0 +1,18 @@
1
+ {
2
+ "@context": {
3
+ "quote": {
4
+ "@id": "https://w3id.org/fep/044f#quote",
5
+ "@type": "@id"
6
+ },
7
+ "quoteAuthorization": {
8
+ "@id": "https://w3id.org/fep/044f#quoteAuthorization",
9
+ "@type": "@id"
10
+ },
11
+ "QuoteRequest": "https://w3id.org/fep/044f#QuoteRequest",
12
+ "QuoteAuthorization": "https://w3id.org/fep/044f#QuoteAuthorization",
13
+ "_misskey_quote": {
14
+ "@id": "https://misskey-hub.net/ns#_misskey_quote",
15
+ "@type": "@id"
16
+ }
17
+ }
18
+ }
@@ -55,8 +55,13 @@ function buildTerms() {
55
55
  const terms = new Map();
56
56
  const order = [AS_CTX, ...Object.keys(CONTEXTS).filter(u => u !== AS_CTX)];
57
57
  for (const url of order) {
58
- const ctx = CONTEXTS[url]?.['@context'];
59
- if (!ctx || typeof ctx !== 'object') continue;
58
+ const body = CONTEXTS[url]?.['@context'];
59
+ // A context document's body is one term map, or a list mixing URLs of
60
+ // other contexts with term maps — the litepub schema is the second shape.
61
+ // The URLs are held on their own; only the maps carry terms.
62
+ const maps = (Array.isArray(body) ? body : [body]).filter(c => c && typeof c === 'object');
63
+ if (!maps.length) continue;
64
+ const ctx = Object.assign({}, ...maps);
60
65
  const prefixes = {};
61
66
  for (const [k, v] of Object.entries(ctx)) {
62
67
  if (typeof v === 'string' && /^https?:\/\//.test(v)) prefixes[k] = v;
@@ -189,8 +194,41 @@ function isListHead(term, ctx) {
189
194
  * IRI, which is what a bare reference was. Handlers already test which they
190
195
  * got (`typeof activity.object === 'object'`), so both stay readable.
191
196
  */
197
+ // A literal's `.value` is its LEXICAL form, which is a string whatever the
198
+ // literal meant: `false` written in JSON-LD arrives here as the four characters
199
+ // "false", and `42` as two digits. Every handler downstream then reads a string
200
+ // where the document said a boolean or a number — and `!!"false"` is true, which
201
+ // is how a moderator's reopen was applied as another close.
202
+ //
203
+ // So the two datatypes whose lexical form lies about the value are turned back.
204
+ // Everything else is left exactly as it was, dates above all: a timestamp is
205
+ // carried, compared and republished as its string everywhere in this project,
206
+ // and "helpfully" turning it into a Date would break all of it.
207
+ const XSD = 'http://www.w3.org/2001/XMLSchema#';
208
+ const NUMERIC = new Set(['integer', 'decimal', 'double', 'float', 'long', 'int', 'short', 'byte',
209
+ 'nonNegativeInteger', 'positiveInteger', 'nonPositiveInteger', 'negativeInteger',
210
+ 'unsignedLong', 'unsignedInt', 'unsignedShort', 'unsignedByte'].map(t => XSD + t));
211
+
212
+ function fromLiteral(term) {
213
+ const type = term.datatype?.value || '';
214
+ if (type === `${XSD}boolean`) {
215
+ // The lexical space of xsd:boolean is exactly these four. Anything else is
216
+ // not a boolean the document can have meant, so it stays as it was written.
217
+ if (term.value === 'true' || term.value === '1') return true;
218
+ if (term.value === 'false' || term.value === '0') return false;
219
+ return term.value;
220
+ }
221
+ if (NUMERIC.has(type)) {
222
+ const n = Number(term.value);
223
+ // A numeric literal that will not parse is malformed; handing back NaN
224
+ // would lose what the document actually said.
225
+ return Number.isFinite(n) ? n : term.value;
226
+ }
227
+ return term.value;
228
+ }
229
+
192
230
  function toValue(term, ctx, depth, path) {
193
- if (term.termType === 'Literal') return term.value;
231
+ if (term.termType === 'Literal') return fromLiteral(term);
194
232
  if (isListHead(term, ctx)) return readList(term.value, ctx, depth, path);
195
233
  const preds = ctx.bySubject.get(term.value);
196
234
  if (preds && preds.size) return makeView(term.value, ctx, depth + 1, path);