improve docs
This commit is contained in:
@@ -36,7 +36,6 @@ export default defineConfig({
|
||||
{ text: '@cap.js/widget', link: '/guide/widget.md' }
|
||||
]
|
||||
},
|
||||
{ text: "Vulnerabilities", link: "/guide/vulnerabilities.md" },
|
||||
{ text: "Demo", link: "https://cap-starter.glitch.me/" },
|
||||
],
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@ import { h } from 'vue'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import './style.css'
|
||||
|
||||
import vitepressNprogress from 'vitepress-plugin-nprogress'
|
||||
import 'vitepress-plugin-nprogress/lib/css/index.css'
|
||||
|
||||
/** @type {import('vitepress').Theme} */
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
@@ -13,8 +10,5 @@ export default {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||
})
|
||||
},
|
||||
enhanceApp: (ctx) => {
|
||||
vitepressNprogress(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,4 @@ Cap is fully compliant with GDPR and CCPA. It doesn't use cookies or track you i
|
||||
## Security
|
||||
|
||||
- IP addresses are not stored by default
|
||||
- Requests are stored in-memory in make sure they are not tampered with (hashed tokens only, this is stored in .data/tokensList.json by default)
|
||||
- Confirmation tokens reset after 20 minutes
|
||||
- Challenges are only valid for 10 minutes
|
||||
- Challenges are stored in memory to make sure they are not tampered with (expire after 10 minutes by default), while tokens are stored in a file (hashed tokens only, this is `.data/tokensList.json` by default and expire after 20 minutes)
|
||||
+12
-8
@@ -1,24 +1,28 @@
|
||||
# Floating mode
|
||||
|
||||
Cap can automatically hide the captcha until the form is submitted. To use this feature, add the `data-cap-floating` attribute to the Cap widget with the query selector of the `cap-widget` element you want to use.
|
||||
Cap can automatically hide the CAPTCHA until a button is pressed. To use this, add the `data-cap-floating` attribute to the Cap widget with the query selector of the `cap-widget` element you want to use.
|
||||
|
||||
```html
|
||||
<cap-widget id="floating" onsolve="alert(`Verification token: ${event.detail.token}`)" data-cap-api-endpoint="<api endpoint>"></cap-widget>
|
||||
<cap-widget
|
||||
id="floating"
|
||||
onsolve="console.log(`token: ${event.detail.token}`)"
|
||||
data-cap-api-endpoint="<api endpoint>"
|
||||
></cap-widget>
|
||||
|
||||
<button data-cap-floating="#floating" data-cap-floating-position="bottom">Trigger floating mode</button>
|
||||
<button data-cap-floating="#floating" data-cap-floating-position="bottom">
|
||||
Trigger floating mode
|
||||
</button>
|
||||
```
|
||||
|
||||
You'll also need to import both the Cap library and the floating mode script from JSDelivr:
|
||||
|
||||
```html{2}
|
||||
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget/cap-floating.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget/cap-floating.min.js"></script> <!-- [!code ++] -->
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> You'll not need to re-import the main Cap library if you've already done it.
|
||||
|
||||
The following attributes are supported:
|
||||
|
||||
- `data-cap-floating`: The query selector of the `cap-widget` element you want to use.
|
||||
- `data-cap-floating`: The CSS selector of the `cap-widget` element you want to use.
|
||||
- `data-cap-floating-position`: The position of the floating widget. Can be `top` or `bottom`.
|
||||
- `data-cap-floating-offset`: The offset of the floating widget from the trigger element.
|
||||
+112
-17
@@ -3,12 +3,14 @@ outline: deep
|
||||
---
|
||||
|
||||
# Quickstart
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Requirements
|
||||
* **Server-side library:** At least Node 14. Most modern Bun or Deno versions should work too. If you're using Glitch, make sure to set node 14 or higher in your `engines` field in `package.json`
|
||||
|
||||
* **Client-side widget:** All modern browsers should be supported, but the build script specifically targets the last 10 versions of Chrome, Firefox, Safari and Edge.
|
||||
- **Server-side library:** At least Node 14. Most modern Bun or Deno versions should work too. If you're using Glitch, make sure to set Node 14 or higher in your `engines` field in `package.json`. If you don't use a JavaScript runtime, consider using the [Standalone](standalone.md) server.
|
||||
|
||||
- **Client-side widget:** All modern browsers should be supported. A [compatibility version](widget.md#compatibility-version) is available too.
|
||||
|
||||
## Client-side
|
||||
|
||||
@@ -21,22 +23,24 @@ Start by adding importing the Cap widget library from a CDN:
|
||||
Next, add the `<cap-widget>` component to your HTML.
|
||||
|
||||
```html
|
||||
<cap-widget id="cap" data-cap-api-endpoint="<your cap api endpoint>"></cap-widget>
|
||||
<cap-widget
|
||||
id="cap"
|
||||
data-cap-api-endpoint="<your cap api endpoint>"
|
||||
></cap-widget>
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> You'll need to start a server with the Cap API running at the same URL as specified in the `data-cap-api-endpoint` attribute.
|
||||
> In the server-side example we provided, it's set to `/api`, but you can change this by replacing every `app.post('/api/...', ...)` to `app.post('/<endpoint>/...', ...)`.
|
||||
|
||||
|
||||
Then, in your JavaScript, listen for the `solve` event to capture the token when generated:
|
||||
|
||||
```js{3}
|
||||
const widget = document.querySelector("#cap");
|
||||
|
||||
widget.addEventListener("solve", function (e) {
|
||||
widget.addEventListener("solve", function (e) {
|
||||
const token = e.detail.token;
|
||||
|
||||
|
||||
// Handle the token as needed
|
||||
});
|
||||
```
|
||||
@@ -44,6 +48,7 @@ widget.addEventListener("solve", function (e) {
|
||||
Alternatively, you can use `onsolve=""` directly within the widget or wrap the widget in a `<form></form>` (where Cap will automatically submit the token alongside other form data).
|
||||
|
||||
## Server-side
|
||||
|
||||
Cap is fully self-hosted, so you'll need to start a server with the Cap API running at the same URL as specified in the `data-cap-api-endpoint` attribute. This is easy since we've already pre-made a library to help you generate and validate challenges for you.
|
||||
|
||||
Start by installing it using npm or bun:
|
||||
@@ -55,24 +60,114 @@ npm i @cap.js/server
|
||||
> [!NOTE]
|
||||
> It is recommended to use at least Node.js 14 or Bun 1.0.0. You might experience multiple issues on older versions of these runtimes.
|
||||
|
||||
Now, you'll need to change your server code to add the routes that Cap needs to work. Here's an example with Express.js:
|
||||
Now, you'll need to change your server code to add the routes that Cap needs to work. Here's an example:
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const Cap = require('@cap.js/server');
|
||||
::: code-group
|
||||
|
||||
```js [Elysia]
|
||||
import { Elysia } from "elysia";
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const cap = new Cap({
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
});
|
||||
|
||||
new Elysia()
|
||||
.post("/api/challenge", () => {
|
||||
return cap.createChallenge();
|
||||
})
|
||||
|
||||
.post("/api/redeem", async ({ body, set }) => {
|
||||
const { token, solutions } = body;
|
||||
|
||||
if (!token || !solutions) {
|
||||
set.status = 400;
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
return await cap.redeemChallenge({ token, solutions });
|
||||
})
|
||||
.listen(3000);
|
||||
|
||||
console.log(`🦊 Elysia is running at http://localhost:3000`);
|
||||
```
|
||||
|
||||
```js [Fastify]
|
||||
import Fastify from "fastify";
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const fastify = Fastify();
|
||||
const cap = new Cap({
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
});
|
||||
|
||||
fastify.post("/api/challenge", (req, res) => {
|
||||
res.send(cap.createChallenge());
|
||||
});
|
||||
|
||||
fastify.post("/api/redeem", async (req, res) => {
|
||||
const { token, solutions } = req.body;
|
||||
if (!token || !solutions) {
|
||||
return res.code(400).send({ success: false });
|
||||
}
|
||||
|
||||
res.send(await cap.redeemChallenge({ token, solutions }));
|
||||
});
|
||||
|
||||
fastify.listen({ port: 3000, host: "0.0.0.0" }).then(() => {
|
||||
console.log("Server is running on http://localhost:3000");
|
||||
});
|
||||
```
|
||||
|
||||
```js [Bun.serve]
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const cap = new Cap({
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
});
|
||||
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
routes: {
|
||||
"/api/challenge": {
|
||||
POST: () => {
|
||||
return Response.json(cap.createChallenge());
|
||||
},
|
||||
},
|
||||
"/api/redeem": {
|
||||
POST: async (req) => {
|
||||
const body = await req.json();
|
||||
const { token, solutions } = body;
|
||||
|
||||
if (!token || !solutions) {
|
||||
return Response.json({ success: false }, { status: 400 });
|
||||
}
|
||||
|
||||
return Response.json(await cap.redeemChallenge({ token, solutions }));
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Server running at http://localhost:3000`);
|
||||
```
|
||||
|
||||
```js [Express]
|
||||
import express from "express";
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const cap = new Cap({
|
||||
tokens_store_path: '.data/tokensList.json' // make sure this file has already been created and added to your gitignore
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
});
|
||||
|
||||
app.post('/api/challenge', (req, res) => {
|
||||
app.post("/api/challenge", (req, res) => {
|
||||
res.json(cap.createChallenge());
|
||||
});
|
||||
|
||||
app.post('/api/redeem', async (req, res) => {
|
||||
app.post("/api/redeem", async (req, res) => {
|
||||
const { token, solutions } = req.body;
|
||||
if (!token || !solutions) {
|
||||
return res.status(400).json({ success: false });
|
||||
@@ -81,18 +176,18 @@ app.post('/api/redeem', async (req, res) => {
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Listening on port 3000');
|
||||
})
|
||||
console.log("Listening on port 3000");
|
||||
});
|
||||
```
|
||||
|
||||
It should be pretty easy to replicate this code but with other frameworks such as Hono.
|
||||
:::
|
||||
|
||||
### Token Validation
|
||||
|
||||
Once the token is generated and captured, you can use it later to validate the user's identity. You can do this by calling `await cap.validateToken` in your server-side code:
|
||||
|
||||
```js
|
||||
await cap.validateToken("...") // returns { success: Boolean }
|
||||
await cap.validateToken("..."); // returns { success: Boolean }
|
||||
```
|
||||
|
||||
Note that the token will immediately be deleted after this. To prevent this, use `await cap.validateToken("...", { keepToken: true })`.
|
||||
+18
-5
@@ -2,16 +2,29 @@
|
||||
|
||||
You can use `new Cap({ ... })` in your client-side JavaScript to create a new Cap instance and use the `solve()` method to solve the challenge. This is helpful for situations where you don't want the Cap widget to be visible but still want security, e.g. on a social media app when posting something.
|
||||
|
||||
Behind the scenes, Cap creates a hidden `cap-widget` element and uses it to solve the challenge.
|
||||
|
||||
```js
|
||||
const cap = new Cap({
|
||||
apiEndpoint: "/api/"
|
||||
});
|
||||
const result = await cap.solve();
|
||||
console.log(result.token);
|
||||
const solution = await cap.solve();
|
||||
|
||||
console.log(solution.token);
|
||||
```
|
||||
|
||||
You can also set up [event listeners](widget.md#supported-events):
|
||||
|
||||
```js
|
||||
const cap = new Cap({
|
||||
apiEndpoint: "/api/"
|
||||
});
|
||||
|
||||
cap.addEventListener("progress", (event) => { // [!code focus]
|
||||
console.log(`Solving... ${event.detail.progress}% done`); // [!code focus]
|
||||
}); // [!code focus]
|
||||
```
|
||||
|
||||
Behind the scenes, Cap creates a hidden `cap-widget` element and uses it to solve the challenge.
|
||||
|
||||
## Supported methods and arguments
|
||||
|
||||
The following methods are supported:
|
||||
@@ -39,4 +52,4 @@ Returns the token from the latest solve
|
||||
Resets `cap.token`
|
||||
|
||||
#### `cap.addEventListener(..., function () { ... })`
|
||||
Listens for an event for the cap widget. See [Widget: Supported events](widget.md#supported-events)
|
||||
Listens for an event for the cap widget. See [supported events](widget.md#supported-events)
|
||||
+122
-7
@@ -1,26 +1,129 @@
|
||||
# @cap.js/server
|
||||
|
||||
`@cap.js/server` is Cap's server-side library. It helps you create and validate challenges for your users. Start by installing it using npm or bun:
|
||||
`@cap.js/server` is Cap's server-side library. It helps you create and validate challenges for your users. Start by installing it using bun (recommended), npm, or pnpm:
|
||||
|
||||
::: code-group
|
||||
|
||||
```bash [bun]
|
||||
bun add @cap.js/server
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm i @cap.js/server
|
||||
```
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm i @cap.js/server
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
> [!NOTE]
|
||||
> It is recommended to use at least Node.js 14 or Bun 1.0.0. You might experience multiple issues on older versions of these runtimes.
|
||||
> If you're using Glitch, make sure to set node 14 or higher in your `engines` field in `package.json`
|
||||
> It is recommended to use at least Node.js 14 or Bun v1.0.0. You might experience multiple issues on older versions of these runtimes.
|
||||
> If you're using Glitch, make sure to set your Node version to 14 in your `engines` field in `package.json`
|
||||
|
||||
## Example code
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const Cap = require('@cap.js/server');
|
||||
::: code-group
|
||||
```js [Elysia]
|
||||
import { Elysia } from 'elysia';
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const cap = new Cap({
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
});
|
||||
|
||||
new Elysia()
|
||||
.post("/api/challenge", () => {
|
||||
return cap.createChallenge();
|
||||
})
|
||||
|
||||
.post("/api/redeem", async ({ body, set }) => {
|
||||
const { token, solutions } = body;
|
||||
|
||||
if (!token || !solutions) {
|
||||
set.status = 400;
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
return await cap.redeemChallenge({ token, solutions });
|
||||
})
|
||||
.listen(3000);
|
||||
|
||||
console.log(`🦊 Elysia is running at http://localhost:3000`);
|
||||
```
|
||||
|
||||
```js [Fastify]
|
||||
import Fastify from "fastify";
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const fastify = Fastify();
|
||||
const cap = new Cap({
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
});
|
||||
|
||||
fastify.post("/api/challenge", (req, res) => {
|
||||
res.send(
|
||||
cap.createChallenge()
|
||||
);
|
||||
});
|
||||
|
||||
fastify.post("/api/redeem", async (req, res) => {
|
||||
const { token, solutions } = req.body;
|
||||
if (!token || !solutions) {
|
||||
return res.code(400).send({ success: false });
|
||||
}
|
||||
|
||||
res.send(await cap.redeemChallenge({ token, solutions }));
|
||||
});
|
||||
|
||||
fastify.listen({ port: 3000, host: "0.0.0.0" }).then(() => {
|
||||
console.log("Server is running on http://localhost:3000");
|
||||
});
|
||||
```
|
||||
|
||||
```js [Bun.serve]
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const cap = new Cap({
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
});
|
||||
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
routes: {
|
||||
"/api/challenge": {
|
||||
POST: () => {
|
||||
return Response.json(cap.createChallenge());
|
||||
},
|
||||
},
|
||||
"/api/redeem": {
|
||||
POST: async (req) => {
|
||||
const body = await req.json();
|
||||
const { token, solutions } = body;
|
||||
|
||||
if (!token || !solutions) {
|
||||
return Response.json({ success: false }, { status: 400 });
|
||||
}
|
||||
|
||||
return Response.json(await cap.redeemChallenge({ token, solutions }));
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Server running at http://localhost:3000`);
|
||||
```
|
||||
|
||||
```js [Express]
|
||||
import express from "express";
|
||||
import Cap from "@cap.js/server";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const cap = new Cap({
|
||||
tokens_store_path: '.data/tokensList.json' // make sure this file has already been created and added to your gitignore
|
||||
tokens_store_path: '.data/tokensList.json'
|
||||
});
|
||||
|
||||
app.post('/api/challenge', (req, res) => {
|
||||
@@ -39,6 +142,18 @@ app.listen(3000, () => {
|
||||
console.log('Listening on port 3000');
|
||||
})
|
||||
```
|
||||
:::
|
||||
|
||||
Then, you can verify the CAPTCHA tokens on your server by calling the `await cap.validateToken("<token>")` method. Example:
|
||||
```js
|
||||
const { success } = await cap.validateToken("9363220f..."); // [!code highlight]
|
||||
|
||||
if (success) {
|
||||
console.log("Valid token");
|
||||
} else {
|
||||
console.log("Invalid token");
|
||||
}
|
||||
```
|
||||
|
||||
## Supported methods and arguments
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# Cap Standalone client
|
||||
# Standalone server
|
||||
|
||||
## Installation
|
||||
|
||||
Cap Standalone is a self-hosted version of Cap's backend that allows you to spin up a server to validate and create challenges so you can use it with languages other than JS.
|
||||
|
||||
To install Cap Standalone, you need to have Docker installed on your server. You can find instructions on how to install Docker [here](https://docs.docker.com/get-docker/).
|
||||
|
||||
Once you have Docker installed, you can run the following command to pull the image:
|
||||
To install Cap Standalone, you need to have [Docker](https://docs.docker.com/get-docker/) installed on your server. Once you have it installed, you can run the following command to pull the image:
|
||||
|
||||
```bash
|
||||
docker pull tiago2/cap:latest
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Both `x86_64` (amd64) and `arm64` architectures are supported. Docker Engine 20.10 or higher is recommended
|
||||
|
||||
Then, to run the server, use the following command:
|
||||
|
||||
```bash
|
||||
@@ -71,18 +72,10 @@ Your request needs to include the following data:
|
||||
Example using `curl`:
|
||||
|
||||
```bash
|
||||
curl "https://<instance_url>/<key_id>/siteverify" \
|
||||
-X POST \
|
||||
-d "secret=<key_secret>&response=<captcha_token>"
|
||||
|
||||
# You can also send JSON:
|
||||
curl "https://<instance_url>/<key_id>/siteverify" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"secret": "<key_secret>",
|
||||
"response": "<captcha_token>"
|
||||
}'
|
||||
-d '{ "secret": "<key_secret>", "response": "<captcha_token>" }'
|
||||
```
|
||||
|
||||
Remember to replace:
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Vulnerabilities
|
||||
So far there have been no publicly disclosed vulnerabilities. If you find any, please let us know [here](https://github.com/tiagorangel1/cap/security/advisories/new)
|
||||
+21
-15
@@ -1,9 +1,8 @@
|
||||
# @cap.js/widget
|
||||
|
||||
> [!NOTE]
|
||||
> [!NOTE]
|
||||
> **Requirements:** All modern browsers should be supported, but the build script specifically targets the last 10 versions of Chrome, Firefox, Safari and Edge.
|
||||
|
||||
|
||||
`@cap.js/widget` is Cap's client-side library. It includes the `cap-widget` web component, the invisible mode and the Captcha solver. It is recommended to use unpkg to install it:
|
||||
|
||||
```html
|
||||
@@ -13,7 +12,10 @@
|
||||
You can now use the `<cap-widget>` component in your HTML.
|
||||
|
||||
```html
|
||||
<cap-widget id="cap" data-cap-api-endpoint="<your cap api endpoint>"></cap-widget>
|
||||
<cap-widget
|
||||
id="cap"
|
||||
data-cap-api-endpoint="<your cap api endpoint>"
|
||||
></cap-widget>
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -22,34 +24,38 @@ You can now use the `<cap-widget>` component in your HTML.
|
||||
|
||||
> [!TIP]
|
||||
> The following attributes are supported:
|
||||
>
|
||||
> * `data-cap-api-endpoint`: API endpoint (required)
|
||||
> * `data-cap-worker-count`: Number of workers to use (defaults to `navigator.hardwareConcurrency || 8`)
|
||||
>
|
||||
> - `data-cap-api-endpoint`: API endpoint (required)
|
||||
> - `data-cap-worker-count`: Number of workers to use (defaults to `navigator.hardwareConcurrency || 8`)
|
||||
|
||||
Then, in your JavaScript, listen for the `solve` event to capture the token when generated:
|
||||
|
||||
```js{3}
|
||||
const widget = document.querySelector("#cap");
|
||||
|
||||
widget.addEventListener("solve", function (e) {
|
||||
widget.addEventListener("solve", function (e) {
|
||||
const token = e.detail.token;
|
||||
|
||||
|
||||
// Handle the token as needed
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, you can use `onsolve=""` directly within the widget or wrap the widget in a `<form></form>` (where Cap will automatically submit the token alongside other form data).
|
||||
|
||||
## Compatibility version
|
||||
|
||||
Use this script instead if you want compatibility with more browsers but a slightly bigger code size:
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=default,fetch,CustomEvent,TextEncoder,Element,CustomElements,ShadowDOM&flags=gated"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget/cap.compat.min.js"></script>
|
||||
```
|
||||
|
||||
## Supported events
|
||||
The following custom events are supported:
|
||||
|
||||
The following custom events are supported:
|
||||
|
||||
- `solve`: Triggered when the token is generated.
|
||||
- `error`: Triggered when an error occurs.
|
||||
- `reset`: Triggered when the widget is reset.
|
||||
- `progress`: Triggered when the there's a progress update while in verification.
|
||||
|
||||
## Invisible mode
|
||||
For docs on how to use the invisible mode, see ["Invisible mode"](invisible.md).
|
||||
|
||||
## Floating mode
|
||||
For docs on how to use the floating mode, see ["Floating mode"](floating.md).
|
||||
@@ -6,7 +6,6 @@
|
||||
"preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress-plugin-nprogress": "^0.0.4",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
Reference in New Issue
Block a user