@ncukondo/slide-generation 0.2.5 → 0.4.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.
package/dist/index.d.ts CHANGED
@@ -598,7 +598,7 @@ declare class ReferenceManager {
598
598
  */
599
599
  getById(id: string): Promise<CSLItem | null>;
600
600
  /**
601
- * Get multiple references by IDs
601
+ * Get multiple references by IDs using ref export for better performance
602
602
  */
603
603
  getByIds(ids: string[]): Promise<Map<string, CSLItem>>;
604
604
  private execCommand;
@@ -624,7 +624,13 @@ interface FormatterConfig {
624
624
  declare class CitationFormatter {
625
625
  private manager;
626
626
  private config;
627
+ private availabilityChecked;
628
+ private isAvailable;
627
629
  constructor(manager: ReferenceManager, config?: FormatterConfig);
630
+ /**
631
+ * Check if reference-manager is available (cached)
632
+ */
633
+ private checkAvailability;
628
634
  /**
629
635
  * Format an inline citation
630
636
  * e.g., "(Smith et al., 2024; PMID: 12345678)"
@@ -644,13 +650,7 @@ declare class CitationFormatter {
644
650
  */
645
651
  generateBibliography(ids: string[], sort?: 'author' | 'year' | 'citation-order'): Promise<string[]>;
646
652
  private formatInlineItem;
647
- private formatFullItem;
648
653
  private formatAuthorInline;
649
- private formatAuthorsFull;
650
- private isJapaneseAuthors;
651
- private getFirstAuthorFamily;
652
- private getYear;
653
- private getIdentifier;
654
654
  }
655
655
 
656
656
  /**
@@ -966,6 +966,7 @@ declare class Pipeline {
966
966
  private referenceManager;
967
967
  private citationExtractor;
968
968
  private citationFormatter;
969
+ private bibliographyGenerator;
969
970
  private transformer;
970
971
  private renderer;
971
972
  private warnings;
@@ -1006,6 +1007,19 @@ declare class Pipeline {
1006
1007
  * Stage 5: Render the final Marp markdown
1007
1008
  */
1008
1009
  private render;
1010
+ /**
1011
+ * Process bibliography slides with autoGenerate: true
1012
+ * Populates references array from collected citations
1013
+ */
1014
+ private processBibliography;
1015
+ /**
1016
+ * Format authors for template-compatible format
1017
+ */
1018
+ private formatAuthorsForTemplate;
1019
+ /**
1020
+ * Get year from CSL item
1021
+ */
1022
+ private getYear;
1009
1023
  }
1010
1024
 
1011
1025
  /**
package/dist/index.js CHANGED
@@ -918,20 +918,20 @@ var ReferenceManager = class {
918
918
  return items[0] || null;
919
919
  }
920
920
  /**
921
- * Get multiple references by IDs
921
+ * Get multiple references by IDs using ref export for better performance
922
922
  */
923
923
  async getByIds(ids) {
924
924
  if (ids.length === 0) {
925
925
  return /* @__PURE__ */ new Map();
926
926
  }
927
- const result = await this.execCommand(`${this.command} list --format json`);
928
- const allItems = this.parseJSON(result);
929
- const idSet = new Set(ids);
927
+ const idsArg = ids.map((id) => `"${id}"`).join(" ");
928
+ const result = await this.execCommand(
929
+ `${this.command} export ${idsArg}`
930
+ );
931
+ const items = this.parseJSON(result);
930
932
  const map = /* @__PURE__ */ new Map();
931
- for (const item of allItems) {
932
- if (idSet.has(item.id)) {
933
- map.set(item.id, item);
934
- }
933
+ for (const item of items) {
934
+ map.set(item.id, item);
935
935
  }
936
936
  return map;
937
937
  }
@@ -1075,6 +1075,82 @@ var CitationExtractor = class {
1075
1075
  }
1076
1076
  };
1077
1077
 
1078
+ // src/references/utils.ts
1079
+ var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
1080
+ function isJapaneseAuthors(authors) {
1081
+ if (!authors || authors.length === 0) {
1082
+ return false;
1083
+ }
1084
+ return JAPANESE_PATTERN.test(authors[0].family);
1085
+ }
1086
+ function getYear(item) {
1087
+ const dateParts = item.issued?.["date-parts"];
1088
+ if (dateParts && dateParts[0] && dateParts[0][0]) {
1089
+ return dateParts[0][0];
1090
+ }
1091
+ return 0;
1092
+ }
1093
+ function getIdentifier(item) {
1094
+ if (item.PMID) {
1095
+ return `PMID: ${item.PMID}`;
1096
+ }
1097
+ if (item.DOI) {
1098
+ return `DOI: ${item.DOI}`;
1099
+ }
1100
+ return null;
1101
+ }
1102
+ function getFirstAuthorFamily(item) {
1103
+ return item.author?.[0]?.family || "";
1104
+ }
1105
+ function formatAuthorsFull(authors, isJapanese) {
1106
+ if (!authors || authors.length === 0) {
1107
+ return "Unknown";
1108
+ }
1109
+ if (isJapanese) {
1110
+ return authors.map((a) => `${a.family}${a.given || ""}`).join(", ");
1111
+ }
1112
+ if (authors.length === 1) {
1113
+ const a = authors[0];
1114
+ const initial = a.given ? `${a.given.charAt(0)}.` : "";
1115
+ return `${a.family}, ${initial}`;
1116
+ }
1117
+ const formatted = authors.map((a, i) => {
1118
+ const initial = a.given ? `${a.given.charAt(0)}.` : "";
1119
+ if (i === authors.length - 1) {
1120
+ return `& ${a.family}, ${initial}`;
1121
+ }
1122
+ return `${a.family}, ${initial}`;
1123
+ });
1124
+ return formatted.join(", ").replace(", &", ", &");
1125
+ }
1126
+ function formatFullEntry(item) {
1127
+ const parts = [];
1128
+ const japanese = isJapaneseAuthors(item.author);
1129
+ const authors = formatAuthorsFull(item.author, japanese);
1130
+ parts.push(authors);
1131
+ const year = getYear(item);
1132
+ parts.push(`(${year}).`);
1133
+ if (item.title) {
1134
+ parts.push(`${item.title}.`);
1135
+ }
1136
+ if (item["container-title"]) {
1137
+ const journal = japanese ? item["container-title"] : `*${item["container-title"]}*`;
1138
+ let location = "";
1139
+ if (item.volume) {
1140
+ location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
1141
+ }
1142
+ if (item.page) {
1143
+ location = location ? `${location}, ${item.page}` : item.page;
1144
+ }
1145
+ parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
1146
+ }
1147
+ const identifier = getIdentifier(item);
1148
+ if (identifier) {
1149
+ parts.push(identifier);
1150
+ }
1151
+ return parts.join(" ");
1152
+ }
1153
+
1078
1154
  // src/references/formatter.ts
1079
1155
  var DEFAULT_CONFIG = {
1080
1156
  author: {
@@ -1089,7 +1165,6 @@ var DEFAULT_CONFIG = {
1089
1165
  multiSep: "), ("
1090
1166
  }
1091
1167
  };
1092
- var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
1093
1168
  var CITATION_BRACKET_PATTERN2 = /\[(@[\w-]+(?:,\s*[^;\]]+)?(?:;\s*@[\w-]+(?:,\s*[^;\]]+)?)*)\]/g;
