@singi-labs/sifa-sdk 0.19.44 → 0.19.46

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 (582) hide show
  1. package/dist/atproto/index.js +9 -3
  2. package/dist/atproto/index.js.map +1 -1
  3. package/dist/badge/index.cjs.map +1 -1
  4. package/dist/badge/index.js +5 -141
  5. package/dist/badge/index.js.map +1 -1
  6. package/dist/cards/adult-content.js +14 -0
  7. package/dist/cards/adult-content.js.map +1 -0
  8. package/dist/cards/app-url-patterns.js +369 -0
  9. package/dist/cards/app-url-patterns.js.map +1 -0
  10. package/dist/cards/index.js +24 -0
  11. package/dist/cards/index.js.map +1 -0
  12. package/dist/cards/resolve-card-url.js +198 -0
  13. package/dist/cards/resolve-card-url.js.map +1 -0
  14. package/dist/cards/visibility.js +45 -0
  15. package/dist/cards/visibility.js.map +1 -0
  16. package/dist/flags/index.js +4 -3
  17. package/dist/flags/index.js.map +1 -1
  18. package/dist/format/collapse-contributors.js +19 -0
  19. package/dist/format/collapse-contributors.js.map +1 -0
  20. package/dist/format/company-name.js +61 -0
  21. package/dist/format/company-name.js.map +1 -0
  22. package/dist/format/compare-text.js +9 -0
  23. package/dist/format/compare-text.js.map +1 -0
  24. package/dist/format/doi.js +8 -0
  25. package/dist/format/doi.js.map +1 -0
  26. package/dist/format/embed.js +131 -0
  27. package/dist/format/embed.js.map +1 -0
  28. package/dist/format/endorsement-context.js +35 -0
  29. package/dist/format/endorsement-context.js.map +1 -0
  30. package/dist/format/format-display-url.js +42 -0
  31. package/dist/format/format-display-url.js.map +1 -0
  32. package/dist/format/format-duration.js +13 -0
  33. package/dist/format/format-duration.js.map +1 -0
  34. package/dist/format/format-time.js +30 -0
  35. package/dist/format/format-time.js.map +1 -0
  36. package/dist/format/handle-utils.js +19 -0
  37. package/dist/format/handle-utils.js.map +1 -0
  38. package/dist/format/index.js +106 -0
  39. package/dist/format/index.js.map +1 -0
  40. package/dist/format/location-segments.js +29 -0
  41. package/dist/format/location-segments.js.map +1 -0
  42. package/dist/format/location-utils.js +42 -0
  43. package/dist/format/location-utils.js.map +1 -0
  44. package/dist/format/meta-description.js +35 -0
  45. package/dist/format/meta-description.js.map +1 -0
  46. package/dist/format/normalize-company-key.js +12 -0
  47. package/dist/format/normalize-company-key.js.map +1 -0
  48. package/dist/format/normalize-legal-form.js +71 -0
  49. package/dist/format/normalize-legal-form.js.map +1 -0
  50. package/dist/format/pds-utils.js +73 -0
  51. package/dist/format/pds-utils.js.map +1 -0
  52. package/dist/format/sort-by-date.js +39 -0
  53. package/dist/format/sort-by-date.js.map +1 -0
  54. package/dist/format/summarize-deliveries.js +37 -0
  55. package/dist/format/summarize-deliveries.js.map +1 -0
  56. package/dist/format/talk-slug.js +21 -0
  57. package/dist/format/talk-slug.js.map +1 -0
  58. package/dist/format/text-sanitize.js +29 -0
  59. package/dist/format/text-sanitize.js.map +1 -0
  60. package/dist/format/text-truncate.js +14 -0
  61. package/dist/format/text-truncate.js.map +1 -0
  62. package/dist/format/time-utils.js +19 -0
  63. package/dist/format/time-utils.js.map +1 -0
  64. package/dist/format/timeline.js +33 -0
  65. package/dist/format/timeline.js.map +1 -0
  66. package/dist/format/wcag-contrast.js +34 -0
  67. package/dist/format/wcag-contrast.js.map +1 -0
  68. package/dist/import/index.js +22 -0
  69. package/dist/import/index.js.map +1 -0
  70. package/dist/import/presentation-csv.js +116 -0
  71. package/dist/import/presentation-csv.js.map +1 -0
  72. package/dist/index.cjs +7 -1
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.cts +8 -1
  75. package/dist/index.d.ts +8 -1
  76. package/dist/index.js +677 -7086
  77. package/dist/index.js.map +1 -1
  78. package/dist/jev/index.js +18 -5
  79. package/dist/jev/index.js.map +1 -1
  80. package/dist/jsonld/index.cjs.map +1 -1
  81. package/dist/jsonld/index.js +30 -987
  82. package/dist/jsonld/index.js.map +1 -1
  83. package/dist/jsonld/profile.js +222 -0
  84. package/dist/jsonld/profile.js.map +1 -0
  85. package/dist/jsonld/terms.js +496 -0
  86. package/dist/jsonld/terms.js.map +1 -0
  87. package/dist/jsonld/url.js +17 -0
  88. package/dist/jsonld/url.js.map +1 -0
  89. package/dist/jsonld/works-graph.js +41 -0
  90. package/dist/jsonld/works-graph.js.map +1 -0
  91. package/dist/jsonld/works.js +178 -0
  92. package/dist/jsonld/works.js.map +1 -0
  93. package/dist/logic/actor-identity.js +8 -0
  94. package/dist/logic/actor-identity.js.map +1 -0
  95. package/dist/logic/agent-ref.js +24 -0
  96. package/dist/logic/agent-ref.js.map +1 -0
  97. package/dist/logic/company-page-indexable.js +23 -0
  98. package/dist/logic/company-page-indexable.js.map +1 -0
  99. package/dist/logic/date-lookahead.js +33 -0
  100. package/dist/logic/date-lookahead.js.map +1 -0
  101. package/dist/logic/domain-detect.js +43 -0
  102. package/dist/logic/domain-detect.js.map +1 -0
  103. package/dist/logic/entity-disambiguation.js +24 -0
  104. package/dist/logic/entity-disambiguation.js.map +1 -0
  105. package/dist/logic/entity-ref-anchor.js +33 -0
  106. package/dist/logic/entity-ref-anchor.js.map +1 -0
  107. package/dist/logic/index.js +91 -0
  108. package/dist/logic/index.js.map +1 -0
  109. package/dist/logic/org-floor.js +66 -0
  110. package/dist/logic/org-floor.js.map +1 -0
  111. package/dist/logic/primary-item.js +9 -0
  112. package/dist/logic/primary-item.js.map +1 -0
  113. package/dist/logic/primary-position.js +16 -0
  114. package/dist/logic/primary-position.js.map +1 -0
  115. package/dist/logic/profile-completeness.js +21 -0
  116. package/dist/logic/profile-completeness.js.map +1 -0
  117. package/dist/logic/profile-dimensions.js +50 -0
  118. package/dist/logic/profile-dimensions.js.map +1 -0
  119. package/dist/logic/profile-summary.js +23 -0
  120. package/dist/logic/profile-summary.js.map +1 -0
  121. package/dist/logic/pseudo-employer.js +34 -0
  122. package/dist/logic/pseudo-employer.js.map +1 -0
  123. package/dist/logic/recent-activity.js +10 -0
  124. package/dist/logic/recent-activity.js.map +1 -0
  125. package/dist/logic/self-app.js +10 -0
  126. package/dist/logic/self-app.js.map +1 -0
  127. package/dist/{org-BCrvebcf.d.ts → org-CxTEjjrm.d.ts} +1 -1
  128. package/dist/{org-settings-C73Sin5E.d.ts → org-settings-BShvhyFp.d.ts} +1 -1
  129. package/dist/profile/headline-role-dedupe.js +12 -0
  130. package/dist/profile/headline-role-dedupe.js.map +1 -0
  131. package/dist/profile/highlights.js +200 -0
  132. package/dist/profile/highlights.js.map +1 -0
  133. package/dist/profile/index.js +66 -0
  134. package/dist/profile/index.js.map +1 -0
  135. package/dist/profile/involvement-grouping.js +31 -0
  136. package/dist/profile/involvement-grouping.js.map +1 -0
  137. package/dist/profile/language-sort.js +24 -0
  138. package/dist/profile/language-sort.js.map +1 -0
  139. package/dist/profile/publication-versions.js +98 -0
  140. package/dist/profile/publication-versions.js.map +1 -0
  141. package/dist/profile/range-sort.js +14 -0
  142. package/dist/profile/range-sort.js.map +1 -0
  143. package/dist/profile/section-model.js +122 -0
  144. package/dist/profile/section-model.js.map +1 -0
  145. package/dist/profile/section-sorts.js +33 -0
  146. package/dist/profile/section-sorts.js.map +1 -0
  147. package/dist/publishers/index.js +16 -0
  148. package/dist/publishers/index.js.map +1 -0
  149. package/dist/publishers/registry.js +87 -0
  150. package/dist/publishers/registry.js.map +1 -0
  151. package/dist/publishing/index.js +46 -184
  152. package/dist/publishing/index.js.map +1 -1
  153. package/dist/publishing/linkblog.js +42 -0
  154. package/dist/publishing/linkblog.js.map +1 -0
  155. package/dist/publishing/registry.js +32 -0
  156. package/dist/publishing/registry.js.map +1 -0
  157. package/dist/publishing/schemas.js +74 -0
  158. package/dist/publishing/schemas.js.map +1 -0
  159. package/dist/publishing/types.js +32 -0
  160. package/dist/publishing/types.js.map +1 -0
  161. package/dist/query/client.js +119 -0
  162. package/dist/query/client.js.map +1 -0
  163. package/dist/query/config.js +23 -0
  164. package/dist/query/config.js.map +1 -0
  165. package/dist/query/fetchers/accounts.js +29 -0
  166. package/dist/query/fetchers/accounts.js.map +1 -0
  167. package/dist/query/fetchers/activity-items-hide.js +31 -0
  168. package/dist/query/fetchers/activity-items-hide.js.map +1 -0
  169. package/dist/query/fetchers/activity.js +61 -0
  170. package/dist/query/fetchers/activity.js.map +1 -0
  171. package/dist/query/fetchers/actor.js +30 -0
  172. package/dist/query/fetchers/actor.js.map +1 -0
  173. package/dist/query/fetchers/admin-feature-allowlists.js +41 -0
  174. package/dist/query/fetchers/admin-feature-allowlists.js.map +1 -0
  175. package/dist/query/fetchers/admin-review-queues.js +30 -0
  176. package/dist/query/fetchers/admin-review-queues.js.map +1 -0
  177. package/dist/query/fetchers/apps.js +31 -0
  178. package/dist/query/fetchers/apps.js.map +1 -0
  179. package/dist/query/fetchers/bsky-preferences.js +55 -0
  180. package/dist/query/fetchers/bsky-preferences.js.map +1 -0
  181. package/dist/query/fetchers/confirmations.js +55 -0
  182. package/dist/query/fetchers/confirmations.js.map +1 -0
  183. package/dist/query/fetchers/destructive.js +34 -0
  184. package/dist/query/fetchers/destructive.js.map +1 -0
  185. package/dist/query/fetchers/discovery.js +67 -0
  186. package/dist/query/fetchers/discovery.js.map +1 -0
  187. package/dist/query/fetchers/education.js +23 -0
  188. package/dist/query/fetchers/education.js.map +1 -0
  189. package/dist/query/fetchers/endorsement-inbox.js +36 -0
  190. package/dist/query/fetchers/endorsement-inbox.js.map +1 -0
  191. package/dist/query/fetchers/endorsement.js +23 -0
  192. package/dist/query/fetchers/endorsement.js.map +1 -0
  193. package/dist/query/fetchers/endorsements.js +20 -0
  194. package/dist/query/fetchers/endorsements.js.map +1 -0
  195. package/dist/query/fetchers/entities.js +65 -0
  196. package/dist/query/fetchers/entities.js.map +1 -0
  197. package/dist/query/fetchers/external-accounts.js +74 -0
  198. package/dist/query/fetchers/external-accounts.js.map +1 -0
  199. package/dist/query/fetchers/follow-extras.js +38 -0
  200. package/dist/query/fetchers/follow-extras.js.map +1 -0
  201. package/dist/query/fetchers/follow.js +105 -0
  202. package/dist/query/fetchers/follow.js.map +1 -0
  203. package/dist/query/fetchers/get-profile-view.js +21 -0
  204. package/dist/query/fetchers/get-profile-view.js.map +1 -0
  205. package/dist/query/fetchers/github-prs.js +45 -0
  206. package/dist/query/fetchers/github-prs.js.map +1 -0
  207. package/dist/query/fetchers/inbox.js +29 -0
  208. package/dist/query/fetchers/inbox.js.map +1 -0
  209. package/dist/query/fetchers/index.cjs.map +1 -1
  210. package/dist/query/fetchers/index.d.ts +2 -2
  211. package/dist/query/fetchers/index.js +360 -2045
  212. package/dist/query/fetchers/index.js.map +1 -1
  213. package/dist/query/fetchers/investments.js +18 -0
  214. package/dist/query/fetchers/investments.js.map +1 -0
  215. package/dist/query/fetchers/keytrace-claims.js +23 -0
  216. package/dist/query/fetchers/keytrace-claims.js.map +1 -0
  217. package/dist/query/fetchers/marque-domains.js +23 -0
  218. package/dist/query/fetchers/marque-domains.js.map +1 -0
  219. package/dist/query/fetchers/network-map.js +36 -0
  220. package/dist/query/fetchers/network-map.js.map +1 -0
  221. package/dist/query/fetchers/org.js +48 -0
  222. package/dist/query/fetchers/org.js.map +1 -0
  223. package/dist/query/fetchers/positions.js +77 -0
  224. package/dist/query/fetchers/positions.js.map +1 -0
  225. package/dist/query/fetchers/profile-completeness.js +45 -0
  226. package/dist/query/fetchers/profile-completeness.js.map +1 -0
  227. package/dist/query/fetchers/profile-items-hide.js +36 -0
  228. package/dist/query/fetchers/profile-items-hide.js.map +1 -0
  229. package/dist/query/fetchers/profile-locations.js +23 -0
  230. package/dist/query/fetchers/profile-locations.js.map +1 -0
  231. package/dist/query/fetchers/profile-mutations.js +82 -0
  232. package/dist/query/fetchers/profile-mutations.js.map +1 -0
  233. package/dist/query/fetchers/profile-summary.js +14 -0
  234. package/dist/query/fetchers/profile-summary.js.map +1 -0
  235. package/dist/query/fetchers/profile.js +31 -0
  236. package/dist/query/fetchers/profile.js.map +1 -0
  237. package/dist/query/fetchers/publications.js +66 -0
  238. package/dist/query/fetchers/publications.js.map +1 -0
  239. package/dist/query/fetchers/quoted-posts.js +40 -0
  240. package/dist/query/fetchers/quoted-posts.js.map +1 -0
  241. package/dist/query/fetchers/reactions.js +85 -0
  242. package/dist/query/fetchers/reactions.js.map +1 -0
  243. package/dist/query/fetchers/received-endorsements.js +20 -0
  244. package/dist/query/fetchers/received-endorsements.js.map +1 -0
  245. package/dist/query/fetchers/reciprocity.js +23 -0
  246. package/dist/query/fetchers/reciprocity.js.map +1 -0
  247. package/dist/query/fetchers/records.js +27 -0
  248. package/dist/query/fetchers/records.js.map +1 -0
  249. package/dist/query/fetchers/repo-inventory.js +23 -0
  250. package/dist/query/fetchers/repo-inventory.js.map +1 -0
  251. package/dist/query/fetchers/roadmap.js +73 -0
  252. package/dist/query/fetchers/roadmap.js.map +1 -0
  253. package/dist/query/fetchers/rpg.js +36 -0
  254. package/dist/query/fetchers/rpg.js.map +1 -0
  255. package/dist/query/fetchers/search.js +78 -0
  256. package/dist/query/fetchers/search.js.map +1 -0
  257. package/dist/query/fetchers/section-primary.js +23 -0
  258. package/dist/query/fetchers/section-primary.js.map +1 -0
  259. package/dist/query/fetchers/skills.js +30 -0
  260. package/dist/query/fetchers/skills.js.map +1 -0
  261. package/dist/query/fetchers/speakers.js +42 -0
  262. package/dist/query/fetchers/speakers.js.map +1 -0
  263. package/dist/query/fetchers/stats.js +16 -0
  264. package/dist/query/fetchers/stats.js.map +1 -0
  265. package/dist/query/fetchers/stream.js +30 -0
  266. package/dist/query/fetchers/stream.js.map +1 -0
  267. package/dist/query/fetchers/unlinked-positions.js +30 -0
  268. package/dist/query/fetchers/unlinked-positions.js.map +1 -0
  269. package/dist/query/hooks/index.d.ts +3 -3
  270. package/dist/query/hooks/index.js +283 -3555
  271. package/dist/query/hooks/index.js.map +1 -1
  272. package/dist/query/hooks/use-accounts.js +19 -0
  273. package/dist/query/hooks/use-accounts.js.map +1 -0
  274. package/dist/query/hooks/use-activity-items-hide.js +52 -0
  275. package/dist/query/hooks/use-activity-items-hide.js.map +1 -0
  276. package/dist/query/hooks/use-activity.js +44 -0
  277. package/dist/query/hooks/use-activity.js.map +1 -0
  278. package/dist/query/hooks/use-admin-review-queues.js +21 -0
  279. package/dist/query/hooks/use-admin-review-queues.js.map +1 -0
  280. package/dist/query/hooks/use-apps.js +31 -0
  281. package/dist/query/hooks/use-apps.js.map +1 -0
  282. package/dist/query/hooks/use-bsky-content-prefs.js +45 -0
  283. package/dist/query/hooks/use-bsky-content-prefs.js.map +1 -0
  284. package/dist/query/hooks/use-confirmations.js +121 -0
  285. package/dist/query/hooks/use-confirmations.js.map +1 -0
  286. package/dist/query/hooks/use-create-position.js +28 -0
  287. package/dist/query/hooks/use-create-position.js.map +1 -0
  288. package/dist/query/hooks/use-destructive.js +63 -0
  289. package/dist/query/hooks/use-destructive.js.map +1 -0
  290. package/dist/query/hooks/use-discovery.js +53 -0
  291. package/dist/query/hooks/use-discovery.js.map +1 -0
  292. package/dist/query/hooks/use-education-mutations.js +67 -0
  293. package/dist/query/hooks/use-education-mutations.js.map +1 -0
  294. package/dist/query/hooks/use-endorsement-inbox.js +80 -0
  295. package/dist/query/hooks/use-endorsement-inbox.js.map +1 -0
  296. package/dist/query/hooks/use-endorsement-mutations.js +33 -0
  297. package/dist/query/hooks/use-endorsement-mutations.js.map +1 -0
  298. package/dist/query/hooks/use-endorsement.js +20 -0
  299. package/dist/query/hooks/use-endorsement.js.map +1 -0
  300. package/dist/query/hooks/use-entity-search.js +73 -0
  301. package/dist/query/hooks/use-entity-search.js.map +1 -0
  302. package/dist/query/hooks/use-external-accounts.js +143 -0
  303. package/dist/query/hooks/use-external-accounts.js.map +1 -0
  304. package/dist/query/hooks/use-feature-allowlist.js +104 -0
  305. package/dist/query/hooks/use-feature-allowlist.js.map +1 -0
  306. package/dist/query/hooks/use-follow-extras.js +38 -0
  307. package/dist/query/hooks/use-follow-extras.js.map +1 -0
  308. package/dist/query/hooks/use-follow.js +113 -0
  309. package/dist/query/hooks/use-follow.js.map +1 -0
  310. package/dist/query/hooks/use-get-profile-view.js +20 -0
  311. package/dist/query/hooks/use-get-profile-view.js.map +1 -0
  312. package/dist/query/hooks/use-github-prs.js +21 -0
  313. package/dist/query/hooks/use-github-prs.js.map +1 -0
  314. package/dist/query/hooks/use-inbox.js +19 -0
  315. package/dist/query/hooks/use-inbox.js.map +1 -0
  316. package/dist/query/hooks/use-investment-mutations.js +65 -0
  317. package/dist/query/hooks/use-investment-mutations.js.map +1 -0
  318. package/dist/query/hooks/use-keytrace-claims.js +50 -0
  319. package/dist/query/hooks/use-keytrace-claims.js.map +1 -0
  320. package/dist/query/hooks/use-location-mutations.js +71 -0
  321. package/dist/query/hooks/use-location-mutations.js.map +1 -0
  322. package/dist/query/hooks/use-marque-domains.js +50 -0
  323. package/dist/query/hooks/use-marque-domains.js.map +1 -0
  324. package/dist/query/hooks/use-org-claim.js +29 -0
  325. package/dist/query/hooks/use-org-claim.js.map +1 -0
  326. package/dist/query/hooks/use-org-domains.js +28 -0
  327. package/dist/query/hooks/use-org-domains.js.map +1 -0
  328. package/dist/query/hooks/use-org-notification-emails.js +28 -0
  329. package/dist/query/hooks/use-org-notification-emails.js.map +1 -0
  330. package/dist/query/hooks/use-org-profile-settings.js +29 -0
  331. package/dist/query/hooks/use-org-profile-settings.js.map +1 -0
  332. package/dist/query/hooks/use-org-profile.js +21 -0
  333. package/dist/query/hooks/use-org-profile.js.map +1 -0
  334. package/dist/query/hooks/use-position-mutations.js +128 -0
  335. package/dist/query/hooks/use-position-mutations.js.map +1 -0
  336. package/dist/query/hooks/use-profile-completeness.js +21 -0
  337. package/dist/query/hooks/use-profile-completeness.js.map +1 -0
  338. package/dist/query/hooks/use-profile-items-hide.js +53 -0
  339. package/dist/query/hooks/use-profile-items-hide.js.map +1 -0
  340. package/dist/query/hooks/use-profile-mutations.js +143 -0
  341. package/dist/query/hooks/use-profile-mutations.js.map +1 -0
  342. package/dist/query/hooks/use-profile.js +30 -0
  343. package/dist/query/hooks/use-profile.js.map +1 -0
  344. package/dist/query/hooks/use-publication-mutations.js +90 -0
  345. package/dist/query/hooks/use-publication-mutations.js.map +1 -0
  346. package/dist/query/hooks/use-reaction-mutations.js +48 -0
  347. package/dist/query/hooks/use-reaction-mutations.js.map +1 -0
  348. package/dist/query/hooks/use-reactions.js +32 -0
  349. package/dist/query/hooks/use-reactions.js.map +1 -0
  350. package/dist/query/hooks/use-received-endorsements.js +23 -0
  351. package/dist/query/hooks/use-received-endorsements.js.map +1 -0
  352. package/dist/query/hooks/use-reciprocity.js +19 -0
  353. package/dist/query/hooks/use-reciprocity.js.map +1 -0
  354. package/dist/query/hooks/use-record-mutations.js +67 -0
  355. package/dist/query/hooks/use-record-mutations.js.map +1 -0
  356. package/dist/query/hooks/use-repo-inventory.js +43 -0
  357. package/dist/query/hooks/use-repo-inventory.js.map +1 -0
  358. package/dist/query/hooks/use-roadmap-mutations.js +51 -0
  359. package/dist/query/hooks/use-roadmap-mutations.js.map +1 -0
  360. package/dist/query/hooks/use-roadmap.js +31 -0
  361. package/dist/query/hooks/use-roadmap.js.map +1 -0
  362. package/dist/query/hooks/use-search.js +63 -0
  363. package/dist/query/hooks/use-search.js.map +1 -0
  364. package/dist/query/hooks/use-section-primary-mutations.js +53 -0
  365. package/dist/query/hooks/use-section-primary-mutations.js.map +1 -0
  366. package/dist/query/hooks/use-skill-mutations.js +91 -0
  367. package/dist/query/hooks/use-skill-mutations.js.map +1 -0
  368. package/dist/query/hooks/use-stats.js +19 -0
  369. package/dist/query/hooks/use-stats.js.map +1 -0
  370. package/dist/query/hooks/use-stream.js +20 -0
  371. package/dist/query/hooks/use-stream.js.map +1 -0
  372. package/dist/query/hooks/use-unlinked-positions.js +45 -0
  373. package/dist/query/hooks/use-unlinked-positions.js.map +1 -0
  374. package/dist/query/index.cjs.map +1 -1
  375. package/dist/query/index.d.ts +4 -4
  376. package/dist/query/index.js +685 -3744
  377. package/dist/query/index.js.map +1 -1
  378. package/dist/query/keys.js +138 -0
  379. package/dist/query/keys.js.map +1 -0
  380. package/dist/repo/groups.js +81 -0
  381. package/dist/repo/groups.js.map +1 -0
  382. package/dist/repo/index.js +9 -0
  383. package/dist/repo/index.js.map +1 -0
  384. package/dist/repo/labels.js +121 -0
  385. package/dist/repo/labels.js.map +1 -0
  386. package/dist/repo/types.js +1 -0
  387. package/dist/repo/types.js.map +1 -0
  388. package/dist/resume/index.js +5 -179
  389. package/dist/resume/index.js.map +1 -1
  390. package/dist/resume/json-resume.js +153 -0
  391. package/dist/resume/json-resume.js.map +1 -0
  392. package/dist/roadmap/index.js +101 -0
  393. package/dist/roadmap/index.js.map +1 -0
  394. package/dist/rpg/catalog.js +101 -0
  395. package/dist/rpg/catalog.js.map +1 -0
  396. package/dist/rpg/index.js +13 -116
  397. package/dist/rpg/index.js.map +1 -1
  398. package/dist/rpg/unlocks.js +21 -0
  399. package/dist/rpg/unlocks.js.map +1 -0
  400. package/dist/schemas/blob-ref.js +12 -0
  401. package/dist/schemas/blob-ref.js.map +1 -0
  402. package/dist/schemas/confirmation.js +23 -0
  403. package/dist/schemas/confirmation.js.map +1 -0
  404. package/dist/schemas/endorsement-confirmation.js +18 -0
  405. package/dist/schemas/endorsement-confirmation.js.map +1 -0
  406. package/dist/schemas/endorsement.js +19 -0
  407. package/dist/schemas/endorsement.js.map +1 -0
  408. package/dist/schemas/entity.js +74 -0
  409. package/dist/schemas/entity.js.map +1 -0
  410. package/dist/schemas/feed.js +73 -0
  411. package/dist/schemas/feed.js.map +1 -0
  412. package/dist/schemas/follow-profile.js +32 -0
  413. package/dist/schemas/follow-profile.js.map +1 -0
  414. package/dist/schemas/graph-follow.js +19 -0
  415. package/dist/schemas/graph-follow.js.map +1 -0
  416. package/dist/schemas/index.js +164 -629
  417. package/dist/schemas/index.js.map +1 -1
  418. package/dist/schemas/org-employment-attestation.js +28 -0
  419. package/dist/schemas/org-employment-attestation.js.map +1 -0
  420. package/dist/schemas/org-profile.js +64 -0
  421. package/dist/schemas/org-profile.js.map +1 -0
  422. package/dist/schemas/profile-certification.js +28 -0
  423. package/dist/schemas/profile-certification.js.map +1 -0
  424. package/dist/schemas/profile-course.js +32 -0
  425. package/dist/schemas/profile-course.js.map +1 -0
  426. package/dist/schemas/profile-education.js +33 -0
  427. package/dist/schemas/profile-education.js.map +1 -0
  428. package/dist/schemas/profile-external-account.js +15 -0
  429. package/dist/schemas/profile-external-account.js.map +1 -0
  430. package/dist/schemas/profile-honor.js +18 -0
  431. package/dist/schemas/profile-honor.js.map +1 -0
  432. package/dist/schemas/profile-investment.js +48 -0
  433. package/dist/schemas/profile-investment.js.map +1 -0
  434. package/dist/schemas/profile-involvement.js +47 -0
  435. package/dist/schemas/profile-involvement.js.map +1 -0
  436. package/dist/schemas/profile-language.js +12 -0
  437. package/dist/schemas/profile-language.js.map +1 -0
  438. package/dist/schemas/profile-position.js +42 -0
  439. package/dist/schemas/profile-position.js.map +1 -0
  440. package/dist/schemas/profile-presentation-delivery.js +38 -0
  441. package/dist/schemas/profile-presentation-delivery.js.map +1 -0
  442. package/dist/schemas/profile-presentation.js +36 -0
  443. package/dist/schemas/profile-presentation.js.map +1 -0
  444. package/dist/schemas/profile-project.js +48 -0
  445. package/dist/schemas/profile-project.js.map +1 -0
  446. package/dist/schemas/profile-publication.js +30 -0
  447. package/dist/schemas/profile-publication.js.map +1 -0
  448. package/dist/schemas/profile-self.js +22 -0
  449. package/dist/schemas/profile-self.js.map +1 -0
  450. package/dist/schemas/profile-skill.js +13 -0
  451. package/dist/schemas/profile-skill.js.map +1 -0
  452. package/dist/schemas/profile-volunteering.js +20 -0
  453. package/dist/schemas/profile-volunteering.js.map +1 -0
  454. package/dist/schemas/shared.js +56 -0
  455. package/dist/schemas/shared.js.map +1 -0
  456. package/dist/schemas/write/certification.js +16 -0
  457. package/dist/schemas/write/certification.js.map +1 -0
  458. package/dist/schemas/write/course.js +36 -0
  459. package/dist/schemas/write/course.js.map +1 -0
  460. package/dist/schemas/write/education.js +18 -0
  461. package/dist/schemas/write/education.js.map +1 -0
  462. package/dist/schemas/write/external-account.js +13 -0
  463. package/dist/schemas/write/external-account.js.map +1 -0
  464. package/dist/schemas/write/honor.js +14 -0
  465. package/dist/schemas/write/honor.js.map +1 -0
  466. package/dist/schemas/write/index.d.ts +1 -1
  467. package/dist/schemas/write/index.js +89 -593
  468. package/dist/schemas/write/index.js.map +1 -1
  469. package/dist/schemas/write/investment.js +32 -0
  470. package/dist/schemas/write/investment.js.map +1 -0
  471. package/dist/schemas/write/involvement.js +34 -0
  472. package/dist/schemas/write/involvement.js.map +1 -0
  473. package/dist/schemas/write/language.js +10 -0
  474. package/dist/schemas/write/language.js.map +1 -0
  475. package/dist/schemas/write/org-claim.js +56 -0
  476. package/dist/schemas/write/org-claim.js.map +1 -0
  477. package/dist/schemas/write/org-employment-attestation.js +19 -0
  478. package/dist/schemas/write/org-employment-attestation.js.map +1 -0
  479. package/dist/schemas/write/org-profile.js +54 -0
  480. package/dist/schemas/write/org-profile.js.map +1 -0
  481. package/dist/schemas/write/org-settings.js +81 -0
  482. package/dist/schemas/write/org-settings.js.map +1 -0
  483. package/dist/schemas/write/position.js +24 -0
  484. package/dist/schemas/write/position.js.map +1 -0
  485. package/dist/schemas/write/presentation-delivery.js +34 -0
  486. package/dist/schemas/write/presentation-delivery.js.map +1 -0
  487. package/dist/schemas/write/presentation.js +23 -0
  488. package/dist/schemas/write/presentation.js.map +1 -0
  489. package/dist/schemas/write/profile-location.js +21 -0
  490. package/dist/schemas/write/profile-location.js.map +1 -0
  491. package/dist/schemas/write/profile-self.js +27 -0
  492. package/dist/schemas/write/profile-self.js.map +1 -0
  493. package/dist/schemas/write/project.js +27 -0
  494. package/dist/schemas/write/project.js.map +1 -0
  495. package/dist/schemas/write/publication.js +41 -0
  496. package/dist/schemas/write/publication.js.map +1 -0
  497. package/dist/schemas/write/shared.js +96 -0
  498. package/dist/schemas/write/shared.js.map +1 -0
  499. package/dist/schemas/write/skill.js +18 -0
  500. package/dist/schemas/write/skill.js.map +1 -0
  501. package/dist/schemas/write/volunteering.js +16 -0
  502. package/dist/schemas/write/volunteering.js.map +1 -0
  503. package/dist/stream/activity-item.js +1 -0
  504. package/dist/stream/activity-item.js.map +1 -0
  505. package/dist/stream/index.js +49 -0
  506. package/dist/stream/index.js.map +1 -0
  507. package/dist/stream/stream-card-vm-schema.js +265 -0
  508. package/dist/stream/stream-card-vm-schema.js.map +1 -0
  509. package/dist/stream/stream-card-vm.js +1 -0
  510. package/dist/stream/stream-card-vm.js.map +1 -0
  511. package/dist/stream/to-stream-card-vm.js +793 -0
  512. package/dist/stream/to-stream-card-vm.js.map +1 -0
  513. package/dist/stream/two-tier.js +30 -0
  514. package/dist/stream/two-tier.js.map +1 -0
  515. package/dist/stream/verbs.js +120 -0
  516. package/dist/stream/verbs.js.map +1 -0
  517. package/dist/taxonomy/activity-tiers.js +956 -0
  518. package/dist/taxonomy/activity-tiers.js.map +1 -0
  519. package/dist/taxonomy/app-categories.js +42 -0
  520. package/dist/taxonomy/app-categories.js.map +1 -0
  521. package/dist/taxonomy/app-category-map.js +166 -0
  522. package/dist/taxonomy/app-category-map.js.map +1 -0
  523. package/dist/taxonomy/artifact-link-kind.js +29 -0
  524. package/dist/taxonomy/artifact-link-kind.js.map +1 -0
  525. package/dist/taxonomy/calendar-event.js +28 -0
  526. package/dist/taxonomy/calendar-event.js.map +1 -0
  527. package/dist/taxonomy/continents.js +215 -0
  528. package/dist/taxonomy/continents.js.map +1 -0
  529. package/dist/taxonomy/countries.js +203 -0
  530. package/dist/taxonomy/countries.js.map +1 -0
  531. package/dist/taxonomy/course-role.js +37 -0
  532. package/dist/taxonomy/course-role.js.map +1 -0
  533. package/dist/taxonomy/employment-type.js +82 -0
  534. package/dist/taxonomy/employment-type.js.map +1 -0
  535. package/dist/taxonomy/eqf-level.js +59 -0
  536. package/dist/taxonomy/eqf-level.js.map +1 -0
  537. package/dist/taxonomy/index.js +263 -0
  538. package/dist/taxonomy/index.js.map +1 -0
  539. package/dist/taxonomy/industry-taxonomy.js +281 -0
  540. package/dist/taxonomy/industry-taxonomy.js.map +1 -0
  541. package/dist/taxonomy/investment.js +54 -0
  542. package/dist/taxonomy/investment.js.map +1 -0
  543. package/dist/taxonomy/involvement-kind.js +30 -0
  544. package/dist/taxonomy/involvement-kind.js.map +1 -0
  545. package/dist/taxonomy/open-to.js +128 -0
  546. package/dist/taxonomy/open-to.js.map +1 -0
  547. package/dist/taxonomy/platforms.js +56 -0
  548. package/dist/taxonomy/platforms.js.map +1 -0
  549. package/dist/taxonomy/presentation-link-type.js +22 -0
  550. package/dist/taxonomy/presentation-link-type.js.map +1 -0
  551. package/dist/taxonomy/presentation-role.js +21 -0
  552. package/dist/taxonomy/presentation-role.js.map +1 -0
  553. package/dist/taxonomy/publication-type.js +54 -0
  554. package/dist/taxonomy/publication-type.js.map +1 -0
  555. package/dist/taxonomy/relational.js +25 -0
  556. package/dist/taxonomy/relational.js.map +1 -0
  557. package/dist/taxonomy/search-filter-defs.js +66 -0
  558. package/dist/taxonomy/search-filter-defs.js.map +1 -0
  559. package/dist/taxonomy/skill-categories.js +28 -0
  560. package/dist/taxonomy/skill-categories.js.map +1 -0
  561. package/dist/taxonomy/skill-display-groups.js +12 -0
  562. package/dist/taxonomy/skill-display-groups.js.map +1 -0
  563. package/dist/taxonomy/skill-grouping.js +90 -0
  564. package/dist/taxonomy/skill-grouping.js.map +1 -0
  565. package/dist/taxonomy/verification-providers.js +69 -0
  566. package/dist/taxonomy/verification-providers.js.map +1 -0
  567. package/dist/taxonomy/workplace-type.js +38 -0
  568. package/dist/taxonomy/workplace-type.js.map +1 -0
  569. package/dist/tokens/colors.js +11 -0
  570. package/dist/tokens/colors.js.map +1 -0
  571. package/dist/tokens/icons.js +15 -0
  572. package/dist/tokens/icons.js.map +1 -0
  573. package/dist/tokens/index.js +10 -39
  574. package/dist/tokens/index.js.map +1 -1
  575. package/dist/tokens/typography.js +23 -0
  576. package/dist/tokens/typography.js.map +1 -0
  577. package/dist/types/index.js +10 -0
  578. package/dist/types/index.js.map +1 -0
  579. package/dist/types/profile-view.js +1 -0
  580. package/dist/types/profile-view.js.map +1 -0
  581. package/dist/{use-org-notification-emails-0hjt-dEw.d.ts → use-org-notification-emails-CWWi1D8t.d.ts} +2 -2
  582. package/package.json +4 -3
