@singi-labs/sifa-sdk 0.19.88 → 0.19.90

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 (58) hide show
  1. package/dist/{follow-profile-CcMgQDsh.d.ts → follow-profile-CVcA2w1v.d.ts} +1 -1
  2. package/dist/{follow-profile-D3zDoDll.d.cts → follow-profile-CgLLLQtJ.d.cts} +1 -1
  3. package/dist/{index-LxGL5I-J.d.ts → index-BEAuoL6l.d.ts} +1 -1
  4. package/dist/{index-C2n-GjH2.d.cts → index-DU0pYHch.d.cts} +1 -1
  5. package/dist/index.cjs +19 -1
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +9 -9
  8. package/dist/index.d.ts +9 -9
  9. package/dist/index.js +6 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/jsonld/index.d.cts +2 -2
  12. package/dist/jsonld/index.d.ts +2 -2
  13. package/dist/{network-map-k2QxGpGf.d.ts → network-map-Bx5ohCEV.d.ts} +4 -4
  14. package/dist/{network-map-CFZx_xXE.d.cts → network-map-D39CQj9J.d.cts} +4 -4
  15. package/dist/{profile-summary-CwJD0-wo.d.cts → profile-summary-D56VII1x.d.cts} +1 -1
  16. package/dist/{profile-summary-2pFtukEK.d.ts → profile-summary-DPNTzeDv.d.ts} +1 -1
  17. package/dist/query/fetchers/index.d.cts +8 -8
  18. package/dist/query/fetchers/index.d.ts +8 -8
  19. package/dist/query/hooks/index.d.cts +6 -6
  20. package/dist/query/hooks/index.d.ts +6 -6
  21. package/dist/query/index.d.cts +11 -11
  22. package/dist/query/index.d.ts +11 -11
  23. package/dist/resume/export-contact.js +57 -0
  24. package/dist/resume/export-contact.js.map +1 -0
  25. package/dist/resume/funder-cv.js +3 -2
  26. package/dist/resume/funder-cv.js.map +1 -1
  27. package/dist/resume/index.cjs +63 -3
  28. package/dist/resume/index.cjs.map +1 -1
  29. package/dist/resume/index.d.cts +90 -5
  30. package/dist/resume/index.d.ts +90 -5
  31. package/dist/resume/index.js +12 -0
  32. package/dist/resume/index.js.map +1 -1
  33. package/dist/resume/json-resume.js +8 -1
  34. package/dist/resume/json-resume.js.map +1 -1
  35. package/dist/schemas/index.cjs.map +1 -1
  36. package/dist/schemas/index.d.cts +2 -2
  37. package/dist/schemas/index.d.ts +2 -2
  38. package/dist/taxonomy/index.js +6 -2
  39. package/dist/taxonomy/index.js.map +1 -1
  40. package/dist/taxonomy/verification-providers.js +19 -1
  41. package/dist/taxonomy/verification-providers.js.map +1 -1
  42. package/dist/tokens/colors.js +6 -1
  43. package/dist/tokens/colors.js.map +1 -1
  44. package/dist/tokens/index.cjs +5 -0
  45. package/dist/tokens/index.cjs.map +1 -1
  46. package/dist/tokens/index.d.cts +17 -1
  47. package/dist/tokens/index.d.ts +17 -1
  48. package/dist/tokens/index.js +3 -2
  49. package/dist/tokens/index.js.map +1 -1
  50. package/dist/{types-CICITVJa.d.ts → types-BqxbmLY6.d.ts} +2 -2
  51. package/dist/{types-DkrZo5eF.d.cts → types-Dd7UMXIz.d.cts} +2 -2
  52. package/dist/{use-user-settings-Azxf51V9.d.cts → use-user-settings-CBH1CeDP.d.cts} +4 -4
  53. package/dist/{use-user-settings-DQNz9PYA.d.ts → use-user-settings-CFhaGcAq.d.ts} +4 -4
  54. package/dist/{user-settings-BDZ9i-Pk.d.cts → user-settings-ChPm0BFN.d.cts} +4 -4
  55. package/dist/{user-settings-DvqRXhdC.d.ts → user-settings-D1zKOWa2.d.ts} +4 -4
  56. package/dist/{verification-providers-BPdY3KyH.d.cts → verification-providers-DeyGqaoC.d.cts} +24 -4
  57. package/dist/{verification-providers-BPdY3KyH.d.ts → verification-providers-DeyGqaoC.d.ts} +24 -4
  58. package/package.json +1 -1