1094
1169
  var SINGLE_CITATION_PATTERN2 = /@([\w-]+)(?:,\s*([^;\]]+))?/g;
1095
1170
  var CitationFormatter = class {
@@ -1101,6 +1176,18 @@ var CitationFormatter = class {
1101
1176
  };
1102
1177
  }
1103
1178
  config;
1179
+ availabilityChecked = false;
1180
+ isAvailable = false;
1181
+ /**
1182
+ * Check if reference-manager is available (cached)
1183
+ */
1184
+ async checkAvailability() {
1185
+ if (!this.availabilityChecked) {
1186
+ this.isAvailable = await this.manager.isAvailable();
1187
+ this.availabilityChecked = true;
1188
+ }
1189
+ return this.isAvailable;
1190
+ }
1104
1191
  /**
1105
1192
  * Format an inline citation
1106
1193
  * e.g., "(Smith et al., 2024; PMID: 12345678)"
@@ -1120,13 +1207,16 @@ var CitationFormatter = class {
1120
1207
  if (!item) {
1121
1208
  return `[${id}]`;
1122
1209
  }
1123
- return this.formatFullItem(item);
1210
+ return formatFullEntry(item);
1124
1211
  }
1125
1212
  /**
1126
1213
  * Expand all citations in text
1127
1214
  * e.g., "[@smith2024]" -> "(Smith et al., 2024; PMID: 12345678)"
1128
1215
  */
