feat(panel): add electron dashboards for data, podcasts, media, and publish
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
@@ -0,0 +1,698 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const { app, BrowserWindow, dialog, ipcMain } = require("electron");
|
||||||
|
const { execFile } = require("node:child_process");
|
||||||
|
const fs = require("node:fs/promises");
|
||||||
|
const path = require("node:path");
|
||||||
|
const vm = require("node:vm");
|
||||||
|
const matter = require("gray-matter");
|
||||||
|
|
||||||
|
const WORKSPACE_ROOT = path.resolve(__dirname, "../..");
|
||||||
|
const DATA_DIR = path.join(WORKSPACE_ROOT, "data");
|
||||||
|
const PUBLIC_DIR = path.join(WORKSPACE_ROOT, "public");
|
||||||
|
const BLOG_CONTENT_DIR = path.join(WORKSPACE_ROOT, "content", "blog");
|
||||||
|
const PODCAST_CONTENT_DIR = path.join(WORKSPACE_ROOT, "content", "podcasts");
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 1320,
|
||||||
|
height: 860,
|
||||||
|
minWidth: 1080,
|
||||||
|
minHeight: 700,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, "preload.cjs"),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadFile(path.join(__dirname, "renderer", "index.html"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertSafePath(baseDir, targetPath) {
|
||||||
|
const resolvedBase = path.resolve(baseDir);
|
||||||
|
const resolvedTarget = path.resolve(targetPath);
|
||||||
|
const isSafe =
|
||||||
|
resolvedTarget === resolvedBase || resolvedTarget.startsWith(`${resolvedBase}${path.sep}`);
|
||||||
|
|
||||||
|
if (!isSafe) {
|
||||||
|
throw new Error("Invalid path access attempt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDataFileName(fileName) {
|
||||||
|
if (!/^[a-z0-9-]+\.ts$/i.test(fileName)) {
|
||||||
|
throw new Error("File name must match: <name>.ts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateBlogSlug(slug) {
|
||||||
|
const normalized = String(slug || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(normalized)) {
|
||||||
|
throw new Error("Slug must match: lowercase-kebab-case");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePodcastKind(kind) {
|
||||||
|
const normalized = String(kind || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
if (normalized !== "yazilim" && normalized !== "masa-basi") {
|
||||||
|
throw new Error("Podcast kind must be one of: yazilim, masa-basi");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runGit(args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
execFile("git", args, { cwd: WORKSPACE_ROOT }, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(stderr || error.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTsKey(key) {
|
||||||
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTsLiteral(value, indent = 0) {
|
||||||
|
const space = " ";
|
||||||
|
const current = space.repeat(indent);
|
||||||
|
const next = space.repeat(indent + 1);
|
||||||
|
|
||||||
|
if (value === null) return "null";
|
||||||
|
if (typeof value === "string") return JSON.stringify(value);
|
||||||
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (!value.length) return "[]";
|
||||||
|
return `[\n${value
|
||||||
|
.map((item) => `${next}${toTsLiteral(item, indent + 1)}`)
|
||||||
|
.join(",\n")}\n${current}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "object") {
|
||||||
|
const entries = Object.entries(value);
|
||||||
|
if (!entries.length) return "{}";
|
||||||
|
return `{\n${entries
|
||||||
|
.map(([key, entryValue]) => {
|
||||||
|
return `${next}${formatTsKey(key)}: ${toTsLiteral(entryValue, indent + 1)}`;
|
||||||
|
})
|
||||||
|
.join(",\n")}\n${current}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findExpressionBounds(source, startIndex) {
|
||||||
|
let i = startIndex;
|
||||||
|
while (i < source.length && /\s/.test(source[i])) i += 1;
|
||||||
|
|
||||||
|
const start = i;
|
||||||
|
const first = source[start];
|
||||||
|
if (!first) throw new Error("Cannot parse export value.");
|
||||||
|
|
||||||
|
if (first !== "[" && first !== "{" && first !== "(" && first !== '"' && first !== "'" && first !== "`") {
|
||||||
|
throw new Error("Only literal exports are supported in structured mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const closingMap = { "[": "]", "{": "}", "(": ")" };
|
||||||
|
let stack = [];
|
||||||
|
if (closingMap[first]) stack = [closingMap[first]];
|
||||||
|
|
||||||
|
let mode = first === '"' || first === "'" || first === "`" ? first : "normal";
|
||||||
|
if (mode !== "normal") i += 1;
|
||||||
|
if (mode === "normal") i = start + 1;
|
||||||
|
|
||||||
|
while (i < source.length) {
|
||||||
|
const ch = source[i];
|
||||||
|
const next = source[i + 1];
|
||||||
|
|
||||||
|
if (mode === "normal") {
|
||||||
|
if (ch === "'" || ch === '"' || ch === "`") {
|
||||||
|
mode = ch;
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === "/" && next === "/") {
|
||||||
|
mode = "line-comment";
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === "/" && next === "*") {
|
||||||
|
mode = "block-comment";
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closingMap[ch]) {
|
||||||
|
stack.push(closingMap[ch]);
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.length && ch === stack[stack.length - 1]) {
|
||||||
|
stack.pop();
|
||||||
|
i += 1;
|
||||||
|
if (!stack.length) {
|
||||||
|
return { start, end: i };
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "line-comment") {
|
||||||
|
if (ch === "\n") mode = "normal";
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "block-comment") {
|
||||||
|
if (ch === "*" && next === "/") {
|
||||||
|
mode = "normal";
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "`") {
|
||||||
|
if (ch === "\\") {
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === "`") {
|
||||||
|
mode = "normal";
|
||||||
|
i += 1;
|
||||||
|
if (!stack.length) return { start, end: i };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "'" || mode === '"') {
|
||||||
|
if (ch === "\\") {
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === mode) {
|
||||||
|
mode = "normal";
|
||||||
|
i += 1;
|
||||||
|
if (!stack.length) return { start, end: i };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unable to parse export expression boundaries.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExportBlocks(source) {
|
||||||
|
const blocks = [];
|
||||||
|
const regex = /export\s+const\s+([A-Za-z0-9_]+)(\s*:\s*[^=]+)?\s*=\s*/g;
|
||||||
|
let match = regex.exec(source);
|
||||||
|
|
||||||
|
while (match) {
|
||||||
|
const exportName = match[1];
|
||||||
|
const expressionStart = regex.lastIndex;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bounds = findExpressionBounds(source, expressionStart);
|
||||||
|
const expression = source.slice(bounds.start, bounds.end);
|
||||||
|
|
||||||
|
let parsed = null;
|
||||||
|
let parseError = null;
|
||||||
|
try {
|
||||||
|
parsed = vm.runInNewContext(`(${expression})`, {}, { timeout: 800 });
|
||||||
|
} catch (error) {
|
||||||
|
parseError = error instanceof Error ? error.message : "Unknown parse error.";
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
name: exportName,
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end,
|
||||||
|
expression,
|
||||||
|
parsed,
|
||||||
|
isArray: Array.isArray(parsed),
|
||||||
|
parseError,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
blocks.push({
|
||||||
|
name: exportName,
|
||||||
|
start: -1,
|
||||||
|
end: -1,
|
||||||
|
expression: "",
|
||||||
|
parsed: null,
|
||||||
|
isArray: false,
|
||||||
|
parseError: error instanceof Error ? error.message : "Unknown parse error.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match = regex.exec(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listDataFiles() {
|
||||||
|
const files = await fs.readdir(DATA_DIR, { withFileTypes: true });
|
||||||
|
return files
|
||||||
|
.filter((item) => item.isFile() && item.name.endsWith(".ts"))
|
||||||
|
.map((item) => item.name)
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readDataFile(fileName) {
|
||||||
|
validateDataFileName(fileName);
|
||||||
|
const target = assertSafePath(DATA_DIR, path.join(DATA_DIR, fileName));
|
||||||
|
const raw = await fs.readFile(target, "utf8");
|
||||||
|
const exports = getExportBlocks(raw).map((entry) => ({
|
||||||
|
name: entry.name,
|
||||||
|
isArray: entry.isArray,
|
||||||
|
value: entry.parsed,
|
||||||
|
parseError: entry.parseError,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { fileName, raw, exports };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveRawDataFile(fileName, raw) {
|
||||||
|
validateDataFileName(fileName);
|
||||||
|
const target = assertSafePath(DATA_DIR, path.join(DATA_DIR, fileName));
|
||||||
|
await fs.writeFile(target, raw, "utf8");
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDataExport({ fileName, exportName, value }) {
|
||||||
|
validateDataFileName(fileName);
|
||||||
|
const target = assertSafePath(DATA_DIR, path.join(DATA_DIR, fileName));
|
||||||
|
const source = await fs.readFile(target, "utf8");
|
||||||
|
const blocks = getExportBlocks(source);
|
||||||
|
const selected = blocks.find((block) => block.name === exportName);
|
||||||
|
|
||||||
|
if (!selected || selected.start < 0 || selected.end < 0) {
|
||||||
|
throw new Error(`Export "${exportName}" not found or cannot be parsed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const literal = toTsLiteral(value, 0);
|
||||||
|
const nextSource = `${source.slice(0, selected.start)}${literal}${source.slice(selected.end)}`;
|
||||||
|
await fs.writeFile(target, nextSource, "utf8");
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDataFile({ fileName, exportName }) {
|
||||||
|
validateDataFileName(fileName);
|
||||||
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(exportName)) {
|
||||||
|
throw new Error("Export name is invalid.");
|
||||||
|
}
|
||||||
|
const target = assertSafePath(DATA_DIR, path.join(DATA_DIR, fileName));
|
||||||
|
await fs.writeFile(target, `export const ${exportName} = [] as const;\n`, {
|
||||||
|
encoding: "utf8",
|
||||||
|
flag: "wx",
|
||||||
|
});
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteDataFile(fileName) {
|
||||||
|
validateDataFileName(fileName);
|
||||||
|
const target = assertSafePath(DATA_DIR, path.join(DATA_DIR, fileName));
|
||||||
|
await fs.unlink(target);
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapMarkdownToBlog(fileName, raw) {
|
||||||
|
const parsed = matter(raw);
|
||||||
|
const slug = fileName.replace(/\.md$/i, "");
|
||||||
|
return {
|
||||||
|
slug,
|
||||||
|
title: String(parsed.data.title || slug),
|
||||||
|
category: String(parsed.data.category || "General"),
|
||||||
|
date: String(parsed.data.date || ""),
|
||||||
|
readTime: String(parsed.data.readTime || ""),
|
||||||
|
author: String(parsed.data.author || "Poyraz Avsever"),
|
||||||
|
excerpt: String(parsed.data.excerpt || ""),
|
||||||
|
coverImage: String(parsed.data.coverImage || "/news/design.svg"),
|
||||||
|
markdown: String(parsed.content || "").trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listBlogs() {
|
||||||
|
await fs.mkdir(BLOG_CONTENT_DIR, { recursive: true });
|
||||||
|
const files = await fs.readdir(BLOG_CONTENT_DIR, { withFileTypes: true });
|
||||||
|
const markdownFiles = files
|
||||||
|
.filter((item) => item.isFile() && item.name.endsWith(".md"))
|
||||||
|
.map((item) => item.name)
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
|
const posts = await Promise.all(
|
||||||
|
markdownFiles.map(async (fileName) => {
|
||||||
|
const target = assertSafePath(BLOG_CONTENT_DIR, path.join(BLOG_CONTENT_DIR, fileName));
|
||||||
|
const raw = await fs.readFile(target, "utf8");
|
||||||
|
return mapMarkdownToBlog(fileName, raw);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upsertBlog({ originalSlug, post }) {
|
||||||
|
if (!post || typeof post !== "object") {
|
||||||
|
throw new Error("Post payload is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const slug = validateBlogSlug(post.slug);
|
||||||
|
const previousSlug = originalSlug ? validateBlogSlug(originalSlug) : null;
|
||||||
|
|
||||||
|
const frontmatter = {
|
||||||
|
title: String(post.title || slug),
|
||||||
|
category: String(post.category || "General"),
|
||||||
|
date: String(post.date || ""),
|
||||||
|
readTime: String(post.readTime || ""),
|
||||||
|
author: String(post.author || "Poyraz Avsever"),
|
||||||
|
excerpt: String(post.excerpt || ""),
|
||||||
|
coverImage: String(post.coverImage || "/news/design.svg"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const markdownBody = String(post.markdown || "").trim();
|
||||||
|
const raw = matter.stringify(markdownBody ? `${markdownBody}\n` : "", frontmatter);
|
||||||
|
|
||||||
|
await fs.mkdir(BLOG_CONTENT_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const nextPath = assertSafePath(BLOG_CONTENT_DIR, path.join(BLOG_CONTENT_DIR, `${slug}.md`));
|
||||||
|
const previousPath =
|
||||||
|
previousSlug && previousSlug !== slug
|
||||||
|
? assertSafePath(BLOG_CONTENT_DIR, path.join(BLOG_CONTENT_DIR, `${previousSlug}.md`))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
await fs.writeFile(nextPath, raw, "utf8");
|
||||||
|
|
||||||
|
if (previousPath) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(previousPath);
|
||||||
|
} catch {
|
||||||
|
// ignore missing previous file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, slug };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteBlogBySlug(slug) {
|
||||||
|
const normalized = validateBlogSlug(slug);
|
||||||
|
const target = assertSafePath(BLOG_CONTENT_DIR, path.join(BLOG_CONTENT_DIR, `${normalized}.md`));
|
||||||
|
await fs.unlink(target);
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapMarkdownToPodcast(kind, fileName, raw) {
|
||||||
|
const parsed = matter(raw);
|
||||||
|
const slug = fileName.replace(/\.md$/i, "");
|
||||||
|
|
||||||
|
return {
|
||||||
|
slug,
|
||||||
|
title: String(parsed.data.title || slug),
|
||||||
|
date: String(parsed.data.date || ""),
|
||||||
|
youtubeUrl: String(parsed.data.youtubeUrl || ""),
|
||||||
|
spotifyUrl: String(parsed.data.spotifyUrl || ""),
|
||||||
|
podcast: kind,
|
||||||
|
markdown: String(parsed.content || "").trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listPodcastEpisodes(kind) {
|
||||||
|
const safeKind = validatePodcastKind(kind);
|
||||||
|
const podcastDir = assertSafePath(PODCAST_CONTENT_DIR, path.join(PODCAST_CONTENT_DIR, safeKind));
|
||||||
|
|
||||||
|
await fs.mkdir(podcastDir, { recursive: true });
|
||||||
|
const files = await fs.readdir(podcastDir, { withFileTypes: true });
|
||||||
|
const markdownFiles = files
|
||||||
|
.filter((item) => item.isFile() && item.name.endsWith(".md"))
|
||||||
|
.map((item) => item.name)
|
||||||
|
.sort((a, b) => b.localeCompare(a));
|
||||||
|
|
||||||
|
const episodes = await Promise.all(
|
||||||
|
markdownFiles.map(async (fileName) => {
|
||||||
|
const target = assertSafePath(podcastDir, path.join(podcastDir, fileName));
|
||||||
|
const raw = await fs.readFile(target, "utf8");
|
||||||
|
return mapMarkdownToPodcast(safeKind, fileName, raw);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return episodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upsertPodcastEpisode(kind, { originalSlug, episode }) {
|
||||||
|
const safeKind = validatePodcastKind(kind);
|
||||||
|
if (!episode || typeof episode !== "object") {
|
||||||
|
throw new Error("Episode payload is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const slug = validateBlogSlug(episode.slug);
|
||||||
|
const previousSlug = originalSlug ? validateBlogSlug(originalSlug) : null;
|
||||||
|
|
||||||
|
const frontmatter = {
|
||||||
|
title: String(episode.title || slug),
|
||||||
|
date: String(episode.date || ""),
|
||||||
|
youtubeUrl: String(episode.youtubeUrl || ""),
|
||||||
|
spotifyUrl: String(episode.spotifyUrl || ""),
|
||||||
|
podcast: safeKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
const markdownBody = String(episode.markdown || "").trim();
|
||||||
|
const raw = matter.stringify(markdownBody ? `${markdownBody}\n` : "", frontmatter);
|
||||||
|
|
||||||
|
const podcastDir = assertSafePath(PODCAST_CONTENT_DIR, path.join(PODCAST_CONTENT_DIR, safeKind));
|
||||||
|
await fs.mkdir(podcastDir, { recursive: true });
|
||||||
|
|
||||||
|
const nextPath = assertSafePath(podcastDir, path.join(podcastDir, `${slug}.md`));
|
||||||
|
const previousPath =
|
||||||
|
previousSlug && previousSlug !== slug
|
||||||
|
? assertSafePath(podcastDir, path.join(podcastDir, `${previousSlug}.md`))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
await fs.writeFile(nextPath, raw, "utf8");
|
||||||
|
|
||||||
|
if (previousPath) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(previousPath);
|
||||||
|
} catch {
|
||||||
|
// ignore missing previous file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, slug };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePodcastEpisode(kind, slug) {
|
||||||
|
const safeKind = validatePodcastKind(kind);
|
||||||
|
const normalized = validateBlogSlug(slug);
|
||||||
|
const podcastDir = assertSafePath(PODCAST_CONTENT_DIR, path.join(PODCAST_CONTENT_DIR, safeKind));
|
||||||
|
const target = assertSafePath(podcastDir, path.join(podcastDir, `${normalized}.md`));
|
||||||
|
await fs.unlink(target);
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listFoldersRecursive(baseDir, current = "") {
|
||||||
|
const target = assertSafePath(baseDir, path.join(baseDir, current));
|
||||||
|
const entries = await fs.readdir(target, { withFileTypes: true });
|
||||||
|
const folders = [current];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory()) continue;
|
||||||
|
const child = current ? path.posix.join(current, entry.name) : entry.name;
|
||||||
|
const nested = await listFoldersRecursive(baseDir, child);
|
||||||
|
folders.push(...nested);
|
||||||
|
}
|
||||||
|
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listMediaFolders() {
|
||||||
|
const folders = await listFoldersRecursive(PUBLIC_DIR);
|
||||||
|
return folders.sort((a, b) => a.localeCompare(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createMediaFolder(folder) {
|
||||||
|
const normalized = folder.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
||||||
|
if (!normalized) throw new Error("Folder path is required.");
|
||||||
|
const target = assertSafePath(PUBLIC_DIR, path.join(PUBLIC_DIR, normalized));
|
||||||
|
await fs.mkdir(target, { recursive: true });
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listMediaFiles(folder = "") {
|
||||||
|
const normalized = folder.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
||||||
|
const target = assertSafePath(PUBLIC_DIR, path.join(PUBLIC_DIR, normalized));
|
||||||
|
const entries = await fs.readdir(target, { withFileTypes: true });
|
||||||
|
const files = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isFile()) continue;
|
||||||
|
const relativePath = normalized
|
||||||
|
? path.posix.join(normalized, entry.name)
|
||||||
|
: entry.name;
|
||||||
|
const absolutePath = assertSafePath(PUBLIC_DIR, path.join(PUBLIC_DIR, relativePath));
|
||||||
|
files.push({
|
||||||
|
name: entry.name,
|
||||||
|
relativePath,
|
||||||
|
absolutePath,
|
||||||
|
isImage: /\.(png|jpe?g|webp|gif|svg)$/i.test(entry.name),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadMediaFiles(folder = "") {
|
||||||
|
const normalized = folder.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
||||||
|
const targetDir = assertSafePath(PUBLIC_DIR, path.join(PUBLIC_DIR, normalized));
|
||||||
|
await fs.mkdir(targetDir, { recursive: true });
|
||||||
|
|
||||||
|
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||||
|
title: "Select image files",
|
||||||
|
properties: ["openFile", "multiSelections"],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Images",
|
||||||
|
extensions: ["png", "jpg", "jpeg", "webp", "gif", "svg"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled || !filePaths.length) {
|
||||||
|
return { uploaded: 0, files: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploaded = [];
|
||||||
|
|
||||||
|
for (const sourcePath of filePaths) {
|
||||||
|
const originalName = path.basename(sourcePath);
|
||||||
|
let finalName = originalName;
|
||||||
|
let counter = 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const candidate = path.join(targetDir, finalName);
|
||||||
|
try {
|
||||||
|
await fs.access(candidate);
|
||||||
|
const ext = path.extname(originalName);
|
||||||
|
const base = path.basename(originalName, ext);
|
||||||
|
finalName = `${base}-${counter}${ext}`;
|
||||||
|
counter += 1;
|
||||||
|
} catch {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const destinationPath = path.join(targetDir, finalName);
|
||||||
|
await fs.copyFile(sourcePath, destinationPath);
|
||||||
|
|
||||||
|
const relativePath = normalized
|
||||||
|
? path.posix.join(normalized, finalName)
|
||||||
|
: finalName;
|
||||||
|
uploaded.push(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { uploaded: uploaded.length, files: uploaded };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMediaFile(relativePath) {
|
||||||
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
||||||
|
if (!normalized) throw new Error("File path is required.");
|
||||||
|
const target = assertSafePath(PUBLIC_DIR, path.join(PUBLIC_DIR, normalized));
|
||||||
|
await fs.unlink(target);
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPublishStatus() {
|
||||||
|
const { stdout } = await runGit(["status", "--short", "--", "data", "public", "content"]);
|
||||||
|
return stdout
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAutoMessage(files) {
|
||||||
|
const names = files
|
||||||
|
.map((item) => path.basename(item))
|
||||||
|
.slice(0, 4)
|
||||||
|
.join(", ");
|
||||||
|
const now = new Date().toISOString().slice(0, 16).replace("T", " ");
|
||||||
|
return names ? `content: update ${names} (${now})` : `content: update data/public/content (${now})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function publishChanges(message = "") {
|
||||||
|
await runGit(["add", "data", "public", "content"]);
|
||||||
|
const staged = await runGit(["diff", "--cached", "--name-only"]);
|
||||||
|
const files = staged.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
if (!files.length) {
|
||||||
|
return { ok: false, message: "No staged changes in data/public/content." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitMessage = message.trim() || buildAutoMessage(files);
|
||||||
|
await runGit(["commit", "-m", commitMessage]);
|
||||||
|
await runGit(["push"]);
|
||||||
|
const head = await runGit(["rev-parse", "--short", "HEAD"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
message: `Published successfully (${head.stdout}).`,
|
||||||
|
commit: head.stdout,
|
||||||
|
commitMessage,
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle("data:list", async () => listDataFiles());
|
||||||
|
ipcMain.handle("data:read", async (_, fileName) => readDataFile(fileName));
|
||||||
|
ipcMain.handle("data:saveRaw", async (_, payload) => saveRawDataFile(payload.fileName, payload.raw));
|
||||||
|
ipcMain.handle("data:updateExport", async (_, payload) => updateDataExport(payload));
|
||||||
|
ipcMain.handle("data:create", async (_, payload) => createDataFile(payload));
|
||||||
|
ipcMain.handle("data:delete", async (_, fileName) => deleteDataFile(fileName));
|
||||||
|
|
||||||
|
ipcMain.handle("blog:list", async () => listBlogs());
|
||||||
|
ipcMain.handle("blog:upsert", async (_, payload) => upsertBlog(payload));
|
||||||
|
ipcMain.handle("blog:delete", async (_, slug) => deleteBlogBySlug(slug));
|
||||||
|
ipcMain.handle("podcast:list", async (_, kind) => listPodcastEpisodes(kind));
|
||||||
|
ipcMain.handle("podcast:upsert", async (_, payload) => upsertPodcastEpisode(payload.kind, payload));
|
||||||
|
ipcMain.handle("podcast:delete", async (_, payload) => deletePodcastEpisode(payload.kind, payload.slug));
|
||||||
|
|
||||||
|
ipcMain.handle("media:listFolders", async () => listMediaFolders());
|
||||||
|
ipcMain.handle("media:createFolder", async (_, folder) => createMediaFolder(folder));
|
||||||
|
ipcMain.handle("media:listFiles", async (_, folder) => listMediaFiles(folder));
|
||||||
|
ipcMain.handle("media:upload", async (_, folder) => uploadMediaFiles(folder));
|
||||||
|
ipcMain.handle("media:deleteFile", async (_, relativePath) => deleteMediaFile(relativePath));
|
||||||
|
|
||||||
|
ipcMain.handle("publish:status", async () => getPublishStatus());
|
||||||
|
ipcMain.handle("publish:run", async (_, message) => publishChanges(message));
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
app.on("activate", () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("window-all-closed", () => {
|
||||||
|
if (process.platform !== "darwin") app.quit();
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@portfolio/data-panel",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"main": "main.cjs",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "electron ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^33.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld("panelAPI", {
|
||||||
|
data: {
|
||||||
|
list: () => ipcRenderer.invoke("data:list"),
|
||||||
|
read: (fileName) => ipcRenderer.invoke("data:read", fileName),
|
||||||
|
saveRaw: (payload) => ipcRenderer.invoke("data:saveRaw", payload),
|
||||||
|
updateExport: (payload) => ipcRenderer.invoke("data:updateExport", payload),
|
||||||
|
create: (payload) => ipcRenderer.invoke("data:create", payload),
|
||||||
|
delete: (fileName) => ipcRenderer.invoke("data:delete", fileName),
|
||||||
|
},
|
||||||
|
blog: {
|
||||||
|
list: () => ipcRenderer.invoke("blog:list"),
|
||||||
|
upsert: (payload) => ipcRenderer.invoke("blog:upsert", payload),
|
||||||
|
delete: (slug) => ipcRenderer.invoke("blog:delete", slug),
|
||||||
|
},
|
||||||
|
podcast: {
|
||||||
|
list: (kind) => ipcRenderer.invoke("podcast:list", kind),
|
||||||
|
upsert: (payload) => ipcRenderer.invoke("podcast:upsert", payload),
|
||||||
|
delete: (payload) => ipcRenderer.invoke("podcast:delete", payload),
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
listFolders: () => ipcRenderer.invoke("media:listFolders"),
|
||||||
|
createFolder: (folder) => ipcRenderer.invoke("media:createFolder", folder),
|
||||||
|
listFiles: (folder) => ipcRenderer.invoke("media:listFiles", folder),
|
||||||
|
upload: (folder) => ipcRenderer.invoke("media:upload", folder),
|
||||||
|
deleteFile: (relativePath) => ipcRenderer.invoke("media:deleteFile", relativePath),
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
status: () => ipcRenderer.invoke("publish:status"),
|
||||||
|
run: (message) => ipcRenderer.invoke("publish:run", message),
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,742 @@
|
|||||||
|
|
||||||
|
import { COLLECTION_CONFIGS, PODCAST_LABELS } from "./configs.js";
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
activeTab: "blog",
|
||||||
|
folders: [],
|
||||||
|
selectedFolder: "",
|
||||||
|
blogPosts: [],
|
||||||
|
selectedBlogSlug: "",
|
||||||
|
blogOriginalSlug: "",
|
||||||
|
collection: {
|
||||||
|
key: "announcement",
|
||||||
|
items: [],
|
||||||
|
selectedIndex: null,
|
||||||
|
editingIndex: null,
|
||||||
|
formInputs: {},
|
||||||
|
},
|
||||||
|
podcast: {
|
||||||
|
kind: "yazilim",
|
||||||
|
episodes: [],
|
||||||
|
selectedSlug: "",
|
||||||
|
originalSlug: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const el = {
|
||||||
|
tabs: document.querySelectorAll(".tab-btn"),
|
||||||
|
panels: {
|
||||||
|
blog: document.getElementById("tab-blog"),
|
||||||
|
collection: document.getElementById("tab-collection"),
|
||||||
|
podcast: document.getElementById("tab-podcast"),
|
||||||
|
media: document.getElementById("tab-media"),
|
||||||
|
publish: document.getElementById("tab-publish"),
|
||||||
|
},
|
||||||
|
blogCardList: document.getElementById("blog-card-list"),
|
||||||
|
refreshBlogFiles: document.getElementById("refresh-blog-files"),
|
||||||
|
newBlogFile: document.getElementById("new-blog-file"),
|
||||||
|
deleteBlogFile: document.getElementById("delete-blog-file"),
|
||||||
|
blogEditorTitle: document.getElementById("blog-editor-title"),
|
||||||
|
blogSlug: document.getElementById("blog-slug"),
|
||||||
|
blogTitle: document.getElementById("blog-title"),
|
||||||
|
blogCategory: document.getElementById("blog-category"),
|
||||||
|
blogDate: document.getElementById("blog-date"),
|
||||||
|
blogReadTime: document.getElementById("blog-read-time"),
|
||||||
|
blogAuthor: document.getElementById("blog-author"),
|
||||||
|
blogCoverImage: document.getElementById("blog-cover-image"),
|
||||||
|
blogExcerpt: document.getElementById("blog-excerpt"),
|
||||||
|
blogEditor: document.getElementById("blog-editor"),
|
||||||
|
saveBlogFile: document.getElementById("save-blog-file"),
|
||||||
|
collectionSidebarTitle: document.getElementById("collection-sidebar-title"),
|
||||||
|
collectionCardList: document.getElementById("collection-card-list"),
|
||||||
|
refreshCollection: document.getElementById("refresh-collection"),
|
||||||
|
newCollectionItem: document.getElementById("new-collection-item"),
|
||||||
|
deleteCollectionItem: document.getElementById("delete-collection-item"),
|
||||||
|
saveCollectionItem: document.getElementById("save-collection-item"),
|
||||||
|
collectionEditorTitle: document.getElementById("collection-editor-title"),
|
||||||
|
collectionHelper: document.getElementById("collection-helper"),
|
||||||
|
collectionFormGrid: document.getElementById("collection-form-grid"),
|
||||||
|
podcastSidebarTitle: document.getElementById("podcast-sidebar-title"),
|
||||||
|
podcastCardList: document.getElementById("podcast-card-list"),
|
||||||
|
refreshPodcastFiles: document.getElementById("refresh-podcast-files"),
|
||||||
|
newPodcastFile: document.getElementById("new-podcast-file"),
|
||||||
|
deletePodcastFile: document.getElementById("delete-podcast-file"),
|
||||||
|
podcastEditorTitle: document.getElementById("podcast-editor-title"),
|
||||||
|
podcastSlug: document.getElementById("podcast-slug"),
|
||||||
|
podcastTitle: document.getElementById("podcast-title"),
|
||||||
|
podcastDate: document.getElementById("podcast-date"),
|
||||||
|
podcastYoutubeUrl: document.getElementById("podcast-youtube-url"),
|
||||||
|
podcastSpotifyUrl: document.getElementById("podcast-spotify-url"),
|
||||||
|
podcastEditor: document.getElementById("podcast-editor"),
|
||||||
|
savePodcastFile: document.getElementById("save-podcast-file"),
|
||||||
|
folderSelect: document.getElementById("folder-select"),
|
||||||
|
newFolder: document.getElementById("new-folder"),
|
||||||
|
createFolder: document.getElementById("create-folder"),
|
||||||
|
uploadFile: document.getElementById("upload-file"),
|
||||||
|
refreshMedia: document.getElementById("refresh-media"),
|
||||||
|
mediaFiles: document.getElementById("media-files"),
|
||||||
|
refreshStatus: document.getElementById("refresh-status"),
|
||||||
|
commitMessage: document.getElementById("commit-message"),
|
||||||
|
publishBtn: document.getElementById("publish-btn"),
|
||||||
|
gitStatus: document.getElementById("git-status"),
|
||||||
|
publishLog: document.getElementById("publish-log"),
|
||||||
|
};
|
||||||
|
|
||||||
|
function notify(message) {
|
||||||
|
el.publishLog.textContent = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCollectionConfig(key = state.collection.key) {
|
||||||
|
const config = COLLECTION_CONFIGS[key];
|
||||||
|
if (!config) throw new Error(`Unknown collection config: ${key}`);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveTab(tab, options = {}) {
|
||||||
|
state.activeTab = tab;
|
||||||
|
if (tab === "collection" && options.collectionKey) state.collection.key = options.collectionKey;
|
||||||
|
if (tab === "podcast" && options.podcastKind) state.podcast.kind = options.podcastKind;
|
||||||
|
|
||||||
|
for (const button of el.tabs) {
|
||||||
|
const buttonTab = button.dataset.tab;
|
||||||
|
const isCollection = buttonTab === "collection";
|
||||||
|
const isPodcast = buttonTab === "podcast";
|
||||||
|
const isActive = isCollection
|
||||||
|
? tab === "collection" && button.dataset.collectionKey === state.collection.key
|
||||||
|
: isPodcast
|
||||||
|
? tab === "podcast" && button.dataset.podcastKind === state.podcast.kind
|
||||||
|
: buttonTab === tab;
|
||||||
|
button.classList.toggle("active", isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, panel] of Object.entries(el.panels)) {
|
||||||
|
panel.classList.toggle("active", key === tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab === "collection") {
|
||||||
|
void loadCollection(state.collection.key).catch((error) => notify(String(error.message || error)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab === "podcast") {
|
||||||
|
void loadPodcastFiles(state.podcast.kind).catch((error) => notify(String(error.message || error)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyBlogDraft() {
|
||||||
|
return {
|
||||||
|
slug: "",
|
||||||
|
title: "",
|
||||||
|
category: "General",
|
||||||
|
date: "",
|
||||||
|
readTime: "",
|
||||||
|
author: "Poyraz Avsever",
|
||||||
|
excerpt: "",
|
||||||
|
coverImage: "/news/design.svg",
|
||||||
|
markdown: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentBlogDraft() {
|
||||||
|
return {
|
||||||
|
slug: el.blogSlug.value.trim(),
|
||||||
|
title: el.blogTitle.value.trim(),
|
||||||
|
category: el.blogCategory.value.trim(),
|
||||||
|
date: el.blogDate.value.trim(),
|
||||||
|
readTime: el.blogReadTime.value.trim(),
|
||||||
|
author: el.blogAuthor.value.trim(),
|
||||||
|
excerpt: el.blogExcerpt.value.trim(),
|
||||||
|
coverImage: el.blogCoverImage.value.trim(),
|
||||||
|
markdown: el.blogEditor.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillBlogForm(post) {
|
||||||
|
const draft = post || emptyBlogDraft();
|
||||||
|
el.blogSlug.value = draft.slug || "";
|
||||||
|
el.blogTitle.value = draft.title || "";
|
||||||
|
el.blogCategory.value = draft.category || "General";
|
||||||
|
el.blogDate.value = draft.date || "";
|
||||||
|
el.blogReadTime.value = draft.readTime || "";
|
||||||
|
el.blogAuthor.value = draft.author || "Poyraz Avsever";
|
||||||
|
el.blogExcerpt.value = draft.excerpt || "";
|
||||||
|
el.blogCoverImage.value = draft.coverImage || "/news/design.svg";
|
||||||
|
el.blogEditor.value = draft.markdown || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPostCards(root, items, selectedSlug, onEdit, onDelete) {
|
||||||
|
root.innerHTML = "";
|
||||||
|
for (const item of items) {
|
||||||
|
const card = document.createElement("article");
|
||||||
|
card.className = `blog-card ${selectedSlug === item.slug ? "active" : ""}`;
|
||||||
|
|
||||||
|
const title = document.createElement("div");
|
||||||
|
title.className = "title";
|
||||||
|
title.textContent = item.title || item.slug;
|
||||||
|
|
||||||
|
const meta = document.createElement("div");
|
||||||
|
meta.className = "meta";
|
||||||
|
meta.textContent = item.category ? `${item.category} - ${item.date || "-"}` : item.date || "-";
|
||||||
|
|
||||||
|
const slug = document.createElement("div");
|
||||||
|
slug.className = "meta";
|
||||||
|
slug.textContent = item.slug;
|
||||||
|
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "row";
|
||||||
|
|
||||||
|
const editBtn = document.createElement("button");
|
||||||
|
editBtn.className = "btn btn-ghost";
|
||||||
|
editBtn.textContent = "Edit";
|
||||||
|
editBtn.addEventListener("click", () => onEdit(item.slug));
|
||||||
|
|
||||||
|
const deleteBtn = document.createElement("button");
|
||||||
|
deleteBtn.className = "btn btn-danger";
|
||||||
|
deleteBtn.textContent = "Delete";
|
||||||
|
deleteBtn.addEventListener("click", () => onDelete(item.slug));
|
||||||
|
|
||||||
|
row.append(editBtn, deleteBtn);
|
||||||
|
card.append(title, meta, slug, row);
|
||||||
|
root.appendChild(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBlogFiles() {
|
||||||
|
state.blogPosts = await window.panelAPI.blog.list();
|
||||||
|
renderPostCards(el.blogCardList, state.blogPosts, state.selectedBlogSlug, selectBlog, (slug) => void removeBlogFile(slug));
|
||||||
|
if (!state.selectedBlogSlug && state.blogPosts.length) selectBlog(state.blogPosts[0].slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectBlog(slug) {
|
||||||
|
const post = state.blogPosts.find((item) => item.slug === slug);
|
||||||
|
if (!post) return;
|
||||||
|
state.selectedBlogSlug = slug;
|
||||||
|
state.blogOriginalSlug = slug;
|
||||||
|
el.blogEditorTitle.textContent = `Edit Blog: ${slug}`;
|
||||||
|
fillBlogForm(post);
|
||||||
|
renderPostCards(el.blogCardList, state.blogPosts, state.selectedBlogSlug, selectBlog, (value) => void removeBlogFile(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBlogFile() {
|
||||||
|
state.selectedBlogSlug = "";
|
||||||
|
state.blogOriginalSlug = "";
|
||||||
|
el.blogEditorTitle.textContent = "Create Blog Post";
|
||||||
|
fillBlogForm(emptyBlogDraft());
|
||||||
|
renderPostCards(el.blogCardList, state.blogPosts, state.selectedBlogSlug, selectBlog, (slug) => void removeBlogFile(slug));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveBlogFile() {
|
||||||
|
const post = currentBlogDraft();
|
||||||
|
if (!post.slug || !post.title) {
|
||||||
|
notify("Slug and title are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await window.panelAPI.blog.upsert({
|
||||||
|
originalSlug: state.blogOriginalSlug || undefined,
|
||||||
|
post,
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadBlogFiles();
|
||||||
|
state.selectedBlogSlug = result.slug;
|
||||||
|
state.blogOriginalSlug = result.slug;
|
||||||
|
selectBlog(result.slug);
|
||||||
|
notify(`Saved blog post: ${result.slug}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeBlogFile(slugArg) {
|
||||||
|
const targetSlug = slugArg || state.selectedBlogSlug;
|
||||||
|
if (!targetSlug) return;
|
||||||
|
if (!window.confirm(`Delete blog post ${targetSlug}?`)) return;
|
||||||
|
|
||||||
|
await window.panelAPI.blog.delete(targetSlug);
|
||||||
|
state.selectedBlogSlug = "";
|
||||||
|
state.blogOriginalSlug = "";
|
||||||
|
createBlogFile();
|
||||||
|
await loadBlogFiles();
|
||||||
|
notify("Blog post deleted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyPodcastDraft(kind = state.podcast.kind) {
|
||||||
|
return { slug: "", title: "", date: "", youtubeUrl: "", spotifyUrl: "", podcast: kind, markdown: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillPodcastForm(episode) {
|
||||||
|
const draft = episode || emptyPodcastDraft();
|
||||||
|
el.podcastSlug.value = draft.slug || "";
|
||||||
|
el.podcastTitle.value = draft.title || "";
|
||||||
|
el.podcastDate.value = draft.date || "";
|
||||||
|
el.podcastYoutubeUrl.value = draft.youtubeUrl || "";
|
||||||
|
el.podcastSpotifyUrl.value = draft.spotifyUrl || "";
|
||||||
|
el.podcastEditor.value = draft.markdown || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPodcast(slug) {
|
||||||
|
const episode = state.podcast.episodes.find((item) => item.slug === slug);
|
||||||
|
if (!episode) return;
|
||||||
|
state.podcast.selectedSlug = slug;
|
||||||
|
state.podcast.originalSlug = slug;
|
||||||
|
el.podcastEditorTitle.textContent = `Edit Episode: ${slug}`;
|
||||||
|
fillPodcastForm(episode);
|
||||||
|
renderPostCards(el.podcastCardList, state.podcast.episodes, state.podcast.selectedSlug, selectPodcast, (value) => void removePodcastFile(value));
|
||||||
|
}
|
||||||
|
function createPodcastFile() {
|
||||||
|
state.podcast.selectedSlug = "";
|
||||||
|
state.podcast.originalSlug = "";
|
||||||
|
el.podcastEditorTitle.textContent = `Create Episode (${PODCAST_LABELS[state.podcast.kind] || state.podcast.kind})`;
|
||||||
|
fillPodcastForm(emptyPodcastDraft(state.podcast.kind));
|
||||||
|
renderPostCards(el.podcastCardList, state.podcast.episodes, state.podcast.selectedSlug, selectPodcast, (slug) => void removePodcastFile(slug));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPodcastFiles(kind = state.podcast.kind) {
|
||||||
|
state.podcast.kind = kind;
|
||||||
|
state.podcast.episodes = await window.panelAPI.podcast.list(kind);
|
||||||
|
el.podcastSidebarTitle.textContent = PODCAST_LABELS[kind] || "Podcast";
|
||||||
|
renderPostCards(el.podcastCardList, state.podcast.episodes, state.podcast.selectedSlug, selectPodcast, (value) => void removePodcastFile(value));
|
||||||
|
|
||||||
|
const selected = state.podcast.episodes.find((item) => item.slug === state.podcast.selectedSlug);
|
||||||
|
if (selected) {
|
||||||
|
selectPodcast(selected.slug);
|
||||||
|
} else if (state.podcast.episodes.length > 0) {
|
||||||
|
selectPodcast(state.podcast.episodes[0].slug);
|
||||||
|
} else {
|
||||||
|
createPodcastFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentPodcastDraft() {
|
||||||
|
return {
|
||||||
|
slug: el.podcastSlug.value.trim(),
|
||||||
|
title: el.podcastTitle.value.trim(),
|
||||||
|
date: el.podcastDate.value.trim(),
|
||||||
|
youtubeUrl: el.podcastYoutubeUrl.value.trim(),
|
||||||
|
spotifyUrl: el.podcastSpotifyUrl.value.trim(),
|
||||||
|
podcast: state.podcast.kind,
|
||||||
|
markdown: el.podcastEditor.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function savePodcastFile() {
|
||||||
|
const episode = currentPodcastDraft();
|
||||||
|
if (!episode.slug || !episode.title) {
|
||||||
|
notify("Slug and title are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await window.panelAPI.podcast.upsert({
|
||||||
|
kind: state.podcast.kind,
|
||||||
|
originalSlug: state.podcast.originalSlug || undefined,
|
||||||
|
episode,
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadPodcastFiles(state.podcast.kind);
|
||||||
|
state.podcast.selectedSlug = result.slug;
|
||||||
|
state.podcast.originalSlug = result.slug;
|
||||||
|
selectPodcast(result.slug);
|
||||||
|
notify(`Saved podcast episode: ${result.slug}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removePodcastFile(slugArg) {
|
||||||
|
const targetSlug = slugArg || state.podcast.selectedSlug;
|
||||||
|
if (!targetSlug) return;
|
||||||
|
if (!window.confirm(`Delete podcast episode ${targetSlug}?`)) return;
|
||||||
|
|
||||||
|
await window.panelAPI.podcast.delete({ kind: state.podcast.kind, slug: targetSlug });
|
||||||
|
state.podcast.selectedSlug = "";
|
||||||
|
state.podcast.originalSlug = "";
|
||||||
|
createPodcastFile();
|
||||||
|
await loadPodcastFiles(state.podcast.kind);
|
||||||
|
notify("Podcast episode deleted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeCollectionItem(config, rawItem) {
|
||||||
|
if (typeof config.deserializeItem === "function") return config.deserializeItem(rawItem);
|
||||||
|
if (rawItem && typeof rawItem === "object" && !Array.isArray(rawItem)) return { ...rawItem };
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeCollectionItem(config, item) {
|
||||||
|
if (typeof config.serializeItem === "function") return config.serializeItem(item);
|
||||||
|
return { ...item };
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyCollectionItem(config) {
|
||||||
|
const base = {};
|
||||||
|
for (const field of config.fields) {
|
||||||
|
if (field.type !== "number") base[field.key] = field.defaultValue || "";
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCollectionHeader(config) {
|
||||||
|
el.collectionSidebarTitle.textContent = config.label;
|
||||||
|
el.collectionHelper.textContent = `${config.fileName} -> ${config.exportName}`;
|
||||||
|
el.newCollectionItem.textContent = `New ${config.itemLabel}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCollectionForm(config) {
|
||||||
|
state.collection.formInputs = {};
|
||||||
|
el.collectionFormGrid.innerHTML = "";
|
||||||
|
|
||||||
|
for (const field of config.fields) {
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
if (field.full) wrapper.className = "full";
|
||||||
|
|
||||||
|
const label = document.createElement("label");
|
||||||
|
const fieldId = `collection-${state.collection.key}-${field.key}`;
|
||||||
|
label.setAttribute("for", fieldId);
|
||||||
|
label.textContent = field.label;
|
||||||
|
|
||||||
|
let input;
|
||||||
|
if (field.type === "textarea") {
|
||||||
|
input = document.createElement("textarea");
|
||||||
|
input.className = "small-editor";
|
||||||
|
input.spellcheck = false;
|
||||||
|
} else {
|
||||||
|
input = document.createElement("input");
|
||||||
|
input.type = field.type === "number" ? "number" : field.type === "url" ? "url" : "text";
|
||||||
|
}
|
||||||
|
|
||||||
|
input.id = fieldId;
|
||||||
|
if (field.placeholder) input.placeholder = field.placeholder;
|
||||||
|
if (field.required) input.required = true;
|
||||||
|
if (field.type === "number") {
|
||||||
|
if (typeof field.min === "number") input.min = String(field.min);
|
||||||
|
if (typeof field.max === "number") input.max = String(field.max);
|
||||||
|
if (typeof field.step === "number") input.step = String(field.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.collection.formInputs[field.key] = input;
|
||||||
|
wrapper.append(label, input);
|
||||||
|
el.collectionFormGrid.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillCollectionForm(config, item) {
|
||||||
|
const source = item || emptyCollectionItem(config);
|
||||||
|
for (const field of config.fields) {
|
||||||
|
const input = state.collection.formInputs[field.key];
|
||||||
|
if (!input) continue;
|
||||||
|
const value = source[field.key];
|
||||||
|
input.value = value === undefined || value === null ? "" : String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCollectionForm(config) {
|
||||||
|
const values = {};
|
||||||
|
|
||||||
|
for (const field of config.fields) {
|
||||||
|
const input = state.collection.formInputs[field.key];
|
||||||
|
if (!input) continue;
|
||||||
|
|
||||||
|
if (field.type === "number") {
|
||||||
|
const value = input.value.trim();
|
||||||
|
if (!value) {
|
||||||
|
if (field.required) throw new Error(`${field.label} is required.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const parsed = Number(value);
|
||||||
|
if (Number.isNaN(parsed)) throw new Error(`${field.label} must be a number.`);
|
||||||
|
values[field.key] = parsed;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = field.type === "textarea" ? input.value.trim() : input.value.trim();
|
||||||
|
if (!value) {
|
||||||
|
if (field.required) throw new Error(`${field.label} is required.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
values[field.key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCollectionCards(config) {
|
||||||
|
el.collectionCardList.innerHTML = "";
|
||||||
|
|
||||||
|
state.collection.items.forEach((item, index) => {
|
||||||
|
const summary = typeof config.card === "function"
|
||||||
|
? config.card(item, index)
|
||||||
|
: { title: item.title || item.id || `Item ${index + 1}`, meta: [] };
|
||||||
|
|
||||||
|
const card = document.createElement("article");
|
||||||
|
card.className = `blog-card ${state.collection.selectedIndex === index ? "active" : ""}`;
|
||||||
|
|
||||||
|
const title = document.createElement("div");
|
||||||
|
title.className = "title";
|
||||||
|
title.textContent = summary.title || `Item ${index + 1}`;
|
||||||
|
card.appendChild(title);
|
||||||
|
|
||||||
|
for (const metaText of summary.meta || []) {
|
||||||
|
if (!metaText) continue;
|
||||||
|
const meta = document.createElement("div");
|
||||||
|
meta.className = "meta";
|
||||||
|
meta.textContent = String(metaText);
|
||||||
|
card.appendChild(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.footer) {
|
||||||
|
const footer = document.createElement("div");
|
||||||
|
footer.className = "meta";
|
||||||
|
footer.textContent = String(summary.footer);
|
||||||
|
card.appendChild(footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "row";
|
||||||
|
|
||||||
|
const editBtn = document.createElement("button");
|
||||||
|
editBtn.className = "btn btn-ghost";
|
||||||
|
editBtn.textContent = "Edit";
|
||||||
|
editBtn.addEventListener("click", () => selectCollectionItem(index));
|
||||||
|
|
||||||
|
const deleteBtn = document.createElement("button");
|
||||||
|
deleteBtn.className = "btn btn-danger";
|
||||||
|
deleteBtn.textContent = "Delete";
|
||||||
|
deleteBtn.addEventListener("click", () => void removeCollectionItem(index));
|
||||||
|
|
||||||
|
row.append(editBtn, deleteBtn);
|
||||||
|
card.appendChild(row);
|
||||||
|
el.collectionCardList.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCollectionCreateMode(config) {
|
||||||
|
state.collection.selectedIndex = null;
|
||||||
|
state.collection.editingIndex = null;
|
||||||
|
el.collectionEditorTitle.textContent = `Create ${config.itemLabel}`;
|
||||||
|
fillCollectionForm(config, emptyCollectionItem(config));
|
||||||
|
renderCollectionCards(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCollectionItem(index) {
|
||||||
|
const config = getCollectionConfig();
|
||||||
|
const item = state.collection.items[index];
|
||||||
|
if (!item) return;
|
||||||
|
state.collection.selectedIndex = index;
|
||||||
|
state.collection.editingIndex = index;
|
||||||
|
el.collectionEditorTitle.textContent = `Edit ${config.itemLabel} #${index + 1}`;
|
||||||
|
fillCollectionForm(config, item);
|
||||||
|
renderCollectionCards(config);
|
||||||
|
}
|
||||||
|
async function loadCollection(key = state.collection.key, options = {}) {
|
||||||
|
const config = getCollectionConfig(key);
|
||||||
|
const file = await window.panelAPI.data.read(config.fileName);
|
||||||
|
const targetExport = file.exports.find((entry) => entry.name === config.exportName);
|
||||||
|
|
||||||
|
if (!targetExport) throw new Error(`Export ${config.exportName} not found in ${config.fileName}.`);
|
||||||
|
if (targetExport.parseError) throw new Error(targetExport.parseError);
|
||||||
|
if (!Array.isArray(targetExport.value)) throw new Error(`Export ${config.exportName} must be array.`);
|
||||||
|
|
||||||
|
state.collection.key = key;
|
||||||
|
state.collection.items = targetExport.value.map((item) => deserializeCollectionItem(config, item));
|
||||||
|
|
||||||
|
renderCollectionHeader(config);
|
||||||
|
renderCollectionForm(config);
|
||||||
|
|
||||||
|
let selectedIndex = null;
|
||||||
|
if (typeof options.selectIndex === "number") {
|
||||||
|
selectedIndex = options.selectIndex;
|
||||||
|
} else if (options.keepSelection && typeof state.collection.selectedIndex === "number") {
|
||||||
|
selectedIndex = state.collection.selectedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof selectedIndex === "number" && selectedIndex >= 0 && selectedIndex < state.collection.items.length) {
|
||||||
|
state.collection.selectedIndex = selectedIndex;
|
||||||
|
state.collection.editingIndex = selectedIndex;
|
||||||
|
el.collectionEditorTitle.textContent = `Edit ${config.itemLabel} #${selectedIndex + 1}`;
|
||||||
|
fillCollectionForm(config, state.collection.items[selectedIndex]);
|
||||||
|
renderCollectionCards(config);
|
||||||
|
} else {
|
||||||
|
startCollectionCreateMode(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCollectionItem() {
|
||||||
|
const config = getCollectionConfig();
|
||||||
|
const draft = readCollectionForm(config);
|
||||||
|
|
||||||
|
const next = [...state.collection.items];
|
||||||
|
let selectedIndex;
|
||||||
|
|
||||||
|
if (typeof state.collection.editingIndex === "number") {
|
||||||
|
selectedIndex = state.collection.editingIndex;
|
||||||
|
next[selectedIndex] = draft;
|
||||||
|
} else {
|
||||||
|
next.push(draft);
|
||||||
|
selectedIndex = next.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.panelAPI.data.updateExport({
|
||||||
|
fileName: config.fileName,
|
||||||
|
exportName: config.exportName,
|
||||||
|
value: next.map((item) => serializeCollectionItem(config, item)),
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadCollection(state.collection.key, { selectIndex: selectedIndex });
|
||||||
|
notify(`Saved ${config.itemLabel.toLowerCase()} in ${config.label}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeCollectionItem(indexArg) {
|
||||||
|
const config = getCollectionConfig();
|
||||||
|
const targetIndex = typeof indexArg === "number" ? indexArg : state.collection.selectedIndex;
|
||||||
|
if (typeof targetIndex !== "number" || !state.collection.items[targetIndex]) return;
|
||||||
|
if (!window.confirm(`Delete ${config.itemLabel} #${targetIndex + 1}?`)) return;
|
||||||
|
|
||||||
|
const next = state.collection.items.filter((_, index) => index !== targetIndex);
|
||||||
|
await window.panelAPI.data.updateExport({
|
||||||
|
fileName: config.fileName,
|
||||||
|
exportName: config.exportName,
|
||||||
|
value: next.map((item) => serializeCollectionItem(config, item)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedIndex = next.length ? Math.max(0, targetIndex - 1) : undefined;
|
||||||
|
await loadCollection(state.collection.key, { selectIndex: selectedIndex });
|
||||||
|
notify(`${config.itemLabel} deleted from ${config.label}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMediaFolders() {
|
||||||
|
state.folders = await window.panelAPI.media.listFolders();
|
||||||
|
el.folderSelect.innerHTML = "";
|
||||||
|
|
||||||
|
for (const folder of state.folders) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = folder;
|
||||||
|
option.textContent = folder || "/";
|
||||||
|
el.folderSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.selectedFolder || !state.folders.includes(state.selectedFolder)) {
|
||||||
|
state.selectedFolder = state.folders[0] ?? "";
|
||||||
|
}
|
||||||
|
el.folderSelect.value = state.selectedFolder;
|
||||||
|
await loadMediaFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMediaFiles() {
|
||||||
|
const files = await window.panelAPI.media.listFiles(state.selectedFolder);
|
||||||
|
el.mediaFiles.innerHTML = "";
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const item = document.createElement("article");
|
||||||
|
item.className = "media-item";
|
||||||
|
|
||||||
|
if (file.isImage) {
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = `file:///${file.absolutePath.replaceAll("\\", "/")}`;
|
||||||
|
img.alt = file.name;
|
||||||
|
item.appendChild(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = document.createElement("div");
|
||||||
|
meta.className = "meta";
|
||||||
|
|
||||||
|
const name = document.createElement("div");
|
||||||
|
name.className = "name";
|
||||||
|
name.textContent = file.relativePath;
|
||||||
|
|
||||||
|
const del = document.createElement("button");
|
||||||
|
del.className = "btn btn-danger";
|
||||||
|
del.textContent = "Delete";
|
||||||
|
del.addEventListener("click", async () => {
|
||||||
|
if (!window.confirm(`Delete ${file.relativePath}?`)) return;
|
||||||
|
await window.panelAPI.media.deleteFile(file.relativePath);
|
||||||
|
await loadMediaFiles();
|
||||||
|
notify(`Deleted: ${file.relativePath}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
meta.append(name, del);
|
||||||
|
item.appendChild(meta);
|
||||||
|
el.mediaFiles.appendChild(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createFolder() {
|
||||||
|
const folder = el.newFolder.value.trim();
|
||||||
|
if (!folder) return;
|
||||||
|
await window.panelAPI.media.createFolder(folder);
|
||||||
|
el.newFolder.value = "";
|
||||||
|
await loadMediaFolders();
|
||||||
|
state.selectedFolder = folder.replaceAll("\\", "/").replace(/^\/+/, "");
|
||||||
|
el.folderSelect.value = state.selectedFolder;
|
||||||
|
await loadMediaFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadFiles() {
|
||||||
|
await window.panelAPI.media.upload(state.selectedFolder);
|
||||||
|
await loadMediaFiles();
|
||||||
|
notify("Upload completed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshPublishStatus() {
|
||||||
|
const status = await window.panelAPI.publish.status();
|
||||||
|
el.gitStatus.textContent = status.length
|
||||||
|
? status.join("\n")
|
||||||
|
: "No pending changes in data/, public/ or content/.";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function publishNow() {
|
||||||
|
const result = await window.panelAPI.publish.run(el.commitMessage.value);
|
||||||
|
el.publishLog.textContent = JSON.stringify(result, null, 2);
|
||||||
|
await refreshPublishStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindEvents() {
|
||||||
|
for (const button of el.tabs) {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const tab = button.dataset.tab;
|
||||||
|
if (tab === "collection") {
|
||||||
|
setActiveTab("collection", { collectionKey: button.dataset.collectionKey });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tab === "podcast") {
|
||||||
|
setActiveTab("podcast", { podcastKind: button.dataset.podcastKind });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setActiveTab(tab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
el.refreshBlogFiles.addEventListener("click", () => void loadBlogFiles());
|
||||||
|
el.newBlogFile.addEventListener("click", () => createBlogFile());
|
||||||
|
el.deleteBlogFile.addEventListener("click", () => void removeBlogFile().catch((error) => notify(String(error.message || error))));
|
||||||
|
el.saveBlogFile.addEventListener("click", () => void saveBlogFile().catch((error) => notify(String(error.message || error))));
|
||||||
|
|
||||||
|
el.refreshCollection.addEventListener("click", () =>
|
||||||
|
void loadCollection(state.collection.key, { keepSelection: true }).catch((error) => notify(String(error.message || error)))
|
||||||
|
);
|
||||||
|
el.newCollectionItem.addEventListener("click", () => startCollectionCreateMode(getCollectionConfig()));
|
||||||
|
el.deleteCollectionItem.addEventListener("click", () => void removeCollectionItem().catch((error) => notify(String(error.message || error))));
|
||||||
|
el.saveCollectionItem.addEventListener("click", () => void saveCollectionItem().catch((error) => notify(String(error.message || error))));
|
||||||
|
|
||||||
|
el.refreshPodcastFiles.addEventListener("click", () => void loadPodcastFiles(state.podcast.kind).catch((error) => notify(String(error.message || error))));
|
||||||
|
el.newPodcastFile.addEventListener("click", () => createPodcastFile());
|
||||||
|
el.deletePodcastFile.addEventListener("click", () => void removePodcastFile().catch((error) => notify(String(error.message || error))));
|
||||||
|
el.savePodcastFile.addEventListener("click", () => void savePodcastFile().catch((error) => notify(String(error.message || error))));
|
||||||
|
|
||||||
|
el.folderSelect.addEventListener("change", () => {
|
||||||
|
state.selectedFolder = el.folderSelect.value;
|
||||||
|
void loadMediaFiles();
|
||||||
|
});
|
||||||
|
el.refreshMedia.addEventListener("click", () => void loadMediaFolders());
|
||||||
|
el.createFolder.addEventListener("click", () => void createFolder().catch((error) => notify(String(error.message || error))));
|
||||||
|
el.uploadFile.addEventListener("click", () => void uploadFiles().catch((error) => notify(String(error.message || error))));
|
||||||
|
|
||||||
|
el.refreshStatus.addEventListener("click", () => void refreshPublishStatus());
|
||||||
|
el.publishBtn.addEventListener("click", () => void publishNow().catch((error) => notify(String(error.message || error))));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
bindEvents();
|
||||||
|
await loadBlogFiles();
|
||||||
|
if (!state.selectedBlogSlug) createBlogFile();
|
||||||
|
await loadCollection(state.collection.key);
|
||||||
|
await loadPodcastFiles(state.podcast.kind);
|
||||||
|
await loadMediaFolders();
|
||||||
|
await refreshPublishStatus();
|
||||||
|
setActiveTab("blog");
|
||||||
|
}
|
||||||
|
|
||||||
|
void init().catch((error) => {
|
||||||
|
notify(`Initialization error: ${String(error.message || error)}`);
|
||||||
|
});
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
|
||||||
|
export const COLLECTION_CONFIGS = {
|
||||||
|
announcement: {
|
||||||
|
label: "Announcement",
|
||||||
|
itemLabel: "Announcement",
|
||||||
|
fileName: "site-settings.ts",
|
||||||
|
exportName: "ANNOUNCEMENT_ITEMS",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "main-announcement" },
|
||||||
|
{ key: "text", label: "Text", type: "textarea", full: true, required: true, placeholder: "Bu hafta yeni bilesenler eklendi!" },
|
||||||
|
{ key: "actionLabel", label: "Action Label", required: true, placeholder: "Incele ->" },
|
||||||
|
{ key: "actionHref", label: "Action Link", type: "url", required: true, placeholder: "https://..." },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.id || "Announcement",
|
||||||
|
meta: [item.actionLabel, item.actionHref],
|
||||||
|
footer: item.text,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
bookmarks: {
|
||||||
|
label: "Bookmarks",
|
||||||
|
itemLabel: "Bookmark",
|
||||||
|
fileName: "bookmarks.ts",
|
||||||
|
exportName: "BOOKMARKS",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "bookmark-poyraz-ui" },
|
||||||
|
{ key: "title", label: "Title", required: true, placeholder: "Poyraz UI" },
|
||||||
|
{ key: "href", label: "Link", type: "url", required: true, placeholder: "https://..." },
|
||||||
|
{ key: "tag", label: "Tag", required: true, placeholder: "UI Kit" },
|
||||||
|
{ key: "description", label: "Description", type: "textarea", full: true, required: true, placeholder: "Short description" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.title || item.id || "Untitled bookmark",
|
||||||
|
meta: [item.tag, item.href],
|
||||||
|
footer: item.description,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
certificates: {
|
||||||
|
label: "Certificates",
|
||||||
|
itemLabel: "Certificate",
|
||||||
|
fileName: "certificates.ts",
|
||||||
|
exportName: "certificates",
|
||||||
|
fields: [
|
||||||
|
{ key: "name", label: "Name", required: true, placeholder: "Certificate name" },
|
||||||
|
{ key: "organization", label: "Organization", required: true, placeholder: "Udemy" },
|
||||||
|
{ key: "date", label: "Date", required: true, placeholder: "June 2025" },
|
||||||
|
{ key: "category", label: "Category", required: true, placeholder: "mobile" },
|
||||||
|
{ key: "image", label: "Image Path", required: true, placeholder: "/certificates/reactnative.png" },
|
||||||
|
{ key: "description", label: "Description", type: "textarea", full: true, required: true, placeholder: "What this certificate represents" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.name || "Untitled certificate",
|
||||||
|
meta: [item.organization, item.date, item.category],
|
||||||
|
footer: item.image,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
education: {
|
||||||
|
label: "Education",
|
||||||
|
itemLabel: "Education Item",
|
||||||
|
fileName: "education.ts",
|
||||||
|
exportName: "EDUCATION",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "ostim-university" },
|
||||||
|
{ key: "title", label: "Title", required: true, placeholder: "Software Engineering - 2nd Year Student" },
|
||||||
|
{ key: "institution", label: "Institution", required: true, placeholder: "OSTIM Technical University" },
|
||||||
|
{ key: "period", label: "Period", required: true, placeholder: "2024 - Present" },
|
||||||
|
{ key: "description", label: "Description", type: "textarea", full: true, required: true, placeholder: "Education summary" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.title || item.id || "Education",
|
||||||
|
meta: [item.institution, item.period],
|
||||||
|
footer: item.description,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
experience: {
|
||||||
|
label: "Experience",
|
||||||
|
itemLabel: "Experience Item",
|
||||||
|
fileName: "experience.ts",
|
||||||
|
exportName: "EXPERIENCE",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "omedya-part-time" },
|
||||||
|
{ key: "role", label: "Role", required: true, placeholder: "Fullstack Developer" },
|
||||||
|
{ key: "company", label: "Company", required: true, placeholder: "Omedya Bilisim A.S" },
|
||||||
|
{ key: "period", label: "Period", required: true, placeholder: "August 2025 - Present" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.role || item.id || "Experience",
|
||||||
|
meta: [item.company, item.period],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
references: {
|
||||||
|
label: "References",
|
||||||
|
itemLabel: "Reference",
|
||||||
|
fileName: "references.ts",
|
||||||
|
exportName: "REFERENCES",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "ali-korkmaz" },
|
||||||
|
{ key: "author", label: "Author", required: true, placeholder: "Ali Korkmaz" },
|
||||||
|
{ key: "role", label: "Role", required: true, placeholder: "Musteri - 2025" },
|
||||||
|
{ key: "avatar", label: "Avatar Path", required: true, placeholder: "/avatars/ali.png" },
|
||||||
|
{ key: "rating", label: "Rating", type: "number", min: 1, max: 5, step: 1, placeholder: "5" },
|
||||||
|
{ key: "profileHref", label: "Profile Link", type: "url", placeholder: "https://linkedin.com/in/..." },
|
||||||
|
{ key: "quote", label: "Quote", type: "textarea", full: true, required: true, placeholder: "Reference quote" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.author || item.id || "Reference",
|
||||||
|
meta: [item.role, item.rating ? `Rating: ${item.rating}` : ""],
|
||||||
|
footer: item.quote,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
projectsMobile: {
|
||||||
|
label: "Projects Mobile",
|
||||||
|
itemLabel: "Mobile Project",
|
||||||
|
fileName: "projects.ts",
|
||||||
|
exportName: "MOBILE_APPS",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "mobile-habit-flow" },
|
||||||
|
{ key: "title", label: "Title", required: true, placeholder: "Habit Flow" },
|
||||||
|
{ key: "badge", label: "Badge", placeholder: "React Native" },
|
||||||
|
{ key: "image", label: "Image Path", required: true, placeholder: "/images/hero1.png" },
|
||||||
|
{ key: "href", label: "Link", type: "url", placeholder: "https://..." },
|
||||||
|
{ key: "description", label: "Description", type: "textarea", full: true, required: true, placeholder: "Project summary" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.title || item.id || "Project",
|
||||||
|
meta: [item.badge, item.href],
|
||||||
|
footer: item.description,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
projectsWeb: {
|
||||||
|
label: "Projects Web",
|
||||||
|
itemLabel: "Web Project",
|
||||||
|
fileName: "projects.ts",
|
||||||
|
exportName: "WEB_APPS",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "web-portfolio" },
|
||||||
|
{ key: "title", label: "Title", required: true, placeholder: "Personal Portfolio" },
|
||||||
|
{ key: "badge", label: "Badge", placeholder: "Next.js" },
|
||||||
|
{ key: "image", label: "Image Path", required: true, placeholder: "/news/design.svg" },
|
||||||
|
{ key: "href", label: "Link", type: "url", placeholder: "https://..." },
|
||||||
|
{ key: "description", label: "Description", type: "textarea", full: true, required: true, placeholder: "Project summary" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.title || item.id || "Project",
|
||||||
|
meta: [item.badge, item.href],
|
||||||
|
footer: item.description,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
projectsFigma: {
|
||||||
|
label: "Projects Figma",
|
||||||
|
itemLabel: "Figma Template",
|
||||||
|
fileName: "projects.ts",
|
||||||
|
exportName: "FIGMA_TEMPLATES",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "figma-minimal-saas" },
|
||||||
|
{ key: "title", label: "Title", required: true, placeholder: "Minimal SaaS Landing Kit" },
|
||||||
|
{ key: "badge", label: "Badge", placeholder: "Figma" },
|
||||||
|
{ key: "image", label: "Image Path", required: true, placeholder: "/news/design.svg" },
|
||||||
|
{ key: "href", label: "Link", type: "url", placeholder: "https://..." },
|
||||||
|
{ key: "description", label: "Description", type: "textarea", full: true, required: true, placeholder: "Project summary" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.title || item.id || "Project",
|
||||||
|
meta: [item.badge, item.href],
|
||||||
|
footer: item.description,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
community: {
|
||||||
|
label: "Community",
|
||||||
|
itemLabel: "Community Item",
|
||||||
|
fileName: "volunteer-community.ts",
|
||||||
|
exportName: "VOLUNTEER_COMMUNITY_ITEMS",
|
||||||
|
fields: [
|
||||||
|
{ key: "id", label: "ID", required: true, placeholder: "youtube" },
|
||||||
|
{ key: "title", label: "Title", required: true, placeholder: "YouTube" },
|
||||||
|
{ key: "timeline", label: "Timeline", required: true, placeholder: "2025 - Present" },
|
||||||
|
{ key: "link", label: "Link", type: "url", placeholder: "https://..." },
|
||||||
|
{ key: "focus", label: "Focus", type: "textarea", full: true, required: true, placeholder: "Community contribution summary" },
|
||||||
|
],
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.title || item.id || "Community",
|
||||||
|
meta: [item.timeline, item.link],
|
||||||
|
footer: item.focus,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
youtube: {
|
||||||
|
label: "YouTube Videos",
|
||||||
|
itemLabel: "Video Link",
|
||||||
|
fileName: "youtube-videos.ts",
|
||||||
|
exportName: "YOUTUBE_VIDEO_LINKS",
|
||||||
|
fields: [
|
||||||
|
{ key: "url", label: "Video URL", type: "url", required: true, placeholder: "https://www.youtube.com/watch?v=..." },
|
||||||
|
],
|
||||||
|
deserializeItem: (raw) => ({ url: String(raw || "") }),
|
||||||
|
serializeItem: (item) => String(item.url || "").trim(),
|
||||||
|
card: (item) => ({
|
||||||
|
title: item.url || "YouTube Link",
|
||||||
|
meta: [],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PODCAST_LABELS = {
|
||||||
|
yazilim: "Podcast Yazilim",
|
||||||
|
"masa-basi": "Podcast Masa Basi",
|
||||||
|
};
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Portfolio Data Panel</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="topbar">
|
||||||
|
<div>
|
||||||
|
<h1>Portfolio Data Panel</h1>
|
||||||
|
<p>Manage blog posts, dashboard collections, media files, and publish updates.</p>
|
||||||
|
</div>
|
||||||
|
<div class="chip">Electron Monorepo</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="tabs">
|
||||||
|
<button class="tab-btn active" data-tab="blog">Blog</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="announcement">Announcement</button>
|
||||||
|
<button class="tab-btn" data-tab="podcast" data-podcast-kind="yazilim">Podcast Yazilim</button>
|
||||||
|
<button class="tab-btn" data-tab="podcast" data-podcast-kind="masa-basi">Podcast Masa Basi</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="bookmarks">Bookmarks</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="certificates">Certificates</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="education">Education</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="experience">Experience</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="references">References</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="projectsMobile">Projects Mobile</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="projectsWeb">Projects Web</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="projectsFigma">Projects Figma</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="community">Community</button>
|
||||||
|
<button class="tab-btn" data-tab="collection" data-collection-key="youtube">YouTube</button>
|
||||||
|
<button class="tab-btn" data-tab="media">Media</button>
|
||||||
|
<button class="tab-btn" data-tab="publish">Publish</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section id="tab-blog" class="tab-panel active">
|
||||||
|
<div class="split">
|
||||||
|
<aside class="card sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h2>Blog Posts</h2>
|
||||||
|
<button id="refresh-blog-files" class="btn btn-ghost">Refresh</button>
|
||||||
|
</div>
|
||||||
|
<div id="blog-card-list" class="blog-card-list"></div>
|
||||||
|
<button id="new-blog-file" class="btn mt-8">New Blog Post</button>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="card editor">
|
||||||
|
<div class="editor-head">
|
||||||
|
<h2 id="blog-editor-title">Create Blog Post</h2>
|
||||||
|
<div class="row">
|
||||||
|
<button id="delete-blog-file" class="btn btn-danger">Delete</button>
|
||||||
|
<button id="save-blog-file" class="btn">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blog-form-grid">
|
||||||
|
<div>
|
||||||
|
<label for="blog-slug">Slug</label>
|
||||||
|
<input id="blog-slug" placeholder="building-minimal-design-systems" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="blog-title">Title</label>
|
||||||
|
<input id="blog-title" placeholder="Building Minimal Design Systems" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="blog-category">Category</label>
|
||||||
|
<input id="blog-category" placeholder="React" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="blog-date">Date</label>
|
||||||
|
<input id="blog-date" placeholder="March 2026" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="blog-read-time">Read Time</label>
|
||||||
|
<input id="blog-read-time" placeholder="12 min read" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="blog-author">Author</label>
|
||||||
|
<input id="blog-author" placeholder="Poyraz Avsever" />
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label for="blog-cover-image">Cover Image</label>
|
||||||
|
<input id="blog-cover-image" placeholder="/news/design.svg" />
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label for="blog-excerpt">Excerpt</label>
|
||||||
|
<textarea id="blog-excerpt" class="small-editor" spellcheck="false"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label for="blog-editor">Markdown Content</label>
|
||||||
|
<textarea id="blog-editor" spellcheck="false"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="tab-collection" class="tab-panel">
|
||||||
|
<div class="split">
|
||||||
|
<aside class="card sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h2 id="collection-sidebar-title">Bookmarks</h2>
|
||||||
|
<button id="refresh-collection" class="btn btn-ghost">Refresh</button>
|
||||||
|
</div>
|
||||||
|
<div id="collection-card-list" class="blog-card-list"></div>
|
||||||
|
<button id="new-collection-item" class="btn mt-8">New Item</button>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="card editor">
|
||||||
|
<div class="editor-head">
|
||||||
|
<h2 id="collection-editor-title">Create Item</h2>
|
||||||
|
<div class="row">
|
||||||
|
<button id="delete-collection-item" class="btn btn-danger">Delete</button>
|
||||||
|
<button id="save-collection-item" class="btn">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="collection-helper" class="hint">Fill inputs and save to update the related data export.</p>
|
||||||
|
<div id="collection-form-grid" class="blog-form-grid"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="tab-podcast" class="tab-panel">
|
||||||
|
<div class="split">
|
||||||
|
<aside class="card sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h2 id="podcast-sidebar-title">Podcast Episodes</h2>
|
||||||
|
<button id="refresh-podcast-files" class="btn btn-ghost">Refresh</button>
|
||||||
|
</div>
|
||||||
|
<div id="podcast-card-list" class="blog-card-list"></div>
|
||||||
|
<button id="new-podcast-file" class="btn mt-8">New Episode</button>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="card editor">
|
||||||
|
<div class="editor-head">
|
||||||
|
<h2 id="podcast-editor-title">Create Episode</h2>
|
||||||
|
<div class="row">
|
||||||
|
<button id="delete-podcast-file" class="btn btn-danger">Delete</button>
|
||||||
|
<button id="save-podcast-file" class="btn">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blog-form-grid">
|
||||||
|
<div>
|
||||||
|
<label for="podcast-slug">Slug</label>
|
||||||
|
<input id="podcast-slug" placeholder="2026-03-09-foundations-over-tools" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="podcast-title">Title</label>
|
||||||
|
<input id="podcast-title" placeholder="Foundations Over Tools" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="podcast-date">Date</label>
|
||||||
|
<input id="podcast-date" placeholder="2026-03-09" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="podcast-youtube-url">YouTube URL</label>
|
||||||
|
<input id="podcast-youtube-url" placeholder="https://www.youtube.com/watch?v=..." />
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label for="podcast-spotify-url">Spotify URL</label>
|
||||||
|
<input id="podcast-spotify-url" placeholder="https://open.spotify.com/show/..." />
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label for="podcast-editor">Markdown Content</label>
|
||||||
|
<textarea id="podcast-editor" spellcheck="false"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="tab-media" class="tab-panel">
|
||||||
|
<div class="split">
|
||||||
|
<section class="card">
|
||||||
|
<div class="row between">
|
||||||
|
<h2>Public Folders</h2>
|
||||||
|
<button id="refresh-media" class="btn btn-ghost">Refresh</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label for="folder-select">Target Folder</label>
|
||||||
|
<select id="folder-select"></select>
|
||||||
|
|
||||||
|
<div class="row mt-8">
|
||||||
|
<input id="new-folder" placeholder="e.g. blog/covers" />
|
||||||
|
<button id="create-folder" class="btn">Create</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-8">
|
||||||
|
<button id="upload-file" class="btn">Upload Image</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>Files</h2>
|
||||||
|
<div id="media-files" class="media-grid"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="tab-publish" class="tab-panel">
|
||||||
|
<div class="card publish-card">
|
||||||
|
<div class="row between">
|
||||||
|
<h2>Publish Changes</h2>
|
||||||
|
<button id="refresh-status" class="btn btn-ghost">Refresh Status</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="hint">This will stage <code>data/</code>, <code>public/</code> and <code>content/</code>, create commit, and push.</p>
|
||||||
|
|
||||||
|
<label for="commit-message">Commit Message (optional)</label>
|
||||||
|
<input id="commit-message" placeholder="Auto-generated if empty" />
|
||||||
|
|
||||||
|
<div class="row mt-8">
|
||||||
|
<button id="publish-btn" class="btn">Publish</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Git Status</h3>
|
||||||
|
<pre id="git-status" class="log"></pre>
|
||||||
|
|
||||||
|
<h3>Publish Log</h3>
|
||||||
|
<pre id="publish-log" class="log"></pre>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script type="module" src="./app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #f4f5f7;
|
||||||
|
--card: #ffffff;
|
||||||
|
--muted: #6b7280;
|
||||||
|
--border: #d8dbe2;
|
||||||
|
--text: #111827;
|
||||||
|
--red: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Segoe UI", Arial, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar p {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #fff;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn.active {
|
||||||
|
border-color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-panel.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: 280px 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.soft {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 680px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 10px 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
max-height: 560px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list button.active {
|
||||||
|
border-color: var(--text);
|
||||||
|
background: #f7f7f7;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-card-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 620px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-card {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-card.active {
|
||||||
|
border-color: var(--text);
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-card .title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-card .meta {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-form-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-form-grid .full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-8 {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea,
|
||||||
|
select,
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 340px;
|
||||||
|
font-family: Consolas, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-editor {
|
||||||
|
min-height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border: 1px solid var(--text);
|
||||||
|
background: var(--text);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
background: #fff;
|
||||||
|
color: var(--text);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #fff;
|
||||||
|
color: var(--red);
|
||||||
|
border-color: #f3b3b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||||
|
max-height: 680px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-item {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 96px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-item .meta {
|
||||||
|
padding: 6px;
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-item .name {
|
||||||
|
font-size: 11px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #f9fafb;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 80px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.split {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -6,7 +6,9 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint",
|
||||||
|
"panel:dev": "pnpm --filter @portfolio/data-panel dev",
|
||||||
|
"panel:start": "pnpm --filter @portfolio/data-panel dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
|
|||||||
Generated
+419
@@ -70,6 +70,12 @@ importers:
|
|||||||
specifier: ^5
|
specifier: ^5
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
apps/data-panel:
|
||||||
|
devDependencies:
|
||||||
|
electron:
|
||||||
|
specifier: ^33.2.0
|
||||||
|
version: 33.4.11
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0':
|
'@alloc/quick-lru@5.2.0':
|
||||||
@@ -168,6 +174,10 @@ packages:
|
|||||||
'@chevrotain/utils@11.1.2':
|
'@chevrotain/utils@11.1.2':
|
||||||
resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==}
|
resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==}
|
||||||
|
|
||||||
|
'@electron/get@2.0.3':
|
||||||
|
resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@emnapi/core@1.8.1':
|
'@emnapi/core@1.8.1':
|
||||||
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
|
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
|
||||||
|
|
||||||
@@ -1098,9 +1108,17 @@ packages:
|
|||||||
'@rtsao/scc@1.1.0':
|
'@rtsao/scc@1.1.0':
|
||||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||||
|
|
||||||
|
'@sindresorhus/is@4.6.0':
|
||||||
|
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||||
|
|
||||||
|
'@szmarczak/http-timer@4.0.6':
|
||||||
|
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
'@tailwindcss/node@4.2.1':
|
'@tailwindcss/node@4.2.1':
|
||||||
resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==}
|
resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==}
|
||||||
|
|
||||||
@@ -1192,6 +1210,9 @@ packages:
|
|||||||
'@tybys/wasm-util@0.10.1':
|
'@tybys/wasm-util@0.10.1':
|
||||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||||
|
|
||||||
|
'@types/cacheable-request@6.0.3':
|
||||||
|
resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
|
||||||
|
|
||||||
'@types/d3-array@3.2.2':
|
'@types/d3-array@3.2.2':
|
||||||
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
|
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
|
||||||
|
|
||||||
@@ -1300,12 +1321,18 @@ packages:
|
|||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||||
|
|
||||||
|
'@types/http-cache-semantics@4.2.0':
|
||||||
|
resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
'@types/json5@0.0.29':
|
'@types/json5@0.0.29':
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
|
||||||
|
'@types/keyv@3.1.4':
|
||||||
|
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||||
|
|
||||||
@@ -1326,6 +1353,9 @@ packages:
|
|||||||
'@types/react@19.2.14':
|
'@types/react@19.2.14':
|
||||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||||
|
|
||||||
|
'@types/responselike@1.0.3':
|
||||||
|
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7':
|
'@types/trusted-types@2.0.7':
|
||||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||||
|
|
||||||
@@ -1335,6 +1365,9 @@ packages:
|
|||||||
'@types/unist@3.0.3':
|
'@types/unist@3.0.3':
|
||||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||||
|
|
||||||
|
'@types/yauzl@2.10.3':
|
||||||
|
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.56.1':
|
'@typescript-eslint/eslint-plugin@8.56.1':
|
||||||
resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==}
|
resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1592,6 +1625,10 @@ packages:
|
|||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
boolean@3.2.0:
|
||||||
|
resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
|
||||||
|
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
brace-expansion@1.1.12:
|
||||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||||
|
|
||||||
@@ -1608,6 +1645,17 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
buffer-crc32@0.2.13:
|
||||||
|
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||||
|
|
||||||
|
cacheable-lookup@5.0.4:
|
||||||
|
resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
|
||||||
|
engines: {node: '>=10.6.0'}
|
||||||
|
|
||||||
|
cacheable-request@7.0.4:
|
||||||
|
resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1660,6 +1708,9 @@ packages:
|
|||||||
client-only@0.0.1:
|
client-only@0.0.1:
|
||||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
|
|
||||||
|
clone-response@1.0.3:
|
||||||
|
resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
|
||||||
|
|
||||||
clsx@2.1.1:
|
clsx@2.1.1:
|
||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1898,9 +1949,17 @@ packages:
|
|||||||
decode-named-character-reference@1.3.0:
|
decode-named-character-reference@1.3.0:
|
||||||
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
|
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
|
||||||
|
|
||||||
|
decompress-response@6.0.0:
|
||||||
|
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
deep-is@0.1.4:
|
deep-is@0.1.4:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
|
|
||||||
|
defer-to-connect@2.0.1:
|
||||||
|
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
define-data-property@1.1.4:
|
define-data-property@1.1.4:
|
||||||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1923,6 +1982,9 @@ packages:
|
|||||||
detect-node-es@1.1.0:
|
detect-node-es@1.1.0:
|
||||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||||
|
|
||||||
|
detect-node@2.1.0:
|
||||||
|
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
|
||||||
|
|
||||||
devlop@1.1.0:
|
devlop@1.1.0:
|
||||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||||
|
|
||||||
@@ -1941,13 +2003,25 @@ packages:
|
|||||||
electron-to-chromium@1.5.307:
|
electron-to-chromium@1.5.307:
|
||||||
resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==}
|
resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==}
|
||||||
|
|
||||||
|
electron@33.4.11:
|
||||||
|
resolution: {integrity: sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==}
|
||||||
|
engines: {node: '>= 12.20.55'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
emoji-regex@9.2.2:
|
emoji-regex@9.2.2:
|
||||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||||
|
|
||||||
enhanced-resolve@5.20.0:
|
enhanced-resolve@5.20.0:
|
||||||
resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==}
|
resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
env-paths@2.2.1:
|
||||||
|
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
es-abstract@1.24.1:
|
es-abstract@1.24.1:
|
||||||
resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
|
resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1980,6 +2054,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
|
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es6-error@4.1.1:
|
||||||
|
resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2127,6 +2204,11 @@ packages:
|
|||||||
extend@3.0.2:
|
extend@3.0.2:
|
||||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||||
|
|
||||||
|
extract-zip@2.0.1:
|
||||||
|
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
|
||||||
|
engines: {node: '>= 10.17.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
fast-deep-equal@3.1.3:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
@@ -2146,6 +2228,9 @@ packages:
|
|||||||
fault@1.0.4:
|
fault@1.0.4:
|
||||||
resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
|
resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
|
||||||
|
|
||||||
|
fd-slicer@1.1.0:
|
||||||
|
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||||
|
|
||||||
fdir@6.5.0:
|
fdir@6.5.0:
|
||||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@@ -2182,6 +2267,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
|
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
|
||||||
engines: {node: '>=0.4.x'}
|
engines: {node: '>=0.4.x'}
|
||||||
|
|
||||||
|
fs-extra@8.1.0:
|
||||||
|
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
|
||||||
|
engines: {node: '>=6 <7 || >=8'}
|
||||||
|
|
||||||
function-bind@1.1.2:
|
function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
@@ -2212,6 +2301,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-stream@5.2.0:
|
||||||
|
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
get-symbol-description@1.1.0:
|
get-symbol-description@1.1.0:
|
||||||
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2227,6 +2320,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
global-agent@3.0.0:
|
||||||
|
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
|
||||||
|
engines: {node: '>=10.0'}
|
||||||
|
|
||||||
globals@14.0.0:
|
globals@14.0.0:
|
||||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -2243,6 +2340,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
got@11.8.6:
|
||||||
|
resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==}
|
||||||
|
engines: {node: '>=10.19.0'}
|
||||||
|
|
||||||
graceful-fs@4.2.11:
|
graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
@@ -2307,6 +2408,13 @@ packages:
|
|||||||
html-url-attributes@3.0.1:
|
html-url-attributes@3.0.1:
|
||||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||||
|
|
||||||
|
http-cache-semantics@4.2.0:
|
||||||
|
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
|
||||||
|
|
||||||
|
http2-wrapper@1.0.3:
|
||||||
|
resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
|
||||||
|
engines: {node: '>=10.19.0'}
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2503,6 +2611,9 @@ packages:
|
|||||||
json-stable-stringify-without-jsonify@1.0.1:
|
json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
|
|
||||||
|
json-stringify-safe@5.0.1:
|
||||||
|
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
||||||
|
|
||||||
json5@1.0.2:
|
json5@1.0.2:
|
||||||
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2512,6 +2623,9 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
jsonfile@4.0.0:
|
||||||
|
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
|
||||||
|
|
||||||
jsx-ast-utils@3.3.5:
|
jsx-ast-utils@3.3.5:
|
||||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -2638,6 +2752,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
lowercase-keys@2.0.0:
|
||||||
|
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
lowlight@1.20.0:
|
lowlight@1.20.0:
|
||||||
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
|
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
|
||||||
|
|
||||||
@@ -2660,6 +2778,10 @@ packages:
|
|||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
matcher@3.0.0:
|
||||||
|
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2804,6 +2926,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
mimic-response@1.0.1:
|
||||||
|
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
mimic-response@3.1.0:
|
||||||
|
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
minimatch@10.2.4:
|
minimatch@10.2.4:
|
||||||
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
@@ -2864,6 +2994,10 @@ packages:
|
|||||||
node-releases@2.0.36:
|
node-releases@2.0.36:
|
||||||
resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==}
|
resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==}
|
||||||
|
|
||||||
|
normalize-url@6.1.0:
|
||||||
|
resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
object-assign@4.1.1:
|
object-assign@4.1.1:
|
||||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2896,6 +3030,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -2904,6 +3041,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
|
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
p-cancelable@2.1.1:
|
||||||
|
resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
p-limit@3.1.0:
|
p-limit@3.1.0:
|
||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2943,6 +3084,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw==}
|
resolution: {integrity: sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw==}
|
||||||
engines: {node: '>=20.19.0 || >=22.13.0 || >=24'}
|
engines: {node: '>=20.19.0 || >=22.13.0 || >=24'}
|
||||||
|
|
||||||
|
pend@1.2.0:
|
||||||
|
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
@@ -3004,12 +3148,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
progress@2.0.3:
|
||||||
|
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||||
|
|
||||||
property-information@7.1.0:
|
property-information@7.1.0:
|
||||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||||
|
|
||||||
punycode@2.3.1:
|
punycode@2.3.1:
|
||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -3017,6 +3168,10 @@ packages:
|
|||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
quick-lru@5.1.1:
|
||||||
|
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
react-dom@19.2.3:
|
react-dom@19.2.3:
|
||||||
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
|
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3100,6 +3255,9 @@ packages:
|
|||||||
remark-stringify@11.0.0:
|
remark-stringify@11.0.0:
|
||||||
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||||
|
|
||||||
|
resolve-alpn@1.2.1:
|
||||||
|
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -3117,10 +3275,17 @@ packages:
|
|||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
responselike@2.0.1:
|
||||||
|
resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
|
||||||
|
|
||||||
reusify@1.1.0:
|
reusify@1.1.0:
|
||||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
|
|
||||||
|
roarr@2.15.4:
|
||||||
|
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
|
||||||
|
engines: {node: '>=8.0'}
|
||||||
|
|
||||||
robust-predicates@3.0.2:
|
robust-predicates@3.0.2:
|
||||||
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
|
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
|
||||||
|
|
||||||
@@ -3155,6 +3320,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
semver-compare@1.0.0:
|
||||||
|
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
|
||||||
|
|
||||||
semver@6.3.1:
|
semver@6.3.1:
|
||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -3164,6 +3332,10 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
serialize-error@7.0.1:
|
||||||
|
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3220,6 +3392,9 @@ packages:
|
|||||||
sprintf-js@1.0.3:
|
sprintf-js@1.0.3:
|
||||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||||
|
|
||||||
|
sprintf-js@1.1.3:
|
||||||
|
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
|
||||||
|
|
||||||
stable-hash@0.0.5:
|
stable-hash@0.0.5:
|
||||||
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
|
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
|
||||||
|
|
||||||
@@ -3287,6 +3462,10 @@ packages:
|
|||||||
stylis@4.3.6:
|
stylis@4.3.6:
|
||||||
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
|
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
|
||||||
|
|
||||||
|
sumchecker@3.0.1:
|
||||||
|
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
|
||||||
|
engines: {node: '>= 8.0'}
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3343,6 +3522,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
type-fest@0.13.1:
|
||||||
|
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
typed-array-buffer@1.0.3:
|
typed-array-buffer@1.0.3:
|
||||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3399,6 +3582,10 @@ packages:
|
|||||||
unist-util-visit@5.1.0:
|
unist-util-visit@5.1.0:
|
||||||
resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
|
resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
|
||||||
|
|
||||||
|
universalify@0.1.2:
|
||||||
|
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||||
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
unrs-resolver@1.11.1:
|
unrs-resolver@1.11.1:
|
||||||
resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
|
resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
|
||||||
|
|
||||||
@@ -3497,9 +3684,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
wrappy@1.0.2:
|
||||||
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
yallist@3.1.1:
|
yallist@3.1.1:
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||||
|
|
||||||
|
yauzl@2.10.0:
|
||||||
|
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||||
|
|
||||||
yocto-queue@0.1.0:
|
yocto-queue@0.1.0:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3646,6 +3839,20 @@ snapshots:
|
|||||||
|
|
||||||
'@chevrotain/utils@11.1.2': {}
|
'@chevrotain/utils@11.1.2': {}
|
||||||
|
|
||||||
|
'@electron/get@2.0.3':
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
env-paths: 2.2.1
|
||||||
|
fs-extra: 8.1.0
|
||||||
|
got: 11.8.6
|
||||||
|
progress: 2.0.3
|
||||||
|
semver: 6.3.1
|
||||||
|
sumchecker: 3.0.1
|
||||||
|
optionalDependencies:
|
||||||
|
global-agent: 3.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@emnapi/core@1.8.1':
|
'@emnapi/core@1.8.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.1.0
|
'@emnapi/wasi-threads': 1.1.0
|
||||||
@@ -4527,10 +4734,16 @@ snapshots:
|
|||||||
|
|
||||||
'@rtsao/scc@1.1.0': {}
|
'@rtsao/scc@1.1.0': {}
|
||||||
|
|
||||||
|
'@sindresorhus/is@4.6.0': {}
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@szmarczak/http-timer@4.0.6':
|
||||||
|
dependencies:
|
||||||
|
defer-to-connect: 2.0.1
|
||||||
|
|
||||||
'@tailwindcss/node@4.2.1':
|
'@tailwindcss/node@4.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/remapping': 2.3.5
|
'@jridgewell/remapping': 2.3.5
|
||||||
@@ -4605,6 +4818,13 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@types/cacheable-request@6.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/http-cache-semantics': 4.2.0
|
||||||
|
'@types/keyv': 3.1.4
|
||||||
|
'@types/node': 20.19.37
|
||||||
|
'@types/responselike': 1.0.3
|
||||||
|
|
||||||
'@types/d3-array@3.2.2': {}
|
'@types/d3-array@3.2.2': {}
|
||||||
|
|
||||||
'@types/d3-axis@3.0.6':
|
'@types/d3-axis@3.0.6':
|
||||||
@@ -4738,10 +4958,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
'@types/http-cache-semantics@4.2.0': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
|
|
||||||
|
'@types/keyv@3.1.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.19.37
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
@@ -4762,6 +4988,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
'@types/responselike@1.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.19.37
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7':
|
'@types/trusted-types@2.0.7':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -4769,6 +4999,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/unist@3.0.3': {}
|
'@types/unist@3.0.3': {}
|
||||||
|
|
||||||
|
'@types/yauzl@2.10.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.19.37
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
@@ -5042,6 +5277,9 @@ snapshots:
|
|||||||
|
|
||||||
baseline-browser-mapping@2.10.0: {}
|
baseline-browser-mapping@2.10.0: {}
|
||||||
|
|
||||||
|
boolean@3.2.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
brace-expansion@1.1.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
@@ -5063,6 +5301,20 @@ snapshots:
|
|||||||
node-releases: 2.0.36
|
node-releases: 2.0.36
|
||||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||||
|
|
||||||
|
buffer-crc32@0.2.13: {}
|
||||||
|
|
||||||
|
cacheable-lookup@5.0.4: {}
|
||||||
|
|
||||||
|
cacheable-request@7.0.4:
|
||||||
|
dependencies:
|
||||||
|
clone-response: 1.0.3
|
||||||
|
get-stream: 5.2.0
|
||||||
|
http-cache-semantics: 4.2.0
|
||||||
|
keyv: 4.5.4
|
||||||
|
lowercase-keys: 2.0.0
|
||||||
|
normalize-url: 6.1.0
|
||||||
|
responselike: 2.0.1
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
@@ -5119,6 +5371,10 @@ snapshots:
|
|||||||
|
|
||||||
client-only@0.0.1: {}
|
client-only@0.0.1: {}
|
||||||
|
|
||||||
|
clone-response@1.0.3:
|
||||||
|
dependencies:
|
||||||
|
mimic-response: 1.0.1
|
||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
@@ -5373,8 +5629,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
character-entities: 2.0.2
|
character-entities: 2.0.2
|
||||||
|
|
||||||
|
decompress-response@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
mimic-response: 3.1.0
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
|
|
||||||
|
defer-to-connect@2.0.1: {}
|
||||||
|
|
||||||
define-data-property@1.1.4:
|
define-data-property@1.1.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-define-property: 1.0.1
|
es-define-property: 1.0.1
|
||||||
@@ -5397,6 +5659,9 @@ snapshots:
|
|||||||
|
|
||||||
detect-node-es@1.1.0: {}
|
detect-node-es@1.1.0: {}
|
||||||
|
|
||||||
|
detect-node@2.1.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
devlop@1.1.0:
|
devlop@1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
@@ -5417,13 +5682,27 @@ snapshots:
|
|||||||
|
|
||||||
electron-to-chromium@1.5.307: {}
|
electron-to-chromium@1.5.307: {}
|
||||||
|
|
||||||
|
electron@33.4.11:
|
||||||
|
dependencies:
|
||||||
|
'@electron/get': 2.0.3
|
||||||
|
'@types/node': 20.19.37
|
||||||
|
extract-zip: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
emoji-regex@9.2.2: {}
|
emoji-regex@9.2.2: {}
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
dependencies:
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
enhanced-resolve@5.20.0:
|
enhanced-resolve@5.20.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
tapable: 2.3.0
|
tapable: 2.3.0
|
||||||
|
|
||||||
|
env-paths@2.2.1: {}
|
||||||
|
|
||||||
es-abstract@1.24.1:
|
es-abstract@1.24.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
array-buffer-byte-length: 1.0.2
|
array-buffer-byte-length: 1.0.2
|
||||||
@@ -5525,6 +5804,9 @@ snapshots:
|
|||||||
is-date-object: 1.1.0
|
is-date-object: 1.1.0
|
||||||
is-symbol: 1.1.1
|
is-symbol: 1.1.1
|
||||||
|
|
||||||
|
es6-error@4.1.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
@@ -5746,6 +6028,16 @@ snapshots:
|
|||||||
|
|
||||||
extend@3.0.2: {}
|
extend@3.0.2: {}
|
||||||
|
|
||||||
|
extract-zip@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
get-stream: 5.2.0
|
||||||
|
yauzl: 2.10.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/yauzl': 2.10.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
fast-glob@3.3.1:
|
fast-glob@3.3.1:
|
||||||
@@ -5768,6 +6060,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
format: 0.2.2
|
format: 0.2.2
|
||||||
|
|
||||||
|
fd-slicer@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
pend: 1.2.0
|
||||||
|
|
||||||
fdir@6.5.0(picomatch@4.0.3):
|
fdir@6.5.0(picomatch@4.0.3):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
@@ -5798,6 +6094,12 @@ snapshots:
|
|||||||
|
|
||||||
format@0.2.2: {}
|
format@0.2.2: {}
|
||||||
|
|
||||||
|
fs-extra@8.1.0:
|
||||||
|
dependencies:
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
jsonfile: 4.0.0
|
||||||
|
universalify: 0.1.2
|
||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
function.prototype.name@1.1.8:
|
function.prototype.name@1.1.8:
|
||||||
@@ -5835,6 +6137,10 @@ snapshots:
|
|||||||
dunder-proto: 1.0.1
|
dunder-proto: 1.0.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
get-stream@5.2.0:
|
||||||
|
dependencies:
|
||||||
|
pump: 3.0.4
|
||||||
|
|
||||||
get-symbol-description@1.1.0:
|
get-symbol-description@1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -5853,6 +6159,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
||||||
|
global-agent@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
boolean: 3.2.0
|
||||||
|
es6-error: 4.1.1
|
||||||
|
matcher: 3.0.0
|
||||||
|
roarr: 2.15.4
|
||||||
|
semver: 7.7.4
|
||||||
|
serialize-error: 7.0.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
globals@14.0.0: {}
|
globals@14.0.0: {}
|
||||||
|
|
||||||
globals@16.4.0: {}
|
globals@16.4.0: {}
|
||||||
@@ -5864,6 +6180,20 @@ snapshots:
|
|||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
|
got@11.8.6:
|
||||||
|
dependencies:
|
||||||
|
'@sindresorhus/is': 4.6.0
|
||||||
|
'@szmarczak/http-timer': 4.0.6
|
||||||
|
'@types/cacheable-request': 6.0.3
|
||||||
|
'@types/responselike': 1.0.3
|
||||||
|
cacheable-lookup: 5.0.4
|
||||||
|
cacheable-request: 7.0.4
|
||||||
|
decompress-response: 6.0.0
|
||||||
|
http2-wrapper: 1.0.3
|
||||||
|
lowercase-keys: 2.0.0
|
||||||
|
p-cancelable: 2.1.1
|
||||||
|
responselike: 2.0.1
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
gray-matter@4.0.3:
|
gray-matter@4.0.3:
|
||||||
@@ -5945,6 +6275,13 @@ snapshots:
|
|||||||
|
|
||||||
html-url-attributes@3.0.1: {}
|
html-url-attributes@3.0.1: {}
|
||||||
|
|
||||||
|
http-cache-semantics@4.2.0: {}
|
||||||
|
|
||||||
|
http2-wrapper@1.0.3:
|
||||||
|
dependencies:
|
||||||
|
quick-lru: 5.1.1
|
||||||
|
resolve-alpn: 1.2.1
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
@@ -6133,12 +6470,19 @@ snapshots:
|
|||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||||
|
|
||||||
|
json-stringify-safe@5.0.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
json5@1.0.2:
|
json5@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
|
|
||||||
json5@2.2.3: {}
|
json5@2.2.3: {}
|
||||||
|
|
||||||
|
jsonfile@4.0.0:
|
||||||
|
optionalDependencies:
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
|
||||||
jsx-ast-utils@3.3.5:
|
jsx-ast-utils@3.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.9
|
array-includes: 3.1.9
|
||||||
@@ -6244,6 +6588,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
|
lowercase-keys@2.0.0: {}
|
||||||
|
|
||||||
lowlight@1.20.0:
|
lowlight@1.20.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fault: 1.0.4
|
fault: 1.0.4
|
||||||
@@ -6265,6 +6611,11 @@ snapshots:
|
|||||||
|
|
||||||
marked@16.4.2: {}
|
marked@16.4.2: {}
|
||||||
|
|
||||||
|
matcher@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
escape-string-regexp: 4.0.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
mdast-util-find-and-replace@3.0.2:
|
mdast-util-find-and-replace@3.0.2:
|
||||||
@@ -6642,6 +6993,10 @@ snapshots:
|
|||||||
braces: 3.0.3
|
braces: 3.0.3
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
mimic-response@1.0.1: {}
|
||||||
|
|
||||||
|
mimic-response@3.1.0: {}
|
||||||
|
|
||||||
minimatch@10.2.4:
|
minimatch@10.2.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 5.0.4
|
brace-expansion: 5.0.4
|
||||||
@@ -6703,6 +7058,8 @@ snapshots:
|
|||||||
|
|
||||||
node-releases@2.0.36: {}
|
node-releases@2.0.36: {}
|
||||||
|
|
||||||
|
normalize-url@6.1.0: {}
|
||||||
|
|
||||||
object-assign@4.1.1: {}
|
object-assign@4.1.1: {}
|
||||||
|
|
||||||
object-inspect@1.13.4: {}
|
object-inspect@1.13.4: {}
|
||||||
@@ -6745,6 +7102,10 @@ snapshots:
|
|||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
wrappy: 1.0.2
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-is: 0.1.4
|
deep-is: 0.1.4
|
||||||
@@ -6760,6 +7121,8 @@ snapshots:
|
|||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
safe-push-apply: 1.0.0
|
safe-push-apply: 1.0.0
|
||||||
|
|
||||||
|
p-cancelable@2.1.1: {}
|
||||||
|
|
||||||
p-limit@3.1.0:
|
p-limit@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
yocto-queue: 0.1.0
|
yocto-queue: 0.1.0
|
||||||
@@ -6799,6 +7162,8 @@ snapshots:
|
|||||||
'@napi-rs/canvas': 0.1.96
|
'@napi-rs/canvas': 0.1.96
|
||||||
node-readable-to-web-readable-stream: 0.4.2
|
node-readable-to-web-readable-stream: 0.4.2
|
||||||
|
|
||||||
|
pend@1.2.0: {}
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@2.3.1: {}
|
||||||
@@ -6871,6 +7236,8 @@ snapshots:
|
|||||||
|
|
||||||
prismjs@1.30.0: {}
|
prismjs@1.30.0: {}
|
||||||
|
|
||||||
|
progress@2.0.3: {}
|
||||||
|
|
||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
@@ -6879,10 +7246,17 @@ snapshots:
|
|||||||
|
|
||||||
property-information@7.1.0: {}
|
property-information@7.1.0: {}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
dependencies:
|
||||||
|
end-of-stream: 1.4.5
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
|
quick-lru@5.1.1: {}
|
||||||
|
|
||||||
react-dom@19.2.3(react@19.2.3):
|
react-dom@19.2.3(react@19.2.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.3
|
react: 19.2.3
|
||||||
@@ -7012,6 +7386,8 @@ snapshots:
|
|||||||
mdast-util-to-markdown: 2.1.2
|
mdast-util-to-markdown: 2.1.2
|
||||||
unified: 11.0.5
|
unified: 11.0.5
|
||||||
|
|
||||||
|
resolve-alpn@1.2.1: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
@@ -7031,8 +7407,22 @@ snapshots:
|
|||||||
path-parse: 1.0.7
|
path-parse: 1.0.7
|
||||||
supports-preserve-symlinks-flag: 1.0.0
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
|
responselike@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
lowercase-keys: 2.0.0
|
||||||
|
|
||||||
reusify@1.1.0: {}
|
reusify@1.1.0: {}
|
||||||
|
|
||||||
|
roarr@2.15.4:
|
||||||
|
dependencies:
|
||||||
|
boolean: 3.2.0
|
||||||
|
detect-node: 2.1.0
|
||||||
|
globalthis: 1.0.4
|
||||||
|
json-stringify-safe: 5.0.1
|
||||||
|
semver-compare: 1.0.0
|
||||||
|
sprintf-js: 1.1.3
|
||||||
|
optional: true
|
||||||
|
|
||||||
robust-predicates@3.0.2: {}
|
robust-predicates@3.0.2: {}
|
||||||
|
|
||||||
roughjs@4.6.6:
|
roughjs@4.6.6:
|
||||||
@@ -7076,10 +7466,18 @@ snapshots:
|
|||||||
extend-shallow: 2.0.1
|
extend-shallow: 2.0.1
|
||||||
kind-of: 6.0.3
|
kind-of: 6.0.3
|
||||||
|
|
||||||
|
semver-compare@1.0.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.7.4: {}
|
semver@7.7.4: {}
|
||||||
|
|
||||||
|
serialize-error@7.0.1:
|
||||||
|
dependencies:
|
||||||
|
type-fest: 0.13.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
define-data-property: 1.1.4
|
define-data-property: 1.1.4
|
||||||
@@ -7179,6 +7577,9 @@ snapshots:
|
|||||||
|
|
||||||
sprintf-js@1.0.3: {}
|
sprintf-js@1.0.3: {}
|
||||||
|
|
||||||
|
sprintf-js@1.1.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
stable-hash@0.0.5: {}
|
stable-hash@0.0.5: {}
|
||||||
|
|
||||||
stop-iteration-iterator@1.1.0:
|
stop-iteration-iterator@1.1.0:
|
||||||
@@ -7264,6 +7665,12 @@ snapshots:
|
|||||||
|
|
||||||
stylis@4.3.6: {}
|
stylis@4.3.6: {}
|
||||||
|
|
||||||
|
sumchecker@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
@@ -7310,6 +7717,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|
||||||
|
type-fest@0.13.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
typed-array-buffer@1.0.3:
|
typed-array-buffer@1.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -7400,6 +7810,8 @@ snapshots:
|
|||||||
unist-util-is: 6.0.1
|
unist-util-is: 6.0.1
|
||||||
unist-util-visit-parents: 6.0.2
|
unist-util-visit-parents: 6.0.2
|
||||||
|
|
||||||
|
universalify@0.1.2: {}
|
||||||
|
|
||||||
unrs-resolver@1.11.1:
|
unrs-resolver@1.11.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
napi-postinstall: 0.3.4
|
napi-postinstall: 0.3.4
|
||||||
@@ -7538,8 +7950,15 @@ snapshots:
|
|||||||
|
|
||||||
word-wrap@1.2.5: {}
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
|
yauzl@2.10.0:
|
||||||
|
dependencies:
|
||||||
|
buffer-crc32: 0.2.13
|
||||||
|
fd-slicer: 1.1.0
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
zod-validation-error@4.0.2(zod@4.3.6):
|
zod-validation-error@4.0.2(zod@4.3.6):
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
packages:
|
||||||
|
- .
|
||||||
|
- apps/*
|
||||||
|
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- electron
|
||||||
|
|
||||||
ignoredBuiltDependencies:
|
ignoredBuiltDependencies:
|
||||||
- sharp
|
- sharp
|
||||||
- unrs-resolver
|
- unrs-resolver
|
||||||
|
|||||||
Reference in New Issue
Block a user