@@ -1,19 +1,19 @@
1
- import { a0 as SifaApiConfig, a1 as ApiFetchOptions, a2 as CreateResult, a3 as WriteResult, c as AdultContentLabel } from '../types-CICITVJa.js';
2
- export { a4 as ApiError, a5 as FilterOptions, a6 as ProfileSearchResult, a7 as ProfileSearchSuggestion, S as SearchFilters, a8 as SearchResponse, a9 as SkillSearchResult, aa as apiFetch, ab as apiFetchOrNull, ac as apiWrite, ad as apiWriteCreate, ae as fetchSearchFilters, af as fetchSearchProfiles, ag as fetchSkillSuggestions, ah as searchSkills } from '../types-CICITVJa.js';
3
- export { A as AddFeatureAllowlistVariables, a as AttestationRequest, b as AttestationRequestSchema, c as AttestationStatus, C as CreateOrgAttestationInput, d as CreateOrgAttestationResult, e as CreateReactionVariables, f as CreateRecordVariables, D as DeleteReactionVariables, g as DeleteRecordVariables, F as FetchOrgAttestationOptions, h as FetchVerificationLogOptions, i as FollowVariables, O as OrgAttestation, j as OrgAttestationSchema, P as PositionSkillLinkVariables, R as RemoveFeatureAllowlistVariables, k as RequestEmploymentConfirmationInput, l as RequestEmploymentConfirmationResult, S as SifaProvider, m as SifaProviderProps, U as UnfollowVariables, n as UpdateEducationVariables, o as UpdateExternalAccountVariables, p as UpdatePositionVariables, q as UpdateProfileLocationVariables, r as UpdateRecordVariables, s as UpdateSkillSubCategoriesVariables, t as UpdateSkillVariables, u as UseEntitySearchOptions, v as UseMyGithubPullRequestsParams, V as VERIFICATION_EVENT_KINDS, w as VerificationEventKind, x as VerificationLog, y as VerificationLogEntry, z as VerificationLogEntrySchema, B as VerificationLogSchema, E as createOrgAttestation, G as fetchAttestationRequests, H as fetchOrgAttestations, I as fetchVerificationLog, J as offboardOrgAttestation, K as rejectAttestationRequest, L as requestEmploymentConfirmation, M as revokeOrgAttestation, N as useAccounts, Q as useActivityFeed, T as useActivityPreferences, W as useActivityTeaser, X as useAddFeatureAllowlist, Y as useAddOrgNotificationEmail, Z as useAdminReviewQueues, _ as useAppAccountCheck, $ as useAppsRegistry, a0 as useAtFundLink, a1 as useAttestationRequests, a2 as useBlueskySuggestions, a3 as useBulkHideProfileItems, a4 as useBulkHideStandardPublications, a5 as useBulkUnhideProfileItems, a6 as useBulkUnhideStandardPublications, a7 as useCanonicalSkillSearch, a8 as useCastRoadmapVote, a9 as useCreateConfirmation, aa as useCreateEducation, ab as useCreateEndorsement, ac as useCreateExternalAccount, ad as useCreateOrgAttestation, ae as useCreatePosition, af as useCreateProfileLocation, ag as useCreateReaction, ah as useCreateRecord, ai as useCreateSkill, aj as useDebouncedValue, ak as useDeleteAccount, al as useDeleteAvatarOverride, am as useDeleteEducation, an as useDeleteExternalAccount, ao as useDeleteNamePronunciationAudio, ap as useDeletePosition, aq as useDeleteProfileLocation, ar as useDeleteReaction, as as useDeleteRecord, at as useDeleteRepoRecords, au as useDeleteSkill, av as useDismissConfirmation, aw as useDismissUnlinkedPosition, ax as useEndorsementCount, ay as useEntitySearch, az as useExternalAccounts, aA as useFeatureAllowlist, aB as useFeaturedProfile, aC as useFollow, aD as useFollowers, aE as useFollowing, aF as useFollowingFeed, aG as useFollowingFeedVersion, aH as useFollowingList, aI as useGetProfileView, aJ as useGivenConfirmations, aK as useHeatmapData, aL as useHiddenActivityItems, aM as useHiddenApps, aN as useHideActivityItem, aO as useHideKeytraceClaim, aP as useHideOrcidPublication, aQ as useHideProfileItem, aR as useHideSifaPublication, aS as useHideStandardPublication, aT as useImportSearchEntities, aU as useInboxCounts, aV as useLinkSkillToPosition, aW as useMintEntityDomain, aX as useMutuals, aY as useMyGithubPullRequests, aZ as useMyRoadmapVotes, a_ as useNetworkStreamCount, a$ as useOffboardOrgAttestation, b0 as useOrgAttestations, b1 as useOrgClaim, b2 as useOrgDomainChallenge, b3 as useOrgDomainVerify, b4 as useOrgProfile, b5 as usePendingConfirmations, b6 as useProfile, b7 as useProfileCompleteness, b8 as useReactionStatus, b9 as useRefreshOrcidPublications, ba as useRefreshPds, bb as useRejectAttestationRequest, bc as useRemoveFeatureAllowlist, bd as useRemoveOrgNotificationEmail, be as useRepoInventory, bf as useRequestEmploymentConfirmation, bg as useResetProfile, bh as useResolveEntityDomain, bi as useRetractRoadmapVote, bj as useRevealMarqueDomain, bk as useRevokeConfirmation, bl as useRevokeOrgAttestation, bm as useRoadmapVotes, bn as useRpgClaimable, bo as useRpgStatus, bp as useSearchFilters, bq as useSearchProfiles, br as useSelectEntity, bs as useSetExternalAccountPrimary, bt as useSetPositionPrimary, bu as useSetSectionPrimary, bv as useSifaConfig, bw as useSimilarProfiles, bx as useSiteActivityPreset, by as useSkillSuggestions, bz as useStats, bA as useSuggestionCount, bB as useSuggestions, bC as useUnfollow, bD as useUnhideActivityItem, bE as useUnhideKeytraceClaim, bF as useUnhideOrcidPublication, bG as useUnhideProfileItem, bH as useUnhideSifaPublication, bI as useUnhideStandardPublication, bJ as useUnlinkSkillFromPosition, bK as useUnlinkedPositions, bL as useUnrevealMarqueDomain, bM as useUnsetExternalAccountPrimary, bN as useUnsetPositionPrimary, bO as useUnsetSectionPrimary, bP as useUpdateActivityPreferences, bQ as useUpdateEducation, bR as useUpdateExternalAccount, bS as useUpdateOrgProfile, bT as useUpdatePosition, bU as useUpdateProfileLocation, bV as useUpdateProfileOverride, bW as useUpdateProfileSelf, bX as useUpdateRecord, bY as useUpdateSkill, bZ as useUpdateSkillSubCategories, b_ as useUpdateUserSettings, b$ as useUploadAvatar, c0 as useUploadNamePronunciationAudio, c1 as useUserSettings, c2 as useVerificationLog, c3 as useVerifyExternalAccount, c4 as useWipePreview } from '../use-user-settings-DQNz9PYA.js';
4
- import { R as ReceivedEndorsementsPage, a as ReciprocityCandidate, D as DismissEndorsementInput, P as PendingEndorsementsPage } from '../network-map-k2QxGpGf.js';
5
- export { F as FetchNetworkStreamCountOptions, b as FetchPendingEndorsementsOptions, N as NetworkMapEdge, c as NetworkMapGenerationJob, d as NetworkMapGraphData, e as NetworkMapNode, f as NetworkMapPendingJob, g as NetworkMapResponse, h as PendingEndorsement, i as ReceivedEndorsement, j as ReciprocitySkill, S as SpeakerCard, k as SpeakerDirectoryFilters, l as SpeakerGroup, m as SpeakersResponse, T as TalkEvent, n as TalkRow, o as TalkSpeaker, p as TalksResponse, q as checkNetworkMapJobStatus, r as createEducation, s as createPosition, t as createRecord, u as deleteEducation, v as deletePosition, w as deleteRecord, x as dismissEndorsement, y as fetchAtFundLink, z as fetchEndorsementCount, A as fetchEntitySearch, B as fetchGetProfileView, C as fetchNetworkMap, E as fetchNetworkStreamCount, G as fetchPendingEndorsements, H as fetchProfile, I as fetchProfileSummary, J as fetchReceivedEndorsements, K as fetchReciprocityCandidate, L as fetchSpeakers, M as fetchTalks, O as hideKeytraceClaim, Q as importSearchEntities, U as initiateNetworkMapGeneration, V as isNetworkMapResponse, W as linkSkillToPosition, X as mintEntityDomain, Y as resolveEntityDomain, Z as revealMarqueDomain, _ as selectEntity, $ as setPositionPrimary, a0 as unhideKeytraceClaim, a1 as unlinkSkillFromPosition, a2 as unrevealMarqueDomain, a3 as unsetPositionPrimary, a4 as updateEducation, a5 as updatePosition, a6 as updateRecord } from '../network-map-k2QxGpGf.js';
6
- import { s as sifaQueryKeys, C as ConfirmEndorsementInput, a as ConfirmEndorsementResult } from '../user-settings-DvqRXhdC.js';
7
- export { A as AccountCheckResult, b as AccountSummary, c as ActivityFeedResponse, d as ActivityItem, e as ActivityItemLinkHealth, f as ActivityPreferences, g as ActivityTeaserResponse, h as AdminReviewQueues, i as AppRegistryEntry, B as BulkHideProfileItemInput, j as CastRoadmapVoteResult, k as CheckAppAccountOptions, l as ConfirmationInput, m as ConfirmationSubjectInput, n as CreateExternalAccountResult, D as DeleteAccountResult, o as DismissUnlinkedPositionInput, E as EndorsementInput, p as ExternalAccountInput, F as FeatureAllowlistResponse, q as FeaturedProfile, r as FetchAccountsOptions, t as FetchActivityFeedOptions, u as FetchActivityPreferencesOptions, v as FetchActivityTeaserOptions, w as FetchConfirmationsOptions, x as FetchFollowListOptions, y as FetchFollowProfilePageOptions, z as FetchFollowingFeedOptions, G as FetchHiddenActivityItemsOptions, H as FetchHiddenAppsOptions, I as FetchInboxCountsOptions, J as FetchMyGithubPullRequestsOptions, K as FetchMyRoadmapVotesOptions, L as FetchProfileCompletenessOptions, M as FetchReactionStatusOptions, N as FetchRpgClaimableOptions, O as FetchRpgStatusOptions, P as FetchSuggestionsOptions, Q as FetchUnlinkedPositionsOptions, R as FetchUserSettingsOptions, S as FollowListPage, T as FollowProfile, U as FollowProfilePageResponse, V as FollowUserResult, W as FollowingFeedApp, X as FollowingFeedBuild, Y as FollowingFeedEmptyReason, Z as FollowingFeedResponse, _ as FollowingFeedVersion, $ as FollowingResponse, a0 as GetAdminReviewQueuesOptions, a1 as GithubPullRequest, a2 as GivenConfirmation, a3 as HIDDEN_ITEM_SOURCES, a4 as HIDDEN_ITEM_TYPES, a5 as HeatmapDay, a6 as HeatmapResponse, a7 as HiddenActivityItem, a8 as HiddenApp, a9 as HiddenItemSource, aa as HiddenItemType, ab as HideActivityItemInput, ac as HideProfileItemInput, ad as InboxCounts, ae as ListFeatureAllowlistOptions, af as MyGithubPullRequestsResponse, ag as OrgClaimBinding, ah as OrgClaimResult, ai as OrgDomainChallengeResult, aj as OrgDomainVerifyResult, ak as OrgNotificationEmailAddResult, al as OrgNotificationEmailRemoveResult, am as OrgProfileEcho, an as OrgProfileUpdateResult, ao as PROFILE_COMPLETENESS_SIGNALS, ap as PdsWipeOutcome, aq as PendingConfirmation, ar as PendingConfirmationsPage, as as PrimarySection, at as ProfileCompleteness, au as ProfileCompletenessSignal, av as ProfileIndustryInput, aw as ProfileLocationAddress, ax as ProfileLocationInput, ay as ProfileSelfLocation, az as QUOTED_POSTS_BATCH_MAX, aA as QuotedPostAuthor, aB as QuotedPostImage, aC as QuotedPostResult, aD as QuotedPostView, aE as ReactionError, aF as ReactionResult, aG as ReactionStatus, aH as RefreshOrcidPublicationsResult, aI as RefreshPdsResult, aJ as RepoDeleteInput, aK as ResetProfileResult, aL as ResolveQuotedPostsOptions, aM as RoadmapVoteError, aN as RoadmapVoteResult, aO as RoadmapVoter, aP as RoadmapVotesResponse, aQ as RpgClaimableResponse, aR as RpgClaimableResponseSchema, aS as RpgItemStatus, aT as RpgItemStatusSchema, aU as RpgStatusResponse, aV as RpgStatusResponseSchema, aW as SifaQueryKey, aX as SimilarProfile, aY as SiteActivityPresetResponse, aZ as StatsResponse, a_ as SubCategoryBulkResult, a$ as SuggestionProfile, b0 as SuggestionsResponse, b1 as UnhideActivityItemInput, b2 as UnlinkedPosition, b3 as UnlinkedPositionsResult, b4 as UpdateActivityPreferencesInput, b5 as UpdateProfileOverrideInput, b6 as UpdateProfileSelfInput, b7 as UploadAvatarResult, b8 as UploadPronunciationAudioResult, b9 as UserSettings, ba as UserSettingsPatch, bb as UserSettingsSchema, bc as VerifyExternalAccountResult, bd as WipePreview, be as addFeatureAllowlist, bf as addOrgNotificationEmail, bg as bulkHideProfileItems, bh as bulkHideStandardPublications, bi as bulkUnhideProfileItems, bj as bulkUnhideStandardPublications, bk as castRoadmapVote, bl as checkAppAccount, bm as confirmEndorsement, bn as createConfirmation, bo as createEndorsement, bp as createExternalAccount, bq as createProfileLocation, br as createReaction, bs as createSkill, bt as deleteAccount, bu as deleteAvatarOverride, bv as deleteExternalAccount, bw as deleteNamePronunciationAudio, bx as deleteProfileLocation, by as deleteReaction, bz as deleteRepoRecords, bA as deleteSkill, bB as dismissConfirmation, bC as dismissUnlinkedPosition, bD as fetchAccounts, bE as fetchActivityFeed, bF as fetchActivityPreferences, bG as fetchActivityTeaser, bH as fetchAppsRegistry, bI as fetchExternalAccounts, bJ as fetchFeaturedProfile, bK as fetchFollowing, bL as fetchFollowingFeed, bM as fetchFollowingFeedVersion, bN as fetchGivenConfirmations, bO as fetchHeatmapData, bP as fetchHiddenActivityItems, bQ as fetchHiddenApps, bR as fetchInboxCounts, bS as fetchMyGithubPullRequests, bT as fetchMyRoadmapVotes, bU as fetchPendingConfirmations, bV as fetchProfileCompleteness, bW as fetchReactionStatus, bX as fetchRepoInventory, bY as fetchRoadmapVotes, bZ as fetchRpgClaimable, b_ as fetchRpgStatus, b$ as fetchSimilarProfiles, c0 as fetchSiteActivityPreset, c1 as fetchStats, c2 as fetchSuggestionCount, c3 as fetchSuggestions, c4 as fetchUnlinkedPositions, c5 as fetchUserSettings, c6 as fetchViewerPositionConfirmations, c7 as fetchWipePreview, c8 as followUser, c9 as getAdminReviewQueues, ca as getBlueskySuggestions, cb as getFollowers, cc as getFollowing, cd as getMutuals, ce as hideActivityItem, cf as hideOrcidPublication, cg as hideProfileItem, ch as hideSifaPublication, ci as hideStandardPublication, cj as isNewerFollowingFeed, ck as listFeatureAllowlist, cl as refreshOrcidPublications, cm as refreshPds, cn as removeFeatureAllowlist, co as removeOrgNotificationEmail, cp as repoExportUrl, cq as requestOrgDomainChallenge, cr as resetProfile, cs as resolveQuotedPosts, ct as retractRoadmapVote, cu as revokeConfirmation, cv as setExternalAccountPrimary, cw as setSectionPrimary, cx as submitOrgClaim, cy as switchAccount, cz as unfollowUser, cA as unhideActivityItem, cB as unhideOrcidPublication, cC as unhideProfileItem, cD as unhideSifaPublication, cE as unhideStandardPublication, cF as unsetExternalAccountPrimary, cG as unsetSectionPrimary, cH as updateActivityPreferences, cI as updateExternalAccount, cJ as updateOrgProfile, cK as updateProfileLocation, cL as updateProfileOverride, cM as updateProfileSelf, cN as updateSkill, cO as updateSkillSubCategories, cP as updateUserSettings, cQ as uploadAvatar, cR as uploadNamePronunciationAudio, cS as verifyExternalAccount, cT as verifyOrgDomain } from '../user-settings-DvqRXhdC.js';
8
- import { C as CoSpeaker, m as ActorCard } from '../index-LxGL5I-J.js';
1
+ import { a0 as SifaApiConfig, a1 as ApiFetchOptions, a2 as CreateResult, a3 as WriteResult, c as AdultContentLabel } from '../types-BqxbmLY6.js';
2
+ export { a4 as ApiError, a5 as FilterOptions, a6 as ProfileSearchResult, a7 as ProfileSearchSuggestion, S as SearchFilters, a8 as SearchResponse, a9 as SkillSearchResult, aa as apiFetch, ab as apiFetchOrNull, ac as apiWrite, ad as apiWriteCreate, ae as fetchSearchFilters, af as fetchSearchProfiles, ag as fetchSkillSuggestions, ah as searchSkills } from '../types-BqxbmLY6.js';
3
+ export { A as AddFeatureAllowlistVariables, a as AttestationRequest, b as AttestationRequestSchema, c as AttestationStatus, C as CreateOrgAttestationInput, d as CreateOrgAttestationResult, e as CreateReactionVariables, f as CreateRecordVariables, D as DeleteReactionVariables, g as DeleteRecordVariables, F as FetchOrgAttestationOptions, h as FetchVerificationLogOptions, i as FollowVariables, O as OrgAttestation, j as OrgAttestationSchema, P as PositionSkillLinkVariables, R as RemoveFeatureAllowlistVariables, k as RequestEmploymentConfirmationInput, l as RequestEmploymentConfirmationResult, S as SifaProvider, m as SifaProviderProps, U as UnfollowVariables, n as UpdateEducationVariables, o as UpdateExternalAccountVariables, p as UpdatePositionVariables, q as UpdateProfileLocationVariables, r as UpdateRecordVariables, s as UpdateSkillSubCategoriesVariables, t as UpdateSkillVariables, u as UseEntitySearchOptions, v as UseMyGithubPullRequestsParams, V as VERIFICATION_EVENT_KINDS, w as VerificationEventKind, x as VerificationLog, y as VerificationLogEntry, z as VerificationLogEntrySchema, B as VerificationLogSchema, E as createOrgAttestation, G as fetchAttestationRequests, H as fetchOrgAttestations, I as fetchVerificationLog, J as offboardOrgAttestation, K as rejectAttestationRequest, L as requestEmploymentConfirmation, M as revokeOrgAttestation, N as useAccounts, Q as useActivityFeed, T as useActivityPreferences, W as useActivityTeaser, X as useAddFeatureAllowlist, Y as useAddOrgNotificationEmail, Z as useAdminReviewQueues, _ as useAppAccountCheck, $ as useAppsRegistry, a0 as useAtFundLink, a1 as useAttestationRequests, a2 as useBlueskySuggestions, a3 as useBulkHideProfileItems, a4 as useBulkHideStandardPublications, a5 as useBulkUnhideProfileItems, a6 as useBulkUnhideStandardPublications, a7 as useCanonicalSkillSearch, a8 as useCastRoadmapVote, a9 as useCreateConfirmation, aa as useCreateEducation, ab as useCreateEndorsement, ac as useCreateExternalAccount, ad as useCreateOrgAttestation, ae as useCreatePosition, af as useCreateProfileLocation, ag as useCreateReaction, ah as useCreateRecord, ai as useCreateSkill, aj as useDebouncedValue, ak as useDeleteAccount, al as useDeleteAvatarOverride, am as useDeleteEducation, an as useDeleteExternalAccount, ao as useDeleteNamePronunciationAudio, ap as useDeletePosition, aq as useDeleteProfileLocation, ar as useDeleteReaction, as as useDeleteRecord, at as useDeleteRepoRecords, au as useDeleteSkill, av as useDismissConfirmation, aw as useDismissUnlinkedPosition, ax as useEndorsementCount, ay as useEntitySearch, az as useExternalAccounts, aA as useFeatureAllowlist, aB as useFeaturedProfile, aC as useFollow, aD as useFollowers, aE as useFollowing, aF as useFollowingFeed, aG as useFollowingFeedVersion, aH as useFollowingList, aI as useGetProfileView, aJ as useGivenConfirmations, aK as useHeatmapData, aL as useHiddenActivityItems, aM as useHiddenApps, aN as useHideActivityItem, aO as useHideKeytraceClaim, aP as useHideOrcidPublication, aQ as useHideProfileItem, aR as useHideSifaPublication, aS as useHideStandardPublication, aT as useImportSearchEntities, aU as useInboxCounts, aV as useLinkSkillToPosition, aW as useMintEntityDomain, aX as useMutuals, aY as useMyGithubPullRequests, aZ as useMyRoadmapVotes, a_ as useNetworkStreamCount, a$ as useOffboardOrgAttestation, b0 as useOrgAttestations, b1 as useOrgClaim, b2 as useOrgDomainChallenge, b3 as useOrgDomainVerify, b4 as useOrgProfile, b5 as usePendingConfirmations, b6 as useProfile, b7 as useProfileCompleteness, b8 as useReactionStatus, b9 as useRefreshOrcidPublications, ba as useRefreshPds, bb as useRejectAttestationRequest, bc as useRemoveFeatureAllowlist, bd as useRemoveOrgNotificationEmail, be as useRepoInventory, bf as useRequestEmploymentConfirmation, bg as useResetProfile, bh as useResolveEntityDomain, bi as useRetractRoadmapVote, bj as useRevealMarqueDomain, bk as useRevokeConfirmation, bl as useRevokeOrgAttestation, bm as useRoadmapVotes, bn as useRpgClaimable, bo as useRpgStatus, bp as useSearchFilters, bq as useSearchProfiles, br as useSelectEntity, bs as useSetExternalAccountPrimary, bt as useSetPositionPrimary, bu as useSetSectionPrimary, bv as useSifaConfig, bw as useSimilarProfiles, bx as useSiteActivityPreset, by as useSkillSuggestions, bz as useStats, bA as useSuggestionCount, bB as useSuggestions, bC as useUnfollow, bD as useUnhideActivityItem, bE as useUnhideKeytraceClaim, bF as useUnhideOrcidPublication, bG as useUnhideProfileItem, bH as useUnhideSifaPublication, bI as useUnhideStandardPublication, bJ as useUnlinkSkillFromPosition, bK as useUnlinkedPositions, bL as useUnrevealMarqueDomain, bM as useUnsetExternalAccountPrimary, bN as useUnsetPositionPrimary, bO as useUnsetSectionPrimary, bP as useUpdateActivityPreferences, bQ as useUpdateEducation, bR as useUpdateExternalAccount, bS as useUpdateOrgProfile, bT as useUpdatePosition, bU as useUpdateProfileLocation, bV as useUpdateProfileOverride, bW as useUpdateProfileSelf, bX as useUpdateRecord, bY as useUpdateSkill, bZ as useUpdateSkillSubCategories, b_ as useUpdateUserSettings, b$ as useUploadAvatar, c0 as useUploadNamePronunciationAudio, c1 as useUserSettings, c2 as useVerificationLog, c3 as useVerifyExternalAccount, c4 as useWipePreview } from '../use-user-settings-CFhaGcAq.js';
4
+ import { R as ReceivedEndorsementsPage, a as ReciprocityCandidate, D as DismissEndorsementInput, P as PendingEndorsementsPage } from '../network-map-Bx5ohCEV.js';
5
+ export { F as FetchNetworkStreamCountOptions, b as FetchPendingEndorsementsOptions, N as NetworkMapEdge, c as NetworkMapGenerationJob, d as NetworkMapGraphData, e as NetworkMapNode, f as NetworkMapPendingJob, g as NetworkMapResponse, h as PendingEndorsement, i as ReceivedEndorsement, j as ReciprocitySkill, S as SpeakerCard, k as SpeakerDirectoryFilters, l as SpeakerGroup, m as SpeakersResponse, T as TalkEvent, n as TalkRow, o as TalkSpeaker, p as TalksResponse, q as checkNetworkMapJobStatus, r as createEducation, s as createPosition, t as createRecord, u as deleteEducation, v as deletePosition, w as deleteRecord, x as dismissEndorsement, y as fetchAtFundLink, z as fetchEndorsementCount, A as fetchEntitySearch, B as fetchGetProfileView, C as fetchNetworkMap, E as fetchNetworkStreamCount, G as fetchPendingEndorsements, H as fetchProfile, I as fetchProfileSummary, J as fetchReceivedEndorsements, K as fetchReciprocityCandidate, L as fetchSpeakers, M as fetchTalks, O as hideKeytraceClaim, Q as importSearchEntities, U as initiateNetworkMapGeneration, V as isNetworkMapResponse, W as linkSkillToPosition, X as mintEntityDomain, Y as resolveEntityDomain, Z as revealMarqueDomain, _ as selectEntity, $ as setPositionPrimary, a0 as unhideKeytraceClaim, a1 as unlinkSkillFromPosition, a2 as unrevealMarqueDomain, a3 as unsetPositionPrimary, a4 as updateEducation, a5 as updatePosition, a6 as updateRecord } from '../network-map-Bx5ohCEV.js';
6
+ import { s as sifaQueryKeys, C as ConfirmEndorsementInput, a as ConfirmEndorsementResult } from '../user-settings-D1zKOWa2.js';
7
+ export { A as AccountCheckResult, b as AccountSummary, c as ActivityFeedResponse, d as ActivityItem, e as ActivityItemLinkHealth, f as ActivityPreferences, g as ActivityTeaserResponse, h as AdminReviewQueues, i as AppRegistryEntry, B as BulkHideProfileItemInput, j as CastRoadmapVoteResult, k as CheckAppAccountOptions, l as ConfirmationInput, m as ConfirmationSubjectInput, n as CreateExternalAccountResult, D as DeleteAccountResult, o as DismissUnlinkedPositionInput, E as EndorsementInput, p as ExternalAccountInput, F as FeatureAllowlistResponse, q as FeaturedProfile, r as FetchAccountsOptions, t as FetchActivityFeedOptions, u as FetchActivityPreferencesOptions, v as FetchActivityTeaserOptions, w as FetchConfirmationsOptions, x as FetchFollowListOptions, y as FetchFollowProfilePageOptions, z as FetchFollowingFeedOptions, G as FetchHiddenActivityItemsOptions, H as FetchHiddenAppsOptions, I as FetchInboxCountsOptions, J as FetchMyGithubPullRequestsOptions, K as FetchMyRoadmapVotesOptions, L as FetchProfileCompletenessOptions, M as FetchReactionStatusOptions, N as FetchRpgClaimableOptions, O as FetchRpgStatusOptions, P as FetchSuggestionsOptions, Q as FetchUnlinkedPositionsOptions, R as FetchUserSettingsOptions, S as FollowListPage, T as FollowProfile, U as FollowProfilePageResponse, V as FollowUserResult, W as FollowingFeedApp, X as FollowingFeedBuild, Y as FollowingFeedEmptyReason, Z as FollowingFeedResponse, _ as FollowingFeedVersion, $ as FollowingResponse, a0 as GetAdminReviewQueuesOptions, a1 as GithubPullRequest, a2 as GivenConfirmation, a3 as HIDDEN_ITEM_SOURCES, a4 as HIDDEN_ITEM_TYPES, a5 as HeatmapDay, a6 as HeatmapResponse, a7 as HiddenActivityItem, a8 as HiddenApp, a9 as HiddenItemSource, aa as HiddenItemType, ab as HideActivityItemInput, ac as HideProfileItemInput, ad as InboxCounts, ae as ListFeatureAllowlistOptions, af as MyGithubPullRequestsResponse, ag as OrgClaimBinding, ah as OrgClaimResult, ai as OrgDomainChallengeResult, aj as OrgDomainVerifyResult, ak as OrgNotificationEmailAddResult, al as OrgNotificationEmailRemoveResult, am as OrgProfileEcho, an as OrgProfileUpdateResult, ao as PROFILE_COMPLETENESS_SIGNALS, ap as PdsWipeOutcome, aq as PendingConfirmation, ar as PendingConfirmationsPage, as as PrimarySection, at as ProfileCompleteness, au as ProfileCompletenessSignal, av as ProfileIndustryInput, aw as ProfileLocationAddress, ax as ProfileLocationInput, ay as ProfileSelfLocation, az as QUOTED_POSTS_BATCH_MAX, aA as QuotedPostAuthor, aB as QuotedPostImage, aC as QuotedPostResult, aD as QuotedPostView, aE as ReactionError, aF as ReactionResult, aG as ReactionStatus, aH as RefreshOrcidPublicationsResult, aI as RefreshPdsResult, aJ as RepoDeleteInput, aK as ResetProfileResult, aL as ResolveQuotedPostsOptions, aM as RoadmapVoteError, aN as RoadmapVoteResult, aO as RoadmapVoter, aP as RoadmapVotesResponse, aQ as RpgClaimableResponse, aR as RpgClaimableResponseSchema, aS as RpgItemStatus, aT as RpgItemStatusSchema, aU as RpgStatusResponse, aV as RpgStatusResponseSchema, aW as SifaQueryKey, aX as SimilarProfile, aY as SiteActivityPresetResponse, aZ as StatsResponse, a_ as SubCategoryBulkResult, a$ as SuggestionProfile, b0 as SuggestionsResponse, b1 as UnhideActivityItemInput, b2 as UnlinkedPosition, b3 as UnlinkedPositionsResult, b4 as UpdateActivityPreferencesInput, b5 as UpdateProfileOverrideInput, b6 as UpdateProfileSelfInput, b7 as UploadAvatarResult, b8 as UploadPronunciationAudioResult, b9 as UserSettings, ba as UserSettingsPatch, bb as UserSettingsSchema, bc as VerifyExternalAccountResult, bd as WipePreview, be as addFeatureAllowlist, bf as addOrgNotificationEmail, bg as bulkHideProfileItems, bh as bulkHideStandardPublications, bi as bulkUnhideProfileItems, bj as bulkUnhideStandardPublications, bk as castRoadmapVote, bl as checkAppAccount, bm as confirmEndorsement, bn as createConfirmation, bo as createEndorsement, bp as createExternalAccount, bq as createProfileLocation, br as createReaction, bs as createSkill, bt as deleteAccount, bu as deleteAvatarOverride, bv as deleteExternalAccount, bw as deleteNamePronunciationAudio, bx as deleteProfileLocation, by as deleteReaction, bz as deleteRepoRecords, bA as deleteSkill, bB as dismissConfirmation, bC as dismissUnlinkedPosition, bD as fetchAccounts, bE as fetchActivityFeed, bF as fetchActivityPreferences, bG as fetchActivityTeaser, bH as fetchAppsRegistry, bI as fetchExternalAccounts, bJ as fetchFeaturedProfile, bK as fetchFollowing, bL as fetchFollowingFeed, bM as fetchFollowingFeedVersion, bN as fetchGivenConfirmations, bO as fetchHeatmapData, bP as fetchHiddenActivityItems, bQ as fetchHiddenApps, bR as fetchInboxCounts, bS as fetchMyGithubPullRequests, bT as fetchMyRoadmapVotes, bU as fetchPendingConfirmations, bV as fetchProfileCompleteness, bW as fetchReactionStatus, bX as fetchRepoInventory, bY as fetchRoadmapVotes, bZ as fetchRpgClaimable, b_ as fetchRpgStatus, b$ as fetchSimilarProfiles, c0 as fetchSiteActivityPreset, c1 as fetchStats, c2 as fetchSuggestionCount, c3 as fetchSuggestions, c4 as fetchUnlinkedPositions, c5 as fetchUserSettings, c6 as fetchViewerPositionConfirmations, c7 as fetchWipePreview, c8 as followUser, c9 as getAdminReviewQueues, ca as getBlueskySuggestions, cb as getFollowers, cc as getFollowing, cd as getMutuals, ce as hideActivityItem, cf as hideOrcidPublication, cg as hideProfileItem, ch as hideSifaPublication, ci as hideStandardPublication, cj as isNewerFollowingFeed, ck as listFeatureAllowlist, cl as refreshOrcidPublications, cm as refreshPds, cn as removeFeatureAllowlist, co as removeOrgNotificationEmail, cp as repoExportUrl, cq as requestOrgDomainChallenge, cr as resetProfile, cs as resolveQuotedPosts, ct as retractRoadmapVote, cu as revokeConfirmation, cv as setExternalAccountPrimary, cw as setSectionPrimary, cx as submitOrgClaim, cy as switchAccount, cz as unfollowUser, cA as unhideActivityItem, cB as unhideOrcidPublication, cC as unhideProfileItem, cD as unhideSifaPublication, cE as unhideStandardPublication, cF as unsetExternalAccountPrimary, cG as unsetSectionPrimary, cH as updateActivityPreferences, cI as updateExternalAccount, cJ as updateOrgProfile, cK as updateProfileLocation, cL as updateProfileOverride, cM as updateProfileSelf, cN as updateSkill, cO as updateSkillSubCategories, cP as updateUserSettings, cQ as uploadAvatar, cR as uploadNamePronunciationAudio, cS as verifyExternalAccount, cT as verifyOrgDomain } from '../user-settings-D1zKOWa2.js';
8
+ import { C as CoSpeaker, m as ActorCard } from '../index-BEAuoL6l.js';
9
9
  import * as _tanstack_react_query from '@tanstack/react-query';
