mirror of
https://github.com/papra-hq/papra.git
synced 2026-01-06 08:59:37 -06:00
refactor(docs): using starlight theme (#59)
This commit is contained in:
committed by
GitHub
parent
81aa273378
commit
5f044e281d
5
apps/docs/.gitignore
vendored
5
apps/docs/.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
# build output
|
||||
dist/
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
@@ -13,12 +12,10 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
@@ -1,16 +1,54 @@
|
||||
# Papra docs
|
||||
# Starlight Starter Kit: Basics
|
||||
|
||||
## Introduction
|
||||
[](https://starlight.astro.build)
|
||||
|
||||
This Papra documentation website. It is built with Astro, Unocss, solidjs and shadcn-solid.
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started, you can clone the repository and run the development server.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/papra-hq/papra.git
|
||||
cd apps/docs
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
npm create astro@latest -- --template starlight
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics)
|
||||
[](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics)
|
||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro + Starlight project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
.
|
||||
├── public/
|
||||
├── src/
|
||||
│ ├── assets/
|
||||
│ ├── content/
|
||||
│ │ ├── docs/
|
||||
│ └── content.config.ts
|
||||
├── astro.config.mjs
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
|
||||
|
||||
Images can be added to `src/assets/` and embedded in Markdown with a relative link.
|
||||
|
||||
Static assets, like favicons, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).
|
||||
|
||||
@@ -1,24 +1,60 @@
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import starlight from '@astrojs/starlight';
|
||||
import { defineConfig } from 'astro/config';
|
||||
import UnoCSS from 'unocss/astro';
|
||||
import starlightThemeRapide from 'starlight-theme-rapide';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://docs.papra.app',
|
||||
|
||||
integrations: [
|
||||
UnoCSS({
|
||||
injectReset: true,
|
||||
}),
|
||||
sitemap(),
|
||||
],
|
||||
|
||||
markdown: {
|
||||
|
||||
shikiConfig: {
|
||||
themes: {
|
||||
light: 'vitesse-light',
|
||||
dark: 'vitesse-dark',
|
||||
starlight({
|
||||
plugins: [starlightThemeRapide()],
|
||||
title: 'Papra Docs',
|
||||
logo: {
|
||||
dark: './src/assets/logo-dark.svg',
|
||||
light: './src/assets/logo-light.svg',
|
||||
alt: 'Papra Logo',
|
||||
},
|
||||
},
|
||||
},
|
||||
social: {
|
||||
blueSky: 'https://bsky.app/profile/papra.app',
|
||||
github: 'https://github.com/papra-hq/papra',
|
||||
},
|
||||
editLink: {
|
||||
baseUrl: 'https://github.com/papra-hq/papra/edit/main/apps/docs/',
|
||||
},
|
||||
sidebar: [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
{ label: 'Introduction', slug: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Self Hosting',
|
||||
items: [
|
||||
{ label: 'Using Docker', slug: 'self-hosting/using-docker' },
|
||||
{ label: 'Using Docker Compose', slug: 'self-hosting/using-docker-compose' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Configuration',
|
||||
items: [
|
||||
{ label: 'Environment variables', slug: 'configuration/environment-variables' },
|
||||
],
|
||||
},
|
||||
],
|
||||
favicon: '/favicon.svg',
|
||||
head: [
|
||||
// Add ICO favicon fallback for Safari.
|
||||
{
|
||||
tag: 'link',
|
||||
attrs: {
|
||||
rel: 'icon',
|
||||
href: '/favicon.ico',
|
||||
sizes: '32x32',
|
||||
},
|
||||
},
|
||||
],
|
||||
customCss: ['./src/assets/app.css'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "@papra/docs",
|
||||
"name": "docs",
|
||||
"type": "module",
|
||||
"version": "0.0.1-beta.1",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
@@ -12,23 +13,17 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"astro": "^5.1.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"markdown-to-txt": "^2.0.1",
|
||||
"minisearch": "^7.1.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"unstorage": "^1.14.4"
|
||||
"@astrojs/starlight": "^0.30.5",
|
||||
"astro": "^5.0.2",
|
||||
"sharp": "^0.32.5",
|
||||
"starlight-theme-rapide": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.12.2",
|
||||
"@antfu/eslint-config": "^3.13.0",
|
||||
"@iconify-json/tabler": "^1.1.120",
|
||||
"@unocss/reset": "^0.64.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-astro": "^1.3.1",
|
||||
"typescript": "^5.7.2",
|
||||
"unocss": "0.65.0-beta.2",
|
||||
"unocss-preset-animations": "^1.1.0"
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
1342
apps/docs/pnpm-lock.yaml
generated
1342
apps/docs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
8
apps/docs/src/assets/app.css
Normal file
8
apps/docs/src/assets/app.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.site-title {
|
||||
color:inherit !important;
|
||||
gap: 0.5rem !important;
|
||||
}
|
||||
|
||||
.site-title img {
|
||||
width: 1.8rem !important;
|
||||
}
|
||||
1
apps/docs/src/assets/logo-dark.svg
Normal file
1
apps/docs/src/assets/logo-dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#c4bdbd" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2zM9 9h1m-1 4h6m-6 4h6"/></g></svg>
|
||||
|
After Width: | Height: | Size: 318 B |
1
apps/docs/src/assets/logo-light.svg
Normal file
1
apps/docs/src/assets/logo-light.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#403a3a" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2zM9 9h1m-1 4h6m-6 4h6"/></g></svg>
|
||||
|
After Width: | Height: | Size: 318 B |
@@ -1,41 +0,0 @@
|
||||
---
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium focus-visible:(outline-none ring-1.5 ring-ring) disabled:(pointer-events-none opacity-50) bg-inherit',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:(bg-accent text-accent-foreground)',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 px-3 text-xs',
|
||||
lg: 'h-10 px-8',
|
||||
icon: 'h-9 w-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
export type Props<T extends 'a' | 'button' = 'button'> = { as?: T } & VariantProps<typeof buttonVariants> & HTMLAttributes<T>;
|
||||
|
||||
const { as: Element = 'button', class: className, variant, size, ...props } = Astro.props;
|
||||
|
||||
---
|
||||
|
||||
<Element class={cn(buttonVariants({ variant, size }), className)} {...props}>
|
||||
<slot />
|
||||
</Element>
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
import { createStorage } from 'unstorage';
|
||||
import fsLiteDriver from 'unstorage/drivers/fs-lite';
|
||||
import Button from './Button.astro';
|
||||
|
||||
const repo = 'papra-hq/papra';
|
||||
|
||||
const repoUrl = `https://github.com/${repo}`;
|
||||
const apiUrl = `https://ungh.cc/repos/${repo}`;
|
||||
|
||||
const storage = createStorage<{ stars: number; formattedStars: string }>({
|
||||
driver: fsLiteDriver({ base: './.cache/stars' }),
|
||||
});
|
||||
|
||||
|
||||
async function getStars() {
|
||||
const cachedStars = await storage.getItem(repo);
|
||||
|
||||
if (cachedStars) {
|
||||
return cachedStars;
|
||||
}
|
||||
|
||||
const stars: number = await fetch(apiUrl)
|
||||
.then(res => res.json())
|
||||
.then(data => data?.repo?.stars);
|
||||
|
||||
const formattedStars = new Intl.NumberFormat('en-US', { notation: 'compact' }).format(stars).toLowerCase();
|
||||
|
||||
await storage.setItem(repo, { stars, formattedStars });
|
||||
|
||||
return { stars, formattedStars };
|
||||
}
|
||||
|
||||
const { stars, formattedStars } = await getStars();
|
||||
---
|
||||
|
||||
<Button as="a" variant="outline" href={repoUrl} target="_blank" rel="noopener noreferrer" class="flex items-center gap-2" size={stars ? undefined : 'icon'} aria-label="GitHub repository">
|
||||
<div class="i-tabler-brand-github size-4.5"></div>
|
||||
{stars && <span>{formattedStars}</span>}
|
||||
</Button>
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
import Button from './Button.astro';
|
||||
import GitHubStarsButton from './GitHubStarsButton.astro';
|
||||
import ToggleThemeButton from './ToggleThemeButton.astro';
|
||||
---
|
||||
|
||||
<nav class="flex justify-between items-center py-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" aria-label="Toggle menu" size="icon" class="sm:hidden" data-open-sidebar>
|
||||
<div class="i-tabler-menu-2 size-4"></div>
|
||||
</Button>
|
||||
<Button variant="outline" class="justify-start items-center gap-2 md:min-w-60 bg-card" data-open-search-modal>
|
||||
<div class="i-tabler-search size-4"></div>
|
||||
<span class="flex-1 text-left">Search...</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="hidden sm:flex items-center gap-2">
|
||||
<Button as="a" href="https://demo.papra.app" target="_blank" rel="noreferrer" variant="ghost" class="text-foreground flex items-center gap-2">
|
||||
Demo app
|
||||
<div class="i-tabler-external-link size-4"></div>
|
||||
</Button>
|
||||
<GitHubStarsButton />
|
||||
<ToggleThemeButton />
|
||||
</div>
|
||||
|
||||
<div class="flex sm:hidden items-center gap-2">
|
||||
<Button as="a" href="https://github.com/papra-hq/papra" target="_blank" rel="noreferrer" variant="ghost" size="icon" aria-label="GitHub repository">
|
||||
<div class="i-tabler-brand-github size-4.5"></div>
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
const searchButton = document.querySelector('[data-open-search-modal]')!;
|
||||
|
||||
// @ts-expect-error navigator.userAgentData is not supported in all browsers
|
||||
const isMac = navigator.userAgentData?.platform?.toLowerCase().includes('mac') ?? navigator.userAgent.toLowerCase().includes('mac');
|
||||
|
||||
const shortcut = document.createElement('span');
|
||||
shortcut.textContent = isMac ? '⌘ + K' : 'Ctrl + K';
|
||||
shortcut.classList.add('text-xs', 'text-muted-foreground', 'px-1.5', 'py-0.5', 'bg-muted', 'rounded-sm', 'hidden', 'md:inline');
|
||||
|
||||
searchButton.appendChild(shortcut);
|
||||
</script>
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
type Props = {
|
||||
slug: string;
|
||||
title: string;
|
||||
direction: 'prev' | 'next';
|
||||
} & HTMLAttributes<'a'>;
|
||||
|
||||
const { slug: rawSlug, title, direction, class: className, ...props } = Astro.props;
|
||||
|
||||
const slug = `/${rawSlug.replace(/^\//, '')}`;
|
||||
|
||||
const variants
|
||||
= direction === 'prev'
|
||||
? {
|
||||
textAlign: 'text-left',
|
||||
labelWrapper: 'justify-start',
|
||||
icon: 'i-tabler-arrow-left',
|
||||
label: 'Previous',
|
||||
}
|
||||
: {
|
||||
textAlign: 'text-right',
|
||||
labelWrapper: 'flex-row-reverse',
|
||||
icon: 'i-tabler-arrow-right',
|
||||
label: 'Next',
|
||||
};
|
||||
---
|
||||
|
||||
<a href={slug} class={cn('w-full p-6 bg-card rounded-lg border border-border hover:bg-accent hover:text-accent-foreground', variants.textAlign, className)} {...props}>
|
||||
<div class={cn('flex items-center justify-start gap-2 text-sm text-muted-foreground', variants.labelWrapper)}>
|
||||
<div class={cn('size-4', variants.icon)}></div>
|
||||
<span>{variants.label}</span>
|
||||
</div>
|
||||
<div class="text-base font-semibold">{title}</div>
|
||||
</a>
|
||||
@@ -1,188 +0,0 @@
|
||||
---
|
||||
import Button from './Button.astro';
|
||||
---
|
||||
|
||||
<div class="fixed inset-0 bg-black backdrop-blur-sm bg-opacity-50 flex justify-center items-center z-60 hidden!" role="dialog" aria-labelledby="search-title" aria-hidden="true" id="search-modal">
|
||||
<div class="absolute inset-0" role="button" tabindex="0" aria-label="Close Search" data-close-search-modal></div>
|
||||
|
||||
<div class="bg-card border rounded-lg max-w-lg w-full z-10">
|
||||
<header class="flex items-center border-b py-1.5 pl-4 pr-1.5 gap-3">
|
||||
<div class="i-tabler-search size-4"></div>
|
||||
|
||||
<div class="flex-1">
|
||||
<label for="search-input" class="sr-only">Search Query</label>
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
class="bg-transparent border-none focus:ring-none outline-none w-full text-base"
|
||||
placeholder="Type to search..."
|
||||
aria-describedby="search-results"
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button variant="ghost" aria-label="Close" data-close-search-modal size="icon">
|
||||
<div class="i-tabler-x size-4"></div>
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<div class="text-muted-foreground text-center pt-8 pb-4" id="no-results">No results found</div>
|
||||
|
||||
<ul id="search-results" class="flex flex-col gap-2 p-2" role="list" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import MiniSearch, { type SearchResult } from 'minisearch';
|
||||
|
||||
// eslint-disable-next-line antfu/no-top-level-await
|
||||
const docsIndex = await fetch('/search.json').then(res => res.json());
|
||||
|
||||
type Doc = {
|
||||
title: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
const miniSearch = MiniSearch.loadJS<Doc>(docsIndex, {
|
||||
idField: 'slug',
|
||||
fields: ['title', 'description', 'content'],
|
||||
storeFields: ['title', 'description', 'slug'],
|
||||
searchOptions: {
|
||||
fuzzy: 0.2,
|
||||
},
|
||||
});
|
||||
|
||||
const modalContainer = document.getElementById('search-modal')!;
|
||||
const resultsList = document.getElementById('search-results')!;
|
||||
const noResults = document.getElementById('no-results')!;
|
||||
const searchInput = document.getElementById('search-input')!;
|
||||
const closeSearch = document.querySelectorAll('[data-close-search-modal]');
|
||||
const openSearch = document.querySelectorAll('[data-open-search-modal]');
|
||||
|
||||
let currentIndex = -1;
|
||||
let filteredResults: SearchResult[] = [];
|
||||
|
||||
function openModal() {
|
||||
modalContainer.setAttribute('aria-hidden', 'false');
|
||||
modalContainer.classList.remove('hidden!');
|
||||
searchInput.focus();
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
modalContainer.setAttribute('aria-hidden', 'true');
|
||||
modalContainer.classList.add('hidden!');
|
||||
}
|
||||
|
||||
function filterResults(event: Event) {
|
||||
const query = (event.target as HTMLInputElement).value.toLowerCase();
|
||||
resultsList.innerHTML = '';
|
||||
currentIndex = -1;
|
||||
|
||||
filteredResults = miniSearch
|
||||
.search(query, {
|
||||
boost: { title: 2 },
|
||||
prefix: true,
|
||||
})
|
||||
.slice(0, 10);
|
||||
|
||||
if (filteredResults.length === 0) {
|
||||
noResults.style.display = 'block';
|
||||
currentIndex = -1;
|
||||
} else {
|
||||
currentIndex = 0;
|
||||
noResults.style.display = 'none';
|
||||
|
||||
const resultItems = filteredResults.map((item, index) => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'py-1.5 px-3 rounded cursor-pointer hover:bg-accent';
|
||||
li.dataset.index = String(index);
|
||||
|
||||
const titleDiv = document.createElement('div');
|
||||
titleDiv.className = 'text-base font-semibold';
|
||||
titleDiv.textContent = item.title;
|
||||
li.appendChild(titleDiv);
|
||||
|
||||
const contentDiv = document.createElement('div');
|
||||
contentDiv.className = 'text-muted-foreground';
|
||||
contentDiv.textContent = item.description;
|
||||
li.appendChild(contentDiv);
|
||||
|
||||
li.onclick = () => navigateTo(item.slug);
|
||||
resultsList.appendChild(li);
|
||||
|
||||
return li;
|
||||
});
|
||||
|
||||
highlightResult(resultItems);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
const resultItems = Array.from(resultsList.querySelectorAll('li'));
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
if (currentIndex < filteredResults.length - 1) {
|
||||
currentIndex++;
|
||||
highlightResult(resultItems);
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
if (currentIndex > 0) {
|
||||
currentIndex--;
|
||||
highlightResult(resultItems);
|
||||
}
|
||||
break;
|
||||
case 'Enter':
|
||||
if (currentIndex >= 0 && filteredResults.length > 0) {
|
||||
event.preventDefault();
|
||||
selectResult(currentIndex);
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
closeModal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function highlightResult(resultItems: Element[]) {
|
||||
resultItems.forEach((item, index) => {
|
||||
if (index === currentIndex) {
|
||||
item.classList.add('bg-accent');
|
||||
item.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
item.classList.remove('bg-accent');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function selectResult(index: number) {
|
||||
const selectedResult = filteredResults[index];
|
||||
|
||||
navigateTo(selectedResult.slug);
|
||||
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function navigateTo(slug: string) {
|
||||
const url = slug === 'index' ? '/' : `/${slug}`;
|
||||
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
// open modal on Ctrl/Cmd + K
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
||||
event.preventDefault();
|
||||
openModal();
|
||||
}
|
||||
});
|
||||
|
||||
searchInput.addEventListener('input', event => filterResults(event));
|
||||
searchInput.addEventListener('keydown', event => handleKeyDown(event));
|
||||
openSearch.forEach(button => button.addEventListener('click', openModal));
|
||||
closeSearch.forEach(button => button.addEventListener('click', closeModal));
|
||||
</script>
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
import Button from './Button.astro';
|
||||
|
||||
const navigation: { title: string; links: { title: string; href: string }[] }[] = [
|
||||
{
|
||||
title: 'Getting Started',
|
||||
links: [
|
||||
{ title: 'Introduction', href: '/' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Self-hosting',
|
||||
links: [
|
||||
{ title: 'Using Docker', href: '/self-hosting/using-docker' },
|
||||
{ title: 'Using Docker Compose', href: '/self-hosting/using-docker-compose' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Configuration',
|
||||
links: [
|
||||
{ title: 'Environment variables', href: '/configuration/environment-variables' },
|
||||
],
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<aside id="sidebar" class="bg-card flex-1 justify-end sm:sticky top-0 h-screen border-r sm:flex absolute top-0 left-0 z-50 -translate-x-full sm:translate-x-0 transition-transform duration-200 ease-in-out">
|
||||
<div class="min-w-260px p-4">
|
||||
<a href="/" class="flex items-center gap-2 px-3 py-1.5">
|
||||
<div class="i-tabler-file-text size-6"></div>
|
||||
|
||||
<div class="text-lg font-semibold">Papra Docs</div>
|
||||
</a>
|
||||
|
||||
<div class="flex flex-col gap-0.5 mt-6">
|
||||
{
|
||||
navigation?.map(section => (
|
||||
<div class="mb-6">
|
||||
<div class="text-sm pl-3 py-1.5 font-semibold">{section.title}</div>
|
||||
<ul class="flex flex-col ">
|
||||
{section.links.map(link => (
|
||||
<li>
|
||||
<Button as="a" href={link.href} class="w-full flex justify-start p-0 py-1.5 pl-3 h-auto text-muted-foreground hover:bg-accent hover:text-accent-foreground" variant="ghost">
|
||||
{link.title}
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="overlay" class="fixed inset-0 bg-black/40 z-40 sm:hidden transition-opacity duration-200 ease-in-out opacity-0 pointer-events-none backdrop-blur-sm"></div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const menuButtons = document.querySelectorAll('[data-open-sidebar]');
|
||||
const sidebar = document.querySelector<HTMLDivElement>('#sidebar')!;
|
||||
const overlay = document.querySelector('#overlay')!;
|
||||
|
||||
function openSidebar() {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
sidebar.classList.add('translate-x-0');
|
||||
overlay.classList.remove('opacity-0');
|
||||
overlay.classList.remove('pointer-events-none');
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
sidebar.classList.remove('translate-x-0');
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
overlay.classList.add('opacity-0');
|
||||
overlay.classList.add('pointer-events-none');
|
||||
}
|
||||
|
||||
menuButtons.forEach(button => button.addEventListener('click', openSidebar));
|
||||
overlay.addEventListener('click', closeSidebar);
|
||||
</script>
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
import Button from './Button.astro';
|
||||
|
||||
type Props = {
|
||||
headings?: MarkdownHeading[];
|
||||
};
|
||||
|
||||
const { headings: allHeadings } = Astro.props;
|
||||
|
||||
const headings = allHeadings?.filter(item => item.depth > 1);
|
||||
---
|
||||
|
||||
{
|
||||
headings && headings.length > 0 && (
|
||||
<>
|
||||
<div class="text-sm font-semibold flex items-center gap-2">
|
||||
<div class="i-tabler-align-justified size-4" />
|
||||
On this page
|
||||
</div>
|
||||
<ul class="flex flex-col gap-0.5 mt-2 border-l pl-3 ml-2 py-1">
|
||||
{headings.map(item => (
|
||||
<li>
|
||||
<Button as="a" href={`#${item.slug}`} class={`w-full flex justify-start p-0 py-0.5 h-auto text-muted-foreground pl-${(item.depth - 2) * 4}`} variant="link">
|
||||
{item.text}
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
import Button from './Button.astro';
|
||||
---
|
||||
|
||||
<Button variant="outline" size="icon" class="toggle-theme-button" aria-label="Toggle theme">
|
||||
<div class="i-tabler-moon dark:i-tabler-sun size-4.5!"></div>
|
||||
</Button>
|
||||
|
||||
<script>
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
document.body.dataset.kbTheme = theme;
|
||||
} else {
|
||||
const isDarkPreferred = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
document.body.dataset.kbTheme = isDarkPreferred ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
const toggleThemeButtons = document.querySelectorAll('.toggle-theme-button');
|
||||
toggleThemeButtons.forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
document.body.dataset.kbTheme = document.body.dataset.kbTheme === 'dark' ? 'light' : 'dark';
|
||||
localStorage.setItem('theme', document.body.dataset.kbTheme);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
10
apps/docs/src/content.config.ts
Normal file
10
apps/docs/src/content.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||
import { docsSchema } from '@astrojs/starlight/schema';
|
||||
import { defineCollection } from 'astro:content';
|
||||
|
||||
export const collections = {
|
||||
docs: defineCollection({
|
||||
loader: docsLoader(),
|
||||
schema: docsSchema(),
|
||||
}),
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import { glob } from 'astro/loaders';
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
export const docsCollection = defineCollection({
|
||||
loader: glob({ pattern: '**/*.md', base: './src/docs' }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
slug: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { docs: docsCollection };
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Installing Papra using Docker
|
||||
description: Self-host Papra using Docker.
|
||||
slug: self-hosting/using-docker
|
||||
---
|
||||
|
||||
Papra can be easily installed and run using Docker. This method is recommended for users who want a quick and straightforward way to deploy their own instance of Papra with minimal setup.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure that you have Docker installed on your system. You can download and install Docker from the official Docker website.
|
||||
|
||||
## Root and Rootless installation
|
||||
|
||||
Papra can be installed in two different ways:
|
||||
|
||||
- **Root**: This is the default installation method. It requires root privileges to run. The images are suffixed with `-root` like `corentinth/papra:latest-root` or `corentinth/papra:1.0.0-root`.
|
||||
- **Rootless**: This method does not require root privileges to run. The images are suffixed with `-rootless` like `corentinth/papra:latest-rootless` or `corentinth/papra:1.0.0-rootless`.
|
||||
|
||||
## Image Sources
|
||||
|
||||
Papra Docker images are available on both **Docker Hub** and **GitHub Container Registry** (GHCR). You can choose the source that best suits your needs.
|
||||
|
||||
```bash frame="none"
|
||||
# Using Docker Hub
|
||||
docker pull corentinth/papra:latest-root
|
||||
docker pull corentinth/papra:latest-rootless
|
||||
|
||||
# Using GitHub Container Registry
|
||||
docker pull ghcr.io/corentinth/papra:latest-root
|
||||
docker pull ghcr.io/corentinth/papra:latest-rootless
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```bash frame="none"
|
||||
docker run -d --name papra --restart unless-stopped -p 1221:1221 corentinth/papra:latest-root
|
||||
```
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
title: Using Docker Compose
|
||||
description: Self-host Papra using Docker Compose.
|
||||
slug: self-hosting/using-docker-compose
|
||||
---
|
||||
|
||||
# Using Docker Compose
|
||||
|
||||
Coming soon.
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
title: Environment variables
|
||||
description: Environment variables for Papra.
|
||||
slug: configuration/environment-variables
|
||||
---
|
||||
|
||||
# Environment variables
|
||||
|
||||
Coming soon.
|
||||
@@ -1,11 +1,8 @@
|
||||
---
|
||||
title: Introduction
|
||||
title: Papra docs
|
||||
description: Papra documentation.
|
||||
slug: index
|
||||
---
|
||||
|
||||
# Papra docs
|
||||
|
||||
**WIP**: This is still a work in progress. The documentation is not yet complete.
|
||||
|
||||
Papra is a minimalistic document management and archiving platform. It is designed to be simple to use and accessible to everyone. Papra is a plateform for long-term document storage and management, like a digital archive for your documents.
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: Using Docker
|
||||
description: Self-host Papra using Docker.
|
||||
slug: self-hosting/using-docker
|
||||
---
|
||||
|
||||
# Using Docker
|
||||
|
||||
Coming soon.
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
import Header from '@/components/Header.astro';
|
||||
import SearchModal from '@/components/SearchModal.astro';
|
||||
import Sidenav from '@/components/Sidenav.astro';
|
||||
import ToC from '@/components/ToC.astro';
|
||||
import '../styles/app.css';
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
headings?: MarkdownHeading[];
|
||||
};
|
||||
|
||||
const defaultTitle = 'Papra Docs';
|
||||
const defaultDescription = 'Documentation for Papra, the open-source document management platform.';
|
||||
|
||||
const { title, description, headings } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title ?? defaultTitle}</title>
|
||||
<meta name="description" content={description ?? defaultDescription} />
|
||||
<meta name="author" content="Corentin Thomasset" />
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="Papra docs" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<meta property="og:title" content={title ?? defaultTitle} />
|
||||
<meta property="og:description" content={description ?? defaultDescription} />
|
||||
<meta property="og:image" content={new URL('/og-image.png', Astro.url).toString()} />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="Papra Docs" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:image:alt" content="Papra Docs" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:creator" content="@cthmsst" />
|
||||
<meta name="twitter:title" content={title ?? defaultTitle} />
|
||||
<meta name="twitter:description" content={description ?? defaultDescription} />
|
||||
<meta name="twitter:image" content={new URL('/og-image.png', Astro.url).toString()} />
|
||||
|
||||
<link rel="canonical" href="https://docs.papra.app" />
|
||||
</head>
|
||||
<body class="min-h-screen font-sans text-sm bg-background">
|
||||
<SearchModal />
|
||||
|
||||
<main class="flex min-h-screen items-start">
|
||||
<Sidenav />
|
||||
|
||||
<article class="max-w-860px w-full px-4 sm:px-6 z-10 pb-42">
|
||||
<Header />
|
||||
|
||||
<slot />
|
||||
</article>
|
||||
|
||||
<aside class="sticky top-0 flex-1 hidden lg:block">
|
||||
<div class="min-w-260px p-4 mt-16">
|
||||
<ToC headings={headings} />
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
import Layout from '@/layouts/Layout.astro';
|
||||
---
|
||||
|
||||
<Layout title="404 - Not Found" description="The page you are looking for does not exist.">
|
||||
<div class="pt-12 flex items-center justify-center gap-4 flex-col sm:flex-row text-center sm:text-left">
|
||||
|
||||
<div class="i-tabler-coffee size-20 mb-4"></div>
|
||||
<div>
|
||||
<h1 class="text-lg font-medium">404 - Not Found</h1>
|
||||
<p class="text-sm text-muted-foreground">The page you are looking for does not exist.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
import PrevNextDocCard from '@/components/PrevNextDocCard.astro';
|
||||
import Layout from '@/layouts/Layout.astro';
|
||||
import { getCollection, render } from 'astro:content';
|
||||
|
||||
const docs = await getCollection('docs');
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const docs = await getCollection('docs');
|
||||
|
||||
return docs.map(doc => ({
|
||||
params: { slug: doc.data.slug === 'index' ? undefined : doc.data.slug },
|
||||
props: { doc },
|
||||
}));
|
||||
}
|
||||
|
||||
const { doc } = Astro.props;
|
||||
const { Content, headings } = await render(doc);
|
||||
|
||||
const currentDocIndex = docs.findIndex(d => d.data.slug === doc.data.slug);
|
||||
const nextDoc = docs[currentDocIndex + 1];
|
||||
const prevDoc = docs[currentDocIndex - 1];
|
||||
---
|
||||
|
||||
<Layout title={doc.data.title} description={doc.data.description} headings={headings}>
|
||||
<div class="prose max-w-none text-base prose-coolgray dark:prose-invert mb-12">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
<a class="text-sm flex gap-2 items-center" href={`https://github.com/papra-hq/papra/blob/main/apps/docs/${doc.filePath}`} target="_blank" rel="noopener noreferrer">
|
||||
<div class="i-tabler-edit size-4.5"></div>
|
||||
Edit this page on GitHub
|
||||
</a>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{ prevDoc && (<PrevNextDocCard direction="prev" {...prevDoc.data} />)}
|
||||
{ nextDoc && (<PrevNextDocCard direction="next" {...nextDoc.data} class={!prevDoc ? 'grid-col-start-2' : ''} />) }
|
||||
</div>
|
||||
</Layout>
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
|
||||
function getRobotsTxt(sitemapURL: URL) {
|
||||
return `
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: ${sitemapURL.href}
|
||||
`;
|
||||
}
|
||||
|
||||
export const GET: APIRoute = ({ site }) => {
|
||||
const sitemapURL = new URL('sitemap-index.xml', site);
|
||||
return new Response(getRobotsTxt(sitemapURL));
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { markdownToTxt } from 'markdown-to-txt';
|
||||
import MiniSearch from 'minisearch';
|
||||
|
||||
function getRawContent(docsMarkdown: string | undefined) {
|
||||
return markdownToTxt(docsMarkdown ?? '').replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
const docs = await getCollection('docs');
|
||||
|
||||
const docsWithContent = docs.map(doc => ({
|
||||
...doc.data,
|
||||
content: getRawContent(doc.body),
|
||||
}));
|
||||
|
||||
const stopWords = new Set(['the', 'is', 'in', 'to', 'of', 'at', 'by', 'with', 'from', 'up', 'down', 'out', 'over', 'under', 'again', 'further', 'then', 'once', 'this', 'that', 'these', 'those', 'which', 'who', 'whom', 'whose', 'what', 'why', 'how', 'all', 'any', 'some', 'a', 'an', 'and', 'as', 'but', 'if', 'or', 'because', 'as', 'until', 'while']);
|
||||
|
||||
const miniSearch = new MiniSearch({
|
||||
idField: 'slug',
|
||||
fields: ['title', 'description', 'content'],
|
||||
storeFields: ['title', 'description', 'slug'],
|
||||
searchOptions: { fuzzy: 0.2 },
|
||||
processTerm: term => term.toLowerCase().split(' ').filter(word => !stopWords.has(word)).join(' '),
|
||||
});
|
||||
|
||||
miniSearch.addAll(docsWithContent);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify(miniSearch),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 168 4% 25%;
|
||||
|
||||
--card: 0 0% 98%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--primary: 252 94% 69%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--warning: 31 98% 50%;
|
||||
--warning-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
[data-kb-theme="dark"] {
|
||||
--background: 240 4% 10%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--card: 240 4% 8%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 240 4% 8%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 256 100% 73%;
|
||||
--primary: 77 100% 74%;
|
||||
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--warning: 31 98% 50%;
|
||||
--warning-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
[data-kb-theme="dark"] .astro-code,
|
||||
[data-kb-theme="dark"] .astro-code span {
|
||||
--shiki-dark-bg: hsl(var(--card)) !important;
|
||||
|
||||
color: var(--shiki-dark) !important;
|
||||
background-color: var(--shiki-dark-bg) !important;
|
||||
font-style: var(--shiki-dark-font-style) !important;
|
||||
font-weight: var(--shiki-dark-font-weight) !important;
|
||||
text-decoration: var(--shiki-dark-text-decoration) !important;
|
||||
}
|
||||
|
||||
.astro-code{
|
||||
background-color: hsl(var(--card)) !important;
|
||||
/* border: 1px solid hsl(var(--border) / 0.5) !important; */
|
||||
}
|
||||
|
||||
.astro-code span {
|
||||
background-color: hsl(var(--card)) !important;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { ClassValue } from 'clsx';
|
||||
import clsx from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export const cn = (...classLists: ClassValue[]) => twMerge(clsx(classLists));
|
||||
@@ -1,22 +1,5 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"allowJs": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
]
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import {
|
||||
defineConfig,
|
||||
presetIcons,
|
||||
presetTypography,
|
||||
presetUno,
|
||||
presetWebFonts,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss';
|
||||
import presetAnimations from 'unocss-preset-animations';
|
||||
|
||||
export default defineConfig({
|
||||
presets: [
|
||||
presetUno({
|
||||
dark: {
|
||||
dark: '[data-kb-theme="dark"]',
|
||||
light: '[data-kb-theme="light"]',
|
||||
},
|
||||
prefix: '',
|
||||
}),
|
||||
presetAnimations(),
|
||||
presetIcons(),
|
||||
presetTypography({
|
||||
cssExtend: {
|
||||
h1: {
|
||||
'font-size': '1.875rem',
|
||||
'font-weight': '700',
|
||||
'line-height': '2.25rem',
|
||||
},
|
||||
pre: {
|
||||
position: 'relative',
|
||||
},
|
||||
},
|
||||
}),
|
||||
presetWebFonts({
|
||||
provider: 'bunny',
|
||||
fonts: {
|
||||
sans: 'DM Sans:300,400,500,600,700,800',
|
||||
},
|
||||
}),
|
||||
],
|
||||
transformers: [transformerVariantGroup(), transformerDirectives()],
|
||||
theme: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
animation: {
|
||||
keyframes: {
|
||||
'accordion-down':
|
||||
'{ from { height: 0 } to { height: var(--kb-accordion-content-height) } }',
|
||||
'accordion-up':
|
||||
'{ from { height: var(--kb-accordion-content-height) } to { height: 0 } }',
|
||||
'collapsible-down':
|
||||
'{ from { height: 0 } to { height: var(--kb-collapsible-content-height) } }',
|
||||
'collapsible-up':
|
||||
'{ from { height: var(--kb-collapsible-content-height) } to { height: 0 } }',
|
||||
'caret-blink': '{ 0%,70%,100% { opacity: 1 } 20%,50% { opacity: 0 } }',
|
||||
},
|
||||
timingFns: {
|
||||
'accordion-down': 'ease-out',
|
||||
'accordion-up': 'ease-out',
|
||||
'collapsible-down': 'ease-out',
|
||||
'collapsible-up': 'ease-out',
|
||||
'caret-blink': 'ease-out',
|
||||
},
|
||||
durations: {
|
||||
'accordion-down': '0.2s',
|
||||
'accordion-up': '0.2s',
|
||||
'collapsible-down': '0.2s',
|
||||
'collapsible-up': '0.2s',
|
||||
'caret-blink': '1.25s',
|
||||
},
|
||||
counts: {
|
||||
'caret-blink': 'infinite',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
safelist: [
|
||||
...'hidden'.split(' ').flatMap(word => [word, `${word}!`]),
|
||||
|
||||
...Array.from({ length: 30 }, (_, i) => `pl-${i}`),
|
||||
],
|
||||
});
|
||||
2003
pnpm-lock.yaml
generated
2003
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user