@geekity/cms 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/README.md +2091 -214
  2. package/admin/components/fields.njk +165 -0
  3. package/admin/layouts/base.njk +1 -1
  4. package/admin/layouts/settings-page.njk +26 -0
  5. package/admin/layouts/shell.njk +17 -3
  6. package/admin/pages/account/forgot.njk +47 -0
  7. package/admin/pages/account/login.njk +26 -0
  8. package/admin/pages/account/reset.njk +38 -0
  9. package/admin/pages/account/setup.njk +28 -0
  10. package/admin/pages/appearance/themes.njk +69 -0
  11. package/admin/pages/comments/all.njk +133 -0
  12. package/admin/{layouts/dashboard.njk → pages/dashboard/home.njk} +8 -15
  13. package/admin/{layouts/document-conflict.njk → pages/documents/conflict.njk} +1 -0
  14. package/admin/pages/documents/editor.njk +196 -0
  15. package/admin/{layouts/document-list.njk → pages/documents/list.njk} +19 -2
  16. package/admin/pages/documents/taxonomy.njk +95 -0
  17. package/admin/pages/federation/followers.njk +238 -0
  18. package/admin/pages/media/library.njk +160 -0
  19. package/admin/pages/messages/all.njk +110 -0
  20. package/admin/{layouts → pages}/placeholder.njk +5 -1
  21. package/admin/pages/settings/discussion.njk +99 -0
  22. package/admin/pages/settings/email.njk +203 -0
  23. package/admin/pages/settings/federation.njk +75 -0
  24. package/admin/pages/settings/general.njk +29 -0
  25. package/admin/pages/settings/permalinks.njk +50 -0
  26. package/admin/pages/settings/reading.njk +72 -0
  27. package/admin/pages/users/edit.njk +239 -0
  28. package/admin/pages/users/list.njk +48 -0
  29. package/admin/pages/users/new.njk +41 -0
  30. package/admin/static/admin.css +498 -3
  31. package/admin/static/copy.js +52 -0
  32. package/admin/static/editor.js +16 -16
  33. package/admin/static/slug.js +61 -0
  34. package/dist/admin/accounts.d.ts +413 -0
  35. package/dist/admin/accounts.d.ts.map +1 -0
  36. package/dist/admin/accounts.js +680 -0
  37. package/dist/admin/accounts.js.map +1 -0
  38. package/dist/admin/appearance.d.ts +80 -0
  39. package/dist/admin/appearance.d.ts.map +1 -0
  40. package/dist/admin/appearance.js +136 -0
  41. package/dist/admin/appearance.js.map +1 -0
  42. package/dist/admin/comments.d.ts +90 -0
  43. package/dist/admin/comments.d.ts.map +1 -0
  44. package/dist/admin/comments.js +244 -0
  45. package/dist/admin/comments.js.map +1 -0
  46. package/dist/admin/credentials.d.ts +26 -2
  47. package/dist/admin/credentials.d.ts.map +1 -1
  48. package/dist/admin/credentials.js +35 -2
  49. package/dist/admin/credentials.js.map +1 -1
  50. package/dist/admin/documents.d.ts +127 -10
  51. package/dist/admin/documents.d.ts.map +1 -1
  52. package/dist/admin/documents.js +360 -41
  53. package/dist/admin/documents.js.map +1 -1
  54. package/dist/admin/federation.d.ts +151 -50
  55. package/dist/admin/federation.d.ts.map +1 -1
  56. package/dist/admin/federation.js +225 -93
  57. package/dist/admin/federation.js.map +1 -1
  58. package/dist/admin/formatting.d.ts +20 -0
  59. package/dist/admin/formatting.d.ts.map +1 -0
  60. package/dist/admin/formatting.js +72 -0
  61. package/dist/admin/formatting.js.map +1 -0
  62. package/dist/admin/headers.d.ts +65 -0
  63. package/dist/admin/headers.d.ts.map +1 -0
  64. package/dist/admin/headers.js +104 -0
  65. package/dist/admin/headers.js.map +1 -0
  66. package/dist/admin/index.d.ts +38 -12
  67. package/dist/admin/index.d.ts.map +1 -1
  68. package/dist/admin/index.js +25 -7
  69. package/dist/admin/index.js.map +1 -1
  70. package/dist/admin/media.d.ts +168 -0
  71. package/dist/admin/media.d.ts.map +1 -0
  72. package/dist/admin/media.js +295 -0
  73. package/dist/admin/media.js.map +1 -0
  74. package/dist/admin/menu.d.ts +79 -0
  75. package/dist/admin/menu.d.ts.map +1 -0
  76. package/dist/admin/menu.js +126 -0
  77. package/dist/admin/menu.js.map +1 -0
  78. package/dist/admin/messages.d.ts +80 -0
  79. package/dist/admin/messages.d.ts.map +1 -0
  80. package/dist/admin/messages.js +152 -0
  81. package/dist/admin/messages.js.map +1 -0
  82. package/dist/admin/preview.d.ts.map +1 -1
  83. package/dist/admin/preview.js +31 -7
  84. package/dist/admin/preview.js.map +1 -1
  85. package/dist/admin/recovery.d.ts +92 -0
  86. package/dist/admin/recovery.d.ts.map +1 -0
  87. package/dist/admin/recovery.js +229 -0
  88. package/dist/admin/recovery.js.map +1 -0
  89. package/dist/admin/routes.d.ts +0 -20
  90. package/dist/admin/routes.d.ts.map +1 -1
  91. package/dist/admin/routes.js +155 -105
  92. package/dist/admin/routes.js.map +1 -1
  93. package/dist/admin/settings-discussion.d.ts +37 -0
  94. package/dist/admin/settings-discussion.d.ts.map +1 -0
  95. package/dist/admin/settings-discussion.js +117 -0
  96. package/dist/admin/settings-discussion.js.map +1 -0
  97. package/dist/admin/settings-email.d.ts +54 -0
  98. package/dist/admin/settings-email.d.ts.map +1 -0
  99. package/dist/admin/settings-email.js +189 -0
  100. package/dist/admin/settings-email.js.map +1 -0
  101. package/dist/admin/settings-federation.d.ts +39 -0
  102. package/dist/admin/settings-federation.d.ts.map +1 -0
  103. package/dist/admin/settings-federation.js +103 -0
  104. package/dist/admin/settings-federation.js.map +1 -0
  105. package/dist/admin/settings-general.d.ts +12 -0
  106. package/dist/admin/settings-general.d.ts.map +1 -0
  107. package/dist/admin/settings-general.js +48 -0
  108. package/dist/admin/settings-general.js.map +1 -0
  109. package/dist/admin/settings-page.d.ts +102 -0
  110. package/dist/admin/settings-page.d.ts.map +1 -0
  111. package/dist/admin/settings-page.js +126 -0
  112. package/dist/admin/settings-page.js.map +1 -0
  113. package/dist/admin/settings-pages.d.ts +19 -0
  114. package/dist/admin/settings-pages.d.ts.map +1 -0
  115. package/dist/admin/settings-pages.js +33 -0
  116. package/dist/admin/settings-pages.js.map +1 -0
  117. package/dist/admin/settings-permalinks.d.ts +16 -0
  118. package/dist/admin/settings-permalinks.d.ts.map +1 -0
  119. package/dist/admin/settings-permalinks.js +32 -0
  120. package/dist/admin/settings-permalinks.js.map +1 -0
  121. package/dist/admin/settings-reading.d.ts +33 -0
  122. package/dist/admin/settings-reading.d.ts.map +1 -0
  123. package/dist/admin/settings-reading.js +48 -0
  124. package/dist/admin/settings-reading.js.map +1 -0
  125. package/dist/admin/settings.d.ts +325 -96
  126. package/dist/admin/settings.d.ts.map +1 -1
  127. package/dist/admin/settings.js +650 -308
  128. package/dist/admin/settings.js.map +1 -1
  129. package/dist/admin/store.d.ts +606 -152
  130. package/dist/admin/store.d.ts.map +1 -1
  131. package/dist/admin/store.js +863 -268
  132. package/dist/admin/store.js.map +1 -1
  133. package/dist/admin/taxonomy.d.ts +114 -0
  134. package/dist/admin/taxonomy.d.ts.map +1 -0
  135. package/dist/admin/taxonomy.js +289 -0
  136. package/dist/admin/taxonomy.js.map +1 -0
  137. package/dist/admin/templates.d.ts +44 -14
  138. package/dist/admin/templates.d.ts.map +1 -1
  139. package/dist/admin/templates.js +44 -14
  140. package/dist/admin/templates.js.map +1 -1
  141. package/dist/admin/throttle.d.ts +84 -0
  142. package/dist/admin/throttle.d.ts.map +1 -0
  143. package/dist/admin/throttle.js +143 -0
  144. package/dist/admin/throttle.js.map +1 -0
  145. package/dist/admin/uploads.d.ts +28 -2
  146. package/dist/admin/uploads.d.ts.map +1 -1
  147. package/dist/admin/uploads.js +43 -4
  148. package/dist/admin/uploads.js.map +1 -1
  149. package/dist/admin/users.d.ts +100 -13
  150. package/dist/admin/users.d.ts.map +1 -1
  151. package/dist/admin/users.js +412 -39
  152. package/dist/admin/users.js.map +1 -1
  153. package/dist/cache.d.ts +141 -0
  154. package/dist/cache.d.ts.map +1 -0
  155. package/dist/cache.js +207 -0
  156. package/dist/cache.js.map +1 -0
  157. package/dist/cli.d.ts +14 -2
  158. package/dist/cli.d.ts.map +1 -1
  159. package/dist/cli.js +424 -21
  160. package/dist/cli.js.map +1 -1
  161. package/dist/comments/akismet.d.ts +130 -0
  162. package/dist/comments/akismet.d.ts.map +1 -0
  163. package/dist/comments/akismet.js +274 -0
  164. package/dist/comments/akismet.js.map +1 -0
  165. package/dist/comments/conversation.d.ts +29 -0
  166. package/dist/comments/conversation.d.ts.map +1 -0
  167. package/dist/comments/conversation.js +60 -0
  168. package/dist/comments/conversation.js.map +1 -0
  169. package/dist/comments/form.d.ts +75 -0
  170. package/dist/comments/form.d.ts.map +1 -0
  171. package/dist/comments/form.js +64 -0
  172. package/dist/comments/form.js.map +1 -0
  173. package/dist/comments/index.d.ts +15 -0
  174. package/dist/comments/index.d.ts.map +1 -0
  175. package/dist/comments/index.js +9 -0
  176. package/dist/comments/index.js.map +1 -0
  177. package/dist/comments/markdown.d.ts +8 -0
  178. package/dist/comments/markdown.d.ts.map +1 -0
  179. package/dist/comments/markdown.js +85 -0
  180. package/dist/comments/markdown.js.map +1 -0
  181. package/dist/comments/moderate.d.ts +71 -0
  182. package/dist/comments/moderate.d.ts.map +1 -0
  183. package/dist/comments/moderate.js +88 -0
  184. package/dist/comments/moderate.js.map +1 -0
  185. package/dist/comments/policy.d.ts +57 -0
  186. package/dist/comments/policy.d.ts.map +1 -0
  187. package/dist/comments/policy.js +80 -0
  188. package/dist/comments/policy.js.map +1 -0
  189. package/dist/comments/records.d.ts +266 -0
  190. package/dist/comments/records.d.ts.map +1 -0
  191. package/dist/comments/records.js +487 -0
  192. package/dist/comments/records.js.map +1 -0
  193. package/dist/comments/routes.d.ts +58 -0
  194. package/dist/comments/routes.d.ts.map +1 -0
  195. package/dist/comments/routes.js +177 -0
  196. package/dist/comments/routes.js.map +1 -0
  197. package/dist/comments/submission.d.ts +263 -0
  198. package/dist/comments/submission.d.ts.map +1 -0
  199. package/dist/comments/submission.js +241 -0
  200. package/dist/comments/submission.js.map +1 -0
  201. package/dist/config.d.ts +190 -5
  202. package/dist/config.d.ts.map +1 -1
  203. package/dist/config.js +118 -6
  204. package/dist/config.js.map +1 -1
  205. package/dist/contact/delivery.d.ts +60 -0
  206. package/dist/contact/delivery.d.ts.map +1 -0
  207. package/dist/contact/delivery.js +87 -0
  208. package/dist/contact/delivery.js.map +1 -0
  209. package/dist/contact/form.d.ts +130 -0
  210. package/dist/contact/form.d.ts.map +1 -0
  211. package/dist/contact/form.js +163 -0
  212. package/dist/contact/form.js.map +1 -0
  213. package/dist/contact/index.d.ts +17 -0
  214. package/dist/contact/index.d.ts.map +1 -0
  215. package/dist/contact/index.js +13 -0
  216. package/dist/contact/index.js.map +1 -0
  217. package/dist/contact/records.d.ts +124 -0
  218. package/dist/contact/records.d.ts.map +1 -0
  219. package/dist/contact/records.js +218 -0
  220. package/dist/contact/records.js.map +1 -0
  221. package/dist/contact/routes.d.ts +17 -0
  222. package/dist/contact/routes.d.ts.map +1 -0
  223. package/dist/contact/routes.js +127 -0
  224. package/dist/contact/routes.js.map +1 -0
  225. package/dist/contact/submission.d.ts +103 -0
  226. package/dist/contact/submission.d.ts.map +1 -0
  227. package/dist/contact/submission.js +166 -0
  228. package/dist/contact/submission.js.map +1 -0
  229. package/dist/content/document.d.ts +7 -2
  230. package/dist/content/document.d.ts.map +1 -1
  231. package/dist/content/document.js +1 -0
  232. package/dist/content/document.js.map +1 -1
  233. package/dist/content/index.d.ts +7 -2
  234. package/dist/content/index.d.ts.map +1 -1
  235. package/dist/content/index.js +4 -1
  236. package/dist/content/index.js.map +1 -1
  237. package/dist/content/markdown.d.ts.map +1 -1
  238. package/dist/content/markdown.js +24 -2
  239. package/dist/content/markdown.js.map +1 -1
  240. package/dist/content/parser.d.ts.map +1 -1
  241. package/dist/content/parser.js +11 -4
  242. package/dist/content/parser.js.map +1 -1
  243. package/dist/content/save.d.ts +5 -0
  244. package/dist/content/save.d.ts.map +1 -1
  245. package/dist/content/save.js +44 -1
  246. package/dist/content/save.js.map +1 -1
  247. package/dist/content/schedule.d.ts +125 -0
  248. package/dist/content/schedule.d.ts.map +1 -0
  249. package/dist/content/schedule.js +207 -0
  250. package/dist/content/schedule.js.map +1 -0
  251. package/dist/content/search.d.ts +64 -0
  252. package/dist/content/search.d.ts.map +1 -0
  253. package/dist/content/search.js +136 -0
  254. package/dist/content/search.js.map +1 -0
  255. package/dist/content/store.d.ts +202 -7
  256. package/dist/content/store.d.ts.map +1 -1
  257. package/dist/content/store.js +374 -67
  258. package/dist/content/store.js.map +1 -1
  259. package/dist/content/sync.d.ts +7 -1
  260. package/dist/content/sync.d.ts.map +1 -1
  261. package/dist/content/sync.js +18 -10
  262. package/dist/content/sync.js.map +1 -1
  263. package/dist/content/time.d.ts +56 -0
  264. package/dist/content/time.d.ts.map +1 -0
  265. package/dist/content/time.js +141 -0
  266. package/dist/content/time.js.map +1 -0
  267. package/dist/content/writer.d.ts.map +1 -1
  268. package/dist/content/writer.js +3 -0
  269. package/dist/content/writer.js.map +1 -1
  270. package/dist/env.d.ts +58 -4
  271. package/dist/env.d.ts.map +1 -1
  272. package/dist/federation/actor.d.ts +95 -41
  273. package/dist/federation/actor.d.ts.map +1 -1
  274. package/dist/federation/actor.js +180 -53
  275. package/dist/federation/actor.js.map +1 -1
  276. package/dist/federation/article.d.ts +44 -17
  277. package/dist/federation/article.d.ts.map +1 -1
  278. package/dist/federation/article.js +98 -46
  279. package/dist/federation/article.js.map +1 -1
  280. package/dist/federation/delivery.d.ts +63 -15
  281. package/dist/federation/delivery.d.ts.map +1 -1
  282. package/dist/federation/delivery.js +154 -80
  283. package/dist/federation/delivery.js.map +1 -1
  284. package/dist/federation/federation.d.ts +22 -17
  285. package/dist/federation/federation.d.ts.map +1 -1
  286. package/dist/federation/federation.js +98 -77
  287. package/dist/federation/federation.js.map +1 -1
  288. package/dist/federation/followers.d.ts +3 -2
  289. package/dist/federation/followers.d.ts.map +1 -1
  290. package/dist/federation/followers.js +6 -5
  291. package/dist/federation/followers.js.map +1 -1
  292. package/dist/federation/import-wordpress.d.ts +128 -0
  293. package/dist/federation/import-wordpress.d.ts.map +1 -0
  294. package/dist/federation/import-wordpress.js +426 -0
  295. package/dist/federation/import-wordpress.js.map +1 -0
  296. package/dist/federation/inbox.d.ts +36 -13
  297. package/dist/federation/inbox.d.ts.map +1 -1
  298. package/dist/federation/inbox.js +120 -49
  299. package/dist/federation/inbox.js.map +1 -1
  300. package/dist/federation/index.d.ts +22 -10
  301. package/dist/federation/index.d.ts.map +1 -1
  302. package/dist/federation/index.js +13 -8
  303. package/dist/federation/index.js.map +1 -1
  304. package/dist/federation/keys.d.ts +98 -20
  305. package/dist/federation/keys.d.ts.map +1 -1
  306. package/dist/federation/keys.js +269 -39
  307. package/dist/federation/keys.js.map +1 -1
  308. package/dist/federation/mount.d.ts +42 -10
  309. package/dist/federation/mount.d.ts.map +1 -1
  310. package/dist/federation/mount.js +266 -26
  311. package/dist/federation/mount.js.map +1 -1
  312. package/dist/federation/paths.d.ts +23 -35
  313. package/dist/federation/paths.d.ts.map +1 -1
  314. package/dist/federation/paths.js +26 -39
  315. package/dist/federation/paths.js.map +1 -1
  316. package/dist/federation/records.d.ts +223 -0
  317. package/dist/federation/records.d.ts.map +1 -0
  318. package/dist/federation/records.js +506 -0
  319. package/dist/federation/records.js.map +1 -0
  320. package/dist/federation/relays.d.ts +114 -0
  321. package/dist/federation/relays.d.ts.map +1 -0
  322. package/dist/federation/relays.js +290 -0
  323. package/dist/federation/relays.js.map +1 -0
  324. package/dist/federation/replies.d.ts +80 -0
  325. package/dist/federation/replies.d.ts.map +1 -0
  326. package/dist/federation/replies.js +166 -0
  327. package/dist/federation/replies.js.map +1 -0
  328. package/dist/federation/wordpress.d.ts +131 -0
  329. package/dist/federation/wordpress.d.ts.map +1 -0
  330. package/dist/federation/wordpress.js +341 -0
  331. package/dist/federation/wordpress.js.map +1 -0
  332. package/dist/files/atomic.d.ts +103 -0
  333. package/dist/files/atomic.d.ts.map +1 -0
  334. package/dist/files/atomic.js +150 -0
  335. package/dist/files/atomic.js.map +1 -0
  336. package/dist/files/index.d.ts +3 -0
  337. package/dist/files/index.d.ts.map +1 -0
  338. package/dist/files/index.js +2 -0
  339. package/dist/files/index.js.map +1 -0
  340. package/dist/forms/protection.d.ts +85 -0
  341. package/dist/forms/protection.d.ts.map +1 -0
  342. package/dist/forms/protection.js +116 -0
  343. package/dist/forms/protection.js.map +1 -0
  344. package/dist/images/icons.d.ts +59 -0
  345. package/dist/images/icons.d.ts.map +1 -0
  346. package/dist/images/icons.js +131 -0
  347. package/dist/images/icons.js.map +1 -0
  348. package/dist/images/index.d.ts +7 -0
  349. package/dist/images/index.d.ts.map +1 -0
  350. package/dist/images/index.js +4 -0
  351. package/dist/images/index.js.map +1 -0
  352. package/dist/images/markup.d.ts +47 -0
  353. package/dist/images/markup.d.ts.map +1 -0
  354. package/dist/images/markup.js +175 -0
  355. package/dist/images/markup.js.map +1 -0
  356. package/dist/images/paths.d.ts +34 -0
  357. package/dist/images/paths.d.ts.map +1 -0
  358. package/dist/images/paths.js +41 -0
  359. package/dist/images/paths.js.map +1 -0
  360. package/dist/images/variants.d.ts +104 -0
  361. package/dist/images/variants.d.ts.map +1 -0
  362. package/dist/images/variants.js +320 -0
  363. package/dist/images/variants.js.map +1 -0
  364. package/dist/index.d.ts +123 -19
  365. package/dist/index.d.ts.map +1 -1
  366. package/dist/index.js +327 -25
  367. package/dist/index.js.map +1 -1
  368. package/dist/init.d.ts +18 -0
  369. package/dist/init.d.ts.map +1 -1
  370. package/dist/init.js +34 -3
  371. package/dist/init.js.map +1 -1
  372. package/dist/mail/brevo.d.ts +35 -0
  373. package/dist/mail/brevo.d.ts.map +1 -0
  374. package/dist/mail/brevo.js +85 -0
  375. package/dist/mail/brevo.js.map +1 -0
  376. package/dist/mail/credentials.d.ts +62 -0
  377. package/dist/mail/credentials.d.ts.map +1 -0
  378. package/dist/mail/credentials.js +86 -0
  379. package/dist/mail/credentials.js.map +1 -0
  380. package/dist/mail/index.d.ts +15 -0
  381. package/dist/mail/index.d.ts.map +1 -0
  382. package/dist/mail/index.js +8 -0
  383. package/dist/mail/index.js.map +1 -0
  384. package/dist/mail/memory.d.ts +35 -0
  385. package/dist/mail/memory.d.ts.map +1 -0
  386. package/dist/mail/memory.js +29 -0
  387. package/dist/mail/memory.js.map +1 -0
  388. package/dist/mail/provider.d.ts +80 -0
  389. package/dist/mail/provider.d.ts.map +1 -0
  390. package/dist/mail/provider.js +23 -0
  391. package/dist/mail/provider.js.map +1 -0
  392. package/dist/mail/service.d.ts +156 -0
  393. package/dist/mail/service.d.ts.map +1 -0
  394. package/dist/mail/service.js +265 -0
  395. package/dist/mail/service.js.map +1 -0
  396. package/dist/mail/smtp.d.ts +39 -0
  397. package/dist/mail/smtp.d.ts.map +1 -0
  398. package/dist/mail/smtp.js +58 -0
  399. package/dist/mail/smtp.js.map +1 -0
  400. package/dist/mail/templates.d.ts +91 -0
  401. package/dist/mail/templates.d.ts.map +1 -0
  402. package/dist/mail/templates.js +69 -0
  403. package/dist/mail/templates.js.map +1 -0
  404. package/dist/notifications/comments.d.ts +66 -0
  405. package/dist/notifications/comments.d.ts.map +1 -0
  406. package/dist/notifications/comments.js +151 -0
  407. package/dist/notifications/comments.js.map +1 -0
  408. package/dist/notifications/digest.d.ts +135 -0
  409. package/dist/notifications/digest.d.ts.map +1 -0
  410. package/dist/notifications/digest.js +287 -0
  411. package/dist/notifications/digest.js.map +1 -0
  412. package/dist/notifications/index.d.ts +13 -0
  413. package/dist/notifications/index.d.ts.map +1 -0
  414. package/dist/notifications/index.js +8 -0
  415. package/dist/notifications/index.js.map +1 -0
  416. package/dist/notifications/links.d.ts +60 -0
  417. package/dist/notifications/links.d.ts.map +1 -0
  418. package/dist/notifications/links.js +63 -0
  419. package/dist/notifications/links.js.map +1 -0
  420. package/dist/notifications/optouts.d.ts +48 -0
  421. package/dist/notifications/optouts.d.ts.map +1 -0
  422. package/dist/notifications/optouts.js +93 -0
  423. package/dist/notifications/optouts.js.map +1 -0
  424. package/dist/notifications/preferences.d.ts +158 -0
  425. package/dist/notifications/preferences.d.ts.map +1 -0
  426. package/dist/notifications/preferences.js +195 -0
  427. package/dist/notifications/preferences.js.map +1 -0
  428. package/dist/notifications/routes.d.ts +28 -0
  429. package/dist/notifications/routes.d.ts.map +1 -0
  430. package/dist/notifications/routes.js +184 -0
  431. package/dist/notifications/routes.js.map +1 -0
  432. package/dist/notifications/tokens.d.ts +60 -0
  433. package/dist/notifications/tokens.d.ts.map +1 -0
  434. package/dist/notifications/tokens.js +125 -0
  435. package/dist/notifications/tokens.js.map +1 -0
  436. package/dist/notify.d.ts +84 -0
  437. package/dist/notify.d.ts.map +1 -0
  438. package/dist/notify.js +149 -0
  439. package/dist/notify.js.map +1 -0
  440. package/dist/web/archive.d.ts +49 -0
  441. package/dist/web/archive.d.ts.map +1 -0
  442. package/dist/web/archive.js +59 -0
  443. package/dist/web/archive.js.map +1 -0
  444. package/dist/web/assets.d.ts +6 -5
  445. package/dist/web/assets.d.ts.map +1 -1
  446. package/dist/web/assets.js +5 -7
  447. package/dist/web/assets.js.map +1 -1
  448. package/dist/web/authors.d.ts +143 -0
  449. package/dist/web/authors.d.ts.map +1 -0
  450. package/dist/web/authors.js +178 -0
  451. package/dist/web/authors.js.map +1 -0
  452. package/dist/web/comments.d.ts +60 -0
  453. package/dist/web/comments.d.ts.map +1 -0
  454. package/dist/web/comments.js +207 -0
  455. package/dist/web/comments.js.map +1 -0
  456. package/dist/web/context.d.ts +162 -28
  457. package/dist/web/context.d.ts.map +1 -1
  458. package/dist/web/context.js +123 -26
  459. package/dist/web/context.js.map +1 -1
  460. package/dist/web/conversation.d.ts +209 -0
  461. package/dist/web/conversation.d.ts.map +1 -0
  462. package/dist/web/conversation.js +557 -0
  463. package/dist/web/conversation.js.map +1 -0
  464. package/dist/web/documents.d.ts +36 -6
  465. package/dist/web/documents.d.ts.map +1 -1
  466. package/dist/web/documents.js +52 -10
  467. package/dist/web/documents.js.map +1 -1
  468. package/dist/web/feed-atom.d.ts +22 -0
  469. package/dist/web/feed-atom.d.ts.map +1 -0
  470. package/dist/web/feed-atom.js +83 -0
  471. package/dist/web/feed-atom.js.map +1 -0
  472. package/dist/web/feed-item.d.ts +145 -0
  473. package/dist/web/feed-item.d.ts.map +1 -0
  474. package/dist/web/feed-item.js +120 -0
  475. package/dist/web/feed-item.js.map +1 -0
  476. package/dist/web/feed-json.d.ts +79 -0
  477. package/dist/web/feed-json.d.ts.map +1 -0
  478. package/dist/web/feed-json.js +53 -0
  479. package/dist/web/feed-json.js.map +1 -0
  480. package/dist/web/feed-rss.d.ts +26 -0
  481. package/dist/web/feed-rss.d.ts.map +1 -0
  482. package/dist/web/feed-rss.js +203 -0
  483. package/dist/web/feed-rss.js.map +1 -0
  484. package/dist/web/feed-source.d.ts +244 -0
  485. package/dist/web/feed-source.d.ts.map +1 -0
  486. package/dist/web/feed-source.js +201 -0
  487. package/dist/web/feed-source.js.map +1 -0
  488. package/dist/web/feed-xml.d.ts +63 -0
  489. package/dist/web/feed-xml.d.ts.map +1 -0
  490. package/dist/web/feed-xml.js +116 -0
  491. package/dist/web/feed-xml.js.map +1 -0
  492. package/dist/web/feeds.d.ts +31 -96
  493. package/dist/web/feeds.d.ts.map +1 -1
  494. package/dist/web/feeds.js +94 -198
  495. package/dist/web/feeds.js.map +1 -1
  496. package/dist/web/health.d.ts +31 -0
  497. package/dist/web/health.d.ts.map +1 -0
  498. package/dist/web/health.js +44 -0
  499. package/dist/web/health.js.map +1 -0
  500. package/dist/web/index.d.ts +30 -10
  501. package/dist/web/index.d.ts.map +1 -1
  502. package/dist/web/index.js +18 -7
  503. package/dist/web/index.js.map +1 -1
  504. package/dist/web/navigation.d.ts +73 -0
  505. package/dist/web/navigation.d.ts.map +1 -0
  506. package/dist/web/navigation.js +110 -0
  507. package/dist/web/navigation.js.map +1 -0
  508. package/dist/web/negotiate.d.ts +9 -0
  509. package/dist/web/negotiate.d.ts.map +1 -1
  510. package/dist/web/negotiate.js +6 -1
  511. package/dist/web/negotiate.js.map +1 -1
  512. package/dist/web/recent.d.ts +43 -0
  513. package/dist/web/recent.d.ts.map +1 -0
  514. package/dist/web/recent.js +37 -0
  515. package/dist/web/recent.js.map +1 -0
  516. package/dist/web/render.d.ts +197 -9
  517. package/dist/web/render.d.ts.map +1 -1
  518. package/dist/web/render.js +334 -17
  519. package/dist/web/render.js.map +1 -1
  520. package/dist/web/routes.d.ts +25 -8
  521. package/dist/web/routes.d.ts.map +1 -1
  522. package/dist/web/routes.js +845 -93
  523. package/dist/web/routes.js.map +1 -1
  524. package/dist/web/sanitize.d.ts +19 -0
  525. package/dist/web/sanitize.d.ts.map +1 -0
  526. package/dist/web/sanitize.js +272 -0
  527. package/dist/web/sanitize.js.map +1 -0
  528. package/dist/web/search.d.ts +79 -0
  529. package/dist/web/search.d.ts.map +1 -0
  530. package/dist/web/search.js +94 -0
  531. package/dist/web/search.js.map +1 -0
  532. package/dist/web/sitemap.d.ts +120 -0
  533. package/dist/web/sitemap.d.ts.map +1 -0
  534. package/dist/web/sitemap.js +199 -0
  535. package/dist/web/sitemap.js.map +1 -0
  536. package/dist/web/taxonomy.d.ts +152 -0
  537. package/dist/web/taxonomy.d.ts.map +1 -0
  538. package/dist/web/taxonomy.js +237 -0
  539. package/dist/web/taxonomy.js.map +1 -0
  540. package/dist/web/templates.d.ts +54 -16
  541. package/dist/web/templates.d.ts.map +1 -1
  542. package/dist/web/templates.js +109 -28
  543. package/dist/web/templates.js.map +1 -1
  544. package/dist/web/themes.d.ts +205 -0
  545. package/dist/web/themes.d.ts.map +1 -0
  546. package/dist/web/themes.js +277 -0
  547. package/dist/web/themes.js.map +1 -0
  548. package/dist/webmention/discovery.d.ts +51 -0
  549. package/dist/webmention/discovery.d.ts.map +1 -0
  550. package/dist/webmention/discovery.js +176 -0
  551. package/dist/webmention/discovery.js.map +1 -0
  552. package/dist/webmention/html.d.ts +71 -0
  553. package/dist/webmention/html.d.ts.map +1 -0
  554. package/dist/webmention/html.js +285 -0
  555. package/dist/webmention/html.js.map +1 -0
  556. package/dist/webmention/index.d.ts +12 -0
  557. package/dist/webmention/index.d.ts.map +1 -0
  558. package/dist/webmention/index.js +8 -0
  559. package/dist/webmention/index.js.map +1 -0
  560. package/dist/webmention/links.d.ts +18 -0
  561. package/dist/webmention/links.d.ts.map +1 -0
  562. package/dist/webmention/links.js +64 -0
  563. package/dist/webmention/links.js.map +1 -0
  564. package/dist/webmention/microformats.d.ts +93 -0
  565. package/dist/webmention/microformats.d.ts.map +1 -0
  566. package/dist/webmention/microformats.js +376 -0
  567. package/dist/webmention/microformats.js.map +1 -0
  568. package/dist/webmention/receive.d.ts +141 -0
  569. package/dist/webmention/receive.d.ts.map +1 -0
  570. package/dist/webmention/receive.js +264 -0
  571. package/dist/webmention/receive.js.map +1 -0
  572. package/dist/webmention/routes.d.ts +37 -0
  573. package/dist/webmention/routes.d.ts.map +1 -0
  574. package/dist/webmention/routes.js +75 -0
  575. package/dist/webmention/routes.js.map +1 -0
  576. package/dist/webmention/service.d.ts +94 -0
  577. package/dist/webmention/service.d.ts.map +1 -0
  578. package/dist/webmention/service.js +207 -0
  579. package/dist/webmention/service.js.map +1 -0
  580. package/package.json +11 -6
  581. package/templates/site/content/_data/site.json +1 -0
  582. package/templates/site/content/posts/2026-01-01-hello-world.md +5 -3
  583. package/templates/site/geekity.config.ts +11 -1
  584. package/templates/site/tsconfig.json +1 -1
  585. package/themes/default/README.md +1096 -50
  586. package/themes/default/layouts/404.njk +2 -3
  587. package/themes/default/layouts/author.njk +37 -0
  588. package/themes/default/layouts/base.njk +172 -31
  589. package/themes/default/layouts/category.njk +27 -0
  590. package/themes/default/layouts/front-page.njk +57 -0
  591. package/themes/default/layouts/home.njk +17 -1
  592. package/themes/default/layouts/page.njk +57 -5
  593. package/themes/default/layouts/post.njk +78 -19
  594. package/themes/default/layouts/search.njk +57 -0
  595. package/themes/default/layouts/tag.njk +3 -6
  596. package/themes/default/mail/comment-digest.html.njk +37 -0
  597. package/themes/default/mail/comment-digest.subject.njk +3 -0
  598. package/themes/default/mail/comment-digest.txt.njk +40 -0
  599. package/themes/default/mail/comment-pending.html.njk +27 -0
  600. package/themes/default/mail/comment-pending.subject.njk +2 -0
  601. package/themes/default/mail/comment-pending.txt.njk +24 -0
  602. package/themes/default/mail/comment-reply.html.njk +21 -0
  603. package/themes/default/mail/comment-reply.subject.njk +2 -0
  604. package/themes/default/mail/comment-reply.txt.njk +22 -0
  605. package/themes/default/mail/contact-message.html.njk +24 -0
  606. package/themes/default/mail/contact-message.subject.njk +5 -0
  607. package/themes/default/mail/contact-message.txt.njk +20 -0
  608. package/themes/default/mail/password-changed.html.njk +18 -0
  609. package/themes/default/mail/password-changed.subject.njk +2 -0
  610. package/themes/default/mail/password-changed.txt.njk +17 -0
  611. package/themes/default/mail/password-reset.html.njk +22 -0
  612. package/themes/default/mail/password-reset.subject.njk +2 -0
  613. package/themes/default/mail/password-reset.txt.njk +18 -0
  614. package/themes/default/mail/test.html.njk +14 -0
  615. package/themes/default/mail/test.subject.njk +2 -0
  616. package/themes/default/mail/test.txt.njk +12 -0
  617. package/themes/default/partials/archive.njk +27 -0
  618. package/themes/default/partials/bio.njk +77 -0
  619. package/themes/default/partials/comment-form.njk +109 -0
  620. package/themes/default/partials/contact-form.njk +88 -0
  621. package/themes/default/partials/conversation.njk +139 -0
  622. package/themes/default/partials/feeds.njk +26 -0
  623. package/themes/default/partials/jsonld.njk +120 -0
  624. package/themes/default/partials/pagination.njk +12 -6
  625. package/themes/default/partials/post-list.njk +51 -18
  626. package/themes/default/partials/search-form.njk +17 -0
  627. package/themes/default/partials/tags.njk +26 -5
  628. package/themes/default/static/highlight.js +4 -0
  629. package/themes/default/static/style.css +1119 -194
  630. package/themes/default/theme.json +5 -0
  631. package/admin/layouts/document-editor.njk +0 -167
  632. package/admin/layouts/federation.njk +0 -145
  633. package/admin/layouts/login.njk +0 -23
  634. package/admin/layouts/settings.njk +0 -116
  635. package/admin/layouts/setup.njk +0 -27
  636. package/admin/layouts/users.njk +0 -91
  637. /package/admin/{layouts/_flash.njk → components/flash.njk} +0 -0
