@singi-labs/sifa-sdk 0.19.104 → 0.19.106

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/resume/json-resume.ts"],"sourcesContent":["/**\n * JSON Resume v1.0.0 emitter for a Sifa profile.\n *\n * https://jsonresume.org/schema/\n *\n * One pure function, no fetching, no DOM, no framework -- the same shape as the\n * JSON-LD emitter next door, and for the same reason: sifa.id, page.sifa.id and\n * any future consumer should emit one document from one implementation.\n *\n * Why this format earns its place: it is the lingua franca of CV tooling, so\n * emitting it hands a user every JSON Resume theme, and the Typst CV templates\n * on Typst Universe, without Sifa writing a renderer for any of them.\n *\n * Fields Sifa holds that JSON Resume has no slot for -- endorsements,\n * confirmations, involvement, investments, presentations, courses -- are\n * dropped rather than smuggled into a nearby field. The loss is documented for\n * the user at the point of download.\n */\n\nimport { parseLocationString } from '../format/location-utils.js';\nimport { formatStructuredName } from '../format/pds-utils.js';\nimport { filterHidden } from '../profile/section-model.js';\nimport {\n sortCertifications,\n sortEducation,\n sortHonors,\n sortLanguages,\n sortPositions,\n sortProjects,\n sortPublications,\n} from '../profile/section-sorts.js';\nimport { sortByActiveDateRange } from '../profile/range-sort.js';\nimport type {\n ExternalAccount,\n LanguageProficiency,\n LocationValue,\n ProfileCertification,\n ProfileEducation,\n ProfileHonor,\n ProfileLanguage,\n ProfileLocation,\n ProfilePosition,\n ProfileProject,\n ProfilePublication,\n ProfileSkill,\n ProfileVolunteering,\n} from '../types/index.js';\nimport { normaliseBaseUrl, profileUrl } from '../jsonld/url.js';\nimport { normalizeExportContact, type ExportContact } from './export-contact.js';\n\nexport type Sanitizer = (input: string) => string;\n\nexport interface JsonResumeOptions {\n /** Canonical origin for the profile URL. Defaults to `https://sifa.id`. */\n readonly baseUrl?: string;\n /** Applied to every user-authored string. Defaults to identity. */\n readonly sanitize?: Sanitizer;\n /**\n * Absolute URL of the profile this resume describes. Defaults to\n * `${baseUrl}/p/${handle}`. A personal site served from a custom domain is\n * not at that path.\n */\n readonly canonicalUrl?: string;\n /**\n * Email, phone and postal address to print. Sifa holds none of these; the\n * owner types them on the export page. Without it the resume has no\n * `email`, `phone` or `location.address`. Personal details in the same object\n * (driver's license, right to work, nationality) are left out: JSON Resume\n * has no field for them, and strict consumers such as altacv refuse a file\n * with an unknown key.\n */\n readonly contact?: ExportContact;\n}\n\n/**\n * The subset of `Profile` this emitter reads. Deliberately structural rather\n * than the full `Profile`, so a caller holding a partially hydrated profile --\n * or a test -- does not have to invent forty unrelated fields.\n */\nexport interface JsonResumeProfileInput {\n readonly handle: string;\n readonly displayName?: string;\n readonly givenName?: string;\n readonly familyName?: string;\n readonly avatar?: string;\n readonly headline?: string;\n readonly about?: string;\n readonly website?: string;\n /**\n * The structured location. The profile API sends a display string here\n * instead; the emitter then reads the primary entry of `locations`.\n */\n readonly location?: LocationValue | string | null;\n readonly locations?: ProfileLocation[];\n readonly positions?: ProfilePosition[];\n readonly education?: ProfileEducation[];\n readonly skills?: ProfileSkill[];\n readonly certifications?: ProfileCertification[];\n readonly projects?: ProfileProject[];\n readonly publications?: ProfilePublication[];\n readonly volunteering?: ProfileVolunteering[];\n readonly honors?: ProfileHonor[];\n readonly languages?: ProfileLanguage[];\n readonly externalAccounts?: ExternalAccount[];\n}\n\nexport interface JsonResumeLocation {\n /** Street address; lines are separated by `\\n`, as the schema asks. */\n readonly address?: string;\n readonly city?: string;\n readonly region?: string;\n readonly countryCode?: string;\n readonly postalCode?: string;\n}\n\nexport interface JsonResumeProfileLink {\n readonly network: string;\n readonly username?: string;\n readonly url?: string;\n}\n\nexport interface JsonResumeBasics {\n readonly name: string;\n readonly label?: string;\n readonly image?: string;\n readonly url?: string;\n readonly email?: string;\n readonly phone?: string;\n readonly summary?: string;\n readonly location?: JsonResumeLocation;\n readonly profiles?: JsonResumeProfileLink[];\n}\n\nexport interface JsonResumeWork {\n readonly name?: string;\n readonly position: string;\n readonly summary?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeVolunteer {\n /**\n * Omitted when the record carries no organization name at all. Emitting an\n * empty string would render as a blank organization line in a CV template.\n */\n readonly organization?: string;\n readonly position?: string;\n readonly summary?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeEducation {\n /** Omitted when the record carries no institution name. See volunteer. */\n readonly institution?: string;\n readonly area?: string;\n readonly studyType?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeAward {\n readonly title: string;\n readonly date?: string;\n readonly awarder?: string;\n readonly summary?: string;\n}\n\nexport interface JsonResumeCertificate {\n readonly name: string;\n readonly date?: string;\n readonly issuer?: string;\n readonly url?: string;\n}\n\nexport interface JsonResumePublication {\n readonly name: string;\n readonly publisher?: string;\n readonly releaseDate?: string;\n readonly url?: string;\n readonly summary?: string;\n}\n\nexport interface JsonResumeSkill {\n readonly name: string;\n}\n\nexport interface JsonResumeLanguage {\n readonly language: string;\n readonly fluency?: string;\n}\n\nexport interface JsonResumeProject {\n readonly name: string;\n readonly description?: string;\n readonly url?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeMeta {\n readonly canonical: string;\n readonly version: string;\n readonly lastModified?: string;\n}\n\nexport interface JsonResume {\n readonly basics?: JsonResumeBasics;\n readonly work?: JsonResumeWork[];\n readonly volunteer?: JsonResumeVolunteer[];\n readonly education?: JsonResumeEducation[];\n readonly awards?: JsonResumeAward[];\n readonly certificates?: JsonResumeCertificate[];\n readonly publications?: JsonResumePublication[];\n readonly skills?: JsonResumeSkill[];\n readonly languages?: JsonResumeLanguage[];\n readonly projects?: JsonResumeProject[];\n readonly meta?: JsonResumeMeta;\n}\n\n/** Schema version this emitter targets, as JSON Resume writes it in `meta`. */\nconst SCHEMA_VERSION = 'v1.0.0';\n\n/**\n * A date JSON Resume will accept: `YYYY`, `YYYY-MM` or `YYYY-MM-DD`. Sifa\n * stores RFC 3339 for some fields, and a full timestamp pasted into a CV\n * template renders as `2020-03-04T00:00:00.000Z` on the page.\n */\nconst RESUME_DATE_RE = /^(\\d{4})(-\\d{2})?(-\\d{2})?/;\n\nexport function toResumeDate(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n if (!trimmed) return undefined;\n const match = RESUME_DATE_RE.exec(trimmed);\n return match ? match[0] : undefined;\n}\n\n/**\n * JSON Resume's `fluency` is free text, so these are display labels rather\n * than a mapped vocabulary. Kept in one place so export and a future import\n * agree on the spelling.\n */\nconst FLUENCY_LABELS: Record<LanguageProficiency, string> = {\n elementary: 'Elementary',\n limited_working: 'Limited working',\n professional_working: 'Professional working',\n full_professional: 'Full professional',\n native: 'Native or bilingual',\n};\n\nconst identity: Sanitizer = (input) => input;\n\n/** Drops keys whose value is undefined, so the document carries no empty slots. */\nfunction compact<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n\n/** Adds the owner's street address to the profile's location. */\nfunction withAddress(\n location: JsonResumeLocation | undefined,\n address: string | undefined,\n): JsonResumeLocation | undefined {\n return address ? { address, ...location } : location;\n}\n\n/** Omits the key entirely when the mapped list came out empty. */\nfunction section<K extends string, T>(key: K, items: T[]): Record<K, T[]> | undefined {\n return items.length > 0 ? ({ [key]: items } as Record<K, T[]>) : undefined;\n}\n\n/**\n * The first of these that yields anything: the structured `location` object;\n * the primary entry of `locations` (or the first entry when none is marked\n * primary), which is what the profile API sends; a display-string `location`,\n * split into city and region. JSON Resume has no field for a country name, so a\n * country-only location maps to nothing.\n */\nfunction mapLocation(\n profile: Pick<JsonResumeProfileInput, 'location' | 'locations'>,\n s: Sanitizer,\n): JsonResumeLocation | undefined {\n const fromValue = (location: LocationValue | null | undefined) =>\n location\n ? compact<JsonResumeLocation>({\n city: text(location.locality || location.city, s),\n region: text(location.region, s),\n countryCode: text(location.countryCode, s),\n postalCode: text(location.postalCode, s),\n })\n : {};\n const entry = profile.locations?.find((l) => l.isPrimary) ?? profile.locations?.[0];\n const candidates: JsonResumeLocation[] = [\n typeof profile.location === 'object' ? fromValue(profile.location) : {},\n entry\n ? compact<JsonResumeLocation>({\n city: text((entry.locationLocality || entry.locationCity) ?? undefined, s),\n region: text(entry.locationRegion ?? undefined, s),\n countryCode: text(entry.countryCode ?? undefined, s),\n })\n : {},\n typeof profile.location === 'string' ? fromValue(parseLocationString(profile.location)) : {},\n ];\n return candidates.find((c) => Object.keys(c).length > 0);\n}\n\n/**\n * A link field as a valid URI. A bare domain (\"example.com\") gets `https://`;\n * anything else that does not parse is left out. JSON Resume declares these as\n * URIs, and strict consumers such as altacv refuse the whole file otherwise.\n */\nfunction link(value: string | undefined, s: Sanitizer): string | undefined {\n // No whitespace belongs in a link; a pasted space made altacv refuse a real\n // ORCID URL that the URL parser itself accepts.\n const t = value?.replace(/\\s+/g, '');\n if (!t) return undefined;\n // Checked before sanitizing, so the sanitizer still sees every value.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(t)) return URL.canParse(t) ? s(t) : undefined;\n const withScheme = `https://${t}`;\n return /^[^\\s/]+\\.[^\\s/]+/.test(t) && URL.canParse(withScheme) ? s(withScheme) : undefined;\n}\n\n/** Trimmed, sanitised, and undefined when there is nothing left. */\nfunction text(value: string | undefined, s: Sanitizer): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? s(trimmed) : undefined;\n}\n\n/**\n * The name to print for a linked organization. `entityName` is the AppView's\n * resolved current name and wins when present, matching how the profile itself\n * renders these.\n */\nfunction orgName(\n entityName: string | undefined,\n stored: string | undefined,\n s: Sanitizer,\n): string | undefined {\n return text(entityName, s) ?? text(stored, s);\n}\n\n/**\n * Build a JSON Resume v1.0.0 document from a Sifa profile.\n *\n * Hidden records are dropped. Keys are omitted rather than emitted empty, so a\n * profile with no publications produces a document with no `publications` key\n * instead of one a reader has to interpret.\n */\nexport function profileToJsonResume(\n profile: JsonResumeProfileInput,\n options: JsonResumeOptions = {},\n): JsonResume {\n const s = options.sanitize ?? identity;\n const baseUrl = normaliseBaseUrl(options.baseUrl);\n const canonical = options.canonicalUrl ?? profileUrl(baseUrl, profile.handle);\n const contact = normalizeExportContact(options.contact ?? {});\n\n const name =\n formatStructuredName(profile.givenName, profile.familyName) ??\n text(profile.displayName, s) ??\n profile.handle;\n\n const profiles = filterHidden(profile.externalAccounts).map((account) =>\n compact<JsonResumeProfileLink>({\n network: s(account.platform),\n username: text(account.label, s),\n url: link(account.url, s),\n }),\n );\n\n const basics = compact<JsonResumeBasics>({\n name,\n label: text(profile.headline, s),\n image: text(profile.avatar, s),\n email: text(contact.email, s),\n phone: text(contact.phone, s),\n url: link(profile.website, s) ?? canonical,\n summary: text(profile.about, s),\n location: withAddress(mapLocation(profile, s), text(contact.address, s)),\n ...section('profiles', profiles),\n });\n\n // Same order as the profile page and the other exports: newest first.\n const work = sortPositions(filterHidden(profile.positions)).map((position) =>\n compact<JsonResumeWork>({\n name: orgName(position.entityName, position.company, s),\n position: s(position.title),\n summary: text(position.description, s),\n startDate: toResumeDate(position.startedAt),\n endDate: toResumeDate(position.endedAt),\n }),\n );\n\n const volunteer = sortByActiveDateRange(filterHidden(profile.volunteering)).map((entry) =>\n compact<JsonResumeVolunteer>({\n organization: orgName(entry.entityName, entry.organization, s),\n position: text(entry.role, s),\n summary: text(entry.description, s),\n startDate: toResumeDate(entry.startDate),\n endDate: toResumeDate(entry.endDate),\n }),\n );\n\n const education = sortEducation(filterHidden(profile.education)).map((entry) =>\n compact<JsonResumeEducation>({\n institution: orgName(entry.entityName, entry.institution, s),\n area: text(entry.fieldOfStudy, s),\n studyType: text(entry.degree, s),\n startDate: toResumeDate(entry.startedAt),\n endDate: toResumeDate(entry.endedAt),\n }),\n );\n\n const awards = sortHonors(filterHidden(profile.honors)).map((honor) =>\n compact<JsonResumeAward>({\n title: s(honor.title),\n date: toResumeDate(honor.date),\n awarder: orgName(honor.entityName, honor.issuer, s),\n summary: text(honor.description, s),\n }),\n );\n\n const certificates = sortCertifications(filterHidden(profile.certifications)).map((cert) =>\n compact<JsonResumeCertificate>({\n name: s(cert.name),\n date: toResumeDate(cert.issueDate),\n // `issuingOrg` is the deprecated read-view alias for `authority`; records\n // indexed before the rename carry only the alias.\n issuer: orgName(cert.entityName, cert.authority ?? cert.issuingOrg, s),\n url: link(cert.credentialUrl, s),\n }),\n );\n\n const publications = sortPublications(filterHidden(profile.publications)).map((publication) =>\n compact<JsonResumePublication>({\n name: s(publication.title),\n publisher: text(publication.publisher, s),\n releaseDate: toResumeDate(publication.date),\n url: link(publication.url, s),\n summary: text(publication.description, s),\n }),\n );\n\n // One entry per skill rather than JSON Resume's grouped shape: Sifa stores one\n // record per skill, and keeping the two symmetric is what makes a round-trip\n // through a third-party editor safe. Category and endorsement counts have no\n // JSON Resume slot and are dropped. Skill records carry no `hidden` flag, so\n // there is nothing to filter here.\n const skills = (profile.skills ?? []).map((skill) => ({ name: s(skill.name) }));\n\n const languages = sortLanguages(filterHidden(profile.languages)).map((language) =>\n compact<JsonResumeLanguage>({\n language: s(language.language),\n fluency: language.proficiency ? FLUENCY_LABELS[language.proficiency] : undefined,\n }),\n );\n\n const projects = sortProjects(filterHidden(profile.projects)).map((project) =>\n compact<JsonResumeProject>({\n name: s(project.name),\n description: text(project.description, s),\n url: link(project.url, s),\n startDate: toResumeDate(project.startDate),\n endDate: toResumeDate(project.endDate),\n }),\n );\n\n return {\n basics,\n ...section('work', work),\n ...section('volunteer', volunteer),\n ...section('education', education),\n ...section('awards', awards),\n ...section('certificates', certificates),\n ...section('publications', publications),\n ...section('skills', skills),\n ...section('languages', languages),\n ...section('projects', projects),\n meta: { canonical, version: SCHEMA_VERSION },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/resume/json-resume.ts"],"sourcesContent":["/**\n * JSON Resume v1.0.0 emitter for a Sifa profile.\n *\n * https://jsonresume.org/schema/\n *\n * One pure function, no fetching, no DOM, no framework -- the same shape as the\n * JSON-LD emitter next door, and for the same reason: sifa.id, page.sifa.id and\n * any future consumer should emit one document from one implementation.\n *\n * Why this format earns its place: it is the lingua franca of CV tooling, so\n * emitting it hands a user every JSON Resume theme, and the Typst CV templates\n * on Typst Universe, without Sifa writing a renderer for any of them.\n *\n * Fields Sifa holds that JSON Resume has no slot for -- endorsements,\n * confirmations, involvement, investments, presentations, courses -- are\n * dropped rather than smuggled into a nearby field. The loss is documented for\n * the user at the point of download.\n */\n\nimport { parseLocationString } from '../format/location-utils.js';\nimport { formatStructuredName } from '../format/pds-utils.js';\nimport { filterHidden } from '../profile/section-model.js';\nimport {\n sortCertifications,\n sortEducation,\n sortHonors,\n sortLanguages,\n sortPositions,\n sortProjects,\n sortPublications,\n} from '../profile/section-sorts.js';\nimport { sortByActiveDateRange } from '../profile/range-sort.js';\nimport type {\n ExternalAccount,\n LanguageProficiency,\n LocationValue,\n ProfileCertification,\n ProfileEducation,\n ProfileHonor,\n ProfileLanguage,\n ProfileLocation,\n ProfilePosition,\n ProfileProject,\n ProfilePublication,\n ProfileSkill,\n ProfileVolunteering,\n} from '../types/index.js';\nimport { normaliseBaseUrl, profileUrl } from '../jsonld/url.js';\nimport { normalizeExportContact, type ExportContact } from './export-contact.js';\n\nexport type Sanitizer = (input: string) => string;\n\nexport interface JsonResumeOptions {\n /** Canonical origin for the profile URL. Defaults to `https://sifa.id`. */\n readonly baseUrl?: string;\n /** Applied to every user-authored string. Defaults to identity. */\n readonly sanitize?: Sanitizer;\n /**\n * Absolute URL of the profile this resume describes. Defaults to\n * `${baseUrl}/p/${handle}`. A personal site served from a custom domain is\n * not at that path.\n */\n readonly canonicalUrl?: string;\n /**\n * Email, phone and postal address to print. Sifa holds none of these; the\n * owner types them on the export page. Without it the resume has no\n * `email`, `phone` or `location.address`. Personal details in the same object\n * (driver's license, right to work, nationality) are left out: JSON Resume\n * has no field for them, and strict consumers such as altacv refuse a file\n * with an unknown key.\n */\n readonly contact?: ExportContact;\n}\n\n/**\n * The subset of `Profile` this emitter reads. Deliberately structural rather\n * than the full `Profile`, so a caller holding a partially hydrated profile --\n * or a test -- does not have to invent forty unrelated fields.\n */\nexport interface JsonResumeProfileInput {\n readonly handle: string;\n readonly displayName?: string;\n readonly givenName?: string;\n readonly familyName?: string;\n readonly avatar?: string;\n readonly headline?: string;\n readonly about?: string;\n readonly website?: string;\n /**\n * The structured location. The profile API sends a display string here\n * instead; the emitter then reads the primary entry of `locations`.\n */\n readonly location?: LocationValue | string | null;\n readonly locations?: ProfileLocation[];\n readonly positions?: ProfilePosition[];\n readonly education?: ProfileEducation[];\n readonly skills?: ProfileSkill[];\n readonly certifications?: ProfileCertification[];\n readonly projects?: ProfileProject[];\n readonly publications?: ProfilePublication[];\n readonly volunteering?: ProfileVolunteering[];\n readonly honors?: ProfileHonor[];\n readonly languages?: ProfileLanguage[];\n readonly externalAccounts?: ExternalAccount[];\n}\n\nexport interface JsonResumeLocation {\n /** Street address; lines are separated by `\\n`, as the schema asks. */\n readonly address?: string;\n readonly city?: string;\n readonly region?: string;\n readonly countryCode?: string;\n readonly postalCode?: string;\n}\n\nexport interface JsonResumeProfileLink {\n readonly network: string;\n readonly username?: string;\n readonly url?: string;\n}\n\nexport interface JsonResumeBasics {\n readonly name: string;\n readonly label?: string;\n readonly image?: string;\n readonly url?: string;\n readonly email?: string;\n readonly phone?: string;\n readonly summary?: string;\n readonly location?: JsonResumeLocation;\n readonly profiles?: JsonResumeProfileLink[];\n}\n\nexport interface JsonResumeWork {\n readonly name?: string;\n readonly position: string;\n readonly summary?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeVolunteer {\n /**\n * Omitted when the record carries no organization name at all. Emitting an\n * empty string would render as a blank organization line in a CV template.\n */\n readonly organization?: string;\n readonly position?: string;\n readonly summary?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeEducation {\n /** Omitted when the record carries no institution name. See volunteer. */\n readonly institution?: string;\n readonly area?: string;\n readonly studyType?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeAward {\n readonly title: string;\n readonly date?: string;\n readonly awarder?: string;\n readonly summary?: string;\n}\n\nexport interface JsonResumeCertificate {\n readonly name: string;\n readonly date?: string;\n /** Expiry date. Not in the JSON Resume schema, which allows extra fields. */\n readonly endDate?: string;\n readonly issuer?: string;\n readonly url?: string;\n}\n\nexport interface JsonResumePublication {\n readonly name: string;\n readonly publisher?: string;\n readonly releaseDate?: string;\n readonly url?: string;\n readonly summary?: string;\n}\n\nexport interface JsonResumeSkill {\n readonly name: string;\n}\n\nexport interface JsonResumeLanguage {\n readonly language: string;\n readonly fluency?: string;\n}\n\nexport interface JsonResumeProject {\n readonly name: string;\n readonly description?: string;\n readonly url?: string;\n readonly startDate?: string;\n readonly endDate?: string;\n}\n\nexport interface JsonResumeMeta {\n readonly canonical: string;\n readonly version: string;\n readonly lastModified?: string;\n}\n\nexport interface JsonResume {\n readonly basics?: JsonResumeBasics;\n readonly work?: JsonResumeWork[];\n readonly volunteer?: JsonResumeVolunteer[];\n readonly education?: JsonResumeEducation[];\n readonly awards?: JsonResumeAward[];\n readonly certificates?: JsonResumeCertificate[];\n readonly publications?: JsonResumePublication[];\n readonly skills?: JsonResumeSkill[];\n readonly languages?: JsonResumeLanguage[];\n readonly projects?: JsonResumeProject[];\n readonly meta?: JsonResumeMeta;\n}\n\n/** Schema version this emitter targets, as JSON Resume writes it in `meta`. */\nconst SCHEMA_VERSION = 'v1.0.0';\n\n/**\n * A date JSON Resume will accept: `YYYY`, `YYYY-MM` or `YYYY-MM-DD`. Sifa\n * stores RFC 3339 for some fields, and a full timestamp pasted into a CV\n * template renders as `2020-03-04T00:00:00.000Z` on the page.\n */\nconst RESUME_DATE_RE = /^(\\d{4})(-\\d{2})?(-\\d{2})?/;\n\nexport function toResumeDate(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n if (!trimmed) return undefined;\n const match = RESUME_DATE_RE.exec(trimmed);\n return match ? match[0] : undefined;\n}\n\n/**\n * JSON Resume's `fluency` is free text, so these are display labels rather\n * than a mapped vocabulary. Kept in one place so export and a future import\n * agree on the spelling.\n */\nconst FLUENCY_LABELS: Record<LanguageProficiency, string> = {\n elementary: 'Elementary',\n limited_working: 'Limited working',\n professional_working: 'Professional working',\n full_professional: 'Full professional',\n native: 'Native or bilingual',\n};\n\nconst identity: Sanitizer = (input) => input;\n\n/** Drops keys whose value is undefined, so the document carries no empty slots. */\nfunction compact<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n\n/** Adds the owner's street address to the profile's location. */\nfunction withAddress(\n location: JsonResumeLocation | undefined,\n address: string | undefined,\n): JsonResumeLocation | undefined {\n return address ? { address, ...location } : location;\n}\n\n/** Omits the key entirely when the mapped list came out empty. */\nfunction section<K extends string, T>(key: K, items: T[]): Record<K, T[]> | undefined {\n return items.length > 0 ? ({ [key]: items } as Record<K, T[]>) : undefined;\n}\n\n/**\n * The first of these that yields anything: the structured `location` object;\n * the primary entry of `locations` (or the first entry when none is marked\n * primary), which is what the profile API sends; a display-string `location`,\n * split into city and region. JSON Resume has no field for a country name, so a\n * country-only location maps to nothing.\n */\nfunction mapLocation(\n profile: Pick<JsonResumeProfileInput, 'location' | 'locations'>,\n s: Sanitizer,\n): JsonResumeLocation | undefined {\n const fromValue = (location: LocationValue | null | undefined) =>\n location\n ? compact<JsonResumeLocation>({\n city: text(location.locality || location.city, s),\n region: text(location.region, s),\n countryCode: text(location.countryCode, s),\n postalCode: text(location.postalCode, s),\n })\n : {};\n const entry = profile.locations?.find((l) => l.isPrimary) ?? profile.locations?.[0];\n const candidates: JsonResumeLocation[] = [\n typeof profile.location === 'object' ? fromValue(profile.location) : {},\n entry\n ? compact<JsonResumeLocation>({\n city: text((entry.locationLocality || entry.locationCity) ?? undefined, s),\n region: text(entry.locationRegion ?? undefined, s),\n countryCode: text(entry.countryCode ?? undefined, s),\n })\n : {},\n typeof profile.location === 'string' ? fromValue(parseLocationString(profile.location)) : {},\n ];\n return candidates.find((c) => Object.keys(c).length > 0);\n}\n\n/**\n * A link field as a valid URI. A bare domain (\"example.com\") gets `https://`;\n * anything else that does not parse is left out. JSON Resume declares these as\n * URIs, and strict consumers such as altacv refuse the whole file otherwise.\n */\nfunction link(value: string | undefined, s: Sanitizer): string | undefined {\n // No whitespace belongs in a link; a pasted space made altacv refuse a real\n // ORCID URL that the URL parser itself accepts.\n const t = value?.replace(/\\s+/g, '');\n if (!t) return undefined;\n // Checked before sanitizing, so the sanitizer still sees every value.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(t)) return URL.canParse(t) ? s(t) : undefined;\n const withScheme = `https://${t}`;\n return /^[^\\s/]+\\.[^\\s/]+/.test(t) && URL.canParse(withScheme) ? s(withScheme) : undefined;\n}\n\n/** Trimmed, sanitised, and undefined when there is nothing left. */\nfunction text(value: string | undefined, s: Sanitizer): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? s(trimmed) : undefined;\n}\n\n/**\n * The name to print for a linked organization. `entityName` is the AppView's\n * resolved current name and wins when present, matching how the profile itself\n * renders these.\n */\nfunction orgName(\n entityName: string | undefined,\n stored: string | undefined,\n s: Sanitizer,\n): string | undefined {\n return text(entityName, s) ?? text(stored, s);\n}\n\n/**\n * Build a JSON Resume v1.0.0 document from a Sifa profile.\n *\n * Hidden records are dropped. Keys are omitted rather than emitted empty, so a\n * profile with no publications produces a document with no `publications` key\n * instead of one a reader has to interpret.\n */\nexport function profileToJsonResume(\n profile: JsonResumeProfileInput,\n options: JsonResumeOptions = {},\n): JsonResume {\n const s = options.sanitize ?? identity;\n const baseUrl = normaliseBaseUrl(options.baseUrl);\n const canonical = options.canonicalUrl ?? profileUrl(baseUrl, profile.handle);\n const contact = normalizeExportContact(options.contact ?? {});\n\n const name =\n formatStructuredName(profile.givenName, profile.familyName) ??\n text(profile.displayName, s) ??\n profile.handle;\n\n const profiles = filterHidden(profile.externalAccounts).map((account) =>\n compact<JsonResumeProfileLink>({\n network: s(account.platform),\n username: text(account.label, s),\n url: link(account.url, s),\n }),\n );\n\n const basics = compact<JsonResumeBasics>({\n name,\n label: text(profile.headline, s),\n image: text(profile.avatar, s),\n email: text(contact.email, s),\n phone: text(contact.phone, s),\n url: link(profile.website, s) ?? canonical,\n summary: text(profile.about, s),\n location: withAddress(mapLocation(profile, s), text(contact.address, s)),\n ...section('profiles', profiles),\n });\n\n // Same order as the profile page and the other exports: newest first.\n const work = sortPositions(filterHidden(profile.positions)).map((position) =>\n compact<JsonResumeWork>({\n name: orgName(position.entityName, position.company, s),\n position: s(position.title),\n summary: text(position.description, s),\n startDate: toResumeDate(position.startedAt),\n endDate: toResumeDate(position.endedAt),\n }),\n );\n\n const volunteer = sortByActiveDateRange(filterHidden(profile.volunteering)).map((entry) =>\n compact<JsonResumeVolunteer>({\n organization: orgName(entry.entityName, entry.organization, s),\n position: text(entry.role, s),\n summary: text(entry.description, s),\n startDate: toResumeDate(entry.startDate),\n endDate: toResumeDate(entry.endDate),\n }),\n );\n\n const education = sortEducation(filterHidden(profile.education)).map((entry) =>\n compact<JsonResumeEducation>({\n institution: orgName(entry.entityName, entry.institution, s),\n area: text(entry.fieldOfStudy, s),\n studyType: text(entry.degree, s),\n startDate: toResumeDate(entry.startedAt),\n endDate: toResumeDate(entry.endedAt),\n }),\n );\n\n const awards = sortHonors(filterHidden(profile.honors)).map((honor) =>\n compact<JsonResumeAward>({\n title: s(honor.title),\n date: toResumeDate(honor.date),\n awarder: orgName(honor.entityName, honor.issuer, s),\n summary: text(honor.description, s),\n }),\n );\n\n const certificates = sortCertifications(filterHidden(profile.certifications)).map((cert) =>\n compact<JsonResumeCertificate>({\n name: s(cert.name),\n date: toResumeDate(cert.issueDate),\n endDate: toResumeDate(cert.expiryDate),\n // `issuingOrg` is the deprecated read-view alias for `authority`; records\n // indexed before the rename carry only the alias.\n issuer: orgName(cert.entityName, cert.authority ?? cert.issuingOrg, s),\n url: link(cert.credentialUrl, s),\n }),\n );\n\n const publications = sortPublications(filterHidden(profile.publications)).map((publication) =>\n compact<JsonResumePublication>({\n name: s(publication.title),\n publisher: text(publication.publisher, s),\n releaseDate: toResumeDate(publication.date),\n url: link(publication.url, s),\n summary: text(publication.description, s),\n }),\n );\n\n // One entry per skill rather than JSON Resume's grouped shape: Sifa stores one\n // record per skill, and keeping the two symmetric is what makes a round-trip\n // through a third-party editor safe. Category and endorsement counts have no\n // JSON Resume slot and are dropped. Skill records carry no `hidden` flag, so\n // there is nothing to filter here.\n const skills = (profile.skills ?? []).map((skill) => ({ name: s(skill.name) }));\n\n const languages = sortLanguages(filterHidden(profile.languages)).map((language) =>\n compact<JsonResumeLanguage>({\n language: s(language.language),\n fluency: language.proficiency ? FLUENCY_LABELS[language.proficiency] : undefined,\n }),\n );\n\n const projects = sortProjects(filterHidden(profile.projects)).map((project) =>\n compact<JsonResumeProject>({\n name: s(project.name),\n description: text(project.description, s),\n url: link(project.url, s),\n startDate: toResumeDate(project.startDate),\n endDate: toResumeDate(project.endDate),\n }),\n );\n\n return {\n basics,\n ...section('work', work),\n ...section('volunteer', volunteer),\n ...section('education', education),\n ...section('awards', awards),\n ...section('certificates', certificates),\n ...section('publications', publications),\n ...section('skills', skills),\n ...section('languages', languages),\n ...section('projects', projects),\n meta: { canonical, version: SCHEMA_VERSION },\n };\n}\n"],"mappings":"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","names":[]}
@@ -241,6 +241,7 @@ var streamCardVMSchema = z.lazy(
241
241
  timestamp: z.string(),
242
242
  title: z.string(),
243
243
  sourceUrl: z.string().optional(),
244
+ sourceUrlIsProfile: z.boolean().optional(),
244
245
  body: streamCardBodySchema.optional(),
245
246
  media: z.array(streamMediaSchema).optional(),
246
247
  externalLink: streamExternalLinkSchema.optional(),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/stream/stream-card-vm-schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport type {\n StreamAddress,\n StreamAuthor,\n StreamCardBody,\n StreamCardSubject,\n StreamCardVM,\n StreamExternalLink,\n StreamGeo,\n StreamMedia,\n StreamRichSegment,\n StreamSource,\n StreamTheme,\n} from './stream-card-vm.js';\nimport { streamVerbSchema } from './verb-schema.js';\n\n/** RGB channel: finite number in [0, 255] (mirrors `isValidRgbColor`). */\nconst rgbChannelSchema = z.number().min(0).max(255);\nconst rgbColorSchema = z.object({\n r: rgbChannelSchema,\n g: rgbChannelSchema,\n b: rgbChannelSchema,\n});\n\nconst aspectRatioSchema = z.object({\n width: z.number(),\n height: z.number(),\n});\n\nexport const streamSourceSchema: z.ZodType<StreamSource> = z.object({\n appId: z.string(),\n label: z.string(),\n color: z.string(),\n});\n\nexport const streamAuthorSchema: z.ZodType<StreamAuthor> = z.object({\n did: z.string().optional(),\n handle: z.string().optional(),\n displayName: z.string().optional(),\n avatar: z.string().optional(),\n});\n\nexport const streamThemeSchema: z.ZodType<StreamTheme> = z.object({\n background: rgbColorSchema,\n foreground: rgbColorSchema,\n accent: rgbColorSchema,\n});\n\nconst streamMediaResolvedSchema = z.object({\n url: z.string(),\n alt: z.string(),\n aspectRatio: aspectRatioSchema.optional(),\n mimeType: z.string().optional(),\n});\n\nconst streamMediaBlobSchema = z.object({\n did: z.string(),\n cid: z.string(),\n alt: z.string(),\n aspectRatio: aspectRatioSchema.optional(),\n mimeType: z.string().optional(),\n size: z.number().int().nonnegative().optional(),\n});\n\nexport const streamMediaSchema: z.ZodType<StreamMedia> = z.union([\n streamMediaResolvedSchema,\n streamMediaBlobSchema,\n]);\n\nexport const streamExternalLinkSchema: z.ZodType<StreamExternalLink> = z.object({\n url: z.string(),\n title: z.string().optional(),\n thumb: z.string().optional(),\n});\n\nexport const streamGeoSchema: z.ZodType<StreamGeo> = z.object({\n latitude: z.number(),\n longitude: z.number(),\n});\n\nexport const streamAddressSchema: z.ZodType<StreamAddress> = z.object({\n name: z.string().optional(),\n street: z.string().optional(),\n locality: z.string().optional(),\n region: z.string().optional(),\n country: z.string().optional(),\n postalCode: z.string().optional(),\n});\n\nexport const streamRichSegmentSchema: z.ZodType<StreamRichSegment> = z.object({\n text: z.string(),\n link: z.string().optional(),\n mention: z.string().optional(),\n tag: z.string().optional(),\n});\n\nexport const streamCardBodySchema: z.ZodType<StreamCardBody> = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('text'),\n text: z.string(),\n richSegments: z.array(streamRichSegmentSchema).optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('media'),\n text: z.string().optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('link'),\n text: z.string().optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('track'),\n text: z.string().optional(),\n trackTitle: z.string().optional(),\n artist: z.string().optional(),\n }),\n z.object({\n kind: z.literal('generic'),\n text: z.string().optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('github-pr'),\n repoOwner: z.string(),\n repoName: z.string(),\n prNumber: z.number(),\n title: z.string(),\n url: z.string().optional(),\n language: z.string().optional(),\n additions: z.number(),\n deletions: z.number(),\n mergedAt: z.string().optional(),\n }),\n z.object({\n kind: z.literal('book'),\n title: z.string(),\n authors: z.array(z.string()),\n stars: z.number().optional(),\n status: z.string().optional(),\n review: z.string().optional(),\n }),\n z.object({\n kind: z.literal('media-review'),\n reviewKind: z.enum(['review', 'post', 'note', 'other']),\n title: z.string().optional(),\n mediaType: z.string().optional(),\n rating: z.number().optional(),\n mainCredit: z.string().optional(),\n reviewText: z.string().optional(),\n isRevisit: z.boolean(),\n }),\n z.object({\n kind: z.literal('event-rsvp'),\n rsvpStatus: z.enum(['going', 'interested', 'notgoing', 'unknown']),\n eventName: z.string().optional(),\n startsAt: z.string().optional(),\n endsAt: z.string().optional(),\n mode: z.enum(['inperson', 'virtual', 'hybrid']).optional(),\n locationName: z.string().optional(),\n locationLocality: z.string().optional(),\n locationCountry: z.string().optional(),\n }),\n z.object({\n kind: z.literal('verification'),\n platform: z.string(),\n verified: z.boolean(),\n subjectLabel: z.string().optional(),\n handle: z.string().optional(),\n profileUrl: z.string().optional(),\n }),\n z.object({\n kind: z.literal('membership'),\n communityName: z.string().optional(),\n description: z.string().optional(),\n communityUri: z.string().optional(),\n }),\n z.object({\n kind: z.literal('location'),\n venueName: z.string().optional(),\n shout: z.string().optional(),\n address: streamAddressSchema.optional(),\n geo: streamGeoSchema.optional(),\n }),\n z.object({\n kind: z.literal('travel'),\n origin: z.string().optional(),\n destination: z.string().optional(),\n transportation: z.string().optional(),\n carrier: z.string().optional(),\n carrierCode: z.string().optional(),\n startDate: z.string().optional(),\n endDate: z.string().optional(),\n }),\n z.object({\n kind: z.literal('standard-site'),\n title: z.string().optional(),\n description: z.string().optional(),\n siteUrl: z.string().optional(),\n path: z.string().optional(),\n publisherName: z.string().optional(),\n icon: z.string().optional(),\n coverImageUrl: z.string().optional(),\n readingTime: z.number().optional(),\n publishedAt: z.string().optional(),\n }),\n]);\n\n/**\n * A stream item's repost / reply / quote target. The `post` variant nests a\n * full {@link streamCardVMSchema} (deferred via `z.lazy` for recursion).\n */\nexport const streamCardSubjectSchema: z.ZodType<StreamCardSubject> = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('post'),\n post: z.lazy(() => streamCardVMSchema),\n }),\n z.object({\n kind: z.literal('person'),\n did: z.string(),\n handle: z.string().optional(),\n displayName: z.string().optional(),\n avatar: z.string().optional(),\n }),\n z.object({\n kind: z.literal('record'),\n uri: z.string(),\n title: z.string().optional(),\n url: z.string().optional(),\n }),\n]);\n\n/** Hypercert credit roll + outbound links, hydrated by sifa-api. */\nconst hypercertDetailsSchema = z.object({\n contributors: z.array(\n z.object({\n displayName: z.string(),\n imageUrl: z.string().optional(),\n identifier: z.string().optional(),\n weight: z.number().optional(),\n }),\n ),\n contributorCount: z.number(),\n attachments: z.array(z.object({ title: z.string().optional(), url: z.string() })),\n});\n\n/** An account a Certified record points at. */\nconst certifiedActorSchema = z.object({\n did: z.string(),\n handle: z.string(),\n displayName: z.string().optional(),\n avatar: z.string().optional(),\n});\n\n/** Certified badge + resolved accounts, hydrated by sifa-api. */\nconst certifiedDetailsSchema = z.object({\n badge: z\n .object({\n title: z.string(),\n description: z.string().optional(),\n icon: z.string().optional(),\n badgeType: z.string().optional(),\n })\n .optional(),\n recipient: certifiedActorSchema.optional(),\n issuer: certifiedActorSchema.optional(),\n group: certifiedActorSchema.optional(),\n role: z.string().optional(),\n});\n\n/** Zod schema for a {@link StreamCardVM}. Recursive through `subject.post`. */\nexport const streamCardVMSchema: z.ZodType<StreamCardVM> = z.lazy(() =>\n z.object({\n uri: z.string(),\n cid: z.string(),\n verb: streamVerbSchema,\n source: streamSourceSchema,\n author: streamAuthorSchema.optional(),\n tier: z.enum(['creation', 'action', 'filtered']),\n timestamp: z.string(),\n title: z.string(),\n sourceUrl: z.string().optional(),\n body: streamCardBodySchema.optional(),\n media: z.array(streamMediaSchema).optional(),\n externalLink: streamExternalLinkSchema.optional(),\n theme: streamThemeSchema.optional(),\n subject: z.lazy(() => streamCardSubjectSchema).optional(),\n hypercertDetails: hypercertDetailsSchema.optional(),\n certifiedDetails: certifiedDetailsSchema.optional(),\n }),\n);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/stream/stream-card-vm-schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport type {\n StreamAddress,\n StreamAuthor,\n StreamCardBody,\n StreamCardSubject,\n StreamCardVM,\n StreamExternalLink,\n StreamGeo,\n StreamMedia,\n StreamRichSegment,\n StreamSource,\n StreamTheme,\n} from './stream-card-vm.js';\nimport { streamVerbSchema } from './verb-schema.js';\n\n/** RGB channel: finite number in [0, 255] (mirrors `isValidRgbColor`). */\nconst rgbChannelSchema = z.number().min(0).max(255);\nconst rgbColorSchema = z.object({\n r: rgbChannelSchema,\n g: rgbChannelSchema,\n b: rgbChannelSchema,\n});\n\nconst aspectRatioSchema = z.object({\n width: z.number(),\n height: z.number(),\n});\n\nexport const streamSourceSchema: z.ZodType<StreamSource> = z.object({\n appId: z.string(),\n label: z.string(),\n color: z.string(),\n});\n\nexport const streamAuthorSchema: z.ZodType<StreamAuthor> = z.object({\n did: z.string().optional(),\n handle: z.string().optional(),\n displayName: z.string().optional(),\n avatar: z.string().optional(),\n});\n\nexport const streamThemeSchema: z.ZodType<StreamTheme> = z.object({\n background: rgbColorSchema,\n foreground: rgbColorSchema,\n accent: rgbColorSchema,\n});\n\nconst streamMediaResolvedSchema = z.object({\n url: z.string(),\n alt: z.string(),\n aspectRatio: aspectRatioSchema.optional(),\n mimeType: z.string().optional(),\n});\n\nconst streamMediaBlobSchema = z.object({\n did: z.string(),\n cid: z.string(),\n alt: z.string(),\n aspectRatio: aspectRatioSchema.optional(),\n mimeType: z.string().optional(),\n size: z.number().int().nonnegative().optional(),\n});\n\nexport const streamMediaSchema: z.ZodType<StreamMedia> = z.union([\n streamMediaResolvedSchema,\n streamMediaBlobSchema,\n]);\n\nexport const streamExternalLinkSchema: z.ZodType<StreamExternalLink> = z.object({\n url: z.string(),\n title: z.string().optional(),\n thumb: z.string().optional(),\n});\n\nexport const streamGeoSchema: z.ZodType<StreamGeo> = z.object({\n latitude: z.number(),\n longitude: z.number(),\n});\n\nexport const streamAddressSchema: z.ZodType<StreamAddress> = z.object({\n name: z.string().optional(),\n street: z.string().optional(),\n locality: z.string().optional(),\n region: z.string().optional(),\n country: z.string().optional(),\n postalCode: z.string().optional(),\n});\n\nexport const streamRichSegmentSchema: z.ZodType<StreamRichSegment> = z.object({\n text: z.string(),\n link: z.string().optional(),\n mention: z.string().optional(),\n tag: z.string().optional(),\n});\n\nexport const streamCardBodySchema: z.ZodType<StreamCardBody> = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('text'),\n text: z.string(),\n richSegments: z.array(streamRichSegmentSchema).optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('media'),\n text: z.string().optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('link'),\n text: z.string().optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('track'),\n text: z.string().optional(),\n trackTitle: z.string().optional(),\n artist: z.string().optional(),\n }),\n z.object({\n kind: z.literal('generic'),\n text: z.string().optional(),\n tags: z.array(z.string()).optional(),\n }),\n z.object({\n kind: z.literal('github-pr'),\n repoOwner: z.string(),\n repoName: z.string(),\n prNumber: z.number(),\n title: z.string(),\n url: z.string().optional(),\n language: z.string().optional(),\n additions: z.number(),\n deletions: z.number(),\n mergedAt: z.string().optional(),\n }),\n z.object({\n kind: z.literal('book'),\n title: z.string(),\n authors: z.array(z.string()),\n stars: z.number().optional(),\n status: z.string().optional(),\n review: z.string().optional(),\n }),\n z.object({\n kind: z.literal('media-review'),\n reviewKind: z.enum(['review', 'post', 'note', 'other']),\n title: z.string().optional(),\n mediaType: z.string().optional(),\n rating: z.number().optional(),\n mainCredit: z.string().optional(),\n reviewText: z.string().optional(),\n isRevisit: z.boolean(),\n }),\n z.object({\n kind: z.literal('event-rsvp'),\n rsvpStatus: z.enum(['going', 'interested', 'notgoing', 'unknown']),\n eventName: z.string().optional(),\n startsAt: z.string().optional(),\n endsAt: z.string().optional(),\n mode: z.enum(['inperson', 'virtual', 'hybrid']).optional(),\n locationName: z.string().optional(),\n locationLocality: z.string().optional(),\n locationCountry: z.string().optional(),\n }),\n z.object({\n kind: z.literal('verification'),\n platform: z.string(),\n verified: z.boolean(),\n subjectLabel: z.string().optional(),\n handle: z.string().optional(),\n profileUrl: z.string().optional(),\n }),\n z.object({\n kind: z.literal('membership'),\n communityName: z.string().optional(),\n description: z.string().optional(),\n communityUri: z.string().optional(),\n }),\n z.object({\n kind: z.literal('location'),\n venueName: z.string().optional(),\n shout: z.string().optional(),\n address: streamAddressSchema.optional(),\n geo: streamGeoSchema.optional(),\n }),\n z.object({\n kind: z.literal('travel'),\n origin: z.string().optional(),\n destination: z.string().optional(),\n transportation: z.string().optional(),\n carrier: z.string().optional(),\n carrierCode: z.string().optional(),\n startDate: z.string().optional(),\n endDate: z.string().optional(),\n }),\n z.object({\n kind: z.literal('standard-site'),\n title: z.string().optional(),\n description: z.string().optional(),\n siteUrl: z.string().optional(),\n path: z.string().optional(),\n publisherName: z.string().optional(),\n icon: z.string().optional(),\n coverImageUrl: z.string().optional(),\n readingTime: z.number().optional(),\n publishedAt: z.string().optional(),\n }),\n]);\n\n/**\n * A stream item's repost / reply / quote target. The `post` variant nests a\n * full {@link streamCardVMSchema} (deferred via `z.lazy` for recursion).\n */\nexport const streamCardSubjectSchema: z.ZodType<StreamCardSubject> = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('post'),\n post: z.lazy(() => streamCardVMSchema),\n }),\n z.object({\n kind: z.literal('person'),\n did: z.string(),\n handle: z.string().optional(),\n displayName: z.string().optional(),\n avatar: z.string().optional(),\n }),\n z.object({\n kind: z.literal('record'),\n uri: z.string(),\n title: z.string().optional(),\n url: z.string().optional(),\n }),\n]);\n\n/** Hypercert credit roll + outbound links, hydrated by sifa-api. */\nconst hypercertDetailsSchema = z.object({\n contributors: z.array(\n z.object({\n displayName: z.string(),\n imageUrl: z.string().optional(),\n identifier: z.string().optional(),\n weight: z.number().optional(),\n }),\n ),\n contributorCount: z.number(),\n attachments: z.array(z.object({ title: z.string().optional(), url: z.string() })),\n});\n\n/** An account a Certified record points at. */\nconst certifiedActorSchema = z.object({\n did: z.string(),\n handle: z.string(),\n displayName: z.string().optional(),\n avatar: z.string().optional(),\n});\n\n/** Certified badge + resolved accounts, hydrated by sifa-api. */\nconst certifiedDetailsSchema = z.object({\n badge: z\n .object({\n title: z.string(),\n description: z.string().optional(),\n icon: z.string().optional(),\n badgeType: z.string().optional(),\n })\n .optional(),\n recipient: certifiedActorSchema.optional(),\n issuer: certifiedActorSchema.optional(),\n group: certifiedActorSchema.optional(),\n role: z.string().optional(),\n});\n\n/** Zod schema for a {@link StreamCardVM}. Recursive through `subject.post`. */\nexport const streamCardVMSchema: z.ZodType<StreamCardVM> = z.lazy(() =>\n z.object({\n uri: z.string(),\n cid: z.string(),\n verb: streamVerbSchema,\n source: streamSourceSchema,\n author: streamAuthorSchema.optional(),\n tier: z.enum(['creation', 'action', 'filtered']),\n timestamp: z.string(),\n title: z.string(),\n sourceUrl: z.string().optional(),\n sourceUrlIsProfile: z.boolean().optional(),\n body: streamCardBodySchema.optional(),\n media: z.array(streamMediaSchema).optional(),\n externalLink: streamExternalLinkSchema.optional(),\n theme: streamThemeSchema.optional(),\n subject: z.lazy(() => streamCardSubjectSchema).optional(),\n hypercertDetails: hypercertDetailsSchema.optional(),\n certifiedDetails: certifiedDetailsSchema.optional(),\n }),\n);\n"],"mappings":"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","names":[]}
