doxla 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cli/index.js +3 -7
- package/dist/cli/index.js.map +1 -1
- package/package.json +3 -2
- package/src/app/package.json +1 -0
- package/src/app/src/assets/logo.png +0 -0
- package/src/app/src/assets/logo.svg +31 -0
- package/src/app/src/components/MarkdownRenderer.tsx +2 -1
- package/src/app/src/components/layout/Header.tsx +3 -2
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.png" alt="Doxla" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# doxla
|
|
2
6
|
|
|
7
|
+
[](https://ready.kerex.app/analysis/alsiola/doxla)
|
|
8
|
+
|
|
3
9
|
Improve documentation discoverability within repos. Doxla discovers all `.md` and `.mdx` files in your repository, builds a beautiful docs viewer, and deploys it to GitHub Pages.
|
|
4
10
|
|
|
5
11
|
## Why?
|
package/dist/cli/index.js
CHANGED
|
@@ -107,18 +107,14 @@ function extractTitleAndContent(content, filePath) {
|
|
|
107
107
|
const lines = content.split("\n");
|
|
108
108
|
for (let i = 0; i < lines.length; i++) {
|
|
109
109
|
if (lines[i].trim() === "") continue;
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
const title =
|
|
110
|
+
const match = lines[i].match(/^#\s+(.+)$/);
|
|
111
|
+
if (match) {
|
|
112
|
+
const title = match[1].trim();
|
|
113
113
|
const remaining = lines.slice(i + 1).join("\n").trimStart();
|
|
114
114
|
return { title, content: remaining };
|
|
115
115
|
}
|
|
116
116
|
break;
|
|
117
117
|
}
|
|
118
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
119
|
-
if (match) {
|
|
120
|
-
return { title: match[1].trim(), content };
|
|
121
|
-
}
|
|
122
118
|
const ext = filePath.endsWith(".mdx") ? ".mdx" : ".md";
|
|
123
119
|
const name = basename(filePath, ext);
|
|
124
120
|
if (name.toLowerCase() === "readme") {
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/init.ts","../../src/cli/templates/doxla-workflow.ts","../../src/cli/commands/build.ts","../../src/cli/lib/discover.ts","../../src/cli/lib/manifest.ts","../../src/cli/lib/build-app.ts","../../src/cli/lib/template.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { buildCommand } from \"./commands/build.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"doxla\")\n .description(\"Improve documentation discoverability within repos\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"init\")\n .description(\"Set up GitHub Actions workflow for Doxla docs deployment\")\n .action(initCommand);\n\nprogram\n .command(\"build\")\n .description(\"Discover markdown files and build the docs viewer\")\n .option(\"-o, --output <dir>\", \"Output directory\", \"doxla-dist\")\n .option(\"-r, --root <dir>\", \"Root directory to scan for markdown files\", \".\")\n .option(\"--base-path <path>\", \"Base path for GitHub Pages deployment\", \"/\")\n .action(buildCommand);\n\nprogram.parse();\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { workflowTemplate } from \"../templates/doxla-workflow.js\";\n\nexport async function initCommand() {\n const workflowDir = join(process.cwd(), \".github\", \"workflows\");\n const workflowPath = join(workflowDir, \"doxla.yml\");\n\n if (existsSync(workflowPath)) {\n console.log(\n chalk.yellow(\"⚠ Workflow file already exists at .github/workflows/doxla.yml\")\n );\n console.log(chalk.yellow(\" Overwriting with latest template...\"));\n }\n\n await mkdir(workflowDir, { recursive: true });\n await writeFile(workflowPath, workflowTemplate, \"utf-8\");\n\n console.log(chalk.green(\"✓ Created .github/workflows/doxla.yml\"));\n console.log();\n console.log(\"Next steps:\");\n console.log(\n ` 1. Enable GitHub Pages in your repo settings (Settings → Pages → Source: ${chalk.bold(\"GitHub Actions\")})`\n );\n console.log(\" 2. Commit and push the workflow file\");\n console.log(\n \" 3. Your docs will be built and deployed on every push to main\"\n );\n}\n","export const workflowTemplate = `name: Deploy Doxla Docs\n\non:\n push:\n branches: [main]\n workflow_dispatch:\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\nconcurrency:\n group: \"pages\"\n cancel-in-progress: false\n\njobs:\n build-and-deploy:\n runs-on: ubuntu-latest\n environment:\n name: github-pages\n url: \\${{ steps.deployment.outputs.page_url }}\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n - run: npx doxla@latest build --base-path \"/\\${{ github.event.repository.name }}\"\n - uses: actions/upload-pages-artifact@v3\n with:\n path: ./doxla-dist\n - id: deployment\n uses: actions/deploy-pages@v4\n`;\n","import { resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { discoverMarkdownFiles } from \"../lib/discover.js\";\nimport { generateManifest } from \"../lib/manifest.js\";\nimport { buildApp } from \"../lib/build-app.js\";\n\ninterface BuildOptions {\n output: string;\n root: string;\n basePath: string;\n}\n\nexport async function buildCommand(options: BuildOptions) {\n const rootDir = resolve(options.root);\n const outputDir = resolve(options.output);\n const basePath = options.basePath;\n\n console.log(chalk.bold(\"doxla build\"));\n console.log();\n\n // Step 1: Discover markdown files\n const discoverSpinner = ora(\"Discovering markdown files...\").start();\n const files = await discoverMarkdownFiles(rootDir);\n\n if (files.length === 0) {\n discoverSpinner.fail(\"No markdown files found\");\n process.exit(1);\n }\n\n discoverSpinner.succeed(`Found ${files.length} markdown file${files.length === 1 ? \"\" : \"s\"}`);\n\n // Step 2: Generate manifest\n const manifestSpinner = ora(\"Generating manifest...\").start();\n const manifest = await generateManifest(rootDir, files);\n manifestSpinner.succeed(\"Manifest generated\");\n\n // Step 3: Build docs viewer\n const buildSpinner = ora(\"Building docs viewer...\").start();\n try {\n await buildApp(manifest, { output: outputDir, basePath });\n buildSpinner.succeed(\"Docs viewer built\");\n } catch (error) {\n buildSpinner.fail(\"Build failed\");\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green(`✓ Output written to ${options.output}/`));\n}\n","import fg from \"fast-glob\";\n\nconst IGNORE_PATTERNS = [\n \"**/node_modules/**\",\n \"**/.git/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/doxla-dist/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n];\n\nexport async function discoverMarkdownFiles(rootDir: string): Promise<string[]> {\n const files = await fg(\"**/*.{md,mdx}\", {\n cwd: rootDir,\n ignore: IGNORE_PATTERNS,\n onlyFiles: true,\n });\n\n // Sort: README.md/README.mdx first, then alphabetical\n return files.sort((a, b) => {\n const aIsReadme = /^readme\\.mdx?$/i.test(a);\n const bIsReadme = /^readme\\.mdx?$/i.test(b);\n if (aIsReadme && !bIsReadme) return -1;\n if (!aIsReadme && bIsReadme) return 1;\n return a.localeCompare(b);\n });\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join, basename, dirname } from \"node:path\";\nimport type { Manifest, DocFile } from \"../../app/src/types/manifest.js\";\n\nfunction extractTitleAndContent(\n content: string,\n filePath: string\n): { title: string; content: string } {\n // Check if the first non-blank line is a # heading\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].trim() === \"\") continue;\n\n const match = lines[i].match(/^#\\s+(.+)$/);\n if (match) {\n const title = match[1].trim();\n const remaining = lines.slice(i + 1).join(\"\\n\").trimStart();\n return { title, content: remaining };\n }\n break;\n }\n\n // No leading heading — try any heading in the document for the title\n const match = content.match(/^#\\s+(.+)$/m);\n if (match) {\n return { title: match[1].trim(), content };\n }\n\n // Fallback to filename without extension\n const ext = filePath.endsWith(\".mdx\") ? \".mdx\" : \".md\";\n const name = basename(filePath, ext);\n if (name.toLowerCase() === \"readme\") {\n const dir = dirname(filePath);\n if (dir === \".\") return { title: \"README\", content };\n return { title: `${dir} - README`, content };\n }\n return {\n title: name.replace(/[-_]/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase()),\n content,\n };\n}\n\nfunction createSlug(filePath: string): string {\n return filePath\n .replace(/\\.mdx?$/i, \"\")\n .replace(/\\\\/g, \"/\")\n .toLowerCase()\n .replace(/[^a-z0-9/.-]/g, \"-\");\n}\n\nexport async function generateManifest(\n rootDir: string,\n files: string[]\n): Promise<Manifest> {\n const docs: DocFile[] = await Promise.all(\n files.map(async (filePath) => {\n const fullPath = join(rootDir, filePath);\n const raw = await readFile(fullPath, \"utf-8\");\n const { title, content } = extractTitleAndContent(raw, filePath);\n return {\n slug: createSlug(filePath),\n path: filePath,\n title,\n content,\n };\n })\n );\n\n const repoName = basename(rootDir);\n\n return {\n repoName,\n generatedAt: new Date().toISOString(),\n totalDocs: docs.length,\n docs,\n };\n}\n","import { cp, writeFile, rm, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport type { Manifest } from \"../../app/src/types/manifest.js\";\nimport { getTemplatePath } from \"./template.js\";\n\ninterface BuildOptions {\n output: string;\n basePath: string;\n}\n\nexport async function buildApp(\n manifest: Manifest,\n options: BuildOptions\n): Promise<void> {\n const templateDir = getTemplatePath();\n const tempDir = join(\n tmpdir(),\n `doxla-build-${randomBytes(6).toString(\"hex\")}`\n );\n\n try {\n // Copy template to temp dir\n await cp(templateDir, tempDir, { recursive: true });\n\n // Write manifest.json into the app's src directory\n await writeFile(\n join(tempDir, \"src\", \"manifest.json\"),\n JSON.stringify(manifest, null, 2),\n \"utf-8\"\n );\n\n // Install dependencies\n execSync(\"npm install --no-audit --no-fund\", {\n cwd: tempDir,\n stdio: \"pipe\",\n });\n\n // Build with Vite, passing base path as env var\n execSync(\"npx vite build\", {\n cwd: tempDir,\n stdio: \"pipe\",\n env: { ...process.env, VITE_BASE_PATH: options.basePath },\n });\n\n // Copy output\n await rm(options.output, { recursive: true, force: true });\n await mkdir(options.output, { recursive: true });\n await cp(join(tempDir, \"dist\"), options.output, { recursive: true });\n } finally {\n // Clean up temp dir\n await rm(tempDir, { recursive: true, force: true }).catch(() => {});\n }\n}\n","import { fileURLToPath } from \"node:url\";\nimport { dirname, resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nexport function getTemplatePath(): string {\n const __filename = fileURLToPath(import.meta.url);\n let dir = dirname(__filename);\n\n // Walk up from the current file location until we find src/app\n // Works both from source (src/cli/lib/) and built (dist/cli/)\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(dir, \"src\", \"app\");\n if (existsSync(resolve(candidate, \"package.json\"))) {\n return candidate;\n }\n dir = dirname(dir);\n }\n\n // Fallback: assume built location dist/cli/ -> ../../src/app\n const fallback = resolve(dirname(__filename), \"..\", \"..\", \"src\", \"app\");\n return fallback;\n}\n"],"mappings":"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","names":["resolve","chalk","join","match","writeFile","mkdir","join","dirname","existsSync","join","writeFile","mkdir","resolve","chalk"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/init.ts","../../src/cli/templates/doxla-workflow.ts","../../src/cli/commands/build.ts","../../src/cli/lib/discover.ts","../../src/cli/lib/manifest.ts","../../src/cli/lib/build-app.ts","../../src/cli/lib/template.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { buildCommand } from \"./commands/build.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"doxla\")\n .description(\"Improve documentation discoverability within repos\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"init\")\n .description(\"Set up GitHub Actions workflow for Doxla docs deployment\")\n .action(initCommand);\n\nprogram\n .command(\"build\")\n .description(\"Discover markdown files and build the docs viewer\")\n .option(\"-o, --output <dir>\", \"Output directory\", \"doxla-dist\")\n .option(\"-r, --root <dir>\", \"Root directory to scan for markdown files\", \".\")\n .option(\"--base-path <path>\", \"Base path for GitHub Pages deployment\", \"/\")\n .action(buildCommand);\n\nprogram.parse();\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { workflowTemplate } from \"../templates/doxla-workflow.js\";\n\nexport async function initCommand() {\n const workflowDir = join(process.cwd(), \".github\", \"workflows\");\n const workflowPath = join(workflowDir, \"doxla.yml\");\n\n if (existsSync(workflowPath)) {\n console.log(\n chalk.yellow(\"⚠ Workflow file already exists at .github/workflows/doxla.yml\")\n );\n console.log(chalk.yellow(\" Overwriting with latest template...\"));\n }\n\n await mkdir(workflowDir, { recursive: true });\n await writeFile(workflowPath, workflowTemplate, \"utf-8\");\n\n console.log(chalk.green(\"✓ Created .github/workflows/doxla.yml\"));\n console.log();\n console.log(\"Next steps:\");\n console.log(\n ` 1. Enable GitHub Pages in your repo settings (Settings → Pages → Source: ${chalk.bold(\"GitHub Actions\")})`\n );\n console.log(\" 2. Commit and push the workflow file\");\n console.log(\n \" 3. Your docs will be built and deployed on every push to main\"\n );\n}\n","export const workflowTemplate = `name: Deploy Doxla Docs\n\non:\n push:\n branches: [main]\n workflow_dispatch:\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\nconcurrency:\n group: \"pages\"\n cancel-in-progress: false\n\njobs:\n build-and-deploy:\n runs-on: ubuntu-latest\n environment:\n name: github-pages\n url: \\${{ steps.deployment.outputs.page_url }}\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n - run: npx doxla@latest build --base-path \"/\\${{ github.event.repository.name }}\"\n - uses: actions/upload-pages-artifact@v3\n with:\n path: ./doxla-dist\n - id: deployment\n uses: actions/deploy-pages@v4\n`;\n","import { resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { discoverMarkdownFiles } from \"../lib/discover.js\";\nimport { generateManifest } from \"../lib/manifest.js\";\nimport { buildApp } from \"../lib/build-app.js\";\n\ninterface BuildOptions {\n output: string;\n root: string;\n basePath: string;\n}\n\nexport async function buildCommand(options: BuildOptions) {\n const rootDir = resolve(options.root);\n const outputDir = resolve(options.output);\n const basePath = options.basePath;\n\n console.log(chalk.bold(\"doxla build\"));\n console.log();\n\n // Step 1: Discover markdown files\n const discoverSpinner = ora(\"Discovering markdown files...\").start();\n const files = await discoverMarkdownFiles(rootDir);\n\n if (files.length === 0) {\n discoverSpinner.fail(\"No markdown files found\");\n process.exit(1);\n }\n\n discoverSpinner.succeed(`Found ${files.length} markdown file${files.length === 1 ? \"\" : \"s\"}`);\n\n // Step 2: Generate manifest\n const manifestSpinner = ora(\"Generating manifest...\").start();\n const manifest = await generateManifest(rootDir, files);\n manifestSpinner.succeed(\"Manifest generated\");\n\n // Step 3: Build docs viewer\n const buildSpinner = ora(\"Building docs viewer...\").start();\n try {\n await buildApp(manifest, { output: outputDir, basePath });\n buildSpinner.succeed(\"Docs viewer built\");\n } catch (error) {\n buildSpinner.fail(\"Build failed\");\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.green(`✓ Output written to ${options.output}/`));\n}\n","import fg from \"fast-glob\";\n\nconst IGNORE_PATTERNS = [\n \"**/node_modules/**\",\n \"**/.git/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/doxla-dist/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n];\n\nexport async function discoverMarkdownFiles(rootDir: string): Promise<string[]> {\n const files = await fg(\"**/*.{md,mdx}\", {\n cwd: rootDir,\n ignore: IGNORE_PATTERNS,\n onlyFiles: true,\n });\n\n // Sort: README.md/README.mdx first, then alphabetical\n return files.sort((a, b) => {\n const aIsReadme = /^readme\\.mdx?$/i.test(a);\n const bIsReadme = /^readme\\.mdx?$/i.test(b);\n if (aIsReadme && !bIsReadme) return -1;\n if (!aIsReadme && bIsReadme) return 1;\n return a.localeCompare(b);\n });\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join, basename, dirname } from \"node:path\";\nimport type { Manifest, DocFile } from \"../../app/src/types/manifest.js\";\n\nfunction extractTitleAndContent(\n content: string,\n filePath: string\n): { title: string; content: string } {\n // Check if the first non-blank line is a # heading\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].trim() === \"\") continue;\n\n const match = lines[i].match(/^#\\s+(.+)$/);\n if (match) {\n const title = match[1].trim();\n const remaining = lines.slice(i + 1).join(\"\\n\").trimStart();\n return { title, content: remaining };\n }\n break;\n }\n\n // No leading heading — fallback to filename\n const ext = filePath.endsWith(\".mdx\") ? \".mdx\" : \".md\";\n const name = basename(filePath, ext);\n if (name.toLowerCase() === \"readme\") {\n const dir = dirname(filePath);\n if (dir === \".\") return { title: \"README\", content };\n return { title: `${dir} - README`, content };\n }\n return {\n title: name.replace(/[-_]/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase()),\n content,\n };\n}\n\nfunction createSlug(filePath: string): string {\n return filePath\n .replace(/\\.mdx?$/i, \"\")\n .replace(/\\\\/g, \"/\")\n .toLowerCase()\n .replace(/[^a-z0-9/.-]/g, \"-\");\n}\n\nexport async function generateManifest(\n rootDir: string,\n files: string[]\n): Promise<Manifest> {\n const docs: DocFile[] = await Promise.all(\n files.map(async (filePath) => {\n const fullPath = join(rootDir, filePath);\n const raw = await readFile(fullPath, \"utf-8\");\n const { title, content } = extractTitleAndContent(raw, filePath);\n return {\n slug: createSlug(filePath),\n path: filePath,\n title,\n content,\n };\n })\n );\n\n const repoName = basename(rootDir);\n\n return {\n repoName,\n generatedAt: new Date().toISOString(),\n totalDocs: docs.length,\n docs,\n };\n}\n","import { cp, writeFile, rm, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport type { Manifest } from \"../../app/src/types/manifest.js\";\nimport { getTemplatePath } from \"./template.js\";\n\ninterface BuildOptions {\n output: string;\n basePath: string;\n}\n\nexport async function buildApp(\n manifest: Manifest,\n options: BuildOptions\n): Promise<void> {\n const templateDir = getTemplatePath();\n const tempDir = join(\n tmpdir(),\n `doxla-build-${randomBytes(6).toString(\"hex\")}`\n );\n\n try {\n // Copy template to temp dir\n await cp(templateDir, tempDir, { recursive: true });\n\n // Write manifest.json into the app's src directory\n await writeFile(\n join(tempDir, \"src\", \"manifest.json\"),\n JSON.stringify(manifest, null, 2),\n \"utf-8\"\n );\n\n // Install dependencies\n execSync(\"npm install --no-audit --no-fund\", {\n cwd: tempDir,\n stdio: \"pipe\",\n });\n\n // Build with Vite, passing base path as env var\n execSync(\"npx vite build\", {\n cwd: tempDir,\n stdio: \"pipe\",\n env: { ...process.env, VITE_BASE_PATH: options.basePath },\n });\n\n // Copy output\n await rm(options.output, { recursive: true, force: true });\n await mkdir(options.output, { recursive: true });\n await cp(join(tempDir, \"dist\"), options.output, { recursive: true });\n } finally {\n // Clean up temp dir\n await rm(tempDir, { recursive: true, force: true }).catch(() => {});\n }\n}\n","import { fileURLToPath } from \"node:url\";\nimport { dirname, resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nexport function getTemplatePath(): string {\n const __filename = fileURLToPath(import.meta.url);\n let dir = dirname(__filename);\n\n // Walk up from the current file location until we find src/app\n // Works both from source (src/cli/lib/) and built (dist/cli/)\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(dir, \"src\", \"app\");\n if (existsSync(resolve(candidate, \"package.json\"))) {\n return candidate;\n }\n dir = dirname(dir);\n }\n\n // Fallback: assume built location dist/cli/ -> ../../src/app\n const fallback = resolve(dirname(__filename), \"..\", \"..\", \"src\", \"app\");\n return fallback;\n}\n"],"mappings":"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","names":["resolve","chalk","join","writeFile","mkdir","join","dirname","existsSync","join","writeFile","mkdir","resolve","chalk"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doxla",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Improve documentation discoverability within repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"chalk": "^5.4.1",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"fast-glob": "^3.3.3",
|
|
41
|
-
"ora": "^9.3.0"
|
|
41
|
+
"ora": "^9.3.0",
|
|
42
|
+
"rehype-raw": "^7.0.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@mdx-js/mdx": "^3.1.0",
|
package/src/app/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0%" stop-color="#6366f1" />
|
|
5
|
+
<stop offset="100%" stop-color="#06b6d4" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="fold" x1="0" y1="0" x2="1" y2="1">
|
|
8
|
+
<stop offset="0%" stop-color="#4f46e5" />
|
|
9
|
+
<stop offset="100%" stop-color="#0891b2" />
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
|
|
13
|
+
<!-- Rounded square background -->
|
|
14
|
+
<rect x="8" y="8" width="104" height="104" rx="24" fill="url(#grad)" />
|
|
15
|
+
|
|
16
|
+
<!-- Document body -->
|
|
17
|
+
<path d="M38 28h26l20 20v44a4 4 0 0 1-4 4H38a4 4 0 0 1-4-4V32a4 4 0 0 1 4-4z" fill="white" opacity="0.95" />
|
|
18
|
+
|
|
19
|
+
<!-- Folded corner -->
|
|
20
|
+
<path d="M64 28v16a4 4 0 0 0 4 4h16z" fill="white" opacity="0.55" />
|
|
21
|
+
|
|
22
|
+
<!-- Text lines on document -->
|
|
23
|
+
<rect x="42" y="56" width="28" height="3" rx="1.5" fill="url(#grad)" opacity="0.5" />
|
|
24
|
+
<rect x="42" y="64" width="22" height="3" rx="1.5" fill="url(#grad)" opacity="0.35" />
|
|
25
|
+
<rect x="42" y="72" width="26" height="3" rx="1.5" fill="url(#grad)" opacity="0.25" />
|
|
26
|
+
|
|
27
|
+
<!-- Small discovery dot / compass indicator -->
|
|
28
|
+
<circle cx="82" cy="82" r="14" fill="white" opacity="0.2" />
|
|
29
|
+
<circle cx="82" cy="82" r="8" fill="white" opacity="0.35" />
|
|
30
|
+
<circle cx="82" cy="82" r="3.5" fill="white" />
|
|
31
|
+
</svg>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import Markdown from "react-markdown";
|
|
3
|
+
import rehypeRaw from "rehype-raw";
|
|
3
4
|
import remarkGfm from "remark-gfm";
|
|
4
5
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
5
6
|
import { oneLight, oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
@@ -61,7 +62,7 @@ export function MarkdownRenderer({ content, theme, docPath }: MarkdownRendererPr
|
|
|
61
62
|
|
|
62
63
|
return (
|
|
63
64
|
<div className={`prose prose-neutral max-w-none ${theme === "dark" ? "prose-invert" : ""}`}>
|
|
64
|
-
<Markdown remarkPlugins={[remarkGfm]} components={components}>
|
|
65
|
+
<Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} components={components}>
|
|
65
66
|
{content}
|
|
66
67
|
</Markdown>
|
|
67
68
|
</div>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { Search,
|
|
2
|
+
import { Search, Sun, Moon } from "lucide-react";
|
|
3
3
|
import type { Theme } from "../../App";
|
|
4
4
|
import { Input } from "../ui/Input";
|
|
5
5
|
import { Button } from "../ui/Button";
|
|
6
|
+
import logoSrc from "../../assets/logo.svg";
|
|
6
7
|
|
|
7
8
|
interface HeaderProps {
|
|
8
9
|
repoName: string;
|
|
@@ -23,7 +24,7 @@ export function Header({ repoName, theme, onToggleTheme }: HeaderProps) {
|
|
|
23
24
|
return (
|
|
24
25
|
<header className="sticky top-0 z-10 flex h-14 items-center gap-4 border-b border-border bg-background px-6">
|
|
25
26
|
<a href="#/" className="flex items-center gap-2 font-semibold">
|
|
26
|
-
<
|
|
27
|
+
<img src={logoSrc} alt="Doxla" className="h-5 w-5 object-contain" />
|
|
27
28
|
<span>{repoName}</span>
|
|
28
29
|
</a>
|
|
29
30
|
|