@@ -0,0 +1,131 @@
1
+ import type { InboxContext } from '@fedify/fedify';
2
+ import type { User } from '../admin/accounts.ts';
3
+ import type { CreateSiteFederationOptions, FederationContextData } from './federation.ts';
4
+ import type { SiteFederation } from './federation.ts';
5
+ /**
6
+ * The WordPress ActivityPub plugin's paths, served behind a switch (TASK-70).
7
+ *
8
+ * A follower's server delivers to the inbox URL it cached from the actor
9
+ * document, and replaces it only when it next refetches the actor. A site that
10
+ * moved here from the plugin therefore goes on being delivered to at
11
+ * `/wp-json/activitypub/1.0/actors/2/inbox` and `/wp-json/activitypub/1.0/inbox`
12
+ * for as long as those caches last, and decision-14 calls that **cache rather
13
+ * than identity**: the CMS carries the plugin's layout until the caches have
14
+ * moved on, and no longer.
15
+ *
16
+ * So everything here is behind the `wordpressActivityPub` site setting, and
17
+ * everything a peer reads back is the canonical identity. The actor served at
18
+ * `…/actors/2` is the same `Person` the author URL serves — same `id`, same
19
+ * canonical `inbox` — so a peer that refetches here learns the new endpoints
20
+ * rather than being handed the old ones again. That is what eventually makes
21
+ * the switch safe to turn off.
22
+ *
23
+ * A second `Federation` is how Fedify serves a second inbox path at all: one
24
+ * object may have exactly one pair of inbox listeners, and `ctx.routeActivity`
25
+ * re-verifies from scratch in a way a Mastodon or WordPress `Follow` cannot
26
+ * satisfy (doc-8). The second object shares the canonical one's KV store, and
27
+ * both sets of listeners are set to `per-origin` idempotence, because the
28
+ * default folds the recipient identifier into the key — so the same `Follow`
29
+ * redelivered to `andrew` and to `2` would otherwise be handled twice.
30
+ */
31
+ /** Where the plugin's REST namespace lives, on the site's origin. */
32
+ export declare const WORDPRESS_ACTIVITYPUB_BASE = "/wp-json/activitypub/1.0";
33
+ /** The plugin's actor path. Its identifier is the WordPress user's number. */
34
+ export declare const WORDPRESS_ACTOR_PATH: "/wp-json/activitypub/1.0/actors/{identifier}";
35
+ /** The actor's inbox, outbox and two follow collections, as the plugin spells them. */
36
+ export declare const WORDPRESS_INBOX_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/inbox";
37
+ export declare const WORDPRESS_OUTBOX_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/outbox";
38
+ export declare const WORDPRESS_FOLLOWERS_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/followers";
39
+ export declare const WORDPRESS_FOLLOWING_PATH: "/wp-json/activitypub/1.0/actors/{identifier}/following";
40
+ /** The plugin's instance-wide inbox, which its `sharedInbox` endpoint names. */
41
+ export declare const WORDPRESS_SHARED_INBOX_PATH: "/wp-json/activitypub/1.0/inbox";
42
+ /** What {@link createWordPressFederation} takes beyond the canonical options. */
43
+ export interface CreateWordPressFederationOptions extends CreateSiteFederationOptions {
44
+ /**
45
+ * The site's own federation, whose contexts every identity here is built
46
+ * from.
47
+ *
48
+ * Fedify derives an actor's id, its key ids and its collection URLs from the
49
+ * path it dispatches the actor at, and the paths here are the plugin's. A
50
+ * document built off this object's own context would tell a peer that the
51
+ * person *is* `…/wp-json/…/actors/2` and sign with a key id under it, which
52
+ * is the opposite of what the switch is for.
53
+ */
54
+ canonical: SiteFederation;
55
+ }
56
+ /**
57
+ * Build the compatibility federation: the plugin's inbox and collections,
58
+ * mapped to users by the number WordPress gave them.
59
+ *
60
+ * Nothing else is registered. WebFinger, NodeInfo and the actor's real
61
+ * endpoints belong to the canonical federation and stay there; this object
62
+ * exists only so that deliveries and fetches aimed at the old layout land
63
+ * somewhere, signature-verified at the real request path exactly as the
64
+ * canonical inbox verifies them.
65
+ */
66
+ export declare function createWordPressFederation(options: CreateWordPressFederationOptions): SiteFederation;
67
+ /**
68
+ * The user the WordPress ActivityPub plugin numbered `identifier`, or
69
+ * `undefined`.
70
+ *
71
+ * The identifier is a path segment, so it is compared as the decimal the
72
+ * plugin writes: `02` and `2.0` name nobody, because neither is a URL the
73
+ * plugin ever published.
74
+ */
75
+ export declare function userByWordPressActorId(dataDir: string, identifier: string): User | undefined;
76
+ /** One of the plugin's paths, as the record of what was asked for names it. */
77
+ export type WordPressRoute = 'actor' | 'inbox' | 'outbox' | 'followers' | 'following';
78
+ /** Which compatibility path a request is for, and whose. */
79
+ export interface WordPressRequestTarget {
80
+ /** The path it is, or `sharedInbox` for the instance-wide one. */
81
+ readonly route: WordPressRoute | 'sharedInbox';
82
+ /** The WordPress number in the path, or `undefined` for the shared inbox. */
83
+ readonly wordpressActorId?: string | undefined;
84
+ }
85
+ /**
86
+ * The compatibility path a request asks for, or `undefined` for anything else.
87
+ *
88
+ * Matched here rather than left to Fedify because the record of what was last
89
+ * asked for has to be written whether or not a user answers: a delivery to a
90
+ * number nobody carries is still a peer holding the old URL, and it is exactly
91
+ * the sort of thing the owner watching the screen wants to see. Fedify's
92
+ * router still decides what is actually served.
93
+ */
94
+ export declare function wordPressRequestTarget(pathname: string): WordPressRequestTarget | undefined;
95
+ /** The file the instants live in, relative to `dataDir`. */
96
+ export declare const WORDPRESS_REQUESTS_FILE = "wordpress-activitypub.json";
97
+ /** Where that file lives for a given data directory. */
98
+ export declare function wordPressRequestsFile(dataDir: string): string;
99
+ /**
100
+ * When each compatibility path was last asked for.
101
+ *
102
+ * A file under `data/` rather than a table, because the database is a cache a
103
+ * site may delete (decision-9) and this is the one thing the switch is watched
104
+ * by: an owner deciding whether every follower's server has refetched the
105
+ * actor should not have that answer reset by a rebuild.
106
+ */
107
+ export interface WordPressRequests {
108
+ /** When the instance-wide inbox was last delivered to, or `undefined`. */
109
+ readonly sharedInbox?: string | undefined;
110
+ /** When each of a user's own paths was last asked for, by username. */
111
+ readonly users: Readonly<Record<string, Readonly<Partial<Record<WordPressRoute, string>>>>>;
112
+ }
113
+ /** The file as it reads now; a site nobody has asked has nothing to say. */
114
+ export declare function readWordPressRequests(dataDir: string): WordPressRequests;
115
+ /**
116
+ * Record that one compatibility path was just asked for.
117
+ *
118
+ * Written through {@link updateFileAtomically}, so two deliveries arriving
119
+ * together cannot lose one another's instant and a reader never sees a
120
+ * half-written file. A path whose number names nobody is not recorded: the
121
+ * record is per user, and there is no user to record it against.
122
+ */
123
+ export declare function recordWordPressRequest(options: {
124
+ dataDir: string;
125
+ target: WordPressRequestTarget;
126
+ username?: string | undefined;
127
+ at: Date;
128
+ }): Promise<void>;
129
+ /** A compatibility context, for the handlers that are handed one. */
130
+ export type WordPressInboxContext = InboxContext<FederationContextData>;
131
+ //# sourceMappingURL=wordpress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wordpress.d.ts","sourceRoot":"","sources":["../../src/federation/wordpress.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAW,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAK5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAKjD,OAAO,KAAK,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAa1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,qEAAqE;AACrE,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAErE,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,gDAA+D,CAAC;AAEjG,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,sDAA2C,CAAC;AAC7E,eAAO,MAAM,qBAAqB,uDAA4C,CAAC;AAC/E,eAAO,MAAM,wBAAwB,0DAA+C,CAAC;AACrF,eAAO,MAAM,wBAAwB,0DAA+C,CAAC;AAErF,gFAAgF;AAChF,eAAO,MAAM,2BAA2B,kCAAiD,CAAC;AAE1F,iFAAiF;AACjF,MAAM,WAAW,gCAAiC,SAAQ,2BAA2B;IACnF;;;;;;;;;OASG;IACH,SAAS,EAAE,cAAc,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,cAAc,CAuIhB;AA6BD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAI5F;AAED,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AAEtF,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,aAAa,CAAC;IAC/C,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChD;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAc3F;AAED,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,+BAA+B,CAAC;AAEpE,wDAAwD;AACxD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7F;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAaxE;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,EAAE,EAAE,IAAI,CAAC;CACV,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBhB;AAsDD,qEAAqE;AACrE,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC"}
@@ -0,0 +1,341 @@
1
+ import path from 'node:path';
2
+ import { createFederation, InProcessMessageQueue, MemoryKvStore } from '@fedify/fedify';
3
+ import { Accept, Announce, Create, Delete, Follow, Like, Reject, Undo } from '@fedify/vocab';
4
+ import { listUsers } from '../admin/accounts.js';
5
+ import { readFileIfPresentSync, updateFileAtomically } from '../files/atomic.js';
6
+ import { authorNames } from '../web/authors.js';
7
+ import { userActor } from './actor.js';
8
+ import { federationOrigin } from './paths.js';
9
+ import { outboxPage, OUTBOX_PAGE_SIZE } from './federation.js';
10
+ import { followersPage, lastFollowersCursor } from './followers.js';
11
+ import { handleAccept, handleDelete, handleFollow, handleLoggedActivity, handleReject, handleUndo, } from './inbox.js';
12
+ import { loadActorKeyPairs } from './keys.js';
13
+ /**
14
+ * The WordPress ActivityPub plugin's paths, served behind a switch (TASK-70).
15
+ *
16
+ * A follower's server delivers to the inbox URL it cached from the actor
17
+ * document, and replaces it only when it next refetches the actor. A site that
18
+ * moved here from the plugin therefore goes on being delivered to at
19
+ * `/wp-json/activitypub/1.0/actors/2/inbox` and `/wp-json/activitypub/1.0/inbox`
20
+ * for as long as those caches last, and decision-14 calls that **cache rather
21
+ * than identity**: the CMS carries the plugin's layout until the caches have
22
+ * moved on, and no longer.
23
+ *
24
+ * So everything here is behind the `wordpressActivityPub` site setting, and
25
+ * everything a peer reads back is the canonical identity. The actor served at
26
+ * `…/actors/2` is the same `Person` the author URL serves — same `id`, same
27
+ * canonical `inbox` — so a peer that refetches here learns the new endpoints
28
+ * rather than being handed the old ones again. That is what eventually makes
29
+ * the switch safe to turn off.
30
+ *
31
+ * A second `Federation` is how Fedify serves a second inbox path at all: one
32
+ * object may have exactly one pair of inbox listeners, and `ctx.routeActivity`
33
+ * re-verifies from scratch in a way a Mastodon or WordPress `Follow` cannot
34
+ * satisfy (doc-8). The second object shares the canonical one's KV store, and
35
+ * both sets of listeners are set to `per-origin` idempotence, because the
36
+ * default folds the recipient identifier into the key — so the same `Follow`
37
+ * redelivered to `andrew` and to `2` would otherwise be handled twice.
38
+ */
39
+ /** Where the plugin's REST namespace lives, on the site's origin. */
40
+ export const WORDPRESS_ACTIVITYPUB_BASE = '/wp-json/activitypub/1.0';
41
+ /** The plugin's actor path. Its identifier is the WordPress user's number. */
42
+ export const WORDPRESS_ACTOR_PATH = `${WORDPRESS_ACTIVITYPUB_BASE}/actors/{identifier}`;
43
+ /** The actor's inbox, outbox and two follow collections, as the plugin spells them. */
44
+ export const WORDPRESS_INBOX_PATH = `${WORDPRESS_ACTOR_PATH}/inbox`;
45
+ export const WORDPRESS_OUTBOX_PATH = `${WORDPRESS_ACTOR_PATH}/outbox`;
46
+ export const WORDPRESS_FOLLOWERS_PATH = `${WORDPRESS_ACTOR_PATH}/followers`;
47
+ export const WORDPRESS_FOLLOWING_PATH = `${WORDPRESS_ACTOR_PATH}/following`;
48
+ /** The plugin's instance-wide inbox, which its `sharedInbox` endpoint names. */
49
+ export const WORDPRESS_SHARED_INBOX_PATH = `${WORDPRESS_ACTIVITYPUB_BASE}/inbox`;
50
+ /**
51
+ * Build the compatibility federation: the plugin's inbox and collections,
52
+ * mapped to users by the number WordPress gave them.
53
+ *
54
+ * Nothing else is registered. WebFinger, NodeInfo and the actor's real
55
+ * endpoints belong to the canonical federation and stay there; this object
56
+ * exists only so that deliveries and fetches aimed at the old layout land
57
+ * somewhere, signature-verified at the real request path exactly as the
58
+ * canonical inbox verifies them.
59
+ */
60
+ export function createWordPressFederation(options) {
61
+ const { canonical } = options;
62
+ const federation = createFederation({
63
+ kv: options.kv ?? new MemoryKvStore(),
64
+ origin: federationOrigin(options.baseUrl),
65
+ ...(options.queue === null ? {} : { queue: options.queue ?? new InProcessMessageQueue() }),
66
+ ...(options.allowPrivateAddress === undefined
67
+ ? {}
68
+ : { allowPrivateAddress: options.allowPrivateAddress }),
69
+ ...options.federationOptions,
70
+ });
71
+ /** The user a dispatcher's identifier names, which here is a number. */
72
+ function actorFor(context, identifier) {
73
+ return userByWordPressActorId(context.data.config.dataDir, identifier);
74
+ }
75
+ /**
76
+ * A context of the canonical federation over the same data.
77
+ *
78
+ * Every id a peer reads or verifies comes from here rather than from the
79
+ * compatibility context, so the actor, the key ids, the `Accept` and the
80
+ * collections a peer is pointed at are the ones the site really publishes.
81
+ */
82
+ function identity(context) {
83
+ return canonical.createContext(new URL(context.origin), context.data);
84
+ }
85
+ /** Every stored `author` string that reads as this user (TASK-67). */
86
+ function namesOf(context, user) {
87
+ return authorNames(listUsers(context.data.config.dataDir), user);
88
+ }
89
+ federation
90
+ .setActorDispatcher(WORDPRESS_ACTOR_PATH, async (context, identifier) => {
91
+ const user = actorFor(context, identifier);
92
+ if (user === undefined)
93
+ return null;
94
+ // The canonical document, verbatim: a peer refetching the actor at the
95
+ // old URL is exactly the peer that should learn the new inbox.
96
+ return await userActor(identity(context), user, { baseUrl: context.data.config.baseUrl });
97
+ })
98
+ .setKeyPairsDispatcher(async (context, identifier) => {
99
+ const user = actorFor(context, identifier);
100
+ return user === undefined
101
+ ? []
102
+ : await loadActorKeyPairs(context.data.config.dataDir, user.username);
103
+ });
104
+ federation
105
+ .setOutboxDispatcher(WORDPRESS_OUTBOX_PATH, (context, identifier, cursor) => {
106
+ const user = actorFor(context, identifier);
107
+ return user === undefined
108
+ ? null
109
+ : outboxPage(identity(context), namesOf(context, user), cursor);
110
+ })
111
+ .setCounter((context, identifier) => {
112
+ const user = actorFor(context, identifier);
113
+ return user === undefined ? null : context.data.store.countByAuthor(namesOf(context, user));
114
+ })
115
+ .setFirstCursor((context, identifier) => actorFor(context, identifier) === undefined ? null : '0')
116
+ .setLastCursor((context, identifier) => {
117
+ const user = actorFor(context, identifier);
118
+ if (user === undefined)
119
+ return null;
120
+ const total = context.data.store.countByAuthor(namesOf(context, user));
121
+ return String(total === 0 ? 0 : Math.floor((total - 1) / OUTBOX_PAGE_SIZE) * OUTBOX_PAGE_SIZE);
122
+ });
123
+ federation
124
+ .setFollowersDispatcher(WORDPRESS_FOLLOWERS_PATH, (context, identifier, cursor) => {
125
+ const user = actorFor(context, identifier);
126
+ return user === undefined ? null : followersPage(context, user.username, cursor);
127
+ })
128
+ .setCounter((context, identifier) => {
129
+ const user = actorFor(context, identifier);
130
+ return user === undefined ? null : context.data.admin.countFollowers(user.username);
131
+ })
132
+ .setFirstCursor((context, identifier) => actorFor(context, identifier) === undefined ? null : '0')
133
+ .setLastCursor((context, identifier) => {
134
+ const user = actorFor(context, identifier);
135
+ return user === undefined
136
+ ? null
137
+ : lastFollowersCursor(context.data.admin.countFollowers(user.username));
138
+ });
139
+ federation.setFollowingDispatcher(WORDPRESS_FOLLOWING_PATH, (context, identifier) => actorFor(context, identifier) === undefined ? null : { items: [] });
140
+ // The same five handlers the canonical inbox runs, over a canonical context.
141
+ // `per-origin` idempotence is what keeps one `Follow` redelivered to both
142
+ // inboxes from being handled twice (doc-8); the canonical federation is set
143
+ // the same way, and they share a KV store so the two agree about what they
144
+ // have already seen.
145
+ federation
146
+ .setInboxListeners(WORDPRESS_INBOX_PATH, WORDPRESS_SHARED_INBOX_PATH)
147
+ .withIdempotency('per-origin')
148
+ .on(Follow, canonically(handleFollow))
149
+ .on(Accept, canonically(handleAccept))
150
+ .on(Reject, canonically(handleReject))
151
+ .on(Undo, canonically(handleUndo))
152
+ .on(Delete, canonically(handleDelete))
153
+ .on(Like, canonically(handleLoggedActivity))
154
+ .on(Announce, canonically(handleLoggedActivity))
155
+ .on(Create, canonically(handleLoggedActivity));
156
+ /**
157
+ * One inbox handler, run against the canonical federation.
158
+ *
159
+ * Two things are swapped. The context is the canonical one, so `parseUri`
160
+ * recognises the site's own actor URLs, the `Accept` is signed with the key
161
+ * the actor document publishes and sent from the id its followers hold. And
162
+ * `recipient` — which arrives here as the WordPress number, straight off the
163
+ * path — is the username, because that is what every handler and the inbox
164
+ * log mean by a recipient.
165
+ */
166
+ function canonically(handle) {
167
+ return async (context, activity) => {
168
+ const user = context.recipient === null ? undefined : actorFor(context, context.recipient);
169
+ await handle(withRecipient(identity(context), context, user?.username ?? null), activity);
170
+ };
171
+ }
172
+ return federation;
173
+ }
174
+ /**
175
+ * A canonical context wearing one inbox delivery's recipient.
176
+ *
177
+ * A `Proxy` rather than a copy: Fedify's context is a class with private state,
178
+ * so its methods have to keep running against the object they were made for.
179
+ * Everything but `recipient` and `forwardActivity` is the canonical context's
180
+ * own.
181
+ */
182
+ function withRecipient(canonical, delivered, recipient) {
183
+ return new Proxy(canonical, {
184
+ get(target, property) {
185
+ if (property === 'recipient')
186
+ return recipient;
187
+ // Forwarding is the one thing only the delivering context can do: it
188
+ // holds the bytes that arrived, signatures and all.
189
+ if (property === 'forwardActivity')
190
+ return delivered.forwardActivity.bind(delivered);
191
+ const value = Reflect.get(target, property, target);
192
+ return typeof value === 'function'
193
+ ? value.bind(target)
194
+ : value;
195
+ },
196
+ });
197
+ }
198
+ /**
199
+ * The user the WordPress ActivityPub plugin numbered `identifier`, or
200
+ * `undefined`.
201
+ *
202
+ * The identifier is a path segment, so it is compared as the decimal the
203
+ * plugin writes: `02` and `2.0` name nobody, because neither is a URL the
204
+ * plugin ever published.
205
+ */
206
+ export function userByWordPressActorId(dataDir, identifier) {
207
+ if (!/^[1-9][0-9]*$/.test(identifier))
208
+ return undefined;
209
+ const wanted = Number(identifier);
210
+ return listUsers(dataDir).find((user) => user.wordpressActorId === wanted);
211
+ }
212
+ /**
213
+ * The compatibility path a request asks for, or `undefined` for anything else.
214
+ *
215
+ * Matched here rather than left to Fedify because the record of what was last
216
+ * asked for has to be written whether or not a user answers: a delivery to a
217
+ * number nobody carries is still a peer holding the old URL, and it is exactly
218
+ * the sort of thing the owner watching the screen wants to see. Fedify's
219
+ * router still decides what is actually served.
220
+ */
221
+ export function wordPressRequestTarget(pathname) {
222
+ if (pathname === WORDPRESS_SHARED_INBOX_PATH)
223
+ return { route: 'sharedInbox' };
224
+ const under = `${WORDPRESS_ACTIVITYPUB_BASE}/actors/`;
225
+ if (!pathname.startsWith(under))
226
+ return undefined;
227
+ const [number, name, ...rest] = pathname.slice(under.length).split('/');
228
+ if (number === undefined || number === '' || rest.length > 0)
229
+ return undefined;
230
+ if (name === undefined || name === '')
231
+ return { route: 'actor', wordpressActorId: number };
232
+ if (name === 'inbox' || name === 'outbox' || name === 'followers' || name === 'following') {
233
+ return { route: name, wordpressActorId: number };
234
+ }
235
+ return undefined;
236
+ }
237
+ /** The file the instants live in, relative to `dataDir`. */
238
+ export const WORDPRESS_REQUESTS_FILE = 'wordpress-activitypub.json';
239
+ /** Where that file lives for a given data directory. */
240
+ export function wordPressRequestsFile(dataDir) {
241
+ return path.join(dataDir, WORDPRESS_REQUESTS_FILE);
242
+ }
243
+ /** The file as it reads now; a site nobody has asked has nothing to say. */
244
+ export function readWordPressRequests(dataDir) {
245
+ const source = readFileIfPresentSync(wordPressRequestsFile(dataDir));
246
+ if (source === undefined)
247
+ return { users: {} };
248
+ let parsed;
249
+ try {
250
+ parsed = JSON.parse(source);
251
+ }
252
+ catch {
253
+ // A record of who asked for what is not worth failing a request over, and
254
+ // "never" is the honest answer when the file cannot be read.
255
+ return { users: {} };
256
+ }
257
+ return requestsFrom(parsed);
258
+ }
259
+ /**
260
+ * Record that one compatibility path was just asked for.
261
+ *
262
+ * Written through {@link updateFileAtomically}, so two deliveries arriving
263
+ * together cannot lose one another's instant and a reader never sees a
264
+ * half-written file. A path whose number names nobody is not recorded: the
265
+ * record is per user, and there is no user to record it against.
266
+ */
267
+ export async function recordWordPressRequest(options) {
268
+ const { target, username } = options;
269
+ if (target.route !== 'sharedInbox' && username === undefined)
270
+ return;
271
+ const instant = options.at.toISOString();
272
+ const file = wordPressRequestsFile(options.dataDir);
273
+ await updateFileAtomically(file, (current) => {
274
+ const requests = requestsFrom(parseOrEmpty(current));
275
+ const next = target.route === 'sharedInbox'
276
+ ? { ...requests, sharedInbox: instant }
277
+ : {
278
+ ...requests,
279
+ users: {
280
+ ...requests.users,
281
+ [username ?? '']: {
282
+ ...requests.users[username ?? ''],
283
+ [target.route]: instant,
284
+ },
285
+ },
286
+ };
287
+ return `${JSON.stringify(next, null, 2)}\n`;
288
+ });
289
+ }
290
+ /** The bytes of the file as whatever JSON they hold, or nothing usable. */
291
+ function parseOrEmpty(source) {
292
+ if (source === undefined)
293
+ return undefined;
294
+ try {
295
+ return JSON.parse(source);
296
+ }
297
+ catch {
298
+ return undefined;
299
+ }
300
+ }
301
+ /**
302
+ * Whatever the file held as the instants this version understands.
303
+ *
304
+ * Read key by key and dropped rather than refused, on the rule the users file
305
+ * and `site.json` are read by: this is a record of what happened, and a
306
+ * damaged one should cost the screen a column, not the site a boot.
307
+ */
308
+ function requestsFrom(value) {
309
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
310
+ return { users: {} };
311
+ const record = value;
312
+ const users = {};
313
+ const listed = record['users'];
314
+ if (typeof listed === 'object' && listed !== null && !Array.isArray(listed)) {
315
+ for (const [username, routes] of Object.entries(listed)) {
316
+ if (typeof routes !== 'object' || routes === null || Array.isArray(routes))
317
+ continue;
318
+ const kept = {};
319
+ for (const [name, instant] of Object.entries(routes)) {
320
+ if (typeof instant === 'string' && isWordPressRoute(name))
321
+ kept[name] = instant;
322
+ }
323
+ if (Object.keys(kept).length > 0)
324
+ users[username] = kept;
325
+ }
326
+ }
327
+ const shared = record['sharedInbox'];
328
+ return {
329
+ ...(typeof shared === 'string' ? { sharedInbox: shared } : {}),
330
+ users,
331
+ };
332
+ }
333
+ /** Whether a stored key names a path this version serves. */
334
+ function isWordPressRoute(name) {
335
+ return (name === 'actor' ||
336
+ name === 'inbox' ||
337
+ name === 'outbox' ||
338
+ name === 'followers' ||
339
+ name === 'following');
340
+ }
341
+ //# sourceMappingURL=wordpress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wordpress.js","sourceRoot":"","sources":["../../src/federation/wordpress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Writing a file the way decision-9 needs it written.
3
+ *
4
+ * Files are the source of truth for everything durable, and SQLite is a cache
5
+ * that may be deleted — which means the transactions that used to cover a
6
+ * settings save or a new follower cover nothing any more. Two properties take
7
+ * their place, and both live here so no caller has to remember them:
8
+ *
9
+ * - **Atomic.** The bytes go to a temporary sibling and are renamed over the
10
+ * target, and `rename(2)` within one directory is atomic. A reader — an
11
+ * Eleventy build, the site data source, another process entirely — sees the
12
+ * whole old file or the whole new one, never half of either, and a crash
13
+ * half way through leaves the old file intact.
14
+ * - **Serialised.** Every write to one path queues behind the last, in this
15
+ * process, so two requests that both rewrite `site.json` cannot interleave.
16
+ * {@link updateFileAtomically} puts the read inside that queue as well,
17
+ * which is what stops the second writer reading the file before the first
18
+ * has written it and then overwriting what it wrote.
19
+ *
20
+ * The queue is per path, so writes to different files still run at once, and
21
+ * a path is forgotten again as soon as its queue drains.
22
+ */
23
+ /** What every write here takes besides the bytes. */
24
+ export interface WriteFileAtomicallyOptions {
25
+ /**
26
+ * The permissions the file ends up with, e.g. `0o600` for a private key.
27
+ *
28
+ * It is applied to the temporary file before the rename, so the bytes are
29
+ * never readable by anybody the finished file would not be readable by.
30
+ * Left off, the file is created with the process's usual mode — and, because
31
+ * the rename replaces the target rather than writing through it, that is
32
+ * true of a rewrite too: the mode is the one named here, not the one the old
33
+ * file happened to have.
34
+ */
35
+ mode?: number | undefined;
36
+ }
37
+ /** What a write puts in a file: text, or bytes as they are. */
38
+ export type FileContents = string | Uint8Array;
39
+ /**
40
+ * Produce the new contents from the current ones.
41
+ *
42
+ * `current` is `undefined` when the file does not exist yet, which is a real
43
+ * state rather than an error: the first follower, the first settings save on a
44
+ * site that has no `site.json`.
45
+ */
46
+ export type ProduceFileContents = (current: string | undefined) => FileContents | Promise<FileContents>;
47
+ /**
48
+ * Write a file atomically, behind whatever is already being written to it.
49
+ *
50
+ * The directory it lives in is created if it is missing, so a caller writing
51
+ * the first file under `data/keys` or `content/_data` does not have to.
52
+ */
53
+ export declare function writeFileAtomically(file: string, contents: FileContents, options?: WriteFileAtomicallyOptions): Promise<void>;
54
+ /**
55
+ * Read a file, decide what it should say next, and write it — all three as one
56
+ * step nothing else writing that file can get between.
57
+ *
58
+ * The read-modify-write is the shape almost every file in this CMS is changed
59
+ * by: `site.json` keeps the keys the settings form does not manage,
60
+ * `followers.json` gains or loses one entry, a month's inbox log gains a line.
61
+ * A queue around the write alone would not be enough for any of them, because
62
+ * two writers could both read the old file first.
63
+ */
64
+ export declare function updateFileAtomically(file: string, produce: ProduceFileContents, options?: WriteFileAtomicallyOptions): Promise<void>;
65
+ /**
66
+ * Run a task with one path's queue to itself, and hand back what it returned.
67
+ *
68
+ * The queue {@link updateFileAtomically} keeps around a read and a write, for
69
+ * a caller whose step is more than those two. The one that needs it is a file
70
+ * that SQLite indexes: a follower arriving has to reach `followers.json` and
71
+ * the `followers` table, and if the second of those were a step after the lock
72
+ * rather than inside it, a follow and an unfollow racing could leave the index
73
+ * saying something the file does not — and the file is the truth, so the index
74
+ * would be wrong until the next boot rebuilt it.
75
+ *
76
+ * The task is not handed a writer, because the writer it wants is the one that
77
+ * does not queue: {@link readFileIfPresentSync} and
78
+ * {@link writeFileAtomicallySync} inside the lock are the whole pattern.
79
+ * Calling {@link writeFileAtomically} or {@link updateFileAtomically} on the
80
+ * same path from inside a task would wait for a queue the task is itself at
81
+ * the head of, and never return.
82
+ */
83
+ export declare function withFileLock<T>(file: string, task: () => T | Promise<T>): Promise<T>;
84
+ /**
85
+ * The same write for a caller that cannot await: boot.
86
+ *
87
+ * `createCms` is synchronous — a site calls it and reads `cms.app` — so the
88
+ * once-only migrations that turn an old database's rows into files have no
89
+ * `await` to spend. They run before the server is listening and before
90
+ * anything else has written a file, so stepping outside the queue costs
91
+ * nothing; the rename still makes the write atomic against a reader.
92
+ */
93
+ export declare function writeFileAtomicallySync(file: string, contents: FileContents, options?: WriteFileAtomicallyOptions): void;
94
+ /**
95
+ * Read a file's whole contents as text, or `undefined` when it is not there.
96
+ *
97
+ * The counterpart of the writes above, for the same reason they exist: a file
98
+ * that is the source of truth is read on the way to being written, and a
99
+ * missing one means "nothing stored yet" everywhere in this CMS rather than an
100
+ * error to handle at each call site.
101
+ */
102
+ export declare function readFileIfPresentSync(file: string): string | undefined;
103
+ //# sourceMappingURL=atomic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../../src/files/atomic.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,qDAAqD;AACrD,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,MAAM,GAAG,SAAS,KACxB,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAa1C;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,YAAY,EACtB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,YAAY,EACtB,OAAO,GAAE,0BAA+B,GACvC,IAAI,CAeN;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMtE"}