@treeviz/gedcom-parser 2.0.10 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +236 -989
  2. package/dist/classes/common.d.ts +10 -5
  3. package/dist/classes/common.d.ts.map +1 -1
  4. package/dist/classes/date.d.ts +2 -0
  5. package/dist/classes/date.d.ts.map +1 -1
  6. package/dist/classes/fam.d.ts +10 -1
  7. package/dist/classes/fam.d.ts.map +1 -1
  8. package/dist/classes/fams.d.ts.map +1 -1
  9. package/dist/classes/gedcom.d.ts +45 -4
  10. package/dist/classes/gedcom.d.ts.map +1 -1
  11. package/dist/classes/index.js +1770 -279
  12. package/dist/classes/indi.d.ts +24 -1
  13. package/dist/classes/indi.d.ts.map +1 -1
  14. package/dist/classes/list.d.ts.map +1 -1
  15. package/dist/classes/note.d.ts.map +1 -1
  16. package/dist/classes/obje.d.ts +2 -1
  17. package/dist/classes/obje.d.ts.map +1 -1
  18. package/dist/cli/index.js +2802 -1296
  19. package/dist/constants/constants.d.ts +7 -0
  20. package/dist/constants/constants.d.ts.map +1 -1
  21. package/dist/constants/index.js +1599 -989
  22. package/dist/factories/index.js +719 -110
  23. package/dist/index.d.ts +12 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3837 -2028
  26. package/dist/interfaces/common.d.ts +6 -4
  27. package/dist/interfaces/common.d.ts.map +1 -1
  28. package/dist/interfaces/fam.d.ts +10 -1
  29. package/dist/interfaces/fam.d.ts.map +1 -1
  30. package/dist/interfaces/gedcom.d.ts +25 -1
  31. package/dist/interfaces/gedcom.d.ts.map +1 -1
  32. package/dist/interfaces/indi.d.ts +15 -1
  33. package/dist/interfaces/indi.d.ts.map +1 -1
  34. package/dist/kinship-translator/index.js +719 -110
  35. package/dist/structures/gedcom.d.ts +2 -0
  36. package/dist/structures/gedcom.d.ts.map +1 -1
  37. package/dist/types/types.d.ts +32 -4
  38. package/dist/types/types.d.ts.map +1 -1
  39. package/dist/utils/common-creator.d.ts.map +1 -1
  40. package/dist/utils/fact-edit.d.ts +34 -0
  41. package/dist/utils/fact-edit.d.ts.map +1 -0
  42. package/dist/utils/family-edit.d.ts +23 -0
  43. package/dist/utils/family-edit.d.ts.map +1 -0
  44. package/dist/utils/gedcom-trailer.d.ts +6 -0
  45. package/dist/utils/gedcom-trailer.d.ts.map +1 -0
  46. package/dist/utils/gedcom-version.d.ts +6 -0
  47. package/dist/utils/gedcom-version.d.ts.map +1 -0
  48. package/dist/utils/gedcom7-dates.d.ts +20 -0
  49. package/dist/utils/gedcom7-dates.d.ts.map +1 -0
  50. package/dist/utils/gedcom7-enumerations.d.ts +26 -0
  51. package/dist/utils/gedcom7-enumerations.d.ts.map +1 -0
  52. package/dist/utils/gedcom7-metadata.d.ts +19 -0
  53. package/dist/utils/gedcom7-metadata.d.ts.map +1 -0
  54. package/dist/utils/gedcom7-schema.d.ts +27 -0
  55. package/dist/utils/gedcom7-schema.d.ts.map +1 -0
  56. package/dist/utils/gedzip.d.ts +81 -0
  57. package/dist/utils/gedzip.d.ts.map +1 -0
  58. package/dist/utils/index.d.ts +13 -1
  59. package/dist/utils/index.d.ts.map +1 -1
  60. package/dist/utils/index.js +2225 -416
  61. package/dist/utils/local-media.d.ts +30 -0
  62. package/dist/utils/local-media.d.ts.map +1 -0
  63. package/dist/utils/media-utils.d.ts +10 -6
  64. package/dist/utils/media-utils.d.ts.map +1 -1
  65. package/dist/utils/multimedia.d.ts +38 -0
  66. package/dist/utils/multimedia.d.ts.map +1 -0
  67. package/dist/utils/parser.d.ts +3 -0
  68. package/dist/utils/parser.d.ts.map +1 -1
  69. package/dist/utils/uid.d.ts +6 -0
  70. package/dist/utils/uid.d.ts.map +1 -0
  71. package/package.json +2 -1
