Files
lms-sb/src/lib/tools/style-settings.ts
T
admins b6d555ab3b Add Obsidian Toolbox — 4 syntax generators under /tools
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>
2026-06-23 15:45:52 +05:00

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;
}