@@ -0,0 +1,42 @@
1
+ // src/format/location-utils.ts
2
+ function formatLocation(loc) {
3
+ if (!loc) return "";
4
+ const locality = loc.locality ?? loc.city;
5
+ const parts = [locality, loc.region, loc.country].filter(Boolean);
6
+ if (loc.postalCode && !locality) {
7
+ return `${loc.postalCode}, ${loc.country}`;
8
+ }
9
+ return parts.join(", ");
10
+ }
11
+ function parseLocationString(str) {
12
+ if (!str.trim()) return null;
13
+ const parts = str.split(",").map((p) => p.trim()).filter(Boolean);
14
+ if (parts.length >= 3) {
15
+ return {
16
+ city: parts[0],
17
+ region: parts[1],
18
+ country: parts[parts.length - 1]
19
+ };
20
+ }
21
+ if (parts.length === 2) {
22
+ return {
23
+ city: parts[0],
24
+ country: parts[1]
25
+ };
26
+ }
27
+ return { country: parts[0] };
28
+ }
29
+ function countryCodeToFlag(code) {
30
+ if (!code || code.length !== 2) return "";
31
+ const upper = code.toUpperCase();
32
+ const a = upper.codePointAt(0);
33
+ const b = upper.codePointAt(1);
34
+ if (a === void 0 || b === void 0) return "";
35
+ return String.fromCodePoint(a - 65 + 127462, b - 65 + 127462);
36
+ }
37
+ export {
38
+ countryCodeToFlag,
39
+ formatLocation,
40
+ parseLocationString
41
+ };
42
+ //# sourceMappingURL=location-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/location-utils.ts"],"sourcesContent":["import type { LocationValue } from '../types/index.js';\n\n/**\n * Format a LocationValue for display.\n *\n * Prefers `locality` (the community.lexicon.location.address field) and\n * falls back to the legacy `city` slot so values produced before the\n * sifa-api alias migration still render correctly during the additive\n * transition window.\n */\nexport function formatLocation(loc: LocationValue | null | undefined): string {\n if (!loc) return '';\n const locality = loc.locality ?? loc.city;\n const parts = [locality, loc.region, loc.country].filter(Boolean);\n if (loc.postalCode && !locality) {\n return `${loc.postalCode}, ${loc.country}`;\n }\n return parts.join(', ');\n}\n\n/** Parse a display string back into a LocationValue (best-effort for legacy data) */\nexport function parseLocationString(str: string): LocationValue | null {\n if (!str.trim()) return null;\n const parts = str\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean);\n if (parts.length >= 3) {\n return {\n city: parts[0],\n region: parts[1],\n country: parts[parts.length - 1]!,\n };\n }\n if (parts.length === 2) {\n return {\n city: parts[0],\n country: parts[1]!,\n };\n }\n return { country: parts[0]! };\n}\n\n/** Convert ISO 3166-1 alpha-2 country code to flag emoji */\nexport function countryCodeToFlag(code: string | undefined): string {\n if (!code || code.length !== 2) return '';\n const upper = code.toUpperCase();\n const a = upper.codePointAt(0);\n const b = upper.codePointAt(1);\n if (a === undefined || b === undefined) return '';\n return String.fromCodePoint(a - 0x41 + 0x1f1e6, b - 0x41 + 0x1f1e6);\n}\n"],"mappings":";AAUO,SAAS,eAAe,KAA+C;AAC5E,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,WAAW,IAAI,YAAY,IAAI;AACrC,QAAM,QAAQ,CAAC,UAAU,IAAI,QAAQ,IAAI,OAAO,EAAE,OAAO,OAAO;AAChE,MAAI,IAAI,cAAc,CAAC,UAAU;AAC/B,WAAO,GAAG,IAAI,UAAU,KAAK,IAAI,OAAO;AAAA,EAC1C;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAGO,SAAS,oBAAoB,KAAmC;AACrE,MAAI,CAAC,IAAI,KAAK,EAAG,QAAO;AACxB,QAAM,QAAQ,IACX,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AACjB,MAAI,MAAM,UAAU,GAAG;AACrB,WAAO;AAAA,MACL,MAAM,MAAM,CAAC;AAAA,MACb,QAAQ,MAAM,CAAC;AAAA,MACf,SAAS,MAAM,MAAM,SAAS,CAAC;AAAA,IACjC;AAAA,EACF;AACA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO;AAAA,MACL,MAAM,MAAM,CAAC;AAAA,MACb,SAAS,MAAM,CAAC;AAAA,IAClB;AAAA,EACF;AACA,SAAO,EAAE,SAAS,MAAM,CAAC,EAAG;AAC9B;AAGO,SAAS,kBAAkB,MAAkC;AAClE,MAAI,CAAC,QAAQ,KAAK,WAAW,EAAG,QAAO;AACvC,QAAM,QAAQ,KAAK,YAAY;AAC/B,QAAM,IAAI,MAAM,YAAY,CAAC;AAC7B,QAAM,IAAI,MAAM,YAAY,CAAC;AAC7B,MAAI,MAAM,UAAa,MAAM,OAAW,QAAO;AAC/C,SAAO,OAAO,cAAc,IAAI,KAAO,QAAS,IAAI,KAAO,MAAO;AACpE;","names":[]}
@@ -0,0 +1,35 @@
1
+ // src/format/meta-description.ts
2
+ import { pickPrimaryPosition } from "../logic/primary-position.js";
3
+ import { isRoleLineRedundant } from "../profile/headline-role-dedupe.js";
4
+ import { filterHidden } from "../profile/section-model.js";
5
+ import { formatLocation } from "./location-utils.js";
6
+ var MEANINGFUL_TEXT_RE = /[\p{L}\p{N}]/u;
7
+ function buildMetaDescription(profile) {
8
+ const parts = [];
9
+ if (typeof profile.headline === "string" && MEANINGFUL_TEXT_RE.test(profile.headline)) {
10
+ parts.push(profile.headline);
11
+ }
12
+ const positions = filterHidden(profile.positions ? [...profile.positions] : void 0);
13
+ const currentPosition = pickPrimaryPosition(positions);
14
+ if (currentPosition) {
15
+ const positionParts = [];
16
+ if (currentPosition.title) positionParts.push(currentPosition.title);
17
+ const company = currentPosition.entityName ?? currentPosition.company;
18
+ if (company) positionParts.push(`at ${company}`);
19
+ if (positionParts.length > 0) {
20
+ const roleLine = positionParts.join(" ");
21
+ if (!isRoleLineRedundant(profile.headline, roleLine)) parts.push(roleLine);
22
+ }
23
+ }
24
+ if (profile.location) {
25
+ parts.push(formatLocation(profile.location));
26
+ }
27
+ if (parts.length === 0) {
28
+ return `${profile.displayName ?? profile.handle} on Sifa`;
29
+ }
30
+ return parts.join(" \xB7 ");
31
+ }
32
+ export {
33
+ buildMetaDescription
34
+ };
35
+ //# sourceMappingURL=meta-description.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/meta-description.ts"],"sourcesContent":["/**\n * Meta description for a profile page.\n *\n * Ported from sifa-web so the description and the JSON-LD `jobTitle` are\n * derived from one place. They previously used different rules for \"which\n * position is current\" and could name different roles on the same page.\n */\n\nimport { pickPrimaryPosition } from '../logic/primary-position.js';\nimport { isRoleLineRedundant } from '../profile/headline-role-dedupe.js';\nimport { filterHidden } from '../profile/section-model.js';\nimport type { LocationValue } from '../types/index.js';\nimport { formatLocation } from './location-utils.js';\n\n/**\n * Treat a headline as meaningful only when it contains at least one letter or\n * digit. Profiles carry headlines that are a single emoji, which reads as\n * garbage in a search result snippet.\n */\nconst MEANINGFUL_TEXT_RE = /[\\p{L}\\p{N}]/u;\n\nexport interface MetaDescriptionPosition {\n readonly company?: string;\n /**\n * Current canonical name of the linked company entity, resolved by the\n * AppView at read time. Preferred over the free-text `company` snapshot so a\n * corrected company name (and a decoded one) shows here, not the value frozen\n * into the position record.\n */\n readonly entityName?: string;\n readonly title?: string;\n readonly startedAt?: string;\n readonly endedAt?: string;\n readonly primary?: boolean;\n readonly hidden?: boolean;\n}\n\nexport interface MetaDescriptionInput {\n readonly handle: string;\n readonly displayName?: string;\n readonly headline?: string;\n readonly location?: LocationValue | null;\n readonly positions?: readonly MetaDescriptionPosition[];\n}\n\nexport function buildMetaDescription(profile: MetaDescriptionInput): string {\n const parts: string[] = [];\n\n if (typeof profile.headline === 'string' && MEANINGFUL_TEXT_RE.test(profile.headline)) {\n parts.push(profile.headline);\n }\n\n const positions = filterHidden(profile.positions ? [...profile.positions] : undefined);\n const currentPosition = pickPrimaryPosition(positions);\n if (currentPosition) {\n const positionParts: string[] = [];\n if (currentPosition.title) positionParts.push(currentPosition.title);\n const company = currentPosition.entityName ?? currentPosition.company;\n if (company) positionParts.push(`at ${company}`);\n if (positionParts.length > 0) {\n const roleLine = positionParts.join(' ');\n // Skip the derived role line when the headline already says the same thing\n // (common after LinkedIn import, where the headline is \"Title at Company\").\n if (!isRoleLineRedundant(profile.headline, roleLine)) parts.push(roleLine);\n }\n }\n\n if (profile.location) {\n parts.push(formatLocation(profile.location));\n }\n\n if (parts.length === 0) {\n return `${profile.displayName ?? profile.handle} on Sifa`;\n }\n\n return parts.join(' · ');\n}\n"],"mappings":";AAQA,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAE7B,SAAS,sBAAsB;AAO/B,IAAM,qBAAqB;AA0BpB,SAAS,qBAAqB,SAAuC;AAC1E,QAAM,QAAkB,CAAC;AAEzB,MAAI,OAAO,QAAQ,aAAa,YAAY,mBAAmB,KAAK,QAAQ,QAAQ,GAAG;AACrF,UAAM,KAAK,QAAQ,QAAQ;AAAA,EAC7B;AAEA,QAAM,YAAY,aAAa,QAAQ,YAAY,CAAC,GAAG,QAAQ,SAAS,IAAI,MAAS;AACrF,QAAM,kBAAkB,oBAAoB,SAAS;AACrD,MAAI,iBAAiB;AACnB,UAAM,gBAA0B,CAAC;AACjC,QAAI,gBAAgB,MAAO,eAAc,KAAK,gBAAgB,KAAK;AACnE,UAAM,UAAU,gBAAgB,cAAc,gBAAgB;AAC9D,QAAI,QAAS,eAAc,KAAK,MAAM,OAAO,EAAE;AAC/C,QAAI,cAAc,SAAS,GAAG;AAC5B,YAAM,WAAW,cAAc,KAAK,GAAG;AAGvC,UAAI,CAAC,oBAAoB,QAAQ,UAAU,QAAQ,EAAG,OAAM,KAAK,QAAQ;AAAA,IAC3E;AAAA,EACF;AAEA,MAAI,QAAQ,UAAU;AACpB,UAAM,KAAK,eAAe,QAAQ,QAAQ,CAAC;AAAA,EAC7C;AAEA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,GAAG,QAAQ,eAAe,QAAQ,MAAM;AAAA,EACjD;AAEA,SAAO,MAAM,KAAK,QAAK;AACzB;","names":[]}
@@ -0,0 +1,12 @@
1
+ // src/format/normalize-company-key.ts
2
+ var COMBINING_MARKS = /[\u0300-\u036f]/g;
3
+ function normalizeCompanyKey(name) {
4
+ const trimmed = name.trim();
5
+ if (!trimmed) return "";
6
+ const caseFolded = trimmed.normalize("NFC").toLowerCase();
7
+ return caseFolded.normalize("NFD").replace(COMBINING_MARKS, "").normalize("NFC");
8
+ }
9
+ export {
10
+ normalizeCompanyKey
11
+ };
12
+ //# sourceMappingURL=normalize-company-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/normalize-company-key.ts"],"sourcesContent":["// Combining diacritical marks (U+0300-U+036F) produced by NFD decomposition.\n// Stripping these after decomposing is the standard way to fold \"é\" -> \"e\"\n// without a per-character lookup table.\nconst COMBINING_MARKS = /[\\u0300-\\u036f]/g;\n\n/**\n * Build a stable dedup/prefix key for a company name (decision D8).\n *\n * NFC-normalizes, case-folds, and strips Latin diacritics so that visually\n * or logically equivalent spellings collapse to one key: \"Nestlé\", \"Nestle\",\n * and \"NESTLE\" all produce the same value. Non-Latin scripts (Cyrillic, CJK,\n * etc) have no Latin diacritics to strip, so they pass through case-folded\n * and NFC-normalized only.\n *\n * This is a pure SDK helper for the in-memory/dedup key. Persisting it as a\n * dedicated column on the sifa-api side is tracked separately\n * (sifa-workspace#229) and out of scope here.\n */\nexport function normalizeCompanyKey(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) return '';\n\n const caseFolded = trimmed.normalize('NFC').toLowerCase();\n return caseFolded.normalize('NFD').replace(COMBINING_MARKS, '').normalize('NFC');\n}\n"],"mappings":";AAGA,IAAM,kBAAkB;AAejB,SAAS,oBAAoB,MAAsB;AACxD,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,aAAa,QAAQ,UAAU,KAAK,EAAE,YAAY;AACxD,SAAO,WAAW,UAAU,KAAK,EAAE,QAAQ,iBAAiB,EAAE,EAAE,UAAU,KAAK;AACjF;","names":[]}
@@ -0,0 +1,71 @@
1
+ // src/format/normalize-legal-form.ts
2
+ import { hasNonAsciiLetter } from "./company-name.js";
3
+ var CANONICAL_LEGAL_FORMS = {
4
+ llc: "LLC",
5
+ inc: "Inc",
6
+ ltd: "Ltd",
7
+ ltda: "Ltda",
8
+ corp: "Corp",
9
+ co: "Co",
10
+ plc: "PLC",
11
+ llp: "LLP",
12
+ lp: "LP",
13
+ gmbh: "GmbH",
14
+ ag: "AG",
15
+ ab: "AB",
16
+ as: "AS",
17
+ aps: "ApS",
18
+ oy: "Oy",
19
+ oyj: "Oyj",
20
+ nv: "NV",
21
+ bv: "BV",
22
+ sa: "SA",
23
+ sas: "SAS",
24
+ sarl: "SARL",
25
+ kk: "KK",
26
+ sl: "SL",
27
+ pty: "Pty"
28
+ };
29
+ function recase(token, canonical) {
30
+ let li = 0;
31
+ let out = "";
32
+ for (const ch of token) {
33
+ if (/[A-Za-z]/.test(ch)) {
34
+ out += canonical.charAt(li);
35
+ li += 1;
36
+ } else {
37
+ out += ch;
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+ function normalizeToken(token) {
43
+ const letters = token.replace(/\./g, "");
44
+ if (!/^[A-Za-z]+$/.test(letters)) return token;
45
+ const key = letters.toLowerCase();
46
+ const canonical = CANONICAL_LEGAL_FORMS[key];
47
+ if (typeof canonical !== "string") return token;
48
+ return recase(token, canonical);
49
+ }
50
+ function normalizeLegalForm(name) {
51
+ if (!name || hasNonAsciiLetter(name)) return name;
52
+ const parts = name.split(/(\s+)/);
53
+ const wordIndices = [];
54
+ for (let i = 0; i < parts.length; i += 1) {
55
+ const part = parts[i];
56
+ if (part !== void 0 && part.length > 0 && !/^\s+$/.test(part)) wordIndices.push(i);
57
+ }
58
+ if (wordIndices.length === 0) return name;
59
+ const lastIdx = wordIndices[wordIndices.length - 1];
60
+ if (lastIdx === void 0) return name;
61
+ const original = parts[lastIdx];
62
+ if (original === void 0) return name;
63
+ const rewritten = normalizeToken(original);
64
+ if (rewritten === original) return name;
65
+ parts[lastIdx] = rewritten;
66
+ return parts.join("");
67
+ }
68
+ export {
69
+ normalizeLegalForm
70
+ };
71
+ //# sourceMappingURL=normalize-legal-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/normalize-legal-form.ts"],"sourcesContent":["import { hasNonAsciiLetter } from './company-name.js';\n\n/**\n * Canonical casing for common legal-form designators, keyed by the token with\n * its dots stripped and lowercased (`n.v.` and `nv` both key to `nv`). The value\n * is the canonical LETTER sequence only -- dots are re-applied from the input, so\n * `n.v.` -> `N.V.` and `nv` -> `NV` from the single `nv` entry.\n *\n * Deliberately conservative. Designators whose canonical casing genuinely\n * depends on the dot style (Italian `srl`/`S.r.l.`, `spa`/`S.p.A.`) are OMITTED\n * rather than guessed -- see the module doc on `normalizeLegalForm`. Multi-word\n * forms (`Sp. z o.o.`, `Pty Ltd`) are out of scope for this single-token pass.\n */\nconst CANONICAL_LEGAL_FORMS: Readonly<Record<string, string>> = {\n llc: 'LLC',\n inc: 'Inc',\n ltd: 'Ltd',\n ltda: 'Ltda',\n corp: 'Corp',\n co: 'Co',\n plc: 'PLC',\n llp: 'LLP',\n lp: 'LP',\n gmbh: 'GmbH',\n ag: 'AG',\n ab: 'AB',\n as: 'AS',\n aps: 'ApS',\n oy: 'Oy',\n oyj: 'Oyj',\n nv: 'NV',\n bv: 'BV',\n sa: 'SA',\n sas: 'SAS',\n sarl: 'SARL',\n kk: 'KK',\n sl: 'SL',\n pty: 'Pty',\n};\n\n/**\n * Re-apply `canonical`'s casing onto `token`, letter by letter, keeping every\n * non-letter character (the dots) exactly where the input had them. `token` is\n * guaranteed ASCII (the caller bailed on non-ASCII names) and its letter count\n * equals `canonical.length`, so the index never runs past the end.\n */\nfunction recase(token: string, canonical: string): string {\n let li = 0;\n let out = '';\n for (const ch of token) {\n if (/[A-Za-z]/.test(ch)) {\n out += canonical.charAt(li);\n li += 1;\n } else {\n out += ch;\n }\n }\n return out;\n}\n\n/** Normalize one candidate token, or return it unchanged if it isn't a designator. */\nfunction normalizeToken(token: string): string {\n // Strip dots only; a clean designator token is then a pure ASCII-letter run.\n // A hyphen, comma, paren, or digit means it isn't a bare designator -> skip.\n const letters = token.replace(/\\./g, '');\n if (!/^[A-Za-z]+$/.test(letters)) return token;\n\n const key = letters.toLowerCase();\n const canonical = CANONICAL_LEGAL_FORMS[key];\n // Require a string value, not merely truthy: a bare `map[key]` lookup resolves\n // inherited Object.prototype members, so a token like \"Constructor\" (key\n // `constructor`) or \"__proto__\" returns a function/object that is truthy but\n // not a string, and `recase` would then throw on `.charAt`. This also narrows\n // away the `undefined` from a missing key.\n if (typeof canonical !== 'string') return token;\n\n return recase(token, canonical);\n}\n\n/**\n * Normalize the letter-case of a legal-form designator (GmbH, LLC, N.V., B.V.,\n * S.A., ...) appearing as the trailing whole-word token of a company\n * name. Only that one token is touched; the company name itself is never\n * recased, since brand-wordmark casing is not deterministic (see\n * sifa-workspace#235, deliberately distinct from this pass).\n *\n * The input's dot and spacing style is preserved verbatim -- dotted vs undotted\n * (`N.V.` vs `NV`) is a style choice, so the function never adds, removes, or\n * reorders dots. It only maps `gmbh`/`Gmbh`/`GMBH` -> `GmbH`, `nv` -> `NV`,\n * `n.v.` -> `N.V.`, and so on.\n *\n * Scoped to ASCII-Latin (decision D8, matching `formatCompanyName`): any name\n * containing a non-ASCII letter is returned unchanged, because locale-dependent\n * casing (Turkish i/İ) makes naive upper/lower unsafe. Idempotent: normalizing\n * an already-correct name is a no-op.\n */\nexport function normalizeLegalForm(name: string): string {\n if (!name || hasNonAsciiLetter(name)) return name;\n\n // Split into words and whitespace runs, keeping the separators so the exact\n // original spacing (including leading/trailing) survives the round-trip.\n const parts = name.split(/(\\s+)/);\n const wordIndices: number[] = [];\n for (let i = 0; i < parts.length; i += 1) {\n const part = parts[i];\n if (part !== undefined && part.length > 0 && !/^\\s+$/.test(part)) wordIndices.push(i);\n }\n if (wordIndices.length === 0) return name;\n\n const lastIdx = wordIndices[wordIndices.length - 1];\n if (lastIdx === undefined) return name;\n // Only the TRAILING token is a candidate. Legal-form designators almost always\n // trail the company name (Acme GmbH, Halma PLC); a LEADING designator token is\n // far more likely part of the brand (\"INC Research\", \"AG Innovations\"), so\n // recasing it would corrupt the name. Restricting to the trailing token avoids\n // that whole class of false positives.\n const original = parts[lastIdx];\n if (original === undefined) return name;\n const rewritten = normalizeToken(original);\n if (rewritten === original) return name;\n parts[lastIdx] = rewritten;\n return parts.join('');\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAalC,IAAM,wBAA0D;AAAA,EAC9D,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,KAAK;AACP;AAQA,SAAS,OAAO,OAAe,WAA2B;AACxD,MAAI,KAAK;AACT,MAAI,MAAM;AACV,aAAW,MAAM,OAAO;AACtB,QAAI,WAAW,KAAK,EAAE,GAAG;AACvB,aAAO,UAAU,OAAO,EAAE;AAC1B,YAAM;AAAA,IACR,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,eAAe,OAAuB;AAG7C,QAAM,UAAU,MAAM,QAAQ,OAAO,EAAE;AACvC,MAAI,CAAC,cAAc,KAAK,OAAO,EAAG,QAAO;AAEzC,QAAM,MAAM,QAAQ,YAAY;AAChC,QAAM,YAAY,sBAAsB,GAAG;AAM3C,MAAI,OAAO,cAAc,SAAU,QAAO;AAE1C,SAAO,OAAO,OAAO,SAAS;AAChC;AAmBO,SAAS,mBAAmB,MAAsB;AACvD,MAAI,CAAC,QAAQ,kBAAkB,IAAI,EAAG,QAAO;AAI7C,QAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,QAAM,cAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,SAAS,UAAa,KAAK,SAAS,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAG,aAAY,KAAK,CAAC;AAAA,EACtF;AACA,MAAI,YAAY,WAAW,EAAG,QAAO;AAErC,QAAM,UAAU,YAAY,YAAY,SAAS,CAAC;AAClD,MAAI,YAAY,OAAW,QAAO;AAMlC,QAAM,WAAW,MAAM,OAAO;AAC9B,MAAI,aAAa,OAAW,QAAO;AACnC,QAAM,YAAY,eAAe,QAAQ;AACzC,MAAI,cAAc,SAAU,QAAO;AACnC,QAAM,OAAO,IAAI;AACjB,SAAO,MAAM,KAAK,EAAE;AACtB;","names":[]}
@@ -0,0 +1,73 @@
1
+ // src/format/pds-utils.ts
2
+ var BSKY_PROFILE_BASE = "https://bsky.app/profile/";
3
+ var PDS_PROVIDERS = [
4
+ { suffix: ".bsky.social", name: "bluesky" },
5
+ { suffix: ".blacksky.app", name: "blacksky" },
6
+ { suffix: ".eurosky.social", name: "eurosky" },
7
+ { suffix: ".northsky.social", name: "northsky" }
8
+ ];
9
+ var KNOWN_PROVIDER_NAMES = new Set(PDS_PROVIDERS.map((p) => p.name));
10
+ var ICON_ONLY_PROVIDERS = /* @__PURE__ */ new Set(["selfhosted-social", "selfhosted"]);
11
+ function pdsProviderFromApi(apiProvider, handle) {
12
+ if (!apiProvider) return null;
13
+ if (ICON_ONLY_PROVIDERS.has(apiProvider.name)) {
14
+ return { name: apiProvider.name, profileUrl: "", host: apiProvider.host };
15
+ }
16
+ if (!KNOWN_PROVIDER_NAMES.has(apiProvider.name)) return null;
17
+ return {
18
+ name: apiProvider.name,
19
+ profileUrl: `${BSKY_PROFILE_BASE}${handle}`,
20
+ host: apiProvider.host
21
+ };
22
+ }
23
+ function getHandleStem(handle) {
24
+ const lower = handle.toLowerCase();
25
+ for (const provider of PDS_PROVIDERS) {
26
+ if (lower.endsWith(provider.suffix) && lower.length > provider.suffix.length) {
27
+ return handle.slice(0, -provider.suffix.length);
28
+ }
29
+ }
30
+ return handle;
31
+ }
32
+ function getDisplayLabel(displayName, handle) {
33
+ if (displayName) return displayName;
34
+ return getHandleStem(handle);
35
+ }
36
+ function formatStructuredName(givenName, familyName) {
37
+ const given = givenName?.trim();
38
+ const family = familyName?.trim();
39
+ if (given && family) return `${given} ${family}`;
40
+ return given || family || void 0;
41
+ }
42
+ var PDS_DISPLAY_NAMES = {
43
+ bluesky: "Bluesky",
44
+ blacksky: "BlackSky",
45
+ eurosky: "EuroSky",
46
+ northsky: "NorthSky",
47
+ "selfhosted-social": "Self-hosted",
48
+ selfhosted: "Self-hosted"
49
+ };
50
+ function getPdsDisplayName(providerName) {
51
+ return PDS_DISPLAY_NAMES[providerName] ?? providerName;
52
+ }
53
+ function detectPdsProvider(handle) {
54
+ const lower = handle.toLowerCase();
55
+ for (const provider of PDS_PROVIDERS) {
56
+ if (lower.endsWith(provider.suffix)) {
57
+ return {
58
+ name: provider.name,
59
+ profileUrl: `${BSKY_PROFILE_BASE}${handle}`
60
+ };
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+ export {
66
+ detectPdsProvider,
67
+ formatStructuredName,
68
+ getDisplayLabel,
69
+ getHandleStem,
70
+ getPdsDisplayName,
71
+ pdsProviderFromApi
72
+ };
73
+ //# sourceMappingURL=pds-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/pds-utils.ts"],"sourcesContent":["import type { PdsProviderInfo } from '../types/index.js';\n\nexport interface PdsProvider {\n name: string;\n profileUrl: string;\n host?: string;\n}\n\nconst BSKY_PROFILE_BASE = 'https://bsky.app/profile/';\n\nconst PDS_PROVIDERS: { suffix: string; name: string }[] = [\n { suffix: '.bsky.social', name: 'bluesky' },\n { suffix: '.blacksky.app', name: 'blacksky' },\n { suffix: '.eurosky.social', name: 'eurosky' },\n { suffix: '.northsky.social', name: 'northsky' },\n];\n\nconst KNOWN_PROVIDER_NAMES = new Set(PDS_PROVIDERS.map((p) => p.name));\n\nconst ICON_ONLY_PROVIDERS = new Set(['selfhosted-social', 'selfhosted']);\n\nexport function pdsProviderFromApi(\n apiProvider: PdsProviderInfo | null | undefined,\n handle: string,\n): PdsProvider | null {\n if (!apiProvider) return null;\n if (ICON_ONLY_PROVIDERS.has(apiProvider.name)) {\n return { name: apiProvider.name, profileUrl: '', host: apiProvider.host };\n }\n if (!KNOWN_PROVIDER_NAMES.has(apiProvider.name)) return null;\n return {\n name: apiProvider.name,\n profileUrl: `${BSKY_PROFILE_BASE}${handle}`,\n host: apiProvider.host,\n };\n}\n\nexport function getHandleStem(handle: string): string {\n const lower = handle.toLowerCase();\n for (const provider of PDS_PROVIDERS) {\n if (lower.endsWith(provider.suffix) && lower.length > provider.suffix.length) {\n return handle.slice(0, -provider.suffix.length);\n }\n }\n return handle;\n}\n\nexport function getDisplayLabel(displayName: string | undefined, handle: string): string {\n if (displayName) return displayName;\n return getHandleStem(handle);\n}\n\n/**\n * Combine optional structured name fields (`id.sifa.profile.self.givenName`,\n * `familyName`) into a single string in Schema.org `Person` order — given\n * before family. Returns `undefined` when both are absent so callers can\n * fall back to `displayName`. Whitespace-only inputs are treated as absent.\n * Does not impose cultural ordering: users whose preferred presentation\n * differs (e.g. Eastern name order) should set `displayName` accordingly,\n * which this helper deliberately does not consult.\n */\nexport function formatStructuredName(\n givenName: string | undefined,\n familyName: string | undefined,\n): string | undefined {\n const given = givenName?.trim();\n const family = familyName?.trim();\n if (given && family) return `${given} ${family}`;\n return given || family || undefined;\n}\n\nconst PDS_DISPLAY_NAMES: Record<string, string> = {\n bluesky: 'Bluesky',\n blacksky: 'BlackSky',\n eurosky: 'EuroSky',\n northsky: 'NorthSky',\n 'selfhosted-social': 'Self-hosted',\n selfhosted: 'Self-hosted',\n};\n\nexport function getPdsDisplayName(providerName: string): string {\n return PDS_DISPLAY_NAMES[providerName] ?? providerName;\n}\n\nexport function detectPdsProvider(handle: string): PdsProvider | null {\n const lower = handle.toLowerCase();\n for (const provider of PDS_PROVIDERS) {\n if (lower.endsWith(provider.suffix)) {\n return {\n name: provider.name,\n profileUrl: `${BSKY_PROFILE_BASE}${handle}`,\n };\n }\n }\n return null;\n}\n"],"mappings":";AAQA,IAAM,oBAAoB;AAE1B,IAAM,gBAAoD;AAAA,EACxD,EAAE,QAAQ,gBAAgB,MAAM,UAAU;AAAA,EAC1C,EAAE,QAAQ,iBAAiB,MAAM,WAAW;AAAA,EAC5C,EAAE,QAAQ,mBAAmB,MAAM,UAAU;AAAA,EAC7C,EAAE,QAAQ,oBAAoB,MAAM,WAAW;AACjD;AAEA,IAAM,uBAAuB,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAErE,IAAM,sBAAsB,oBAAI,IAAI,CAAC,qBAAqB,YAAY,CAAC;AAEhE,SAAS,mBACd,aACA,QACoB;AACpB,MAAI,CAAC,YAAa,QAAO;AACzB,MAAI,oBAAoB,IAAI,YAAY,IAAI,GAAG;AAC7C,WAAO,EAAE,MAAM,YAAY,MAAM,YAAY,IAAI,MAAM,YAAY,KAAK;AAAA,EAC1E;AACA,MAAI,CAAC,qBAAqB,IAAI,YAAY,IAAI,EAAG,QAAO;AACxD,SAAO;AAAA,IACL,MAAM,YAAY;AAAA,IAClB,YAAY,GAAG,iBAAiB,GAAG,MAAM;AAAA,IACzC,MAAM,YAAY;AAAA,EACpB;AACF;AAEO,SAAS,cAAc,QAAwB;AACpD,QAAM,QAAQ,OAAO,YAAY;AACjC,aAAW,YAAY,eAAe;AACpC,QAAI,MAAM,SAAS,SAAS,MAAM,KAAK,MAAM,SAAS,SAAS,OAAO,QAAQ;AAC5E,aAAO,OAAO,MAAM,GAAG,CAAC,SAAS,OAAO,MAAM;AAAA,IAChD;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,aAAiC,QAAwB;AACvF,MAAI,YAAa,QAAO;AACxB,SAAO,cAAc,MAAM;AAC7B;AAWO,SAAS,qBACd,WACA,YACoB;AACpB,QAAM,QAAQ,WAAW,KAAK;AAC9B,QAAM,SAAS,YAAY,KAAK;AAChC,MAAI,SAAS,OAAQ,QAAO,GAAG,KAAK,IAAI,MAAM;AAC9C,SAAO,SAAS,UAAU;AAC5B;AAEA,IAAM,oBAA4C;AAAA,EAChD,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,YAAY;AACd;AAEO,SAAS,kBAAkB,cAA8B;AAC9D,SAAO,kBAAkB,YAAY,KAAK;AAC5C;AAEO,SAAS,kBAAkB,QAAoC;AACpE,QAAM,QAAQ,OAAO,YAAY;AACjC,aAAW,YAAY,eAAe;AACpC,QAAI,MAAM,SAAS,SAAS,MAAM,GAAG;AACnC,aAAO;AAAA,QACL,MAAM,SAAS;AAAA,QACf,YAAY,GAAG,iBAAiB,GAAG,MAAM;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,39 @@
1
+ // src/format/sort-by-date.ts
2
+ var FAR_FUTURE = "9999-12-31";
3
+ var FAR_PAST = "0000-01-01";
4
+ function sortByDateDesc(items, extract) {
5
+ return [...items].sort((a, b) => {
6
+ const da = extract(a);
7
+ const db = extract(b);
8
+ const endA = da.current ? FAR_FUTURE : da.endDate ?? "";
9
+ const endB = db.current ? FAR_FUTURE : db.endDate ?? "";
10
+ const hasDateA = endA || da.startDate;
11
+ const hasDateB = endB || db.startDate;
12
+ if (hasDateA && !hasDateB) return -1;
13
+ if (!hasDateA && hasDateB) return 1;
14
+ if (endA !== endB) return endB.localeCompare(endA);
15
+ const startA = da.startDate ?? FAR_PAST;
16
+ const startB = db.startDate ?? FAR_PAST;
17
+ return startB.localeCompare(startA);
18
+ });
19
+ }
20
+ function dateRangeExtractor(item) {
21
+ return { startDate: item.startDate, endDate: item.endDate, current: item.current };
22
+ }
23
+ function lexiconDateExtractor(item) {
24
+ return { startDate: item.startedAt, endDate: item.endedAt, current: !item.endedAt };
25
+ }
26
+ function singleDateExtractor(item) {
27
+ return { endDate: item.date };
28
+ }
29
+ function certDateExtractor(item) {
30
+ return { startDate: item.issueDate, endDate: item.expiryDate ?? item.issueDate };
31
+ }
32
+ export {
33
+ certDateExtractor,
34
+ dateRangeExtractor,
35
+ lexiconDateExtractor,
36
+ singleDateExtractor,
37
+ sortByDateDesc
38
+ };
39
+ //# sourceMappingURL=sort-by-date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/sort-by-date.ts"],"sourcesContent":["/**\n * Sort profile section items from newest to oldest.\n *\n * Rules:\n * - Items with no end date (ongoing/current) sort to the top.\n * - Primary sort: end date descending (newest first).\n * - Secondary sort: start date descending (newest first).\n * - Items with no dates sort to the bottom.\n *\n * Date strings are ISO-ish (\"2024\", \"2024-03\", \"2024-03-15\") and\n * lexicographic comparison works correctly for them.\n */\n\ninterface DateRange {\n startDate?: string;\n endDate?: string;\n current?: boolean;\n}\n\ntype DateExtractor<T> = (item: T) => DateRange;\n\nconst FAR_FUTURE = '9999-12-31';\nconst FAR_PAST = '0000-01-01';\n\nexport function sortByDateDesc<T>(items: T[], extract: DateExtractor<T>): T[] {\n return [...items].sort((a, b) => {\n const da = extract(a);\n const db = extract(b);\n\n const endA = da.current ? FAR_FUTURE : (da.endDate ?? '');\n const endB = db.current ? FAR_FUTURE : (db.endDate ?? '');\n\n // Items with any date beat items with no dates at all\n const hasDateA = endA || da.startDate;\n const hasDateB = endB || db.startDate;\n if (hasDateA && !hasDateB) return -1;\n if (!hasDateA && hasDateB) return 1;\n\n // Primary: end date descending\n if (endA !== endB) return endB.localeCompare(endA);\n\n // Secondary: start date descending\n const startA = da.startDate ?? FAR_PAST;\n const startB = db.startDate ?? FAR_PAST;\n return startB.localeCompare(startA);\n });\n}\n\n/**\n * Convenience extractor for items that use `startDate` / `endDate` fields directly.\n * Used by volunteering, projects, and other sections that still use startDate/endDate.\n */\nexport function dateRangeExtractor<\n T extends { startDate?: string; endDate?: string; current?: boolean },\n>(item: T): DateRange {\n return { startDate: item.startDate, endDate: item.endDate, current: item.current };\n}\n\n/**\n * Extractor for lexicon-aligned items that use `startedAt` / `endedAt` fields.\n * Used by positions and education (current derived from !endedAt).\n */\nexport function lexiconDateExtractor<T extends { startedAt?: string; endedAt?: string }>(\n item: T,\n): DateRange {\n return { startDate: item.startedAt, endDate: item.endedAt, current: !item.endedAt };\n}\n\n/**\n * Convenience extractor for items with a single `date` field.\n */\nexport function singleDateExtractor<T extends { date?: string }>(item: T): DateRange {\n return { endDate: item.date };\n}\n\n/**\n * Extractor for certifications which use `issueDate` / `expiryDate`.\n */\nexport function certDateExtractor<T extends { issueDate?: string; expiryDate?: string }>(\n item: T,\n): DateRange {\n return { startDate: item.issueDate, endDate: item.expiryDate ?? item.issueDate };\n}\n"],"mappings":";AAqBA,IAAM,aAAa;AACnB,IAAM,WAAW;AAEV,SAAS,eAAkB,OAAY,SAAgC;AAC5E,SAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AAC/B,UAAM,KAAK,QAAQ,CAAC;AACpB,UAAM,KAAK,QAAQ,CAAC;AAEpB,UAAM,OAAO,GAAG,UAAU,aAAc,GAAG,WAAW;AACtD,UAAM,OAAO,GAAG,UAAU,aAAc,GAAG,WAAW;AAGtD,UAAM,WAAW,QAAQ,GAAG;AAC5B,UAAM,WAAW,QAAQ,GAAG;AAC5B,QAAI,YAAY,CAAC,SAAU,QAAO;AAClC,QAAI,CAAC,YAAY,SAAU,QAAO;AAGlC,QAAI,SAAS,KAAM,QAAO,KAAK,cAAc,IAAI;AAGjD,UAAM,SAAS,GAAG,aAAa;AAC/B,UAAM,SAAS,GAAG,aAAa;AAC/B,WAAO,OAAO,cAAc,MAAM;AAAA,EACpC,CAAC;AACH;AAMO,SAAS,mBAEd,MAAoB;AACpB,SAAO,EAAE,WAAW,KAAK,WAAW,SAAS,KAAK,SAAS,SAAS,KAAK,QAAQ;AACnF;AAMO,SAAS,qBACd,MACW;AACX,SAAO,EAAE,WAAW,KAAK,WAAW,SAAS,KAAK,SAAS,SAAS,CAAC,KAAK,QAAQ;AACpF;AAKO,SAAS,oBAAiD,MAAoB;AACnF,SAAO,EAAE,SAAS,KAAK,KAAK;AAC9B;AAKO,SAAS,kBACd,MACW;AACX,SAAO,EAAE,WAAW,KAAK,WAAW,SAAS,KAAK,cAAc,KAAK,UAAU;AACjF;","names":[]}
@@ -0,0 +1,37 @@
1
+ // src/format/summarize-deliveries.ts
2
+ var CANCELLED_STATUS = "community.lexicon.calendar.event#cancelled";
3
+ var KEYNOTE_ROLE = "id.sifa.defs#keynote";
4
+ function summarizePresentationDeliveries(deliveries, options = {}) {
5
+ const venueLimit = options.venueLimit ?? 3;
6
+ const counted = (deliveries ?? []).filter((delivery) => delivery.status !== CANCELLED_STATUS);
7
+ const sorted = [...counted].sort((a, b) => (b.date ?? "").localeCompare(a.date ?? ""));
8
+ let recentYear;
9
+ for (const delivery of counted) {
10
+ if (!delivery.date) continue;
11
+ const year = Number(delivery.date.slice(0, 4));
12
+ if (!Number.isNaN(year) && (recentYear === void 0 || year > recentYear)) {
13
+ recentYear = year;
14
+ }
15
+ }
16
+ const seen = /* @__PURE__ */ new Set();
17
+ const distinctVenues = [];
18
+ for (const delivery of sorted) {
19
+ const name = delivery.eventName?.trim();
20
+ if (name && !seen.has(name)) {
21
+ seen.add(name);
22
+ distinctVenues.push(name);
23
+ }
24
+ }
25
+ const venues = distinctVenues.slice(0, venueLimit);
26
+ return {
27
+ count: counted.length,
28
+ recentYear,
29
+ keynoteCount: counted.filter((delivery) => delivery.role === KEYNOTE_ROLE).length,
30
+ venues,
31
+ moreVenues: distinctVenues.length - venues.length
32
+ };
33
+ }
34
+ export {
35
+ summarizePresentationDeliveries
36
+ };
37
+ //# sourceMappingURL=summarize-deliveries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/summarize-deliveries.ts"],"sourcesContent":["import type { ProfilePresentationDelivery } from '../types/index.js';\n\nconst CANCELLED_STATUS = 'community.lexicon.calendar.event#cancelled';\nconst KEYNOTE_ROLE = 'id.sifa.defs#keynote';\n\n/** A compact roll-up of a presentation's deliveries for the collapsed view. */\nexport interface PresentationDeliverySummary {\n /** Times the talk was actually delivered (cancelled occasions excluded). */\n count: number;\n /** Most recent year among counted deliveries that carry a date. */\n recentYear?: number;\n /** How many counted deliveries were keynotes. */\n keynoteCount: number;\n /** Up to `venueLimit` distinct event names, most-recent first. */\n venues: string[];\n /** Distinct counted venues beyond the ones listed in `venues`. */\n moreVenues: number;\n}\n\n/**\n * Summarize a talk's delivery history into the signals the profile shows in the\n * collapsed view: how many times it was given, the most recent year, how many\n * were keynotes, and a small sample of the most recent distinct venues.\n *\n * Cancelled occasions are excluded from every figure: a talk that was booked\n * then cancelled was not actually delivered, and counting it would inflate the\n * \"given Nx\" signal.\n */\nexport function summarizePresentationDeliveries(\n deliveries: ProfilePresentationDelivery[] | undefined,\n options: { venueLimit?: number } = {},\n): PresentationDeliverySummary {\n const venueLimit = options.venueLimit ?? 3;\n const counted = (deliveries ?? []).filter((delivery) => delivery.status !== CANCELLED_STATUS);\n const sorted = [...counted].sort((a, b) => (b.date ?? '').localeCompare(a.date ?? ''));\n\n let recentYear: number | undefined;\n for (const delivery of counted) {\n if (!delivery.date) continue;\n const year = Number(delivery.date.slice(0, 4));\n if (!Number.isNaN(year) && (recentYear === undefined || year > recentYear)) {\n recentYear = year;\n }\n }\n\n const seen = new Set<string>();\n const distinctVenues: string[] = [];\n for (const delivery of sorted) {\n const name = delivery.eventName?.trim();\n if (name && !seen.has(name)) {\n seen.add(name);\n distinctVenues.push(name);\n }\n }\n const venues = distinctVenues.slice(0, venueLimit);\n\n return {\n count: counted.length,\n recentYear,\n keynoteCount: counted.filter((delivery) => delivery.role === KEYNOTE_ROLE).length,\n venues,\n moreVenues: distinctVenues.length - venues.length,\n };\n}\n"],"mappings":";AAEA,IAAM,mBAAmB;AACzB,IAAM,eAAe;AAyBd,SAAS,gCACd,YACA,UAAmC,CAAC,GACP;AAC7B,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,WAAW,cAAc,CAAC,GAAG,OAAO,CAAC,aAAa,SAAS,WAAW,gBAAgB;AAC5F,QAAM,SAAS,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,EAAE,CAAC;AAErF,MAAI;AACJ,aAAW,YAAY,SAAS;AAC9B,QAAI,CAAC,SAAS,KAAM;AACpB,UAAM,OAAO,OAAO,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC;AAC7C,QAAI,CAAC,OAAO,MAAM,IAAI,MAAM,eAAe,UAAa,OAAO,aAAa;AAC1E,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,iBAA2B,CAAC;AAClC,aAAW,YAAY,QAAQ;AAC7B,UAAM,OAAO,SAAS,WAAW,KAAK;AACtC,QAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,GAAG;AAC3B,WAAK,IAAI,IAAI;AACb,qBAAe,KAAK,IAAI;AAAA,IAC1B;AAAA,EACF;AACA,QAAM,SAAS,eAAe,MAAM,GAAG,UAAU;AAEjD,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf;AAAA,IACA,cAAc,QAAQ,OAAO,CAAC,aAAa,SAAS,SAAS,YAAY,EAAE;AAAA,IAC3E;AAAA,IACA,YAAY,eAAe,SAAS,OAAO;AAAA,EAC7C;AACF;","names":[]}
@@ -0,0 +1,21 @@
1
+ // src/format/talk-slug.ts
2
+ var MAX_SLUG_LENGTH = 60;
3
+ var COMBINING_MARKS = /[̀-ͯ]/g;
4
+ function slugifyTitle(title) {
5
+ const slug = title.normalize("NFKD").replace(COMBINING_MARKS, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
6
+ return slug.slice(0, MAX_SLUG_LENGTH).replace(/-+$/g, "");
7
+ }
8
+ function buildTalkSlug(title, rkey) {
9
+ const slug = slugifyTitle(title);
10
+ return slug ? `${slug}-${rkey}` : rkey;
11
+ }
12
+ function parseTalkRkey(segment) {
13
+ const idx = segment.lastIndexOf("-");
14
+ return idx === -1 ? segment : segment.slice(idx + 1);
15
+ }
16
+ export {
17
+ buildTalkSlug,
18
+ parseTalkRkey,
19
+ slugifyTitle
20
+ };
21
+ //# sourceMappingURL=talk-slug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/talk-slug.ts"],"sourcesContent":["/**\n * Slug helpers for talk-page URLs. Pure and offline: a talk page lives at a\n * path like `/{slug}-{rkey}`, where the slug is a human-readable, SEO-friendly\n * rendering of the title and the rkey is the record's stable identifier.\n * Because TIDs never contain a `-`, the rkey can always be recovered as the\n * substring after the final `-`.\n */\n\n/** Maximum slug length before the trailing rkey is appended. */\nconst MAX_SLUG_LENGTH = 60;\n\n/** Combining diacritical marks left behind by NFKD decomposition (U+0300–U+036F). */\nconst COMBINING_MARKS = /[̀-ͯ]/g;\n\n/**\n * Render a title as a URL slug: lowercased, diacritics stripped, runs of\n * non-alphanumerics collapsed to a single `-`, trimmed, and capped at 60 chars.\n * Returns `''` for an empty or whitespace-only title.\n */\nexport function slugifyTitle(title: string): string {\n const slug = title\n .normalize('NFKD')\n // Strip combining marks left by NFKD decomposition (é -> e).\n .replace(COMBINING_MARKS, '')\n .toLowerCase()\n // Collapse any run of non-alphanumerics into a single hyphen.\n .replace(/[^a-z0-9]+/g, '-')\n // Collapse repeated hyphens.\n .replace(/-+/g, '-')\n // Trim leading/trailing hyphens.\n .replace(/^-+|-+$/g, '');\n // Cap length, then trim a trailing hyphen the cut may have exposed.\n return slug.slice(0, MAX_SLUG_LENGTH).replace(/-+$/g, '');\n}\n\n/**\n * Build the talk-page path segment `${slug}-${rkey}`, or just `rkey` when the\n * title produces an empty slug.\n */\nexport function buildTalkSlug(title: string, rkey: string): string {\n const slug = slugifyTitle(title);\n return slug ? `${slug}-${rkey}` : rkey;\n}\n\n/**\n * Recover the rkey from a talk-page segment: the substring after the final `-`\n * (TIDs contain no `-`). Returns the whole segment when there is no `-`.\n */\nexport function parseTalkRkey(segment: string): string {\n const idx = segment.lastIndexOf('-');\n return idx === -1 ? segment : segment.slice(idx + 1);\n}\n"],"mappings":";AASA,IAAM,kBAAkB;AAGxB,IAAM,kBAAkB;AAOjB,SAAS,aAAa,OAAuB;AAClD,QAAM,OAAO,MACV,UAAU,MAAM,EAEhB,QAAQ,iBAAiB,EAAE,EAC3B,YAAY,EAEZ,QAAQ,eAAe,GAAG,EAE1B,QAAQ,OAAO,GAAG,EAElB,QAAQ,YAAY,EAAE;AAEzB,SAAO,KAAK,MAAM,GAAG,eAAe,EAAE,QAAQ,QAAQ,EAAE;AAC1D;AAMO,SAAS,cAAc,OAAe,MAAsB;AACjE,QAAM,OAAO,aAAa,KAAK;AAC/B,SAAO,OAAO,GAAG,IAAI,IAAI,IAAI,KAAK;AACpC;AAMO,SAAS,cAAc,SAAyB;AACrD,QAAM,MAAM,QAAQ,YAAY,GAAG;AACnC,SAAO,QAAQ,KAAK,UAAU,QAAQ,MAAM,MAAM,CAAC;AACrD;","names":[]}
@@ -0,0 +1,29 @@
1
+ // src/format/text-sanitize.ts
2
+ var COMBINING_MARK = /\p{M}/u;
3
+ var BIDI_CONTROLS = /[‎‏‪-‮⁦-⁩]/gu;
4
+ function limitCombiningMarks(value, maxPerBase = 4) {
5
+ if (!value || !COMBINING_MARK.test(value)) return value;
6
+ let out = "";
7
+ let combiningRun = 0;
8
+ for (const char of value) {
9
+ if (/\p{M}/u.test(char)) {
10
+ if (combiningRun < maxPerBase) {
11
+ out += char;
12
+ combiningRun += 1;
13
+ }
14
+ } else {
15
+ out += char;
16
+ combiningRun = 0;
17
+ }
18
+ }
19
+ return out;
20
+ }
21
+ function sanitizeDisplayText(value, maxCombiningPerBase = 4) {
22
+ if (!value) return value;
23
+ return limitCombiningMarks(value.replace(BIDI_CONTROLS, ""), maxCombiningPerBase);
24
+ }
25
+ export {
26
+ limitCombiningMarks,
27
+ sanitizeDisplayText
28
+ };
29
+ //# sourceMappingURL=text-sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/text-sanitize.ts"],"sourcesContent":["const COMBINING_MARK = /\\p{M}/u;\nconst BIDI_CONTROLS = /[‎‏‪-‮⁦-⁩]/gu;\n\n/**\n * Limit runs of Unicode combining marks (`\\p{M}`) to at most `maxPerBase`\n * marks following any single base character. Defuses \"Zalgo\" text where\n * dozens of stacked combining marks render outside the line box and bleed\n * into neighbouring UI.\n *\n * `maxPerBase` defaults to 4 — high enough to preserve legitimate stacks\n * in Thai, Arabic, Vietnamese, and IPA, low enough to neutralise the\n * vertical-overflow attack vector.\n */\nexport function limitCombiningMarks(value: string, maxPerBase = 4): string {\n if (!value || !COMBINING_MARK.test(value)) return value;\n\n let out = '';\n let combiningRun = 0;\n for (const char of value) {\n if (/\\p{M}/u.test(char)) {\n if (combiningRun < maxPerBase) {\n out += char;\n combiningRun += 1;\n }\n } else {\n out += char;\n combiningRun = 0;\n }\n }\n return out;\n}\n\n/**\n * Sanitise untrusted display text from PDS records before rendering in UI:\n * - strips bidi formatting controls (LRM/RLM/LRE/RLE/PDF/LRO/RLO/LRI/RLI/FSI/PDI)\n * that can hijack reading order or crash `next/og` (see Satori LRM+emoji bug)\n * - limits stacked combining marks (Zalgo defence)\n *\n * Preserves ZWJ (U+200D) so emoji sequences keep rendering.\n */\nexport function sanitizeDisplayText(value: string, maxCombiningPerBase = 4): string {\n if (!value) return value;\n return limitCombiningMarks(value.replace(BIDI_CONTROLS, ''), maxCombiningPerBase);\n}\n"],"mappings":";AAAA,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AAYf,SAAS,oBAAoB,OAAe,aAAa,GAAW;AACzE,MAAI,CAAC,SAAS,CAAC,eAAe,KAAK,KAAK,EAAG,QAAO;AAElD,MAAI,MAAM;AACV,MAAI,eAAe;AACnB,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,KAAK,IAAI,GAAG;AACvB,UAAI,eAAe,YAAY;AAC7B,eAAO;AACP,wBAAgB;AAAA,MAClB;AAAA,IACF,OAAO;AACL,aAAO;AACP,qBAAe;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAUO,SAAS,oBAAoB,OAAe,sBAAsB,GAAW;AAClF,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,oBAAoB,MAAM,QAAQ,eAAe,EAAE,GAAG,mBAAmB;AAClF;","names":[]}
@@ -0,0 +1,14 @@
1
+ // src/format/text-truncate.ts
2
+ var ELLIPSIS = "\u2026";
3
+ function truncateGraphemes(value, maxLen) {
4
+ if (maxLen <= 0) return "";
5
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
6
+ const graphemes = Array.from(segmenter.segment(value), (s) => s.segment);
7
+ if (graphemes.length <= maxLen) return value;
8
+ if (maxLen === 1) return ELLIPSIS;
9
+ return graphemes.slice(0, maxLen - 1).join("") + ELLIPSIS;
10
+ }
11
+ export {
12
+ truncateGraphemes
13
+ };
14
+ //# sourceMappingURL=text-truncate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/text-truncate.ts"],"sourcesContent":["const ELLIPSIS = '\\u2026';\n\n/**\n * Truncate a string to at most `maxLen` grapheme clusters, appending an\n * ellipsis when the string was shortened. Grapheme-aware so it never splits\n * emoji sequences (ZWJ, regional indicators, combining marks, surrogate pairs).\n */\nexport function truncateGraphemes(value: string, maxLen: number): string {\n if (maxLen <= 0) return '';\n const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n const graphemes = Array.from(segmenter.segment(value), (s) => s.segment);\n if (graphemes.length <= maxLen) return value;\n if (maxLen === 1) return ELLIPSIS;\n return graphemes.slice(0, maxLen - 1).join('') + ELLIPSIS;\n}\n"],"mappings":";AAAA,IAAM,WAAW;AAOV,SAAS,kBAAkB,OAAe,QAAwB;AACvE,MAAI,UAAU,EAAG,QAAO;AACxB,QAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAC3E,QAAM,YAAY,MAAM,KAAK,UAAU,QAAQ,KAAK,GAAG,CAAC,MAAM,EAAE,OAAO;AACvE,MAAI,UAAU,UAAU,OAAQ,QAAO;AACvC,MAAI,WAAW,EAAG,QAAO;AACzB,SAAO,UAAU,MAAM,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI;AACnD;","names":[]}
@@ -0,0 +1,19 @@
1
+ // src/format/time-utils.ts
2
+ function formatDistanceToNow(date) {
3
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
4
+ if (seconds < 60) return "just now";
5
+ const minutes = Math.floor(seconds / 60);
6
+ if (minutes < 60) return `${minutes}m ago`;
7
+ const hours = Math.floor(minutes / 60);
8
+ if (hours < 24) return `${hours}h ago`;
9
+ const days = Math.floor(hours / 24);
10
+ if (days < 7) return `${days}d ago`;
11
+ const weeks = Math.floor(days / 7);
12
+ if (weeks < 5) return `${weeks}w ago`;
13
+ const months = Math.floor(days / 30);
14
+ return `${months}mo ago`;
15
+ }
16
+ export {
17
+ formatDistanceToNow
18
+ };
19
+ //# sourceMappingURL=time-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/time-utils.ts"],"sourcesContent":["/**\n * Format a date as a relative time string (e.g. \"2 minutes ago\", \"3 days ago\").\n */\nexport function formatDistanceToNow(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000);\n if (seconds < 60) return 'just now';\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n\n const days = Math.floor(hours / 24);\n if (days < 7) return `${days}d ago`;\n\n const weeks = Math.floor(days / 7);\n if (weeks < 5) return `${weeks}w ago`;\n\n const months = Math.floor(days / 30);\n return `${months}mo ago`;\n}\n"],"mappings":";AAGO,SAAS,oBAAoB,MAAoB;AACtD,QAAM,UAAU,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,QAAQ,KAAK,GAAI;AAC/D,MAAI,UAAU,GAAI,QAAO;AAEzB,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AAEnC,QAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AACrC,MAAI,QAAQ,GAAI,QAAO,GAAG,KAAK;AAE/B,QAAM,OAAO,KAAK,MAAM,QAAQ,EAAE;AAClC,MAAI,OAAO,EAAG,QAAO,GAAG,IAAI;AAE5B,QAAM,QAAQ,KAAK,MAAM,OAAO,CAAC;AACjC,MAAI,QAAQ,EAAG,QAAO,GAAG,KAAK;AAE9B,QAAM,SAAS,KAAK,MAAM,OAAO,EAAE;AACnC,SAAO,GAAG,MAAM;AAClB;","names":[]}
@@ -0,0 +1,33 @@
1
+ // src/format/timeline.ts
2
+ import { isUpcomingStart } from "../logic/date-lookahead.js";
3
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
4
+ function formatTimelineDate(dateStr) {
5
+ if (dateStr.length === 4) return dateStr;
6
+ const [year, month] = dateStr.split("-");
7
+ if (!month) return year ?? dateStr;
8
+ const idx = parseInt(month, 10) - 1;
9
+ return `${MONTHS[idx]} ${year}`;
10
+ }
11
+ function formatDateRange(start, end, showPresent = true) {
12
+ if (!start && !end) return "";
13
+ if (!start) return end ? formatTimelineDate(end) : "";
14
+ const formattedStart = formatTimelineDate(start);
15
+ if (!end) return showPresent ? `${formattedStart} - Present` : formattedStart;
16
+ const formattedEnd = formatTimelineDate(end);
17
+ if (formattedStart === formattedEnd) return formattedStart;
18
+ return `${formattedStart} - ${formattedEnd}`;
19
+ }
20
+ function formatPositionDateRange(start, end, now = /* @__PURE__ */ new Date()) {
21
+ if (start && !end && isUpcomingStart(start, now)) return `Starts ${formatTimelineDate(start)}`;
22
+ return formatDateRange(start, end);
23
+ }
24
+ function formatCredentialDateRange(issueDate, expiryDate) {
25
+ return formatDateRange(issueDate ?? void 0, expiryDate ?? void 0, false);
26
+ }
27
+ export {
28
+ formatCredentialDateRange,
29
+ formatDateRange,
30
+ formatPositionDateRange,
31
+ formatTimelineDate
32
+ };
33
+ //# sourceMappingURL=timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/timeline.ts"],"sourcesContent":["/**\n * Server-safe timeline date formatters shared by every profile surface.\n */\n\nimport { isUpcomingStart } from '../logic/date-lookahead.js';\n\nconst MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n\n/** Format \"2007-01\" as \"Jan 2007\", pass through year-only strings. */\nexport function formatTimelineDate(dateStr: string): string {\n if (dateStr.length === 4) return dateStr;\n const [year, month] = dateStr.split('-');\n if (!month) return year ?? dateStr;\n const idx = parseInt(month, 10) - 1;\n return `${MONTHS[idx]} ${year}`;\n}\n\n/**\n * Format a date range for display. Handles missing dates, equal start/end,\n * and ongoing entries (showPresent defaults to true).\n */\nexport function formatDateRange(start?: string, end?: string, showPresent = true): string {\n if (!start && !end) return '';\n if (!start) return end ? formatTimelineDate(end) : '';\n const formattedStart = formatTimelineDate(start);\n if (!end) return showPresent ? `${formattedStart} - Present` : formattedStart;\n const formattedEnd = formatTimelineDate(end);\n if (formattedStart === formattedEnd) return formattedStart;\n return `${formattedStart} - ${formattedEnd}`;\n}\n\n/**\n * Format a position's dates. A role whose start month has not arrived yet (an\n * accepted offer) reads \"Starts Jan 2027\" rather than \"Jan 2027 - Present\",\n * which would claim it is already under way (sifa-workspace#581).\n */\nexport function formatPositionDateRange(\n start?: string,\n end?: string,\n now: Date = new Date(),\n): string {\n if (start && !end && isUpcomingStart(start, now)) return `Starts ${formatTimelineDate(start)}`;\n return formatDateRange(start, end);\n}\n\n/**\n * Format a credential's issue and expiry dates: \"Jan 2025 - Jan 2029\", or the\n * issue date alone for one that doesn't expire (never \"Present\").\n */\nexport function formatCredentialDateRange(\n issueDate?: string | null,\n expiryDate?: string | null,\n): string {\n return formatDateRange(issueDate ?? undefined, expiryDate ?? undefined, false);\n}\n"],"mappings":";AAIA,SAAS,uBAAuB;AAEhC,IAAM,SAAS,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAG3F,SAAS,mBAAmB,SAAyB;AAC1D,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,CAAC,MAAM,KAAK,IAAI,QAAQ,MAAM,GAAG;AACvC,MAAI,CAAC,MAAO,QAAO,QAAQ;AAC3B,QAAM,MAAM,SAAS,OAAO,EAAE,IAAI;AAClC,SAAO,GAAG,OAAO,GAAG,CAAC,IAAI,IAAI;AAC/B;AAMO,SAAS,gBAAgB,OAAgB,KAAc,cAAc,MAAc;AACxF,MAAI,CAAC,SAAS,CAAC,IAAK,QAAO;AAC3B,MAAI,CAAC,MAAO,QAAO,MAAM,mBAAmB,GAAG,IAAI;AACnD,QAAM,iBAAiB,mBAAmB,KAAK;AAC/C,MAAI,CAAC,IAAK,QAAO,cAAc,GAAG,cAAc,eAAe;AAC/D,QAAM,eAAe,mBAAmB,GAAG;AAC3C,MAAI,mBAAmB,aAAc,QAAO;AAC5C,SAAO,GAAG,cAAc,MAAM,YAAY;AAC5C;AAOO,SAAS,wBACd,OACA,KACA,MAAY,oBAAI,KAAK,GACb;AACR,MAAI,SAAS,CAAC,OAAO,gBAAgB,OAAO,GAAG,EAAG,QAAO,UAAU,mBAAmB,KAAK,CAAC;AAC5F,SAAO,gBAAgB,OAAO,GAAG;AACnC;AAMO,SAAS,0BACd,WACA,YACQ;AACR,SAAO,gBAAgB,aAAa,QAAW,cAAc,QAAW,KAAK;AAC/E;","names":[]}
@@ -0,0 +1,34 @@
1
+ // src/format/wcag-contrast.ts
2
+ function isValidRgbColor(value) {
3
+ if (value == null || typeof value !== "object") return false;
4
+ const { r, g, b } = value;
5
+ return typeof r === "number" && typeof g === "number" && typeof b === "number" && Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b) && r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255;
6
+ }
7
+ function rgbToString(color) {
8
+ return `rgb(${Math.floor(color.r)}, ${Math.floor(color.g)}, ${Math.floor(color.b)})`;
9
+ }
10
+ function relativeLuminance(color) {
11
+ const [rs, gs, bs] = [color.r / 255, color.g / 255, color.b / 255];
12
+ const r = rs <= 0.04045 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4);
13
+ const g = gs <= 0.04045 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4);
14
+ const b = bs <= 0.04045 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4);
15
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
16
+ }
17
+ function contrastRatio(color1, color2) {
18
+ const l1 = relativeLuminance(color1);
19
+ const l2 = relativeLuminance(color2);
20
+ const lighter = Math.max(l1, l2);
21
+ const darker = Math.min(l1, l2);
22
+ return (lighter + 0.05) / (darker + 0.05);
23
+ }
24
+ function meetsContrastAA(foreground, background) {
25
+ return contrastRatio(foreground, background) >= 4.5;
26
+ }
27
+ export {
28
+ contrastRatio,
29
+ isValidRgbColor,
30
+ meetsContrastAA,
31
+ relativeLuminance,
32
+ rgbToString
33
+ };
34
+ //# sourceMappingURL=wcag-contrast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/format/wcag-contrast.ts"],"sourcesContent":["/**\n * WCAG 2.2 contrast ratio utilities for validating publication theme colors.\n * Implements the relative luminance and contrast ratio formulas from\n * https://www.w3.org/TR/WCAG22/#dfn-relative-luminance\n */\n\nexport interface RgbColor {\n r: number;\n g: number;\n b: number;\n}\n\n/**\n * Return true when the value is an RgbColor with all channels as integers in [0, 255].\n * Use this before passing untrusted data to the luminance / contrast functions.\n */\nexport function isValidRgbColor(value: unknown): value is RgbColor {\n if (value == null || typeof value !== 'object') return false;\n const { r, g, b } = value as Record<string, unknown>;\n return (\n typeof r === 'number' &&\n typeof g === 'number' &&\n typeof b === 'number' &&\n Number.isFinite(r) &&\n Number.isFinite(g) &&\n Number.isFinite(b) &&\n r >= 0 &&\n r <= 255 &&\n g >= 0 &&\n g <= 255 &&\n b >= 0 &&\n b <= 255\n );\n}\n\n/**\n * Serialize an RgbColor to a CSS `rgb(...)` string.\n * Channels are floored to integers to prevent sub-pixel drift.\n */\nexport function rgbToString(color: RgbColor): string {\n return `rgb(${Math.floor(color.r)}, ${Math.floor(color.g)}, ${Math.floor(color.b)})`;\n}\n\n/**\n * Calculate relative luminance of an sRGB color per WCAG 2.2.\n * Input channels must be numbers in [0, 255].\n */\nexport function relativeLuminance(color: RgbColor): number {\n const [rs, gs, bs] = [color.r / 255, color.g / 255, color.b / 255];\n\n const r = rs <= 0.04045 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4);\n const g = gs <= 0.04045 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4);\n const b = bs <= 0.04045 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4);\n\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/**\n * Calculate WCAG 2.2 contrast ratio between two colors.\n * Returns a value between 1 (identical) and 21 (black/white).\n */\nexport function contrastRatio(color1: RgbColor, color2: RgbColor): number {\n const l1 = relativeLuminance(color1);\n const l2 = relativeLuminance(color2);\n const lighter = Math.max(l1, l2);\n const darker = Math.min(l1, l2);\n return (lighter + 0.05) / (darker + 0.05);\n}\n\n/**\n * Check if two colors meet WCAG 2.2 AA contrast requirement (4.5:1 for normal text).\n */\nexport function meetsContrastAA(foreground: RgbColor, background: RgbColor): boolean {\n return contrastRatio(foreground, background) >= 4.5;\n}\n"],"mappings":";AAgBO,SAAS,gBAAgB,OAAmC;AACjE,MAAI,SAAS,QAAQ,OAAO,UAAU,SAAU,QAAO;AACvD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI;AACpB,SACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,OAAO,MAAM,YACb,OAAO,SAAS,CAAC,KACjB,OAAO,SAAS,CAAC,KACjB,OAAO,SAAS,CAAC,KACjB,KAAK,KACL,KAAK,OACL,KAAK,KACL,KAAK,OACL,KAAK,KACL,KAAK;AAET;AAMO,SAAS,YAAY,OAAyB;AACnD,SAAO,OAAO,KAAK,MAAM,MAAM,CAAC,CAAC,KAAK,KAAK,MAAM,MAAM,CAAC,CAAC,KAAK,KAAK,MAAM,MAAM,CAAC,CAAC;AACnF;AAMO,SAAS,kBAAkB,OAAyB;AACzD,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG;AAEjE,QAAM,IAAI,MAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,KAAK,SAAS,OAAO,GAAG;AACzE,QAAM,IAAI,MAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,KAAK,SAAS,OAAO,GAAG;AACzE,QAAM,IAAI,MAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,KAAK,SAAS,OAAO,GAAG;AAEzE,SAAO,SAAS,IAAI,SAAS,IAAI,SAAS;AAC5C;AAMO,SAAS,cAAc,QAAkB,QAA0B;AACxE,QAAM,KAAK,kBAAkB,MAAM;AACnC,QAAM,KAAK,kBAAkB,MAAM;AACnC,QAAM,UAAU,KAAK,IAAI,IAAI,EAAE;AAC/B,QAAM,SAAS,KAAK,IAAI,IAAI,EAAE;AAC9B,UAAQ,UAAU,SAAS,SAAS;AACtC;AAKO,SAAS,gBAAgB,YAAsB,YAA+B;AACnF,SAAO,cAAc,YAAY,UAAU,KAAK;AAClD;","names":[]}
@@ -0,0 +1,22 @@
1
+ // src/import/index.ts
2
+ import {
3
+ parsePresentationDuration,
4
+ durationFromMinutes,
5
+ parseIntendedAudiences,
6
+ stripHtmlToText,
7
+ normalizePresentationRole,
8
+ normalizePresentationMode,
9
+ presentationCsvRowToRecord,
10
+ presentationDeliveryCsvRowToRecord
11
+ } from "./presentation-csv.js";
12
+ export {
13
+ durationFromMinutes,
14
+ normalizePresentationMode,
15
+ normalizePresentationRole,
16
+ parseIntendedAudiences,
17
+ parsePresentationDuration,
18
+ presentationCsvRowToRecord,
19
+ presentationDeliveryCsvRowToRecord,
20
+ stripHtmlToText
21
+ };
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/import/index.ts"],"sourcesContent":["export {\n parsePresentationDuration,\n durationFromMinutes,\n parseIntendedAudiences,\n stripHtmlToText,\n normalizePresentationRole,\n normalizePresentationMode,\n presentationCsvRowToRecord,\n presentationDeliveryCsvRowToRecord,\n type CsvRow,\n type ParsedPresentation,\n type ParsedDelivery,\n} from './presentation-csv.js';\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;","names":[]}