b6d555ab3b
Inside-LMS toolbox for registered students: callout-CSS, YAML-frontmatter, theme CSS-variables, Style Settings generators. Auth inherited from (student) route group; no public surface, no email gate. - Pure generators in src/lib/tools/* with Vitest (33 tests) - Shared YAML-safe scalar quoter (_shared/yaml.ts) used by frontmatter + style-settings: unquoted user input was silently breaking YAML (tags '#x' -> null, color default '#7C3AED' -> null, 'a: b' -> parse error) - hex validation in callout (no NaN), date input constrained, clipboard + analytics calls guarded - Prisma ToolUsage model + migration; logToolUsage Server Action (auth-first, 10-min dedup window per user/tool) - Tool index /tools + ToolCard (explicit lucide map, no import *) + header link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
import { yamlScalar } from "./_shared/yaml";
|
|
|
|
export type SettingType =
|
|
| "heading" | "class-toggle" | "class-select"
|
|
| "variable-text" | "variable-number" | "variable-number-slider"
|
|
| "variable-select" | "variable-color" | "info-text";
|
|
|
|
export interface SettingItem {
|
|
id: string;
|
|
title: string;
|
|
type: SettingType;
|
|
default?: string;
|
|
/** для variable-select / class-select: значения через запятую */
|
|
options?: string;
|
|
/** для variable-color: формат, напр. hex */
|
|
format?: string;
|
|
}
|
|
|
|
export interface StyleSettingsInput {
|
|
pluginName: string;
|
|
settings: SettingItem[];
|
|
}
|
|
|
|
function renderItem(item: SettingItem): string {
|
|
// id/title/default/options — пользовательский ввод → безопасный YAML-скаляр
|
|
// (иначе, напр., default "#7C3AED" YAML читает как комментарий → null).
|
|
const lines = [
|
|
` -`,
|
|
` id: ${yamlScalar(item.id)}`,
|
|
` title: ${yamlScalar(item.title)}`,
|
|
` type: ${item.type}`,
|
|
];
|
|
if (item.type !== "heading" && item.type !== "info-text" && item.default !== undefined && item.default !== "")
|
|
lines.push(` default: ${yamlScalar(item.default)}`);
|
|
if ((item.type === "variable-select" || item.type === "class-select") && item.options) {
|
|
const opts = item.options.split(",").map((o) => o.trim()).filter(Boolean);
|
|
lines.push(` options:`);
|
|
opts.forEach((o) => lines.push(` - ${yamlScalar(o)}`));
|
|
}
|
|
if (item.type === "variable-color")
|
|
lines.push(` format: ${item.format || "hex"}`);
|
|
return lines.join("\n");
|
|
}
|
|
|
|
export function buildStyleSettings(input: StyleSettingsInput): string {
|
|
const id = input.pluginName.toLowerCase().replace(/\s+/g, "-");
|
|
const body = [
|
|
`/* @settings`, ``,
|
|
`name: ${yamlScalar(input.pluginName)}`,
|
|
`id: ${yamlScalar(id)}`,
|
|
`settings:`,
|
|
...input.settings.map(renderItem),
|
|
``, `*/`,
|
|
].join("\n");
|
|
return body;
|
|
}
|