1129
1216
  async expandCitations(text) {
1217
+ if (!await this.checkAvailability()) {
1218
+ return text;
1219
+ }
1130
1220
  const ids = /* @__PURE__ */ new Set();
1131
1221
  CITATION_BRACKET_PATTERN2.lastIndex = 0;
1132
1222
  let match;
@@ -1181,117 +1271,102 @@ var CitationFormatter = class {
1181
1271
  sortedItems = ids.map((id) => items.get(id)).filter((item) => item !== void 0);
1182
1272
  } else if (sort === "author") {
1183
1273
  sortedItems = [...items.values()].sort((a, b) => {
1184
- const authorA = this.getFirstAuthorFamily(a);
1185
- const authorB = this.getFirstAuthorFamily(b);
1274
+ const authorA = getFirstAuthorFamily(a);
1275
+ const authorB = getFirstAuthorFamily(b);
1186
1276
  return authorA.localeCompare(authorB);
1187
1277
  });
1188
1278
  } else {
1189
1279
  sortedItems = [...items.values()].sort((a, b) => {
1190
- const yearA = this.getYear(a);
1191
- const yearB = this.getYear(b);
1280
+ const yearA = getYear(a);
1281
+ const yearB = getYear(b);
1192
1282
  return yearA - yearB;
1193
1283
  });
1194
1284
  }
1195
- return sortedItems.map((item) => this.formatFullItem(item));
1285
+ return sortedItems.map((item) => formatFullEntry(item));
1196
1286
  }
1197
1287
  formatInlineItem(item) {
1198
1288
  const author = this.formatAuthorInline(item.author);
1199
- const year = this.getYear(item);
1200
- const identifier = this.getIdentifier(item);
1289
+ const year = getYear(item);
1290
+ const identifier = getIdentifier(item);
1201
1291
  if (identifier) {
1202
1292
  return `(${author}, ${year}; ${identifier})`;
1203
1293
  }
1204
1294
  return `(${author}, ${year})`;
1205
1295
  }
