From 7f8062b9c1a34503fd9e3aaca2c149761acc558b Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:30:21 -0600 Subject: [PATCH 01/12] refactor(editor): convert CodeEditor from React to Astro --- src/components/shared/editor/CodeEditor.astro | 80 +++++++++++++++++++ src/components/shared/editor/CodeEditor.tsx | 23 ------ .../shared/editor}/config.ts | 2 +- .../shared/editor}/keywords.ts | 0 .../shared/editor}/theme.ts | 21 ++--- 5 files changed, 92 insertions(+), 34 deletions(-) create mode 100644 src/components/shared/editor/CodeEditor.astro delete mode 100644 src/components/shared/editor/CodeEditor.tsx rename src/{helpers => components/shared/editor}/config.ts (94%) rename src/{helpers => components/shared/editor}/keywords.ts (100%) rename src/{helpers => components/shared/editor}/theme.ts (92%) diff --git a/src/components/shared/editor/CodeEditor.astro b/src/components/shared/editor/CodeEditor.astro new file mode 100644 index 0000000..8f38bde --- /dev/null +++ b/src/components/shared/editor/CodeEditor.astro @@ -0,0 +1,80 @@ +--- +interface Props { + slug: string + initialCode?: string +} + +const { slug, initialCode = 'fn main() {\n println!("Hola, mundo!");\n}\n' } = Astro.props +--- + +
+ + diff --git a/src/components/shared/editor/CodeEditor.tsx b/src/components/shared/editor/CodeEditor.tsx deleted file mode 100644 index 7a4105e..0000000 --- a/src/components/shared/editor/CodeEditor.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import CodeMirror from "@uiw/react-codemirror" -import { useCallback, useState } from "react" -import { rust } from "~/helpers/config" -import { gruvbox } from "~/helpers/theme" - -export function CodeEditor() { - const [value, setValue] = useState('fn main() {\n println!("Hola, mundo!");\n}\n') - - const onChange = useCallback((val: string) => { - setValue(val) - }, []) - - return ( - - ) -} diff --git a/src/helpers/config.ts b/src/components/shared/editor/config.ts similarity index 94% rename from src/helpers/config.ts rename to src/components/shared/editor/config.ts index 3bdc74b..4445bb6 100644 --- a/src/helpers/config.ts +++ b/src/components/shared/editor/config.ts @@ -8,7 +8,7 @@ import { LRLanguage, } from "@codemirror/language" import { parser } from "@lezer/rust" -import { rustCompletions } from "./keywords" +import { rustCompletions } from "~/components/shared/editor/keywords" const rustLanguage = LRLanguage.define({ name: "rust", diff --git a/src/helpers/keywords.ts b/src/components/shared/editor/keywords.ts similarity index 100% rename from src/helpers/keywords.ts rename to src/components/shared/editor/keywords.ts diff --git a/src/helpers/theme.ts b/src/components/shared/editor/theme.ts similarity index 92% rename from src/helpers/theme.ts rename to src/components/shared/editor/theme.ts index c1b814d..49510d9 100644 --- a/src/helpers/theme.ts +++ b/src/components/shared/editor/theme.ts @@ -5,21 +5,22 @@ import type { Extension } from "@codemirror/state" import { EditorView } from "@codemirror/view" import { tags as t } from "@lezer/highlight" -const chalky = "#d79921", - coral = "#fb4934", - cyan = "#83a598", +// Colores ajustados para cumplir WCAG AA (4.5:1 contrast ratio) contra #1d2021 +const chalky = "#fdd835", + coral = "#ff6b6b", + cyan = "#4dd0e1", invalid = "#ffffff", - ivory = "#abb2bf", - stone = "#7d8799", - malibu = "#458588", - sage = "#8ec07c", - whiskey = "#fe9019", - violet = "#b16286", + ivory = "#f5f5f5", + stone = "#b0b0b0", + malibu = "#64b5f6", + sage = "#aed581", + whiskey = "#ffb74d", + violet = "#f06292", darkBackground = "#1d2021", highlightBackground = "#1d2021", tooltipBackground = "#3c3836", selection = "#504945", - cursor = "#fabd2f" + cursor = "#fdd835" export const gruvboxDarkTheme = EditorView.theme( { From 339b17535ecbfbac39a62bbd015732d49178a528 Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:30:37 -0600 Subject: [PATCH 02/12] feat(editor): add Terminal component and output renderer --- .../shared/editor/output/Terminal.astro | 168 +++++++++++++++++ .../shared/editor/output/renderer.ts | 178 ++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 src/components/shared/editor/output/Terminal.astro create mode 100644 src/components/shared/editor/output/renderer.ts diff --git a/src/components/shared/editor/output/Terminal.astro b/src/components/shared/editor/output/Terminal.astro new file mode 100644 index 0000000..db1be0f --- /dev/null +++ b/src/components/shared/editor/output/Terminal.astro @@ -0,0 +1,168 @@ +--- +import IconLoader2 from "~icons/tabler/loader-2" +import IconPlayerPlay from "~icons/tabler/player-play" +import IconTerminal from "~icons/tabler/terminal" +--- + + +
+
+
+ +
+
+
+ $ + cargo run +
+
+
+ + diff --git a/src/components/shared/editor/output/renderer.ts b/src/components/shared/editor/output/renderer.ts new file mode 100644 index 0000000..c441ce4 --- /dev/null +++ b/src/components/shared/editor/output/renderer.ts @@ -0,0 +1,178 @@ +const esc = (s: string) => + s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c] || c) + +const span = (className: string, text: string) => `${esc(text)}` +const bold = (html: string) => `${html}` + +const PATTERNS = { + errorCode: /^error\[E\d+\]:/, + errorGeneric: /^error:/, + warning: /^warning:/, + buildStage: /^\s*(Compiling|Finished|Running)/, + arrow: /-->/, + pipeOnly: /^\s*\|\s*$/, + dashLine: /^(\s*)(\|?)(\s*)(-+)(\s+)(.+)$/, + lineWithPlus: /^(\s*)(\d+)(\s*)(\+)(.*)$/, + lineWithPipe: /^(\s*)(\d+)(\s*)(\|)(.*)$/, + errorMarkers: /\^+/, + pipeWithPlus: /^(\s*)(\|)(\s*)(\+)$/, + helpOrNote: /^(\s*)(=)(\s+)(help|note)(:)(.*)$/, + helpOrNoteStart: /^(\s*)(help|note)(:)(.*)$/, + boldInfo: /^(Some errors have detailed|For more information|error: could not compile)/, + blockStart: /^(error\[|error:|warning:)/, + detailedErrors: /^Some errors have detailed/, +} as const + +export function getStderrHTML(text: string): string { + if (!text) return "" + + const blocks = splitIntoBlocks(text.split("\n")) + return blocks + .map((block) => block.map((line, i) => colorizeLine(line, block[i + 1] || "")).join("\n")) + .join('
') +} + +function splitIntoBlocks(lines: string[]): string[][] { + const blocks: string[][] = [] + let block: string[] = [] + + for (const line of lines) { + const trimmed = line.trim() + if (block.length > 0 && (PATTERNS.blockStart.test(trimmed) || PATTERNS.detailedErrors.test(trimmed))) { + blocks.push(block) + block = [] + } + block.push(line) + } + + if (block.length > 0) blocks.push(block) + return blocks +} + +function colorizeLine(line: string, nextLine: string): string { + const trimmed = line.trim() + + if (PATTERNS.errorCode.test(trimmed)) return colorizeErrorCode(line) + if (PATTERNS.errorGeneric.test(trimmed)) return colorizeErrorGeneric(line) + if (PATTERNS.warning.test(trimmed)) return colorizeWarning(line) + if (PATTERNS.buildStage.test(line)) return colorizeBuildStage(line) + if (PATTERNS.arrow.test(line)) return colorizeArrow(line) + if (PATTERNS.pipeOnly.test(line)) return colorizePipeOnly(line) + + const dashLine = line.match(PATTERNS.dashLine) + if (dashLine) return colorizeDashLine(dashLine) + + const lineWithPlus = line.match(PATTERNS.lineWithPlus) + if (lineWithPlus) return colorizeLineWithPlus(lineWithPlus) + + const lineWithPipe = line.match(PATTERNS.lineWithPipe) + if (lineWithPipe) return colorizeLineWithPipe(lineWithPipe, line, nextLine) + + if (PATTERNS.errorMarkers.test(trimmed)) return colorizeErrorMarkers(line) + + const pipeWithPlus = line.match(PATTERNS.pipeWithPlus) + if (pipeWithPlus) return colorizePipeWithPlus(pipeWithPlus) + + const helpOrNote = line.match(PATTERNS.helpOrNote) + if (helpOrNote) return colorizeHelpOrNote(helpOrNote) + + const helpOrNoteStart = line.match(PATTERNS.helpOrNoteStart) + if (helpOrNoteStart) return colorizeHelpOrNoteStart(helpOrNoteStart) + + if (PATTERNS.boldInfo.test(trimmed)) return bold(esc(line)) + + return esc(line) +} + +function colorizeErrorCode(line: string): string { + return esc(line) + .replace( + /error\[E(\d+)\]/, + bold( + `error[E$1]`, + ), + ) + .replace(/(error\[E\d+\]<\/a><\/b>)(: )(.+)$/, `$1${bold(`: ${span("text-red-400", "$3")}`)}`) +} + +function colorizeErrorGeneric(line: string): string { + return esc(line).replace(/^(\s*)(error)(: .+)$/, `$1${bold(`${span("text-red-400", "$2")}$3`)}`) +} + +function colorizeWarning(line: string): string { + return esc(line).replace(/^(\s*)(warning)(: .+)$/, `$1${bold(`${span("text-yellow-400", "$2")}$3`)}`) +} + +function colorizeDashLine(match: RegExpMatchArray): string { + const [, spaces, pipe, spacesAfter, dashes, spacesBeforeText, text] = match + let result = esc(spaces) + if (pipe) result += bold(span("text-blue-300", pipe)) + result += esc(spacesAfter) + bold(span("text-blue-300", dashes + spacesBeforeText + text)) + return result +} + +function colorizeBuildStage(line: string): string { + return esc(line).replace(/(Compiling|Finished|Running)/, bold(span("text-green-400", "$1"))) +} + +function colorizeArrow(line: string): string { + return esc(line).replace(/(-->)/g, bold(span("text-blue-300", "$1"))) +} + +function colorizePipeOnly(line: string): string { + return esc(line).replace(/(\|)/g, bold(span("text-blue-300", "$1"))) +} + +function colorizeLineWithPlus(match: RegExpMatchArray): string { + const [, spaces, num, spacesAfter, plus, rest] = match + return `${esc(spaces)}${bold(span("text-blue-300", num))}${esc(spacesAfter)}${span("text-green-400", plus + rest)}` +} + +function colorizeLineWithPipe(match: RegExpMatchArray, line: string, nextLine: string): string { + const [, spaces, num, spacesAfter, pipe, code] = match + const prefix = `${esc(spaces)}${bold(span("text-blue-300", num + spacesAfter + pipe))}` + + if (/^\s*\|\s*\+/.test(nextLine)) { + const charIndex = nextLine.indexOf("+") - line.indexOf("|") - 1 + if (charIndex >= 0 && charIndex < code.length) { + return ( + prefix + + esc(code.substring(0, charIndex)) + + span("text-green-400", code[charIndex]) + + esc(code.substring(charIndex + 1)) + ) + } + } + return prefix + esc(code) +} + +function colorizeErrorMarkers(line: string): string { + const match = line.match(/^(\s*)(\|?)(\s*)(\^+)(\s+(.+))?$/) + if (!match) return esc(line) + + const [, spaces, pipe, spacesAfter, carets, , message] = match + let result = esc(spaces) + if (pipe) result += bold(span("text-blue-300", pipe)) + result += esc(spacesAfter) + bold(span("text-red-400", carets)) + if (message) { + const messageStart = match[5].substring(0, match[5].indexOf(message)) + result += esc(messageStart) + bold(span("text-red-400", message)) + } + return result +} + +function colorizePipeWithPlus(match: RegExpMatchArray): string { + const [, spaces, pipe, spacesAfter, plus] = match + return `${esc(spaces)}${bold(span("text-blue-300", pipe))}${esc(spacesAfter)}${span("text-green-400", plus)}` +} + +function colorizeHelpOrNote(match: RegExpMatchArray): string { + const [, spaces, equals, , type, colon, rest] = match + return `${esc(spaces)}${bold(span("text-blue-300", equals))} ${bold(esc(type + colon))}${esc(rest)}` +} + +function colorizeHelpOrNoteStart(match: RegExpMatchArray): string { + const [, spaces, type, colon, rest] = match + const color = type === "help" ? "text-green-400" : "text-purple-400" + return `${esc(spaces)}${bold(span(color, type + colon))}${esc(rest)}` +} From 1d0372da6f9b4bb513cf7d223db50cb9e1ee90d1 Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:30:46 -0600 Subject: [PATCH 03/12] refactor(sidebar): convert Sidebar components to Astro --- src/components/shared/sidebar/Sidebar.astro | 134 ++++++++++++++++++ src/components/shared/sidebar/Sidebar.tsx | 92 ------------ .../shared/sidebar/ToggleSidebarButton.astro | 40 ++++++ 3 files changed, 174 insertions(+), 92 deletions(-) create mode 100644 src/components/shared/sidebar/Sidebar.astro delete mode 100644 src/components/shared/sidebar/Sidebar.tsx create mode 100644 src/components/shared/sidebar/ToggleSidebarButton.astro diff --git a/src/components/shared/sidebar/Sidebar.astro b/src/components/shared/sidebar/Sidebar.astro new file mode 100644 index 0000000..0df7444 --- /dev/null +++ b/src/components/shared/sidebar/Sidebar.astro @@ -0,0 +1,134 @@ +--- +import { Image } from "astro:assets" +import IconX from "~icons/tabler/x" + +export interface SidebarItem { + label: string + href?: string + icon?: string +} + +interface Props { + title?: string + items?: SidebarItem[] + position?: "left" | "right" + id?: string + logo?: ImageMetadata +} + +const { title = "Menú", items = [], position = "left", id = "sidebar", logo } = Astro.props +--- + + + + + + diff --git a/src/components/shared/sidebar/Sidebar.tsx b/src/components/shared/sidebar/Sidebar.tsx deleted file mode 100644 index 2e0c8ec..0000000 --- a/src/components/shared/sidebar/Sidebar.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useStore } from "@nanostores/react" -import { IconX } from "@tabler/icons-react" -import { closeSidebarStore, useOpenSidebarStore } from "~/stores/sidebar/open-sidebar-store" - -export type SidebarItem = { - label: string - href?: string - onClick?: () => void -} - -interface SidebarProps { - title?: string - items?: SidebarItem[] - position?: "left" | "right" -} - -export default function Sidebar({ title = "Menú", items = [], position = "left" }: SidebarProps) { - const isOpen = useStore(useOpenSidebarStore) - - const sidePosition = position === "right" ? "right-0 border-l rounded-l-2xl" : "-left-[1px] border-r rounded-r-2xl" - - const getTransformClass = () => { - if (isOpen) return "translate-x-0 opacity-100" - return position === "right" ? "translate-x-full opacity-0" : "-translate-x-full opacity-0" - } - - return ( - <> - {isOpen && ( - - - - - - - ) -} diff --git a/src/components/shared/sidebar/ToggleSidebarButton.astro b/src/components/shared/sidebar/ToggleSidebarButton.astro new file mode 100644 index 0000000..e6651cd --- /dev/null +++ b/src/components/shared/sidebar/ToggleSidebarButton.astro @@ -0,0 +1,40 @@ +--- +import IconMenu4 from "~icons/tabler/menu-4" + +interface Props { + sidebarId?: string + label?: string +} + +const { sidebarId = "sidebar", label = "Menú" } = Astro.props +--- + + + + From 70201b573e64a1fa2e53d91679d328617ccc515e Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:30:55 -0600 Subject: [PATCH 04/12] refactor(content): replace Astro collections with custom content manager --- src/content-manager.ts | 51 ++++++++++++++++++++++++++++++++++++++++++ src/content.config.ts | 33 --------------------------- 2 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 src/content-manager.ts delete mode 100644 src/content.config.ts diff --git a/src/content-manager.ts b/src/content-manager.ts new file mode 100644 index 0000000..9592e68 --- /dev/null +++ b/src/content-manager.ts @@ -0,0 +1,51 @@ +import type { AstroComponentFactory } from "astro/runtime/server/index.js" + +type ContentFrontmatter = { + editor: boolean +} + +type ContentHeading = { + depth: number + slug: string + text: string +} + +type ContentMeta = { + id: number + slug: string + title: string +} + +interface RawContent { + frontmatter: ContentFrontmatter + Content: AstroComponentFactory + getHeadings: () => ContentHeading[] +} + +export type Content = Omit & { + data: ContentFrontmatter + meta: ContentMeta +} + +function extractMetadata(filepath: string) { + const filename = + filepath + .split("/") + .pop() + ?.replace(/\.mdx?$/, "") ?? "" + const [, id, slug] = filename.match(/^(\d+)\.(.+)$/) ?? [] + const title = slug?.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) ?? "" + + return { id: Number(id), slug, title } +} + +const files = import.meta.glob("./content/*.{md,mdx}", { eager: true }) + +export const lessons: Content[] = Object.entries(files) + .map(([filepath, { Content, getHeadings, frontmatter }]) => ({ + Content, + getHeadings, + data: frontmatter, + meta: extractMetadata(filepath), + })) + .sort((a, b) => a.meta.id - b.meta.id) diff --git a/src/content.config.ts b/src/content.config.ts deleted file mode 100644 index 7e8e7d1..0000000 --- a/src/content.config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { defineCollection, getCollection, z } from "astro:content" -import { glob } from "astro/loaders" - -const content = defineCollection({ - loader: glob({ pattern: "*.{md,mdx}", base: "./src/content" }), - schema: z.object({ - title: z.string().optional(), - description: z.string().optional(), - order: z.number(), - nextPath: z.string().optional(), - previousPath: z.string().optional(), - editor: z.boolean().optional(), - }), -}) - -/** - * Obtiene todo el contenido de la colección "content", lo ordena por el campo 'order' - * @returns id (número inicial) y slug (nombre) de cada entrada. - */ -export const allContent = await getCollection("content").then((entries) => { - return entries - .sort((a, b) => a.data.order - b.data.order) - .map((entry) => { - const [, id, slug] = entry.id.match(/^(\d+)(.+)$/) || [] - return { ...entry, id, slug } - }) -}) -/** - * Exporta las colecciones definidas para Astro. - * - * ! ADVERTENCIA: Esta es una variable interna de Astro y no debe ser utilizada directamente en el código de la aplicación. - */ -export const collections = { content } From 9a1f0f9c5ee4245f542443658834c68708291d8f Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:31:05 -0600 Subject: [PATCH 05/12] feat(editor): add Rust playground client and editor store --- src/lib/rust-playground.ts | 117 +++++++++++++++++++++++ src/lib/stores/editor-store.ts | 67 +++++++++++++ src/stores/sidebar/open-sidebar-store.ts | 11 --- 3 files changed, 184 insertions(+), 11 deletions(-) create mode 100644 src/lib/rust-playground.ts create mode 100644 src/lib/stores/editor-store.ts delete mode 100644 src/stores/sidebar/open-sidebar-store.ts diff --git a/src/lib/rust-playground.ts b/src/lib/rust-playground.ts new file mode 100644 index 0000000..e80bdd2 --- /dev/null +++ b/src/lib/rust-playground.ts @@ -0,0 +1,117 @@ +export interface RustPlaygroundConfig { + channel: string + mode: string + edition: string + crateType: string + tests: boolean + backtrace: boolean + code: string +} + +const WS_URL = "wss://play.rust-lang.org/websocket" +const DEFAULT_CONFIG: Partial = { + channel: "stable", + mode: "debug", + edition: "2024", + crateType: "bin", + tests: false, + backtrace: false, +} + +class RustPlaygroundClient { + private ws: WebSocket + private sequenceNumber = 0 + private stderr = "" + private stdout = "" + private latestSequenceNumber = -1 + private onOutputCallback: ((stderr: string, stdout: string, done: boolean) => void) | null = null + + constructor() { + this.ws = new WebSocket(WS_URL) + this.ws.onopen = () => this.sendHandshake() + this.ws.onmessage = (event) => this.handleMessage(event) + this.ws.onerror = () => this.reconnect() + this.ws.onclose = () => this.reconnect() + } + + private sendHandshake(): void { + this.send({ + type: "websocket/connected", + payload: { iAcceptThisIsAnUnsupportedApi: true }, + meta: { websocket: true, sequenceNumber: this.sequenceNumber++ }, + }) + } + + private handleMessage(event: MessageEvent): void { + const data = JSON.parse(event.data) + const messageSeq = data.meta?.sequenceNumber + + if (messageSeq !== undefined && messageSeq < this.latestSequenceNumber) { + // Ignorar mensajes de ejecuciones antiguas + return + } + + if (data.type === "output/execute/wsExecuteStderr") { + this.stderr += data.payload + this.onOutputCallback?.(this.stderr, this.stdout, false) + } else if (data.type === "output/execute/wsExecuteStdout") { + this.stdout += data.payload + this.onOutputCallback?.(this.stderr, this.stdout, false) + } else if (data.type === "output/execute/wsExecuteEnd") { + this.onOutputCallback?.(this.stderr, this.stdout, true) + } + } + + private reconnect(): void { + if (this.ws.readyState === WebSocket.CLOSED) { + setTimeout(() => { + this.ws = new WebSocket(WS_URL) + this.ws.onopen = () => this.sendHandshake() + this.ws.onmessage = (event) => this.handleMessage(event) + this.ws.onerror = () => this.reconnect() + this.ws.onclose = () => this.reconnect() + }, 1000) + } + } + + execute( + code: string, + onOutput: (stderr: string, stdout: string, done: boolean) => void, + config: Partial = {}, + ): void { + this.stderr = "" + this.stdout = "" + this.onOutputCallback = onOutput + + this.latestSequenceNumber = this.sequenceNumber++ + + this.send({ + type: "output/execute/wsExecuteRequest", + payload: { ...DEFAULT_CONFIG, ...config, code }, + meta: { websocket: true, sequenceNumber: this.latestSequenceNumber }, + }) + } + + sendStdin(input: string): void { + this.send({ + type: "output/execute/wsExecuteStdin", + payload: input, + meta: { websocket: true, sequenceNumber: this.latestSequenceNumber }, + }) + } + + private send(data: unknown): void { + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(data)) + } else { + const interval = setInterval(() => { + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(data)) + clearInterval(interval) + } + }, 100) + } + } +} + +export const rustPlayground = new RustPlaygroundClient() diff --git a/src/lib/stores/editor-store.ts b/src/lib/stores/editor-store.ts new file mode 100644 index 0000000..d819a5b --- /dev/null +++ b/src/lib/stores/editor-store.ts @@ -0,0 +1,67 @@ +import { atom } from "nanostores" + +export const editorCode = atom("") + +class RustlingsDB { + private dbPromise: Promise | null = null + private readonly DB_VERSION = 1 + private readonly STORE_CODE = "snippets" + private readonly STORE_PROGRESS = "progress" + + private initDB(): Promise { + if (this.dbPromise) return this.dbPromise + + this.dbPromise = new Promise((resolve, reject) => { + const request = indexedDB.open("rustlings", this.DB_VERSION) + + request.onupgradeneeded = (e) => { + const db = (e.target as IDBOpenDBRequest).result + + if (!db.objectStoreNames.contains(this.STORE_CODE)) { + db.createObjectStore(this.STORE_CODE) + } + + if (!db.objectStoreNames.contains(this.STORE_PROGRESS)) { + db.createObjectStore(this.STORE_PROGRESS) + } + } + + request.onsuccess = (e) => resolve((e.target as IDBOpenDBRequest).result) + request.onerror = () => reject(new Error("Failed to open IndexedDB")) + }) + + return this.dbPromise + } + + private async get(store: string, key: string): Promise { + const db = await this.initDB() + return new Promise((resolve) => { + const request = db.transaction(store, "readonly").objectStore(store).get(key) + request.onsuccess = () => resolve(request.result || null) + request.onerror = () => resolve(null) + }) + } + + private async set(store: string, key: string, value: unknown): Promise { + const db = await this.initDB() + db.transaction(store, "readwrite").objectStore(store).put(value, key) + } + + async saveCode(slug: string, val: string) { + await this.set(this.STORE_CODE, slug, val) + } + + async getCode(slug: string): Promise { + return this.get(this.STORE_CODE, slug) + } + + async markCompleted(slug: string) { + await this.set(this.STORE_PROGRESS, slug, true) + } + + async isCompleted(slug: string): Promise { + return (await this.get(this.STORE_PROGRESS, slug)) || false + } +} + +export const db = new RustlingsDB() diff --git a/src/stores/sidebar/open-sidebar-store.ts b/src/stores/sidebar/open-sidebar-store.ts deleted file mode 100644 index d592c3c..0000000 --- a/src/stores/sidebar/open-sidebar-store.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { atom } from "nanostores" - -export const useOpenSidebarStore = atom(false) - -export const toggleOpenSidebarStore = () => { - useOpenSidebarStore.set(!useOpenSidebarStore.get()) -} - -export const closeSidebarStore = () => { - useOpenSidebarStore.set(false) -} From 01af35a2c9ae21c2a782da29f73964ebac26ee86 Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:31:14 -0600 Subject: [PATCH 06/12] refactor(navbar): consolidate navbar into content feature --- src/components/shared/navbar/Navbar.astro | 45 -------- .../content/components/NavButtons.astro | 107 ++++++++++-------- src/features/content/components/Navbar.astro | 34 ++++++ .../components/ToggleSidebarButton.tsx | 16 --- 4 files changed, 94 insertions(+), 108 deletions(-) delete mode 100644 src/components/shared/navbar/Navbar.astro create mode 100644 src/features/content/components/Navbar.astro delete mode 100644 src/features/content/components/ToggleSidebarButton.tsx diff --git a/src/components/shared/navbar/Navbar.astro b/src/components/shared/navbar/Navbar.astro deleted file mode 100644 index be7ede1..0000000 --- a/src/components/shared/navbar/Navbar.astro +++ /dev/null @@ -1,45 +0,0 @@ ---- -import { Image } from "astro:assets" -import { IconBrandGithub, IconPlayerPlay } from "@tabler/icons-react" -import logoImage from "~/assets/images/webp/logo.webp" -import Sidebar from "~/components/shared/sidebar/Sidebar" -import { allContent } from "~/content.config" -import ToggleSidebarButton from "~/features/content/components/ToggleSidebarButton" - -const sidebarItems = allContent.map((i) => ({ - label: i.slug || "", - href: `/${i.slug}`, -})) ---- - -
- -
diff --git a/src/features/content/components/NavButtons.astro b/src/features/content/components/NavButtons.astro index d6379a9..cf0416c 100644 --- a/src/features/content/components/NavButtons.astro +++ b/src/features/content/components/NavButtons.astro @@ -1,53 +1,66 @@ --- -import { IconArrowBigLeftLinesFilled, IconArrowBigRightLinesFilled } from "@tabler/icons-react" +import IconArrowBigLeftLinesFilled from "~icons/tabler/arrow-big-left-lines-filled" +import IconArrowBigRightLinesFilled from "~icons/tabler/arrow-big-right-lines-filled" -const { ...pagination } = Astro.props +const { previous, next } = Astro.props --- - - + + + + + diff --git a/src/features/content/components/Navbar.astro b/src/features/content/components/Navbar.astro new file mode 100644 index 0000000..9e58eeb --- /dev/null +++ b/src/features/content/components/Navbar.astro @@ -0,0 +1,34 @@ +--- +import logoImage from "~/assets/images/webp/logo.webp" +import Sidebar from "~/components/shared/sidebar/Sidebar.astro" +import ToggleSidebarButton from "~/components/shared/sidebar/ToggleSidebarButton.astro" +import { lessons } from "~/content-manager" +import IconBrandGithub from "~icons/tabler/brand-github" + +const sidebarItems = lessons.map((content) => ({ + label: content.meta.title, + href: `/${content.meta.slug}`, +})) +--- + +
+ +
+ + diff --git a/src/features/content/components/ToggleSidebarButton.tsx b/src/features/content/components/ToggleSidebarButton.tsx deleted file mode 100644 index 331e3de..0000000 --- a/src/features/content/components/ToggleSidebarButton.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { IconMenu4 } from "@tabler/icons-react" -import { toggleOpenSidebarStore } from "~/stores/sidebar/open-sidebar-store" - -export default function ToggleSidebarButton() { - return ( - - ) -} From 6edb4ae839f96c9fb868afccc25ba8ca3e4e94b8 Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:31:23 -0600 Subject: [PATCH 07/12] refactor(home): replace React icons with unplugin-icons --- src/features/home/components/Community.astro | 5 ++--- src/features/home/components/Hero.astro | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/features/home/components/Community.astro b/src/features/home/components/Community.astro index 27746c5..151dfad 100644 --- a/src/features/home/components/Community.astro +++ b/src/features/home/components/Community.astro @@ -1,7 +1,6 @@ --- -import { Image } from "astro:assets" -import discordMarkImage from "~/assets/images/webp/discord-mark-white.webp" import Section from "~/components/shared/sections/Section.astro" +import FaBrandsDiscord from "~icons/fa-brands/discord" ---
- Logo Discord + Discord diff --git a/src/features/home/components/Hero.astro b/src/features/home/components/Hero.astro index 8045dfb..08eb56e 100644 --- a/src/features/home/components/Hero.astro +++ b/src/features/home/components/Hero.astro @@ -1,11 +1,8 @@ --- import { Image } from "astro:assets" -import { IconBrandGithub } from "@tabler/icons-react" import logoImage from "~/assets/images/webp/logo.webp" import Section from "~/components/shared/sections/Section.astro" -import { allContent } from "~/content.config" - -const content = allContent.find((c) => c.slug) +import JamGithub from "~icons/jam/github" ---
c.slug) class="absolute top-4 right-6 text-secondary hover:text-yellow transition-transform duration-300 hover:scale-110" aria-label="Repositorio de RustLings en GitHub" > - + c.slug) Aprender From b4d3aa844795d179abf2ccacb6689d777d136e62 Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:31:32 -0600 Subject: [PATCH 08/12] refactor(layout): add page transitions and improve structure --- src/layouts/Layout.astro | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 8fa4807..ffaec8a 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,5 +1,7 @@ --- +import { ClientRouter } from "astro:transitions" import "~/styles/global.css" +import favicon from "~/assets/images/webp/favicon.webp" const { title = "RustLings Web", @@ -11,13 +13,13 @@ const { - - - - {title} + + + + - + From 24a65119b9902fdab62cd984dadd0ecc901ac00c Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:31:41 -0600 Subject: [PATCH 09/12] refactor(pages): rewrite content page with new resizable layout --- src/pages/[slug].astro | 327 +++++++++++++++-------------------------- 1 file changed, 120 insertions(+), 207 deletions(-) diff --git a/src/pages/[slug].astro b/src/pages/[slug].astro index a62a257..f0c0004 100644 --- a/src/pages/[slug].astro +++ b/src/pages/[slug].astro @@ -1,244 +1,157 @@ --- -import { render } from "astro:content" -import { IconCode, IconGripVertical, IconLicense, IconTerminal } from "@tabler/icons-react" -import { CodeEditor } from "~/components/shared/editor/CodeEditor" -import Navbar from "~/components/shared/navbar/Navbar.astro" -import Section from "~/components/shared/sections/Section.astro" -import { allContent } from "~/content.config" +import CodeEditor from "~/components/shared/editor/CodeEditor.astro" +import Terminal from "~/components/shared/editor/output/Terminal.astro" +import { lessons } from "~/content-manager" import NavButtons from "~/features/content/components/NavButtons.astro" +import Navbar from "~/features/content/components/Navbar.astro" import Layout from "~/layouts/Layout.astro" +import IconBook from "~icons/tabler/book" +import IconCode from "~icons/tabler/code" +import IconGripVertical from "~icons/tabler/grip-vertical" export function getStaticPaths() { - return allContent.map((tutorial) => { - return { - params: { slug: tutorial.slug }, - props: { - tutorial, - previous: tutorial.data.previousPath || null, - next: tutorial.data.nextPath || null, - }, - } - }) + return lessons.map((lesson, index) => ({ + params: { slug: lesson.meta.slug }, + props: { + lesson, + previousLesson: lessons[index - 1], + nextLesson: lessons[index + 1], + }, + })) } -const { tutorial, previous, next } = Astro.props -const { Content } = await render(tutorial) +const { lesson, previousLesson, nextLesson } = Astro.props --- -
- -
+ +
- - - +
+ - From fc34ade8bea7659fae1f7a19ffdd049fde48e52f Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:31:51 -0600 Subject: [PATCH 10/12] style: update global styles and scrollbar behavior --- src/styles/global.css | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/styles/global.css b/src/styles/global.css index 155feb2..fd37941 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@import "@fontsource/poppins/400.css"; @import "@fontsource/poppins/500.css"; @import "@fontsource/poppins/600.css"; @import "@fontsource/poppins/700.css"; @@ -42,20 +43,21 @@ } } -.scroll-container { +* { scrollbar-width: thin; scrollbar-color: var(--color-editor-bg) transparent; } -.scroll-container::-webkit-scrollbar { +*::-webkit-scrollbar { width: 8px; + height: 8px; } -.scroll-container::-webkit-scrollbar-thumb { +*::-webkit-scrollbar-thumb { background-color: transparent; border-radius: 8px; } -.scroll-container::-webkit-scrollbar-thumb:hover { +*::-webkit-scrollbar-thumb:hover { background-color: var(--color-editor-bg); } From 09d1440f3c3ac9697efa724fad1bf33b8423a98c Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:32:00 -0600 Subject: [PATCH 11/12] build: add unplugin-icons and remove React dependencies --- astro.config.mjs | 10 +- package-lock.json | 1092 +++++++++++---------------------------------- package.json | 13 +- tsconfig.json | 3 +- 4 files changed, 276 insertions(+), 842 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 25a2a73..25a3fe0 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,18 +1,22 @@ // @ts-check import mdx from "@astrojs/mdx" -import react from "@astrojs/react" import tailwindcss from "@tailwindcss/vite" import { defineConfig } from "astro/config" +import Icons from "unplugin-icons/vite" // https://astro.build/config export default defineConfig({ vite: { - plugins: [tailwindcss()], + plugins: [ + tailwindcss(), + Icons({ + compiler: "astro", + }), + ], }, integrations: [ - react(), mdx({ syntaxHighlight: "shiki", shikiConfig: { theme: "dracula" }, diff --git a/package-lock.json b/package-lock.json index d9957c1..3c4756a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.1", "dependencies": { "@astrojs/mdx": "^4.3.8", - "@astrojs/react": "^4.4.0", "@codemirror/autocomplete": "^6.19.1", "@codemirror/lang-rust": "^6.0.2", "@codemirror/language": "^6.11.3", @@ -19,21 +18,41 @@ "@lezer/highlight": "^1.2.2", "@lezer/rust": "^1.0.2", "@nanostores/react": "^1.0.0", - "@tabler/icons-react": "^3.35.0", "@tailwindcss/vite": "^4.1.16", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", - "@uiw/codemirror-themes": "^4.25.2", - "@uiw/react-codemirror": "^4.25.2", "astro": "^5.15.0", + "codemirror": "^6.0.2", "nanostores": "^1.0.1", - "react": "^19.2.0", - "react-dom": "^19.2.0", "tailwindcss": "^4.1.16" }, "devDependencies": { "@biomejs/biome": "2.3.2", - "lefthook": "^2.0.0" + "@iconify/json": "^2.2.405", + "lefthook": "^2.0.0", + "unplugin-icons": "^22.5.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" } }, "node_modules/@astrojs/compiler": { @@ -108,24 +127,6 @@ "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, - "node_modules/@astrojs/react": { - "version": "4.4.0", - "license": "MIT", - "dependencies": { - "@vitejs/plugin-react": "^4.7.0", - "ultrahtml": "^1.6.0", - "vite": "^6.3.6" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" - }, - "peerDependencies": { - "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", - "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", - "react": "^17.0.2 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/@astrojs/telemetry": { "version": "3.3.0", "license": "MIT", @@ -142,223 +143,6 @@ "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/@babel/parser": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/parser": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "license": "MIT", @@ -373,42 +157,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { "version": "7.28.4", "license": "MIT", @@ -422,131 +170,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/parser": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/parser": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { "version": "7.28.4", "license": "MIT", @@ -780,9 +403,9 @@ } }, "node_modules/@codemirror/lint": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.1.tgz", - "integrity": "sha512-te7To1EQHePBQQzasDKWmK2xKINIXpk+xAiSYr9ZN+VB4KaT+/Hi2PEkeErTk5BV3PTz1TLyQL4MtJfPkKZ9sw==", + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", + "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -810,24 +433,11 @@ "@marijn/find-cluster-break": "^1.0.0" } }, - "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", - "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/highlight": "^1.0.0" - } - }, "node_modules/@codemirror/view": { "version": "6.38.6", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1268,6 +878,41 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@iconify/json": { + "version": "2.2.405", + "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.405.tgz", + "integrity": "sha512-wpZMsZoqt6tHbipJ/JmkhRK1TWkmRrTBfL2pi48qXyqUxF56+OEUVB61VkcQ1hMKj/tDAIicFSirkaRZkMwliw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*", + "pathe": "^2.0.3" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.1", + "globals": "^15.15.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "mlly": "^1.7.4" + } + }, "node_modules/@img/colour": { "version": "1.0.0", "license": "MIT", @@ -1828,10 +1473,6 @@ "version": "1.1.0", "license": "MIT" }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "license": "MIT" - }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "license": "MIT", @@ -2198,28 +1839,6 @@ "tslib": "^2.8.0" } }, - "node_modules/@tabler/icons": { - "version": "3.35.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/codecalm" - } - }, - "node_modules/@tabler/icons-react": { - "version": "3.35.0", - "license": "MIT", - "dependencies": { - "@tabler/icons": "3.35.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/codecalm" - }, - "peerDependencies": { - "react": ">= 16" - } - }, "node_modules/@tailwindcss/node": { "version": "4.1.16", "license": "MIT", @@ -2469,106 +2088,6 @@ "vite": "^5.2.0 || ^6 || ^7" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__generator/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/babel__generator/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template/node_modules/@babel/parser": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@types/babel__template/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/babel__template/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/babel__traverse/node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/babel__traverse/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@types/debug": { "version": "4.1.12", "license": "MIT", @@ -2614,140 +2133,34 @@ "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/@types/nlcst": { - "version": "2.0.3", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/node": { - "version": "24.9.1", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.2", - "license": "MIT", - "peer": true, - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.2", - "license": "MIT", - "peer": true, - "peerDependencies": { - "@types/react": "^19.2.0" - } + "license": "MIT" }, - "node_modules/@types/unist": { - "version": "3.0.3", + "node_modules/@types/ms": { + "version": "2.1.0", "license": "MIT" }, - "node_modules/@uiw/codemirror-extensions-basic-setup": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.2.tgz", - "integrity": "sha512-s2fbpdXrSMWEc86moll/d007ZFhu6jzwNu5cWv/2o7egymvLeZO52LWkewgbr+BUCGWGPsoJVWeaejbsb/hLcw==", + "node_modules/@types/nlcst": { + "version": "2.0.3", "license": "MIT", "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - }, - "funding": { - "url": "https://jaywcjlove.github.io/#/sponsor" - }, - "peerDependencies": { - "@codemirror/autocomplete": ">=6.0.0", - "@codemirror/commands": ">=6.0.0", - "@codemirror/language": ">=6.0.0", - "@codemirror/lint": ">=6.0.0", - "@codemirror/search": ">=6.0.0", - "@codemirror/state": ">=6.0.0", - "@codemirror/view": ">=6.0.0" + "@types/unist": "*" } }, - "node_modules/@uiw/codemirror-themes": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.2.tgz", - "integrity": "sha512-WFYxW3OlCkMomXQBlQdGj1JZ011UNCa7xYdmgYqywVc4E8f5VgIzRwCZSBNVjpWGGDBOjc+Z6F65l7gttP16pg==", + "node_modules/@types/node": { + "version": "24.9.1", "license": "MIT", "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - }, - "funding": { - "url": "https://jaywcjlove.github.io/#/sponsor" - }, - "peerDependencies": { - "@codemirror/language": ">=6.0.0", - "@codemirror/state": ">=6.0.0", - "@codemirror/view": ">=6.0.0" + "undici-types": "~7.16.0" } }, - "node_modules/@uiw/react-codemirror": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.2.tgz", - "integrity": "sha512-XP3R1xyE0CP6Q0iR0xf3ed+cJzJnfmbLelgJR6osVVtMStGGZP3pGQjjwDRYptmjGHfEELUyyBLdY25h0BQg7w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.6", - "@codemirror/commands": "^6.1.0", - "@codemirror/state": "^6.1.1", - "@codemirror/theme-one-dark": "^6.0.0", - "@uiw/codemirror-extensions-basic-setup": "4.25.2", - "codemirror": "^6.0.0" - }, - "funding": { - "url": "https://jaywcjlove.github.io/#/sponsor" - }, - "peerDependencies": { - "@babel/runtime": ">=7.11.0", - "@codemirror/state": ">=6.0.0", - "@codemirror/theme-one-dark": ">=6.0.0", - "@codemirror/view": ">=6.0.0", - "codemirror": ">=6.0.0", - "react": ">=17.0.0", - "react-dom": ">=17.0.0" - } + "node_modules/@types/unist": { + "version": "3.0.3", + "license": "MIT" }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "license": "ISC" }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, "node_modules/acorn": { "version": "8.15.0", "license": "MIT", @@ -2998,13 +2411,6 @@ ], "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.20", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, "node_modules/boxen": { "version": "8.0.1", "license": "MIT", @@ -3032,38 +2438,6 @@ "base64-js": "^1.1.2" } }, - "node_modules/browserslist": { - "version": "4.27.0", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/camelcase": { "version": "8.0.0", "license": "MIT", @@ -3074,24 +2448,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001751", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/ccount": { "version": "2.0.1", "license": "MIT", @@ -3231,8 +2587,11 @@ "version": "1.0.1", "license": "ISC" }, - "node_modules/convert-source-map": { - "version": "2.0.0", + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -3280,10 +2639,6 @@ "node": ">=4" } }, - "node_modules/csstype": { - "version": "3.1.3", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "license": "MIT", @@ -3379,10 +2734,6 @@ "node": ">=4" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.239", - "license": "ISC" - }, "node_modules/emoji-regex": { "version": "10.6.0", "license": "MIT" @@ -3483,13 +2834,6 @@ "@esbuild/win32-x64": "0.25.11" } }, - "node_modules/escalade": { - "version": "3.2.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "5.0.0", "license": "MIT", @@ -3593,6 +2937,13 @@ "version": "5.0.1", "license": "MIT" }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true, + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "license": "MIT" @@ -3660,13 +3011,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-east-asian-width": { "version": "1.4.0", "license": "MIT", @@ -3681,6 +3025,19 @@ "version": "2.0.0", "license": "ISC" }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "license": "ISC" @@ -4067,10 +3424,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "license": "MIT" - }, "node_modules/js-yaml": { "version": "4.1.0", "license": "MIT", @@ -4081,26 +3434,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/kleur": { "version": "3.0.3", "license": "MIT", @@ -4108,6 +3441,13 @@ "node": ">=6" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lefthook": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-2.0.1.tgz", @@ -4516,6 +3856,24 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "license": "MIT", @@ -5505,6 +4863,38 @@ ], "license": "MIT" }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/mrmime": { "version": "2.0.1", "license": "MIT", @@ -5574,10 +4964,6 @@ "version": "1.0.3", "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.26", - "license": "MIT" - }, "node_modules/normalize-path": { "version": "3.0.0", "license": "MIT", @@ -5707,6 +5093,13 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -5721,6 +5114,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/postcss": { "version": "8.5.6", "funding": [ @@ -5773,6 +5178,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/radix3": { "version": "1.1.2", "license": "MIT" @@ -5785,24 +5207,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-dom": { - "version": "19.2.0", - "license": "MIT", - "peer": true, - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.0" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readdirp": { "version": "4.1.2", "license": "MIT", @@ -6149,10 +5553,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/scheduler": { - "version": "0.27.0", - "license": "MIT" - }, "node_modules/semver": { "version": "7.7.3", "license": "ISC", @@ -6593,6 +5993,67 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unplugin": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-icons": { + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-22.5.0.tgz", + "integrity": "sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/utils": "^3.0.2", + "debug": "^4.4.3", + "local-pkg": "^1.1.2", + "unplugin": "^2.3.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@svgx/core": "^1.0.1", + "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0", + "vue-template-compiler": "^2.6.12", + "vue-template-es2015-compiler": "^1.9.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@svgx/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + }, + "vue-template-es2015-compiler": { + "optional": true + } + } + }, "node_modules/unstorage": { "version": "1.17.1", "license": "MIT", @@ -6687,34 +6148,6 @@ } } }, - "node_modules/update-browserslist-db": { - "version": "1.1.4", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/vfile": { "version": "6.0.3", "license": "MIT", @@ -6855,6 +6288,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/which-pm-runs": { "version": "1.1.0", "license": "MIT", @@ -6894,10 +6334,6 @@ "version": "1.1.0", "license": "MIT" }, - "node_modules/yallist": { - "version": "3.1.1", - "license": "ISC" - }, "node_modules/yargs-parser": { "version": "21.1.1", "license": "ISC", diff --git a/package.json b/package.json index b41ddeb..d4f4b4f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ }, "dependencies": { "@astrojs/mdx": "^4.3.8", - "@astrojs/react": "^4.4.0", "@codemirror/autocomplete": "^6.19.1", "@codemirror/lang-rust": "^6.0.2", "@codemirror/language": "^6.11.3", @@ -24,20 +23,16 @@ "@lezer/highlight": "^1.2.2", "@lezer/rust": "^1.0.2", "@nanostores/react": "^1.0.0", - "@tabler/icons-react": "^3.35.0", "@tailwindcss/vite": "^4.1.16", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", - "@uiw/codemirror-themes": "^4.25.2", - "@uiw/react-codemirror": "^4.25.2", "astro": "^5.15.0", + "codemirror": "^6.0.2", "nanostores": "^1.0.1", - "react": "^19.2.0", - "react-dom": "^19.2.0", "tailwindcss": "^4.1.16" }, "devDependencies": { "@biomejs/biome": "2.3.2", - "lefthook": "^2.0.0" + "@iconify/json": "^2.2.405", + "lefthook": "^2.0.0", + "unplugin-icons": "^22.5.0" } } diff --git a/tsconfig.json b/tsconfig.json index ed97de3..ef24096 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,7 @@ "exclude": ["dist"], "compilerOptions": { "baseUrl": ".", - "jsx": "react-jsx", - "jsxImportSource": "react", + "types": ["unplugin-icons/types/astro"], "paths": { "~/*": ["./src/*"] } From bb5a3e380916ec143c1074622ea7bb112a305a76 Mon Sep 17 00:00:00 2001 From: ArturoAtomplay <64356325+ArturoAtomplay@users.noreply.github.com> Date: Wed, 12 Nov 2025 04:32:08 -0600 Subject: [PATCH 12/12] refactor(footer): replace React icons with unplugin-icons --- src/components/shared/footer/Footer.astro | 48 +++++++++++++---------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/components/shared/footer/Footer.astro b/src/components/shared/footer/Footer.astro index 065160d..d732b59 100644 --- a/src/components/shared/footer/Footer.astro +++ b/src/components/shared/footer/Footer.astro @@ -1,43 +1,49 @@ --- import { Image } from "astro:assets" -import { IconBrandDiscord, IconBrandGithub, IconBrandLinkedin } from "@tabler/icons-react" import logoImage from "~/assets/images/webp/logo.webp" - -const githubUrl = "https://github.com/RustLangES/rustlings-web" -const linkedinUrl = "https://www.linkedin.com/company/rustlanges/posts/?feedView=all" -const discordUrl = "https://discord.com/channels/778674594856960012/1292726289479893044" +import IconBrandDiscord from "~icons/tabler/brand-discord" +import IconBrandGithub from "~icons/tabler/brand-github" +import IconBrandLinkedin from "~icons/tabler/brand-linkedin" const socialLinks = [ - { href: githubUrl, icon: IconBrandGithub, label: "GitHub" }, - { href: linkedinUrl, icon: IconBrandLinkedin, label: "LinkedIn" }, - { href: discordUrl, icon: IconBrandDiscord, label: "Discord" }, + { href: "https://github.com/RustLangES/rustlings-web", icon: IconBrandGithub, label: "GitHub" }, + { + href: "https://www.linkedin.com/company/rustlanges/posts/?feedView=all", + icon: IconBrandLinkedin, + label: "LinkedIn", + }, + { + href: "https://discord.com/channels/778674594856960012/1292726289479893044", + icon: IconBrandDiscord, + label: "Discord", + }, ] ---