@takuhon/cli 0.15.1 → 0.17.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/admin-bundle/assets/index-CdQIZMs4.js +9 -0
- package/admin-bundle/assets/style-D8_YjJMF.css +2 -0
- package/admin-bundle/index.html +2 -2
- package/dist/{chunk-DKOAVXLZ.js → chunk-2LA24OFM.js} +7 -5
- package/dist/{chunk-DKOAVXLZ.js.map → chunk-2LA24OFM.js.map} +1 -1
- package/dist/index.js +7 -579
- package/dist/index.js.map +1 -1
- package/dist/init.js +1 -1
- package/package.json +5 -5
- package/admin-bundle/assets/index-C3RPKgaS.js +0 -9
- package/admin-bundle/assets/style-B1PQ2qaY.css +0 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ADMIN_DIST_DIRNAME,
|
|
4
4
|
resolveAdminBundleDir
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2LA24OFM.js";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
import { readFileSync as readFileSync16, realpathSync } from "fs";
|
|
@@ -351,581 +351,8 @@ import { Hono } from "hono";
|
|
|
351
351
|
// src/dev-command.ts
|
|
352
352
|
import { readFileSync as readFileSync3 } from "fs";
|
|
353
353
|
import { createServer } from "http";
|
|
354
|
+
import { escapeHtml, generateSite } from "@takuhon/api";
|
|
354
355
|
import { applyPublicPrivacyFilter, normalize, validate } from "@takuhon/core";
|
|
355
|
-
|
|
356
|
-
// src/build-html.ts
|
|
357
|
-
import { generateJsonLd, renderActivitySvg } from "@takuhon/core";
|
|
358
|
-
|
|
359
|
-
// src/html-helpers.ts
|
|
360
|
-
function escapeHtml(value) {
|
|
361
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
362
|
-
}
|
|
363
|
-
function safeUrl(url) {
|
|
364
|
-
const trimmed = url.trim();
|
|
365
|
-
const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(trimmed)?.[1]?.toLowerCase();
|
|
366
|
-
if (scheme === void 0) return trimmed;
|
|
367
|
-
return scheme === "http" || scheme === "https" || scheme === "mailto" ? trimmed : void 0;
|
|
368
|
-
}
|
|
369
|
-
function dateRange(start, end, isCurrent) {
|
|
370
|
-
const left = start ?? "";
|
|
371
|
-
const right = isCurrent === true || end === null ? "Present" : end ?? "";
|
|
372
|
-
if (left && right) return `${left} \u2013 ${right}`;
|
|
373
|
-
return left || right;
|
|
374
|
-
}
|
|
375
|
-
function nonEmpty(values, separator) {
|
|
376
|
-
const joined = values.filter((v) => typeof v === "string" && v.length > 0).join(separator);
|
|
377
|
-
return joined.length > 0 ? joined : void 0;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// src/build-html.ts
|
|
381
|
-
function escapeJsonLd(json) {
|
|
382
|
-
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
383
|
-
}
|
|
384
|
-
var CSS = `:root{--fg:#1a1a1a;--muted:#666;--accent:#0b5fff;--line:#e5e5e5}
|
|
385
|
-
*{box-sizing:border-box}
|
|
386
|
-
body{margin:0;color:var(--fg);font:16px/1.6 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;background:#fff}
|
|
387
|
-
main{max-width:42rem;margin:0 auto;padding:2rem 1.25rem}
|
|
388
|
-
a{color:var(--accent)}
|
|
389
|
-
h1{font-size:1.9rem;margin:.2rem 0}
|
|
390
|
-
h2{font-size:1.15rem;margin:2rem 0 .75rem;padding-bottom:.3rem;border-bottom:1px solid var(--line)}
|
|
391
|
-
h3{font-size:1rem;margin:0}
|
|
392
|
-
header .avatar{width:96px;height:96px;border-radius:50%;object-fit:cover}
|
|
393
|
-
.tagline{font-size:1.1rem;color:var(--muted);margin:.2rem 0}
|
|
394
|
-
.location{color:var(--muted);margin:.2rem 0}
|
|
395
|
-
.bio{margin:.75rem 0}
|
|
396
|
-
ul{padding:0;margin:0;list-style:none}
|
|
397
|
-
.entries>li{margin:0 0 1.1rem}
|
|
398
|
-
.sub{margin:.1rem 0;font-weight:600}
|
|
399
|
-
.meta{margin:.1rem 0;color:var(--muted);font-size:.9rem}
|
|
400
|
-
.links{display:flex;flex-wrap:wrap;gap:.5rem 1rem;margin:.75rem 0}
|
|
401
|
-
.skills,.tags{display:flex;flex-wrap:wrap;gap:.4rem}
|
|
402
|
-
.skills>li,.tags>li{background:#f2f2f2;border-radius:1rem;padding:.15rem .6rem;font-size:.85rem}
|
|
403
|
-
.rec{margin:0 0 1.1rem}
|
|
404
|
-
.rec blockquote{margin:0;padding-left:.9rem;border-left:3px solid var(--line)}
|
|
405
|
-
.rec figcaption{color:var(--muted);font-size:.9rem;margin-top:.3rem}
|
|
406
|
-
nav.locales{display:flex;gap:.75rem;margin-bottom:1rem;font-size:.9rem}
|
|
407
|
-
.activity svg{max-width:100%;height:auto}
|
|
408
|
-
footer.powered{max-width:42rem;margin:0 auto;padding:1.5rem 1.25rem;color:var(--muted);font-size:.85rem}`;
|
|
409
|
-
function renderEntry(entry) {
|
|
410
|
-
const href = entry.url ? safeUrl(entry.url) : void 0;
|
|
411
|
-
const heading = href ? `<a href="${escapeHtml(href)}">${escapeHtml(entry.heading)}</a>` : escapeHtml(entry.heading);
|
|
412
|
-
const parts = [`<h3>${heading}</h3>`];
|
|
413
|
-
if (entry.sub) parts.push(`<p class="sub">${escapeHtml(entry.sub)}</p>`);
|
|
414
|
-
if (entry.dates) parts.push(`<p class="meta">${escapeHtml(entry.dates)}</p>`);
|
|
415
|
-
if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);
|
|
416
|
-
if (entry.tags && entry.tags.length > 0) {
|
|
417
|
-
parts.push(
|
|
418
|
-
`<ul class="tags">${entry.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>`
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
return `<li>${parts.join("")}</li>`;
|
|
422
|
-
}
|
|
423
|
-
function entryList(title, entries) {
|
|
424
|
-
if (entries.length === 0) return "";
|
|
425
|
-
return `<section><h2>${escapeHtml(title)}</h2><ul class="entries">${entries.map(renderEntry).join("")}</ul></section>`;
|
|
426
|
-
}
|
|
427
|
-
function renderHeader(p) {
|
|
428
|
-
const parts = [];
|
|
429
|
-
const avatarSrc = p.avatar?.url ? safeUrl(p.avatar.url) : void 0;
|
|
430
|
-
if (avatarSrc) {
|
|
431
|
-
parts.push(
|
|
432
|
-
`<img class="avatar" src="${escapeHtml(avatarSrc)}" alt="${escapeHtml(p.avatar?.alt ?? "")}">`
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
parts.push(`<h1>${escapeHtml(p.displayName)}</h1>`);
|
|
436
|
-
if (p.tagline) parts.push(`<p class="tagline">${escapeHtml(p.tagline)}</p>`);
|
|
437
|
-
if (p.location?.display) parts.push(`<p class="location">${escapeHtml(p.location.display)}</p>`);
|
|
438
|
-
if (p.bio) parts.push(`<p class="bio">${escapeHtml(p.bio)}</p>`);
|
|
439
|
-
return `<header>${parts.join("")}</header>`;
|
|
440
|
-
}
|
|
441
|
-
function renderLinks(links) {
|
|
442
|
-
if (links.length === 0) return "";
|
|
443
|
-
const items = links.map((l) => {
|
|
444
|
-
const text = escapeHtml(l.label ?? l.url);
|
|
445
|
-
const href = safeUrl(l.url);
|
|
446
|
-
return href ? `<li><a href="${escapeHtml(href)}">${text}</a></li>` : `<li>${text}</li>`;
|
|
447
|
-
}).join("");
|
|
448
|
-
return `<nav aria-label="Links"><ul class="links">${items}</ul></nav>`;
|
|
449
|
-
}
|
|
450
|
-
function renderSkills(skills) {
|
|
451
|
-
if (skills.length === 0) return "";
|
|
452
|
-
const items = skills.map((s) => `<li>${escapeHtml(s.label)}</li>`).join("");
|
|
453
|
-
return `<section><h2>Skills</h2><ul class="skills">${items}</ul></section>`;
|
|
454
|
-
}
|
|
455
|
-
function renderLanguages(languages) {
|
|
456
|
-
if (languages.length === 0) return "";
|
|
457
|
-
const items = languages.map((l) => `<li>${escapeHtml(`${l.displayName ?? l.language} \u2014 ${l.proficiency}`)}</li>`).join("");
|
|
458
|
-
return `<section><h2>Languages</h2><ul class="entries">${items}</ul></section>`;
|
|
459
|
-
}
|
|
460
|
-
function renderRecommendations(recs) {
|
|
461
|
-
if (recs.length === 0) return "";
|
|
462
|
-
const items = recs.map((r) => {
|
|
463
|
-
const authorHref = r.author.url ? safeUrl(r.author.url) : void 0;
|
|
464
|
-
const name = authorHref ? `<a href="${escapeHtml(authorHref)}">${escapeHtml(r.author.name)}</a>` : escapeHtml(r.author.name);
|
|
465
|
-
const caption = [name, r.author.headline ? escapeHtml(r.author.headline) : ""].filter(Boolean).join(", ");
|
|
466
|
-
const rel = r.relationship ? ` (${escapeHtml(r.relationship)})` : "";
|
|
467
|
-
return `<figure class="rec"><blockquote>${escapeHtml(r.body)}</blockquote><figcaption>\u2014 ${caption}${rel}</figcaption></figure>`;
|
|
468
|
-
}).join("");
|
|
469
|
-
return `<section><h2>Recommendations</h2>${items}</section>`;
|
|
470
|
-
}
|
|
471
|
-
function renderContact(contact) {
|
|
472
|
-
const items = [];
|
|
473
|
-
if (contact.email) {
|
|
474
|
-
items.push(
|
|
475
|
-
`<li><a href="mailto:${escapeHtml(contact.email)}">${escapeHtml(contact.email)}</a></li>`
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
const formHref = contact.formUrl ? safeUrl(contact.formUrl) : void 0;
|
|
479
|
-
if (formHref) {
|
|
480
|
-
items.push(`<li><a href="${escapeHtml(formHref)}">Contact form</a></li>`);
|
|
481
|
-
}
|
|
482
|
-
if (items.length === 0) return "";
|
|
483
|
-
return `<section><h2>Contact</h2><ul class="entries">${items.join("")}</ul></section>`;
|
|
484
|
-
}
|
|
485
|
-
function renderActivity(snapshot) {
|
|
486
|
-
if (!snapshot) return "";
|
|
487
|
-
const svg = renderActivitySvg(snapshot);
|
|
488
|
-
if (svg === "") return "";
|
|
489
|
-
return `<section class="activity"><h2>Activity</h2>${svg}</section>`;
|
|
490
|
-
}
|
|
491
|
-
function renderJsonLdScript(data) {
|
|
492
|
-
const payload = JSON.stringify(generateJsonLd(data));
|
|
493
|
-
return `<script type="application/ld+json">${escapeJsonLd(payload)}</script>`;
|
|
494
|
-
}
|
|
495
|
-
function renderLocaleNav(localeNav) {
|
|
496
|
-
const items = localeNav.map(
|
|
497
|
-
(l) => l.current ? `<span aria-current="true">${escapeHtml(l.locale)}</span>` : `<a href="${escapeHtml(l.href)}">${escapeHtml(l.locale)}</a>`
|
|
498
|
-
).join("");
|
|
499
|
-
return `<nav class="locales" aria-label="Language">${items}</nav>`;
|
|
500
|
-
}
|
|
501
|
-
function renderProfileHtml(input) {
|
|
502
|
-
const d = input.localized;
|
|
503
|
-
const p = d.profile;
|
|
504
|
-
const description = p.tagline ?? p.bio ?? "";
|
|
505
|
-
const head = [
|
|
506
|
-
'<meta charset="utf-8">',
|
|
507
|
-
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
508
|
-
`<title>${escapeHtml(p.displayName)}</title>`,
|
|
509
|
-
description ? `<meta name="description" content="${escapeHtml(description.slice(0, 300))}">` : "",
|
|
510
|
-
input.canonicalUrl ? `<link rel="canonical" href="${escapeHtml(input.canonicalUrl)}">` : "",
|
|
511
|
-
...input.alternates.map(
|
|
512
|
-
(a) => `<link rel="alternate" hreflang="${escapeHtml(a.hreflang)}" href="${escapeHtml(a.href)}">`
|
|
513
|
-
),
|
|
514
|
-
input.jsonLd ? renderJsonLdScript(d) : "",
|
|
515
|
-
`<style>${CSS}</style>`
|
|
516
|
-
].filter(Boolean).join("\n ");
|
|
517
|
-
const body = [
|
|
518
|
-
input.localeNav.length > 1 ? renderLocaleNav(input.localeNav) : "",
|
|
519
|
-
renderHeader(p),
|
|
520
|
-
renderLinks(d.links),
|
|
521
|
-
entryList(
|
|
522
|
-
"Experience",
|
|
523
|
-
d.careers.map((c) => ({
|
|
524
|
-
heading: c.role,
|
|
525
|
-
sub: c.organization,
|
|
526
|
-
dates: dateRange(c.startDate, c.endDate, c.isCurrent),
|
|
527
|
-
body: c.description,
|
|
528
|
-
url: c.url
|
|
529
|
-
}))
|
|
530
|
-
),
|
|
531
|
-
entryList(
|
|
532
|
-
"Projects",
|
|
533
|
-
d.projects.map((x) => ({
|
|
534
|
-
heading: x.title,
|
|
535
|
-
dates: dateRange(x.startDate, x.endDate),
|
|
536
|
-
body: x.description,
|
|
537
|
-
url: x.url,
|
|
538
|
-
tags: x.tags
|
|
539
|
-
}))
|
|
540
|
-
),
|
|
541
|
-
renderSkills(d.skills),
|
|
542
|
-
renderActivity(input.activitySnapshot),
|
|
543
|
-
entryList(
|
|
544
|
-
"Education",
|
|
545
|
-
d.education.map((e) => {
|
|
546
|
-
const degree = nonEmpty([e.degree, e.fieldOfStudy], ", ");
|
|
547
|
-
return {
|
|
548
|
-
heading: degree ?? e.institution,
|
|
549
|
-
sub: degree ? e.institution : void 0,
|
|
550
|
-
dates: dateRange(e.startDate, e.endDate, e.isCurrent),
|
|
551
|
-
body: e.description,
|
|
552
|
-
url: e.url
|
|
553
|
-
};
|
|
554
|
-
})
|
|
555
|
-
),
|
|
556
|
-
entryList(
|
|
557
|
-
"Certifications",
|
|
558
|
-
d.certifications.map((c) => ({
|
|
559
|
-
heading: c.title,
|
|
560
|
-
sub: c.issuingOrganization,
|
|
561
|
-
dates: dateRange(c.issueDate, c.expirationDate),
|
|
562
|
-
url: c.url
|
|
563
|
-
}))
|
|
564
|
-
),
|
|
565
|
-
entryList(
|
|
566
|
-
"Publications",
|
|
567
|
-
d.publications.map((x) => ({
|
|
568
|
-
heading: x.title,
|
|
569
|
-
sub: nonEmpty([x.publisher, x.coAuthors?.join(", ")], " \xB7 "),
|
|
570
|
-
dates: dateRange(x.date),
|
|
571
|
-
body: x.description,
|
|
572
|
-
url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : void 0)
|
|
573
|
-
}))
|
|
574
|
-
),
|
|
575
|
-
entryList(
|
|
576
|
-
"Honors & awards",
|
|
577
|
-
d.honors.map((x) => ({
|
|
578
|
-
heading: x.title,
|
|
579
|
-
sub: x.issuer,
|
|
580
|
-
dates: dateRange(x.date),
|
|
581
|
-
body: x.description,
|
|
582
|
-
url: x.url
|
|
583
|
-
}))
|
|
584
|
-
),
|
|
585
|
-
entryList(
|
|
586
|
-
"Memberships",
|
|
587
|
-
d.memberships.map((x) => ({
|
|
588
|
-
heading: x.role ?? x.organization,
|
|
589
|
-
sub: x.role ? x.organization : void 0,
|
|
590
|
-
dates: dateRange(x.startDate, x.endDate, x.isCurrent),
|
|
591
|
-
body: x.description,
|
|
592
|
-
url: x.url
|
|
593
|
-
}))
|
|
594
|
-
),
|
|
595
|
-
entryList(
|
|
596
|
-
"Volunteering",
|
|
597
|
-
d.volunteering.map((x) => ({
|
|
598
|
-
heading: x.role,
|
|
599
|
-
sub: nonEmpty([x.organization, x.cause], " \xB7 "),
|
|
600
|
-
dates: dateRange(x.startDate, x.endDate, x.isCurrent),
|
|
601
|
-
body: x.description,
|
|
602
|
-
url: x.url
|
|
603
|
-
}))
|
|
604
|
-
),
|
|
605
|
-
entryList(
|
|
606
|
-
"Courses",
|
|
607
|
-
d.courses.map((x) => ({
|
|
608
|
-
heading: x.title,
|
|
609
|
-
sub: x.provider,
|
|
610
|
-
dates: dateRange(x.completionDate),
|
|
611
|
-
body: x.description,
|
|
612
|
-
url: x.certificateUrl
|
|
613
|
-
}))
|
|
614
|
-
),
|
|
615
|
-
entryList(
|
|
616
|
-
"Patents",
|
|
617
|
-
d.patents.map((x) => ({
|
|
618
|
-
heading: x.title,
|
|
619
|
-
sub: nonEmpty([x.patentNumber, x.office, x.status, x.coInventors?.join(", ")], " \xB7 "),
|
|
620
|
-
dates: dateRange(x.filingDate ?? x.grantDate),
|
|
621
|
-
body: x.description,
|
|
622
|
-
url: x.url
|
|
623
|
-
}))
|
|
624
|
-
),
|
|
625
|
-
entryList(
|
|
626
|
-
"Test scores",
|
|
627
|
-
d.testScores.map((x) => ({
|
|
628
|
-
heading: `${x.title}: ${x.score}`,
|
|
629
|
-
dates: dateRange(x.date),
|
|
630
|
-
body: x.description,
|
|
631
|
-
url: x.url
|
|
632
|
-
}))
|
|
633
|
-
),
|
|
634
|
-
renderLanguages(d.languages),
|
|
635
|
-
renderRecommendations(d.recommendations),
|
|
636
|
-
renderContact(d.contact)
|
|
637
|
-
].filter(Boolean).join("\n");
|
|
638
|
-
const footer = d.settings.showPoweredBy === true ? '<footer class="powered">Powered by takuhon</footer>' : "";
|
|
639
|
-
return `<!DOCTYPE html>
|
|
640
|
-
<html lang="${escapeHtml(d.resolvedLocale)}">
|
|
641
|
-
<head>
|
|
642
|
-
${head}
|
|
643
|
-
</head>
|
|
644
|
-
<body>
|
|
645
|
-
<main>
|
|
646
|
-
${body}
|
|
647
|
-
</main>
|
|
648
|
-
${footer ? `${footer}
|
|
649
|
-
` : ""}</body>
|
|
650
|
-
</html>
|
|
651
|
-
`;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// src/site.ts
|
|
655
|
-
import { deriveCv, resolveLocale } from "@takuhon/core";
|
|
656
|
-
|
|
657
|
-
// src/cv-html.ts
|
|
658
|
-
var SECTION_TITLES = {
|
|
659
|
-
experience: "Experience",
|
|
660
|
-
education: "Education",
|
|
661
|
-
skills: "Skills",
|
|
662
|
-
certifications: "Certifications",
|
|
663
|
-
publications: "Publications",
|
|
664
|
-
honors: "Honors & Awards",
|
|
665
|
-
courses: "Courses",
|
|
666
|
-
patents: "Patents",
|
|
667
|
-
languages: "Languages",
|
|
668
|
-
volunteering: "Volunteering",
|
|
669
|
-
memberships: "Memberships"
|
|
670
|
-
};
|
|
671
|
-
var CSS2 = `:root{--fg:#1a1a1a;--muted:#555;--accent:#0b5fff;--line:#d9d9d9}
|
|
672
|
-
*{box-sizing:border-box}
|
|
673
|
-
body{margin:0;background:#f3f4f6;color:var(--fg);font:13px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
674
|
-
main{background:#fff;width:210mm;min-height:297mm;margin:1.5rem auto;padding:18mm 16mm;box-shadow:0 1px 6px rgba(0,0,0,.15)}
|
|
675
|
-
h1{font-size:1.7rem;margin:0}
|
|
676
|
-
.tagline{font-size:1.05rem;color:var(--muted);margin:.15rem 0 0}
|
|
677
|
-
.contact{color:var(--muted);font-size:.85rem;margin:.4rem 0 0;display:flex;flex-wrap:wrap;gap:.25rem 1rem}
|
|
678
|
-
.contact a{color:var(--accent)}
|
|
679
|
-
.bio{margin:.75rem 0 0}
|
|
680
|
-
header{border-bottom:2px solid var(--fg);padding-bottom:.6rem;margin-bottom:.4rem}
|
|
681
|
-
section{margin-top:1.1rem}
|
|
682
|
-
h2{font-size:.95rem;text-transform:uppercase;letter-spacing:.05em;color:var(--accent);border-bottom:1px solid var(--line);margin:0 0 .5rem;padding-bottom:.15rem}
|
|
683
|
-
ul{padding:0;margin:0;list-style:none}
|
|
684
|
-
.entry{margin:0 0 .7rem;break-inside:avoid}
|
|
685
|
-
.entry .row{display:flex;justify-content:space-between;gap:1rem;align-items:baseline}
|
|
686
|
-
.entry h3{font-size:.95rem;margin:0}
|
|
687
|
-
.entry .dates{color:var(--muted);font-size:.8rem;white-space:nowrap}
|
|
688
|
-
.entry .sub{color:var(--muted);margin:.05rem 0}
|
|
689
|
-
.entry p{margin:.2rem 0 0}
|
|
690
|
-
.entry a{color:var(--accent)}
|
|
691
|
-
.chips{display:flex;flex-wrap:wrap;gap:.3rem}
|
|
692
|
-
.chips li{border:1px solid var(--line);border-radius:1rem;padding:.05rem .55rem;font-size:.8rem}
|
|
693
|
-
@media print{
|
|
694
|
-
body{background:#fff}
|
|
695
|
-
main{width:auto;min-height:0;margin:0;padding:0;box-shadow:none}
|
|
696
|
-
a{color:var(--fg)}
|
|
697
|
-
}
|
|
698
|
-
@page{size:A4;margin:14mm}`;
|
|
699
|
-
function renderEntry2(entry) {
|
|
700
|
-
const href = entry.url ? safeUrl(entry.url) : void 0;
|
|
701
|
-
const title = href ? `<a href="${escapeHtml(href)}">${escapeHtml(entry.heading)}</a>` : escapeHtml(entry.heading);
|
|
702
|
-
const dates = entry.dates ? `<span class="dates">${escapeHtml(entry.dates)}</span>` : "";
|
|
703
|
-
const parts = [`<div class="row"><h3>${title}</h3>${dates}</div>`];
|
|
704
|
-
if (entry.sub) parts.push(`<p class="sub">${escapeHtml(entry.sub)}</p>`);
|
|
705
|
-
if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);
|
|
706
|
-
return `<li class="entry">${parts.join("")}</li>`;
|
|
707
|
-
}
|
|
708
|
-
function entryListSection(title, entries) {
|
|
709
|
-
return `<section><h2>${escapeHtml(title)}</h2><ul>${entries.map(renderEntry2).join("")}</ul></section>`;
|
|
710
|
-
}
|
|
711
|
-
function chipSection(title, labels) {
|
|
712
|
-
const chips = labels.map((l) => `<li>${escapeHtml(l)}</li>`).join("");
|
|
713
|
-
return `<section><h2>${escapeHtml(title)}</h2><ul class="chips">${chips}</ul></section>`;
|
|
714
|
-
}
|
|
715
|
-
function languageLabel(l) {
|
|
716
|
-
return `${l.displayName ?? l.language} \u2014 ${l.proficiency}`;
|
|
717
|
-
}
|
|
718
|
-
function renderSection(section) {
|
|
719
|
-
const title = SECTION_TITLES[section.kind];
|
|
720
|
-
switch (section.kind) {
|
|
721
|
-
case "experience":
|
|
722
|
-
return entryListSection(
|
|
723
|
-
title,
|
|
724
|
-
section.entries.map((c) => ({
|
|
725
|
-
heading: c.role,
|
|
726
|
-
sub: c.organization,
|
|
727
|
-
dates: dateRange(c.startDate, c.endDate, c.isCurrent),
|
|
728
|
-
body: c.description,
|
|
729
|
-
url: c.url
|
|
730
|
-
}))
|
|
731
|
-
);
|
|
732
|
-
case "education":
|
|
733
|
-
return entryListSection(
|
|
734
|
-
title,
|
|
735
|
-
section.entries.map((e) => {
|
|
736
|
-
const degree = nonEmpty([e.degree, e.fieldOfStudy], ", ");
|
|
737
|
-
return {
|
|
738
|
-
heading: degree ?? e.institution,
|
|
739
|
-
sub: degree ? e.institution : void 0,
|
|
740
|
-
dates: dateRange(e.startDate, e.endDate, e.isCurrent),
|
|
741
|
-
body: e.description,
|
|
742
|
-
url: e.url
|
|
743
|
-
};
|
|
744
|
-
})
|
|
745
|
-
);
|
|
746
|
-
case "skills":
|
|
747
|
-
return chipSection(
|
|
748
|
-
title,
|
|
749
|
-
section.entries.map((s) => s.label)
|
|
750
|
-
);
|
|
751
|
-
case "languages":
|
|
752
|
-
return chipSection(title, section.entries.map(languageLabel));
|
|
753
|
-
case "certifications":
|
|
754
|
-
return entryListSection(
|
|
755
|
-
title,
|
|
756
|
-
section.entries.map((c) => ({
|
|
757
|
-
heading: c.title,
|
|
758
|
-
sub: c.issuingOrganization,
|
|
759
|
-
dates: dateRange(c.issueDate, c.expirationDate),
|
|
760
|
-
url: c.url
|
|
761
|
-
}))
|
|
762
|
-
);
|
|
763
|
-
case "publications":
|
|
764
|
-
return entryListSection(
|
|
765
|
-
title,
|
|
766
|
-
section.entries.map((x) => ({
|
|
767
|
-
heading: x.title,
|
|
768
|
-
sub: nonEmpty([x.publisher, x.coAuthors?.join(", ")], " \xB7 "),
|
|
769
|
-
dates: dateRange(x.date),
|
|
770
|
-
body: x.description,
|
|
771
|
-
url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : void 0)
|
|
772
|
-
}))
|
|
773
|
-
);
|
|
774
|
-
case "honors":
|
|
775
|
-
return entryListSection(
|
|
776
|
-
title,
|
|
777
|
-
section.entries.map((x) => ({
|
|
778
|
-
heading: x.title,
|
|
779
|
-
sub: x.issuer,
|
|
780
|
-
dates: dateRange(x.date),
|
|
781
|
-
body: x.description,
|
|
782
|
-
url: x.url
|
|
783
|
-
}))
|
|
784
|
-
);
|
|
785
|
-
case "courses":
|
|
786
|
-
return entryListSection(
|
|
787
|
-
title,
|
|
788
|
-
section.entries.map((x) => ({
|
|
789
|
-
heading: x.title,
|
|
790
|
-
sub: x.provider,
|
|
791
|
-
dates: dateRange(x.completionDate),
|
|
792
|
-
body: x.description,
|
|
793
|
-
url: x.certificateUrl
|
|
794
|
-
}))
|
|
795
|
-
);
|
|
796
|
-
case "patents":
|
|
797
|
-
return entryListSection(
|
|
798
|
-
title,
|
|
799
|
-
section.entries.map((x) => ({
|
|
800
|
-
heading: x.title,
|
|
801
|
-
sub: nonEmpty([x.patentNumber, x.office, x.status], " \xB7 "),
|
|
802
|
-
dates: dateRange(x.filingDate ?? x.grantDate),
|
|
803
|
-
body: x.description,
|
|
804
|
-
url: x.url
|
|
805
|
-
}))
|
|
806
|
-
);
|
|
807
|
-
case "volunteering":
|
|
808
|
-
return entryListSection(
|
|
809
|
-
title,
|
|
810
|
-
section.entries.map((x) => ({
|
|
811
|
-
heading: x.role,
|
|
812
|
-
sub: nonEmpty([x.organization, x.cause], " \xB7 "),
|
|
813
|
-
dates: dateRange(x.startDate, x.endDate, x.isCurrent),
|
|
814
|
-
body: x.description,
|
|
815
|
-
url: x.url
|
|
816
|
-
}))
|
|
817
|
-
);
|
|
818
|
-
case "memberships":
|
|
819
|
-
return entryListSection(
|
|
820
|
-
title,
|
|
821
|
-
section.entries.map((x) => ({
|
|
822
|
-
heading: x.role ?? x.organization,
|
|
823
|
-
sub: x.role ? x.organization : void 0,
|
|
824
|
-
dates: dateRange(x.startDate, x.endDate, x.isCurrent),
|
|
825
|
-
body: x.description,
|
|
826
|
-
url: x.url
|
|
827
|
-
}))
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
function renderHeader2(cv) {
|
|
832
|
-
const h = cv.header;
|
|
833
|
-
const parts = [`<h1>${escapeHtml(h.displayName)}</h1>`];
|
|
834
|
-
if (h.tagline) parts.push(`<p class="tagline">${escapeHtml(h.tagline)}</p>`);
|
|
835
|
-
const contact = [];
|
|
836
|
-
if (h.location) contact.push(`<span>${escapeHtml(h.location)}</span>`);
|
|
837
|
-
if (h.email) {
|
|
838
|
-
contact.push(`<a href="mailto:${escapeHtml(h.email)}">${escapeHtml(h.email)}</a>`);
|
|
839
|
-
}
|
|
840
|
-
const formHref = h.formUrl ? safeUrl(h.formUrl) : void 0;
|
|
841
|
-
if (formHref) contact.push(`<a href="${escapeHtml(formHref)}">Contact</a>`);
|
|
842
|
-
if (contact.length > 0) parts.push(`<div class="contact">${contact.join("")}</div>`);
|
|
843
|
-
if (h.bio) parts.push(`<p class="bio">${escapeHtml(h.bio)}</p>`);
|
|
844
|
-
return `<header>${parts.join("")}</header>`;
|
|
845
|
-
}
|
|
846
|
-
function renderCvHtml(cv) {
|
|
847
|
-
const title = `${cv.header.displayName} \u2014 CV`;
|
|
848
|
-
const body = [renderHeader2(cv), ...cv.sections.map(renderSection)].join("\n");
|
|
849
|
-
return `<!DOCTYPE html>
|
|
850
|
-
<html lang="${escapeHtml(cv.resolvedLocale)}">
|
|
851
|
-
<head>
|
|
852
|
-
<meta charset="utf-8">
|
|
853
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
854
|
-
<title>${escapeHtml(title)}</title>
|
|
855
|
-
<style>${CSS2}</style>
|
|
856
|
-
</head>
|
|
857
|
-
<body>
|
|
858
|
-
<main>
|
|
859
|
-
${body}
|
|
860
|
-
</main>
|
|
861
|
-
</body>
|
|
862
|
-
</html>
|
|
863
|
-
`;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// src/site.ts
|
|
867
|
-
function generateSite(profile, options = {}) {
|
|
868
|
-
const { baseUrl } = options;
|
|
869
|
-
const defaultLocale = profile.settings.defaultLocale;
|
|
870
|
-
const locales = [.../* @__PURE__ */ new Set([defaultLocale, ...profile.settings.availableLocales])];
|
|
871
|
-
const jsonLd = profile.settings.enableJsonLd !== false;
|
|
872
|
-
const activitySnapshot = profile.settings.activity?.enabled === true ? options.activitySnapshot ?? void 0 : void 0;
|
|
873
|
-
const pages = [];
|
|
874
|
-
for (const locale of locales) {
|
|
875
|
-
const localized = resolveLocale(profile, locale);
|
|
876
|
-
const isDefault = locale === defaultLocale;
|
|
877
|
-
const localeNav = locales.map((to) => ({
|
|
878
|
-
locale: to,
|
|
879
|
-
href: localeHref(locale, to, defaultLocale),
|
|
880
|
-
current: to === locale
|
|
881
|
-
}));
|
|
882
|
-
const canonicalUrl = baseUrl ? absoluteUrl(baseUrl, locale, defaultLocale) : void 0;
|
|
883
|
-
const alternates = baseUrl ? buildAlternates(baseUrl, locales, defaultLocale) : [];
|
|
884
|
-
const html = renderProfileHtml({
|
|
885
|
-
localized,
|
|
886
|
-
canonicalUrl,
|
|
887
|
-
alternates,
|
|
888
|
-
localeNav,
|
|
889
|
-
jsonLd,
|
|
890
|
-
activitySnapshot
|
|
891
|
-
});
|
|
892
|
-
pages.push({
|
|
893
|
-
route: isDefault ? "/" : `/${locale}/`,
|
|
894
|
-
file: isDefault ? "index.html" : `${locale}/index.html`,
|
|
895
|
-
html
|
|
896
|
-
});
|
|
897
|
-
if (options.cv === true) {
|
|
898
|
-
pages.push({
|
|
899
|
-
route: isDefault ? "/cv/" : `/${locale}/cv/`,
|
|
900
|
-
file: isDefault ? "cv.html" : `${locale}/cv.html`,
|
|
901
|
-
html: renderCvHtml(deriveCv(localized))
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
return pages;
|
|
906
|
-
}
|
|
907
|
-
function absoluteUrl(baseUrl, locale, defaultLocale) {
|
|
908
|
-
return locale === defaultLocale ? `${baseUrl}/` : `${baseUrl}/${locale}/`;
|
|
909
|
-
}
|
|
910
|
-
function buildAlternates(baseUrl, locales, defaultLocale) {
|
|
911
|
-
const alternates = locales.map((locale) => ({
|
|
912
|
-
hreflang: locale,
|
|
913
|
-
href: absoluteUrl(baseUrl, locale, defaultLocale)
|
|
914
|
-
}));
|
|
915
|
-
alternates.push({
|
|
916
|
-
hreflang: "x-default",
|
|
917
|
-
href: absoluteUrl(baseUrl, defaultLocale, defaultLocale)
|
|
918
|
-
});
|
|
919
|
-
return alternates;
|
|
920
|
-
}
|
|
921
|
-
function localeHref(from, to, defaultLocale) {
|
|
922
|
-
const fromRoot = from === defaultLocale;
|
|
923
|
-
const toRoot = to === defaultLocale;
|
|
924
|
-
if (fromRoot) return toRoot ? "./" : `${to}/`;
|
|
925
|
-
return toRoot ? "../" : `../${to}/`;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// src/dev-command.ts
|
|
929
356
|
var DEFAULT_PATH2 = "takuhon.json";
|
|
930
357
|
var DEFAULT_PORT = 4321;
|
|
931
358
|
var USAGE = `Usage: takuhon dev [path] [--port <n>] [--base-url <url>]
|
|
@@ -1693,11 +1120,12 @@ Run \`takuhon admin --help\` for usage.
|
|
|
1693
1120
|
// src/build-command.ts
|
|
1694
1121
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
1695
1122
|
import { dirname as dirname5, join as join6 } from "path";
|
|
1123
|
+
import { generateSite as generateSite2 } from "@takuhon/api";
|
|
1696
1124
|
import {
|
|
1697
1125
|
DARK_PALETTE,
|
|
1698
1126
|
applyPublicPrivacyFilter as applyPublicPrivacyFilter2,
|
|
1699
1127
|
normalize as normalize2,
|
|
1700
|
-
renderActivitySvg
|
|
1128
|
+
renderActivitySvg,
|
|
1701
1129
|
validate as validate2
|
|
1702
1130
|
} from "@takuhon/core";
|
|
1703
1131
|
var DEFAULT_PATH4 = "takuhon.json";
|
|
@@ -1841,7 +1269,7 @@ ${lines.join("\n")}
|
|
|
1841
1269
|
const written = [];
|
|
1842
1270
|
const assets = [];
|
|
1843
1271
|
try {
|
|
1844
|
-
for (const page of
|
|
1272
|
+
for (const page of generateSite2(filtered, { baseUrl, activitySnapshot, cv })) {
|
|
1845
1273
|
const outFile = join6(output, page.file);
|
|
1846
1274
|
mkdirSync3(dirname5(outFile), { recursive: true });
|
|
1847
1275
|
writeFileAtomic(outFile, page.html);
|
|
@@ -1849,8 +1277,8 @@ ${lines.join("\n")}
|
|
|
1849
1277
|
}
|
|
1850
1278
|
if (activitySnapshot) {
|
|
1851
1279
|
const variants = [
|
|
1852
|
-
["activity.svg",
|
|
1853
|
-
["activity-dark.svg",
|
|
1280
|
+
["activity.svg", renderActivitySvg(activitySnapshot)],
|
|
1281
|
+
["activity-dark.svg", renderActivitySvg(activitySnapshot, { palette: DARK_PALETTE })]
|
|
1854
1282
|
];
|
|
1855
1283
|
for (const [file, svg] of variants) {
|
|
1856
1284
|
if (svg === "") continue;
|