10
10
  import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
11
- import '../verification-providers-BPdY3KyH.js';
11
+ import '../verification-providers-DeyGqaoC.js';
12
12
  import 'zod';
13
13
  import 'react';
14
- import '../follow-profile-CcMgQDsh.js';
14
+ import '../follow-profile-CVcA2w1v.js';
15
15
  import '../org-settings-BShvhyFp.js';
16
- import '../profile-summary-2pFtukEK.js';
16
+ import '../profile-summary-DPNTzeDv.js';
17
17
 
18
18
  /**
19
19
  * Resolve any atproto handle (or DID) to a profile card, for the co-speaker
@@ -0,0 +1,57 @@
1
+ // src/resume/export-contact.ts
2
+ import { z } from "zod";
3
+ var EXPORT_CONTACT_LIMITS = {
4
+ email: 254,
5
+ phone: 40,
6
+ address: 500
7
+ };
8
+ var exportContactSchema = z.strictObject({
9
+ email: z.string().max(EXPORT_CONTACT_LIMITS.email).optional(),
10
+ phone: z.string().max(EXPORT_CONTACT_LIMITS.phone).optional(),
11
+ address: z.string().max(EXPORT_CONTACT_LIMITS.address).optional()
12
+ });
13
+ function pickExportContact(draft) {
14
+ return normalizeExportContact({
15
+ email: draft.includeEmail ? draft.email : void 0,
16
+ phone: draft.includePhone ? draft.phone : void 0,
17
+ address: draft.includeAddress ? draft.address : void 0
18
+ });
19
+ }
20
+ function normalizeExportContact(contact) {
21
+ const email = cap(oneLine(contact.email), EXPORT_CONTACT_LIMITS.email);
22
+ const phone = cap(oneLine(contact.phone), EXPORT_CONTACT_LIMITS.phone);
23
+ const address = cap(lines(contact.address), EXPORT_CONTACT_LIMITS.address);
24
+ return {
25
+ ...email ? { email } : {},
26
+ ...phone ? { phone } : {},
27
+ ...address ? { address } : {}
28
+ };
29
+ }
30
+ function looksLikeEmail(value) {
31
+ const trimmed = value.trim();
32
+ if (/\s/.test(trimmed)) return false;
33
+ const parts = trimmed.split("@");
34
+ if (parts.length !== 2) return false;
35
+ const [local, domain] = parts;
36
+ const dot = domain.lastIndexOf(".");
37
+ return local.length > 0 && dot > 0 && dot < domain.length - 1;
38
+ }
39
+ function cap(value, max) {
40
+ return value === void 0 ? void 0 : value.slice(0, max).trimEnd();
41
+ }
42
+ function oneLine(value) {
43
+ const collapsed = value?.replace(/\s+/g, " ").trim();
44
+ return collapsed ? collapsed : void 0;
45
+ }
46
+ function lines(value) {
47
+ const kept = (value ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
48
+ return kept.length > 0 ? kept.join("\n") : void 0;
49
+ }
50
+ export {
51
+ EXPORT_CONTACT_LIMITS,
52
+ exportContactSchema,
53
+ looksLikeEmail,
54
+ normalizeExportContact,
55
+ pickExportContact
56
+ };
57
+ //# sourceMappingURL=export-contact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/resume/export-contact.ts"],"sourcesContent":["/**\n * Private contact details for exports: email, phone and postal address.\n *\n * Sifa never stores these and never writes them to the owner's PDS, where they\n * would be public. The owner types them on the export page, the browser keeps\n * them, and they go only into the files the owner downloads. This module holds\n * the shape, the limits and the pick rule, so the export page and the server\n * that builds a funder CV agree on them.\n */\n\nimport { z } from 'zod';\n\n/** Longest value accepted per field. 254 is the longest valid email address. */\nexport const EXPORT_CONTACT_LIMITS = {\n email: 254,\n phone: 40,\n address: 500,\n} as const;\n\n/** The contact details that go into an export. Absent keys are left out. */\nexport interface ExportContact {\n readonly email?: string;\n readonly phone?: string;\n /** Free text; lines are separated by `\\n`. */\n readonly address?: string;\n}\n\n/** What the owner has typed, plus which fields they chose to include. */\nexport interface ExportContactDraft {\n readonly email?: string;\n readonly phone?: string;\n readonly address?: string;\n readonly includeEmail?: boolean;\n readonly includePhone?: boolean;\n readonly includeAddress?: boolean;\n}\n\n/**\n * Validates a picked contact. Only lengths are checked: phone numbers and\n * addresses vary too much between countries, and a wrong-looking email is a\n * warning for the owner, not a reason to refuse the export.\n */\nexport const exportContactSchema = z.strictObject({\n email: z.string().max(EXPORT_CONTACT_LIMITS.email).optional(),\n phone: z.string().max(EXPORT_CONTACT_LIMITS.phone).optional(),\n address: z.string().max(EXPORT_CONTACT_LIMITS.address).optional(),\n});\n\n/**\n * The fields to put in an export: the included ones that are not blank, run\n * through {@link normalizeExportContact}.\n */\nexport function pickExportContact(draft: ExportContactDraft): ExportContact {\n return normalizeExportContact({\n email: draft.includeEmail ? draft.email : undefined,\n phone: draft.includePhone ? draft.phone : undefined,\n address: draft.includeAddress ? draft.address : undefined,\n });\n}\n\n/**\n * Drops blank fields and tidies the rest. Email and phone become one line with\n * single spaces. An address keeps its line breaks but loses blank lines and the\n * spaces around each line. Each field is cut at its {@link EXPORT_CONTACT_LIMITS}\n * length. The emitters call this too, so a caller that skips\n * {@link pickExportContact} and the schema gets the same bounded output.\n */\nexport function normalizeExportContact(contact: ExportContact): ExportContact {\n const email = cap(oneLine(contact.email), EXPORT_CONTACT_LIMITS.email);\n const phone = cap(oneLine(contact.phone), EXPORT_CONTACT_LIMITS.phone);\n const address = cap(lines(contact.address), EXPORT_CONTACT_LIMITS.address);\n return {\n ...(email ? { email } : {}),\n ...(phone ? { phone } : {}),\n ...(address ? { address } : {}),\n };\n}\n\n/** A loose shape check for a non-blocking \"check this address\" hint. */\nexport function looksLikeEmail(value: string): boolean {\n const trimmed = value.trim();\n if (/\\s/.test(trimmed)) return false;\n const parts = trimmed.split('@');\n if (parts.length !== 2) return false;\n const [local, domain] = parts as [string, string];\n const dot = domain.lastIndexOf('.');\n return local.length > 0 && dot > 0 && dot < domain.length - 1;\n}\n\nfunction cap(value: string | undefined, max: number): string | undefined {\n return value === undefined ? undefined : value.slice(0, max).trimEnd();\n}\n\nfunction oneLine(value: string | undefined): string | undefined {\n const collapsed = value?.replace(/\\s+/g, ' ').trim();\n return collapsed ? collapsed : undefined;\n}\n\nfunction lines(value: string | undefined): string | undefined {\n const kept = (value ?? '')\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean);\n return kept.length > 0 ? kept.join('\\n') : undefined;\n}\n"],"mappings":";AAUA,SAAS,SAAS;AAGX,IAAM,wBAAwB;AAAA,EACnC,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AACX;AAyBO,IAAM,sBAAsB,EAAE,aAAa;AAAA,EAChD,OAAO,EAAE,OAAO,EAAE,IAAI,sBAAsB,KAAK,EAAE,SAAS;AAAA,EAC5D,OAAO,EAAE,OAAO,EAAE,IAAI,sBAAsB,KAAK,EAAE,SAAS;AAAA,EAC5D,SAAS,EAAE,OAAO,EAAE,IAAI,sBAAsB,OAAO,EAAE,SAAS;AAClE,CAAC;AAMM,SAAS,kBAAkB,OAA0C;AAC1E,SAAO,uBAAuB;AAAA,IAC5B,OAAO,MAAM,eAAe,MAAM,QAAQ;AAAA,IAC1C,OAAO,MAAM,eAAe,MAAM,QAAQ;AAAA,IAC1C,SAAS,MAAM,iBAAiB,MAAM,UAAU;AAAA,EAClD,CAAC;AACH;AASO,SAAS,uBAAuB,SAAuC;AAC5E,QAAM,QAAQ,IAAI,QAAQ,QAAQ,KAAK,GAAG,sBAAsB,KAAK;AACrE,QAAM,QAAQ,IAAI,QAAQ,QAAQ,KAAK,GAAG,sBAAsB,KAAK;AACrE,QAAM,UAAU,IAAI,MAAM,QAAQ,OAAO,GAAG,sBAAsB,OAAO;AACzE,SAAO;AAAA,IACL,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IACzB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IACzB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC/B;AACF;AAGO,SAAS,eAAe,OAAwB;AACrD,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,KAAK,KAAK,OAAO,EAAG,QAAO;AAC/B,QAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,OAAO,MAAM,IAAI;AACxB,QAAM,MAAM,OAAO,YAAY,GAAG;AAClC,SAAO,MAAM,SAAS,KAAK,MAAM,KAAK,MAAM,OAAO,SAAS;AAC9D;AAEA,SAAS,IAAI,OAA2B,KAAiC;AACvE,SAAO,UAAU,SAAY,SAAY,MAAM,MAAM,GAAG,GAAG,EAAE,QAAQ;AACvE;AAEA,SAAS,QAAQ,OAA+C;AAC9D,QAAM,YAAY,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACnD,SAAO,YAAY,YAAY;AACjC;AAEA,SAAS,MAAM,OAA+C;AAC5D,QAAM,QAAQ,SAAS,IACpB,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,OAAO;AACjB,SAAO,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI;AAC7C;","names":[]}
@@ -4,6 +4,7 @@ import { formatLocation } from "../format/location-utils.js";
4
4
  import { formatStructuredName } from "../format/pds-utils.js";
