gedcom-ts 2026.5.1 → 2026.5.3
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.
- package/CHANGELOG.md +29 -3
- package/README.md +306 -120
- package/dist/booklet/booklet-date-fr.d.ts +8 -0
- package/dist/booklet/booklet-estimate.d.ts +11 -0
- package/dist/booklet/booklet-gender.d.ts +23 -0
- package/dist/booklet/booklet-logo.d.ts +19 -0
- package/dist/booklet/booklet-narrative.d.ts +10 -0
- package/dist/booklet/booklet-pdf.d.ts +22 -0
- package/dist/booklet/booklet-person.d.ts +48 -0
- package/dist/booklet/booklet-place-fr.d.ts +24 -0
- package/dist/booklet/booklet-structure.d.ts +23 -0
- package/dist/booklet/booklet-timeline-canvas.d.ts +9 -0
- package/dist/booklet/booklet-timeline.d.ts +31 -0
- package/dist/booklet/gedcom-ts-logo.paths.d.ts +3 -0
- package/dist/booklet/index.d.ts +9 -0
- package/dist/booklet.cjs +1 -0
- package/dist/booklet.mjs +1 -0
- package/dist/geocode/index.d.ts +1 -1
- package/dist/geocode/place-city-utils.d.ts +7 -3
- package/dist/geocode/place-clusters.d.ts +44 -5
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes of gedcom-ts
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [2026.5.3] - 2026-05-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Module `gedcom-ts/booklet`** : génération de livrets généalogiques en PDF (collecte des personnes, récits en français, frises chronologiques, estimation de taille, `generateGenealogyBookletPdf`, `downloadBookletPdf`). Dépendance `pdf-lib` incluse dans ce sous-paquet.
|
|
10
|
+
- **Logo couverture** : chemins SVG `GEDCOM_TS_LOGO_PATHS` / `GEDCOM_TS_LOGO_VIEWBOX` et `drawGedcomTsLogoOnPage` exportés ; logo gedcom-ts sur la couverture par défaut (`coverLogo` dans `BookletPdfOptions`).
|
|
11
|
+
- **Tests** : `tests/booklet/` (date/lieu/genre, logo, PDF smoke).
|
|
12
|
+
- **README** : guide `gedcom-ts/booklet`, double point d’entrée npm.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **`findHarmonizationClustersFromActs`** : si plusieurs libellés similaires partagent une seule position GPS et que tous les actes ont des coordonnées, unifie automatiquement le libellé (libellé le plus long) sans cluster d’harmonisation manuel.
|
|
17
|
+
- Critère d’harmonisation : ne signale plus les seules variantes d’orthographe lorsque les coordonnées sont déjà identiques.
|
|
18
|
+
|
|
19
|
+
## [2026.5.2] - 2026-05-16
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Regroupement des lieux** : `groupActsBySimilarCity`, `actsNeedingGeocodeForCity` (alias géoloc), `similarCityKey` ; harmonisation et carte alignées sur `citiesAreSimilar` (plus de divergence `normalizeCityKey` seul).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **`findHarmonizationClustersFromActs`** : union-find sur `citiesAreSimilar` (corrige le cas Pleurtuit 29+11 actes après géoloc complète).
|
|
28
|
+
- **`clusterKeyForCity`** : clé carte basée sur la localité principale (segment avant la virgule), alignée avec le regroupement par similarité.
|
|
29
|
+
- **README** : guide d’intégration géoloc restructuré (section dédiée, tableaux par cas d’usage) ; retrait de la documentation développement interne.
|
|
30
|
+
|
|
31
|
+
## [2026.5.1] - 2026-05-15
|
|
6
32
|
|
|
7
33
|
### Added
|
|
8
34
|
|
|
@@ -101,7 +127,7 @@ Les entrées historiques du changelog conservent leurs numéros semver d’origi
|
|
|
101
127
|
|
|
102
128
|
- Major GEDCOM 7 coverage push: round-trip preservation (`preservedTopLevelRecords`, label-keyed FAM / NOTE xrefs, level-0 `OBJE`), richer `HEAD` export options, full P1.4 dates and places (INT/EST/CAL, `3 PHRASE`, `3 TIME`, `2 SDATE`, `2 PHRASE` event-level, ISO date input, verbatim fallback).
|
|
103
129
|
- New **fluent edit layer** (`editPerson`, `editAct`, `editActs`, `editDateAct`, `editPlace`, `editNotes` + `*Edit` classes) for in-place model mutations with chainable APIs.
|
|
104
|
-
- **README fully rewritten** with a complete public-API reference and a link to the live graphical demo at **[https://gedcomts.
|
|
130
|
+
- **README fully rewritten** with a complete public-API reference and a link to the live graphical demo at **[https://gedcomts.com](https://gedcomts.com)**.
|
|
105
131
|
|
|
106
132
|
### Added
|
|
107
133
|
|
|
@@ -162,7 +188,7 @@ Les entrées historiques du changelog conservent leurs numéros semver d’origi
|
|
|
162
188
|
#### Documentation
|
|
163
189
|
|
|
164
190
|
- **README intégralement réécrit** : référence complète de l’API publique (un sous-titre par export de `src/index.ts`), tables synthétiques pour `ReadGed`, `GedcomExportOptions` et la couche `*Edit`, exemples mis à jour pour chaque type / classe / fonction exposés.
|
|
165
|
-
- Mention en tête du README du **site de démo graphique** : **[https://gedcomts.
|
|
191
|
+
- Mention en tête du README du **site de démo graphique** : **[https://gedcomts.com](https://gedcomts.com)**.
|
|
166
192
|
|
|
167
193
|
### Changed
|
|
168
194
|
|
package/README.md
CHANGED
|
@@ -6,16 +6,29 @@
|
|
|
6
6
|
- work with a typed JSON model (persons, acts, dates, places, notes, media, name variants, attributes)
|
|
7
7
|
- edit the model in-place through a fluent, chainable API (`editPerson`, `editAct`, …)
|
|
8
8
|
- export data back to GEDCOM (`.ged`) or GEDZIP (`.zip`)
|
|
9
|
+
- geocode event places (OpenStreetMap / Nominatim) and group similar city names
|
|
10
|
+
- generate a **genealogy booklet** as PDF (`gedcom-ts/booklet`, French narratives)
|
|
9
11
|
|
|
10
12
|
## Live demo
|
|
11
13
|
|
|
12
|
-
A graphical demo showcasing the public API (import a `.ged` / `.zip`, browse the typed model, export back) is available at **[https://gedcomts.
|
|
14
|
+
A graphical demo showcasing the public API (import a `.ged` / `.zip`, browse the typed model, export back) is available at **[https://gedcomts.com](https://gedcomts.com)**
|
|
13
15
|
|
|
14
|
-
##
|
|
16
|
+
## Contents
|
|
15
17
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Quick start](#quick-start)
|
|
20
|
+
- [Geocoding places](#geocoding-places)
|
|
21
|
+
- [Genealogy booklet (PDF)](#genealogy-booklet-pdf)
|
|
22
|
+
- [API reference](#api-reference)
|
|
23
|
+
- [Error handling](#error-handling)
|
|
24
|
+
|
|
25
|
+
## Package
|
|
26
|
+
|
|
27
|
+
- NPM: [gedcom-ts](https://www.npmjs.com/package/gedcom-ts)
|
|
28
|
+
- Version format **CalVer** `AAAA.M.micro` (e.g. `2026.5.3` = May 2026). See [CHANGELOG.md](CHANGELOG.md).
|
|
29
|
+
- Entry points:
|
|
30
|
+
- **`gedcom-ts`** — import, model, edit layer, export, geocoding
|
|
31
|
+
- **`gedcom-ts/booklet`** — PDF livret (`pdf-lib` bundled in that chunk)
|
|
19
32
|
|
|
20
33
|
## Installation
|
|
21
34
|
|
|
@@ -23,6 +36,8 @@ A graphical demo showcasing the public API (import a `.ged` / `.zip`, browse the
|
|
|
23
36
|
npm install gedcom-ts
|
|
24
37
|
```
|
|
25
38
|
|
|
39
|
+
Both entry points come from the same package; no extra install for the booklet.
|
|
40
|
+
|
|
26
41
|
## Runtime Requirements
|
|
27
42
|
|
|
28
43
|
- modern browser runtime (`File`, `Blob`, `XMLHttpRequest`, `URL.createObjectURL`)
|
|
@@ -51,9 +66,243 @@ Typical workflow:
|
|
|
51
66
|
2. read / mutate the typed `Person`, `Act`, `Place`, `Note`, `MultimediaFile` objects (directly or through `editPerson` / `editAct` / `editPlace` / …)
|
|
52
67
|
3. export as `.ged` (`ExportGedcomFile`) or `.zip` (`ExportGedzipFile`)
|
|
53
68
|
|
|
54
|
-
##
|
|
69
|
+
## Geocoding places
|
|
70
|
+
|
|
71
|
+
Use this section when your app needs **GPS coordinates** on event places, a **list of cities still without coordinates**, **map markers**, or a **data-quality** view for inconsistent place names.
|
|
72
|
+
|
|
73
|
+
You work with a `ReadGed` (after import) and a flat `Act[]` built from every person’s events (see [Prepare act list](#prepare-act-list)).
|
|
74
|
+
|
|
75
|
+
### How city names are matched
|
|
76
|
+
|
|
77
|
+
The library groups variants of the same place with **`citiesAreSimilar`**, not exact string equality.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
| Label A | Label B | Same place? |
|
|
81
|
+
| ----------- | ------------------------------------ | --------------------------- |
|
|
82
|
+
| `Pleurtuit` | `Pleurtuit, Ille-et-Vilaine, France` | Yes |
|
|
83
|
+
| `Paris` | `Paris, TX, USA` | Depends on similarity rules |
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
Use the functions in the tables below for grouping, geocoding, and maps. Do **not** compare raw strings yourself, and do **not** use `normalizeCityKey` for grouping (spelling normalization only).
|
|
87
|
+
|
|
88
|
+
### Prepare act list
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import type { ReadGed, Act } from "gedcom-ts";
|
|
92
|
+
|
|
93
|
+
function collectAllActs(ged: ReadGed): Act[] {
|
|
94
|
+
const acts: Act[] = [];
|
|
95
|
+
for (const person of ged.persons) {
|
|
96
|
+
for (const act of person.acts.list) acts.push(act);
|
|
97
|
+
}
|
|
98
|
+
return acts;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Geocode one city (typical flow)
|
|
103
|
+
|
|
104
|
+
When the user picks a city and a Nominatim result, update **every similar act that still has no GPS** — not only acts with the exact same label.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
| Step | Function |
|
|
108
|
+
| ---------------------------------------------- | ----------------------------------------------------------- |
|
|
109
|
+
| 1. Context from the tree (countries, centroid) | `inferGeocodeContext(ged)` |
|
|
110
|
+
| 2. Search OpenStreetMap | `searchPlacesWithContext(cityName, context)` |
|
|
111
|
+
| 3. Acts to update | `actsNeedingGeocodeForCity(allActs, cityName)` |
|
|
112
|
+
| 4. Write coordinates | `applyGeocodeCandidateToActs(targets, candidate, cityName)` |
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import {
|
|
117
|
+
inferGeocodeContext,
|
|
118
|
+
searchPlacesWithContext,
|
|
119
|
+
actsNeedingGeocodeForCity,
|
|
120
|
+
applyGeocodeCandidateToActs,
|
|
121
|
+
} from "gedcom-ts";
|
|
122
|
+
import type { ReadGed, Act } from "gedcom-ts";
|
|
123
|
+
|
|
124
|
+
async function geocodeCity(ged: ReadGed, allActs: Act[], cityName: string) {
|
|
125
|
+
const context = inferGeocodeContext(ged);
|
|
126
|
+
const candidates = await searchPlacesWithContext(cityName, context);
|
|
127
|
+
const chosen = candidates[0];
|
|
128
|
+
if (!chosen) return;
|
|
129
|
+
|
|
130
|
+
const targets = actsNeedingGeocodeForCity(allActs, cityName);
|
|
131
|
+
applyGeocodeCandidateToActs(targets, chosen, cityName);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
> **Tip:** Do not pass only acts from a harmonization cluster to `applyGeocodeCandidateToActs`. Longer labels (e.g. `Pleurtuit, Ille-et-Vilaine, France`) would stay without GPS. Always use `actsNeedingGeocodeForCity`.
|
|
136
|
+
|
|
137
|
+
### List cities missing coordinates
|
|
138
|
+
|
|
139
|
+
One row per city; `withoutCoordCount` is how many acts still need GPS.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { groupActsBySimilarCity } from "gedcom-ts";
|
|
143
|
+
|
|
144
|
+
const groups = groupActsBySimilarCity(allActs, { onlyWithoutCoordinates: true });
|
|
145
|
+
|
|
146
|
+
for (const group of groups) {
|
|
147
|
+
console.log(group.cityLabel, group.withoutCoordCount);
|
|
148
|
+
// group.acts — acts in this group without GPS
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Largest groups first (`group.acts.length`).
|
|
153
|
+
|
|
154
|
+
### Map: one marker per city
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { clusterKeyForCity } from "gedcom-ts";
|
|
158
|
+
|
|
159
|
+
const markerKey = clusterKeyForCity(act.place?.city ?? "");
|
|
160
|
+
// merge markers that share the same markerKey
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Harmonization (data quality, optional)
|
|
164
|
+
|
|
165
|
+
Use when spellings, GPS positions, or “some acts with / without GPS” disagree for the same similar city.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { findHarmonizationClusters } from "gedcom-ts";
|
|
169
|
+
|
|
170
|
+
for (const cluster of findHarmonizationClusters(ged)) {
|
|
171
|
+
console.log(cluster.labels, cluster.coordVariants, cluster.actsWithoutCoord);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
After a full geocode via `actsNeedingGeocodeForCity`, you should not get a cluster that only reports missing GPS for that city.
|
|
176
|
+
|
|
177
|
+
### Geocoding API cheat sheet
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
| Goal | Call |
|
|
181
|
+
| ------------------------- | ---------------------------------------------------------------- |
|
|
182
|
+
| Search | `searchPlacesWithContext(city, inferGeocodeContext(ged))` |
|
|
183
|
+
| Acts to update on confirm | `actsNeedingGeocodeForCity(acts, city)` |
|
|
184
|
+
| Apply lat/lng | `applyGeocodeCandidateToActs(targets, candidate, city)` |
|
|
185
|
+
| Cities without GPS | `groupActsBySimilarCity(acts, { onlyWithoutCoordinates: true })` |
|
|
186
|
+
| Map marker id | `clusterKeyForCity(city)` |
|
|
187
|
+
| Inconsistencies | `findHarmonizationClusters(ged)` |
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
Network: `GET https://nominatim.openstreetmap.org/search?q=…&format=jsonv2` with a `User-Agent` (`gedcom-ts/<version> (genealogy library)`). For offline or mocked search, pass `fetchFn` in `searchPlaces` / `searchPlacesWithContext` options.
|
|
191
|
+
|
|
192
|
+
The legacy callback `getCityCoordinates` is deprecated — use the flow above.
|
|
193
|
+
|
|
194
|
+
## Genealogy booklet (PDF)
|
|
195
|
+
|
|
196
|
+
The **`gedcom-ts/booklet`** subpath builds a printable family booklet: cover page, table of contents, chapters by generation (Sosa), family sheets, and French narrative text from GEDCOM acts (birth, marriage, death, …). Optional generation timelines are rasterized for PDF.
|
|
197
|
+
|
|
198
|
+
### Workflow
|
|
199
|
+
|
|
200
|
+
1. Import with `importGedFile` (`gedcom-ts`).
|
|
201
|
+
2. Collect persons with `collectBookletPersons` (`gedcom-ts/booklet`).
|
|
202
|
+
3. Optionally preview size with `estimateBookletSize` + `groupBookletIntoChapters`.
|
|
203
|
+
4. Build bytes with `generateGenealogyBookletPdf`, then `downloadBookletPdf` (browser).
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { importGedFile } from "gedcom-ts";
|
|
207
|
+
import {
|
|
208
|
+
collectBookletPersons,
|
|
209
|
+
groupBookletIntoChapters,
|
|
210
|
+
estimateBookletSize,
|
|
211
|
+
generateGenealogyBookletPdf,
|
|
212
|
+
downloadBookletPdf,
|
|
213
|
+
personDisplayName,
|
|
214
|
+
} from "gedcom-ts/booklet";
|
|
215
|
+
|
|
216
|
+
async function exportBooklet(file: File) {
|
|
217
|
+
const ged = await importGedFile(file);
|
|
218
|
+
const root = ged.persons[0] ?? null;
|
|
219
|
+
|
|
220
|
+
const entries = collectBookletPersons({
|
|
221
|
+
ged,
|
|
222
|
+
scope: "from-reference",
|
|
223
|
+
referencePerson: root,
|
|
224
|
+
maxGeneration: 6,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const chapters = groupBookletIntoChapters(entries);
|
|
228
|
+
const families = chapters.reduce((n, ch) => n + ch.families.length, 0);
|
|
229
|
+
const size = estimateBookletSize(chapters, entries.length, families, "summary", "canvas");
|
|
230
|
+
console.log(size.label);
|
|
231
|
+
|
|
232
|
+
const pdf = await generateGenealogyBookletPdf(
|
|
233
|
+
entries,
|
|
234
|
+
{
|
|
235
|
+
title: "Livret familial",
|
|
236
|
+
scopeLabel: "Ancêtres et descendance",
|
|
237
|
+
personCount: entries.length,
|
|
238
|
+
referenceName: root ? personDisplayName(root) : undefined,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
detailLevel: "summary",
|
|
242
|
+
timelineStyle: "canvas",
|
|
243
|
+
coverLogo: true,
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
downloadBookletPdf(pdf, "livret.pdf");
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Use `scope: "all"` and `referencePerson: null` to include every individual in the file.
|
|
252
|
+
|
|
253
|
+
### `collectBookletPersons` options
|
|
254
|
+
|
|
255
|
+
| Option | Role |
|
|
256
|
+
| --- | --- |
|
|
257
|
+
| `ged` | `ReadGed` after import |
|
|
258
|
+
| `scope` | `"all"` or `"from-reference"` (Sosa from `referencePerson`) |
|
|
259
|
+
| `referencePerson` | Root person for `"from-reference"`; `null` if scope is `"all"` |
|
|
260
|
+
| `maxGeneration` | Max Sosa generation (e.g. `6`); ignored when `scope === "all"` |
|
|
261
|
+
|
|
262
|
+
Helpers: `personDisplayName`, `buildBookletPersonEntry`, `sortBookletEntries`, `bookletSexFromPerson`.
|
|
263
|
+
|
|
264
|
+
### `generateGenealogyBookletPdf` options
|
|
265
|
+
|
|
266
|
+
| Option | Values | Effect |
|
|
267
|
+
| --- | --- | --- |
|
|
268
|
+
| `detailLevel` | `"summary"` \| `"detailed"` | Short prose per person vs longer biographies |
|
|
269
|
+
| `timelineStyle` | `"off"` \| `"canvas"` | Generation timeline pages per chapter |
|
|
270
|
+
| `coverLogo` | `true` (default), `false`, or `DrawGedcomTsLogoOptions` | gedcom-ts vector logo on the cover |
|
|
271
|
+
|
|
272
|
+
`BookletPdfMeta`: `title`, `scopeLabel`, `personCount`, optional `referenceName`.
|
|
273
|
+
|
|
274
|
+
### Cover logo
|
|
275
|
+
|
|
276
|
+
The cover draws the **gedcom-ts logo** from paths shipped in the package (`GEDCOM_TS_LOGO_PATHS`, `GEDCOM_TS_LOGO_VIEWBOX`). Reuse on custom PDF pages:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { PDFDocument } from "pdf-lib";
|
|
280
|
+
import {
|
|
281
|
+
drawGedcomTsLogoOnPage,
|
|
282
|
+
defaultCoverLogoOptions,
|
|
283
|
+
BOOKLET_BRAND_RGB,
|
|
284
|
+
} from "gedcom-ts/booklet";
|
|
285
|
+
|
|
286
|
+
const doc = await PDFDocument.create();
|
|
287
|
+
const page = doc.addPage();
|
|
288
|
+
drawGedcomTsLogoOnPage(page, defaultCoverLogoOptions());
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Booklet API cheat sheet
|
|
292
|
+
|
|
293
|
+
| Goal | Export |
|
|
294
|
+
| --- | --- |
|
|
295
|
+
| Persons for the booklet | `collectBookletPersons` |
|
|
296
|
+
| Chapters / families | `groupBookletIntoChapters`, `partnerNamesLabel` |
|
|
297
|
+
| French narratives (custom UI) | `buildPersonSummaryNarrative`, `buildPersonDetailedNarratives`, `buildFamilyNarrative`, `buildChapterIntroNarrative` |
|
|
298
|
+
| Page estimate | `estimateBookletSize`, `bookletSizeAdvice` |
|
|
299
|
+
| Timeline data / PNG | `buildGenerationTimeline`, `rasterizeGenerationTimelinePng` |
|
|
300
|
+
| PDF output | `generateGenealogyBookletPdf`, `downloadBookletPdf`, `toPdfText` |
|
|
301
|
+
| Logo | `drawGedcomTsLogoOnPage`, `defaultCoverLogoOptions`, `GEDCOM_TS_LOGO_PATHS` |
|
|
302
|
+
|
|
303
|
+
## API reference
|
|
55
304
|
|
|
56
|
-
|
|
305
|
+
Short description and a minimal snippet for each export of **`gedcom-ts`**. For the PDF booklet, see [Genealogy booklet (PDF)](#genealogy-booklet-pdf) (`gedcom-ts/booklet`).
|
|
57
306
|
|
|
58
307
|
### Importing a file
|
|
59
308
|
|
|
@@ -104,22 +353,24 @@ Result of an import. Top-level GEDCOM records not modeled into the typed graph (
|
|
|
104
353
|
|
|
105
354
|
Notable members:
|
|
106
355
|
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
356
|
+
|
|
357
|
+
| Member | Description |
|
|
358
|
+
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
359
|
+
| `persons: Person[]` | Imported individuals. |
|
|
360
|
+
| `mapPersons: Map<number, Person>` | `INDI` (integer) → `Person`. |
|
|
361
|
+
| `partnersMap: Map<number, Person[]>` | Family id → spouses. |
|
|
362
|
+
| `childsMap: Map<number, Person[]>` | Family id → children. |
|
|
363
|
+
| `placesMap: Map<string, Place>` | City name → `Place` (first occurrence wins). |
|
|
364
|
+
| `mapFiles: Map<string, File>` | Relative path → media `File` (for ZIP imports). |
|
|
365
|
+
| `datasetVersion: GedcomDatasetVersion` | `"7.0"`, `"5.5"` or `"unknown"`. |
|
|
366
|
+
| `preservedTopLevelRecords: string[]` | Raw blocks for partial round-trip. |
|
|
367
|
+
| `resolveIndividualPointer(raw)` | Resolves `@I12@`, `I12`, `@Homer_Simpson@`, `Homer_Simpson` to a `Person`. |
|
|
368
|
+
| `getChildrenForParent(parent)` | All children attached to a parent (via `FAMS`). |
|
|
369
|
+
| `getChildrenOfFamily(familyId)` | Children of a single family. |
|
|
370
|
+
| `groupPartners()` | Rebuilds `partnersMap` / `childsMap` after editing links. |
|
|
371
|
+
| `generateUniqueIndi()` | Next free `INDI` number for new persons. |
|
|
372
|
+
| `rehydratePlacesFromActs()` | Rebuilds `placesMap` from act places (e.g. after manual graph edits). Use `editReadGed(readGed).addPerson(...)` to register persons so maps stay coherent. |
|
|
373
|
+
|
|
123
374
|
|
|
124
375
|
```ts
|
|
125
376
|
import { ReadGed } from "gedcom-ts";
|
|
@@ -177,13 +428,15 @@ async function exportZip(persons: Person[]) {
|
|
|
177
428
|
|
|
178
429
|
Options shared by both exporters:
|
|
179
430
|
|
|
180
|
-
|
|
181
|
-
|
|
|
431
|
+
|
|
432
|
+
| Option | Effect |
|
|
433
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
182
434
|
| `extraTopLevelRecords` | Raw `0 …` blocks re-emitted before `SUBM` / `TRLR` (round-trip with `readGed.preservedTopLevelRecords`). |
|
|
183
|
-
| `headLanguageTag`
|
|
184
|
-
| `headCopyright`
|
|
185
|
-
| `headDestination`
|
|
186
|
-
| `headSchemaTagDefs`
|
|
435
|
+
| `headLanguageTag` | BCP 47 tag for `HEAD`.`LANG` (defaults to `en-US`). |
|
|
436
|
+
| `headCopyright` | One-line `1 COPR` notice. |
|
|
437
|
+
| `headDestination` | Value of `HEAD`.`DEST` (target app / URI). |
|
|
438
|
+
| `headSchemaTagDefs` | Extension tag definitions emitted as `HEAD`.`SCHMA` / `2 TAG …`. |
|
|
439
|
+
|
|
187
440
|
|
|
188
441
|
### Domain model
|
|
189
442
|
|
|
@@ -394,14 +647,16 @@ editPerson(person)
|
|
|
394
647
|
editDateAct(person.acts.list[0].dateAct!).setExactDate(1900, "JAN", 1);
|
|
395
648
|
```
|
|
396
649
|
|
|
397
|
-
|
|
398
|
-
|
|
|
399
|
-
|
|
|
400
|
-
| `
|
|
401
|
-
| `
|
|
402
|
-
| `
|
|
403
|
-
| `
|
|
404
|
-
| `
|
|
650
|
+
|
|
651
|
+
| Helper / class | Purpose |
|
|
652
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
653
|
+
| `editPerson(person)` / `PersonEdit` | Identity, `FAMS` / `FAMC` / `removeFams*`, `nameVariants()` / `attributes()` / `multimedia()`, bulk `clear*`, entry points to `acts()` and `notes()`. |
|
|
654
|
+
| `editActs(acts)` / `ActsEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeAct`, `removeLast`, `clear`, `sortByDate`, `at`, `indexOfAct`. |
|
|
655
|
+
| `editAct(act)` / `ActEdit` | `setType`, `setIndis`, dates, place, EVEN fields, preserved lines, `notes()`, `multimedia()`, `clearNotes`, `clearMultimedia`. |
|
|
656
|
+
| `editDateAct(dateAct)` / `DateActEdit` | `clear`, `applyGedcomPayload`, `setExactDate`, `setQualified`, `setBetween`, `setFromTo`, `setTime`, `setDatePhrase` / `appendDatePhrase`, `setVerbatimPayload`. |
|
|
657
|
+
| `editPlace(place)` / `PlaceEdit` | `setFromGedcom7Payload`, `setCity`, `setCounty`, `setState`, `setCountry`, `setPlacPhrase` / `appendPlacPhrase` / `clearPlacPhrase`, `setCoordinates` / `clearCoordinates` / `replaceCoordinateModel`, `clearStructured`. |
|
|
658
|
+
| `editNotes(notes)` / `NotesEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeNote`, `removeLast`, `clear`, `at`, `indexOfNote`. |
|
|
659
|
+
|
|
405
660
|
|
|
406
661
|
### Dataset editing: `ReadGed`, graph, clone, export options, validation
|
|
407
662
|
|
|
@@ -459,15 +714,17 @@ validateReadGed(readGed, {
|
|
|
459
714
|
|
|
460
715
|
`tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, and `tryCreateMarriageFamily` return a `CommandResult` (`ok` + `issues` or `value` + optional `warnings`). `editReadGed(...).addPerson` uses these checks internally (throws on blocking errors). For UI or transactional flows, prefer the `try*` APIs and inspect `commandBlockingIssues`.
|
|
461
716
|
|
|
462
|
-
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
| `
|
|
466
|
-
| `
|
|
467
|
-
| `
|
|
468
|
-
| `
|
|
469
|
-
| `
|
|
470
|
-
| `
|
|
717
|
+
|
|
718
|
+
| Export | Role |
|
|
719
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
720
|
+
| `editReadGed(readGed)` / `ReadGedEdit` | `addPerson`, `removePersonByIndi`, `preserved()` → `PreservedTopLevelEdit` (`append`, `insertAt`, `replaceAt`, `removeAt`, `clear`) on `preservedTopLevelRecords`. Low-level helpers: `addPersonToReadGed`, `removePersonFromReadGedByIndi`. |
|
|
721
|
+
| `editGedcomExportOptions(opts)` / `GedcomExportOptionsEdit` | Fluent setters for `extraTopLevelRecords`, `headLanguageTag`, `headCopyright`, `headDestination`, `headSchemaTagDefs`. |
|
|
722
|
+
| `clonePerson(person, newIndi)` / `cloneAct(act)` / `person.clone` / `act.clone` | Deep copies for templates or undo stacks. |
|
|
723
|
+
| `nextFamilyId(persons)` | Next internal family id `F`. |
|
|
724
|
+
| `createMarriageFamily`, `linkChildToFamily`, `unlinkChildFromFamily`, `removeFamilyReferencesFromDataset` | Create family `F` and spouse acts; optional `{ eventTag }` (default `MARR`, also `ENGA`, banns, contract, `EVEN` + `CreateActInit`, …). See `GEDCOM_7_PAIR_UNION_EVENT_TAGS`. |
|
|
725
|
+
| `validateReadGed`, `assertReadGedConsistent`, `validatePerson`, `assertPersonConsistent` | Typed `code` / `severity` (`error` \| `warn`). Options: `checkMarrParticipants`, `checkDuplicateIndis`, `checkFamcWithoutSpouses`, `checkFamsWithoutSpouses`, `checkDuplicateFamsEntries`, `checkAncestorCycles`. Assertions: `failOn` (default: `error`). |
|
|
726
|
+
| `tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, `tryCreateMarriageFamily`, `commandBlockingIssues` | Command layer with `CommandResult` / `validate*Command` prechecks. |
|
|
727
|
+
|
|
471
728
|
|
|
472
729
|
### Utilities
|
|
473
730
|
|
|
@@ -492,31 +749,9 @@ import { remainingTypesAct, Acts } from "gedcom-ts";
|
|
|
492
749
|
const available = remainingTypesAct(new Acts());
|
|
493
750
|
```
|
|
494
751
|
|
|
495
|
-
#### Geocoding (Nominatim)
|
|
496
|
-
|
|
497
|
-
Place search uses the official Nominatim endpoint `GET https://nominatim.openstreetmap.org/search` with `q`, `format=jsonv2`, and `addressdetails=1` (not the legacy `search.php?city=` API). A `User-Agent` header is required (`gedcom-ts/<version> (genealogy library)` by default).
|
|
498
|
-
|
|
499
|
-
```ts
|
|
500
|
-
import {
|
|
501
|
-
inferGeocodeContext,
|
|
502
|
-
searchPlacesWithContext,
|
|
503
|
-
rankGeocodeCandidates,
|
|
504
|
-
findHarmonizationClusters,
|
|
505
|
-
applyGeocodeCandidateToActs,
|
|
506
|
-
} from "gedcom-ts";
|
|
507
|
-
|
|
508
|
-
const context = inferGeocodeContext(ged);
|
|
509
|
-
const candidates = await searchPlacesWithContext("Valence", context);
|
|
510
|
-
// candidates are ranked (French tree → Valence FR before ES)
|
|
511
|
-
|
|
512
|
-
const clusters = findHarmonizationClusters(ged);
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
For tests or Node without a global `fetch`, pass `fetchFn` in options. Low-level API: `searchPlaces(query, { countryCodes, limit, userAgent, fetchFn })`.
|
|
516
|
-
|
|
517
752
|
#### `getCityCoordinates(cityName, callback)` (deprecated)
|
|
518
753
|
|
|
519
|
-
Legacy callback API
|
|
754
|
+
Legacy callback API. Use [Geocoding places](#geocoding-places) instead.
|
|
520
755
|
|
|
521
756
|
#### `resolveDatasetVersion(headerLines)` / `GedcomDatasetVersion`
|
|
522
757
|
|
|
@@ -561,7 +796,7 @@ const primary = selectPrimaryNameVariant(person.nameVariants);
|
|
|
561
796
|
|
|
562
797
|
#### `GEDCOM_LIBRARY_VERSION`
|
|
563
798
|
|
|
564
|
-
CalVer string
|
|
799
|
+
CalVer string written in exported `HEAD`.`SOUR`.`VERS` (same value as the npm package version, e.g. `2026.5.2`).
|
|
565
800
|
|
|
566
801
|
```ts
|
|
567
802
|
import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";
|
|
@@ -569,56 +804,6 @@ import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";
|
|
|
569
804
|
console.log(`gedcom-ts ${GEDCOM_LIBRARY_VERSION}`);
|
|
570
805
|
```
|
|
571
806
|
|
|
572
|
-
## End-to-end example
|
|
573
|
-
|
|
574
|
-
```ts
|
|
575
|
-
import {
|
|
576
|
-
importGedFile,
|
|
577
|
-
ExportGedzipFile,
|
|
578
|
-
editPerson,
|
|
579
|
-
DateAct,
|
|
580
|
-
Identifier,
|
|
581
|
-
} from "gedcom-ts";
|
|
582
|
-
|
|
583
|
-
async function importModifyExport(file: File) {
|
|
584
|
-
const readGed = await importGedFile(file);
|
|
585
|
-
const persons = readGed.persons;
|
|
586
|
-
|
|
587
|
-
if (persons.length > 0) {
|
|
588
|
-
editPerson(persons[0])
|
|
589
|
-
.setLastname(persons[0].lastname.toUpperCase())
|
|
590
|
-
.acts()
|
|
591
|
-
.at(0)
|
|
592
|
-
.setDateAct(new DateAct("1 JAN 1900"));
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
await new ExportGedzipFile("updated-tree", persons).download();
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
## Development
|
|
600
|
-
|
|
601
|
-
### Local scripts
|
|
602
|
-
|
|
603
|
-
| Script | Role |
|
|
604
|
-
| --- | --- |
|
|
605
|
-
| `npm run lint` | ESLint on the codebase |
|
|
606
|
-
| `npm run test` | Vitest test suite |
|
|
607
|
-
| `npm run build` | Production bundle + `.d.ts` |
|
|
608
|
-
| `npm run tgz` | `build` then `npm pack` (local `.tgz`) |
|
|
609
|
-
|
|
610
|
-
### GitLab CI (`.gitlab-ci.yml`)
|
|
611
|
-
|
|
612
|
-
Every pipeline runs **`InstallDependencies`** (`npm ci`, `node_modules` artifact) — required by all other jobs.
|
|
613
|
-
|
|
614
|
-
| Job | When it runs |
|
|
615
|
-
| --- | --- |
|
|
616
|
-
| `InstallDependencies` | Always |
|
|
617
|
-
| `Lint`, `Test` | Merge requests and branch pushes (not on `master` after a merge commit titled `Merge…`) |
|
|
618
|
-
| `BuildTgz`, `PublishNpm` | Push to `master` after merge only (`Merge…` commit title) |
|
|
619
|
-
|
|
620
|
-
Typical flow: open an MR from a release branch (e.g. `2026.5.0`) → lint + test run there → after merge to `master`, only package build and npm publish run (quality checks are not repeated).
|
|
621
|
-
|
|
622
807
|
## Error handling
|
|
623
808
|
|
|
624
809
|
```ts
|
|
@@ -635,3 +820,4 @@ try {
|
|
|
635
820
|
}
|
|
636
821
|
}
|
|
637
822
|
```
|
|
823
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Date réduite à une année (éventuellement avec qualificateur GEDCOM). */
|
|
2
|
+
export declare function isYearOnlyDate(dateLabel: string): boolean;
|
|
3
|
+
/** « le 15 mars 1991 », « en 1991 », « vers 1700 », « avant 1800 »… */
|
|
4
|
+
export declare function onDate(dateLabel: string): string;
|
|
5
|
+
/** « De 1700 à 1850 » ou « Du 15 mars 1700 au 3 janvier 1850 ». */
|
|
6
|
+
export declare function lifeDateRange(from: string, to: string): string;
|
|
7
|
+
/** « voit le jour en 1991 » ou « voit le jour le 15 mars 1991 ». */
|
|
8
|
+
export declare function seesDayOn(dateLabel: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BookletTimelineStyle } from './booklet-timeline';
|
|
2
|
+
import type { BookletChapter, BookletDetailLevel } from './booklet-structure';
|
|
3
|
+
export interface BookletSizeEstimate {
|
|
4
|
+
readonly pages: number;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
}
|
|
7
|
+
/** Nombre de pages frise estimé pour un chapitre (tranches dynamiques). */
|
|
8
|
+
export declare function estimateTimelineSliceCount(rowCount: number): number;
|
|
9
|
+
/** Estimation grossière du nombre de pages pour orienter l’utilisateur. */
|
|
10
|
+
export declare function estimateBookletSize(chapters: readonly BookletChapter[], personCount: number, familyCount: number, detailLevel: BookletDetailLevel, timelineStyle?: BookletTimelineStyle): BookletSizeEstimate;
|
|
11
|
+
export declare function bookletSizeAdvice(personCount: number, detailLevel: BookletDetailLevel): string | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Sexe GEDCOM exploité pour les accords du livret (M, F, U, X). */
|
|
2
|
+
export type BookletSex = 'M' | 'F' | 'U' | 'X';
|
|
3
|
+
export interface PersonGenderWords {
|
|
4
|
+
readonly born: string;
|
|
5
|
+
readonly died: string;
|
|
6
|
+
readonly baptized: string;
|
|
7
|
+
readonly present: string;
|
|
8
|
+
readonly linked: string;
|
|
9
|
+
readonly offspring: string;
|
|
10
|
+
readonly descendant: string;
|
|
11
|
+
readonly childOf: string;
|
|
12
|
+
readonly parentOf: string;
|
|
13
|
+
}
|
|
14
|
+
/** Participes / noms accordés au genre de la personne. U et X → formes neutres. */
|
|
15
|
+
export declare function personGenderWords(sex: BookletSex): PersonGenderWords;
|
|
16
|
+
/** Suffixe pluriel français (0 ou 1 → singulier). */
|
|
17
|
+
export declare function pluralS(count: number): string;
|
|
18
|
+
/** Choisit la forme singulier / pluriel. */
|
|
19
|
+
export declare function pluralPick<T>(count: number, one: T, many: T): T;
|
|
20
|
+
/** « 1 enfant recensé » / « N enfants recensés ». */
|
|
21
|
+
export declare function recensedChildrenPhrase(count: number): string;
|
|
22
|
+
/** « 1 autre union citée… » / « N autres unions citées… ». */
|
|
23
|
+
export declare function otherUnionsPhrase(count: number): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PDFPage, type RGB } from 'pdf-lib';
|
|
2
|
+
export { GEDCOM_TS_LOGO_PATHS, GEDCOM_TS_LOGO_VIEWBOX } from "./gedcom-ts-logo.paths";
|
|
3
|
+
/** Couleur de marque gedcom-ts (#19196f), pour pdf-lib `rgb(...)`. */
|
|
4
|
+
export declare const BOOKLET_BRAND_RGB: RGB;
|
|
5
|
+
export interface GedcomTsLogoLayout {
|
|
6
|
+
/** Ordonnée PDF du bas du logo (pour placer texte et barre en dessous). */
|
|
7
|
+
readonly bottomY: number;
|
|
8
|
+
readonly height: number;
|
|
9
|
+
}
|
|
10
|
+
export interface DrawGedcomTsLogoOptions {
|
|
11
|
+
readonly pageWidth?: number;
|
|
12
|
+
readonly maxWidth?: number;
|
|
13
|
+
readonly bottomY?: number;
|
|
14
|
+
readonly color?: RGB;
|
|
15
|
+
}
|
|
16
|
+
/** Options par défaut du logo sur une page A4 (couverture du livret). */
|
|
17
|
+
export declare function defaultCoverLogoOptions(pageWidth?: number, pageHeight?: number): DrawGedcomTsLogoOptions;
|
|
18
|
+
/** Dessine le logo gedcom-ts en vecteur SVG sur la page (pdf-lib drawSvgPath). */
|
|
19
|
+
export declare function drawGedcomTsLogoOnPage(page: PDFPage, options?: DrawGedcomTsLogoOptions): GedcomTsLogoLayout;
|