1206
- formatFullItem(item) {
1207
- const parts = [];
1208
- const isJapanese = this.isJapaneseAuthors(item.author);
1209
- const authors = this.formatAuthorsFull(item.author, isJapanese);
1210
- parts.push(authors);
1211
- const year = this.getYear(item);
1212
- parts.push(`(${year}).`);
1213
- if (item.title) {
1214
- parts.push(`${item.title}.`);
1215
- }
1216
- if (item["container-title"]) {
1217
- const journal = isJapanese ? item["container-title"] : `*${item["container-title"]}*`;
1218
- let location = "";
1219
- if (item.volume) {
1220
- location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
1221
- }
1222
- if (item.page) {
1223
- location = location ? `${location}, ${item.page}` : item.page;
1224
- }
1225
- parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
1226
- }
1227
- const identifier = this.getIdentifier(item);
1228
- if (identifier) {
1229
- parts.push(identifier);
1230
- }
1231
- return parts.join(" ");
1232
- }
1233
1296
  formatAuthorInline(authors) {
1234
1297
  if (!authors || authors.length === 0) {
1235
1298
  return "Unknown";
1236
1299
  }
1237
- const isJapanese = this.isJapaneseAuthors(authors);
1300
+ const japanese = isJapaneseAuthors(authors);
1238
1301
  const { etAl, etAlJa, separatorJa } = this.config.author;
1239
1302
  const firstAuthor = authors[0];
1240
1303
  if (authors.length === 1) {
1241
1304
  return firstAuthor.family;
1242
1305
  }
1243
1306
  if (authors.length === 2) {
1244
- const separator = isJapanese ? separatorJa : " & ";
1307
+ const separator = japanese ? separatorJa : " & ";
1245
1308
  return `${firstAuthor.family}${separator}${authors[1].family}`;
1246
1309
  }
1247
- const suffix = isJapanese ? etAlJa : ` ${etAl}`;
1310
+ const suffix = japanese ? etAlJa : ` ${etAl}`;
1248
1311
  return `${firstAuthor.family}${suffix}`;
1249
1312
  }