5
5
  import { filterHidden } from "../profile/section-model.js";
6
6
  import { isTeachingCourse } from "../taxonomy/course-role.js";
7
+ import { normalizeExportContact } from "./export-contact.js";
7
8
  import { toResumeDate } from "./json-resume.js";
8
9
  var FUNDER_CV_FORMATS = ["nih", "erc"];
9
10
  var FUNDER_CV_ITEM_KINDS = ["publication", "presentation", "honor"];
@@ -240,7 +241,7 @@ function honorsOf(ids, profile) {
240
241
  ];
241
242
  });
242
243
  }
243
- function profileToFunderCv(profile, selection) {
244
+ function profileToFunderCv(profile, selection, options = {}) {
244
245
  const person = personOf(profile);
245
246
  const positions = filterHidden(profile.positions);
246
247
  const education = byStartDesc(filterHidden(profile.education), (e) => e.startedAt);
@@ -311,7 +312,7 @@ function profileToFunderCv(profile, selection) {
311
312
  );
312
313
  return {
313
314
  format: "erc",
314
- person,
315
+ person: { ...person, ...normalizeExportContact(options.contact ?? {}) },
315
316
  education: education.map(
316
317
  (e) => compact({
317
318
  date: monthYear(e.endedAt) ?? monthYear(e.startedAt),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/resume/funder-cv.ts"],"sourcesContent":["/**\n * Funder-format CV documents: the NIH Biographical Sketch (Common Form plus\n * NIH Supplement) and the ERC Part B1 \"Curriculum vitae and Track Record\".\n *\n * A pure mapper from a Sifa profile and the owner's pick of items to a plain\n * JSON document a typesetter lays out. Like the JSON Resume emitter next door,\n * nothing here fetches or renders.\n *\n * Funders cap how many items a CV may list. The caps live in\n * `FUNDER_CV_GROUPS`, so the export page that offers the choice and the server\n * that enforces it read one number. Sections a funder asks for that Sifa holds\n * no records for (a personal statement, contributions to science, career\n * breaks) are emitted as `null` or empty, for the template to show as a\n * placeholder the applicant fills in.\n */\n\nimport { z } from 'zod';\n\nimport { formatLocation } from '../format/location-utils.js';\nimport { formatStructuredName } from '../format/pds-utils.js';\nimport { filterHidden } from '../profile/section-model.js';\nimport { isTeachingCourse } from '../taxonomy/course-role.js';\nimport type {\n ExternalAccount,\n ProfileCourse,\n ProfileEducation,\n ProfileHonor,\n ProfilePosition,\n ProfilePresentation,\n ProfilePublication,\n ProfileVolunteering,\n} from '../types/index.js';\nimport { toResumeDate } from './json-resume.js';\n\nexport const FUNDER_CV_FORMATS = ['nih', 'erc'] as const;\nexport type FunderCvFormat = (typeof FUNDER_CV_FORMATS)[number];\n\nexport const FUNDER_CV_ITEM_KINDS = ['publication', 'presentation', 'honor'] as const;\nexport type FunderCvItemKind = (typeof FUNDER_CV_ITEM_KINDS)[number];\n\nexport interface FunderCvGroupSpec {\n readonly kinds: readonly FunderCvItemKind[];\n /** Most items the funder's form takes for this group. */\n readonly max: number;\n}\n\n/**\n * The pickable groups per format, with the funder's item limits.\n *\n * - NIH Common Form \"Products\": up to 5 closely related to the proposed\n * project and up to 5 other significant products. Publications, conference\n * papers and presentations all count. NIH Supplement \"Honors\": up to 15.\n * - ERC Part B1 \"Research achievements\": up to ten research outputs. \"Peer\n * recognition\" is \"selected examples\" with no number; 15 is Sifa's own cap,\n * to keep the section inside the 4-page limit.\n */\nexport const FUNDER_CV_GROUPS = {\n nih: {\n related: { kinds: ['publication', 'presentation'], max: 5 },\n other: { kinds: ['publication', 'presentation'], max: 5 },\n honors: { kinds: ['honor'], max: 15 },\n },\n erc: {\n outputs: { kinds: ['publication'], max: 10 },\n recognition: { kinds: ['honor', 'presentation'], max: 15 },\n },\n} as const satisfies Record<FunderCvFormat, Record<string, FunderCvGroupSpec>>;\n\nexport type FunderCvGroupName<F extends FunderCvFormat> = keyof (typeof FUNDER_CV_GROUPS)[F] &\n string;\n\n/** A picked item: `<kind>:<rkey>`, e.g. `publication:3kxyz`. */\nexport function funderCvItemId(kind: FunderCvItemKind, rkey: string): string {\n return `${kind}:${rkey}`;\n}\n\n/** Record key syntax (atproto): 1-512 of A-Z a-z 0-9 . - _ : ~ */\nconst ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._~:-]{1,512}$/;\n\nfunction kindOf(id: string): FunderCvItemKind | undefined {\n const kind = id.slice(0, id.indexOf(':'));\n return (FUNDER_CV_ITEM_KINDS as readonly string[]).includes(kind)\n ? (kind as FunderCvItemKind)\n : undefined;\n}\n\nfunction groupSchema(spec: FunderCvGroupSpec) {\n return z\n .array(\n z\n .string()\n .regex(ITEM_ID_RE)\n .refine((id) => spec.kinds.includes(kindOf(id) as FunderCvItemKind), {\n message: `Only ${spec.kinds.join(', ')} items fit here`,\n }),\n )\n .max(spec.max)\n .default([]);\n}\n\nfunction noRepeats(lists: readonly string[][]): boolean {\n const all = lists.flat();\n return new Set(all).size === all.length;\n}\n\nconst nihSelectionSchema = z\n .object({\n format: z.literal('nih'),\n related: groupSchema(FUNDER_CV_GROUPS.nih.related),\n other: groupSchema(FUNDER_CV_GROUPS.nih.other),\n honors: groupSchema(FUNDER_CV_GROUPS.nih.honors),\n })\n .refine((s) => noRepeats([s.related, s.other, s.honors]), {\n message: 'An item can be listed once',\n });\n\nconst ercSelectionSchema = z\n .object({\n format: z.literal('erc'),\n outputs: groupSchema(FUNDER_CV_GROUPS.erc.outputs),\n recognition: groupSchema(FUNDER_CV_GROUPS.erc.recognition),\n })\n .refine((s) => noRepeats([s.outputs, s.recognition]), {\n message: 'An item can be listed once',\n });\n\n/**\n * The owner's pick for one format. Enforces the shape and the funder's limits;\n * whether each id is one of the owner's own visible records is a separate\n * check against the profile, `findUnknownFunderCvItems`.\n */\nexport const funderCvSelectionSchema = z.discriminatedUnion('format', [\n nihSelectionSchema,\n ercSelectionSchema,\n]);\n\nexport type FunderCvSelection = z.infer<typeof funderCvSelectionSchema>;\n\n/** The subset of `Profile` the funder mapper reads. */\nexport interface FunderCvProfileInput {\n readonly handle: string;\n readonly displayName?: string;\n readonly givenName?: string;\n readonly familyName?: string;\n readonly headline?: string;\n readonly website?: string;\n readonly positions?: ProfilePosition[];\n readonly education?: ProfileEducation[];\n readonly publications?: ProfilePublication[];\n readonly presentations?: ProfilePresentation[];\n readonly honors?: ProfileHonor[];\n readonly courses?: ProfileCourse[];\n readonly volunteering?: ProfileVolunteering[];\n readonly externalAccounts?: ExternalAccount[];\n}\n\nexport interface FunderCvCandidate {\n readonly id: string;\n readonly kind: FunderCvItemKind;\n readonly title: string;\n /** `YYYY`, `YYYY-MM` or `YYYY-MM-DD`; absent when the record has no date. */\n readonly date?: string;\n}\n\n/** The most recent visible delivery of a talk, which dates it. */\nfunction latestDeliveryRecord(presentation: ProfilePresentation) {\n const dateOf = (d: { date?: string | null }) => toResumeDate(d.date ?? undefined) ?? '';\n return [...filterHidden(presentation.deliveries)]\n .sort((a, b) => dateOf(a).localeCompare(dateOf(b)))\n .at(-1);\n}\n\nfunction latestDelivery(presentation: ProfilePresentation): string | undefined {\n return toResumeDate(latestDeliveryRecord(presentation)?.date ?? undefined);\n}\n\nfunction candidatesOf(profile: FunderCvProfileInput, kind: FunderCvItemKind): FunderCvCandidate[] {\n switch (kind) {\n case 'publication':\n return filterHidden(profile.publications).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: toResumeDate(p.date),\n }));\n case 'presentation':\n return filterHidden(profile.presentations).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: latestDelivery(p),\n }));\n case 'honor':\n return filterHidden(profile.honors).map((h) => ({\n id: funderCvItemId(kind, h.rkey),\n kind,\n title: h.title,\n date: toResumeDate(h.date),\n }));\n }\n}\n\n/**\n * The owner's visible items of the given kinds, most recent first. Undated\n * items go last, in profile order. The sort is stable, so equal dates keep\n * profile order too.\n */\nexport function listFunderCvCandidates(\n profile: FunderCvProfileInput,\n kinds: readonly FunderCvItemKind[],\n): FunderCvCandidate[] {\n const all = kinds.flatMap((kind) => candidatesOf(profile, kind));\n return all.sort((a, b) => {\n if (a.date && b.date) return b.date.localeCompare(a.date);\n if (a.date) return -1;\n if (b.date) return 1;\n return 0;\n });\n}\n\n/**\n * What the export page pre-ticks: the most recent publications up to each\n * limit (for NIH, the first five as closely related and the next five as\n * other), and the most recent honors. Talks are offered but not pre-ticked.\n */\nexport function defaultFunderCvSelection(\n profile: FunderCvProfileInput,\n format: FunderCvFormat,\n): FunderCvSelection {\n const publications = listFunderCvCandidates(profile, ['publication']).map((c) => c.id);\n const honors = listFunderCvCandidates(profile, ['honor']).map((c) => c.id);\n if (format === 'nih') {\n const { related, other } = FUNDER_CV_GROUPS.nih;\n return {\n format,\n related: publications.slice(0, related.max),\n other: publications.slice(related.max, related.max + other.max),\n honors: honors.slice(0, FUNDER_CV_GROUPS.nih.honors.max),\n };\n }\n return {\n format,\n outputs: publications.slice(0, FUNDER_CV_GROUPS.erc.outputs.max),\n recognition: honors.slice(0, FUNDER_CV_GROUPS.erc.recognition.max),\n };\n}\n\nfunction selectedIds(selection: FunderCvSelection): string[] {\n return selection.format === 'nih'\n ? [...selection.related, ...selection.other, ...selection.honors]\n : [...selection.outputs, ...selection.recognition];\n}\n\n/**\n * Ids in the selection that are not visible records on this profile. The\n * profile is the owner's own, so an empty result means every pick is theirs.\n */\nexport function findUnknownFunderCvItems(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n): string[] {\n const known = new Set(listFunderCvCandidates(profile, FUNDER_CV_ITEM_KINDS).map((c) => c.id));\n return selectedIds(selection).filter((id) => !known.has(id));\n}\n\n// ---------------------------------------------------------------------------\n// Document shapes\n// ---------------------------------------------------------------------------\n\nexport interface FunderCvPerson {\n /** Full name as the profile shows it; the handle when there is no name. */\n readonly name: string;\n readonly givenName?: string;\n readonly familyName?: string;\n /** Bare ORCID iD, e.g. `0000-0002-1825-0097`. */\n readonly orcid?: string;\n readonly website?: string;\n}\n\n/** One product or research output, ready to cite. */\nexport interface FunderCvCitation {\n readonly kind: 'publication' | 'presentation';\n readonly title: string;\n /** Authors in record order, comma separated. For a talk, the speaker. */\n readonly authors?: string;\n /** Journal, publisher or event. */\n readonly venue?: string;\n /** Year. */\n readonly date?: string;\n readonly doi?: string;\n readonly url?: string;\n}\n\nexport interface FunderCvHonor {\n readonly year?: string;\n readonly title: string;\n readonly organization?: string;\n}\n\nexport interface NihEducation {\n readonly organization: string;\n readonly location?: string;\n readonly degree?: string;\n /** Start date, `MM/YYYY` or `YYYY`. */\n readonly start?: string;\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly end?: string;\n readonly field?: string;\n}\n\nexport interface FunderCvAppointment {\n /** `YYYY`. */\n readonly start?: string;\n /** `YYYY`, or null for a current appointment. */\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n readonly location?: string;\n}\n\n/** NIH Biographical Sketch: Common Form plus NIH Supplement. */\nexport interface NihFunderCv {\n readonly format: 'nih';\n readonly person: FunderCvPerson;\n readonly positionTitle?: string;\n /** Primary organization: the current position's. */\n readonly organization?: string;\n readonly location?: string;\n readonly education: NihEducation[];\n readonly appointments: FunderCvAppointment[];\n readonly products: { readonly related: FunderCvCitation[]; readonly other: FunderCvCitation[] };\n readonly honors: FunderCvHonor[];\n /** Supplement \"Personal Statement\". Sifa holds no record for it. */\n readonly personalStatement: string | null;\n /** Supplement \"Contributions to Science\". Sifa holds no record for them. */\n readonly contributions: string[];\n}\n\nexport interface ErcEducation {\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly date?: string;\n readonly degree?: string;\n readonly field?: string;\n readonly institution: string;\n}\n\nexport interface ErcRecognition {\n readonly kind: 'honor' | 'presentation';\n readonly year?: string;\n readonly title: string;\n /** Awarding body, or the event a talk was given at. */\n readonly organization?: string;\n}\n\nexport interface ErcContribution {\n readonly kind: 'teaching' | 'service';\n readonly start?: string;\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n}\n\n/** ERC Part B1, section \"Curriculum vitae and Track Record\". */\nexport interface ErcFunderCv {\n readonly format: 'erc';\n readonly person: FunderCvPerson;\n readonly education: ErcEducation[];\n readonly currentPositions: FunderCvAppointment[];\n readonly previousPositions: FunderCvAppointment[];\n readonly outputs: FunderCvCitation[];\n readonly recognition: ErcRecognition[];\n readonly communityContributions: ErcContribution[];\n /** Proposal acronym for the page header. Not a profile fact. */\n readonly acronym: string | null;\n /** \"Career breaks, diverse career paths and major life events\". */\n readonly careerBreaks: string | null;\n}\n\nexport type FunderCvDocument = NihFunderCv | ErcFunderCv;\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\nfunction text(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction compact<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n\nfunction year(value: string | null | undefined): string | undefined {\n return toResumeDate(value ?? undefined)?.slice(0, 4);\n}\n\n/** `MM/YYYY` when the month is known, else `YYYY`. */\nfunction monthYear(value: string | null | undefined): string | undefined {\n const date = toResumeDate(value ?? undefined);\n if (!date) return undefined;\n return date.length >= 7 ? `${date.slice(5, 7)}/${date.slice(0, 4)}` : date;\n}\n\n/** Newest start first; undated entries last, in profile order. */\nfunction byStartDesc<T>(items: T[], start: (item: T) => string | undefined): T[] {\n return [...items].sort((a, b) => {\n const da = toResumeDate(start(a));\n const db = toResumeDate(start(b));\n if (da && db) return db.localeCompare(da);\n if (da) return -1;\n if (db) return 1;\n return 0;\n });\n}\n\nconst ORCID_RE = /\\d{4}-\\d{4}-\\d{4}-\\d{3}[\\dX]/;\n\nfunction orcidOf(accounts: ExternalAccount[] | undefined): string | undefined {\n const account = filterHidden(accounts).find((a) => a.platform.toLowerCase() === 'orcid');\n return account ? ORCID_RE.exec(account.url)?.[0] : undefined;\n}\n\nfunction personOf(profile: FunderCvProfileInput): FunderCvPerson {\n return compact<FunderCvPerson>({\n name:\n formatStructuredName(profile.givenName, profile.familyName) ??\n text(profile.displayName) ??\n profile.handle,\n givenName: text(profile.givenName),\n familyName: text(profile.familyName),\n orcid: orcidOf(profile.externalAccounts),\n website: text(profile.website),\n });\n}\n\nfunction appointment(position: ProfilePosition): FunderCvAppointment {\n return compact<FunderCvAppointment>({\n start: year(position.startedAt),\n end: year(position.endedAt) ?? null,\n title: position.title,\n organization: text(position.entityName) ?? text(position.company),\n location: text(formatLocation(position.location)),\n });\n}\n\n/** The current position to name on the form: the flagged primary, else the newest. */\nfunction currentPosition(positions: ProfilePosition[]): ProfilePosition | undefined {\n const current = byStartDesc(\n positions.filter((p) => !text(p.endedAt)),\n (p) => p.startedAt,\n );\n return current.find((p) => p.primary) ?? current[0];\n}\n\nfunction lookup(profile: FunderCvProfileInput) {\n const publications = new Map(filterHidden(profile.publications).map((p) => [p.rkey, p]));\n const presentations = new Map(filterHidden(profile.presentations).map((p) => [p.rkey, p]));\n const honors = new Map(filterHidden(profile.honors).map((h) => [h.rkey, h]));\n const split = (id: string) => {\n const at = id.indexOf(':');\n return { kind: id.slice(0, at), rkey: id.slice(at + 1) };\n };\n return { publications, presentations, honors, split };\n}\n\nfunction citations(\n ids: readonly string[],\n profile: FunderCvProfileInput,\n person: FunderCvPerson,\n): FunderCvCitation[] {\n const { publications, presentations, split } = lookup(profile);\n return ids.flatMap((id): FunderCvCitation[] => {\n const { kind, rkey } = split(id);\n if (kind === 'publication') {\n const p = publications.get(rkey);\n if (!p) return [];\n const authors = (p.contributors ?? []).map((c) => c.name.trim()).filter(Boolean);\n return [\n compact<FunderCvCitation>({\n kind: 'publication',\n title: p.title,\n authors: authors.length > 0 ? authors.join(', ') : undefined,\n venue: text(p.publisher),\n date: year(p.date),\n doi: text(p.doi),\n url: text(p.url),\n }),\n ];\n }\n if (kind === 'presentation') {\n const talk = presentations.get(rkey);\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<FunderCvCitation>({\n kind: 'presentation',\n title: talk.title,\n authors: person.name,\n venue: text(delivery?.eventName),\n date: year(delivery?.date),\n url: text(talk.links?.[0]?.uri),\n }),\n ];\n }\n return [];\n });\n}\n\nfunction honorsOf(ids: readonly string[], profile: FunderCvProfileInput): FunderCvHonor[] {\n const { honors, split } = lookup(profile);\n return ids.flatMap((id) => {\n const { kind, rkey } = split(id);\n const h = kind === 'honor' ? honors.get(rkey) : undefined;\n if (!h) return [];\n return [\n compact<FunderCvHonor>({\n year: year(h.date),\n title: h.title,\n organization: text(h.entityName) ?? text(h.issuer),\n }),\n ];\n });\n}\n\n/**\n * Build the funder document from the profile and the owner's selection.\n *\n * Hidden records never appear. Selected ids that match no visible record are\n * skipped; callers that must refuse them check `findUnknownFunderCvItems`\n * first. Selected items keep the order they were picked in.\n */\nexport function profileToFunderCv(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n): FunderCvDocument {\n const person = personOf(profile);\n const positions = filterHidden(profile.positions);\n const education = byStartDesc(filterHidden(profile.education), (e) => e.startedAt);\n\n if (selection.format === 'nih') {\n const current = currentPosition(positions);\n return compact<NihFunderCv>({\n format: 'nih',\n person,\n positionTitle: text(current?.title),\n organization: current ? appointment(current).organization : undefined,\n location: text(formatLocation(current?.location)),\n education: education.map((e) =>\n compact<NihEducation>({\n organization: text(e.entityName) ?? e.institution,\n degree: text(e.degree),\n start: monthYear(e.startedAt),\n end: monthYear(e.endedAt),\n field: text(e.fieldOfStudy),\n }),\n ),\n appointments: byStartDesc(positions, (p) => p.startedAt).map(appointment),\n products: {\n related: citations(selection.related, profile, person),\n other: citations(selection.other, profile, person),\n },\n honors: honorsOf(selection.honors, profile),\n personalStatement: null,\n contributions: [],\n });\n }\n\n const appointments = byStartDesc(positions, (p) => p.startedAt);\n const { presentations, split } = lookup(profile);\n\n const recognition = selection.recognition.flatMap((id): ErcRecognition[] => {\n const { kind, rkey } = split(id);\n if (kind === 'honor') {\n const [honor] = honorsOf([id], profile);\n return honor ? [{ kind: 'honor', ...honor }] : [];\n }\n const talk = kind === 'presentation' ? presentations.get(rkey) : undefined;\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<ErcRecognition>({\n kind: 'presentation',\n year: year(delivery?.date),\n title: talk.title,\n organization: text(delivery?.eventName),\n }),\n ];\n });\n\n const teaching = filterHidden(profile.courses)\n .filter(isTeachingCourse)\n .map((c) =>\n compact<ErcContribution>({\n kind: 'teaching',\n start: year(c.startedAt),\n end: year(c.endedAt) ?? null,\n title: c.name,\n organization: text(c.entityName) ?? text(c.institution),\n }),\n );\n const service = filterHidden(profile.volunteering).map((v) =>\n compact<ErcContribution>({\n kind: 'service',\n start: year(v.startDate),\n end: year(v.endDate) ?? null,\n title: text(v.role) ?? text(v.entityName) ?? v.organization,\n organization: text(v.entityName) ?? text(v.organization),\n }),\n );\n\n return {\n format: 'erc',\n person,\n education: education.map((e) =>\n compact<ErcEducation>({\n date: monthYear(e.endedAt) ?? monthYear(e.startedAt),\n degree: text(e.degree),\n field: text(e.fieldOfStudy),\n institution: text(e.entityName) ?? e.institution,\n }),\n ),\n currentPositions: appointments.filter((p) => !text(p.endedAt)).map(appointment),\n previousPositions: appointments.filter((p) => !!text(p.endedAt)).map(appointment),\n outputs: citations(selection.outputs, profile, person),\n recognition,\n communityContributions: [...teaching, ...service],\n acronym: null,\n careerBreaks: null,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/resume/funder-cv.ts"],"sourcesContent":["/**\n * Funder-format CV documents: the NIH Biographical Sketch (Common Form plus\n * NIH Supplement) and the ERC Part B1 \"Curriculum vitae and Track Record\".\n *\n * A pure mapper from a Sifa profile and the owner's pick of items to a plain\n * JSON document a typesetter lays out. Like the JSON Resume emitter next door,\n * nothing here fetches or renders.\n *\n * Funders cap how many items a CV may list. The caps live in\n * `FUNDER_CV_GROUPS`, so the export page that offers the choice and the server\n * that enforces it read one number. Sections a funder asks for that Sifa holds\n * no records for (a personal statement, contributions to science, career\n * breaks) are emitted as `null` or empty, for the template to show as a\n * placeholder the applicant fills in.\n */\n\nimport { z } from 'zod';\n\nimport { formatLocation } from '../format/location-utils.js';\nimport { formatStructuredName } from '../format/pds-utils.js';\nimport { filterHidden } from '../profile/section-model.js';\nimport { isTeachingCourse } from '../taxonomy/course-role.js';\nimport type {\n ExternalAccount,\n ProfileCourse,\n ProfileEducation,\n ProfileHonor,\n ProfilePosition,\n ProfilePresentation,\n ProfilePublication,\n ProfileVolunteering,\n} from '../types/index.js';\nimport { normalizeExportContact, type ExportContact } from './export-contact.js';\nimport { toResumeDate } from './json-resume.js';\n\nexport const FUNDER_CV_FORMATS = ['nih', 'erc'] as const;\nexport type FunderCvFormat = (typeof FUNDER_CV_FORMATS)[number];\n\nexport const FUNDER_CV_ITEM_KINDS = ['publication', 'presentation', 'honor'] as const;\nexport type FunderCvItemKind = (typeof FUNDER_CV_ITEM_KINDS)[number];\n\nexport interface FunderCvGroupSpec {\n readonly kinds: readonly FunderCvItemKind[];\n /** Most items the funder's form takes for this group. */\n readonly max: number;\n}\n\n/**\n * The pickable groups per format, with the funder's item limits.\n *\n * - NIH Common Form \"Products\": up to 5 closely related to the proposed\n * project and up to 5 other significant products. Publications, conference\n * papers and presentations all count. NIH Supplement \"Honors\": up to 15.\n * - ERC Part B1 \"Research achievements\": up to ten research outputs. \"Peer\n * recognition\" is \"selected examples\" with no number; 15 is Sifa's own cap,\n * to keep the section inside the 4-page limit.\n */\nexport const FUNDER_CV_GROUPS = {\n nih: {\n related: { kinds: ['publication', 'presentation'], max: 5 },\n other: { kinds: ['publication', 'presentation'], max: 5 },\n honors: { kinds: ['honor'], max: 15 },\n },\n erc: {\n outputs: { kinds: ['publication'], max: 10 },\n recognition: { kinds: ['honor', 'presentation'], max: 15 },\n },\n} as const satisfies Record<FunderCvFormat, Record<string, FunderCvGroupSpec>>;\n\nexport type FunderCvGroupName<F extends FunderCvFormat> = keyof (typeof FUNDER_CV_GROUPS)[F] &\n string;\n\n/** A picked item: `<kind>:<rkey>`, e.g. `publication:3kxyz`. */\nexport function funderCvItemId(kind: FunderCvItemKind, rkey: string): string {\n return `${kind}:${rkey}`;\n}\n\n/** Record key syntax (atproto): 1-512 of A-Z a-z 0-9 . - _ : ~ */\nconst ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._~:-]{1,512}$/;\n\nfunction kindOf(id: string): FunderCvItemKind | undefined {\n const kind = id.slice(0, id.indexOf(':'));\n return (FUNDER_CV_ITEM_KINDS as readonly string[]).includes(kind)\n ? (kind as FunderCvItemKind)\n : undefined;\n}\n\nfunction groupSchema(spec: FunderCvGroupSpec) {\n return z\n .array(\n z\n .string()\n .regex(ITEM_ID_RE)\n .refine((id) => spec.kinds.includes(kindOf(id) as FunderCvItemKind), {\n message: `Only ${spec.kinds.join(', ')} items fit here`,\n }),\n )\n .max(spec.max)\n .default([]);\n}\n\nfunction noRepeats(lists: readonly string[][]): boolean {\n const all = lists.flat();\n return new Set(all).size === all.length;\n}\n\nconst nihSelectionSchema = z\n .object({\n format: z.literal('nih'),\n related: groupSchema(FUNDER_CV_GROUPS.nih.related),\n other: groupSchema(FUNDER_CV_GROUPS.nih.other),\n honors: groupSchema(FUNDER_CV_GROUPS.nih.honors),\n })\n .refine((s) => noRepeats([s.related, s.other, s.honors]), {\n message: 'An item can be listed once',\n });\n\nconst ercSelectionSchema = z\n .object({\n format: z.literal('erc'),\n outputs: groupSchema(FUNDER_CV_GROUPS.erc.outputs),\n recognition: groupSchema(FUNDER_CV_GROUPS.erc.recognition),\n })\n .refine((s) => noRepeats([s.outputs, s.recognition]), {\n message: 'An item can be listed once',\n });\n\n/**\n * The owner's pick for one format. Enforces the shape and the funder's limits;\n * whether each id is one of the owner's own visible records is a separate\n * check against the profile, `findUnknownFunderCvItems`.\n */\nexport const funderCvSelectionSchema = z.discriminatedUnion('format', [\n nihSelectionSchema,\n ercSelectionSchema,\n]);\n\nexport type FunderCvSelection = z.infer<typeof funderCvSelectionSchema>;\n\n/** The subset of `Profile` the funder mapper reads. */\nexport interface FunderCvProfileInput {\n readonly handle: string;\n readonly displayName?: string;\n readonly givenName?: string;\n readonly familyName?: string;\n readonly headline?: string;\n readonly website?: string;\n readonly positions?: ProfilePosition[];\n readonly education?: ProfileEducation[];\n readonly publications?: ProfilePublication[];\n readonly presentations?: ProfilePresentation[];\n readonly honors?: ProfileHonor[];\n readonly courses?: ProfileCourse[];\n readonly volunteering?: ProfileVolunteering[];\n readonly externalAccounts?: ExternalAccount[];\n}\n\nexport interface FunderCvCandidate {\n readonly id: string;\n readonly kind: FunderCvItemKind;\n readonly title: string;\n /** `YYYY`, `YYYY-MM` or `YYYY-MM-DD`; absent when the record has no date. */\n readonly date?: string;\n}\n\n/** The most recent visible delivery of a talk, which dates it. */\nfunction latestDeliveryRecord(presentation: ProfilePresentation) {\n const dateOf = (d: { date?: string | null }) => toResumeDate(d.date ?? undefined) ?? '';\n return [...filterHidden(presentation.deliveries)]\n .sort((a, b) => dateOf(a).localeCompare(dateOf(b)))\n .at(-1);\n}\n\nfunction latestDelivery(presentation: ProfilePresentation): string | undefined {\n return toResumeDate(latestDeliveryRecord(presentation)?.date ?? undefined);\n}\n\nfunction candidatesOf(profile: FunderCvProfileInput, kind: FunderCvItemKind): FunderCvCandidate[] {\n switch (kind) {\n case 'publication':\n return filterHidden(profile.publications).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: toResumeDate(p.date),\n }));\n case 'presentation':\n return filterHidden(profile.presentations).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: latestDelivery(p),\n }));\n case 'honor':\n return filterHidden(profile.honors).map((h) => ({\n id: funderCvItemId(kind, h.rkey),\n kind,\n title: h.title,\n date: toResumeDate(h.date),\n }));\n }\n}\n\n/**\n * The owner's visible items of the given kinds, most recent first. Undated\n * items go last, in profile order. The sort is stable, so equal dates keep\n * profile order too.\n */\nexport function listFunderCvCandidates(\n profile: FunderCvProfileInput,\n kinds: readonly FunderCvItemKind[],\n): FunderCvCandidate[] {\n const all = kinds.flatMap((kind) => candidatesOf(profile, kind));\n return all.sort((a, b) => {\n if (a.date && b.date) return b.date.localeCompare(a.date);\n if (a.date) return -1;\n if (b.date) return 1;\n return 0;\n });\n}\n\n/**\n * What the export page pre-ticks: the most recent publications up to each\n * limit (for NIH, the first five as closely related and the next five as\n * other), and the most recent honors. Talks are offered but not pre-ticked.\n */\nexport function defaultFunderCvSelection(\n profile: FunderCvProfileInput,\n format: FunderCvFormat,\n): FunderCvSelection {\n const publications = listFunderCvCandidates(profile, ['publication']).map((c) => c.id);\n const honors = listFunderCvCandidates(profile, ['honor']).map((c) => c.id);\n if (format === 'nih') {\n const { related, other } = FUNDER_CV_GROUPS.nih;\n return {\n format,\n related: publications.slice(0, related.max),\n other: publications.slice(related.max, related.max + other.max),\n honors: honors.slice(0, FUNDER_CV_GROUPS.nih.honors.max),\n };\n }\n return {\n format,\n outputs: publications.slice(0, FUNDER_CV_GROUPS.erc.outputs.max),\n recognition: honors.slice(0, FUNDER_CV_GROUPS.erc.recognition.max),\n };\n}\n\nfunction selectedIds(selection: FunderCvSelection): string[] {\n return selection.format === 'nih'\n ? [...selection.related, ...selection.other, ...selection.honors]\n : [...selection.outputs, ...selection.recognition];\n}\n\n/**\n * Ids in the selection that are not visible records on this profile. The\n * profile is the owner's own, so an empty result means every pick is theirs.\n */\nexport function findUnknownFunderCvItems(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n): string[] {\n const known = new Set(listFunderCvCandidates(profile, FUNDER_CV_ITEM_KINDS).map((c) => c.id));\n return selectedIds(selection).filter((id) => !known.has(id));\n}\n\n// ---------------------------------------------------------------------------\n// Document shapes\n// ---------------------------------------------------------------------------\n\nexport interface FunderCvPerson {\n /** Full name as the profile shows it; the handle when there is no name. */\n readonly name: string;\n readonly givenName?: string;\n readonly familyName?: string;\n /** Bare ORCID iD, e.g. `0000-0002-1825-0097`. */\n readonly orcid?: string;\n readonly website?: string;\n}\n\n/**\n * The ERC person, with the contact details the owner typed for this export.\n * The NIH Biographical Sketch has no contact block, so only ERC carries them.\n */\nexport interface ErcFunderCvPerson extends FunderCvPerson {\n readonly email?: string;\n readonly phone?: string;\n /** Lines are separated by `\\n`. */\n readonly address?: string;\n}\n\nexport interface FunderCvOptions {\n /**\n * Contact details the owner typed for this export. Sifa stores none of\n * them. Used for ERC only.\n */\n readonly contact?: ExportContact;\n}\n\n/** One product or research output, ready to cite. */\nexport interface FunderCvCitation {\n readonly kind: 'publication' | 'presentation';\n readonly title: string;\n /** Authors in record order, comma separated. For a talk, the speaker. */\n readonly authors?: string;\n /** Journal, publisher or event. */\n readonly venue?: string;\n /** Year. */\n readonly date?: string;\n readonly doi?: string;\n readonly url?: string;\n}\n\nexport interface FunderCvHonor {\n readonly year?: string;\n readonly title: string;\n readonly organization?: string;\n}\n\nexport interface NihEducation {\n readonly organization: string;\n readonly location?: string;\n readonly degree?: string;\n /** Start date, `MM/YYYY` or `YYYY`. */\n readonly start?: string;\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly end?: string;\n readonly field?: string;\n}\n\nexport interface FunderCvAppointment {\n /** `YYYY`. */\n readonly start?: string;\n /** `YYYY`, or null for a current appointment. */\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n readonly location?: string;\n}\n\n/** NIH Biographical Sketch: Common Form plus NIH Supplement. */\nexport interface NihFunderCv {\n readonly format: 'nih';\n readonly person: FunderCvPerson;\n readonly positionTitle?: string;\n /** Primary organization: the current position's. */\n readonly organization?: string;\n readonly location?: string;\n readonly education: NihEducation[];\n readonly appointments: FunderCvAppointment[];\n readonly products: { readonly related: FunderCvCitation[]; readonly other: FunderCvCitation[] };\n readonly honors: FunderCvHonor[];\n /** Supplement \"Personal Statement\". Sifa holds no record for it. */\n readonly personalStatement: string | null;\n /** Supplement \"Contributions to Science\". Sifa holds no record for them. */\n readonly contributions: string[];\n}\n\nexport interface ErcEducation {\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly date?: string;\n readonly degree?: string;\n readonly field?: string;\n readonly institution: string;\n}\n\nexport interface ErcRecognition {\n readonly kind: 'honor' | 'presentation';\n readonly year?: string;\n readonly title: string;\n /** Awarding body, or the event a talk was given at. */\n readonly organization?: string;\n}\n\nexport interface ErcContribution {\n readonly kind: 'teaching' | 'service';\n readonly start?: string;\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n}\n\n/** ERC Part B1, section \"Curriculum vitae and Track Record\". */\nexport interface ErcFunderCv {\n readonly format: 'erc';\n readonly person: ErcFunderCvPerson;\n readonly education: ErcEducation[];\n readonly currentPositions: FunderCvAppointment[];\n readonly previousPositions: FunderCvAppointment[];\n readonly outputs: FunderCvCitation[];\n readonly recognition: ErcRecognition[];\n readonly communityContributions: ErcContribution[];\n /** Proposal acronym for the page header. Not a profile fact. */\n readonly acronym: string | null;\n /** \"Career breaks, diverse career paths and major life events\". */\n readonly careerBreaks: string | null;\n}\n\nexport type FunderCvDocument = NihFunderCv | ErcFunderCv;\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\nfunction text(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction compact<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n\nfunction year(value: string | null | undefined): string | undefined {\n return toResumeDate(value ?? undefined)?.slice(0, 4);\n}\n\n/** `MM/YYYY` when the month is known, else `YYYY`. */\nfunction monthYear(value: string | null | undefined): string | undefined {\n const date = toResumeDate(value ?? undefined);\n if (!date) return undefined;\n return date.length >= 7 ? `${date.slice(5, 7)}/${date.slice(0, 4)}` : date;\n}\n\n/** Newest start first; undated entries last, in profile order. */\nfunction byStartDesc<T>(items: T[], start: (item: T) => string | undefined): T[] {\n return [...items].sort((a, b) => {\n const da = toResumeDate(start(a));\n const db = toResumeDate(start(b));\n if (da && db) return db.localeCompare(da);\n if (da) return -1;\n if (db) return 1;\n return 0;\n });\n}\n\nconst ORCID_RE = /\\d{4}-\\d{4}-\\d{4}-\\d{3}[\\dX]/;\n\nfunction orcidOf(accounts: ExternalAccount[] | undefined): string | undefined {\n const account = filterHidden(accounts).find((a) => a.platform.toLowerCase() === 'orcid');\n return account ? ORCID_RE.exec(account.url)?.[0] : undefined;\n}\n\nfunction personOf(profile: FunderCvProfileInput): FunderCvPerson {\n return compact<FunderCvPerson>({\n name:\n formatStructuredName(profile.givenName, profile.familyName) ??\n text(profile.displayName) ??\n profile.handle,\n givenName: text(profile.givenName),\n familyName: text(profile.familyName),\n orcid: orcidOf(profile.externalAccounts),\n website: text(profile.website),\n });\n}\n\nfunction appointment(position: ProfilePosition): FunderCvAppointment {\n return compact<FunderCvAppointment>({\n start: year(position.startedAt),\n end: year(position.endedAt) ?? null,\n title: position.title,\n organization: text(position.entityName) ?? text(position.company),\n location: text(formatLocation(position.location)),\n });\n}\n\n/** The current position to name on the form: the flagged primary, else the newest. */\nfunction currentPosition(positions: ProfilePosition[]): ProfilePosition | undefined {\n const current = byStartDesc(\n positions.filter((p) => !text(p.endedAt)),\n (p) => p.startedAt,\n );\n return current.find((p) => p.primary) ?? current[0];\n}\n\nfunction lookup(profile: FunderCvProfileInput) {\n const publications = new Map(filterHidden(profile.publications).map((p) => [p.rkey, p]));\n const presentations = new Map(filterHidden(profile.presentations).map((p) => [p.rkey, p]));\n const honors = new Map(filterHidden(profile.honors).map((h) => [h.rkey, h]));\n const split = (id: string) => {\n const at = id.indexOf(':');\n return { kind: id.slice(0, at), rkey: id.slice(at + 1) };\n };\n return { publications, presentations, honors, split };\n}\n\nfunction citations(\n ids: readonly string[],\n profile: FunderCvProfileInput,\n person: FunderCvPerson,\n): FunderCvCitation[] {\n const { publications, presentations, split } = lookup(profile);\n return ids.flatMap((id): FunderCvCitation[] => {\n const { kind, rkey } = split(id);\n if (kind === 'publication') {\n const p = publications.get(rkey);\n if (!p) return [];\n const authors = (p.contributors ?? []).map((c) => c.name.trim()).filter(Boolean);\n return [\n compact<FunderCvCitation>({\n kind: 'publication',\n title: p.title,\n authors: authors.length > 0 ? authors.join(', ') : undefined,\n venue: text(p.publisher),\n date: year(p.date),\n doi: text(p.doi),\n url: text(p.url),\n }),\n ];\n }\n if (kind === 'presentation') {\n const talk = presentations.get(rkey);\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<FunderCvCitation>({\n kind: 'presentation',\n title: talk.title,\n authors: person.name,\n venue: text(delivery?.eventName),\n date: year(delivery?.date),\n url: text(talk.links?.[0]?.uri),\n }),\n ];\n }\n return [];\n });\n}\n\nfunction honorsOf(ids: readonly string[], profile: FunderCvProfileInput): FunderCvHonor[] {\n const { honors, split } = lookup(profile);\n return ids.flatMap((id) => {\n const { kind, rkey } = split(id);\n const h = kind === 'honor' ? honors.get(rkey) : undefined;\n if (!h) return [];\n return [\n compact<FunderCvHonor>({\n year: year(h.date),\n title: h.title,\n organization: text(h.entityName) ?? text(h.issuer),\n }),\n ];\n });\n}\n\n/**\n * Build the funder document from the profile and the owner's selection.\n *\n * Hidden records never appear. Selected ids that match no visible record are\n * skipped; callers that must refuse them check `findUnknownFunderCvItems`\n * first. Selected items keep the order they were picked in.\n */\nexport function profileToFunderCv(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n options: FunderCvOptions = {},\n): FunderCvDocument {\n const person = personOf(profile);\n const positions = filterHidden(profile.positions);\n const education = byStartDesc(filterHidden(profile.education), (e) => e.startedAt);\n\n if (selection.format === 'nih') {\n const current = currentPosition(positions);\n return compact<NihFunderCv>({\n format: 'nih',\n person,\n positionTitle: text(current?.title),\n organization: current ? appointment(current).organization : undefined,\n location: text(formatLocation(current?.location)),\n education: education.map((e) =>\n compact<NihEducation>({\n organization: text(e.entityName) ?? e.institution,\n degree: text(e.degree),\n start: monthYear(e.startedAt),\n end: monthYear(e.endedAt),\n field: text(e.fieldOfStudy),\n }),\n ),\n appointments: byStartDesc(positions, (p) => p.startedAt).map(appointment),\n products: {\n related: citations(selection.related, profile, person),\n other: citations(selection.other, profile, person),\n },\n honors: honorsOf(selection.honors, profile),\n personalStatement: null,\n contributions: [],\n });\n }\n\n const appointments = byStartDesc(positions, (p) => p.startedAt);\n const { presentations, split } = lookup(profile);\n\n const recognition = selection.recognition.flatMap((id): ErcRecognition[] => {\n const { kind, rkey } = split(id);\n if (kind === 'honor') {\n const [honor] = honorsOf([id], profile);\n return honor ? [{ kind: 'honor', ...honor }] : [];\n }\n const talk = kind === 'presentation' ? presentations.get(rkey) : undefined;\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<ErcRecognition>({\n kind: 'presentation',\n year: year(delivery?.date),\n title: talk.title,\n organization: text(delivery?.eventName),\n }),\n ];\n });\n\n const teaching = filterHidden(profile.courses)\n .filter(isTeachingCourse)\n .map((c) =>\n compact<ErcContribution>({\n kind: 'teaching',\n start: year(c.startedAt),\n end: year(c.endedAt) ?? null,\n title: c.name,\n organization: text(c.entityName) ?? text(c.institution),\n }),\n );\n const service = filterHidden(profile.volunteering).map((v) =>\n compact<ErcContribution>({\n kind: 'service',\n start: year(v.startDate),\n end: year(v.endDate) ?? null,\n title: text(v.role) ?? text(v.entityName) ?? v.organization,\n organization: text(v.entityName) ?? text(v.organization),\n }),\n );\n\n return {\n format: 'erc',\n person: { ...person, ...normalizeExportContact(options.contact ?? {}) },\n education: education.map((e) =>\n compact<ErcEducation>({\n date: monthYear(e.endedAt) ?? monthYear(e.startedAt),\n degree: text(e.degree),\n field: text(e.fieldOfStudy),\n institution: text(e.entityName) ?? e.institution,\n }),\n ),\n currentPositions: appointments.filter((p) => !text(p.endedAt)).map(appointment),\n previousPositions: appointments.filter((p) => !!text(p.endedAt)).map(appointment),\n outputs: citations(selection.outputs, profile, person),\n recognition,\n communityContributions: [...teaching, ...service],\n acronym: null,\n careerBreaks: null,\n };\n}\n"],"mappings":"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","names":[]}
