@anja.codes/vitakit 0.2.0 → 0.3.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/README.md +4 -3
- package/dist/check.mjs +14 -12
- package/dist/chunks/authoring.mjs +50 -45
- package/dist/{shared/vitakit.By5VWQ1n.mjs → chunks/bibliography.mjs} +73 -170
- package/dist/chunks/build.mjs +19 -17
- package/dist/chunks/documents.mjs +15 -6
- package/dist/chunks/draft.mjs +3 -3
- package/dist/chunks/eject.mjs +2 -2
- package/dist/chunks/emit.mjs +38 -22
- package/dist/chunks/fields.mjs +2 -2
- package/dist/chunks/inventory.mjs +29 -27
- package/dist/chunks/migrations.mjs +218 -0
- package/dist/chunks/plan.mjs +36 -8
- package/dist/chunks/publication-lists.mjs +202 -0
- package/dist/chunks/schemas.mjs +2 -2
- package/dist/chunks/sections.mjs +3 -3
- package/dist/chunks/validate.mjs +25 -16
- package/dist/chunks/vue.mjs +89 -52
- package/dist/chunks/yaml.mjs +62 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +12 -10
- package/dist/nuxt.mjs +19 -11
- package/dist/runtime/components/VitaPublications.vue +21 -22
- package/dist/runtime/composables/useVitakit.ts +14 -3
- package/dist/schemas/publications.ts +9 -7
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/{vitakit.BfpxDtz1.d.mts → vitakit.BfR_mUHm.d.mts} +17 -5
- package/dist/shared/{vitakit.BZZIsWlY.mjs → vitakit.CCTDg7pz.mjs} +21 -1
- package/dist/shared/{vitakit.DbSeENVS.mjs → vitakit.DCK90zq4.mjs} +10 -1
- package/dist/shared/vitakit.DteA9vLy.mjs +135 -0
- package/dist/shared/{vitakit.BerUaxtF.mjs → vitakit.DvABWuUc.mjs} +1 -1
- package/dist/shared/{vitakit.Do5HC_MD.mjs → vitakit.DvvUYUTS.mjs} +61 -3
- package/dist/shared/vitakit.UfoXdL7U.mjs +22 -0
- package/dist/shared/{vitakit.CoWX2Z4R.mjs → vitakit.zwr1ssO5.mjs} +353 -57
- package/dist/tex/templates/publications.eta +4 -2
- package/dist/tex/vitakit-cv.cls +15 -4
- package/dist/tex.d.mts +29 -8
- package/dist/tex.mjs +13 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,11 +27,12 @@ published site, [concepts](https://vitakit.anja.codes/guide/concepts),
|
|
|
27
27
|
content/
|
|
28
28
|
profile.yml name, affiliation, links, menu, footer
|
|
29
29
|
<section>.yml education, employment, teaching, talks, …
|
|
30
|
-
publications.bib
|
|
30
|
+
publications.bib your publications, formatted and linked
|
|
31
|
+
publication-lists.yml which of them each list shows: recent, preprints, by year, …
|
|
31
32
|
pages/*.md prose pages, mathematics and images included
|
|
32
33
|
schemas/<section>.ts optional: what a section of your own may contain
|
|
33
|
-
app/pages/*.vue which sections each website page shows
|
|
34
|
-
cv-tex/<document>.tex which sections each PDF shows; cv-tex/<document>.yml selects entries
|
|
34
|
+
app/pages/*.vue which sections and lists each website page shows
|
|
35
|
+
cv-tex/<document>.tex which sections and lists each PDF shows; cv-tex/<document>.yml selects entries
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
- **Validated content.** Every file is checked against its schema. A broken
|
package/dist/check.mjs
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
import { readFile, stat, readdir } from 'node:fs/promises';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
|
-
import { l as loadContent } from './shared/vitakit.
|
|
4
|
-
import { M as MARKER_KEY } from './shared/vitakit.
|
|
3
|
+
import { l as loadContent } from './shared/vitakit.DteA9vLy.mjs';
|
|
4
|
+
import { M as MARKER_KEY } from './shared/vitakit.DvABWuUc.mjs';
|
|
5
5
|
import { a as richSource, r as richHtml } from './shared/vitakit.CtChKO0I.mjs';
|
|
6
6
|
import { a as atMost } from './shared/vitakit.TSMTC5Jl.mjs';
|
|
7
7
|
import 'gray-matter';
|
|
8
8
|
import 'yaml';
|
|
9
|
+
import './chunks/bibliography.mjs';
|
|
10
|
+
import 'node:fs';
|
|
11
|
+
import './shared/vitakit.DQ6PmGoL.mjs';
|
|
12
|
+
import 'node:url';
|
|
13
|
+
import '@behackl/citation-js-extras';
|
|
9
14
|
import 'unified';
|
|
15
|
+
import 'rehype-parse';
|
|
16
|
+
import 'rehype-sanitize';
|
|
17
|
+
import 'rehype-stringify';
|
|
10
18
|
import 'remark-parse';
|
|
11
19
|
import 'remark-gfm';
|
|
12
20
|
import 'remark-math';
|
|
13
21
|
import 'remark-rehype';
|
|
14
22
|
import 'rehype-katex';
|
|
15
23
|
import 'rehype-raw';
|
|
16
|
-
import 'rehype-stringify';
|
|
17
24
|
import 'unist-util-visit';
|
|
18
|
-
import './shared/vitakit.
|
|
19
|
-
import 'node:fs';
|
|
20
|
-
import './shared/vitakit.DQ6PmGoL.mjs';
|
|
21
|
-
import 'node:url';
|
|
22
|
-
import '@behackl/citation-js-extras';
|
|
23
|
-
import 'rehype-parse';
|
|
24
|
-
import 'rehype-sanitize';
|
|
25
|
-
import './shared/vitakit.DbSeENVS.mjs';
|
|
25
|
+
import './shared/vitakit.DCK90zq4.mjs';
|
|
26
26
|
import 'zod';
|
|
27
|
+
import './chunks/publication-lists.mjs';
|
|
28
|
+
import './shared/vitakit.D10HyDhF.mjs';
|
|
27
29
|
import './chunks/sections.mjs';
|
|
28
30
|
import './chunks/schemas.mjs';
|
|
29
31
|
|
|
@@ -190,7 +192,7 @@ async function checkStatic(options) {
|
|
|
190
192
|
}
|
|
191
193
|
checked.push("links into the site lead somewhere");
|
|
192
194
|
}
|
|
193
|
-
if (content.publications.
|
|
195
|
+
if (Object.values(content.publications).some((list) => list.count)) {
|
|
194
196
|
const bibPages = [...pages].filter(([, html]) => html.includes("vita-bib"));
|
|
195
197
|
for (const [file, html] of bibPages) {
|
|
196
198
|
if (/bibmathplaceholderx*\d+end/i.test(html)) fail(`${relative(outDir, file)}: a formula in the bibliography was not rendered`);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { p as propertyKey, a as addSchemaField, e as ensurePackageImports } from '../shared/vitakit.
|
|
3
|
-
import { f as findPdfPlacements } from '../shared/vitakit.
|
|
1
|
+
import { parse } from 'yaml';
|
|
2
|
+
import { p as propertyKey, a as addSchemaField, e as ensurePackageImports } from '../shared/vitakit.CCTDg7pz.mjs';
|
|
3
|
+
import { c as findPdfPublicationPlacements, f as findPdfPlacements } from '../shared/vitakit.DvvUYUTS.mjs';
|
|
4
4
|
import { a as escapeTex } from '../shared/vitakit.DcQOdM4I.mjs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { b as insertLinesAfter, i as indentationAt, c as insertLinesBefore } from '../shared/vitakit.UfoXdL7U.mjs';
|
|
6
|
+
import { addPublicationsToPage, publicationsElementSource, addSectionToPage, sectionElementSource } from './vue.mjs';
|
|
7
|
+
import { addNavItem, yamlScalar } from './yaml.mjs';
|
|
8
|
+
import { C as CommandError } from '../shared/vitakit.zwr1ssO5.mjs';
|
|
7
9
|
import '@babel/parser';
|
|
8
10
|
import 'node:fs/promises';
|
|
9
11
|
import 'node:path';
|
|
@@ -18,56 +20,42 @@ import 'node:readline/promises';
|
|
|
18
20
|
function vitasectionLine(id, heading) {
|
|
19
21
|
return `\\vitasection${heading ? `[${escapeTex(heading)}]` : ""}{${id}}`;
|
|
20
22
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
function vitapublicationsLine(list, heading) {
|
|
24
|
+
return `\\vitapublications${heading ? `[${escapeTex(heading)}]` : ""}{${list}}`;
|
|
25
|
+
}
|
|
26
|
+
function anchors(source) {
|
|
27
|
+
return [
|
|
28
|
+
...findPdfPlacements("", "", source).map((placement) => ({ id: placement.section, line: placement.line })),
|
|
29
|
+
...findPdfPublicationPlacements("", "", source).map((placement) => ({ id: placement.list, line: placement.line }))
|
|
30
|
+
].sort((a, b) => a.line - b.line);
|
|
31
|
+
}
|
|
32
|
+
function insertLine(source, text, after) {
|
|
33
|
+
const found = anchors(source);
|
|
27
34
|
const lines = source.split("\n");
|
|
28
|
-
const offsetOf = (lineNumber) => lines.slice(0, lineNumber - 1).reduce((sum,
|
|
29
|
-
const anchor = (
|
|
35
|
+
const offsetOf = (lineNumber) => lines.slice(0, lineNumber - 1).reduce((sum, line) => sum + line.length + 1, 0);
|
|
36
|
+
const anchor = (after && found.find((existing) => existing.id === after)) ?? found[found.length - 1];
|
|
30
37
|
if (anchor) {
|
|
31
38
|
const offset = offsetOf(anchor.line);
|
|
32
39
|
return {
|
|
33
40
|
ok: true,
|
|
34
|
-
value: { source: insertLinesAfter(source, offset, [`${indentationAt(source, offset)}${
|
|
41
|
+
value: { source: insertLinesAfter(source, offset, [`${indentationAt(source, offset)}${text}`]), line: anchor.line + 1, afterSection: anchor.id }
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
|
-
const end = lines.findIndex((
|
|
44
|
+
const end = lines.findIndex((line) => /^\s*\\end\{document\}/.test(line));
|
|
38
45
|
if (end === -1) return { ok: false, reason: "it has no \\end{document} line" };
|
|
39
|
-
return { ok: true, value: { source: insertLinesBefore(source, offsetOf(end + 1), [
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function yamlScalar(value) {
|
|
43
|
-
return /^[A-Za-z0-9/][\w ./()'-]*$/.test(value) && !/(^|\s)#|:\s|\s$/.test(value) ? value : JSON.stringify(value);
|
|
46
|
+
return { ok: true, value: { source: insertLinesBefore(source, offsetOf(end + 1), [text, ""]), line: end + 1 } };
|
|
44
47
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!isMap(document.contents)) return { ok: false, reason: "it is not a map of settings" };
|
|
49
|
-
const entry = `- { label: ${yamlScalar(item.label)}, to: ${yamlScalar(item.to)} }`;
|
|
50
|
-
const nav = document.contents.get("nav", true);
|
|
51
|
-
if (nav === void 0) {
|
|
52
|
-
const ending = source.endsWith("\n") ? "" : "\n";
|
|
53
|
-
return { ok: true, value: `${source}${ending}
|
|
54
|
-
nav:
|
|
55
|
-
${entry}
|
|
56
|
-
` };
|
|
48
|
+
function addSectionToDocument(source, placement) {
|
|
49
|
+
if (findPdfPlacements("", "", source).some((existing) => existing.section === placement.id && existing.macro === "vitasection")) {
|
|
50
|
+
return { ok: false, reason: `it already includes ${placement.id}` };
|
|
57
51
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
return insertLine(source, vitasectionLine(placement.id, placement.heading), placement.after);
|
|
53
|
+
}
|
|
54
|
+
function addPublicationsToDocument(source, placement) {
|
|
55
|
+
if (findPdfPublicationPlacements("", "", source).some((existing) => existing.list === placement.id)) {
|
|
56
|
+
return { ok: false, reason: `it already includes the list ${placement.id}` };
|
|
61
57
|
}
|
|
62
|
-
|
|
63
|
-
if (!last?.range) return { ok: false, reason: "its nav is empty; add the first entry by hand" };
|
|
64
|
-
const start = lineStart(source, last.range[0]);
|
|
65
|
-
const prefix = /^[ \t]*-[ \t]*/.exec(source.slice(start))?.[0];
|
|
66
|
-
if (!prefix) return { ok: false, reason: "its nav entries are not one per line" };
|
|
67
|
-
const end = lineEnd(source, last.range[1] - 1);
|
|
68
|
-
const indent = prefix.slice(0, prefix.indexOf("-"));
|
|
69
|
-
return { ok: true, value: `${source.slice(0, end)}
|
|
70
|
-
${indent}${entry}${source.slice(end)}` };
|
|
58
|
+
return insertLine(source, vitapublicationsLine(placement.id, placement.heading), placement.after);
|
|
71
59
|
}
|
|
72
60
|
|
|
73
61
|
function dataStarter(id) {
|
|
@@ -255,6 +243,23 @@ async function planPlaceInDocument(ws, document, placement) {
|
|
|
255
243
|
ws.paste(path, vitasectionLine(placement.id, placement.title), `cannot be changed safely (${result.reason}); add this line where the section should appear`);
|
|
256
244
|
}
|
|
257
245
|
}
|
|
246
|
+
async function planListOnPage(ws, inv, path, placement) {
|
|
247
|
+
const source = await ws.read(path);
|
|
248
|
+
if (source === void 0) throw new CommandError(`${path} does not exist`);
|
|
249
|
+
const result = addPublicationsToPage(source, placement, inv.web.components.keys());
|
|
250
|
+
if (result.ok) await ws.edit(path, result.value.source, `shows the list ${placement.list}${result.value.afterSection ? ` after ${result.value.afterSection}` : ""}`);
|
|
251
|
+
else if (result.reason.startsWith("it already shows")) ws.keep(path, `already shows the list ${placement.list}`);
|
|
252
|
+
else ws.paste(path, publicationsElementSource(placement.list, placement.title), `cannot be changed safely (${result.reason}); add this line where the list should appear`);
|
|
253
|
+
}
|
|
254
|
+
async function planListInDocument(ws, document, placement) {
|
|
255
|
+
const path = `cv-tex/${document}.tex`;
|
|
256
|
+
const source = await ws.read(path);
|
|
257
|
+
if (source === void 0) throw new CommandError(`${path} does not exist`);
|
|
258
|
+
const result = addPublicationsToDocument(source, { id: placement.id, ...placement.title ? { heading: placement.title } : {}, ...placement.after ? { after: placement.after } : {} });
|
|
259
|
+
if (result.ok) await ws.edit(path, result.value.source, `includes the list ${placement.id}${result.value.afterSection ? ` after ${result.value.afterSection}` : ""}`);
|
|
260
|
+
else if (result.reason.startsWith("it already includes")) ws.keep(path, `already includes the list ${placement.id}`);
|
|
261
|
+
else ws.paste(path, vitapublicationsLine(placement.id, placement.title), `cannot be changed safely (${result.reason}); add this line where the list should appear`);
|
|
262
|
+
}
|
|
258
263
|
function pageModule(route, name, title) {
|
|
259
264
|
return `<script setup lang="ts">
|
|
260
265
|
/**
|
|
@@ -364,4 +369,4 @@ async function planMissingFields(rootDir, ws, section, entries) {
|
|
|
364
369
|
return { fields: names, changes: extension.changes };
|
|
365
370
|
}
|
|
366
371
|
|
|
367
|
-
export { FIELD_TYPES, STARTER_ENTRIES, dataExample, dataStarter, exampleValue, extensionModule, fieldCode, fieldsNotShown, matchingSchemas, planAddField, planCreatePage, planMissingFields, planPlaceInDocument, planPlaceOnPage, rawEntries, schemaOwner, sharedSchemaModule };
|
|
372
|
+
export { FIELD_TYPES, STARTER_ENTRIES, dataExample, dataStarter, exampleValue, extensionModule, fieldCode, fieldsNotShown, matchingSchemas, planAddField, planCreatePage, planListInDocument, planListOnPage, planMissingFields, planPlaceInDocument, planPlaceOnPage, rawEntries, schemaOwner, sharedSchemaModule };
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { stat, readFile } from 'node:fs/promises';
|
|
2
3
|
import { join, isAbsolute, resolve } from 'node:path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
4
|
+
import { r as resolveAsset } from '../shared/vitakit.DQ6PmGoL.mjs';
|
|
5
|
+
import { badgePresets, Bibliography } from '@behackl/citation-js-extras';
|
|
5
6
|
import { unified } from 'unified';
|
|
7
|
+
import rehypeParse from 'rehype-parse';
|
|
8
|
+
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
|
9
|
+
import rehypeStringify from 'rehype-stringify';
|
|
6
10
|
import remarkParse from 'remark-parse';
|
|
7
11
|
import remarkGfm from 'remark-gfm';
|
|
8
12
|
import remarkMath from 'remark-math';
|
|
9
13
|
import remarkRehype from 'remark-rehype';
|
|
10
14
|
import rehypeKatex from 'rehype-katex';
|
|
11
15
|
import rehypeRaw from 'rehype-raw';
|
|
12
|
-
import rehypeStringify from 'rehype-stringify';
|
|
13
16
|
import { visit } from 'unist-util-visit';
|
|
14
|
-
import {
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
|
-
import { r as resolveAsset } from './vitakit.DQ6PmGoL.mjs';
|
|
17
|
-
import { badgePresets, Bibliography } from '@behackl/citation-js-extras';
|
|
18
|
-
import rehypeParse from 'rehype-parse';
|
|
19
|
-
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
|
20
|
-
import { f as fail, a as formatZodIssues, i as importProjectModule, I as Issues, w as whereOf, m as messageOf } from './vitakit.DbSeENVS.mjs';
|
|
17
|
+
import { f as fail, a as formatZodIssues, I as Issues, i as importProjectModule } from '../shared/vitakit.DCK90zq4.mjs';
|
|
21
18
|
import { z } from 'zod';
|
|
22
|
-
import {
|
|
23
|
-
import { profile, page } from '../chunks/schemas.mjs';
|
|
19
|
+
import { LISTS_FILE, selectPublications, publicationStatus } from './publication-lists.mjs';
|
|
24
20
|
|
|
25
21
|
const URL_PROPERTIES = ["href", "src", "poster", "data", "action", "formAction"];
|
|
26
22
|
const isRootRelative = (value) => value.startsWith("/") && !value.startsWith("//");
|
|
@@ -106,30 +102,34 @@ const badge = z.strictObject({
|
|
|
106
102
|
className: z.string().optional()
|
|
107
103
|
});
|
|
108
104
|
const input = z.strictObject({
|
|
109
|
-
|
|
105
|
+
statuses: z.array(z.strictObject({
|
|
110
106
|
id: z.string().regex(ID, "expected a lowercase id such as `preprint` or `talk-slides`"),
|
|
111
107
|
label: z.string().trim().min(1)
|
|
112
|
-
})).min(1, "expected at least one
|
|
113
|
-
|
|
108
|
+
})).min(1, "expected at least one status").optional(),
|
|
109
|
+
defaultStatus: z.string().optional(),
|
|
114
110
|
badges: z.array(badge).optional(),
|
|
115
111
|
fields: z.array(z.string().regex(/^[a-z][a-z0-9_-]*$/i, "expected a BibTeX field name")).optional(),
|
|
116
112
|
cslStyle: z.string().trim().min(1).optional()
|
|
117
113
|
});
|
|
118
|
-
function resolvePublications(value, defaults = {
|
|
114
|
+
function resolvePublications(value, defaults = { statuses: [], defaultStatus: "", badges: [], fields: [] }, where = "schemas/publications.ts") {
|
|
115
|
+
const renamed = ["groups", "defaultGroup"].filter((key) => typeof value === "object" && value !== null && Object.hasOwn(value, key));
|
|
116
|
+
if (renamed.length) {
|
|
117
|
+
fail(where, `${renamed.join(" and ")} ${renamed.length > 1 ? "are" : "is"} now called ${renamed.map((key) => key === "groups" ? "statuses" : "defaultStatus").join(" and ")}; \`vitakit migrate\` renames ${renamed.length > 1 ? "them" : "it"}`);
|
|
118
|
+
}
|
|
119
119
|
const parsed = input.safeParse(value);
|
|
120
120
|
if (!parsed.success) fail(where, `invalid
|
|
121
121
|
${formatZodIssues(parsed.error)}`);
|
|
122
|
-
const
|
|
123
|
-
const ids =
|
|
122
|
+
const statuses = parsed.data.statuses ?? defaults.statuses;
|
|
123
|
+
const ids = statuses.map((status) => status.id);
|
|
124
124
|
const duplicate = ids.find((id, index) => ids.indexOf(id) !== index);
|
|
125
|
-
if (duplicate) fail(where, `
|
|
126
|
-
const
|
|
127
|
-
if (!ids.includes(
|
|
128
|
-
fail(where, `
|
|
125
|
+
if (duplicate) fail(where, `status "${duplicate}" is defined twice`);
|
|
126
|
+
const defaultStatus = parsed.data.defaultStatus ?? (parsed.data.statuses ? ids[0] : defaults.defaultStatus);
|
|
127
|
+
if (!ids.includes(defaultStatus)) {
|
|
128
|
+
fail(where, `defaultStatus "${defaultStatus}" is not one of the statuses (${ids.join(", ")})`);
|
|
129
129
|
}
|
|
130
130
|
return {
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
statuses,
|
|
132
|
+
defaultStatus,
|
|
133
133
|
badges: parsed.data.badges ?? defaults.badges,
|
|
134
134
|
fields: parsed.data.fields ?? defaults.fields,
|
|
135
135
|
...parsed.data.cslStyle ? { cslStyle: parsed.data.cslStyle } : {}
|
|
@@ -138,18 +138,19 @@ ${formatZodIssues(parsed.error)}`);
|
|
|
138
138
|
|
|
139
139
|
const packageDefaults = publications({
|
|
140
140
|
/**
|
|
141
|
-
* The values of the `status` field,
|
|
142
|
-
*
|
|
141
|
+
* The values of the `status` field, with the heading of their group, in
|
|
142
|
+
* the order the groups appear in a list grouped by status. A status that
|
|
143
|
+
* is not listed here is an error, in any capitalisation.
|
|
143
144
|
*/
|
|
144
|
-
|
|
145
|
+
statuses: [
|
|
145
146
|
{ id: "published", label: "Published" },
|
|
146
147
|
{ id: "accepted", label: "Accepted" },
|
|
147
148
|
{ id: "preprint", label: "Preprints" },
|
|
148
149
|
{ id: "software", label: "Software" },
|
|
149
150
|
{ id: "other", label: "Other" }
|
|
150
151
|
],
|
|
151
|
-
/** The
|
|
152
|
-
|
|
152
|
+
/** The status of an entry without one; the first status if left out. */
|
|
153
|
+
defaultStatus: "published",
|
|
153
154
|
/**
|
|
154
155
|
* BibTeX fields that become links after a reference on the website. The
|
|
155
156
|
* presets accept the spellings real exports use (`doi:10...`, doi.org URLs,
|
|
@@ -172,8 +173,6 @@ const packageDefaults = publications({
|
|
|
172
173
|
});
|
|
173
174
|
|
|
174
175
|
const defaultPublications = resolvePublications(packageDefaults, void 0, "src/schemas/publications.ts");
|
|
175
|
-
defaultPublications.badges;
|
|
176
|
-
defaultPublications.groups;
|
|
177
176
|
async function loadPublicationsSchema(rootDir) {
|
|
178
177
|
const where = "schemas/publications.ts";
|
|
179
178
|
const path = join(rootDir, where);
|
|
@@ -240,17 +239,28 @@ const GENRES = {
|
|
|
240
239
|
techreport: "Technical report"
|
|
241
240
|
};
|
|
242
241
|
const readableGenre = (html, { variable }) => variable === "genre" ? GENRES[html.toLowerCase()] ?? html : html;
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
const readBib = (path) => readFile(path, "utf8").catch((error) => {
|
|
243
|
+
if (error.code === "ENOENT") return "";
|
|
244
|
+
throw error;
|
|
245
|
+
});
|
|
246
|
+
async function readBibliography(bibPath, schema) {
|
|
247
|
+
try {
|
|
248
|
+
return new Bibliography({ data: await readBib(bibPath), customFields: ["status", ...schema.fields], preserveMath: true });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new Error(`${bibPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function loadPublications(bibPath, renderer, schema, lists, baseURL = "/", issues = new Issues(true)) {
|
|
254
|
+
const { statuses, fields } = schema;
|
|
245
255
|
const prefix = baseURL.replace(/\/$/, "");
|
|
246
256
|
const withFile = (error) => new Error(`${bibPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
247
257
|
let bib;
|
|
248
258
|
try {
|
|
249
259
|
bib = new Bibliography({
|
|
250
|
-
data: await
|
|
260
|
+
data: await readBib(bibPath),
|
|
251
261
|
cslStyle: schema.cslStyle ?? projectCslStyle(bibPath) ?? defaultCslStyle,
|
|
252
262
|
// Only fields listed here survive the citation-js pipeline; they then
|
|
253
|
-
// appear as `entry.custom.<field>`. Badges read the raw fields.
|
|
263
|
+
// appear as `entry.custom.<field>`. Badges and lists read the raw fields.
|
|
254
264
|
customFields: ["status", ...fields],
|
|
255
265
|
preserveMath: true,
|
|
256
266
|
// Defaults for every formatHtml call below.
|
|
@@ -262,18 +272,15 @@ async function loadPublications(bibPath, renderer, schema = defaultPublications,
|
|
|
262
272
|
} catch (error) {
|
|
263
273
|
throw withFile(error);
|
|
264
274
|
}
|
|
265
|
-
const statusOf = (entry) => (entry
|
|
266
|
-
const known = new Set(
|
|
275
|
+
const statusOf = (entry) => publicationStatus(entry, schema);
|
|
276
|
+
const known = new Set(statuses.map((status) => status.id));
|
|
267
277
|
const unknown = bib.entries.filter((entry) => !known.has(statusOf(entry)));
|
|
268
278
|
if (unknown.length) {
|
|
269
279
|
throw new Error(
|
|
270
|
-
`${bibPath}: unknown status ${[...new Set(unknown.map(statusOf))].join(", ")} on ${unknown.map((entry) => entry.key).join(", ")}; expected one of ${[...known].join(", ")} (
|
|
280
|
+
`${bibPath}: unknown status ${[...new Set(unknown.map(statusOf))].join(", ")} on ${unknown.map((entry) => entry.key).join(", ")}; expected one of ${[...known].join(", ")} (statuses are defined in schemas/publications.ts)`
|
|
271
281
|
);
|
|
272
282
|
}
|
|
273
|
-
const
|
|
274
|
-
for (const group of groups) {
|
|
275
|
-
const entries = bib.sort(bib.entries.filter((entry) => statusOf(entry) === group.id), { by: "date" });
|
|
276
|
-
if (!entries.length) continue;
|
|
283
|
+
const format = (entries) => {
|
|
277
284
|
let items;
|
|
278
285
|
try {
|
|
279
286
|
items = splitList(bib.formatHtml(entries, {
|
|
@@ -286,7 +293,7 @@ async function loadPublications(bibPath, renderer, schema = defaultPublications,
|
|
|
286
293
|
} catch (error) {
|
|
287
294
|
throw withFile(error);
|
|
288
295
|
}
|
|
289
|
-
|
|
296
|
+
return entries.map((entry) => {
|
|
290
297
|
const html = items.get(entry.key);
|
|
291
298
|
if (html === void 0) throw new Error(`${bibPath}: ${entry.key} is missing from the formatted list`);
|
|
292
299
|
const { title, badges } = bib.links(entry);
|
|
@@ -304,134 +311,30 @@ async function loadPublications(bibPath, renderer, schema = defaultPublications,
|
|
|
304
311
|
}))
|
|
305
312
|
};
|
|
306
313
|
});
|
|
307
|
-
|
|
314
|
+
};
|
|
315
|
+
const result = {};
|
|
316
|
+
for (const [name, list] of Object.entries(lists)) {
|
|
317
|
+
result[name] = await issues.attempt(LISTS_FILE, () => {
|
|
318
|
+
const groups = selectPublications(bib, name, list, schema).map((group, index) => ({
|
|
319
|
+
id: `${name}-${index + 1}`,
|
|
320
|
+
...group.label !== void 0 ? { label: group.label } : {},
|
|
321
|
+
count: group.entries.length,
|
|
322
|
+
entries: format(group.entries)
|
|
323
|
+
}));
|
|
324
|
+
return { count: groups.reduce((sum, group) => sum + group.count, 0), groups };
|
|
325
|
+
}, { count: 0, groups: [] });
|
|
308
326
|
}
|
|
309
327
|
return result;
|
|
310
328
|
}
|
|
311
329
|
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
async function loadProfile(contentDir, baseURL, renderer, target) {
|
|
322
|
-
const data = await loadYaml(join(contentDir, "profile.yml"), "content/profile.yml");
|
|
323
|
-
const parsed = profile.safeParse(data);
|
|
324
|
-
if (!parsed.success) fail("content/profile.yml", `invalid
|
|
325
|
-
${formatZodIssues(parsed.error)}`);
|
|
326
|
-
const prefix = baseURL.replace(/\/$/, "");
|
|
327
|
-
const { avatar, nav, footer, ...rest } = parsed.data;
|
|
328
|
-
return resolveSitePaths({
|
|
329
|
-
...rest,
|
|
330
|
-
...avatar ? { avatar: prefixRootRelative(avatar, prefix) } : {},
|
|
331
|
-
nav: nav.map((item) => ({ label: item.label, url: item.to })),
|
|
332
|
-
footerHtml: footer ? renderer.inline(footer) : ""
|
|
333
|
-
}, sitePathResolver(target, baseURL, parsed.data.url));
|
|
334
|
-
}
|
|
335
|
-
function sitePathResolver(target, baseURL, siteUrl) {
|
|
336
|
-
const prefix = baseURL.replace(/\/$/, "");
|
|
337
|
-
return target === "pdf" ? (path) => absoluteSitePath(path, siteUrl) : (path) => prefixRootRelative(path, prefix);
|
|
338
|
-
}
|
|
339
|
-
function errorPage(route, where, message) {
|
|
340
|
-
const escaped = message.replace(/[&<>]/g, (character) => ({ "&": "&", "<": "<", ">": ">" })[character]);
|
|
341
|
-
return {
|
|
342
|
-
route,
|
|
343
|
-
title: `Error in ${where}`,
|
|
344
|
-
titleHtml: `Error in <code>${where}</code>`,
|
|
345
|
-
order: -1,
|
|
346
|
-
bodyHtml: `<pre>${escaped}</pre>`,
|
|
347
|
-
error: message
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
async function loadPage(dir, file, renderer) {
|
|
351
|
-
const where = `content/pages/${file}`;
|
|
352
|
-
const slug = file.slice(0, -".md".length);
|
|
353
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
|
|
354
|
-
fail(where, "file name must be lowercase kebab-case, e.g. `research-interests.md`");
|
|
355
|
-
}
|
|
356
|
-
const { data, content } = matter(await readFile(join(dir, file), "utf8"));
|
|
357
|
-
const parsed = page.safeParse(data);
|
|
358
|
-
if (!parsed.success) fail(where, `invalid front matter
|
|
359
|
-
${formatZodIssues(parsed.error)}`);
|
|
360
|
-
const route = parsed.data.route ?? (slug === "index" ? "/" : `/${slug}/`);
|
|
361
|
-
try {
|
|
362
|
-
return {
|
|
363
|
-
route,
|
|
364
|
-
title: parsed.data.title,
|
|
365
|
-
titleHtml: renderer.inline(parsed.data.title),
|
|
366
|
-
description: parsed.data.description,
|
|
367
|
-
order: parsed.data.order,
|
|
368
|
-
bodyHtml: content.trim() ? renderer.markdown(content) : ""
|
|
369
|
-
};
|
|
370
|
-
} catch (error) {
|
|
371
|
-
fail(where, messageOf(error));
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
async function loadPages(contentDir, renderer, issues) {
|
|
375
|
-
const dir = join(contentDir, "pages");
|
|
376
|
-
const files = (await readdir(dir).catch(() => [])).filter((name) => name.endsWith(".md")).sort();
|
|
377
|
-
const pages = await Promise.all(files.map(async (file) => {
|
|
378
|
-
try {
|
|
379
|
-
return await loadPage(dir, file, renderer);
|
|
380
|
-
} catch (error) {
|
|
381
|
-
if (issues.strict) throw error;
|
|
382
|
-
const where = whereOf(error, `content/pages/${file}`);
|
|
383
|
-
const message = messageOf(error);
|
|
384
|
-
issues.record(where, message);
|
|
385
|
-
const slug = file.slice(0, -".md".length);
|
|
386
|
-
return errorPage(slug === "index" ? "/" : `/${slug}/`, where, message);
|
|
387
|
-
}
|
|
388
|
-
}));
|
|
389
|
-
const seen = /* @__PURE__ */ new Map();
|
|
390
|
-
for (const [index, page] of pages.entries()) {
|
|
391
|
-
const previous = seen.get(page.route);
|
|
392
|
-
if (previous) issues.record(`content/pages/${files[index]}`, `route ${page.route} is already used by ${previous}`);
|
|
393
|
-
else seen.set(page.route, `content/pages/${files[index]}`);
|
|
394
|
-
}
|
|
395
|
-
return pages.sort((a, b) => a.order - b.order || a.route.localeCompare(b.route));
|
|
396
|
-
}
|
|
397
|
-
async function loadSections(rootDir, maxVisibility, renderer, resolvePath, issues) {
|
|
398
|
-
const { files, problems } = await issues.attempt("content/", () => findSectionFiles(rootDir), { files: [], problems: [] });
|
|
399
|
-
for (const problem of problems) issues.record(problem.where, problem.message);
|
|
400
|
-
const sections = {};
|
|
401
|
-
for (const file of files) {
|
|
402
|
-
sections[file.id] = await issues.attempt(
|
|
403
|
-
file.where,
|
|
404
|
-
async () => loadSection(await resolveSectionSchema(rootDir, file), { maxVisibility, renderer, resolvePath }),
|
|
405
|
-
[]
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
return sections;
|
|
409
|
-
}
|
|
410
|
-
async function loadContent(rootDir, options = {}) {
|
|
411
|
-
const strict = options.strict ?? true;
|
|
412
|
-
const baseURL = options.baseURL ?? "/";
|
|
413
|
-
const maxVisibility = options.maxVisibility ?? "public";
|
|
414
|
-
const issues = new Issues(strict);
|
|
415
|
-
const renderer = createRenderer({ baseURL });
|
|
416
|
-
const contentDir = join(rootDir, "content");
|
|
417
|
-
const target = options.target ?? "website";
|
|
418
|
-
const profile = await issues.attempt("content/profile.yml", () => loadProfile(contentDir, baseURL, renderer, target), FALLBACK_PROFILE);
|
|
419
|
-
const resolvePath = sitePathResolver(target, baseURL, profile.url);
|
|
420
|
-
const [pages, sections, publications] = await Promise.all([
|
|
421
|
-
loadPages(contentDir, renderer, issues),
|
|
422
|
-
loadSections(rootDir, maxVisibility, renderer, resolvePath, issues),
|
|
423
|
-
issues.attempt(
|
|
424
|
-
"content/publications.bib",
|
|
425
|
-
async () => {
|
|
426
|
-
const bib = join(contentDir, "publications.bib");
|
|
427
|
-
const present = await readFile(bib, "utf8").then(() => true, () => false);
|
|
428
|
-
if (!present) return [];
|
|
429
|
-
return loadPublications(bib, renderer, await loadPublicationsSchema(rootDir), baseURL);
|
|
430
|
-
},
|
|
431
|
-
[]
|
|
432
|
-
)
|
|
433
|
-
]);
|
|
434
|
-
return { profile, pages, sections, publications, issues: issues.list };
|
|
435
|
-
}
|
|
330
|
+
const bibliography = {
|
|
331
|
+
__proto__: null,
|
|
332
|
+
defaultCslStyle: defaultCslStyle,
|
|
333
|
+
defaultPublications: defaultPublications,
|
|
334
|
+
loadPublications: loadPublications,
|
|
335
|
+
loadPublicationsSchema: loadPublicationsSchema,
|
|
336
|
+
projectCslStyle: projectCslStyle,
|
|
337
|
+
readBibliography: readBibliography
|
|
338
|
+
};
|
|
436
339
|
|
|
437
|
-
export {
|
|
340
|
+
export { prefixRootRelative as a, loadPublications as b, createRenderer as c, defaultPublications as d, bibliography as e, loadPublicationsSchema as l, publications as p };
|
package/dist/chunks/build.mjs
CHANGED
|
@@ -3,32 +3,35 @@ import { mkdir, readFile, writeFile, rm, copyFile, rename } from 'node:fs/promis
|
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { join, delimiter, resolve } from 'node:path';
|
|
6
|
-
import { l as loadContent
|
|
7
|
-
import { f as fail } from '../shared/vitakit.
|
|
6
|
+
import { l as loadContent } from '../shared/vitakit.DteA9vLy.mjs';
|
|
7
|
+
import { f as fail } from '../shared/vitakit.DCK90zq4.mjs';
|
|
8
8
|
import { discoverDocuments, validateDocumentSections, selectEntries } from './documents.mjs';
|
|
9
|
+
import { p as publicationPlacementProblems } from '../shared/vitakit.DvvUYUTS.mjs';
|
|
9
10
|
import { discoverSections } from './sections.mjs';
|
|
10
11
|
import { emitDocument, bundledTexDir, SITE_PATH_HELP } from './emit.mjs';
|
|
11
12
|
import { a as escapeTex, b as escapeUrl } from '../shared/vitakit.DcQOdM4I.mjs';
|
|
12
13
|
import { i as isSitePath } from '../shared/vitakit.D10HyDhF.mjs';
|
|
13
14
|
import 'gray-matter';
|
|
14
15
|
import 'yaml';
|
|
16
|
+
import './bibliography.mjs';
|
|
17
|
+
import '../shared/vitakit.DQ6PmGoL.mjs';
|
|
18
|
+
import 'node:url';
|
|
19
|
+
import '@behackl/citation-js-extras';
|
|
15
20
|
import 'unified';
|
|
21
|
+
import 'rehype-parse';
|
|
22
|
+
import 'rehype-sanitize';
|
|
23
|
+
import 'rehype-stringify';
|
|
16
24
|
import 'remark-parse';
|
|
17
25
|
import 'remark-gfm';
|
|
18
26
|
import 'remark-math';
|
|
19
27
|
import 'remark-rehype';
|
|
20
28
|
import 'rehype-katex';
|
|
21
29
|
import 'rehype-raw';
|
|
22
|
-
import 'rehype-stringify';
|
|
23
30
|
import 'unist-util-visit';
|
|
24
|
-
import '../shared/vitakit.DQ6PmGoL.mjs';
|
|
25
|
-
import 'node:url';
|
|
26
|
-
import '@behackl/citation-js-extras';
|
|
27
|
-
import 'rehype-parse';
|
|
28
|
-
import 'rehype-sanitize';
|
|
29
31
|
import 'zod';
|
|
32
|
+
import './publication-lists.mjs';
|
|
30
33
|
import './schemas.mjs';
|
|
31
|
-
import '../shared/vitakit.
|
|
34
|
+
import '../shared/vitakit.DvABWuUc.mjs';
|
|
32
35
|
import '../shared/vitakit.TSMTC5Jl.mjs';
|
|
33
36
|
import '../shared/vitakit.D-DdJaDZ.mjs';
|
|
34
37
|
import 'eta';
|
|
@@ -71,7 +74,7 @@ Full log: ${log}` : ""}`));
|
|
|
71
74
|
});
|
|
72
75
|
});
|
|
73
76
|
}
|
|
74
|
-
function profilePreamble(content, document) {
|
|
77
|
+
function profilePreamble(content, document, lists) {
|
|
75
78
|
const { profile } = content;
|
|
76
79
|
const href = (url) => {
|
|
77
80
|
if (isSitePath(url)) fail("content/profile.yml", `links: ${url} ${SITE_PATH_HELP}`);
|
|
@@ -96,6 +99,7 @@ function profilePreamble(content, document) {
|
|
|
96
99
|
...title ? [`\\vitasectiontitle{${id}}{${escapeTex(title)}}`] : []
|
|
97
100
|
];
|
|
98
101
|
}),
|
|
102
|
+
...lists.filter((name) => !content.publications[name]?.count).map((name) => `\\vitaemptypublications{${name}}`),
|
|
99
103
|
""
|
|
100
104
|
].join("\n");
|
|
101
105
|
}
|
|
@@ -112,15 +116,16 @@ function siteUrlSourcemap(siteUrl) {
|
|
|
112
116
|
}
|
|
113
117
|
async function buildDocument(document, content, options, cvDir) {
|
|
114
118
|
const outDir = join(cvDir, "generated", document.name);
|
|
119
|
+
const lists = [...new Set(document.publications.map((placement) => placement.list))];
|
|
115
120
|
const written = await emitDocument({
|
|
116
121
|
sections: content.sections,
|
|
117
|
-
publications: content.publications,
|
|
122
|
+
publications: Object.fromEntries(lists.map((name) => [name, content.publications[name]])),
|
|
118
123
|
siteUrl: content.profile.url,
|
|
119
124
|
options: document.options,
|
|
120
125
|
templateDir: join(cvDir, "templates"),
|
|
121
126
|
outDir
|
|
122
127
|
});
|
|
123
|
-
await writeFile(join(outDir, "profile.tex"), profilePreamble(content, document));
|
|
128
|
+
await writeFile(join(outDir, "profile.tex"), profilePreamble(content, document, lists));
|
|
124
129
|
const generatedClass = join(outDir, CLASS_FILE);
|
|
125
130
|
if (existsSync(join(cvDir, CLASS_FILE))) await rm(generatedClass, { force: true });
|
|
126
131
|
else await copyFile(join(bundledTexDir, CLASS_FILE), generatedClass);
|
|
@@ -149,13 +154,10 @@ async function buildCv(options) {
|
|
|
149
154
|
const content = await loadContent(options.rootDir, { strict: true, maxVisibility: "private", target: "pdf" });
|
|
150
155
|
const documents = await discoverDocuments(cvDir);
|
|
151
156
|
const sources = await discoverSections(options.rootDir);
|
|
152
|
-
const groups = (await loadPublicationsSchema(options.rootDir)).groups.map((group) => group.id);
|
|
153
157
|
for (const document of documents) {
|
|
154
158
|
validateDocumentSections(document, sources);
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
fail(`cv-tex/${document.name}.yml`, `publications.groups: unknown group ${unknown2.join(", ")}; the groups are ${groups.join(", ")} (schemas/publications.ts)`);
|
|
158
|
-
}
|
|
159
|
+
const [problem] = publicationPlacementProblems([], [...document.placements, ...document.publications], Object.keys(content.publications));
|
|
160
|
+
if (problem) fail(problem.where, problem.message);
|
|
159
161
|
}
|
|
160
162
|
const unknown = options.only?.filter((name) => !documents.some((document) => document.name === name)) ?? [];
|
|
161
163
|
if (unknown.length) {
|