@anja.codes/vitakit 0.1.0 → 0.2.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 +51 -131
- package/dist/check.mjs +12 -5
- package/dist/chunks/authoring.mjs +367 -0
- package/dist/chunks/build.mjs +176 -0
- package/dist/chunks/documents.mjs +143 -0
- package/dist/chunks/draft.mjs +222 -0
- package/dist/chunks/eject.mjs +59 -0
- package/dist/chunks/emit.mjs +200 -0
- package/dist/chunks/eta.mjs +16 -0
- package/dist/chunks/fields.mjs +79 -0
- package/dist/chunks/inventory.mjs +199 -0
- package/dist/chunks/plan.mjs +144 -0
- package/dist/chunks/prompt.mjs +33 -0
- package/dist/chunks/schema-source.mjs +12 -0
- package/dist/chunks/schemas.mjs +109 -0
- package/dist/chunks/sections.mjs +109 -0
- package/dist/chunks/snippets.mjs +199 -0
- package/dist/chunks/validate.mjs +124 -0
- package/dist/chunks/vue.mjs +116 -0
- package/dist/cli.d.mts +33 -40
- package/dist/cli.mjs +7 -224
- package/dist/index.d.mts +372 -42
- package/dist/index.mjs +10 -4
- package/dist/nuxt.mjs +35 -5
- package/dist/runtime/components/VitaSection.vue +94 -38
- package/dist/schemas/education.ts +4 -4
- package/dist/schemas/employment.ts +3 -3
- package/dist/schemas/publications.ts +2 -2
- package/dist/schemas/supervision.ts +3 -3
- package/dist/schemas/talks.ts +1 -1
- package/dist/schemas/teaching.ts +3 -3
- package/dist/shared/index.d.mts +60 -9
- package/dist/shared/index.mjs +3 -57
- package/dist/shared/vitakit.BZZIsWlY.mjs +179 -0
- package/dist/shared/vitakit.BerUaxtF.mjs +121 -0
- package/dist/shared/{vitakit.D8WRg_Xx.d.mts → vitakit.BfpxDtz1.d.mts} +9 -8
- package/dist/shared/{vitakit.CypCF-4Q.mjs → vitakit.By5VWQ1n.mjs} +12 -78
- package/dist/shared/vitakit.CfB3hMHl.mjs +60 -0
- package/dist/shared/vitakit.CoWX2Z4R.mjs +1554 -0
- package/dist/shared/{vitakit.BlU-p01e.mjs → vitakit.CtChKO0I.mjs} +1 -1
- package/dist/shared/vitakit.D-DdJaDZ.mjs +20 -0
- package/dist/shared/vitakit.D10HyDhF.mjs +19 -0
- package/dist/shared/vitakit.DQ6PmGoL.mjs +34 -0
- package/dist/shared/vitakit.DbSeENVS.mjs +58 -0
- package/dist/shared/vitakit.DcQOdM4I.mjs +57 -0
- package/dist/shared/vitakit.Do5HC_MD.mjs +142 -0
- package/dist/shared/vitakit.TSMTC5Jl.mjs +6 -0
- package/dist/tex/templates/_generic.eta +6 -4
- package/dist/tex/templates/education.eta +10 -5
- package/dist/tex/templates/employment.eta +12 -5
- package/dist/tex/templates/publications.eta +4 -2
- package/dist/tex/templates/supervision.eta +10 -2
- package/dist/tex/templates/talks.eta +5 -1
- package/dist/tex/templates/teaching.eta +6 -4
- package/dist/tex/vitakit-cv.cls +21 -6
- package/dist/tex.d.mts +53 -5
- package/dist/tex.mjs +20 -113
- package/package.json +10 -7
- package/src/bin/vitakit.mjs +3 -153
- package/dist/shared/vitakit.BRhjqpAt.mjs +0 -250
- package/dist/shared/vitakit.BxcnlFGP.mjs +0 -325
- package/src/bin/vitakit-build-cv.mjs +0 -50
- package/src/bin/vitakit-check.mjs +0 -57
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* A generic section with conventional field selection and optional overrides.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* title/meta/note slots replace those regions; the default slot replaces the
|
|
6
|
+
* whole entry body. The list, row and date column remain owned by vitakit.
|
|
7
|
+
*
|
|
8
|
+
* With `title`, the list comes with its heading, in a <section> carrying the
|
|
9
|
+
* section id as anchor, and neither appears when there are no entries -- the
|
|
10
|
+
* website's `\vitasection[Title]{id}`.
|
|
9
11
|
*/
|
|
10
|
-
import { computed } from 'vue'
|
|
12
|
+
import { computed, type VNodeChild } from 'vue'
|
|
11
13
|
import { useSection } from '#imports'
|
|
12
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
entrySecondaryParts,
|
|
16
|
+
entryTitleField,
|
|
17
|
+
filterWhere,
|
|
18
|
+
richHtml,
|
|
19
|
+
type DateRange,
|
|
20
|
+
type Entry,
|
|
21
|
+
type VitaSectionSlotProps,
|
|
22
|
+
type Where,
|
|
23
|
+
} from '@anja.codes/vitakit/shared'
|
|
13
24
|
|
|
14
25
|
const props = defineProps<{
|
|
15
26
|
/** Section id; ignored when `entries` is given. */
|
|
@@ -17,44 +28,89 @@ const props = defineProps<{
|
|
|
17
28
|
entries?: Entry[]
|
|
18
29
|
/** Fields to try, in order, for the main line. */
|
|
19
30
|
titleFields?: string[]
|
|
31
|
+
/** Only entries whose fields have these values, as `where` in a PDF document. */
|
|
32
|
+
where?: Where
|
|
20
33
|
limit?: number
|
|
34
|
+
/** A heading; the list is then wrapped in a <section> with the id as anchor. */
|
|
35
|
+
title?: string
|
|
21
36
|
}>()
|
|
22
37
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// `@anja.codes/vitakit/shared`, the same code the LaTeX emitter uses; only the
|
|
30
|
-
// separator and the markup are decided here.
|
|
31
|
-
const titleFieldOf = (entry: Entry): string | undefined =>
|
|
32
|
-
entryTitleField(entry, props.titleFields)
|
|
33
|
-
|
|
34
|
-
const titleOf = (entry: Entry): string => {
|
|
35
|
-
const field = titleFieldOf(entry)
|
|
36
|
-
return (field && richHtml(entry[field])) || ''
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const secondaryOf = (entry: Entry): string =>
|
|
40
|
-
entrySecondaryParts(entry, titleFieldOf(entry))
|
|
41
|
-
.map(part => (part.kind === 'institution' ? part.institution.name : part.text))
|
|
42
|
-
.join(' · ')
|
|
38
|
+
const slots = defineSlots<{
|
|
39
|
+
title?(props: VitaSectionSlotProps): VNodeChild
|
|
40
|
+
meta?(props: VitaSectionSlotProps): VNodeChild
|
|
41
|
+
note?(props: VitaSectionSlotProps): VNodeChild
|
|
42
|
+
default?(props: VitaSectionSlotProps): VNodeChild
|
|
43
|
+
}>()
|
|
43
44
|
|
|
44
|
-
const
|
|
45
|
-
|
|
45
|
+
const rows = computed(() => {
|
|
46
|
+
const source = filterWhere(props.entries ?? (props.id ? useSection(props.id) : []), props.where)
|
|
47
|
+
const items = props.limit ? source.slice(0, props.limit) : source
|
|
48
|
+
return items.map((entry, index): VitaSectionSlotProps => {
|
|
49
|
+
// Field selection remains shared with the emitter; only the web-specific
|
|
50
|
+
// presentation (such as a term replacing the date) is decided here.
|
|
51
|
+
const titleField = entryTitleField(entry, props.titleFields)
|
|
52
|
+
const term = typeof entry.term === 'string' && entry.term.length > 0 ? entry.term : undefined
|
|
53
|
+
return {
|
|
54
|
+
entry,
|
|
55
|
+
index,
|
|
56
|
+
display: {
|
|
57
|
+
titleHtml: (titleField && richHtml(entry[titleField])) || '',
|
|
58
|
+
titleUrl: typeof entry.url === 'string' ? entry.url : undefined,
|
|
59
|
+
date: entry.date as DateRange | undefined,
|
|
60
|
+
term,
|
|
61
|
+
meta: entrySecondaryParts(term ? { ...entry, term: undefined } : entry, titleField),
|
|
62
|
+
note: typeof entry.note === 'string' ? entry.note : undefined,
|
|
63
|
+
links: typeof entry.slides === 'string' && entry.slides ? [{ label: 'Slides', url: entry.slides }] : [],
|
|
64
|
+
ongoing: entry.ongoing === true,
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
})
|
|
46
69
|
</script>
|
|
47
70
|
|
|
48
71
|
<template>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
<!-- With a title: the same component without one, inside its section. -->
|
|
73
|
+
<section v-if="title !== undefined && rows.length" :id="id" class="vita-section-group">
|
|
74
|
+
<h2 class="vita-section-group__title">
|
|
75
|
+
{{ title }}
|
|
76
|
+
</h2>
|
|
77
|
+
<VitaSection :entries="rows.map(row => row.entry)" :title-fields="titleFields">
|
|
78
|
+
<template v-for="(_, name) in slots" #[name]="scope">
|
|
79
|
+
<slot :name="name" v-bind="scope" />
|
|
80
|
+
</template>
|
|
81
|
+
</VitaSection>
|
|
82
|
+
</section>
|
|
83
|
+
<ul v-else-if="title === undefined && rows.length" class="vita-section">
|
|
84
|
+
<VitaEntry v-for="row in rows" :key="row.entry.id" :date="row.display.date">
|
|
85
|
+
<template v-if="row.display.term" #date>{{ row.display.term }}</template>
|
|
86
|
+
<!-- Check presence explicitly: an intentionally empty slot must not
|
|
87
|
+
bring back Vue's slot fallback (or an empty default wrapper). -->
|
|
88
|
+
<slot v-if="slots.default" v-bind="row" />
|
|
89
|
+
<template v-else>
|
|
90
|
+
<slot v-if="slots.title" name="title" v-bind="row" />
|
|
91
|
+
<p v-else class="vita-entry__title">
|
|
92
|
+
<!-- eslint-disable vue/no-v-html -- build-time rendered Markdown -->
|
|
93
|
+
<a v-if="row.display.titleUrl" :href="row.display.titleUrl" v-html="row.display.titleHtml" />
|
|
94
|
+
<span v-else v-html="row.display.titleHtml" />
|
|
95
|
+
<span v-if="row.display.ongoing" class="vita-entry__status"> (in progress)</span>
|
|
96
|
+
</p>
|
|
97
|
+
<slot v-if="slots.meta" name="meta" v-bind="row" />
|
|
98
|
+
<p v-else-if="row.display.meta.length" class="vita-entry__meta">
|
|
99
|
+
<template v-for="(part, index) in row.display.meta" :key="index">
|
|
100
|
+
<template v-if="index"> · </template>
|
|
101
|
+
<template v-if="part.kind === 'linked'">
|
|
102
|
+
<a v-if="part.linked.url" :href="part.linked.url">{{ part.linked.label }}</a>
|
|
103
|
+
<template v-else>{{ part.linked.label }}</template>
|
|
104
|
+
</template>
|
|
105
|
+
<template v-else>{{ part.text }}</template>
|
|
106
|
+
</template>
|
|
107
|
+
</p>
|
|
108
|
+
<slot v-if="slots.note" name="note" v-bind="row" />
|
|
109
|
+
<p v-else-if="row.display.note !== undefined" class="vita-entry__note">{{ row.display.note }}</p>
|
|
110
|
+
<p v-if="row.display.links.length" class="vita-entry__links">
|
|
111
|
+
<a v-for="link in row.display.links" :key="link.url" :href="link.url">{{ link.label }}</a>
|
|
112
|
+
</p>
|
|
113
|
+
</template>
|
|
58
114
|
</VitaEntry>
|
|
59
115
|
</ul>
|
|
60
116
|
</template>
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shape of `content/education.yml`.
|
|
3
3
|
*
|
|
4
|
-
* Also the file `vitakit
|
|
4
|
+
* Also the file `vitakit customize education --schema --copy` writes into a project, with the import
|
|
5
5
|
* rewritten to `@anja.codes/vitakit`; it is therefore the single definition of
|
|
6
6
|
* this section, not a copy of one.
|
|
7
7
|
*/
|
|
8
|
-
import { dateRange,
|
|
8
|
+
import { dateRange, linked, richText, section, text, url, z } from '../primitives'
|
|
9
9
|
|
|
10
10
|
export default section({
|
|
11
11
|
date: dateRange,
|
|
12
12
|
degree: text,
|
|
13
|
-
institution,
|
|
13
|
+
institution: linked,
|
|
14
14
|
thesis: z.strictObject({
|
|
15
15
|
title: richText,
|
|
16
16
|
type: text.optional(),
|
|
17
|
-
supervisor:
|
|
17
|
+
supervisor: linked.optional(),
|
|
18
18
|
url: url.optional(),
|
|
19
19
|
}).optional(),
|
|
20
20
|
note: text.optional(),
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shape of `content/employment.yml`.
|
|
3
3
|
*
|
|
4
|
-
* Also the file `vitakit
|
|
4
|
+
* Also the file `vitakit customize employment --schema --copy` writes into a project, with the import
|
|
5
5
|
* rewritten to `@anja.codes/vitakit`; it is therefore the single definition of
|
|
6
6
|
* this section, not a copy of one.
|
|
7
7
|
*/
|
|
8
|
-
import { dateRange,
|
|
8
|
+
import { dateRange, linked, richText, section, text, url, z } from '../primitives'
|
|
9
9
|
|
|
10
10
|
export default section({
|
|
11
11
|
date: dateRange,
|
|
12
12
|
position: text,
|
|
13
|
-
institution,
|
|
13
|
+
institution: linked,
|
|
14
14
|
project: z.strictObject({
|
|
15
15
|
title: richText,
|
|
16
16
|
funder: text.optional(),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* How `content/publications.bib` is grouped and linked -- on the website, in
|
|
3
|
-
* `vitakit
|
|
3
|
+
* `vitakit verify` and in the PDF alike.
|
|
4
4
|
*
|
|
5
|
-
* Also the file `vitakit
|
|
5
|
+
* Also the file `vitakit customize publications` writes into a project, with the
|
|
6
6
|
* import rewritten to `@anja.codes/vitakit`; it is therefore the single definition
|
|
7
7
|
* of the defaults, not a copy of them. Every setting is optional: leave out
|
|
8
8
|
* what you do not change.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shape of `content/supervision.yml`.
|
|
3
3
|
*
|
|
4
|
-
* Also the file `vitakit
|
|
4
|
+
* Also the file `vitakit customize supervision --schema --copy` writes into a project, with the import
|
|
5
5
|
* rewritten to `@anja.codes/vitakit`; it is therefore the single definition of
|
|
6
6
|
* this section, not a copy of one.
|
|
7
7
|
*/
|
|
8
|
-
import { dateRange,
|
|
8
|
+
import { dateRange, linked, richText, section, text, z } from '../primitives'
|
|
9
9
|
|
|
10
10
|
export default section({
|
|
11
11
|
date: dateRange,
|
|
@@ -15,6 +15,6 @@ export default section({
|
|
|
15
15
|
/** Markdown, math allowed. */
|
|
16
16
|
title: richText,
|
|
17
17
|
degree: text.optional(),
|
|
18
|
-
institution:
|
|
18
|
+
institution: linked.optional(),
|
|
19
19
|
coSupervisor: text.optional(),
|
|
20
20
|
})
|
package/dist/schemas/talks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shape of `content/talks.yml`.
|
|
3
3
|
*
|
|
4
|
-
* Also the file `vitakit
|
|
4
|
+
* Also the file `vitakit customize talks --schema --copy` writes into a project, with the import
|
|
5
5
|
* rewritten to `@anja.codes/vitakit`; it is therefore the single definition of
|
|
6
6
|
* this section, not a copy of one.
|
|
7
7
|
*/
|
package/dist/schemas/teaching.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shape of `content/teaching.yml`.
|
|
3
3
|
*
|
|
4
|
-
* Also the file `vitakit
|
|
4
|
+
* Also the file `vitakit customize teaching --schema --copy` writes into a project, with the import
|
|
5
5
|
* rewritten to `@anja.codes/vitakit`; it is therefore the single definition of
|
|
6
6
|
* this section, not a copy of one.
|
|
7
7
|
*/
|
|
8
|
-
import { dateRange,
|
|
8
|
+
import { dateRange, linked, richText, section, text, url } from '../primitives'
|
|
9
9
|
|
|
10
10
|
export default section({
|
|
11
11
|
date: dateRange,
|
|
12
12
|
title: richText,
|
|
13
13
|
role: text.optional(),
|
|
14
|
-
institution:
|
|
14
|
+
institution: linked.optional(),
|
|
15
15
|
/** e.g. `2024S`, `Winter 2023/24` -- free text, shown as written. */
|
|
16
16
|
term: text.optional(),
|
|
17
17
|
url: url.optional(),
|
package/dist/shared/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DateRange,
|
|
2
|
-
export { C as ContentIssue, b as DatePoint, c as EntryBase,
|
|
1
|
+
import { D as DateRange, d as Linked, L as Link, E as Entry } from './vitakit.BfpxDtz1.mjs';
|
|
2
|
+
export { C as ContentIssue, b as DatePoint, c as EntryBase, P as Page, e as Profile, f as Publication, g as PublicationBadge, h as PublicationGroup, i as RichText, R as RichTextMarker, V as Visibility, a as VitakitContent } from './vitakit.BfpxDtz1.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Presentation helpers shared by the website and the LaTeX side.
|
|
@@ -37,32 +37,83 @@ declare function dateSortKey(range: DateRange | undefined): string;
|
|
|
37
37
|
declare const TITLE_FIELDS: readonly string[];
|
|
38
38
|
/** Plain-text fields tried, in order, for the context line. */
|
|
39
39
|
declare const SECONDARY_FIELDS: readonly string[];
|
|
40
|
-
/**
|
|
41
|
-
declare const
|
|
40
|
+
/** `linked` fields tried, in order, for the place on the context line. */
|
|
41
|
+
declare const LINKED_FIELDS: readonly string[];
|
|
42
42
|
/**
|
|
43
43
|
* The field holding the entry's headline, or `undefined` when nothing fits.
|
|
44
44
|
* Returns the field *name* so callers can render its value themselves: the
|
|
45
45
|
* website needs the rendered HTML, the emitter the LaTeX source.
|
|
46
46
|
*/
|
|
47
47
|
declare function entryTitleField(entry: Entry, fields?: readonly string[]): string | undefined;
|
|
48
|
-
/** A piece of the context line: plain text, or
|
|
48
|
+
/** A piece of the context line: plain text, or a `linked` value that may link. */
|
|
49
49
|
type SecondaryPart = {
|
|
50
50
|
kind: 'text';
|
|
51
51
|
text: string;
|
|
52
52
|
} | {
|
|
53
|
-
kind: '
|
|
54
|
-
|
|
53
|
+
kind: 'linked';
|
|
54
|
+
linked: Linked;
|
|
55
55
|
};
|
|
56
|
+
/** Values used by VitaSection's default web rendering, also available to slots. */
|
|
57
|
+
interface VitaSectionDisplay {
|
|
58
|
+
/** Build-time rendered HTML, respecting the component's titleFields. */
|
|
59
|
+
titleHtml: string;
|
|
60
|
+
titleUrl?: string;
|
|
61
|
+
date?: DateRange;
|
|
62
|
+
/** A teaching term replaces the normal date label. */
|
|
63
|
+
term?: string;
|
|
64
|
+
/** Structured context, preserving links. */
|
|
65
|
+
meta: SecondaryPart[];
|
|
66
|
+
note?: string;
|
|
67
|
+
/** Additional links; does not include the title's link. */
|
|
68
|
+
links: Link[];
|
|
69
|
+
ongoing: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Shared context for VitaSection's title, meta, note and default (body) slots. */
|
|
72
|
+
interface VitaSectionSlotProps {
|
|
73
|
+
entry: Entry;
|
|
74
|
+
/** Zero-based position in the displayed list, after limiting. */
|
|
75
|
+
index: number;
|
|
76
|
+
display: VitaSectionDisplay;
|
|
77
|
+
}
|
|
56
78
|
/**
|
|
57
79
|
* The context line, as structured parts. The field used for the title is
|
|
58
80
|
* skipped, so nothing appears twice.
|
|
59
81
|
*/
|
|
60
82
|
declare function entrySecondaryParts(entry: Entry, titleField?: string): SecondaryPart[];
|
|
83
|
+
/**
|
|
84
|
+
* The `linked` values of a field, as a list: none, one, or several. For
|
|
85
|
+
* templates that show a field which may hold one link or a list of them.
|
|
86
|
+
*/
|
|
87
|
+
declare function asLinkedList(value: unknown): Linked[];
|
|
61
88
|
|
|
62
89
|
/** The plain-text form: Markdown source, for LaTeX, comparisons and `title=`. */
|
|
63
90
|
declare function richSource(value: unknown): string;
|
|
64
91
|
/** The rendered form, or `undefined` when there is nothing to render. */
|
|
65
92
|
declare function richHtml(value: unknown): string | undefined;
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Selecting entries by field: the `where` of a PDF document's `.yml` and of
|
|
96
|
+
* `<VitaSection where>`. One implementation, so that the website and the PDF
|
|
97
|
+
* cannot disagree about which entries match.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately small: equality with one value, or with any of several. Values
|
|
100
|
+
* are compared as text, so `where: { year: 2024 }` and `{ year: '2024' }` mean
|
|
101
|
+
* the same.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/** A value to compare with, or several of which any may match. */
|
|
105
|
+
type WhereValue = string | number | boolean | readonly (string | number | boolean)[];
|
|
106
|
+
/** Field name -> wanted value(s). Every field must match. */
|
|
107
|
+
type Where = Readonly<Record<string, WhereValue>>;
|
|
108
|
+
/**
|
|
109
|
+
* The comparable value of a field: `year` from the date, the source text of a
|
|
110
|
+
* formatted-text field, the label of a link.
|
|
111
|
+
*/
|
|
112
|
+
declare function fieldValue(entry: Entry, field: string): unknown;
|
|
113
|
+
/** Whether an entry matches every field of `where`. */
|
|
114
|
+
declare function matchesWhere(entry: Entry, where: Where): boolean;
|
|
115
|
+
/** The entries matching `where`; all of them when it is empty or absent. */
|
|
116
|
+
declare function filterWhere<T extends Entry>(entries: readonly T[], where: Where | undefined): T[];
|
|
117
|
+
|
|
118
|
+
export { DateRange, Entry, LINKED_FIELDS, Link, Linked, SECONDARY_FIELDS, TITLE_FIELDS, asLinkedList, dateSortKey, entrySecondaryParts, entryTitleField, fieldValue, filterWhere, formatDateRange, matchesWhere, richHtml, richSource };
|
|
119
|
+
export type { DateFormatOptions, SecondaryPart, VitaSectionDisplay, VitaSectionSlotProps, Where, WhereValue };
|
package/dist/shared/index.mjs
CHANGED
|
@@ -1,57 +1,3 @@
|
|
|
1
|
-
export { a as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
if (precision === "year" || point.month === void 0) return String(point.year);
|
|
5
|
-
const date = new Date(Date.UTC(point.year, point.month - 1, point.day ?? 1));
|
|
6
|
-
return new Intl.DateTimeFormat(locale, {
|
|
7
|
-
year: "numeric",
|
|
8
|
-
month: "short",
|
|
9
|
-
...point.day === void 0 ? {} : { day: "numeric" },
|
|
10
|
-
timeZone: "UTC"
|
|
11
|
-
}).format(date);
|
|
12
|
-
}
|
|
13
|
-
function formatDateRange(range, options = {}) {
|
|
14
|
-
if (!range) return "";
|
|
15
|
-
const { presentLabel = "present" } = options;
|
|
16
|
-
const from = formatPoint(range.from, options);
|
|
17
|
-
if (range.point) return from;
|
|
18
|
-
if (range.ongoing) return `${from}\u2013${presentLabel}`;
|
|
19
|
-
if (!range.to) return from;
|
|
20
|
-
const to = formatPoint(range.to, options);
|
|
21
|
-
return from === to ? from : `${from}\u2013${to}`;
|
|
22
|
-
}
|
|
23
|
-
function dateSortKey(range) {
|
|
24
|
-
return range?.from.sortKey ?? "";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const TITLE_FIELDS = ["title", "position", "degree", "role", "student"];
|
|
28
|
-
const SECONDARY_FIELDS = ["student", "role", "event", "term", "degree"];
|
|
29
|
-
const INSTITUTION_FIELDS = ["institution", "issuer", "organisation"];
|
|
30
|
-
function entryTitleField(entry, fields = TITLE_FIELDS) {
|
|
31
|
-
return fields.find((field) => {
|
|
32
|
-
const value = entry[field];
|
|
33
|
-
if (typeof value === "string") return value.length > 0;
|
|
34
|
-
return typeof value === "object" && value !== null && "html" in value;
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
function entrySecondaryParts(entry, titleField) {
|
|
38
|
-
const parts = [];
|
|
39
|
-
for (const field of SECONDARY_FIELDS) {
|
|
40
|
-
if (field === titleField) continue;
|
|
41
|
-
const value = entry[field];
|
|
42
|
-
if (typeof value === "string" && value.length > 0) parts.push({ kind: "text", text: value });
|
|
43
|
-
}
|
|
44
|
-
for (const field of INSTITUTION_FIELDS) {
|
|
45
|
-
const value = entry[field];
|
|
46
|
-
if (value?.name) {
|
|
47
|
-
parts.push({ kind: "institution", institution: value });
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (typeof entry.location === "string" && entry.location.length > 0) {
|
|
52
|
-
parts.push({ kind: "text", text: entry.location });
|
|
53
|
-
}
|
|
54
|
-
return parts;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export { INSTITUTION_FIELDS, SECONDARY_FIELDS, TITLE_FIELDS, dateSortKey, entrySecondaryParts, entryTitleField, formatDateRange };
|
|
1
|
+
export { L as LINKED_FIELDS, S as SECONDARY_FIELDS, T as TITLE_FIELDS, a as asLinkedList, d as dateSortKey, e as entrySecondaryParts, b as entryTitleField, f as formatDateRange } from './vitakit.CfB3hMHl.mjs';
|
|
2
|
+
export { r as richHtml, a as richSource } from './vitakit.CtChKO0I.mjs';
|
|
3
|
+
export { f as fieldValue, a as filterWhere, m as matchesWhere } from './vitakit.D-DdJaDZ.mjs';
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
|
|
3
|
+
const PACKAGE = "@anja.codes/vitakit";
|
|
4
|
+
function parseModule(source) {
|
|
5
|
+
return parse(source, { sourceType: "module", plugins: ["typescript"] }).program;
|
|
6
|
+
}
|
|
7
|
+
function unwrap(expression) {
|
|
8
|
+
let current = expression;
|
|
9
|
+
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "ParenthesizedExpression" || current.type === "TSNonNullExpression") {
|
|
10
|
+
current = current.expression;
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
}
|
|
14
|
+
function packageImports(body) {
|
|
15
|
+
const names = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const statement of body) {
|
|
17
|
+
if (statement.type !== "ImportDeclaration" || statement.source.value !== PACKAGE) continue;
|
|
18
|
+
for (const specifier of statement.specifiers) {
|
|
19
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
20
|
+
const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
21
|
+
names.set(specifier.local.name, imported);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return names;
|
|
25
|
+
}
|
|
26
|
+
function defaultExport(body) {
|
|
27
|
+
const exported = body.find((statement) => statement.type === "ExportDefaultDeclaration");
|
|
28
|
+
if (!exported || exported.type !== "ExportDefaultDeclaration") return void 0;
|
|
29
|
+
const declaration = exported.declaration;
|
|
30
|
+
if (declaration.type === "Identifier") {
|
|
31
|
+
for (const statement of body) {
|
|
32
|
+
if (statement.type !== "VariableDeclaration" || statement.kind !== "const") continue;
|
|
33
|
+
for (const declarator of statement.declarations) {
|
|
34
|
+
if (declarator.id.type === "Identifier" && declarator.id.name === declaration.name && declarator.init) {
|
|
35
|
+
return unwrap(declarator.init);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
if (declaration.type.endsWith("Declaration")) return void 0;
|
|
42
|
+
return unwrap(declaration);
|
|
43
|
+
}
|
|
44
|
+
function keyName(property) {
|
|
45
|
+
if (property.type !== "ObjectProperty" || property.computed) return void 0;
|
|
46
|
+
if (property.key.type === "Identifier") return property.key.name;
|
|
47
|
+
if (property.key.type === "StringLiteral") return property.key.value;
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
function recognizeSchema(source) {
|
|
51
|
+
let body;
|
|
52
|
+
try {
|
|
53
|
+
body = parseModule(source).body;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return { ok: false, reason: `cannot be parsed: ${error instanceof Error ? error.message : String(error)}` };
|
|
56
|
+
}
|
|
57
|
+
const expression = defaultExport(body);
|
|
58
|
+
if (!expression || expression.type !== "CallExpression" || expression.callee.type !== "Identifier") {
|
|
59
|
+
return { ok: false, reason: "its default export is not a section(...) or extendSection(...) call" };
|
|
60
|
+
}
|
|
61
|
+
const call = expression;
|
|
62
|
+
const kind = packageImports(body).get(call.callee.name);
|
|
63
|
+
const object = kind === "section" ? call.arguments[0] : kind === "extendSection" ? call.arguments[1] : void 0;
|
|
64
|
+
const expected = kind === "section" ? 1 : 2;
|
|
65
|
+
if (kind !== "section" && kind !== "extendSection" || call.arguments.length !== expected || !object || object.type !== "ObjectExpression") {
|
|
66
|
+
return { ok: false, reason: `its default export is not section({ ... }) or extendSection(base, { ... }) from ${PACKAGE}` };
|
|
67
|
+
}
|
|
68
|
+
const fields = [];
|
|
69
|
+
for (const property of object.properties) {
|
|
70
|
+
const name = keyName(property);
|
|
71
|
+
if (name === void 0) return { ok: false, reason: "its fields contain a spread or a computed key" };
|
|
72
|
+
fields.push(name);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
value: {
|
|
77
|
+
kind,
|
|
78
|
+
fields,
|
|
79
|
+
start: object.start,
|
|
80
|
+
end: object.end,
|
|
81
|
+
properties: object.properties.map((property) => ({ start: property.start, end: property.end }))
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
86
|
+
function propertyKey(name) {
|
|
87
|
+
return IDENTIFIER.test(name) ? name : `'${name.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
88
|
+
}
|
|
89
|
+
function lineStart(source, offset) {
|
|
90
|
+
return source.lastIndexOf("\n", offset - 1) + 1;
|
|
91
|
+
}
|
|
92
|
+
function indentationAt(source, offset) {
|
|
93
|
+
return /^[ \t]*/.exec(source.slice(lineStart(source, offset)))[0];
|
|
94
|
+
}
|
|
95
|
+
function endOfLineContent(source, offset) {
|
|
96
|
+
let position = offset;
|
|
97
|
+
for (; ; ) {
|
|
98
|
+
while (source[position] === " " || source[position] === " ") position++;
|
|
99
|
+
if (source.startsWith("//", position)) {
|
|
100
|
+
const end = source.indexOf("\n", position);
|
|
101
|
+
return end === -1 ? source.length : end;
|
|
102
|
+
}
|
|
103
|
+
if (source.startsWith("/*", position)) {
|
|
104
|
+
const end = source.indexOf("*/", position + 2);
|
|
105
|
+
if (end === -1 || source.slice(position, end).includes("\n")) return position;
|
|
106
|
+
position = end + 2;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
return position;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function addSchemaField(source, name, code) {
|
|
113
|
+
const recognized = recognizeSchema(source);
|
|
114
|
+
if (!recognized.ok) return recognized;
|
|
115
|
+
const { fields, properties, start: open, end } = recognized.value;
|
|
116
|
+
if (fields.includes(name)) return { ok: false, reason: `it already has a field "${name}"` };
|
|
117
|
+
const entry = `${propertyKey(name)}: ${code}`;
|
|
118
|
+
const close = end - 1;
|
|
119
|
+
if (!properties.length) {
|
|
120
|
+
const inner = source.slice(open + 1, close);
|
|
121
|
+
if (!inner.includes("\n")) {
|
|
122
|
+
const indent3 = indentationAt(source, open);
|
|
123
|
+
return { ok: true, value: `${source.slice(0, open)}{
|
|
124
|
+
${indent3} ${entry},
|
|
125
|
+
${indent3}}${source.slice(close + 1)}` };
|
|
126
|
+
}
|
|
127
|
+
const closeLine = lineStart(source, close);
|
|
128
|
+
const indent2 = `${indentationAt(source, close)} `;
|
|
129
|
+
if (source.slice(closeLine, close).trim() !== "") {
|
|
130
|
+
return { ok: true, value: `${source.slice(0, close)}
|
|
131
|
+
${indent2}${entry},
|
|
132
|
+
${source.slice(close)}` };
|
|
133
|
+
}
|
|
134
|
+
return { ok: true, value: `${source.slice(0, closeLine)}${indent2}${entry},
|
|
135
|
+
${source.slice(closeLine)}` };
|
|
136
|
+
}
|
|
137
|
+
const first = properties[0];
|
|
138
|
+
const last = properties[properties.length - 1];
|
|
139
|
+
const multiline = source.slice(open, close).includes("\n") && source.slice(lineStart(source, first.start), first.start).trim() === "";
|
|
140
|
+
let afterLast = last.end;
|
|
141
|
+
while (source[afterLast] === " " || source[afterLast] === " ") afterLast++;
|
|
142
|
+
const comma = source[afterLast] === ",";
|
|
143
|
+
if (!multiline) {
|
|
144
|
+
return { ok: true, value: `${source.slice(0, last.end)}, ${entry}${source.slice(last.end)}` };
|
|
145
|
+
}
|
|
146
|
+
const indent = indentationAt(source, first.start);
|
|
147
|
+
const insertAt = endOfLineContent(source, comma ? afterLast + 1 : last.end);
|
|
148
|
+
const head = comma ? source.slice(0, insertAt) : `${source.slice(0, last.end)},${source.slice(last.end, insertAt)}`;
|
|
149
|
+
return { ok: true, value: `${head}
|
|
150
|
+
${indent}${entry}${comma ? "," : ""}${source.slice(insertAt)}` };
|
|
151
|
+
}
|
|
152
|
+
function ensurePackageImports(source, names) {
|
|
153
|
+
let body;
|
|
154
|
+
try {
|
|
155
|
+
body = parseModule(source).body;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return { ok: false, reason: `cannot be parsed: ${error instanceof Error ? error.message : String(error)}` };
|
|
158
|
+
}
|
|
159
|
+
const imported = packageImports(body);
|
|
160
|
+
const local = new Set(imported.keys());
|
|
161
|
+
const missing = names.filter((name) => !local.has(name));
|
|
162
|
+
if (!missing.length) return { ok: true, value: source };
|
|
163
|
+
const declaration = body.find((statement) => statement.type === "ImportDeclaration" && statement.source.value === PACKAGE && statement.importKind !== "type" && statement.specifiers.some((specifier) => specifier.type === "ImportSpecifier"));
|
|
164
|
+
if (declaration) {
|
|
165
|
+
const specifiers = declaration.specifiers.filter((specifier) => specifier.type === "ImportSpecifier");
|
|
166
|
+
const at2 = specifiers[specifiers.length - 1].end;
|
|
167
|
+
return { ok: true, value: `${source.slice(0, at2)}, ${missing.join(", ")}${source.slice(at2)}` };
|
|
168
|
+
}
|
|
169
|
+
const imports = body.filter((statement) => statement.type === "ImportDeclaration");
|
|
170
|
+
const line = `import { ${missing.join(", ")} } from '${PACKAGE}'
|
|
171
|
+
`;
|
|
172
|
+
if (!imports.length) return { ok: true, value: line + source };
|
|
173
|
+
const at = imports[imports.length - 1].end;
|
|
174
|
+
const end = source.indexOf("\n", at);
|
|
175
|
+
const position = end === -1 ? source.length : end + 1;
|
|
176
|
+
return { ok: true, value: `${source.slice(0, position)}${end === -1 ? "\n" : ""}${line}${source.slice(position)}` };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { addSchemaField as a, ensurePackageImports as e, propertyKey as p };
|