1250
- formatAuthorsFull(authors, isJapanese) {
1251
- if (!authors || authors.length === 0) {
1252
- return "Unknown";
1253
- }
1254
- if (isJapanese) {
1255
- return authors.map((a) => `${a.family}${a.given || ""}`).join(", ");
1256
- }
1257
- if (authors.length === 1) {
1258
- const a = authors[0];
1259
- const initial = a.given ? `${a.given.charAt(0)}.` : "";
1260
- return `${a.family}, ${initial}`;
1261
- }
1262
- const formatted = authors.map((a, i) => {
1263
- const initial = a.given ? `${a.given.charAt(0)}.` : "";
1264
- if (i === authors.length - 1) {
1265
- return `& ${a.family}, ${initial}`;
1266
- }
1267
- return `${a.family}, ${initial}`;
1268
- });
1269
- return formatted.join(", ").replace(", &", ", &");
1270
- }
1271
- isJapaneseAuthors(authors) {
1272
- if (!authors || authors.length === 0) {
1273
- return false;
1274
- }
1275
- return JAPANESE_PATTERN.test(authors[0].family);
1276
- }
1277
- getFirstAuthorFamily(item) {
1278
- return item.author?.[0]?.family || "";
1313
+ };
1314
+
1315
+ // src/references/bibliography.ts
1316
+ var BibliographyGenerator = class {
1317
+ constructor(manager) {
1318
+ this.manager = manager;
1279
1319
  }
1280
- getYear(item) {
1281
- const dateParts = item.issued?.["date-parts"];
1282
- if (dateParts && dateParts[0] && dateParts[0][0]) {
1283
- return dateParts[0][0];
1320
+ /**
1321
+ * Generate bibliography entries from citation IDs
1322
+ */
1323
+ async generate(citationIds, options = {}) {
1324
+ const { sort = "citation-order" } = options;
1325
+ if (citationIds.length === 0) {
1326
+ return { entries: [], items: [], missing: [] };
1327
+ }
1328
+ const uniqueIds = [...new Set(citationIds)];
1329
+ const itemsMap = await this.manager.getByIds(uniqueIds);
1330
+ const missing = [];
1331
+ const foundItems = [];
1332
+ for (const id of uniqueIds) {
1333
+ const item = itemsMap.get(id);
1334
+ if (item) {
1335
+ foundItems.push(item);
1336
+ } else {
1337
+ missing.push(id);
1338
+ }
1284
1339
  }
1285
- return 0;
1340
+ const sortedItems = this.sortItems(foundItems, sort);
1341
+ const entries = sortedItems.map((item) => formatFullEntry(item));
1342
+ return {
1343
+ entries,
1344
+ items: sortedItems,
1345
+ missing
1346
+ };
1286
1347
  }
1287
- getIdentifier(item) {
1288
- if (item.PMID) {
1289
- return `PMID: ${item.PMID}`;
1290
- }
1291
- if (item.DOI) {
1292
- return `DOI: ${item.DOI}`;
1348
+ /**
1349
+ * Sort items according to the specified order
1350
+ */
1351
+ sortItems(items, sort) {
1352
+ switch (sort) {
1353
+ case "citation-order":
1354
+ return items;
1355
+ case "author":
1356
+ return [...items].sort((a, b) => {
1357
+ const authorA = getFirstAuthorFamily(a);
1358
+ const authorB = getFirstAuthorFamily(b);
1359
+ return authorA.localeCompare(authorB);
1360
+ });
1361
+ case "year":
1362
+ return [...items].sort((a, b) => {
1363
+ const yearA = getYear(a);
1364
+ const yearB = getYear(b);
1365
+ return yearA - yearB;
1366
+ });
1367
+ default:
1368
+ return items;
1293
1369
  }
1294
- return null;
1295
1370
  }
1296
1371
  };
1297
1372
 
@@ -1330,6 +1405,7 @@ var Pipeline = class {
1330
1405
  }
1331
1406
  }
1332
1407
  );
1408
+ this.bibliographyGenerator = new BibliographyGenerator(this.referenceManager);
1333
1409
  this.transformer = new Transformer(
1334
1410
  this.templateEngine,
1335
1411
  this.templateLoader,
@@ -1346,6 +1422,7 @@ var Pipeline = class {
1346
1422
  referenceManager;
1347
1423
  citationExtractor;
1348
1424
  citationFormatter;
1425
+ bibliographyGenerator;
1349
1426
  transformer;
1350
1427
  renderer;
1351
1428
  warnings = [];
@@ -1355,9 +1432,10 @@ var Pipeline = class {
1355
1432
  async run(inputPath, options) {
1356
1433
  this.warnings = [];
1357
1434
  try {
1358
- const presentation = await this.parseSource(inputPath);
1435
+ let presentation = await this.parseSource(inputPath);
1359
1436
  const citationIds = this.collectCitations(presentation);
1360
1437
  await this.resolveReferences(citationIds);
1438
+ presentation = await this.processBibliography(presentation, citationIds);
1361
1439
  const transformedSlides = await this.transformSlides(presentation);
1362
1440
  const output = this.render(transformedSlides, presentation);
1363
1441
  if (options?.outputPath) {
@@ -1381,9 +1459,10 @@ var Pipeline = class {
1381
1459
  async runWithResult(inputPath, options) {
1382
1460
  this.warnings = [];
1383
1461
  try {
1384
- const presentation = await this.parseSource(inputPath);
1462
+ let presentation = await this.parseSource(inputPath);
1385
1463
  const citationIds = this.collectCitations(presentation);
1386
1464
  await this.resolveReferences(citationIds);
1465
+ presentation = await this.processBibliography(presentation, citationIds);
1387
1466
  const transformedSlides = await this.transformSlides(presentation);
1388
1467
  const output = this.render(transformedSlides, presentation);
1389
1468
  if (options?.outputPath) {
@@ -1459,6 +1538,13 @@ var Pipeline = class {
1459
1538
  if (!this.config.references.enabled || ids.length === 0) {
1460
1539
  return /* @__PURE__ */ new Map();
1461
1540
  }
1541
+ const isAvailable = await this.referenceManager.isAvailable();
1542
+ if (!isAvailable) {
1543
+ this.warnings.push(
1544
+ "reference-manager CLI is not available. Install it to enable citation features: npm install -g @ncukondo/reference-manager"
1545
+ );
1546
+ return /* @__PURE__ */ new Map();
1547
+ }
1462
1548
  try {
1463
1549
  const items = await this.referenceManager.getByIds(ids);
1464
1550
  for (const id of ids) {
@@ -1507,10 +1593,97 @@ var Pipeline = class {
1507
1593
  );
1508
1594
  }
1509
1595
  }
1596
+ /**
1597
+ * Process bibliography slides with autoGenerate: true
1598
+ * Populates references array from collected citations
1599
+ */
1600
+ async processBibliography(presentation, citationIds) {
1601
+ if (!this.config.references.enabled || citationIds.length === 0) {
1602
+ return presentation;
1603
+ }
1604
+ const hasBibliographyAutoGenerate = presentation.slides.some(
1605
+ (slide) => slide.template === "bibliography" && slide.content?.["autoGenerate"] === true
1606
+ );
1607
+ if (!hasBibliographyAutoGenerate) {
1608
+ return presentation;
1609
+ }
1610
+ const isAvailable = await this.referenceManager.isAvailable();
1611
+ if (!isAvailable) {
1612
+ return presentation;
1613
+ }
1614
+ try {
1615
+ const updatedSlides = await Promise.all(
1616
+ presentation.slides.map(async (slide) => {
1617
+ if (slide.template === "bibliography" && slide.content?.["autoGenerate"] === true) {
1618
+ const sort = slide.content["sort"] || "citation-order";
1619
+ const result = await this.bibliographyGenerator.generate(
1620
+ citationIds,
1621
+ { sort }
1622
+ );
1623
+ for (const id of result.missing) {
1624
+ this.warnings.push(`Bibliography: reference not found: ${id}`);
1625
+ }
1626
+ const references = result.items.map((item) => ({
1627
+ id: item.id,
1628
+ authors: this.formatAuthorsForTemplate(item.author),
1629
+ title: item.title || "",
1630
+ year: this.getYear(item),
1631
+ journal: item["container-title"],
1632
+ volume: item.volume,
1633
+ pages: item.page,
1634
+ doi: item.DOI,
1635
+ url: item.URL
1636
+ }));
1637
+ return {
1638
+ ...slide,
1639
+ content: {
1640
+ ...slide.content,
1641
+ references,
1642
+ _autoGenerated: true,
1643
+ _generatedEntries: result.entries
1644
+ }
1645
+ };
1646
+ }
1647
+ return slide;
1648
+ })
1649
+ );
1650
+ return {
1651
+ ...presentation,
1652
+ slides: updatedSlides
1653
+ };
1654
+ } catch (error) {
1655
+ this.warnings.push(
1656
+ `Failed to auto-generate bibliography: ${error instanceof Error ? error.message : "Unknown error"}`
1657
+ );
1658
+ return presentation;
1659
+ }
1660
+ }
1661
+ /**
1662
+ * Format authors for template-compatible format
1663
+ */
1664
+ formatAuthorsForTemplate(authors) {
1665
+ if (!authors || authors.length === 0) {
1666
+ return void 0;
1667
+ }
1668
+ return authors.map((a) => {
1669
+ const initial = a.given ? `${a.given.charAt(0)}.` : "";
1670
+ return initial ? `${a.family}, ${initial}` : a.family;
1671
+ });
1672
+ }
1673
+ /**
1674
+ * Get year from CSL item
1675
+ */
1676
+ getYear(item) {
1677
+ const dateParts = item.issued?.["date-parts"];
1678
+ if (dateParts && dateParts[0] && dateParts[0][0]) {
1679
+ return dateParts[0][0];
1680
+ }
1681
+ return void 0;
1682
+ }
1510
1683
  };
1511
1684
 
1512
1685
  // src/index.ts
1513
- var VERSION = "0.2.5";
1686
+ var VERSION = "0.4.0";
1514
1687
  export {
1515
1688
  ParseError,
1516
1689
  Parser,