77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
import { rangeDates } from "@/lib/planMeta";
|
|
import type { TripItem } from "@/lib/types";
|
|
|
|
function pad(n: number) {
|
|
return String(n).padStart(2, "0");
|
|
}
|
|
|
|
function formatDate(d: Date): string {
|
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}`;
|
|
}
|
|
|
|
function formatStamp(d = new Date()): string {
|
|
return `${formatDate(d)}T${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}Z`;
|
|
}
|
|
|
|
function escapeText(s: string): string {
|
|
return s.replace(/\\/g, "\\\\").replace(/;/g, "\\;").replace(/,/g, "\\,").replace(/\n/g, "\\n");
|
|
}
|
|
|
|
export interface IcsStop extends TripItem {
|
|
offset: number;
|
|
}
|
|
|
|
/** Build an ICS calendar from plan stops (requires startMonth YYYY-MM). */
|
|
export function buildPlanIcs(opts: {
|
|
title: string;
|
|
startMonth: string;
|
|
stops: IcsStop[];
|
|
}): string | null {
|
|
if (!opts.startMonth || opts.stops.length === 0) return null;
|
|
const now = formatStamp();
|
|
const lines = [
|
|
"BEGIN:VCALENDAR",
|
|
"VERSION:2.0",
|
|
"PRODID:-//nomadro//Move Plan//CN",
|
|
"CALSCALE:GREGORIAN",
|
|
"METHOD:PUBLISH",
|
|
`X-WR-CALNAME:${escapeText(opts.title || "nomadro 旅居计划")}`,
|
|
];
|
|
|
|
opts.stops.forEach((stop, i) => {
|
|
const range = rangeDates(opts.startMonth, stop.offset, stop.months);
|
|
if (!range) return;
|
|
const summary = `${stop.emoji} ${stop.name}, ${stop.country}`;
|
|
const descParts = [
|
|
`停留 ${stop.months} 个月`,
|
|
`约 ¥${(stop.cost * stop.months).toLocaleString()}`,
|
|
stop.note ? `备注:${stop.note}` : "",
|
|
"由 nomadro 旅居计划中心生成",
|
|
].filter(Boolean);
|
|
lines.push(
|
|
"BEGIN:VEVENT",
|
|
`UID:nomadro-${stop.slug}-${i}@nomadro.com`,
|
|
`DTSTAMP:${now}`,
|
|
`DTSTART;VALUE=DATE:${formatDate(range.start)}`,
|
|
`DTEND;VALUE=DATE:${formatDate(range.endExclusive)}`,
|
|
`SUMMARY:${escapeText(summary)}`,
|
|
`DESCRIPTION:${escapeText(descParts.join(" · "))}`,
|
|
`LOCATION:${escapeText(`${stop.name}, ${stop.country}`)}`,
|
|
"END:VEVENT"
|
|
);
|
|
});
|
|
|
|
lines.push("END:VCALENDAR");
|
|
return lines.join("\r\n");
|
|
}
|
|
|
|
export function downloadPlanIcs(filename: string, ics: string) {
|
|
const blob = new Blob([ics], { type: "text/calendar;charset=utf-8" });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|