@@ -3,10 +3,14 @@ import { streamCardContent } from "./stream-card-content.js";
3
3
  import { isSelfAuthoredRich } from "./two-tier.js";
4
4
  function destination(vm) {
5
5
  const subject = vm.subject;
6
+ const own = vm.sourceUrlIsProfile && vm.verb !== "joined" ? void 0 : vm.sourceUrl;
6
7
  if (subject?.kind === "person") return subject.handle ? `/p/${subject.handle}` : null;
7
- if (subject?.kind === "record") return subject.url ?? vm.sourceUrl ?? null;
8
- if (subject?.kind === "post") return subject.post.sourceUrl ?? vm.sourceUrl ?? null;
9
- return vm.sourceUrl ?? vm.externalLink?.url ?? null;
8
+ if (subject?.kind === "record") return subject.url ?? own ?? null;
9
+ if (subject?.kind === "post") {
10
+ const nested = subject.post.sourceUrlIsProfile ? void 0 : subject.post.sourceUrl;
11
+ return nested ?? own ?? null;
12
+ }
13
+ return own ?? vm.externalLink?.url ?? null;
10
14
  }
11
15
  function isTimelineWorthy(vm) {
12
16
  if (!destination(vm)) return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/stream/timeline-worthy.ts"],"sourcesContent":["import { streamCardContent } from './stream-card-content.js';\nimport type { StreamCardVM } from './stream-card-vm.js';\nimport { isSelfAuthoredRich } from './two-tier.js';\n\n/**\n * Where a reader lands when they open this item, or null when nowhere: the\n * subject it acted on (a person's profile, a referenced record or post), else\n * the record's own permalink, else its external link card.\n */\nfunction destination(vm: StreamCardVM): string | null {\n const subject = vm.subject;\n if (subject?.kind === 'person') return subject.handle ? `/p/${subject.handle}` : null;\n if (subject?.kind === 'record') return subject.url ?? vm.sourceUrl ?? null;\n if (subject?.kind === 'post') return subject.post.sourceUrl ?? vm.sourceUrl ?? null;\n return vm.sourceUrl ?? vm.externalLink?.url ?? null;\n}\n\n/**\n * Whether an item belongs on the multi-author timeline: it says concretely what\n * the person did AND links to it. A rich card (a photo, a book log) is concrete\n * by its content; a line needs content after its verb (a subject or the\n * record's own text / title), not a bare \"Shared\". A membership is the\n * exception: the app it joined is its object. Items that fail either half\n * read as noise (\"Shared\" with nothing to open), so the timeline drops them.\n * Single-author surfaces (a profile's own history) do not apply this.\n */\nexport function isTimelineWorthy(vm: StreamCardVM): boolean {\n if (!destination(vm)) return false;\n if (isSelfAuthoredRich(vm)) return true;\n // \"Joined\" needs no object: the app pill next to it names what was joined\n // (\"Joined\" + AT Commons).\n if (vm.verb === 'joined') return true;\n return streamCardContent(vm).text !== null;\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAElC,SAAS,0BAA0B;AAOnC,SAAS,YAAY,IAAiC;AACpD,QAAM,UAAU,GAAG;AACnB,MAAI,SAAS,SAAS,SAAU,QAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM,KAAK;AACjF,MAAI,SAAS,SAAS,SAAU,QAAO,QAAQ,OAAO,GAAG,aAAa;AACtE,MAAI,SAAS,SAAS,OAAQ,QAAO,QAAQ,KAAK,aAAa,GAAG,aAAa;AAC/E,SAAO,GAAG,aAAa,GAAG,cAAc,OAAO;AACjD;AAWO,SAAS,iBAAiB,IAA2B;AAC1D,MAAI,CAAC,YAAY,EAAE,EAAG,QAAO;AAC7B,MAAI,mBAAmB,EAAE,EAAG,QAAO;AAGnC,MAAI,GAAG,SAAS,SAAU,QAAO;AACjC,SAAO,kBAAkB,EAAE,EAAE,SAAS;AACxC;","names":[]}
1
+ {"version":3,"sources":["../../src/stream/timeline-worthy.ts"],"sourcesContent":["import { streamCardContent } from './stream-card-content.js';\nimport type { StreamCardVM } from './stream-card-vm.js';\nimport { isSelfAuthoredRich } from './two-tier.js';\n\n/**\n * Where a reader lands when they open this item, or null when nowhere: the\n * subject it acted on (a person's profile, a referenced record or post), else\n * the record's own permalink, else its external link card.\n */\nfunction destination(vm: StreamCardVM): string | null {\n const subject = vm.subject;\n // A profile or app homepage is not where the item lives.\n // A membership is the exception: the app or community it joined is its\n // object, so its members page or homepage is the right place to land.\n const own = vm.sourceUrlIsProfile && vm.verb !== 'joined' ? undefined : vm.sourceUrl;\n if (subject?.kind === 'person') return subject.handle ? `/p/${subject.handle}` : null;\n if (subject?.kind === 'record') return subject.url ?? own ?? null;\n if (subject?.kind === 'post') {\n const nested = subject.post.sourceUrlIsProfile ? undefined : subject.post.sourceUrl;\n return nested ?? own ?? null;\n }\n return own ?? vm.externalLink?.url ?? null;\n}\n\n/**\n * Whether an item belongs on the multi-author timeline: it says concretely what\n * the person did AND links to it. A rich card (a photo, a book log) is concrete\n * by its content; a line needs content after its verb (a subject or the\n * record's own text / title), not a bare \"Shared\". A membership is the\n * exception: the app it joined is its object. Items that fail either half\n * read as noise (\"Shared\" with nothing to open), so the timeline drops them.\n * Single-author surfaces (a profile's own history) do not apply this.\n */\nexport function isTimelineWorthy(vm: StreamCardVM): boolean {\n if (!destination(vm)) return false;\n if (isSelfAuthoredRich(vm)) return true;\n // \"Joined\" needs no object: the app pill next to it names what was joined\n // (\"Joined\" + AT Commons).\n if (vm.verb === 'joined') return true;\n return streamCardContent(vm).text !== null;\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAElC,SAAS,0BAA0B;AAOnC,SAAS,YAAY,IAAiC;AACpD,QAAM,UAAU,GAAG;AAInB,QAAM,MAAM,GAAG,sBAAsB,GAAG,SAAS,WAAW,SAAY,GAAG;AAC3E,MAAI,SAAS,SAAS,SAAU,QAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM,KAAK;AACjF,MAAI,SAAS,SAAS,SAAU,QAAO,QAAQ,OAAO,OAAO;AAC7D,MAAI,SAAS,SAAS,QAAQ;AAC5B,UAAM,SAAS,QAAQ,KAAK,qBAAqB,SAAY,QAAQ,KAAK;AAC1E,WAAO,UAAU,OAAO;AAAA,EAC1B;AACA,SAAO,OAAO,GAAG,cAAc,OAAO;AACxC;AAWO,SAAS,iBAAiB,IAA2B;AAC1D,MAAI,CAAC,YAAY,EAAE,EAAG,QAAO;AAC7B,MAAI,mBAAmB,EAAE,EAAG,QAAO;AAGnC,MAAI,GAAG,SAAS,SAAU,QAAO;AACjC,SAAO,kBAAkB,EAAE,EAAE,SAAS;AACxC;","names":[]}
@@ -1,5 +1,5 @@
1
1
  // src/stream/to-stream-card-vm.ts
2
- import { resolveCardUrl } from "../cards/resolve-card-url.js";
2
+ import { resolveCardHealth } from "../cards/resolve-card-url.js";
3
3
  import { isVisibleActivityItem } from "../cards/visibility.js";
4
4
  import { isValidRgbColor } from "../format/wcag-contrast.js";
5
5
  import { matchPublisherByUri } from "../publishing/index.js";
@@ -178,11 +178,13 @@ function recordHasSubjectRef(record) {
178
178
  function resolveSourceUrl(item, record) {
179
179
  if (item.collection === "at.marque.domain") {
180
180
  const domain = asNonEmptyString(record.domain);
181
- if (domain && /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(domain)) return `https://${domain}`;
181
+ if (domain && /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(domain)) {
182
+ return { url: `https://${domain}`, isProfile: false };
183
+ }
182
184
  }
183
185
  const authorDid = didFromUri(item.uri) ?? "";
184
186
  const authorHandle = asNonEmptyString(item.authorHandle) ?? asNonEmptyString(record.handle);
185
- const url = resolveCardUrl({
187
+ const health = resolveCardHealth({
186
188
  collection: item.collection,
187
189
  record,
188
190
  uri: item.uri,
@@ -190,7 +192,9 @@ function resolveSourceUrl(item, record) {
190
192
  authorDid,
191
193
  ...authorHandle ? { authorHandle } : {}
192
194
  });
193
- return url && /^https?:\/\//i.test(url) ? url : void 0;
195
+ const url = health.url;
196
+ if (!url || !/^https?:\/\//i.test(url)) return void 0;
197
+ return { url, isProfile: health.isProfileFallback === true };
194
198
  }
195
199
  function readTheme(record) {
196
200
  const theme = asRecord(record?.publicationTheme ?? record?.theme);
@@ -780,8 +784,11 @@ function toStreamCardVM(item, options = {}) {
780
784
  const theme = readTheme(record);
781
785
  if (theme) vm.theme = theme;
782
786
  if (record) {
783
- const sourceUrl = resolveSourceUrl(item, record);
784
- if (sourceUrl) vm.sourceUrl = sourceUrl;
787
+ const source2 = resolveSourceUrl(item, record);
788
+ if (source2) {
789
+ vm.sourceUrl = source2.url;
790
+ if (source2.isProfile) vm.sourceUrlIsProfile = true;
791
+ }
785
792
  }
786
793
  if (item.hypercertDetails) vm.hypercertDetails = item.hypercertDetails;
787
794
  if (item.certifiedDetails) vm.certifiedDetails = item.certifiedDetails;