75 Commits

Author SHA1 Message Date
5acb6a4f81 Merge pull request 'Update dependency eslint-config-next to v15' (#8) from renovate/eslint-config-next-15.x into master
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 4m44s
Reviewed-on: #8
2024-10-22 12:56:57 -07:00
Renovate Bot
7b7a3cf2a0 Update dependency eslint-config-next to v15 2024-10-21 19:05:22 +00:00
ee25889139 small bug fix
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m25s
Took 8 minutes
2024-10-14 00:42:15 -04:00
7cf1b4d16e OG image generation
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 4m10s
Took 54 minutes
2024-10-14 00:16:33 -04:00
e1b7beadbd proper loading state for the on this page component
Took 8 minutes
2024-10-13 20:54:12 -04:00
e5a797718f 10 min -> 5 min content cache
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m24s
Took 2 minutes
2024-10-13 20:45:16 -04:00
b23c48246d remove debug
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 1m29s
Took 41 seconds
2024-10-13 20:43:02 -04:00
573f13568d caching properly works now
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Took 2 hours 0 minutes
2024-10-13 20:42:22 -04:00
48973560d1 pull latest changes from git every 10 mins
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m23s
Took 19 minutes
2024-10-13 14:20:20 -04:00
d022209305 dont assume the slug for the first page is "intro"
Took 22 minutes
2024-10-13 14:01:20 -04:00
e10d447873 Merge pull request 'Update dependency lucide-react to ^0.452.0' (#6) from renovate/lucide-react-0.x into master
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 4m35s
Reviewed-on: #6
2024-10-13 10:01:30 -07:00
Renovate Bot
e38d6a42de Update dependency lucide-react to ^0.452.0 2024-10-11 19:04:43 +00:00
f604af5d4c ignore this stupid err
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 4m24s
Took 16 minutes
2024-10-10 17:00:27 -04:00
1a1e854d73 fix sidebar padding, and add a skeleton for the on this page headers
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m50s
Took 10 minutes
2024-10-10 16:44:26 -04:00
a7a144dbd7 re-add the gradient bg
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 13m48s
Took 6 minutes
2024-10-10 16:21:03 -04:00
22c8034560 does this fix flashing on firefox?
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m8s
Took 2 minutes
2024-10-10 15:59:41 -04:00
8436da4b95 does this fix flashing on firefox?
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Took 3 minutes
2024-10-10 15:57:15 -04:00
091835f01a does this look better?!?!?!?!?
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m20s
Took 6 minutes
2024-10-10 15:45:35 -04:00
74b20a7d7c does this look better?!?!?!?!?
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m30s
Took 4 minutes
2024-10-10 15:39:20 -04:00
63afa18397 does this look better?!?!?!?!?
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m24s
Took 25 minutes
2024-10-10 15:35:26 -04:00
071fe82685 ok
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m14s
Took 17 minutes
2024-10-10 14:12:21 -04:00
24ed4c96a7 test
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 19s
Took 15 minutes
2024-10-10 13:55:04 -04:00
e404482567 test
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 19s
Took 2 minutes
2024-10-10 13:39:37 -04:00
938bf248cc test
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 22s
Took 14 minutes
2024-10-10 13:38:06 -04:00
f8cbb90ef4 yes?
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m37s
Took 3 minutes
2024-10-09 23:04:14 -04:00
990d5c93cb yes?
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Took 3 minutes
2024-10-09 23:00:42 -04:00
a92532f3bb force dynamic
Some checks failed
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Took 5 minutes
2024-10-09 22:57:50 -04:00
4e0f8957df okay this is now 1 workflow
All checks were successful
Deploy & Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m49s
Took 2 minutes
2024-10-09 22:50:46 -04:00
eebf233ce9 docker compose file
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m16s
Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Took 3 minutes
2024-10-09 22:48:56 -04:00
f525a8af7c copy default docs dir
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Successful in 2m55s
Took 3 minutes
2024-10-09 22:43:27 -04:00
d5cb54030c fix publish?
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 1m53s
Took 2 minutes
2024-10-09 22:40:07 -04:00
72e624dfd1 fix publish?
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 1m14s
Took 3 minutes
2024-10-09 22:37:58 -04:00
a0b786ae68 fix publish?
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 1m50s
Took 3 minutes
2024-10-09 22:34:50 -04:00
ffbf485774 fix publish?
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 19s
Took 4 minutes
2024-10-09 22:31:50 -04:00
402962e386 image publishing
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m19s
Publish Image / deploy (ubuntu-latest, 2.44.0) (push) Failing after 1m0s
Took 6 minutes
2024-10-09 22:28:00 -04:00
adf898e3ef add contentEditUrl config url
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m54s
Took 10 minutes
2024-10-09 22:20:01 -04:00
6a2c23229e dont need to copy this anymore
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m35s
Took 3 minutes
2024-10-09 22:07:40 -04:00
519b688f13 move docs content to its own repo
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 56s
Took 30 minutes
2024-10-09 22:04:53 -04:00
447da11d71 fix flashing?
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 2m21s
Took 9 minutes
2024-10-09 21:32:30 -04:00
ffc386ed93 prod request logging
Took 16 minutes
2024-10-09 21:23:57 -04:00
414732bf89 Merge pull request 'Update dependency lucide-react to ^0.451.0' (#4) from renovate/lucide-react-0.x into master
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 2m16s
Reviewed-on: #4
2024-10-09 17:03:47 -07:00
Renovate Bot
2793e1213c Update dependency lucide-react to ^0.451.0 2024-10-09 23:06:28 +00:00
40fd695d03 Merge pull request 'Update dependency eslint-config-next to v14.2.15' (#5) from renovate/eslint-config-next-14.x into master
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m54s
Reviewed-on: #5
2024-10-09 15:52:41 -07:00
Renovate Bot
9c69c76d52 Update dependency eslint-config-next to v14.2.15 2024-10-09 22:13:16 +00:00
5e2946d00c updates
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m32s
Took 5 minutes
2024-10-09 17:43:25 -04:00
cf8530c750 footer configuration
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m29s
Took 8 minutes
2024-10-09 17:38:36 -04:00
3d44677d82 fix build warnings
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m23s
Took 12 minutes
2024-10-09 17:29:43 -04:00
6e949539ab add metadata & viewport to the config
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 33s
Took 11 minutes
2024-10-09 17:17:21 -04:00
45d5a0e2d4 copy docs content
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m11s
Took 2 minutes
2024-10-09 17:05:59 -04:00
18b31bcb7b now will this deploy?
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m12s
Took 1 minute
2024-10-09 17:02:31 -04:00
a7da175499 test
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 17s
Took 30 seconds
2024-10-09 17:01:27 -04:00
24d6eb52f6 test
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 7s
Took 2 minutes
2024-10-09 17:00:58 -04:00
0cd347714e add config.json to the Dockerfile
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 7s
Took 4 minutes
2024-10-09 16:58:58 -04:00
d364ea83b5 add support from pulling from a Git repo for docs content
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 47s
Took 24 minutes
2024-10-09 16:54:36 -04:00
1614889a62 add a config
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 19s
Took 52 minutes
2024-10-09 16:30:22 -04:00
7c6fd8a5ef make the 404 page more responsive
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m5s
Took 6 minutes
2024-10-09 14:45:17 -04:00
e26a9e0267 fix hover on the sidebar in light mode
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m5s
Took 8 minutes
2024-10-09 14:38:54 -04:00
1634fb7520 make indentation lines more visible in light mode
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m4s
Took 2 minutes
2024-10-09 14:31:10 -04:00
d4209bc23d custom 404 page
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m2s
Took 13 minutes
2024-10-09 14:28:54 -04:00
876d0094ca light mode
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m48s
Took 35 minutes
2024-10-09 14:15:16 -04:00
cbc9dcb1ab some changes
Took 1 hour 4 minutes
2024-10-09 13:40:01 -04:00
1296a34657 better responsiveness
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m18s
Took 1 minute
2024-10-07 20:59:59 -04:00
1717c0859d better responsiveness
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m1s
Took 11 minutes
2024-10-07 20:58:31 -04:00
dc89db4eed image viewer component
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m50s
Took 19 minutes
2024-10-07 20:47:52 -04:00
40b799e280 update docs page breadcrumb
Took 5 minutes
2024-10-07 20:29:06 -04:00
71c24bd6cc fix page buttons on the docs page footer being broken for some links
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m9s
Took 3 minutes
2024-10-07 20:24:34 -04:00
c4f7d4bf7e fix active page indicator on the sidebar being broken for some links
Took 9 minutes
2024-10-07 20:21:35 -04:00
54230367e1 sort docs content by order field in frontmatter metadata
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m1s
Took 7 minutes
2024-10-07 20:12:33 -04:00
68fae2e29e Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m4s
2024-10-07 20:06:18 -04:00
be319a4d7b fix the root docs page not working
Took 4 minutes
2024-10-07 20:06:04 -04:00
325891663b Merge pull request 'Update dependency eslint-config-next to v14.2.14' (#1) from renovate/eslint-config-next-14.x into master
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m54s
Reviewed-on: #1
2024-10-07 17:02:47 -07:00
36af41cbe4 some more docs content
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Took 18 minutes
2024-10-07 20:01:53 -04:00
fbde7667bc use proper page extension for edit links
Took 9 minutes
2024-10-07 19:43:41 -04:00
cdcee387b5 some content and changes
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m2s
Took 3 hours 4 minutes
2024-10-07 19:34:00 -04:00
Renovate Bot
901f7706d4 Update dependency eslint-config-next to v14.2.14 2024-10-07 20:14:19 +00:00
44 changed files with 1043 additions and 708 deletions

View File

@ -0,0 +1,56 @@
name: Deploy & Publish Image
on:
push:
branches: [ "master" ]
paths-ignore:
- README.md
- LICENSE
jobs:
deploy:
strategy:
matrix:
arch: [ "ubuntu-latest" ]
git-version: [ "2.44.0" ]
runs-on: ${{ matrix.arch }}
# Steps to run
steps:
# Checkout the repo
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# Deploy to Dokku
- name: Deploy to Dokku
uses: dokku/github-action@master
with:
git_remote_url: "ssh://dokku@10.10.70.73:22/docs"
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
# Set up Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to the Docker registry
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: git.rainnny.club
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_TOKEN }}
# Publish the image
- name: Build Image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: |
git.rainnny.club/pulseapp/docs:${{ github.sha }}
git.rainnny.club/pulseapp/docs:latest
build-args: |
GIT_REV=${{ gitea.sha }}

View File

@ -1,31 +0,0 @@
name: Deploy
on:
push:
branches: [ "master" ]
paths-ignore:
- README.md
- LICENSE
jobs:
deploy:
strategy:
matrix:
arch: [ "ubuntu-latest" ]
git-version: [ "2.44.0" ]
runs-on: ${{ matrix.arch }}
# Steps to run
steps:
# Checkout the repo
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# Deploy to Dokku
- name: Deploy to Dokku
uses: dokku/github-action@master
with:
git_remote_url: "ssh://dokku@10.10.70.73:22/docs"
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}

1
.gitignore vendored
View File

@ -6,7 +6,6 @@ node_modules
.env*.local
next-env.d.ts
.sentryclirc
.env
sw.*
workbox-*
swe-worker-*

View File

@ -13,7 +13,7 @@ FROM base AS builder
WORKDIR /usr/src/app
COPY --from=depends /usr/src/app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun run build
@ -32,14 +32,15 @@ COPY --from=builder --chown=nextjs:nextjs /usr/src/app/.next ./.next
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/public ./public
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/next.config.mjs ./next.config.mjs
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/package.json ./package.json
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/docs ./docs
ENV NODE_ENV production
ENV NODE_ENV=production
# Exposting on port 80 so we can
# access via a reverse proxy for Dokku
ENV HOSTNAME "0.0.0.0"
ENV HOSTNAME="0.0.0.0"
EXPOSE 80
ENV PORT 80
ENV PORT=80
USER nextjs
CMD node server.js
CMD ["node", "server.js"]

View File

@ -1,3 +1,2 @@
# docs
The public documentation for Pulse App.
The source coded for the Pulse App documentation site.

BIN
bun.lockb

Binary file not shown.

90
config.json Normal file
View File

@ -0,0 +1,90 @@
{
"siteName": "Pulse App",
"ogApiUrl": "https://docs.pulseapp.cc/api/og?title={title}",
"metadata": {
"title": {
"default": "Pulse Docs",
"template": "%s • Pulse Docs"
},
"description": "A lightweight service monitoring solution for tracking the availability of whatever service your heart desires!",
"openGraph": {
"images": [
{
"url": "https://pulseapp.cc/media/logo.png",
"width": 128,
"height": 128
}
]
},
"twitter": {
"card": "summary"
}
},
"viewport": {
"themeColor": "#A855F7"
},
"contentSource": "https://git.rainnny.club/PulseApp/docs-content.git",
"contentEditUrl": "https://git.rainnny.club/PulseApp/docs-content/src/branch/master/{slug}{ext}",
"socialLinks": [
{
"name": "GitHub",
"tooltip": "View our Github",
"logo": "./github.svg",
"href": "https://github.com/PulseAppCC",
"navbar": true
},
{
"name": "Discord",
"tooltip": "Join our Discord",
"logo": "./discord.svg",
"href": "https://discord.pulseapp.cc",
"navbar": true
},
{
"name": "Email",
"tooltip": "Email us",
"logo": "Mail",
"href": "mailto:support@pulseapp.cc",
"navbar": false
}
],
"footer": {
"homeUrl": "https://pulseapp.cc",
"links": {
"Resources": [
{
"name": "Support",
"href": "https://support.pulseapp.cc"
},
{
"name": "Jobs",
"href": "https://jobs.pulseapp.cc"
},
{
"name": "Developers",
"shortName": "Devs",
"href": "https://dev.pulseapp.cc",
"external": true
},
{
"name": "System Status",
"shortName": "Status",
"href": "https://status.pulseapp.cc",
"external": true
}
],
"Legal": [
{
"name": "Terms & Conditions",
"shortName": "Terms",
"href": "/legal/terms"
},
{
"name": "Privacy Policy",
"shortName": "Privacy",
"href": "/legal/privacy"
}
]
}
}
}

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
services:
app:
image: git.rainnny.club/pulseapp/docs:latest
restart: unless-stopped
container_name: docs
ports:
- "8080:80"
volumes:
- ./config.json:/usr/src/app/config.json
- ./docs:/usr/src/app/docs

View File

@ -1,7 +0,0 @@
---
title: 'Hello'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---
# hello

View File

@ -1,7 +0,0 @@
---
title: 'Hey'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---
# hey

View File

@ -1,7 +0,0 @@
---
title: 'Hi'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---
# hi

View File

@ -1,293 +0,0 @@
---
title: '🚀 Introduction'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---
# Get started with Pulse App!
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# an mel dissentiunt ponderum eius dicant adhuc,
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# vim an explicari eirmod pro singulis scripta iaculis fermentum.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# eruditi propriae vulputate elit venenatis reprehendunt delectus.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# dicunt antiopam ultricies nisl egestas voluptatibus harum,
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# viverra senserit cursus theophrastus elaboraret iudicabit ligula.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# posidonium dicat eum nostra auctor quaeque harum
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# doctus primis disputationi atqui magnis himenaeos fastidii
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# ligula cras prodesset litora ridens docendi euripidis
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# efficitur detraxit detraxit fames appareat mutat elit
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# donec nominavi qui dolorum adversarium eum eleifend
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# nunc contentiones numquam pharetra his vero solum
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.

9
docs/intro.mdx Normal file
View File

@ -0,0 +1,9 @@
---
title: 'Example'
updated: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
order: 1
---
# Hello World
This is an example content file.

View File

@ -20,11 +20,12 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"@vercel/og": "^0.6.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
"framer-motion": "^11.11.1",
"lucide-react": "^0.447.0",
"lucide-react": "^0.452.0",
"luxon": "^3.5.0",
"next": "^15.0.0-canary.179",
"next-themes": "^0.3.0",
@ -32,6 +33,7 @@
"react-dom": "^19.0.0-rc-1460d67c-20241003",
"remark-gfm": "^4.0.0",
"remote-mdx": "^0.0.8",
"simple-git": "^3.27.0",
"tailwind-merge": "^2.5.3",
"tailwindcss-animate": "^1.0.7"
},
@ -41,7 +43,7 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.8",
"eslint-config-next": "15.0.0",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"

View File

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36">
<path fill="#fff"
<path fill="#5865f2"
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 780 B

View File

@ -1,5 +1,50 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
fill="#fff"/>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 96 96" id="Github-Octocat--Streamline-Svg-Logos.svg"
height="98" width="98" stroke-width="1">
<desc>Github Octocat Streamline Icon: https://streamlinehq.com</desc>
<path fill="#9EDCF2"
d="M74.4304 73.4278c0 5.0396-11.6744 9.1002-26.068 9.1002-14.3936 0-26.068-4.0969-26.068-9.1002 0-5.0396 11.6744-9.1002 26.068-9.1002 14.3936 0 26.068 4.0606 26.068 9.1002Z"></path>
<mask id="a" width="30" height="25" x="33" y="70" maskUnits="userSpaceOnUse" style="mask-type:luminance">
<path fill="#fff"
d="M37.159 89.7428c.5801 2.7192 1.9941 4.3145 3.4081 5.2571h14.9012c1.8128-1.2327 3.6618-3.553 3.6618-7.9037V75.8568s.2175-2.7917 2.7917-3.6981c0 0 1.4865-1.0514-.1088-1.6315 0 0-7.0699-.5801-7.0699 5.2209v8.5564s.2901 3.1542-1.3777 4.4594V78.1772s.1088-3.3718 1.8491-4.6407c0 0 1.1602-2.0666-1.3777-1.5228 0 0-4.8583.6889-5.0759 6.381l-.1087 10.8768H47.492l-.1088-10.8768c-.2175-5.6559-5.0758-6.381-5.0758-6.381-2.5379-.5801-1.3777 1.5228-1.3777 1.5228 1.7402 1.2689 1.849 4.6407 1.849 4.6407v10.6955c-1.6678-1.1965-1.3777-4.5682-1.3777-4.5682v-8.5564c0-5.801-7.0699-5.2209-7.0699-5.2209-1.6315.5801-.1088 1.6315-.1088 1.6315 2.5379.9427 2.7917 3.6981 2.7917 3.6981v7.8676l.145 6.0184Z"></path>
</mask>
<g mask="url(#a)">
<path fill="#7DBCE7"
d="M74.4304 73.4276c0 5.0395-11.6744 9.1002-26.068 9.1002-14.3936 0-26.068-4.0969-26.068-9.1002 0-5.0396 11.6744-9.1003 26.068-9.1003 14.3936 0 26.068 4.0607 26.068 9.1003Z"></path>
</g>
<path fill="#9EDCF2"
d="m18.5959 46.6347-.7614 2.6104s-.1812.9427.6889 1.124c.9426-.0363.8701-.9064.7976-1.1602l-.7251-2.5742Z"></path>
<path fill="#010101"
d="m94.3348 35.6491.0725-.3263c-7.65-1.5227-15.4812-1.559-20.2308-1.3414.7614-2.7917 1.0152-6.0548 1.0152-9.6441 0-5.1846-1.9578-9.3177-5.0758-12.4358.5438-1.7765 1.2689-5.72838-.7251-10.76795 0 0-3.5531-1.12392813-11.6382 4.2782-3.1542-.79763-6.526-1.19645-9.8978-1.19645-3.6981 0-7.4325.47133-10.9493 1.41398C28.5667-.0628898 24.9048 1.0973 24.9048 1.0973c-2.3929 5.98222-.9064 10.4417-.4713 11.5294-2.828 3.0455-4.532 6.9248-4.532 11.6744 0 3.5893.3988 6.8161 1.414 9.6078-4.7858-.1813-12.32702-.1088-19.72321 1.3777l.07251.3263c7.39619-1.4865 15.0099-1.5228 19.7594-1.3415.2176.5801.4714 1.1602.7252 1.704-4.7133.1451-12.72585.7614-20.41209 2.9368l.10877.3263C9.60483 37.0631 17.6899 36.483 22.3669 36.338c2.828 5.2208 8.3389 8.6289 18.2004 9.6803-1.4139.9427-2.8279 2.5379-3.408 5.2571-1.9216.9064-7.94 3.1543-11.5656-3.0817 0 0-2.0304-3.6981-5.9097-3.9882 0 0-3.7706-.0725-.2538 2.3567 0 0 2.5016 1.1964 4.2419 5.6559 0 0 2.2841 7.6137 13.1971 5.1483v7.795s-.2175 2.7917-2.7917 3.6981c0 0-1.5227 1.0514.1088 1.6315 0 0 7.0699.5801 7.0699-5.2208v-8.5564s-.29-3.408 1.3777-4.5682v14.0672s-.1087 3.3718-1.849 4.6408c0 0-1.1602 2.0666 1.3777 1.5227 0 0 4.8583-.6888 5.0758-6.381l.1088-14.2485h1.1602l.1088 14.2485c.2175 5.6559 5.0758 6.381 5.0758 6.381 2.5379.5801 1.3777-1.5227 1.3777-1.5227-1.7403-1.269-1.8491-4.6408-1.8491-4.6408V52.2543c1.6678 1.3052 1.3778 4.4595 1.3778 4.4595v8.5564c0 5.8009 7.0699 5.2208 7.0699 5.2208 1.6315-.5801.1087-1.6315.1087-1.6315-2.5379-.9426-2.7917-3.6981-2.7917-3.6981V53.9221c0-4.387-1.849-6.7073-3.6618-7.9038 10.5142-1.0514 15.5538-4.4232 17.8741-9.7166 4.6045.1088 12.9071.6889 20.8109 2.9368l.1088-.3263c-7.8676-2.2116-16.0976-2.7917-20.7746-2.9368.2175-.5438.3988-1.0876.5801-1.6677 4.8582-.1813 12.7258-.1813 20.412 1.3414Z"></path>
<path fill="#F5CCB3"
d="M64.8947 24.0835c2.2479 2.0666 3.5893 4.532 3.5893 7.1787 0 12.472-9.2815 12.7983-20.7383 12.7983-11.4569 0-20.7384-1.7403-20.7384-12.7983 0-2.6467 1.3052-5.1121 3.5531-7.1424 3.7344-3.4081 10.0429-1.5953 17.1853-1.5953 7.1424 0 13.4147-1.849 17.149 1.559Z"></path>
<path fill="#fff"
d="M40.8579 31.9148c0 3.4443-1.9216 6.1997-4.3144 6.1997-2.3929 0-4.3145-2.7917-4.3145-6.1997 0-3.4443 1.9216-6.1998 4.3145-6.1998 2.3928-.0362 4.3144 2.7555 4.3144 6.1998Z"></path>
<path fill="#AF5C51"
d="M39.4794 31.9872c0 2.2842-1.3052 4.1332-2.8642 4.1332-1.5953 0-2.8642-1.849-2.8642-4.1332 0-2.2841 1.3052-4.1331 2.8642-4.1331s2.8642 1.849 2.8642 4.1331Z"></path>
<path fill="#fff"
d="M64.0254 31.9148c0 3.4443-1.9216 6.1997-4.3145 6.1997s-4.3144-2.7917-4.3144-6.1997c0-3.4443 1.9215-6.1998 4.3144-6.1998 2.3567-.0362 4.3145 2.7555 4.3145 6.1998Z"></path>
<path fill="#AF5C51"
d="M62.6108 31.9872c0 2.2842-1.3053 4.1332-2.8643 4.1332-1.5952 0-2.8642-1.849-2.8642-4.1332 0-2.2841 1.3052-4.1331 2.8642-4.1331 1.5953 0 2.8643 1.849 2.8643 4.1331Z"></path>
<path fill="#AF5C51"
d="M48.9068 37.4257c0 .5801-.4713 1.0877-1.0877 1.0877-.5801 0-1.0877-.4714-1.0877-1.0877 0-.6164.4714-1.0877 1.0877-1.0877.5801 0 1.0877.4713 1.0877 1.0877Z"></path>
<path fill="#AF5C51"
d="M45.3171 40.1449c-.0725-.1813.0363-.3625.2176-.4351.1813-.0725.3625.0363.4351.2176.29.7976 1.0151 1.3052 1.849 1.3052.8339 0 1.559-.5438 1.8491-1.3052.0725-.1813.2537-.2901.435-.2176.1813.0726.2901.2538.2176.4351-.3626 1.0514-1.3778 1.7766-2.5017 1.7766-1.1239 0-2.1391-.7252-2.5017-1.7766Z"></path>
<path fill="#C4E5D9"
d="M21.3518 45.0757c0 .29-.3263.5076-.7613.5076-.3988 0-.7614-.2176-.7614-.5076 0-.29.3263-.5076.7614-.5076.435 0 .7613.2176.7613.5076Z"></path>
<path fill="#C4E5D9"
d="M23.4544 46.2359c0 .29-.3263.5075-.7614.5075-.3988 0-.7614-.2175-.7614-.5075 0-.2901.3263-.5076.7614-.5076s.7614.2175.7614.5076Z"></path>
<path fill="#C4E5D9"
d="M24.7229 47.7586c0 .2901-.3263.5076-.7613.5076-.3988 0-.7614-.2175-.7614-.5076 0-.29.3263-.5076.7614-.5076.435-.0362.7613.2176.7613.5076Z"></path>
<path fill="#C4E5D9"
d="M25.8836 49.499c0 .29-.3263.5075-.7614.5075-.3988 0-.7614-.2175-.7614-.5075 0-.2901.3263-.5076.7614-.5076.4351-.0363.7614.2175.7614.5076Z"></path>
<path fill="#C4E5D9"
d="M27.1521 51.0941c0 .2901-.3263.5076-.7613.5076-.3988 0-.7614-.2175-.7614-.5076 0-.29.3263-.5076.7614-.5076.435 0 .7613.2176.7613.5076Z"></path>
<path fill="#C4E5D9"
d="M28.8924 52.5081c0 .2901-.3263.5076-.7614.5076-.3988 0-.7614-.2175-.7614-.5076 0-.29.3263-.5076.7614-.5076.4351-.0362.7614.2176.7614.5076Z"></path>
<path fill="#C4E5D9"
d="M31.3216 53.4146c0 .29-.3263.5075-.7614.5075-.3988 0-.7614-.2175-.7614-.5075 0-.2901.3263-.5076.7614-.5076s.7614.2175.7614.5076Z"></path>
<path fill="#C4E5D9"
d="M33.7517 53.4146c0 .29-.3263.5075-.7613.5075-.3988 0-.7614-.2175-.7614-.5075 0-.2901.3263-.5076.7614-.5076.435 0 .7613.2175.7613.5076Z"></path>
<path fill="#C4E5D9"
d="M36.2166 53.0157c0 .29-.3263.5076-.7614.5076-.3988 0-.7614-.2176-.7614-.5076 0-.29.3263-.5076.7614-.5076.3988 0 .7614.2176.7614.5076Z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 986 B

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
public/media/mike.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -13,8 +13,8 @@ import { capitalizeWords } from "@/lib/string";
import { Metadata } from "next";
import Embed from "@/components/embed";
import DocsFooter from "@/components/docs-footer";
import { cn } from "@/lib/utils";
import OnThisPage from "@/components/on-this-page";
import config from "@/config";
/**
* The page to render the documentation markdown content.
@ -31,10 +31,11 @@ const DocsPage = async ({
);
// Get the content to display based on the provided slug
const pages: DocsContentMetadata[] = getDocsContent();
const pages: DocsContentMetadata[] = await getDocsContent();
const decodedSlug: string = decodeURIComponent(slug || "");
const page: DocsContentMetadata | undefined = pages.find(
(metadata: DocsContentMetadata): boolean =>
metadata.slug === (slug || "intro")
metadata.slug === (decodedSlug || pages[0].slug)
);
if (!page) {
notFound();
@ -44,20 +45,17 @@ const DocsPage = async ({
return (
<main className="w-full flex flex-col">
{/* Breadcrumb */}
<Breadcrumb className="pt-4 select-none">
<Breadcrumb className="pt-4 pb-3 select-none">
<BreadcrumbList>
{splitSlug.map(
(part: string, index: number): ReactElement => {
const active: boolean =
index === splitSlug.length - 1;
{splitSlug
.slice(0, -1)
.map((part: string, index: number): ReactElement => {
const slug: string = splitSlug
.slice(1, index + 1)
.slice(1, index + 2) // Include one more to account for the index shift
.join("/");
return (
<div className="flex items-center" key={part}>
<BreadcrumbItem
className={cn(active && "text-primary")}
>
<BreadcrumbItem>
<BreadcrumbLink
href={slug}
draggable={false}
@ -65,13 +63,17 @@ const DocsPage = async ({
{capitalizeWords(part)}
</BreadcrumbLink>
</BreadcrumbItem>
{index < splitSlug.length - 1 && (
{index < splitSlug.length - 1 && ( // Adjusted to avoid separator after the last breadcrumb
<BreadcrumbSeparator className="pl-1.5" />
)}
</div>
);
}
)}
})}
<BreadcrumbItem className="text-primary">
<BreadcrumbLink href="#" draggable={false}>
{page.title}{" "}
</BreadcrumbLink>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
@ -98,15 +100,19 @@ export const generateMetadata = async ({
}): Promise<Metadata | undefined> => {
const slug: string = (((await params).slug as string[]) || undefined)?.join(
"/"
); // The slug of the content
);
if (slug) {
const content: DocsContentMetadata | undefined = getDocsContent().find(
(metadata: DocsContentMetadata): boolean => metadata.slug === slug
); // Get the content based on the provided slug
if (content) {
const pages: DocsContentMetadata[] = await getDocsContent();
const decodedSlug: string = decodeURIComponent(slug || "");
const page: DocsContentMetadata | undefined = pages.find(
(metadata: DocsContentMetadata): boolean =>
metadata.slug === (decodedSlug || pages[0].slug)
);
if (page) {
return Embed({
title: content.title,
description: content.summary,
title: page.title,
description: page.summary,
thumbnail: config.ogApiUrl.replace("{title}", page.title),
});
}
}

30
src/app/api/og/route.tsx Normal file
View File

@ -0,0 +1,30 @@
import { ImageResponse } from "next/og";
import config from "@/config";
export const GET = async (request: Request) => {
const { searchParams } = new URL(request.url);
const title: string | undefined = searchParams.has("title")
? searchParams.get("title")?.slice(0, 100)
: "Hello World (:";
return new ImageResponse(
(
<div tw="w-full h-full flex flex-col justify-center items-center bg-black/95 text-white">
{/* Logo */}
<img
src={(config.metadata.openGraph?.images as any)[0].url}
alt={`${config.siteName} Logo`}
width={96}
height={96}
/>
{/* Title */}
<h1 tw="text-5xl font-bold">{title}</h1>
</div>
),
{
width: 1200,
height: 630,
}
);
};

7
src/app/config.ts Normal file
View File

@ -0,0 +1,7 @@
/**
* The configuration for this app.
*/
import config from "@/configJson";
import { Config } from "@/types/config";
export default config as Config;

View File

@ -6,64 +6,55 @@ import Navbar from "@/components/navbar/navbar";
import Sidebar from "@/components/sidebar/sidebar";
import Footer from "@/components/footer";
import { TooltipProvider } from "@/components/ui/tooltip";
import { getDocsContent } from "@/lib/mdx";
import config from "@/config";
/**
* The metadata for this app.
*/
export const metadata: Metadata = {
title: {
default: "Pulse Docs",
template: "%s • Pulse Docs",
},
description:
"A lightweight service monitoring solution for tracking the availability of whatever service your heart desires!",
openGraph: {
images: [
{
url: "https://pulseapp.cc/media/logo.png",
width: 128,
height: 128,
},
],
},
twitter: {
card: "summary",
},
};
export const viewport: Viewport = {
themeColor: "#A855F7",
};
export const metadata: Metadata = config.metadata;
export const viewport: Viewport = config.viewport;
export const dynamic = "force-dynamic";
/**
* The primary layout for this app.
*/
const RootLayout = ({
const RootLayout = async ({
children,
}: Readonly<{
children: ReactNode;
}>): ReactElement => (
}>): Promise<ReactElement> => {
const pages: DocsContentMetadata[] = await getDocsContent();
return (
<html lang="en">
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
<body
className="scroll-smooth antialiased"
style={{
background: "var(--background-gradient)",
}}
>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<TooltipProvider delayDuration={100}>
<div className="px-3 sm:px-7 max-w-screen-2xl min-h-screen mx-auto flex flex-col transition-all">
<Navbar />
<div className="pt-[4.5rem] w-full h-full flex flex-grow gap-5">
<div className="px-3 md:px-7 max-w-screen-2xl min-h-screen mx-auto flex flex-col transition-transform">
<Navbar pages={pages} />
<div className="pt-[4.5rem] w-full h-full flex flex-grow gap-5 sm:gap-8 transition-transform transform-gpu">
<div className="relative hidden xs:flex">
<Sidebar />
<Sidebar pages={pages} />
</div>
{children}
</div>
</div>
<Footer />
</TooltipProvider>
</body>
</ThemeProvider>
</body>
</html>
);
);
};
export default RootLayout;

33
src/app/not-found.tsx Normal file
View File

@ -0,0 +1,33 @@
import { ReactElement } from "react";
import Image from "next/image";
/**
* The not found page.
*
* @return the page jsx
*/
const NotFoundPage = (): ReactElement => (
<main className="w-full pt-[25vh] min-h-screen flex justify-center select-none pointer-events-none">
<div className="h-fit flex gap-5 sm:gap-10 items-center transition-all transform-gpu">
{/* Image */}
<div className="relative w-24 h-24 xs:w-20 xs:h-20 sm:w-44 sm:h-44">
<Image
src="/media/mike.png"
alt="Mike Wazowski"
fill
draggable={false}
/>
</div>
{/* Message */}
<div className="flex flex-col gap-0.5">
<h1 className="text-3xl font-bold">Wrong Door!</h1>
<p className="max-w-72 sm:text-lg opacity-75">
The documentation page you were looking for could not be
found.
</p>
</div>
</div>
</main>
);
export default NotFoundPage;

View File

@ -20,13 +20,13 @@ body {
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary: 271 91% 65%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent: 0 0% 90%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;

104
src/app/types/config.ts Normal file
View File

@ -0,0 +1,104 @@
import type { Metadata, Viewport } from "next";
export type Config = {
/**
* The name of this app.
*/
siteName: string;
/**
* The URL to use for generating OG images.
*/
ogApiUrl: string;
/**
* The metadata for this app.
*/
metadata: Metadata;
/**
* The viewport for this app.
*/
viewport: Viewport;
/**
* The source to get the content from.
* This can either be a local source, or
* a remote Git repository.
*/
contentSource: string;
/**
* The URL to link to for editing content of a page.
*/
contentEditUrl: string;
/**
* Social links for this app.
*/
socialLinks: SocialLinkType[];
/**
* Configuration for the footer.
*/
footer: {
/**
* The URL to link to when the branding is clicked.
*/
homeUrl: string;
/**
* Links for the footer.
*/
links: {
[category: string]: FooterLink[];
};
};
};
export type SocialLinkType = {
/**
* The name of this social link.
*/
name: string;
/**
* The tooltip for this social link.
*/
tooltip: string;
/**
* The logo for this social link.
* This can either be an image URL, or
* the name of an icon from <a href="https://lucide.dev/icons">Lucide Icons</a>
*/
logo: string;
/**
* The href for this social link.
*/
href: string;
/**
* Whether to show this social link in the navbar.
*/
navbar: boolean;
};
export type FooterLink = {
/**
* The name of this link.
*/
name: string;
/**
* The href for this link.
*/
href: string;
/**
* The optional name to show
* when the screen size is small.
*/
shortName?: string;
};

View File

@ -8,14 +8,19 @@ type DocsContentMetadata = MDXMetadata & {
title: string;
/**
* The date this content was published.
* The date this content was updated.
*/
published: string;
updated: string;
/**
* The summary of this content.
*/
summary: string;
/**
* The order of this content.
*/
order: number;
};
/**

View File

@ -13,25 +13,26 @@ const DocsFooter = ({
}: {
pages: DocsContentMetadata[];
}): ReactElement => {
const path: string = usePathname();
const path: string = decodeURIComponent(usePathname());
const current: number = pages.findIndex(
(page: DocsContentMetadata) =>
(path === "/" && page.slug === "intro") || path === `/${page.slug}`
(path === "/" && page.slug === pages[0].slug) ||
path === `/${page.slug}`
);
const previous: DocsContentMetadata | undefined =
current > 0 ? pages[current - 1] : undefined;
const next: DocsContentMetadata | undefined =
current < pages.length - 1 ? pages[current + 1] : undefined;
const [publicationDate, setPublicationDate] = useState<string | null>(
DateTime.fromISO(pages[current]?.published).toRelative()
const [updatedDate, setUpdatedDate] = useState<string | null>(
DateTime.fromISO(pages[current]?.updated).toRelative()
);
useEffect(() => {
const interval = setInterval(() => {
setPublicationDate(
DateTime.fromISO(pages[current]?.published).toRelative()
setUpdatedDate(
DateTime.fromISO(pages[current]?.updated).toRelative()
);
}, 1000);
return () => clearInterval(interval);
@ -39,22 +40,24 @@ const DocsFooter = ({
return (
<footer className="xs:mx-5 sm:mx-10 my-2 flex flex-col select-none transition-all transform-gpu">
{/* Publish Date */}
{/* updated Date */}
<div className="ml-auto pt-4">
<SimpleTooltip
content={DateTime.fromISO(
pages[current]?.published
).toLocaleString(DateTime.DATETIME_MED)}
content={`Last updated on ${DateTime.fromISO(
pages[current]?.updated
).toLocaleString(DateTime.DATETIME_MED)}`}
>
<span className="text-sm opacity-75">
Published {publicationDate}
<span className="text-xs sm:text-sm opacity-75 transition-all transform-gpu">
Updated {updatedDate}
</span>
</SimpleTooltip>
</div>
{/* Pages */}
<Separator className="my-4 bg-separator-gradient" />
<div className="flex justify-between">
{previous || next ? (
<Separator className="my-4 dark:bg-separator-gradient" />
) : undefined}
<div className="flex justify-between text-xs sm:text-base">
{/* Previous */}
{previous && (
<Link

View File

@ -13,6 +13,11 @@ type EmbedProps = {
* The description of the embed.
*/
description: string;
/**
* The optional thumbnail image of the embed.
*/
thumbnail?: string | undefined;
};
/**
@ -21,13 +26,25 @@ type EmbedProps = {
* @param props the embed props
* @returns the embed jsx
*/
const Embed = ({ title, description }: EmbedProps): Metadata => {
const Embed = ({ title, description, thumbnail }: EmbedProps): Metadata => {
return {
title: title,
openGraph: {
title: `${title}`,
description: description,
...(thumbnail && {
images: [
{
url: thumbnail,
},
],
}),
},
...(thumbnail && {
twitter: {
card: "summary_large_image",
},
}),
};
};
export default Embed;

View File

@ -5,44 +5,10 @@ import AnimatedGridPattern from "@/components/ui/animated-grid-pattern";
import Link from "next/link";
import Image from "next/image";
import { cn } from "@/lib/utils";
import { ExternalLink, Mail } from "lucide-react";
const links = {
Resources: [
{
name: "Support",
href: "https://support.pulseapp.cc",
},
{
name: "Jobs",
href: "https://jobs.pulseapp.cc",
},
{
name: "Developers",
shortName: "Devs",
href: "https://dev.pulseapp.cc",
external: true,
},
{
name: "System Status",
shortName: "Status",
href: "https://status.pulseapp.cc",
external: true,
},
],
Legal: [
{
name: "Terms & Conditions",
shortName: "Terms",
href: "/legal/terms",
},
{
name: "Privacy Policy",
shortName: "Privacy",
href: "/legal/privacy",
},
],
};
import { ExternalLink } from "lucide-react";
import SocialLink from "@/components/social-link";
import config from "@/config";
import { SocialLinkType } from "@/types/config";
const Footer = (): ReactElement => (
<footer className="relative mt-3 h-[19.5rem] md:h-[17rem] flex justify-center border-t border-zinc-700/75 overflow-hidden select-none">
@ -54,40 +20,34 @@ const Footer = (): ReactElement => (
{/* Socials */}
<div className="pl-1 flex gap-2.5 items-center z-50">
{config.socialLinks.map((link: SocialLinkType) => (
<SocialLink
name="GitHub"
logo="github.svg"
href="https://github.com/PulseAppCC"
/>
<SocialLink
name="Discord"
logo="discord.svg"
href="https://discord.pulseapp.cc"
/>
<SocialLink
name="Email"
logo={<Mail className="opacity-95 w-6 h-6" />}
href="mailto:support@pulseapp.cc"
key={link.name}
className="w-5 h-5"
{...link}
/>
))}
</div>
</div>
{/* Links */}
<div className="flex gap-7 md:gap-12 transition-all transform-gpu">
{Object.entries(links).map(([title, links]) => (
{Object.entries(config.footer.links).map(
([title, links]) => (
<LinkCategory key={title} title={title}>
{links.map((link) => (
<FooterLink key={link.name} {...link} />
))}
</LinkCategory>
))}
)
)}
</div>
</div>
{/* Copyright */}
<p className="absolute inset-x-0 bottom-3.5 flex text-sm text-center justify-center opacity-60">
Copyright &copy; {new Date().getFullYear()} Pulse App. All
rights reserved.
Copyright &copy; {new Date().getFullYear()} {config.siteName}.
All rights reserved.
</p>
</div>
@ -105,46 +65,17 @@ const Footer = (): ReactElement => (
const Branding = () => (
<Link
className="flex gap-3 items-center hover:opacity-75 transition-all transform-gpu"
href="https://pulseapp.cc"
href={config.footer.homeUrl}
draggable={false}
>
<Image
src="/media/logo.png"
alt="Pulse App Logo"
alt={`${config.siteName} Logo`}
width={40}
height={40}
draggable={false}
/>
<h1 className="text-xl font-bold">Pulse App</h1>
</Link>
);
const SocialLink = ({
name,
logo,
href,
}: {
name: string;
logo: string | ReactElement;
href: string;
}) => (
<Link
className="hover:opacity-75 transition-all transform-gpu"
href={href}
target="_blank"
draggable={false}
>
{typeof logo === "string" ? (
<Image
src={`/media/${logo}`}
alt={`${name}'s Logo`}
width={20}
height={20}
draggable={false}
/>
) : (
logo
)}
<h1 className="text-xl font-bold">{config.siteName}</h1>
</Link>
);

View File

@ -0,0 +1,32 @@
"use client";
import { ReactElement, ReactNode } from "react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
type ImageViewerProps = {
className?: string | undefined;
children: ReactNode;
};
const ImageViewer = ({
className,
children,
}: ImageViewerProps): ReactElement => {
return (
<Dialog>
<DialogTrigger
className={cn(
"hover:scale-[1.005] transition-all transform-gpu",
className
)}
>
{children}
</DialogTrigger>
<DialogContent className="p-0 min-w-[20rem] max-w-screen-xl">
{children}
</DialogContent>
</Dialog>
);
};
export default ImageViewer;

View File

@ -1,12 +1,48 @@
import { ReactElement, ReactNode } from "react";
import { isValidElement, ReactElement, ReactNode } from "react";
import { MDXRemote } from "remote-mdx/rsc";
import { cn } from "@/lib/utils";
import remarkGfm from "remark-gfm";
import {
Activity,
CircleAlert,
Lightbulb,
MessageSquareWarning,
OctagonAlert,
TriangleAlert,
} from "lucide-react";
import Link from "next/link";
import { capitalizeWords } from "@/lib/string";
import ImageViewer from "@/components/image-viewer";
import Image from "next/image";
const blockquoteStyles: { [key: string]: any } = {
NOTE: {
icon: <CircleAlert className="w-4 h-4" />,
style: "text-[#1F6FEB] border-[#1F6FEB]",
},
TIP: {
icon: <Lightbulb className="w-4 h-4" />,
style: "text-[#4A8BD5] border-[#4A8BD5]",
},
IMPORTANT: {
icon: <MessageSquareWarning className="w-4 h-4" />,
style: "text-[#8957E5] border-[#8957E5]",
},
WARNING: {
icon: <TriangleAlert className="w-4 h-4" />,
style: "text-[#9E6A03] border-[#9E6A03]",
},
CAUTION: {
icon: <OctagonAlert className="w-4 h-4" />,
style: "text-[#DA3633] border-[#DA3633]",
},
};
/**
* The MDX components to style.
*/
const components = {
// Headings
h1: ({ children }: { children: ReactNode }): ReactElement => (
<Heading as="h1" size={1} className="text-4xl">
{children}
@ -37,6 +73,8 @@ const components = {
{children}
</Heading>
),
// Text
a: ({
href,
children,
@ -44,19 +82,62 @@ const components = {
href: string;
children: ReactNode;
}): ReactElement => (
<a
className="text-minecraft-green-4 cursor-pointer hover:opacity-85 transition-all transform-gpu"
<Link
className="text-primary cursor-pointer hover:opacity-75 transition-all transform-gpu"
href={href}
draggable={false}
>
{children}
</a>
</Link>
),
p: ({ children }: { children: ReactNode }): ReactElement => (
<p className="leading-4 text-zinc-300/80">{children}</p>
<p className="leading-5 select-none">{children}</p>
),
// Media
img: ({ src, alt }: { src: string; alt: string }): ReactElement => (
<ImageViewer className="m-2 my-2.5">
<Image
className="ring-1 ring-muted/45 rounded-2xl select-none"
src={src}
alt={alt}
width={1920}
height={1080}
unoptimized
draggable={false}
/>
</ImageViewer>
),
// Lists
ul: ({ children }: { children: ReactNode }): ReactElement => (
<ul className="px-3 list-disc list-inside">{children}</ul>
<ul className="px-3 list-disc list-inside select-none">{children}</ul>
),
// Blockquotes
blockquote: ({ children }: { children: ReactNode }): ReactElement => {
const match = extractBlockQuoteText(children).match(
/^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)]\s*(.*)/i
);
let style: any;
if (!match || !(style = blockquoteStyles[match[1]])) {
return <blockquote>{children}</blockquote>;
}
return (
<blockquote
className={cn(
"my-2 pl-3 py-1.5 flex flex-col gap-2 border-l-[3px] select-none",
style.style
)}
>
<h1 className="flex gap-2 items-center">
{style.icon}
{capitalizeWords(match[1])}
</h1>
<p className="text-foreground opacity-85">{match[2]}</p>
</blockquote>
);
},
};
/**
@ -71,6 +152,8 @@ export const CustomMDX = (props: any): ReactElement => (
components={{
...components,
...(props.components || {}),
Link,
Activity,
}}
options={{
mdxOptions: {
@ -83,6 +166,7 @@ export const CustomMDX = (props: any): ReactElement => (
/**
* A heading component.
*
* @param as the type of heading
* @param className the class name of the heading
* @param size the size of the heading
* @param children the children within the heading
@ -104,7 +188,11 @@ const Heading = ({
return (
<Component
id={id}
className={cn("pt-2.5 font-bold", size >= 2 && "pt-7", className)}
className={cn(
"py-3 font-bold select-none",
size >= 2 && "pt-7",
className
)}
>
{children}
</Component>
@ -117,3 +205,16 @@ const slugify = (text: string): string =>
.replace(/[^\w\s-]/g, "")
.replace(/[\s_-]+/g, "-")
.trim();
const extractBlockQuoteText = (node: ReactNode): string => {
if (typeof node === "string") {
return node;
}
if (Array.isArray(node)) {
return node.map(extractBlockQuoteText).join("");
}
if (isValidElement(node)) {
return extractBlockQuoteText(node.props.children);
}
return "";
};

View File

@ -1,15 +1,17 @@
"use client";
import { ReactElement } from "react";
import Link from "next/link";
import Image from "next/image";
import QuickSearchDialog from "@/components/navbar/search-dialog";
import { getDocsContent } from "@/lib/mdx";
import Sidebar from "@/components/sidebar/sidebar";
import SocialLink from "@/components/social-link";
import config from "@/config";
import { SocialLinkType } from "@/types/config";
const Navbar = (): ReactElement => {
const pages: DocsContentMetadata[] = getDocsContent();
return (
<nav className="fixed left-0 inset-x-0 bg-white/[0.007] backdrop-saturate-100 backdrop-blur-xl border-b z-50">
<div className="px-3 sm:px-7 max-w-screen-2xl mx-auto py-4 flex justify-between items-center transition-all transform-gpu">
const Navbar = ({ pages }: { pages: DocsContentMetadata[] }): ReactElement => (
<nav className="fixed left-0 inset-x-0 bg-white/95 dark:bg-white/[0.007] backdrop-saturate-100 backdrop-blur-xl border-b z-50">
<div className="px-3 md:px-7 max-w-screen-2xl mx-auto py-4 flex justify-between items-center transition-all transform-gpu">
{/* Branding */}
<Link
className="flex gap-1 items-end hover:opacity-75 transition-all transform-gpu select-none"
@ -19,7 +21,7 @@ const Navbar = (): ReactElement => {
<h1 className="text-lg font-semibold">docs.</h1>
<Image
src="/media/logo.png"
alt="Pulse App Logo"
alt={`${config.siteName} Logo`}
width={36}
height={36}
draggable={false}
@ -35,42 +37,19 @@ const Navbar = (): ReactElement => {
{/* Social */}
<div className="flex gap-5 items-center">
<SocialLink
name="GitHub"
link="https://github.com/PulseAppCC"
icon="/media/github.svg"
/>
<SocialLink
name="Discord"
link="https://discord.pulseapp.cc"
icon="/media/discord.svg"
/>
{config.socialLinks
.filter((link: SocialLinkType) => link.navbar)
.map((link: SocialLinkType) => (
<SocialLink key={link.name} {...link} />
))}
</div>
{/* Mobile Sidebar */}
<div className="flex xs:hidden">
<Sidebar />
<Sidebar pages={pages} />
</div>
</div>
</div>
</nav>
);
};
const SocialLink = ({
name,
link,
icon,
}: {
name: string;
link: string;
icon: string;
}): ReactElement => (
<div className="relative w-6 h-6 hover:opacity-75 transition-all transform-gpu select-none">
<Link href={link} target="_blank" draggable={false}>
<Image src={icon} alt={`${name} Logo`} fill draggable={false} />
</Link>
</div>
);
export default Navbar;

View File

@ -52,7 +52,7 @@ const QuickSearchDialog = ({
className="cursor-pointer hover:opacity-85 transition-all transform-gpu select-none"
onClick={() => setOpen(true)}
>
<div className="absolute top-2.5 left-3 z-10">
<div className="absolute top-[0.55rem] left-3 z-10">
<Search className="w-[1.15rem] h-[1.15rem]" />
</div>
@ -114,5 +114,4 @@ const QuickSearchDialog = ({
</>
);
};
export default QuickSearchDialog;

View File

@ -8,6 +8,8 @@ import { motion, useInView } from "framer-motion";
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
import { AlignLeftIcon, ArrowUpFromDot, MoveRight } from "lucide-react";
import config from "@/config";
import { Skeleton } from "@/components/ui/skeleton";
type Header = {
id: string;
@ -16,6 +18,7 @@ type Header = {
};
const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
const [loading, setLoading] = useState<boolean>(true);
const [headers, setHeaders] = useState<Header[]>([]);
const [activeHeader, setActiveHeader] = useState<string | undefined>(
undefined
@ -33,7 +36,7 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
let match;
while ((match = headerRegex.exec(page.content)) !== null) {
const level: number = match[1].length; // The number of # symbols determines the header level
const text: string = match[2].trim();
const text: string = match[2].trim().replace(/<[^>]*>/g, "");
const id: string = text
.toLowerCase()
.replace(/\s+/g, "-")
@ -43,6 +46,7 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
}
setHeaders(extractedHeaders);
setLoading(false);
}, [page.content]);
useEffect(() => {
@ -79,8 +83,8 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
return (
<motion.div
ref={ref}
className="sticky top-[7rem] w-44 max-h-[calc(100vh-3.5rem)] flex flex-col gap-2 text-sm select-none"
initial={{ opacity: 0 }}
className="sticky top-[7.5rem] w-44 max-h-[calc(100vh-3.5rem)] flex flex-col gap-2 text-sm select-none"
initial={{ opacity: 1 }}
animate={{ opacity: inView ? 1 : 0 }}
transition={{ duration: 0.2 }}
>
@ -92,7 +96,12 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
{/* Headers */}
<ul className="relative">
{headers.map((header: Header) => (
{loading ? (
<Skeleton className="w-full h-5 bg-accent rounded-lg" />
) : headers.length === 0 ? (
<span className="opacity-75">Nothing ):</span>
) : (
headers.map((header: Header) => (
<li
key={header.id}
className={cn(
@ -101,12 +110,14 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
? "font-semibold text-primary"
: "opacity-65"
)}
style={{ paddingLeft: `${(header.level - 1) * 16}px` }}
style={{
paddingLeft: `${(header.level - 1) * 16}px`,
}}
>
{/* Indentation */}
{header.level > 1 && (
<div
className="absolute left-0 top-0 bottom-0 border-l border-muted"
className="absolute left-0 top-0 bottom-0 border-l border-accent"
style={{
left: `${(header.level - 2) * 16 + 4}px`,
}}
@ -119,15 +130,16 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
draggable={false}
className="block py-1"
>
{truncateText(header.text, 24)}
{truncateText(header.text, 20)}
</Link>
</li>
))}
))
)}
</ul>
{/* Footer */}
<div>
<Separator className="mt-1 mb-3.5" />
<Separator className="mt-1 mb-3.5 dark:bg-separator-gradient" />
<Footer page={page} />
</div>
</motion.div>
@ -148,7 +160,9 @@ const Footer = ({ page }: { page: DocsContentMetadata }): ReactElement => {
{/* Edit on Git */}
<Link
className="flex gap-1.5 items-center text-xs hover:opacity-75 transition-all transform-gpu group"
href={`https://git.rainnny.club/PulseApp/docs/src/branch/master/docs/${page.slug}.md`}
href={config.contentEditUrl
.replace("{slug}", page.slug as string)
.replace("{ext}", page.extension as string)}
target="_blank"
draggable={false}
>

View File

@ -20,11 +20,11 @@ const SidebarLinks = ({
}): ReactElement => {
const tree = useMemo(() => buildTree(pages), [pages]);
return (
<>
<div className="flex flex-col gap-1">
{Object.values(tree).map((node: TreeNode) => (
<CategoryItem key={node.slug} node={node} />
<CategoryItem key={node.slug} pages={pages} node={node} />
))}
</>
</div>
);
};
@ -36,27 +36,30 @@ type TreeNode = {
};
const CategoryItem = ({
pages,
node,
depth = 0,
isLast = true,
}: {
pages: DocsContentMetadata[];
node: TreeNode;
depth?: number;
isLast?: boolean;
}) => {
const path = usePathname();
const path = decodeURIComponent(usePathname());
const active =
(path === "/" && node.slug === "intro") || path === `/${node.slug}`;
(path === "/" && node.slug === pages[0].slug) ||
path === `/${node.slug}`;
const [isOpen, setIsOpen] = useState(true);
const hasChildren = Object.keys(node.children).length > 0;
return (
<div className={cn(`relative select-none`, depth > 0 && "ml-2.5")}>
<div className={cn(`relative select-none`, depth > 0 && "ml-3")}>
{/* Indentation */}
{depth > 0 && (
<div
className={cn(
"absolute left-0 bottom-0 border-l border-muted",
"absolute left-0 bottom-0 border-l border-accent",
isLast ? "h-[32px]" : "h-[100%]",
active && "border-primary"
)}
@ -72,10 +75,13 @@ const CategoryItem = ({
>
<Button
className={cn(
`relative w-full px-1.5 h-8 text-base justify-between hover:bg-accent/20`,
`relative w-full px-1.5 h-8 justify-between hover:bg-accent/35 hover:opacity-90`,
node.isFolder
? "mb-0.5 text-sm font-semibold"
: "lg:text-base",
depth > 0 && "pl-4",
active &&
"text-primary/95 font-bold hover:text-primary"
"text-primary/95 font-semibold hover:text-primary"
)}
variant="ghost"
>
@ -83,7 +89,7 @@ const CategoryItem = ({
{hasChildren && (
<motion.div
initial={false}
animate={{ rotate: isOpen ? 90 : 180 }}
animate={{ rotate: isOpen ? 90 : 0 }}
transition={{ duration: 0.2 }}
>
<ChevronRight className="w-4 h-4" />
@ -118,6 +124,7 @@ const CategoryItem = ({
(child, index, array) => (
<CategoryItem
key={child.slug}
pages={pages}
node={child}
depth={depth + 1}
isLast={index === array.length - 1}

View File

@ -1,13 +1,12 @@
import { ReactElement } from "react";
import { Separator } from "@/components/ui/separator";
import { getDocsContent } from "@/lib/mdx";
import SidebarLinks from "@/components/sidebar/sidebar-links";
import ThemeSwitcher from "@/components/theme-switcher";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import QuickSearchDialog from "@/components/navbar/search-dialog";
import { AlignRightIcon } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import ThemeSwitcher from "@/components/theme-switcher";
const Sidebar = (): ReactElement => (
const Sidebar = ({ pages }: { pages: DocsContentMetadata[] }): ReactElement => (
<>
{/* Mobile */}
<div className="xs:hidden">
@ -16,21 +15,23 @@ const Sidebar = (): ReactElement => (
<AlignRightIcon className="w-6 h-6" />
</SheetTrigger>
<SheetContent className="h-full px-5 pt-11" side="right">
<SidebarContent />
<SidebarContent pages={pages} />
</SheetContent>
</Sheet>
</div>
{/* Desktop */}
<div className="hidden xs:flex sticky top-[4.5rem] max-h-[calc(100vh-3.5rem)] overflow-y-auto min-w-32 w-40 lg:w-52 py-5 flex-col justify-between transition-all transform-gpu">
<SidebarContent />
<div className="hidden xs:flex sticky top-[4.5rem] max-h-[calc(100vh-4.5rem)] min-w-32 w-40 lg:w-52 py-5 pb-3 flex-col justify-between transition-all transform-gpu overflow-y-auto">
<SidebarContent pages={pages} />
</div>
</>
);
const SidebarContent = (): ReactElement => {
const pages: DocsContentMetadata[] = getDocsContent();
return (
const SidebarContent = ({
pages,
}: {
pages: DocsContentMetadata[];
}): ReactElement => (
<div className="h-full flex flex-col justify-between">
{/* Top */}
<div className="flex flex-col">
@ -42,11 +43,10 @@ const SidebarContent = (): ReactElement => {
{/* Theme Switcher */}
<div className="flex flex-col items-center">
<Separator className="mb-3 bg-separator-gradient" />
<Separator className="mb-3 dark:bg-separator-gradient" />
<ThemeSwitcher />
</div>
</div>
);
};
);
export default Sidebar;

View File

@ -39,7 +39,10 @@ const SimpleTooltip = ({
}: SimpleTooltipProps): ReactElement => (
<Tooltip>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent className="bg-muted text-white" side={side}>
<TooltipContent
className="bg-accent text-accent-foreground"
side={side}
>
{content}
</TooltipContent>
</Tooltip>

View File

@ -0,0 +1,46 @@
import SimpleTooltip from "@/components/simple-tooltip";
import Link from "next/link";
import Image from "next/image";
import { cn } from "@/lib/utils";
import Icon from "@/components/ui/icon";
import { icons } from "lucide-react";
import { SocialLinkType } from "@/types/config";
type SocialLinkProps = SocialLinkType & {
className?: string | undefined;
};
const SocialLink = ({
className,
name,
tooltip,
logo,
href,
}: SocialLinkProps) => (
<SimpleTooltip content={tooltip}>
<Link
className={cn(
"w-6 h-6 hover:opacity-75 transition-all transform-gpu select-none",
className
)}
href={href}
target="_blank"
draggable={false}
>
{logo.startsWith("./") ? (
<Image
src={`/media/${logo.substring(2)}`}
alt={`${name}'s Logo`}
fill
draggable={false}
/>
) : (
<Icon
className="opacity-95 w-full h-full"
name={logo as keyof typeof icons}
/>
)}
</Link>
</SimpleTooltip>
);
export default SocialLink;

View File

@ -6,11 +6,13 @@ import { UseThemeProps } from "next-themes/dist/types";
import { Monitor, MoonStar, Sun } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import SimpleTooltip from "@/components/simple-tooltip";
import { capitalizeWords } from "@/lib/string";
const themes = {
system: <Monitor className="w-4 h-4" />,
dark: <MoonStar className="w-4 h-4" />,
light: <Sun className="w-4 h-4" />,
system: <Monitor className="w-4 h-4" />,
};
/**
@ -27,26 +29,29 @@ const ThemeSwitcher = (): ReactElement => {
}, []);
return (
<div className="w-fit p-1 flex gap-1.5 bg-black/30 ring-1 ring-white/5 rounded-full">
<div className="w-fit p-1 flex gap-1.5 bg-gray-600/5 dark:bg-black/30 ring-1 light:ring-inset ring-gray-600/5 dark:ring-white/5 rounded-full">
{Object.entries(themes).map(([theme, icon]) => {
const active: boolean = mounted && theme === activeTheme;
return (
<Button
<SimpleTooltip
key={theme}
content={`${capitalizeWords(theme)} Theme`}
>
<Button
className={cn(
"p-1 h-6 opacity-80 rounded-full",
active &&
"ring-1 bg-zinc-900 ring-white/15 opacity-100"
"ring-1 bg-white dark:bg-zinc-900 ring-gray-900/10 dark:ring-white/15 hover:bg-white hover:dark:bg-zinc-900 opacity-100"
)}
variant="ghost"
onClick={() => setTheme(theme)}
>
{icon}
</Button>
</SimpleTooltip>
);
})}
</div>
);
};
export default ThemeSwitcher;

View File

@ -83,14 +83,16 @@ export function GridPattern({
});
}
});
let current = undefined;
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
current = containerRef.current;
}
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
if (current) {
resizeObserver.unobserve(current);
}
};
}, [containerRef]);

View File

@ -0,0 +1,10 @@
import { icons, LucideProps } from "lucide-react";
interface IconProps extends LucideProps {
name: keyof typeof icons;
}
export default function Icon({ name, color, size, ...props }: IconProps) {
const LucideIcon = icons[name];
return <LucideIcon color={color} size={size} {...props} />;
}

View File

@ -0,0 +1,15 @@
import { cn } from "@/lib/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
);
}
export { Skeleton };

View File

@ -1,6 +1,11 @@
import * as fs from "node:fs";
import { Stats } from "node:fs";
import path from "node:path";
import config from "@/config";
import simpleGit from "simple-git";
import os from "node:os";
import { cache } from "react";
import "server-only";
/**
* The regex to match for metadata.
@ -8,20 +13,91 @@ import path from "node:path";
const METADATA_REGEX: RegExp = /---\s*([\s\S]*?)\s*---/;
/**
* The directory docs are stored in.
* Check if the DOCS_DIR is a Git URL.
*/
const DOCS_DIR: string = path.join(process.cwd(), "docs");
const isGitUrl = (url: string): boolean => {
return /^https?:\/\/|git@|\.git$/.test(url);
};
/**
* Get the content to
* display in the docs.
* The directory docs are stored in.
*/
export const getDocsContent = (): DocsContentMetadata[] => {
const content: DocsContentMetadata[] = [];
for (const directory of getRecursiveDirectories(DOCS_DIR)) {
content.push(...getMetadata<DocsContentMetadata>(DOCS_DIR, directory));
const DOCS_DIR: string = isGitUrl(config.contentSource)
? config.contentSource
: path.join(config.contentSource.replace("{process}", process.cwd()));
const LAST_UPDATE_FILE = path.join(
os.tmpdir(),
"docs_cache",
"last_update.json"
);
const CACHE_DURATION_MS: number = 5 * 60 * 1000; // 5 minutes in milliseconds
/**
* Clone the Git repository if DOCS_DIR is a URL, else use the local directory.
* If it's a Git URL, clone it to a cache directory and reuse it.
*/
const getDocsDirectory = cache(async (): Promise<string> => {
if (isGitUrl(DOCS_DIR)) {
const repoHash: string = Buffer.from(DOCS_DIR).toString("base64"); // Create a unique identifier based on the repo URL
const cacheDir: string = path.join(os.tmpdir(), "docs_cache", repoHash);
// Pull the latest changes from the repo if we don't have it
if (!fs.existsSync(cacheDir) || fs.readdirSync(cacheDir).length < 1) {
console.log("Cloning initial docs content from Git...");
try {
await simpleGit().clone(DOCS_DIR, cacheDir, { "--depth": 1 });
storeUpdatedRepoTime();
} catch (error) {
// Simply ignore this error. When cloning the repo for
// the first time, it'll sometimes error saying the dir
// is already created.
}
return content;
} else if (shouldUpdateRepo()) {
// Pull the latest changes from Git
console.log("Pulling docs content from Git...");
await simpleGit(cacheDir)
.reset(["--hard"]) // Reset any local changes
.pull(); // Pull latest changes
storeUpdatedRepoTime();
}
return cacheDir;
}
return DOCS_DIR;
});
const shouldUpdateRepo = (): boolean => {
if (!fs.existsSync(LAST_UPDATE_FILE)) {
return true;
}
return (
Date.now() -
JSON.parse(fs.readFileSync(LAST_UPDATE_FILE, "utf-8")).lastUpdate >
CACHE_DURATION_MS
);
};
const storeUpdatedRepoTime = () =>
fs.writeFileSync(
LAST_UPDATE_FILE,
JSON.stringify({ lastUpdate: Date.now() }),
"utf-8"
);
/**
* Get the content to display in the docs.
*/
export const getDocsContent = async (): Promise<DocsContentMetadata[]> => {
const docsDir: string = await getDocsDirectory();
const content: DocsContentMetadata[] = [];
for (const directory of getRecursiveDirectories(docsDir)) {
content.push(...getMetadata<DocsContentMetadata>(docsDir, directory));
}
return content.sort((a: DocsContentMetadata, b: DocsContentMetadata) => {
const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
return orderA - orderB;
});
};
/**
@ -41,18 +117,27 @@ const getMetadata = <T extends MDXMetadata>(
const extension: string = path.extname(file); // The file extension
return extension === ".md" || extension === ".mdx";
}); // Read the MDX files
return files.map((file: string): T => {
const metadata: T[] = [];
for (let i = files.length - 1; i >= 0; i--) {
const file: string = files[i];
const filePath: string = path.join(directory, file); // The path of the file
return {
const fileMetadata: T | undefined = parseMetadata<T>(
fs.readFileSync(filePath, "utf-8")
);
if (!fileMetadata) {
continue;
}
metadata.push({
slug: filePath
.replace(parent, "")
.replace(/\\/g, "/") // Normalize the path
.replace(/\.mdx?$/, "")
.substring(1),
extension: path.extname(file),
...parseMetadata<T>(fs.readFileSync(filePath, "utf-8")),
}; // Map each file to its metadata
...fileMetadata,
});
}
return metadata;
};
/**
@ -63,8 +148,15 @@ const getMetadata = <T extends MDXMetadata>(
* @returns the metadata and content
* @template T the type of metadata
*/
const parseMetadata = <T extends MDXMetadata>(content: string): T => {
const metadataBlock: string = METADATA_REGEX.exec(content)![1]; // Get the block of metadata
const parseMetadata = <T extends MDXMetadata>(
content: string
): T | undefined => {
const extracted = METADATA_REGEX.exec(content);
const metadataBlock: string | undefined =
extracted && extracted.length > 1 ? extracted[1] : undefined; // Get the block of metadata
if (!metadataBlock) {
return undefined;
}
content = content.replace(METADATA_REGEX, "").trim(); // Remove the metadata block from the content
const metadata: Partial<{
[key: string]: string;

28
src/middleware.ts Normal file
View File

@ -0,0 +1,28 @@
import { type NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest): NextResponse {
const before: number = Date.now();
const response: NextResponse = NextResponse.next();
if (process.env.NODE_ENV === "production") {
const ip: string | null =
request.headers.get("CF-Connecting-IP") ||
request.headers.get("X-Forwarded-For");
console.log(
`${ip} | ${request.method} ${request.nextUrl.pathname} ${response.status} in ${(Date.now() - before).toFixed(0)}ms`
);
}
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};

View File

@ -24,6 +24,15 @@
"paths": {
"@/*": [
"./src/*"
],
"@/config": [
"./src/app/config.ts"
],
"@/types/*": [
"./src/app/types/*"
],
"@/configJson": [
"./config.json"
]
},
"target": "ES2017"