@@ -343,6 +343,11 @@ var inRange = (year, range, trueIfNoYear = false) => {
343
343
  // src/constants/constants.ts
344
344
  var ID_REG = /^@[_a-zA-Z0-9]+@/;
345
345
  var ID_GETTER_REG = /^(?<at>@)?(?<letter>[A-Z])?/i;
346
+ var ORIG_HEAD_TAG = "_ORIGHEAD";
347
+
348
+ // src/utils/gedcom-trailer.ts
349
+ var GEDCOM_TRAILER_TAG = "TRLR";
350
+ var isGedcomTrailerTag = (tag) => tag === GEDCOM_TRAILER_TAG;
346
351
  var List = class _List {
347
352
  // length = 0;
348
353
  constructor(items) {
@@ -718,11 +723,17 @@ var List = class _List {
718
723
  const gedcom = [];
719
724
  this.entries().forEach(([_, item]) => {
720
725
  const validTag = getValidTag(tag);
721
- const validKey = item.id;
722
- const validValue = validKey ? validTag : item.exportValue();
723
- gedcom.push(
724
- `${level} ${validKey || validTag}${validValue ? ` ${validValue}` : ""}`
725
- );
726
+ const xref = item.id;
727
+ const payload = item.exportValue?.();
728
+ if (xref) {
729
+ gedcom.push(
730
+ `${level} ${xref} ${validTag}${payload ? ` ${payload}` : ""}`
731
+ );
732
+ } else {
733
+ gedcom.push(
734
+ `${level} ${validTag}${payload ? ` ${payload}` : ""}`
735
+ );
736
+ }
726
737
  gedcom.push(
727
738
  ...item.toGedcomLines(
728
739
  validTag,
@@ -853,6 +864,9 @@ var Common = class _Common {
853
864
  delete this._value;
854
865
  }
855
866
  set(name, value) {
867
+ if (isGedcomTrailerTag(name)) {
868
+ return void 0;
869
+ }
856
870
  if (typeof value === "string") {
857
871
  const usedValue = createCommon(this._gedcom, void 0, this.main);
858
872
  usedValue.value = value;
@@ -863,6 +877,9 @@ var Common = class _Common {
863
877
  return get(this, name);
864
878
  }
865
879
  assign(name, value, unique = false) {
880
+ if (isGedcomTrailerTag(name)) {
881
+ return void 0;
882
+ }
866
883
  let curValue = this.get(name);
867
884
  if (curValue?.isListable) {
868
885
  if (curValue instanceof _Common) {
@@ -974,12 +991,14 @@ var Common = class _Common {
974
991
  );
975
992
  }
976
993
  standardizeObject(tag, options) {
977
- if (tag === "OBJE" && !options?.original && options?.obje?.standardize && options?.obje?.namespace && "standardizeMedia" in this) {
994
+ if (tag === "OBJE" && options?.obje?.standardize && "standardizeMedia" in this) {
978
995
  const standardize = this.standardizeMedia;
979
996
  return standardize.call(
980
997
  this,
981
998
  options.obje.namespace,
982
- options.obje.override
999
+ options.obje.override,
1000
+ void 0,
1001
+ options.gedcomVersion
983
1002
  );
984
1003
  }
985
1004
  }
@@ -1006,7 +1025,7 @@ var Common = class _Common {
1006
1025
  if (typeof prop?.toObject === "function") {
1007
1026
  if (prop instanceof _Common) {
1008
1027
  json[validKey] = prop.toObject(validKey, options);
1009
- } else {
1028
+ } else if (prop instanceof List) {
1010
1029
  json = {
1011
1030
  ...json,
1012
1031
  ...prop.toObject(validKey, options)
@@ -1064,8 +1083,12 @@ var Common = class _Common {
1064
1083
  this.standardizeObject(tag, options);
1065
1084
  const validKeys = getValidKeys(this);
1066
1085
  const gedcom = [];
1086
+ const isGedcom7Export = options?.gedcomVersion === "7.0";
1067
1087
  validKeys.forEach((key) => {
1068
1088
  const validKey = key;
1089
+ if (isGedcom7Export && validKey === "CONC") {
1090
+ return;
1091
+ }
1069
1092
  const prop = this.get(validKey);
1070
1093
  if (typeof prop?.toGedcomLines === "function") {
1071
1094
  if (prop instanceof _Common) {
@@ -1076,7 +1099,7 @@ var Common = class _Common {
1076
1099
  gedcom.push(
1077
1100
  ...prop.toGedcomLines(validKey, level + 1, options)
1078
1101
  );
1079
- } else {
1102
+ } else if (prop instanceof List) {
1080
1103
  gedcom.push(
1081
1104
  ...prop.toGedcomLines(validKey, level, options)
1082
1105
  );
@@ -1085,30 +1108,47 @@ var Common = class _Common {
1085
1108
  });
1086
1109
  return gedcom;
1087
1110
  }
1111
+ getHeadRecord() {
1112
+ return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1113
+ }
1114
+ getOriginalHeadRecord() {
1115
+ return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
1116
+ }
1117
+ getSourceHeads() {
1118
+ const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
1119
+ return heads.filter((head, index) => {
1120
+ return Boolean(head && heads.indexOf(head) === index);
1121
+ });
1122
+ }
1123
+ getFromSourceHeads(path) {
1124
+ for (const head of this.getSourceHeads()) {
1125
+ const value = get(head, path);
1126
+ if (value !== void 0 && value !== null && value !== "") {
1127
+ return value;
1128
+ }
1129
+ }
1130
+ return void 0;
1131
+ }
1132
+ sourStartsWith(prefix) {
1133
+ return this.getSourceHeads().some((head) => {
1134
+ const sour = get(head, "SOUR.value");
1135
+ return !!sour?.toLowerCase()?.startsWith(prefix);
1136
+ });
1137
+ }
1088
1138
  isGenoPro() {
1089
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1090
- const sour = get(head, "SOUR.value");
1091
- return !!sour?.toLowerCase()?.startsWith("genopro");
1139
+ return this.sourStartsWith("genopro");
1092
1140
  }
1093
1141
  isAhnenblatt() {
1094
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1095
- const sour = get(head, "SOUR.value");
1096
- return !!sour?.toLowerCase()?.startsWith("ahnenblatt");
1142
+ return this.sourStartsWith("ahnenblatt");
1097
1143
  }
1098
1144
  isGeni() {
1099
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1100
- const sour = get(head, "SOUR.value");
1101
- return !!sour?.toLowerCase()?.startsWith("geni");
1145
+ return this.sourStartsWith("geni");
1102
1146
  }
1103
1147
  isAncestry() {
1104
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1105
- const sour = get(head, "SOUR.value");
1106
- return !!sour?.toLowerCase()?.startsWith("ancestry");
1148
+ return this.sourStartsWith("ancestry");
1107
1149
  }
1108
1150
  isMyHeritage() {
1109
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1110
- const sour = get(head, "SOUR.value");
1111
- return !!sour?.toLowerCase()?.startsWith("myheritage");
1151
+ return this.sourStartsWith("myheritage");
1112
1152
  }
1113
1153
  /**
1114
1154
  * Get the source type as a string (for prefixing tree IDs and names)
@@ -1125,22 +1165,22 @@ var Common = class _Common {
1125
1165
  return void 0;
1126
1166
  }
1127
1167
  isFamilySearch() {
1128
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1129
- const sourName = get(head, "SOUR.NAME.value");
1130
- return sourName === "FamilySearch API";
1168
+ return this.getSourceHeads().some((head) => {
1169
+ const sourName = get(head, "SOUR.NAME.value");
1170
+ return sourName === "FamilySearch API";
1171
+ });
1131
1172
  }
1132
1173
  isGNO2GED() {
1133
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1134
- const sour = get(head, "SOUR.value");
1135
- return sour === "GNO2GED";
1174
+ return this.getSourceHeads().some((head) => {
1175
+ const sour = get(head, "SOUR.value");
1176
+ return sour === "GNO2GED";
1177
+ });
1136
1178
  }
1137
1179
  getAncestryTreeId() {
1138
- const path = "HEAD.SOUR._TREE.RIN.value";
1139
- return get(this, path) || get(this.getGedcom(), path);
1180
+ return this.getFromSourceHeads("SOUR._TREE.RIN.value") || (get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value"));
1140
1181
  }
1141
1182
  getMyHeritageTreeId() {
1142
- const path = "HEAD._EXPORTED_FROM_SITE_ID.value";
1143
- return get(this, path) || get(this.getGedcom(), path);
1183
+ return this.getFromSourceHeads("_EXPORTED_FROM_SITE_ID.value") || (get(this, "HEAD._EXPORTED_FROM_SITE_ID.value") || get(this.getGedcom(), "HEAD._EXPORTED_FROM_SITE_ID.value"));
1144
1184
  }
1145
1185
  getTreeId() {
1146
1186
  if (this?.isAncestry()) {
@@ -1174,8 +1214,7 @@ var Common = class _Common {
1174
1214
  return this.getUniversalTreeId();
1175
1215
  }
1176
1216
  getAncestryTreeName() {
1177
- const path = "HEAD.SOUR._TREE.value";
1178
- return get(this, path) || get(this.getGedcom(), path);
1217
+ return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
1179
1218
  }
1180
1219
  getMyHeritageTreeName() {
1181
1220
  const path = "HEAD.FILE.value";
@@ -1185,14 +1224,14 @@ var Common = class _Common {
1185
1224
  )?.groups?.tree;
1186
1225
  }
1187
1226
  getFamilySearchTreeId() {
1188
- const rin = get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
1227
+ const rin = this.getFromSourceHeads("SOUR._TREE.RIN.value") || get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
1189
1228
  if (rin) {
1190
1229
  return rin;
1191
1230
  }
1192
1231
  return "familysearch";
1193
1232
  }
1194
1233
  getFamilySearchTreeName() {
1195
- const treeName = get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
1234
+ const treeName = this.getFromSourceHeads("SOUR._TREE.value") || get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
1196
1235
  if (treeName) {
1197
1236
  return treeName;
1198
1237
  }
@@ -1284,11 +1323,11 @@ var Common = class _Common {
1284
1323
  const cleanName = fileName.replace(/\.ged$/i, "").replace(/_/g, " ");
1285
1324
  return `${prefix}${cleanName}`;
1286
1325
  }
1287
- const sourName = get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1326
+ const sourName = this.getFromSourceHeads("SOUR.NAME.value") || get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1288
1327
  if (sourName) {
1289
1328
  return `${prefix}${sourName}`;
1290
1329
  }
1291
- const sourValue = get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1330
+ const sourValue = this.getFromSourceHeads("SOUR.value") || get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1292
1331
  if (sourValue) {
1293
1332
  return `${prefix}${sourValue}`;
1294
1333
  }
@@ -1354,7 +1393,7 @@ var isOnlyMainProp = (key) => {
1354
1393
  };
1355
1394
  var isValidKey = (common, key) => {
1356
1395
  const prop = get(common, key);
1357
- return key !== "_gedcom" && key !== "_main" && key !== "_parent" && key !== "_uniqueId" && key !== "_refs" && key !== "_type" && (key === "id" || key === "_id" || key === "value" || key === "_value" || prop instanceof Common || prop instanceof List);
1396
+ return key !== "_gedcom" && key !== "_main" && key !== "_parent" && key !== "_uniqueId" && key !== "_refs" && key !== "_type" && !(typeof key === "string" && isGedcomTrailerTag(key)) && (key === "id" || key === "_id" || key === "value" || key === "_value" || prop instanceof Common || prop instanceof List);
1358
1397
  };
1359
1398
  var getValidKeys = (common) => {
1360
1399
  return Object.keys(common).filter((key) => {
@@ -1401,7 +1440,11 @@ var STANDARD_QUALIFIERS = {
1401
1440
  BEF: "Bef.",
1402
1441
  BEFORE: "Bef.",
1403
1442
  AFT: "Aft.",
1404
- AFTER: "Aft."
1443
+ AFTER: "Aft.",
1444
+ CAL: "Abt.",
1445
+ // calculated ≈ about for display
1446
+ EST: "Abt."
1447
+ // estimated ≈ about for display
1405
1448
  };
1406
1449
  var EXPORT_QUALIFIERS = {
1407
1450
  "Abt.": "ABT",
@@ -1409,6 +1452,11 @@ var EXPORT_QUALIFIERS = {
1409
1452
  "Aft.": "AFT"
1410
1453
  };
1411
1454
  var GEDCOM_MONTHS = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\b/g;
1455
+ var CALENDAR_PREFIX = /^(?<calendar>GREGORIAN|JULIAN|FRENCH_R|HEBREW)\b\s+/i;
1456
+ var isGedcomDateValueForm = (value) => (
1457
+ // Require space/end after the token so display forms like "Abt." are not opaque.
1458
+ /^(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)(\s|$)/i.test(value) || /\bAND\b/i.test(value)
1459
+ );
1412
1460
  var NOTE_MARKER = "####";
1413
1461
  var formatDateWithComponents = (date, hasDay, hasMonth, hasYear, noteValue, baseFormat = "dd MMM yyyy", locale) => {
1414
1462
  const hasNote = baseFormat.includes("NOTE");
@@ -1433,29 +1481,45 @@ var CommonDate = class extends Common {
1433
1481
  super(gedcom, id, main, parent);
1434
1482
  delete this.id;
1435
1483
  }
1484
+ get calendar() {
1485
+ return this._calendar;
1486
+ }
1436
1487
  set value(value) {
1437
1488
  if (value) {
1438
- const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
1439
- const noteMatch = value.match(noteRegExp)?.groups;
1440
1489
  let validValue = value;
1490
+ const calendarMatch = validValue.match(CALENDAR_PREFIX);
1491
+ if (calendarMatch?.groups?.calendar) {
1492
+ this._calendar = calendarMatch.groups.calendar.toUpperCase();
1493
+ validValue = validValue.slice(calendarMatch[0].length);
1494
+ } else {
1495
+ this._calendar = void 0;
1496
+ }
1497
+ const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
1498
+ const noteMatch = validValue.match(noteRegExp)?.groups;
1441
1499
  if (noteMatch?.note) {
1442
1500
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
1443
1501
  this.NOTE.value = noteMatch?.note;
1444
- validValue = value.replace(noteRegExp, "");
1502
+ validValue = validValue.replace(noteRegExp, "");
1445
1503
  } else {
1446
- const qualifierMatch = value.match(
1447
- /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER)\b\s*/i
1504
+ const qualifierMatch = validValue.match(
1505
+ /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER|CAL|EST)\b\s*/i
1448
1506
  );
1449
1507
  const qualifier = qualifierMatch?.groups?.qualifier?.toUpperCase();
1450
1508
  const note = qualifier ? STANDARD_QUALIFIERS[qualifier] : void 0;
1451
1509
  if (note) {
1452
1510
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
1453
1511
  this.NOTE.value = note;
1454
- validValue = value.slice(qualifierMatch[0].length);
1512
+ validValue = validValue.slice(qualifierMatch[0].length);
1455
1513
  }
1456
1514
  }
1457
1515
  const acceptedDate = this.isValidDateFormat(validValue);
1458
- if (acceptedDate) {
1516
+ if (isGedcomDateValueForm(validValue.trim())) {
1517
+ delete this.DAY;
1518
+ delete this.MONTH;
1519
+ delete this.YEAR;
1520
+ this._date = void 0;
1521
+ this._value = value;
1522
+ } else if (acceptedDate) {
1459
1523
  this.DAY = this.DAY || createCommon(this._gedcom, void 0, this.main);
1460
1524
  this.DAY.value = format(acceptedDate, "dd");
1461
1525
  this.MONTH = this.MONTH || createCommon(this._gedcom, void 0, this.main);
@@ -1558,6 +1622,15 @@ var CommonDate = class extends Common {
1558
1622
  );
1559
1623
  }
1560
1624
  exportValue() {
1625
+ if (typeof this._value === "string") {
1626
+ const withoutCalendar = this._value.replace(CALENDAR_PREFIX, "");
1627
+ if (isGedcomDateValueForm(withoutCalendar.trim())) {
1628
+ return this._value.replace(
1629
+ /^((?:GREGORIAN|JULIAN|FRENCH_R|HEBREW)\s+)?(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)\b/i,
1630
+ (_m, cal, q) => `${cal || ""}${q.toUpperCase()}`
1631
+ );
1632
+ }
1633
+ }
1561
1634
  const formatted = this.toValue("NOTE dd MMM yyyy", null);
1562
1635
  if (!formatted) {
1563
1636
  return formatted;
@@ -1565,10 +1638,14 @@ var CommonDate = class extends Common {
1565
1638
  const note = this.NOTE?.value?.trim();
1566
1639
  const qualifier = note ? EXPORT_QUALIFIERS[note] : void 0;
1567
1640
  const standardized = qualifier ? formatted.replace(note, qualifier) : formatted;
1568
- return standardized.replace(
1641
+ const withMonths = standardized.replace(
1569
1642
  GEDCOM_MONTHS,
1570
1643
  (month) => month.toUpperCase()
1571
1644
  );
1645
+ if (this._calendar && this._calendar !== "GREGORIAN") {
1646
+ return `${this._calendar} ${withMonths}`;
1647
+ }
1648
+ return withMonths;
1572
1649
  }
1573
1650
  toGedcomLines(tag, level = 0, options) {
1574
1651
  const internal = new RegExp(
@@ -2029,6 +2106,257 @@ var cacheDiscoveredPath = (gedcom, path) => {
2029
2106
  }
2030
2107
  };
2031
2108
 
2109
+ // src/utils/fact-edit.ts
2110
+ var SINGLETON_TAGS = /* @__PURE__ */ new Set(["BIRT", "DEAT", "SSN", "SEX", "DSCR"]);
2111
+ var firstRecord = (node) => {
2112
+ if (!node) {
2113
+ return void 0;
2114
+ }
2115
+ if (node instanceof List) {
2116
+ return node.index(0);
2117
+ }
2118
+ return node.index(0) ?? node;
2119
+ };
2120
+ var setDateOn = (gedcom, owner, parent, date) => {
2121
+ const trimmed = date.trim();
2122
+ if (!trimmed) {
2123
+ return;
2124
+ }
2125
+ let dateNode = parent.get("DATE");
2126
+ if (!dateNode || dateNode instanceof List) {
2127
+ dateNode = createCommonDate(gedcom, void 0, owner, parent);
2128
+ dateNode.type = "DATE";
2129
+ parent.set("DATE", dateNode);
2130
+ }
2131
+ dateNode.value = trimmed;
2132
+ };
2133
+ var fillFact = (gedcom, owner, event, input) => {
2134
+ if (input.value?.trim()) {
2135
+ event.value = input.value.trim();
2136
+ }
2137
+ if (input.type?.trim()) {
2138
+ event.set("TYPE", input.type.trim());
2139
+ }
2140
+ if (input.date) {
2141
+ setDateOn(gedcom, owner, event, input.date);
2142
+ }
2143
+ if (input.place?.trim()) {
2144
+ event.set("PLAC", input.place.trim());
2145
+ }
2146
+ if (input.note?.trim()) {
2147
+ event.set("NOTE", input.note.trim());
2148
+ }
2149
+ };
2150
+ var addIndividualFact = (indi, input) => {
2151
+ const gedcom = indi.getGedcom();
2152
+ if (!gedcom) {
2153
+ return void 0;
2154
+ }
2155
+ const tag = input.tag;
2156
+ let event = SINGLETON_TAGS.has(tag) ? firstRecord(indi.get(tag)) : void 0;
2157
+ if (!event) {
2158
+ event = createCommon(gedcom, void 0, indi);
2159
+ event.type = tag;
2160
+ if (SINGLETON_TAGS.has(tag)) {
2161
+ indi.set(tag, event);
2162
+ } else {
2163
+ indi.assign(tag, event);
2164
+ }
2165
+ }
2166
+ fillFact(gedcom, indi, event, input);
2167
+ return event;
2168
+ };
2169
+ var addFamilyFact = (fam, input) => {
2170
+ const gedcom = fam.getGedcom();
2171
+ if (!gedcom) {
2172
+ return void 0;
2173
+ }
2174
+ const event = createCommon(gedcom, void 0, fam);
2175
+ event.type = input.tag;
2176
+ fam.assign(input.tag, event);
2177
+ fillFact(gedcom, fam, event, input);
2178
+ return event;
2179
+ };
2180
+ var addNonEvent = (owner, input) => {
2181
+ const gedcom = owner.getGedcom();
2182
+ const eventType = input.event?.trim().toUpperCase();
2183
+ if (!gedcom || !eventType) {
2184
+ return void 0;
2185
+ }
2186
+ const no = createCommon(gedcom, void 0, owner);
2187
+ no.type = "NO";
2188
+ no.value = eventType;
2189
+ if (input.date?.trim()) {
2190
+ setDateOn(gedcom, owner, no, input.date);
2191
+ }
2192
+ if (input.note?.trim()) {
2193
+ no.set("NOTE", input.note.trim());
2194
+ }
2195
+ owner.assign("NO", no);
2196
+ return no;
2197
+ };
2198
+
2199
+ // src/utils/family-edit.ts
2200
+ var PEDIGREE_VALUES = [
2201
+ "birth" /* BIRTH */,
2202
+ "adopted" /* ADOPTED */,
2203
+ "foster" /* FOSTER */,
2204
+ "step" /* STEP */,
2205
+ "sealing" /* SEALING */
2206
+ ];
2207
+ var toGedcomPedigree = (value) => {
2208
+ const normalized = String(value || "birth" /* BIRTH */).toLowerCase();
2209
+ if (normalized === "biological" /* BIOLOGICAL */ || normalized === "birth" /* BIRTH */) {
2210
+ return "birth" /* BIRTH */;
2211
+ }
2212
+ const match = PEDIGREE_VALUES.find((item) => item === normalized);
2213
+ return match ?? "birth" /* BIRTH */;
2214
+ };
2215
+ var pointerIds = (tag) => {
2216
+ if (!tag) {
2217
+ return [];
2218
+ }
2219
+ if (tag instanceof List) {
2220
+ return tag.values().map((item) => item?.toValue?.()).filter((value2) => !!value2);
2221
+ }
2222
+ const value = tag.toValue?.();
2223
+ return value ? [String(value)] : [];
2224
+ };
2225
+ var firstPointerId = (tag) => pointerIds(tag)[0];
2226
+ var makePointer = (gedcom, parent, value, refType) => {
2227
+ const pointer = createCommon(gedcom, void 0, parent);
2228
+ pointer.value = value;
2229
+ pointer.refType = refType;
2230
+ return pointer;
2231
+ };
2232
+ var familyHasParent = (fam, indiId) => pointerIds(fam.get("HUSB")).includes(indiId) || pointerIds(fam.get("WIFE")).includes(indiId);
2233
+ var familyHasChild = (fam, indiId) => pointerIds(fam.get("CHIL")).includes(indiId);
2234
+ var spouseTagFor = (indi, fam) => {
2235
+ const husbId = firstPointerId(fam.get("HUSB"));
2236
+ const wifeId = firstPointerId(fam.get("WIFE"));
2237
+ if (husbId === indi.id) {
2238
+ return "HUSB";
2239
+ }
2240
+ if (wifeId === indi.id) {
2241
+ return "WIFE";
2242
+ }
2243
+ if (indi.isFemale()) {
2244
+ return wifeId && wifeId !== indi.id ? "HUSB" : "WIFE";
2245
+ }
2246
+ if (indi.isMale()) {
2247
+ return husbId && husbId !== indi.id ? "WIFE" : "HUSB";
2248
+ }
2249
+ return husbId ? "WIFE" : "HUSB";
2250
+ };
2251
+ var canAttachAsSpouse = (fam, indi) => {
2252
+ if (!indi.id) {
2253
+ return false;
2254
+ }
2255
+ if (familyHasParent(fam, indi.id)) {
2256
+ return true;
2257
+ }
2258
+ const tag = spouseTagFor(indi, fam);
2259
+ const occupied = firstPointerId(fam.get(tag));
2260
+ return !occupied || occupied === indi.id;
2261
+ };
2262
+ var pointerByValue = (tag, id) => {
2263
+ if (!tag) {
2264
+ return void 0;
2265
+ }
2266
+ const fromValues = tag.toValueList?.().item(id);
2267
+ if (fromValues) {
2268
+ return fromValues;
2269
+ }
2270
+ return tag.toList()?.values().find((item) => item?.toValue?.() === id);
2271
+ };
2272
+ var setChildPedigree = (fam, child, pedigree, parent) => {
2273
+ if (!fam.id || !child.id) {
2274
+ return;
2275
+ }
2276
+ const pedi = toGedcomPedigree(pedigree);
2277
+ const famc = pointerByValue(child.get("FAMC"), fam.id);
2278
+ if (famc) {
2279
+ famc.set("PEDI", pedi);
2280
+ }
2281
+ const chil = pointerByValue(fam.get("CHIL"), child.id);
2282
+ if (!chil) {
2283
+ return;
2284
+ }
2285
+ const setFather = !parent || parent.isMale() || parent.isUnknownSex();
2286
+ const setMother = !parent || parent.isFemale() || parent.isUnknownSex();
2287
+ if (setFather) {
2288
+ chil.set("_FREL", pedi);
2289
+ }
2290
+ if (setMother) {
2291
+ chil.set("_MREL", pedi);
2292
+ }
2293
+ };
2294
+ var attachSpouseToFamily = (fam, indi) => {
2295
+ const gedcom = fam.getGedcom() ?? indi.getGedcom();
2296
+ if (!indi.id || !fam.id || !gedcom) {
2297
+ return false;
2298
+ }
2299
+ if (!canAttachAsSpouse(fam, indi)) {
2300
+ return false;
2301
+ }
2302
+ const tag = spouseTagFor(indi, fam);
2303
+ if (firstPointerId(fam.get(tag)) !== indi.id) {
2304
+ fam.set(tag, makePointer(gedcom, fam, indi.id, "INDI"));
2305
+ }
2306
+ indi.assign("FAMS", makePointer(gedcom, indi, fam.id, "FAM"), true);
2307
+ return true;
2308
+ };
2309
+ var attachChildToFamily = (fam, child, pedigree, parent) => {
2310
+ const gedcom = fam.getGedcom() ?? child.getGedcom();
2311
+ if (!child.id || !fam.id || !gedcom) {
2312
+ return false;
2313
+ }
2314
+ if (!familyHasChild(fam, child.id)) {
2315
+ fam.assign("CHIL", makePointer(gedcom, fam, child.id, "INDI"), true);
2316
+ }
2317
+ const existingFamc = pointerByValue(child.get("FAMC"), fam.id);
2318
+ if (!existingFamc) {
2319
+ child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
2320
+ }
2321
+ setChildPedigree(fam, child, pedigree, parent);
2322
+ return true;
2323
+ };
2324
+ var findReusableParentChildFamily = (parent, child) => {
2325
+ if (!parent.id || !child.id) {
2326
+ return void 0;
2327
+ }
2328
+ let reusableFamc;
2329
+ let already = false;
2330
+ child.getFamilies("FAMC")?.forEach((fam) => {
2331
+ if (!fam) {
2332
+ return;
2333
+ }
2334
+ if (familyHasParent(fam, parent.id) && familyHasChild(fam, child.id)) {
2335
+ already = true;
2336
+ return;
2337
+ }
2338
+ if (!reusableFamc && canAttachAsSpouse(fam, parent)) {
2339
+ reusableFamc = fam;
2340
+ }
2341
+ });
2342
+ if (already) {
2343
+ return "already";
2344
+ }
2345
+ if (reusableFamc) {
2346
+ return reusableFamc;
2347
+ }
2348
+ const parentFams = [];
2349
+ parent.getFamilies("FAMS")?.forEach((fam) => {
2350
+ if (fam) {
2351
+ parentFams.push(fam);
2352
+ }
2353
+ });
2354
+ if (parentFams.length === 1) {
2355
+ return parentFams[0];
2356
+ }
2357
+ return void 0;
2358
+ };
2359
+
2032
2360
  // src/utils/get-places.ts
2033
2361
  var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, mainKey) => {
2034
2362
  const types = Array.isArray(type) ? type : [type];
@@ -2093,16 +2421,58 @@ var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, main
2093
2421
  });
2094
2422
  return places;
2095
2423
  };
2424
+
2425
+ // src/utils/local-media.ts
2426
+ var localMediaResolver;
2427
+ var isRemoteOrEmbeddedMediaUrl = (path) => {
2428
+ if (!path) {
2429
+ return false;
2430
+ }
2431
+ return path.startsWith("data:") || path.startsWith("blob:") || /^https?:\/\//i.test(path);
2432
+ };
2433
+ var resolveLocalMediaUrl = async (path) => {
2434
+ if (!path) {
2435
+ return void 0;
2436
+ }
2437
+ if (isRemoteOrEmbeddedMediaUrl(path)) {
2438
+ return path;
2439
+ }
2440
+ const resolved = await localMediaResolver?.(path);
2441
+ return resolved;
2442
+ };
2096
2443
  var implemented = (type, ...args) => {
2097
2444
  };
2098
2445
 
2099
2446
  // src/utils/media-utils.ts
2100
2447
  var getFileExtension = (filename) => {
2101
- const match = filename.match(/\.([^.]+)$/);
2102
- return match ? match[1] : "";
2448
+ if (!filename) {
2449
+ return "";
2450
+ }
2451
+ if (filename.startsWith("data:")) {
2452
+ const mime = filename.slice(5).split(";")[0] || "";
2453
+ const subtype = (mime.split("/")[1] || "").toLowerCase();
2454
+ if (subtype === "jpeg") {
2455
+ return "jpg";
2456
+ }
2457
+ return subtype.split("+")[0] || "";
2458
+ }
2459
+ const withoutQuery = filename.split(/[?#]/)[0] || filename;
2460
+ const match = withoutQuery.match(/\.([^.]+)$/);
2461
+ return match ? match[1].toLowerCase() : "";
2103
2462
  };
2104
2463
  var isImageFormat = (format2) => {
2105
- if (!format2) return false;
2464
+ if (!format2) {
2465
+ return false;
2466
+ }
2467
+ let normalized = format2.toLowerCase().trim();
2468
+ if (normalized.startsWith("data:")) {
2469
+ normalized = getFileExtension(normalized);
2470
+ } else if (normalized.includes("/")) {
2471
+ normalized = normalized.split("/")[1]?.split("+")[0] || "";
2472
+ if (normalized === "jpeg") {
2473
+ normalized = "jpg";
2474
+ }
2475
+ }
2106
2476
  const imageFormats = [
2107
2477
  "jpg",
2108
2478
  "jpeg",
@@ -2114,7 +2484,11 @@ var isImageFormat = (format2) => {
2114
2484
  "tiff",
2115
2485
  "tif"
2116
2486
  ];
2117
- return imageFormats.includes(format2.toLowerCase());
2487
+ return imageFormats.includes(normalized);
2488
+ };
2489
+ var resolveObjeForm = (obje) => {
2490
+ const value = obje?.get?.("FILE.FORM")?.toValue?.() ?? obje?.get?.("FORM")?.toValue?.();
2491
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
2118
2492
  };
2119
2493
 
2120
2494
  // src/factories/place-parser-provider.ts
@@ -2687,6 +3061,34 @@ var Fam = class extends Common {
2687
3061
  getParents() {
2688
3062
  return this.getHusband().copy().merge(this.getWife());
2689
3063
  }
3064
+ hasParent(indi) {
3065
+ const indiId = typeof indi === "string" ? indi : indi?.id;
3066
+ return !!indiId && familyHasParent(this, indiId);
3067
+ }
3068
+ hasChild(indi) {
3069
+ const indiId = typeof indi === "string" ? indi : indi?.id;
3070
+ return !!indiId && familyHasChild(this, indiId);
3071
+ }
3072
+ canAddSpouse(indi) {
3073
+ return canAttachAsSpouse(this, indi);
3074
+ }
3075
+ addSpouse(indi) {
3076
+ return attachSpouseToFamily(this, indi);
3077
+ }
3078
+ addChild(child, pedigree, parent) {
3079
+ return attachChildToFamily(
3080
+ this,
3081
+ child,
3082
+ pedigree,
3083
+ parent
3084
+ );
3085
+ }
3086
+ addFact(input) {
3087
+ return addFamilyFact(this, input);
3088
+ }
3089
+ addNonEvent(input) {
3090
+ return addNonEvent(this, input);
3091
+ }
2690
3092
  toList() {
2691
3093
  return new Families().concat(
2692
3094
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2787,19 +3189,51 @@ var ALLOWED_FACTS = [
2787
3189
  "OCCU",
2788
3190
  "OCCUPATIONS",
2789
3191
  "RESI",
3192
+ "BIRT",
3193
+ "DEAT",
2790
3194
  "BAPT",
3195
+ "BAPM",
2791
3196
  "CHRI",
2792
3197
  "CHR",
3198
+ "CHRA",
2793
3199
  "BURI",
2794
- "EDUC",
3200
+ "CREM",
3201
+ "ADOP",
3202
+ "BARM",
3203
+ "BASM",
3204
+ "BLES",
3205
+ "CONF",
3206
+ "FCOM",
3207
+ "ORDN",
3208
+ "NATU",
3209
+ "EMIG",
3210
+ "IMMI",
3211
+ "CENS",
3212
+ "PROB",
3213
+ "WILL",
2795
3214
  "GRAD",
3215
+ "RETI",
3216
+ "EDUC",
2796
3217
  "DIV",
2797
- "IMMI",
2798
3218
  "RELI",
3219
+ "CAST",
3220
+ "DSCR",
3221
+ "NATI",
3222
+ "PROP",
3223
+ "SSN",
3224
+ "TITL",
3225
+ "ADDR",
3226
+ "BAPL",
3227
+ "CONL",
3228
+ "ENDL",
3229
+ "SLGC",
2799
3230
  "_MILT",
2800
3231
  "_MILTID",
2801
3232
  "FACT",
2802
- "_ORIG"
3233
+ "_ORIG",
3234
+ "_WLNK",
3235
+ "WWW",
3236
+ "URL"
2803
3237
  ];
2804
3238
  var DISALLOWED_CUSTOM_FACTS = ["DNA Test", "Newspaper"];
2805
3239
  var CustomFactRenderers = {
@@ -3343,7 +3777,7 @@ var Indi = class extends Common {
3343
3777
  return result;
3344
3778
  }
3345
3779
  ancestryLink() {
3346
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3780
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3347
3781
  const tree = this.getAncestryTreeId();
3348
3782
  if (this.id) {
3349
3783
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -3355,7 +3789,7 @@ var Indi = class extends Common {
3355
3789
  async ancestryMedia(namespace) {
3356
3790
  const list = {};
3357
3791
  const objeList = this.get("OBJE")?.toList().copy();
3358
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3792
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3359
3793
  const tree = this.getAncestryTreeId();
3360
3794
  if (objeList) {
3361
3795
  await Promise.all(
@@ -3373,10 +3807,14 @@ var Indi = class extends Common {
3373
3807
  const clone = obje?.get("_CLON._OID")?.toValue();
3374
3808
  const mser = obje?.get("_MSER._LKID")?.toValue();
3375
3809
  let url = obje?.get("FILE")?.toValue();
3376
- const title = obje?.get("TITL")?.toValue() ?? "";
3377
- const type = obje?.get("FORM")?.toValue() ?? "raw";
3378
- const imgId = clone || mser;
3379
- if (!www || !tree || !this.id) {
3810
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
3811
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3812
+ let imgId = clone || mser;
3813
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
3814
+ if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
3815
+ return;
3816
+ }
3817
+ if (!this.id) {
3380
3818
  return;
3381
3819
  }
3382
3820
  if (!namespace && !url) {
@@ -3394,13 +3832,16 @@ var Indi = class extends Common {
3394
3832
  }
3395
3833
  url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
3396
3834
  }
3835
+ if (!imgId && hasEmbeddedOrLocalFile && url) {
3836
+ imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
3837
+ }
3397
3838
  if (url && imgId) {
3398
- const id = `${tree}-${this.id}-${imgId}`;
3839
+ const id = `${tree || "gedzip"}-${this.id}-${imgId}`;
3399
3840
  list[id] = {
3400
3841
  key,
3401
3842
  isPrimary,
3402
3843
  id,
3403
- tree,
3844
+ tree: tree || "gedzip",
3404
3845
  imgId,
3405
3846
  person: this.id,
3406
3847
  title,
@@ -3415,7 +3856,7 @@ var Indi = class extends Common {
3415
3856
  return list;
3416
3857
  }
3417
3858
  myheritageLink(poolId = 0) {
3418
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.value?.toLowerCase().replace(/^www\./gi, "");
3859
+ const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
3419
3860
  const site = this.getMyHeritageTreeId();
3420
3861
  const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
3421
3862
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
@@ -3446,9 +3887,11 @@ var Indi = class extends Common {
3446
3887
  obje.standardizeMedia();
3447
3888
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
3448
3889
  const url = obje?.get("FILE")?.toValue();
3449
- const title = obje?.get("NOTE")?.toValue() ?? "";
3450
- const type = obje?.get("FORM")?.toValue() ?? "raw";
3451
- const imgId = obje?.get("_PHOTO_RIN")?.toValue();
3890
+ const title = obje?.get("FILE.NOTE")?.toValue() ?? obje?.get("NOTE")?.toValue() ?? "";
3891
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3892
+ const photoRin = obje?.get("_PHOTO_RIN")?.toValue();
3893
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
3894
+ const imgId = photoRin || (hasEmbeddedOrLocalFile && url ? `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}` : void 0);
3452
3895
  if (url && imgId) {
3453
3896
  const id = `${tree}-${this.id}-${imgId}`;
3454
3897
  list[id] = {
@@ -3571,10 +4014,10 @@ var Indi = class extends Common {
3571
4014
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
3572
4015
  const url = obje?.get("FILE")?.toValue();
3573
4016
  const title = obje?.get("TITL")?.toValue() ?? "";
3574
- const type = obje?.get("FORM")?.toValue() ?? "raw";
4017
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3575
4018
  if (url) {
3576
4019
  const urlMatch = url.match(/\/([^/]+)\?hash=/);
3577
- const imgId = urlMatch?.[1] || `img-${index}-${Date.now().toString(36)}`;
4020
+ const imgId = urlMatch?.[1] || (url.startsWith("data:") ? `data-${index}-${getFileExtension(url) || "bin"}` : `img-${index}-${Date.now().toString(36)}`);
3578
4021
  const id = `geni-${geniId}-${imgId}`;
3579
4022
  list[id] = {
3580
4023
  isPrimary,
@@ -3611,10 +4054,10 @@ var Indi = class extends Common {
3611
4054
  obje.standardizeMedia();
3612
4055
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
3613
4056
  const url = obje?.get("FILE")?.toValue();
3614
- const title = obje?.get("TITL")?.toValue() ?? "";
3615
- const type = obje?.get("FORM")?.toValue() ?? "raw";
4057
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
4058
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3616
4059
  if (url) {
3617
- const imgId = `media-${index}-${url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
4060
+ const imgId = `media-${index}-${url.startsWith("data:") ? getFileExtension(url) || "bin" : url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
3618
4061
  const id = `${treeId}-${this.id}-${imgId}`;
3619
4062
  list[id] = {
3620
4063
  isPrimary,
@@ -3654,7 +4097,20 @@ var Indi = class extends Common {
3654
4097
  cacheKey
3655
4098
  );
3656
4099
  if (cached !== void 0) {
3657
- return cached;
4100
+ if (cached?.file && !isRemoteOrEmbeddedMediaUrl(cached.file)) {
4101
+ const resolvedCached = await resolveLocalMediaUrl(cached.file);
4102
+ if (resolvedCached && isRemoteOrEmbeddedMediaUrl(resolvedCached)) {
4103
+ const refreshed = { ...cached, file: resolvedCached };
4104
+ profilePictureCache(
4105
+ this._gedcom,
4106
+ cacheKey,
4107
+ refreshed
4108
+ );
4109
+ return refreshed;
4110
+ }
4111
+ } else {
4112
+ return cached;
4113
+ }
3658
4114
  }
3659
4115
  const mediaList = await this.multimedia(namespace);
3660
4116
  if (!mediaList) {
@@ -3666,39 +4122,40 @@ var Indi = class extends Common {
3666
4122
  return void 0;
3667
4123
  }
3668
4124
  const mediaArray = Object.values(mediaList);
3669
- const primaryMedia = mediaArray.find(
3670
- (media) => media.isPrimary && isImageFormat(media.contentType || getFileExtension(media.url))
3671
- );
3672
- if (primaryMedia) {
3673
- const result = {
3674
- file: primaryMedia.url,
3675
- form: primaryMedia.contentType,
3676
- title: primaryMedia.title,
3677
- isPrimary: true
4125
+ const toDisplayableProfile = async (media, isPrimary) => {
4126
+ const resolvedFile = await resolveLocalMediaUrl(media.url);
4127
+ if (!resolvedFile || !isRemoteOrEmbeddedMediaUrl(resolvedFile)) {
4128
+ return void 0;
4129
+ }
4130
+ return {
4131
+ file: resolvedFile,
4132
+ form: media.contentType,
4133
+ title: media.title,
4134
+ isPrimary
3678
4135
  };
3679
- profilePictureCache(this._gedcom, cacheKey, result);
3680
- return result;
3681
- }
3682
- if (onlyPrimary) {
3683
- profilePictureCache(
3684
- this._gedcom,
3685
- cacheKey,
3686
- void 0
3687
- );
3688
- return void 0;
3689
- }
3690
- const secondaryMedia = mediaArray.find(
3691
- (media) => isImageFormat(media.contentType || getFileExtension(media.url))
4136
+ };
4137
+ const candidates = mediaArray.filter(
4138
+ (media) => isImageFormat(
4139
+ media.contentType || media.url || getFileExtension(media.url)
4140
+ )
3692
4141
  );
3693
- if (secondaryMedia) {
3694
- const result = {
3695
- file: secondaryMedia.url,
3696
- form: secondaryMedia.contentType,
3697
- title: secondaryMedia.title,
3698
- isPrimary: false
3699
- };
3700
- profilePictureCache(this._gedcom, cacheKey, result);
3701
- return result;
4142
+ const ordered = [
4143
+ ...candidates.filter((media) => media.isPrimary),
4144
+ ...candidates.filter((media) => !media.isPrimary)
4145
+ ];
4146
+ for (const media of ordered) {
4147
+ if (onlyPrimary && !media.isPrimary) {
4148
+ break;
4149
+ }
4150
+ const result = await toDisplayableProfile(media, !!media.isPrimary);
4151
+ if (result) {
4152
+ profilePictureCache(
4153
+ this._gedcom,
4154
+ cacheKey,
4155
+ result
4156
+ );
4157
+ return result;
4158
+ }
3702
4159
  }
3703
4160
  profilePictureCache(
3704
4161
  this._gedcom,
@@ -3936,6 +4393,156 @@ var Indi = class extends Common {
3936
4393
  isUnknownSex() {
3937
4394
  return !this.isMale() && !this.isFemale();
3938
4395
  }
4396
+ addSpouse(other) {
4397
+ if (!this.id || !other?.id || this.id === other.id || !this._gedcom) {
4398
+ return void 0;
4399
+ }
4400
+ const existing = getFamilyWith(this, other.id);
4401
+ if (existing) {
4402
+ return existing;
4403
+ }
4404
+ const fam = this._gedcom.createFamily();
4405
+ if (!fam.addSpouse(this) || !fam.addSpouse(other)) {
4406
+ return void 0;
4407
+ }
4408
+ return fam;
4409
+ }
4410
+ addChild(child, pedigree) {
4411
+ if (!this.id || !child?.id || this.id === child.id || !this._gedcom) {
4412
+ return void 0;
4413
+ }
4414
+ const existing = findReusableParentChildFamily(
4415
+ this,
4416
+ child
4417
+ );
4418
+ if (existing === "already") {
4419
+ const fam2 = child.getFamilies("FAMC")?.find(
4420
+ (item) => !!item && item.hasParent(this.id) && item.hasChild(child.id)
4421
+ );
4422
+ if (fam2) {
4423
+ setChildPedigree(
4424
+ fam2,
4425
+ child,
4426
+ pedigree,
4427
+ this
4428
+ );
4429
+ }
4430
+ return fam2;
4431
+ }
4432
+ const fam = existing ?? this._gedcom.createFamily();
4433
+ if (!fam.addSpouse(this)) {
4434
+ return void 0;
4435
+ }
4436
+ if (!fam.addChild(child, pedigree, this)) {
4437
+ return void 0;
4438
+ }
4439
+ return fam;
4440
+ }
4441
+ addParent(parent, pedigree) {
4442
+ return parent.addChild(this, pedigree);
4443
+ }
4444
+ /**
4445
+ * Link an existing top-level OBJE record to this individual (`1 OBJE @On@`).
4446
+ */
4447
+ attachMultimedia(objeOrKey, options) {
4448
+ if (!this._gedcom || !this.id) {
4449
+ return void 0;
4450
+ }
4451
+ const obje = typeof objeOrKey === "string" ? this._gedcom.obje(objeOrKey) : objeOrKey;
4452
+ if (!obje?.id) {
4453
+ return void 0;
4454
+ }
4455
+ const already = this.get("OBJE")?.toList()?.values()?.some((item) => item?.toValue?.() === obje.id);
4456
+ if (!already) {
4457
+ const pointer = makePointer(
4458
+ this._gedcom,
4459
+ this,
4460
+ obje.id,
4461
+ "OBJE"
4462
+ );
4463
+ this.assign("OBJE", pointer);
4464
+ }
4465
+ if (options?.primary) {
4466
+ this.get("OBJE")?.toList()?.forEach((item) => {
4467
+ const target = item?.refType === "OBJE" ? this._gedcom?.obje(
4468
+ String(item.toValue())
4469
+ ) : item;
4470
+ if (!target) {
4471
+ return;
4472
+ }
4473
+ if (target.id === obje.id) {
4474
+ target.set("_PRIM", "Y");
4475
+ } else if (target.get("_PRIM")?.toValue() === "Y") {
4476
+ target.set("_PRIM", "N");
4477
+ }
4478
+ });
4479
+ obje.set("_PRIM", "Y");
4480
+ }
4481
+ return obje;
4482
+ }
4483
+ detachMultimedia(objeKey) {
4484
+ const list = this.get("OBJE")?.toList();
4485
+ if (!list) {
4486
+ return;
4487
+ }
4488
+ const toRemove = list.values().filter(
4489
+ (item) => item?.toValue?.() === objeKey || item?.id === objeKey
4490
+ );
4491
+ toRemove.forEach((item) => {
4492
+ if (item) {
4493
+ list.delete(item);
4494
+ }
4495
+ });
4496
+ if (list.length === 0) {
4497
+ this.remove("OBJE");
4498
+ }
4499
+ }
4500
+ /**
4501
+ * Create a multimedia record and attach it to this person.
4502
+ */
4503
+ attachMediaFromUrl(url, options) {
4504
+ if (!this._gedcom || !url) {
4505
+ return void 0;
4506
+ }
4507
+ const obje = this._gedcom.createMultimediaRecord({
4508
+ file: url,
4509
+ form: options?.form,
4510
+ title: options?.title,
4511
+ mediType: options?.mediType,
4512
+ primary: options?.primary,
4513
+ gedcomVersion: options?.gedcomVersion
4514
+ });
4515
+ return this.attachMultimedia(obje, { primary: options?.primary });
4516
+ }
4517
+ /**
4518
+ * Link a shared SNOTE record via `1 NOTE @Nn@` (GEDCOM 7).
4519
+ */
4520
+ attachSharedNote(noteOrKey) {
4521
+ if (!this._gedcom || !this.id) {
4522
+ return void 0;
4523
+ }
4524
+ const note = typeof noteOrKey === "string" ? this._gedcom.snote(noteOrKey) : noteOrKey;
4525
+ if (!note?.id) {
4526
+ return void 0;
4527
+ }
4528
+ const already = this.get("NOTE")?.toList()?.values()?.some((item) => item?.toValue?.() === note.id);
4529
+ if (!already) {
4530
+ const pointer = makePointer(
4531
+ this._gedcom,
4532
+ this,
4533
+ note.id,
4534
+ "SNOTE"
4535
+ );
4536
+ this.assign("NOTE", pointer);
4537
+ }
4538
+ return note;
4539
+ }
4540
+ addFact(input) {
4541
+ return addIndividualFact(this, input);
4542
+ }
4543
+ addNonEvent(input) {
4544
+ return addNonEvent(this, input);
4545
+ }
3939
4546
  getParentType(id) {
3940
4547
  let indi;
3941
4548
  if (typeof id === "string" || typeof id === "number") {
@@ -3976,7 +4583,7 @@ var Indi = class extends Common {
3976
4583
  if (!famChild) {
3977
4584
  const childIndi = this._gedcom?.indi(child.id);
3978
4585
  const childState = childIndi?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
3979
- childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
4586
+ childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
3980
4587
  } else {
3981
4588
  childType = famChild.get(usedRel)?.toValue() || "biological" /* BIOLOGICAL */;
3982
4589
  }
@@ -4136,7 +4743,9 @@ var Indi = class extends Common {
4136
4743
  }
4137
4744
  }
4138
4745
  if (kinship !== "child" || !breakAfterSpouse) {
4139
- indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(indi.getFathers().copy().merge(indi.getMothers())).forEach((relative) => {
4746
+ indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(
4747
+ indi.getFathers().copy().merge(indi.getMothers())
4748
+ ).forEach((relative) => {
4140
4749
  if (!visited.has(relative)) {
4141
4750
  const currentRelation = indi.getParentType(relative);
4142
4751
  const isNonBiological = !!currentRelation && currentRelation !== "biological" /* BIOLOGICAL */;
@@ -4535,7 +5144,7 @@ var Indi = class extends Common {
4535
5144
  return;
4536
5145
  }
4537
5146
  const childState = child?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
4538
- const childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
5147
+ const childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
4539
5148
  if (childType?.toLowerCase() === filter.PEDI.toLowerCase()) {
4540
5149
  children.append(child);
4541
5150
  }
@@ -4643,7 +5252,7 @@ var Indi = class extends Common {
4643
5252
  const child = this.id && family.CHIL?.toValueList()?.item(this.id);
4644
5253
  if (!child) {
4645
5254
  const childState = this?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
4646
- const parentType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
5255
+ const parentType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
4647
5256
  if (parentType?.toLowerCase() === filter.PEDI.toLowerCase()) {
4648
5257
  parents.merge(family.getParents());
4649
5258
  }