mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-16 08:36:40 +01:00
173 lines
4.7 KiB
TypeScript
Executable File
173 lines
4.7 KiB
TypeScript
Executable File
import type { PluginDefinition } from '@yaakapp/api';
|
|
import type { TemplateFunctionArg } from '@yaakapp-internal/plugins';
|
|
|
|
import type { ContextFn } from 'date-fns';
|
|
import {
|
|
addDays,
|
|
addHours,
|
|
addMinutes,
|
|
addMonths,
|
|
addSeconds,
|
|
addYears,
|
|
format as formatDate,
|
|
isValid,
|
|
parseISO,
|
|
subDays,
|
|
subHours,
|
|
subMinutes,
|
|
subMonths,
|
|
subSeconds,
|
|
subYears,
|
|
} from 'date-fns';
|
|
|
|
const dateArg: TemplateFunctionArg = {
|
|
type: 'text',
|
|
name: 'date',
|
|
label: 'Timestamp',
|
|
optional: true,
|
|
description:
|
|
'Can be a timestamp in milliseconds, ISO string, or anything parseable by JS `new Date()`',
|
|
placeholder: new Date().toISOString(),
|
|
};
|
|
|
|
const expressionArg: TemplateFunctionArg = {
|
|
type: 'text',
|
|
name: 'expression',
|
|
label: 'Expression',
|
|
description: "Modification expression (eg. '-5d +2h 3m'). Available units: y, M, d, h, m, s",
|
|
optional: true,
|
|
placeholder: '-5d +2h 3m',
|
|
};
|
|
|
|
const formatArg: TemplateFunctionArg = {
|
|
name: 'format',
|
|
label: 'Format String',
|
|
description: "Format string to describe the output (eg. 'yyyy-MM-dd at HH:mm:ss')",
|
|
optional: true,
|
|
placeholder: 'yyyy-MM-dd HH:mm:ss',
|
|
type: 'text',
|
|
};
|
|
|
|
export const plugin: PluginDefinition = {
|
|
templateFunctions: [
|
|
{
|
|
name: 'timestamp.unix',
|
|
description: 'Get the timestamp in seconds',
|
|
args: [dateArg],
|
|
onRender: async (_ctx, args) => {
|
|
const d = parseDateString(String(args.values.date ?? ''));
|
|
return String(Math.floor(d.getTime() / 1000));
|
|
},
|
|
},
|
|
{
|
|
name: 'timestamp.unixMillis',
|
|
description: 'Get the timestamp in milliseconds',
|
|
args: [dateArg],
|
|
onRender: async (_ctx, args) => {
|
|
const d = parseDateString(String(args.values.date ?? ''));
|
|
return String(d.getTime());
|
|
},
|
|
},
|
|
{
|
|
name: 'timestamp.iso8601',
|
|
description: 'Get the date in ISO8601 format',
|
|
args: [dateArg],
|
|
onRender: async (_ctx, args) => {
|
|
const d = parseDateString(String(args.values.date ?? ''));
|
|
return d.toISOString();
|
|
},
|
|
},
|
|
{
|
|
name: 'timestamp.format',
|
|
description: 'Format a date using a dayjs-compatible format string',
|
|
args: [dateArg, formatArg],
|
|
previewArgs: [formatArg.name],
|
|
onRender: async (_ctx, args) => formatDatetime(args.values),
|
|
},
|
|
{
|
|
name: 'timestamp.offset',
|
|
description: 'Get the offset of a date based on an expression',
|
|
args: [dateArg, expressionArg],
|
|
previewArgs: [expressionArg.name],
|
|
onRender: async (_ctx, args) => calculateDatetime(args.values),
|
|
},
|
|
],
|
|
};
|
|
|
|
function applyDateOp(d: Date, sign: string, amount: number, unit: string): Date {
|
|
switch (unit) {
|
|
case 'y':
|
|
return sign === '-' ? subYears(d, amount) : addYears(d, amount);
|
|
case 'M':
|
|
return sign === '-' ? subMonths(d, amount) : addMonths(d, amount);
|
|
case 'd':
|
|
return sign === '-' ? subDays(d, amount) : addDays(d, amount);
|
|
case 'h':
|
|
return sign === '-' ? subHours(d, amount) : addHours(d, amount);
|
|
case 'm':
|
|
return sign === '-' ? subMinutes(d, amount) : addMinutes(d, amount);
|
|
case 's':
|
|
return sign === '-' ? subSeconds(d, amount) : addSeconds(d, amount);
|
|
default:
|
|
throw new Error(`Invalid data calculation unit: ${unit}`);
|
|
}
|
|
}
|
|
|
|
function parseOp(op: string): { sign: string; amount: number; unit: string } | null {
|
|
const match = op.match(/^([+-]?)(\d+)([yMdhms])$/);
|
|
if (!match) {
|
|
throw new Error(`Invalid date expression: ${op}`);
|
|
}
|
|
const [, sign, amount, unit] = match;
|
|
if (!unit) return null;
|
|
return { sign: sign ?? '+', amount: Number(amount ?? 0), unit };
|
|
}
|
|
|
|
function parseDateString(date: string): Date {
|
|
if (!date.trim()) {
|
|
return new Date();
|
|
}
|
|
|
|
const isoDate = parseISO(date);
|
|
if (isValid(isoDate)) {
|
|
return isoDate;
|
|
}
|
|
|
|
const jsDate = /^\d+(\.\d+)?$/.test(date) ? new Date(Number(date)) : new Date(date);
|
|
if (isValid(jsDate)) {
|
|
return jsDate;
|
|
}
|
|
|
|
throw new Error(`Invalid date: ${date}`);
|
|
}
|
|
|
|
export function calculateDatetime(args: { date?: string; expression?: string }): string {
|
|
const { date, expression } = args;
|
|
let jsDate = parseDateString(date ?? '');
|
|
|
|
if (expression) {
|
|
const ops = String(expression)
|
|
.split(' ')
|
|
.map((s) => s.trim())
|
|
.filter(Boolean);
|
|
for (const op of ops) {
|
|
const parsed = parseOp(op);
|
|
if (parsed) {
|
|
jsDate = applyDateOp(jsDate, parsed.sign, parsed.amount, parsed.unit);
|
|
}
|
|
}
|
|
}
|
|
|
|
return jsDate.toISOString();
|
|
}
|
|
|
|
export function formatDatetime(args: {
|
|
date?: string;
|
|
format?: string;
|
|
in?: ContextFn<Date>;
|
|
}): string {
|
|
const { date, format } = args;
|
|
const d = parseDateString(date ?? '');
|
|
return formatDate(d, String(format || 'yyyy-MM-dd HH:mm:ss'), { in: args.in });
|
|
}
|