@@ -2,6 +2,55 @@
2
2
 
3
3
  var zod = require('zod');
4
4
 
5
+ // src/resume/export-contact.ts
6
+ var EXPORT_CONTACT_LIMITS = {
7
+ email: 254,
8
+ phone: 40,
9
+ address: 500
10
+ };
11
+ var exportContactSchema = zod.z.strictObject({
12
+ email: zod.z.string().max(EXPORT_CONTACT_LIMITS.email).optional(),
13
+ phone: zod.z.string().max(EXPORT_CONTACT_LIMITS.phone).optional(),
14
+ address: zod.z.string().max(EXPORT_CONTACT_LIMITS.address).optional()
15
+ });
16
+ function pickExportContact(draft) {
17
+ return normalizeExportContact({
18
+ email: draft.includeEmail ? draft.email : void 0,
19
+ phone: draft.includePhone ? draft.phone : void 0,
20
+ address: draft.includeAddress ? draft.address : void 0
21
+ });
22
+ }
23
+ function normalizeExportContact(contact) {
24
+ const email = cap(oneLine(contact.email), EXPORT_CONTACT_LIMITS.email);
25
+ const phone = cap(oneLine(contact.phone), EXPORT_CONTACT_LIMITS.phone);
26
+ const address = cap(lines(contact.address), EXPORT_CONTACT_LIMITS.address);
27
+ return {
28
+ ...email ? { email } : {},
29
+ ...phone ? { phone } : {},
30
+ ...address ? { address } : {}
31
+ };
32
+ }
33
+ function looksLikeEmail(value) {
34
+ const trimmed = value.trim();
35
+ if (/\s/.test(trimmed)) return false;
36
+ const parts = trimmed.split("@");
37
+ if (parts.length !== 2) return false;
38
+ const [local, domain] = parts;
39
+ const dot = domain.lastIndexOf(".");
40
+ return local.length > 0 && dot > 0 && dot < domain.length - 1;
41
+ }
42
+ function cap(value, max) {
43
+ return value === void 0 ? void 0 : value.slice(0, max).trimEnd();
44
+ }
45
+ function oneLine(value) {
46
+ const collapsed = value?.replace(/\s+/g, " ").trim();
47
+ return collapsed ? collapsed : void 0;
48
+ }
49
+ function lines(value) {
50
+ const kept = (value ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
51
+ return kept.length > 0 ? kept.join("\n") : void 0;
52
+ }
53
+
5
54
  // src/format/pds-utils.ts
6
55
  var PDS_PROVIDERS = [
7
56
  { suffix: ".bsky.social", name: "bluesky" },
@@ -54,6 +103,9 @@ var identity = (input) => input;
54
103
  function compact(value) {
55
104
  return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
56
105
  }
106
+ function withAddress(location, address) {
107
+ return address ? { address, ...location } : location;
108
+ }
57
109
  function section(key, items) {
58
110
  return items.length > 0 ? { [key]: items } : void 0;
59
111
  }
@@ -78,6 +130,7 @@ function profileToJsonResume(profile, options = {}) {
78
130
  const s = options.sanitize ?? identity;
79
131
  const baseUrl = normaliseBaseUrl(options.baseUrl);
80
132
  const canonical = options.canonicalUrl ?? profileUrl(baseUrl, profile.handle);
133
+ const contact = normalizeExportContact(options.contact ?? {});
81
134
  const name = formatStructuredName(profile.givenName, profile.familyName) ?? text(profile.displayName, s) ?? profile.handle;
82
135
  const profiles = filterHidden(profile.externalAccounts).map(
83
136
  (account) => compact({
@@ -90,9 +143,11 @@ function profileToJsonResume(profile, options = {}) {
90
143
  name,
91
144
  label: text(profile.headline, s),
92
145
  image: text(profile.avatar, s),
146
+ email: text(contact.email, s),
147
+ phone: text(contact.phone, s),
93
148
  url: text(profile.website, s) ?? canonical,
94
149
  summary: text(profile.about, s),
95
- location: mapLocation(profile.location, s),
150
+ location: withAddress(mapLocation(profile.location, s), text(contact.address, s)),
96
151
  ...section("profiles", profiles)
97
152
  });
98
153
  const work = filterHidden(profile.positions).map(
@@ -444,7 +499,7 @@ function honorsOf(ids, profile) {
444
499
  ];
445
500
  });
446
501
  }
447
- function profileToFunderCv(profile, selection) {
502
+ function profileToFunderCv(profile, selection, options = {}) {
448
503
  const person = personOf(profile);
449
504
  const positions = filterHidden(profile.positions);
450
505
  const education = byStartDesc(filterHidden(profile.education), (e) => e.startedAt);
@@ -515,7 +570,7 @@ function profileToFunderCv(profile, selection) {
515
570
  );
516
571
  return {
517
572
  format: "erc",
518
- person,
573
+ person: { ...person, ...normalizeExportContact(options.contact ?? {}) },
519
574
  education: education.map(
520
575
  (e) => compact2({
521
576
  date: monthYear(e.endedAt) ?? monthYear(e.startedAt),
@@ -534,14 +589,19 @@ function profileToFunderCv(profile, selection) {
534
589
  };
535
590
  }
536
591
 
592
+ exports.EXPORT_CONTACT_LIMITS = EXPORT_CONTACT_LIMITS;
537
593
  exports.FUNDER_CV_FORMATS = FUNDER_CV_FORMATS;
538
594
  exports.FUNDER_CV_GROUPS = FUNDER_CV_GROUPS;
539
595
  exports.FUNDER_CV_ITEM_KINDS = FUNDER_CV_ITEM_KINDS;
540
596
  exports.defaultFunderCvSelection = defaultFunderCvSelection;
597
+ exports.exportContactSchema = exportContactSchema;
541
598
  exports.findUnknownFunderCvItems = findUnknownFunderCvItems;
542
599
  exports.funderCvItemId = funderCvItemId;
543
600
  exports.funderCvSelectionSchema = funderCvSelectionSchema;
544
601
  exports.listFunderCvCandidates = listFunderCvCandidates;
602
+ exports.looksLikeEmail = looksLikeEmail;
603
+ exports.normalizeExportContact = normalizeExportContact;
604
+ exports.pickExportContact = pickExportContact;
545
605
  exports.profileToFunderCv = profileToFunderCv;
546
606
  exports.profileToJsonResume = profileToJsonResume;
547
607
  exports.toResumeDate = toResumeDate;