wiki-viewer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.github/workflows/publish.yml +37 -0
- package/.next/standalone/DESIGN.md +521 -0
- package/.next/standalone/README.md +283 -0
- package/.next/standalone/bin/wiki-viewer.js +158 -0
- package/.next/standalone/next.config.ts +11 -0
- package/.next/standalone/package.json +120 -0
- package/.next/standalone/pnpm-lock.yaml +5654 -0
- package/.next/standalone/pnpm-workspace.yaml +2 -0
- package/.next/standalone/postcss.config.mjs +2 -0
- package/.next/standalone/public/logo.svg +11 -0
- package/.next/standalone/public/public/logo.svg +11 -0
- package/.next/standalone/server.js +44 -0
- package/.next/standalone/src/app/api/app-proxy/[...path]/route.ts +247 -0
- package/.next/standalone/src/app/api/assets/[...path]/route.ts +70 -0
- package/.next/standalone/src/app/api/system/browse/route.ts +78 -0
- package/.next/standalone/src/app/api/system/clear-root/route.ts +9 -0
- package/.next/standalone/src/app/api/system/config/route.ts +10 -0
- package/.next/standalone/src/app/api/system/pins/route.ts +31 -0
- package/.next/standalone/src/app/api/system/reveal/route.ts +28 -0
- package/.next/standalone/src/app/api/system/root-status/route.ts +9 -0
- package/.next/standalone/src/app/api/system/set-root/route.ts +23 -0
- package/.next/standalone/src/app/api/upload/[...path]/route.ts +80 -0
- package/.next/standalone/src/app/api/wiki/app/route.ts +41 -0
- package/.next/standalone/src/app/api/wiki/content/route.ts +60 -0
- package/.next/standalone/src/app/api/wiki/file/route.ts +52 -0
- package/.next/standalone/src/app/api/wiki/folder/route.ts +26 -0
- package/.next/standalone/src/app/api/wiki/move/route.ts +45 -0
- package/.next/standalone/src/app/api/wiki/page/route.ts +84 -0
- package/.next/standalone/src/app/api/wiki/route.ts +98 -0
- package/.next/standalone/src/app/api/wiki/slugs/route.ts +50 -0
- package/.next/standalone/src/app/api/wiki/upload/route.ts +87 -0
- package/.next/standalone/src/app/api/wiki/watch/route.ts +86 -0
- package/.next/standalone/src/app/globals.css +716 -0
- package/.next/standalone/src/app/icon.svg +11 -0
- package/.next/standalone/src/app/layout.tsx +35 -0
- package/.next/standalone/src/app/page.tsx +1509 -0
- package/.next/standalone/src/components/confirm-dialog.tsx +56 -0
- package/.next/standalone/src/components/dir-picker.tsx +393 -0
- package/.next/standalone/src/components/editor/bubble-menu.tsx +286 -0
- package/.next/standalone/src/components/editor/callout-extension.ts +36 -0
- package/.next/standalone/src/components/editor/csv-viewer.tsx +335 -0
- package/.next/standalone/src/components/editor/editor-toolbar.tsx +564 -0
- package/.next/standalone/src/components/editor/editor.tsx +633 -0
- package/.next/standalone/src/components/editor/extensions/drag-handle.ts +325 -0
- package/.next/standalone/src/components/editor/extensions/heading-anchors.ts +55 -0
- package/.next/standalone/src/components/editor/extensions/resizable-image.tsx +159 -0
- package/.next/standalone/src/components/editor/extensions.ts +148 -0
- package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +74 -0
- package/.next/standalone/src/components/editor/folder-index.tsx +235 -0
- package/.next/standalone/src/components/editor/google-doc-viewer.tsx +73 -0
- package/.next/standalone/src/components/editor/image-viewer.tsx +56 -0
- package/.next/standalone/src/components/editor/link-popover.tsx +111 -0
- package/.next/standalone/src/components/editor/media-popover.tsx +211 -0
- package/.next/standalone/src/components/editor/media-viewer.tsx +69 -0
- package/.next/standalone/src/components/editor/mermaid-viewer.tsx +285 -0
- package/.next/standalone/src/components/editor/node-app-viewer.tsx +269 -0
- package/.next/standalone/src/components/editor/notebook-viewer.tsx +370 -0
- package/.next/standalone/src/components/editor/office/docx-viewer.tsx +92 -0
- package/.next/standalone/src/components/editor/office/office-chrome.tsx +82 -0
- package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +103 -0
- package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +116 -0
- package/.next/standalone/src/components/editor/pdf-viewer.tsx +31 -0
- package/.next/standalone/src/components/editor/slash-commands.tsx +454 -0
- package/.next/standalone/src/components/editor/source-viewer.tsx +212 -0
- package/.next/standalone/src/components/editor/table-menu.tsx +232 -0
- package/.next/standalone/src/components/editor/website-viewer.tsx +74 -0
- package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +188 -0
- package/.next/standalone/src/components/editor/wiki-link-decorator.ts +79 -0
- package/.next/standalone/src/components/editor/wiki-link-extension.ts +120 -0
- package/.next/standalone/src/components/editor/wiki-link-picker.tsx +250 -0
- package/.next/standalone/src/components/layout/viewer-toolbar.tsx +54 -0
- package/.next/standalone/src/components/theme-provider.tsx +16 -0
- package/.next/standalone/src/components/theme-toggle.tsx +24 -0
- package/.next/standalone/src/components/ui/badge.tsx +35 -0
- package/.next/standalone/src/components/ui/button.tsx +56 -0
- package/.next/standalone/src/components/ui/card.tsx +63 -0
- package/.next/standalone/src/components/ui/collapsible.tsx +9 -0
- package/.next/standalone/src/components/ui/command.tsx +150 -0
- package/.next/standalone/src/components/ui/context-menu.tsx +191 -0
- package/.next/standalone/src/components/ui/dialog.tsx +119 -0
- package/.next/standalone/src/components/ui/dropdown-menu.tsx +194 -0
- package/.next/standalone/src/components/ui/input.tsx +22 -0
- package/.next/standalone/src/components/ui/label.tsx +25 -0
- package/.next/standalone/src/components/ui/popover.tsx +33 -0
- package/.next/standalone/src/components/ui/scroll-area.tsx +47 -0
- package/.next/standalone/src/components/ui/select.tsx +156 -0
- package/.next/standalone/src/components/ui/separator.tsx +30 -0
- package/.next/standalone/src/components/ui/sheet.tsx +140 -0
- package/.next/standalone/src/components/ui/skeleton.tsx +15 -0
- package/.next/standalone/src/components/ui/switch.tsx +28 -0
- package/.next/standalone/src/components/ui/tabs.tsx +54 -0
- package/.next/standalone/src/components/ui/textarea.tsx +21 -0
- package/.next/standalone/src/components/ui/tip.tsx +23 -0
- package/.next/standalone/src/components/ui/toggle-group.tsx +41 -0
- package/.next/standalone/src/components/ui/tooltip.tsx +27 -0
- package/.next/standalone/src/components/wiki/frontmatter-header.tsx +164 -0
- package/.next/standalone/src/lib/app-runner.ts +231 -0
- package/.next/standalone/src/lib/cabinets/tree.ts +75 -0
- package/.next/standalone/src/lib/config.ts +32 -0
- package/.next/standalone/src/lib/embeds/detect.ts +142 -0
- package/.next/standalone/src/lib/google/detect.ts +107 -0
- package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +84 -0
- package/.next/standalone/src/lib/markdown/remark-wikilinks.ts +100 -0
- package/.next/standalone/src/lib/markdown/to-html.ts +168 -0
- package/.next/standalone/src/lib/markdown/to-markdown.ts +238 -0
- package/.next/standalone/src/lib/root-dir.ts +35 -0
- package/.next/standalone/src/lib/toast.ts +9 -0
- package/.next/standalone/src/lib/utils.ts +6 -0
- package/.next/standalone/src/lib/wiki-helpers.ts +36 -0
- package/.next/standalone/src/stores/ai-panel-store.ts +23 -0
- package/.next/standalone/src/stores/editor-store.ts +244 -0
- package/.next/standalone/src/stores/tree-store.ts +52 -0
- package/.next/standalone/src/stores/wiki-slugs-store.ts +74 -0
- package/.next/standalone/src/types/index.ts +47 -0
- package/.next/standalone/tailwind.config.ts +201 -0
- package/.next/standalone/tsconfig.json +41 -0
- package/README.md +283 -0
- package/bin/wiki-viewer.js +158 -0
- package/package.json +120 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="public/logo.svg" width="80" height="80" alt="wiki-viewer logo" />
|
|
3
|
+
<h1>wiki-viewer</h1>
|
|
4
|
+
<p><strong>Browse, read, and edit your local files from a clean web UI.</strong></p>
|
|
5
|
+
<p>
|
|
6
|
+
Markdown · PDF · Office docs · Notebooks · Images · Code · and more
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p>
|
|
10
|
+
<!-- npm badge placeholder — will activate once package is published -->
|
|
11
|
+
<!-- <a href="https://www.npmjs.com/package/wiki-viewer"><img src="https://img.shields.io/npm/v/wiki-viewer" alt="npm version" /></a> -->
|
|
12
|
+
<img src="https://img.shields.io/badge/npm-not%20yet%20published-lightgrey" alt="npm not yet published" />
|
|
13
|
+
<img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node.js ≥18" />
|
|
14
|
+
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license" />
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## What is it?
|
|
21
|
+
|
|
22
|
+
**wiki-viewer** is a zero-config local file browser you run from your terminal. It starts a small web server and lets you navigate, read, and edit any directory on your machine — right from the browser.
|
|
23
|
+
|
|
24
|
+
No cloud. No accounts. No syncing. Your files stay on your machine.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
| Category | What's included |
|
|
31
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| **File viewers** | Markdown (with frontmatter), PDF, images (PNG / JPG / SVG / WebP), video & audio, CSV (table view), source code (syntax highlighting), DOCX, XLSX, PPTX, Jupyter notebooks, Mermaid diagrams, HTML |
|
|
33
|
+
| **Editor** | Rich TipTap editor for Markdown files |
|
|
34
|
+
| **File ops** | Upload files, create folders, delete, drag-to-move |
|
|
35
|
+
| **Wiki links** | `[[page-name]]` links between Markdown files |
|
|
36
|
+
| **Dark mode** | System-aware, with manual toggle |
|
|
37
|
+
| **HTTPS** | Required for remote access — self-signed cert (OpenSSL) or trusted cert (mkcert); runs as HTTPS proxy in front of the internal HTTP server |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
> **Note:** The npm package is not yet published. Use the [dev setup](#-dev-setup) below to run locally from source. The `npx` commands below will work once the package is on npm.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Point it at a directory
|
|
47
|
+
npx wiki-viewer ~/notes
|
|
48
|
+
|
|
49
|
+
# No directory? Pick one in the browser
|
|
50
|
+
npx wiki-viewer
|
|
51
|
+
|
|
52
|
+
# Running on a remote machine? HTTPS is required (see note below)
|
|
53
|
+
npx wiki-viewer ~/notes --https
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then open **http://localhost:3000** (or **https://localhost:3000** with `--https`).
|
|
57
|
+
|
|
58
|
+
> ⚠️ **Running on a remote host?** The app must be accessed over **HTTPS** — not plain HTTP — or several features will silently break (PDF viewer, service workers, and anything that requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)). Use `--https` or put it behind a reverse proxy that handles TLS. Plain HTTP only works correctly on `localhost`.
|
|
59
|
+
|
|
60
|
+
### Options
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
wiki-viewer [directory] [options]
|
|
64
|
+
|
|
65
|
+
directory Directory to serve (optional — pick in the browser if omitted)
|
|
66
|
+
|
|
67
|
+
Options:
|
|
68
|
+
-p, --port <port> Port to listen on (default: 3000)
|
|
69
|
+
-H, --host <host> Host to bind to (default: localhost)
|
|
70
|
+
--https Enable HTTPS (self-signed cert, required for service workers on remote)
|
|
71
|
+
-h, --help Show this help message
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Examples:**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Custom port
|
|
78
|
+
npx wiki-viewer ~/notes -p 8080
|
|
79
|
+
|
|
80
|
+
# Bind to all interfaces (accessible on your local network)
|
|
81
|
+
npx wiki-viewer ~/notes -H 0.0.0.0
|
|
82
|
+
|
|
83
|
+
# HTTPS on a custom port
|
|
84
|
+
npx wiki-viewer ~/notes --https -p 8443
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🔧 Dev setup
|
|
90
|
+
|
|
91
|
+
Follow these steps to run wiki-viewer from source for local development or contribution.
|
|
92
|
+
|
|
93
|
+
### Prerequisites
|
|
94
|
+
|
|
95
|
+
- **Node.js** ≥ 18 — [nodejs.org](https://nodejs.org)
|
|
96
|
+
- **pnpm** — [pnpm.io/installation](https://pnpm.io/installation)
|
|
97
|
+
```bash
|
|
98
|
+
npm install -g pnpm
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 1 · Clone the repo
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/anh-chu/wiki-viewer.git
|
|
105
|
+
cd wiki-viewer
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 2 · Install dependencies
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pnpm install
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3 · Start the dev server
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Serve a directory
|
|
118
|
+
ROOT_DIR=~/notes pnpm dev
|
|
119
|
+
|
|
120
|
+
# No directory — pick one in the browser
|
|
121
|
+
pnpm dev
|
|
122
|
+
|
|
123
|
+
# HTTPS dev mode (uses Next.js experimental HTTPS)
|
|
124
|
+
ROOT_DIR=~/notes pnpm dev:https
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Open **http://localhost:3000**.
|
|
128
|
+
|
|
129
|
+
The dev server supports **hot reload** — changes to source files are reflected instantly.
|
|
130
|
+
|
|
131
|
+
### Available scripts
|
|
132
|
+
|
|
133
|
+
| Command | Description |
|
|
134
|
+
| ---------------- | ---------------------------------------- |
|
|
135
|
+
| `pnpm dev` | Start Next.js development server |
|
|
136
|
+
| `pnpm dev:https` | Start dev server with experimental HTTPS |
|
|
137
|
+
| `pnpm build` | Build production bundle |
|
|
138
|
+
| `pnpm start` | Start production server (after build) |
|
|
139
|
+
| `pnpm wiki` | Run the CLI entry point (after build) |
|
|
140
|
+
|
|
141
|
+
### Environment variables
|
|
142
|
+
|
|
143
|
+
| Variable | Description | Default |
|
|
144
|
+
| ---------- | ------------------------ | --------------------- |
|
|
145
|
+
| `ROOT_DIR` | Directory to serve | `~/wiki-viewer-files` |
|
|
146
|
+
| `PORT` | Port to listen on | `3000` |
|
|
147
|
+
| `HOSTNAME` | Host / interface to bind | `localhost` |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 🚀 Local deployment (self-hosted)
|
|
152
|
+
|
|
153
|
+
Want to run wiki-viewer as a persistent server on your machine or a home server? Here's how.
|
|
154
|
+
|
|
155
|
+
### Option A — Build and run directly
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 1. Clone & install
|
|
159
|
+
git clone https://github.com/anh-chu/wiki-viewer.git
|
|
160
|
+
cd wiki-viewer
|
|
161
|
+
pnpm install
|
|
162
|
+
|
|
163
|
+
# 2. Build the production bundle
|
|
164
|
+
pnpm build
|
|
165
|
+
|
|
166
|
+
# 3. Copy static assets into the standalone output (required step)
|
|
167
|
+
cp -r .next/static .next/standalone/.next/static
|
|
168
|
+
cp -r public .next/standalone/public
|
|
169
|
+
|
|
170
|
+
# 4. Start the server
|
|
171
|
+
ROOT_DIR=/path/to/your/files node .next/standalone/server.js
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Or use the CLI wrapper (which handles the above automatically):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
node bin/wiki-viewer.js /path/to/your/files
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Option B — Run with PM2 (auto-restart on crash / reboot)
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npm install -g pm2
|
|
184
|
+
|
|
185
|
+
# Start and name the process
|
|
186
|
+
pm2 start bin/wiki-viewer.js \
|
|
187
|
+
--name wiki-viewer \
|
|
188
|
+
--node-args="" \
|
|
189
|
+
-- /path/to/your/files --port 3000
|
|
190
|
+
|
|
191
|
+
# Save so it auto-starts on reboot
|
|
192
|
+
pm2 save
|
|
193
|
+
pm2 startup # follow the printed instructions
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Option C — systemd service (Linux)
|
|
197
|
+
|
|
198
|
+
Create `/etc/systemd/system/wiki-viewer.service`:
|
|
199
|
+
|
|
200
|
+
```ini
|
|
201
|
+
[Unit]
|
|
202
|
+
Description=wiki-viewer
|
|
203
|
+
After=network.target
|
|
204
|
+
|
|
205
|
+
[Service]
|
|
206
|
+
Type=simple
|
|
207
|
+
User=YOUR_USER
|
|
208
|
+
WorkingDirectory=/home/YOUR_USER/wiki-viewer
|
|
209
|
+
ExecStart=/usr/bin/node bin/wiki-viewer.js /path/to/your/files --port 3000 --host 0.0.0.0
|
|
210
|
+
Restart=on-failure
|
|
211
|
+
Environment=NODE_ENV=production
|
|
212
|
+
|
|
213
|
+
[Install]
|
|
214
|
+
WantedBy=multi-user.target
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Then enable and start it:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
sudo systemctl daemon-reload
|
|
221
|
+
sudo systemctl enable wiki-viewer
|
|
222
|
+
sudo systemctl start wiki-viewer
|
|
223
|
+
sudo systemctl status wiki-viewer
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### HTTPS on a remote server
|
|
227
|
+
|
|
228
|
+
> ⚠️ **HTTPS is required when wiki-viewer is not on `localhost`.** Browsers restrict several APIs (service workers, PDF.js, and other [secure-context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) features) to HTTPS-only origins. Accessing wiki-viewer over plain HTTP on a remote host will silently break parts of the UI.
|
|
229
|
+
|
|
230
|
+
**How it works under the hood:** `--https` does not make the Next.js server itself speak TLS. Instead it:
|
|
231
|
+
|
|
232
|
+
1. Starts the Next.js standalone server on a random internal HTTP port (`127.0.0.1:XXXXX`)
|
|
233
|
+
2. Starts an HTTPS reverse proxy on the user-facing port that forwards all traffic to the internal server
|
|
234
|
+
|
|
235
|
+
So the chain is always: **browser → HTTPS proxy → internal HTTP server**. This is why `--https` is the correct flag and not a plain HTTP URL even in "HTTPS mode".
|
|
236
|
+
|
|
237
|
+
**Certificate generation** (automatic, stored at `~/.wiki-viewer/certs/`):
|
|
238
|
+
|
|
239
|
+
- If **mkcert** is installed → locally-trusted cert (no browser warning)
|
|
240
|
+
- Otherwise → OpenSSL self-signed cert (browser warns once — click through)
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
node bin/wiki-viewer.js /path/to/your/files --https --port 443 --host 0.0.0.0
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
> **Tip for production:** Use a real reverse proxy (nginx / Caddy) with a proper TLS certificate instead of `--https`. Configure it to proxy to wiki-viewer's plain HTTP port, and wiki-viewer will work correctly behind it — no `--https` flag needed.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Project structure
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
wiki-viewer/
|
|
254
|
+
├── bin/
|
|
255
|
+
│ └── wiki-viewer.js CLI entry point
|
|
256
|
+
├── src/
|
|
257
|
+
│ ├── app/ Next.js app router pages & API routes
|
|
258
|
+
│ ├── components/ React components (viewer, editor, sidebar…)
|
|
259
|
+
│ ├── lib/ File system helpers, parsers, utilities
|
|
260
|
+
│ ├── stores/ Zustand state stores
|
|
261
|
+
│ └── types/ Shared TypeScript types
|
|
262
|
+
├── public/
|
|
263
|
+
│ └── logo.svg
|
|
264
|
+
├── next.config.ts
|
|
265
|
+
└── package.json
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Contributing
|
|
271
|
+
|
|
272
|
+
1. Fork the repo and create a branch: `git checkout -b my-feature`
|
|
273
|
+
2. Make your changes
|
|
274
|
+
3. Test with `ROOT_DIR=~/notes pnpm dev`
|
|
275
|
+
4. Open a pull request
|
|
276
|
+
|
|
277
|
+
Bug reports and feature requests welcome via [GitHub Issues](https://github.com/anh-chu/wiki-viewer/issues).
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT © [anh-chu](https://github.com/anh-chu)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn, execSync } from "node:child_process";
|
|
3
|
+
import { createServer as createHttpsServer } from "node:https";
|
|
4
|
+
import { createServer as createHttpServer, request as httpRequest } from "node:http";
|
|
5
|
+
import { createServer as createNetServer } from "node:net";
|
|
6
|
+
import { readFileSync, mkdirSync, existsSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const appRoot = path.resolve(__dirname, "..");
|
|
13
|
+
const serverJs = path.join(appRoot, ".next", "standalone", "server.js");
|
|
14
|
+
|
|
15
|
+
function printUsage() {
|
|
16
|
+
console.error("Usage: wiki-viewer [directory] [options]");
|
|
17
|
+
console.error("");
|
|
18
|
+
console.error(" directory Directory to serve (optional — pick in browser if omitted)");
|
|
19
|
+
console.error("");
|
|
20
|
+
console.error("Options:");
|
|
21
|
+
console.error(" -p, --port <port> Port to listen on (default: 3000)");
|
|
22
|
+
console.error(" -H, --host <host> Host to bind to (default: localhost)");
|
|
23
|
+
console.error(" --https Enable HTTPS (self-signed cert, enables service workers)");
|
|
24
|
+
console.error("");
|
|
25
|
+
console.error("Examples:");
|
|
26
|
+
console.error(" wiki-viewer ~/notes");
|
|
27
|
+
console.error(" wiki-viewer ~/notes --https");
|
|
28
|
+
console.error(" wiki-viewer ~/notes -p 8080 -H 0.0.0.0");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!existsSync(serverJs)) {
|
|
32
|
+
console.error("Error: pre-built server not found at", serverJs);
|
|
33
|
+
console.error("This is a bug – please report it at https://github.com/anh-chu/wiki-viewer/issues");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const args = process.argv.slice(2);
|
|
38
|
+
|
|
39
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
40
|
+
printUsage();
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rootDir = args.find((a) => !a.startsWith("-"));
|
|
45
|
+
let port = process.env.PORT ?? "3000";
|
|
46
|
+
let host = process.env.HOSTNAME ?? "localhost";
|
|
47
|
+
let useHttps = false;
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < args.length; i++) {
|
|
50
|
+
const a = args[i];
|
|
51
|
+
if (a === "-p" || a === "--port") port = args[++i] ?? port;
|
|
52
|
+
else if (a === "-H" || a === "--host") host = args[++i] ?? host;
|
|
53
|
+
else if (a === "--https") useHttps = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const resolvedRoot = rootDir ? path.resolve(rootDir) : null;
|
|
57
|
+
|
|
58
|
+
// ── HTTPS cert generation ──────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
function ensureCerts() {
|
|
61
|
+
const dir = path.join(os.homedir(), ".wiki-viewer", "certs");
|
|
62
|
+
mkdirSync(dir, { recursive: true });
|
|
63
|
+
const keyPath = path.join(dir, "key.pem");
|
|
64
|
+
const certPath = path.join(dir, "cert.pem");
|
|
65
|
+
if (existsSync(keyPath) && existsSync(certPath)) {
|
|
66
|
+
return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
|
|
67
|
+
}
|
|
68
|
+
// Try mkcert (trusted), fall back to openssl (self-signed)
|
|
69
|
+
try {
|
|
70
|
+
execSync("mkcert -version", { stdio: "ignore" });
|
|
71
|
+
execSync(`mkcert -install 2>/dev/null; mkcert -key-file "${keyPath}" -cert-file "${certPath}" localhost 127.0.0.1 "${host}"`, { stdio: "pipe" });
|
|
72
|
+
console.log("🔒 Trusted cert via mkcert");
|
|
73
|
+
} catch {
|
|
74
|
+
try {
|
|
75
|
+
execSync(
|
|
76
|
+
`openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 825 -nodes -subj "/CN=localhost"`,
|
|
77
|
+
{ stdio: "ignore" },
|
|
78
|
+
);
|
|
79
|
+
console.log("🔒 Self-signed cert (browser will warn once — click through)");
|
|
80
|
+
} catch {
|
|
81
|
+
console.error("Error: --https requires mkcert or openssl");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── free port helper ───────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
function freePort() {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
const s = createNetServer();
|
|
93
|
+
s.listen(0, "127.0.0.1", () => {
|
|
94
|
+
const { port: p } = s.address();
|
|
95
|
+
s.close(() => resolve(p));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── start ──────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
async function start() {
|
|
103
|
+
if (resolvedRoot) {
|
|
104
|
+
console.log(`📂 ${resolvedRoot}`);
|
|
105
|
+
} else {
|
|
106
|
+
console.log("📂 No directory specified — open the browser to choose one");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// When HTTPS is requested, run the standalone server on a random internal
|
|
110
|
+
// HTTP port and stand up an HTTPS reverse-proxy on the user-facing port.
|
|
111
|
+
const internalPort = useHttps ? String(await freePort()) : port;
|
|
112
|
+
const internalHost = "127.0.0.1";
|
|
113
|
+
|
|
114
|
+
const child = spawn(process.execPath, [serverJs], {
|
|
115
|
+
cwd: path.join(appRoot, ".next", "standalone"),
|
|
116
|
+
stdio: "inherit",
|
|
117
|
+
env: {
|
|
118
|
+
...process.env,
|
|
119
|
+
...(resolvedRoot ? { ROOT_DIR: resolvedRoot } : {}),
|
|
120
|
+
PORT: internalPort,
|
|
121
|
+
HOSTNAME: internalHost,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
126
|
+
|
|
127
|
+
if (useHttps) {
|
|
128
|
+
const { key, cert } = ensureCerts();
|
|
129
|
+
|
|
130
|
+
// Simple HTTPS → HTTP proxy
|
|
131
|
+
const proxy = createHttpsServer({ key, cert }, (req, res) => {
|
|
132
|
+
const options = {
|
|
133
|
+
hostname: internalHost,
|
|
134
|
+
port: Number(internalPort),
|
|
135
|
+
path: req.url,
|
|
136
|
+
method: req.method,
|
|
137
|
+
headers: req.headers,
|
|
138
|
+
};
|
|
139
|
+
const upstream = httpRequest(options, (upRes) => {
|
|
140
|
+
res.writeHead(upRes.statusCode, upRes.headers);
|
|
141
|
+
upRes.pipe(res);
|
|
142
|
+
});
|
|
143
|
+
upstream.on("error", () => res.destroy());
|
|
144
|
+
req.pipe(upstream);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Wait a moment for the standalone server to bind before starting proxy
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
proxy.listen(Number(port), host, () => {
|
|
150
|
+
console.log(`🌐 https://${host}:${port}`);
|
|
151
|
+
});
|
|
152
|
+
}, 1_000);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(`🌐 http://${host}:${port}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
start();
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wiki-viewer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Local file viewer and wiki — view markdown, HTML, PDFs, notebooks, office docs, and more from any directory",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"wiki-viewer": "bin/wiki-viewer.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
".next/standalone"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "next dev",
|
|
16
|
+
"dev:https": "next dev --experimental-https",
|
|
17
|
+
"build": "next build",
|
|
18
|
+
"start": "next start",
|
|
19
|
+
"wiki": "node bin/wiki-viewer.js",
|
|
20
|
+
"prepublishOnly": "npm run build && cp -r .next/static .next/standalone/.next/static && cp -r public .next/standalone/public"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"wiki",
|
|
27
|
+
"markdown",
|
|
28
|
+
"viewer",
|
|
29
|
+
"notes",
|
|
30
|
+
"local",
|
|
31
|
+
"cli"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/anh-chu/wiki-viewer.git"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"chokidar": "^5.0.0",
|
|
39
|
+
"undici": "^8.3.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
43
|
+
"@radix-ui/react-context-menu": "^2.2.16",
|
|
44
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
45
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
46
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
47
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
48
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
49
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
50
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
51
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
52
|
+
"@radix-ui/react-switch": "^1.2.6",
|
|
53
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
54
|
+
"@radix-ui/react-toggle-group": "^1.1.11",
|
|
55
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
56
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
57
|
+
"@tiptap/core": "^3.22.5",
|
|
58
|
+
"@tiptap/extension-bubble-menu": "^3.22.5",
|
|
59
|
+
"@tiptap/extension-code-block-lowlight": "^3.22.5",
|
|
60
|
+
"@tiptap/extension-color": "^3.22.5",
|
|
61
|
+
"@tiptap/extension-image": "^3.22.5",
|
|
62
|
+
"@tiptap/extension-link": "^3.22.5",
|
|
63
|
+
"@tiptap/extension-placeholder": "^3.22.5",
|
|
64
|
+
"@tiptap/extension-subscript": "^3.22.5",
|
|
65
|
+
"@tiptap/extension-superscript": "^3.22.5",
|
|
66
|
+
"@tiptap/extension-table": "^3.22.5",
|
|
67
|
+
"@tiptap/extension-table-cell": "^3.22.5",
|
|
68
|
+
"@tiptap/extension-table-header": "^3.22.5",
|
|
69
|
+
"@tiptap/extension-table-row": "^3.22.5",
|
|
70
|
+
"@tiptap/extension-task-item": "^3.22.5",
|
|
71
|
+
"@tiptap/extension-task-list": "^3.22.5",
|
|
72
|
+
"@tiptap/extension-text-align": "^3.22.5",
|
|
73
|
+
"@tiptap/extension-underline": "^3.22.5",
|
|
74
|
+
"@tiptap/pm": "^3.22.5",
|
|
75
|
+
"@tiptap/react": "^3.22.5",
|
|
76
|
+
"@tiptap/starter-kit": "^3.22.5",
|
|
77
|
+
"@tiptap/suggestion": "^3.22.5",
|
|
78
|
+
"@types/mdast": "^4.0.4",
|
|
79
|
+
"@types/node": "^22.15.0",
|
|
80
|
+
"@types/papaparse": "^5.5.2",
|
|
81
|
+
"@types/react": "19.2.14",
|
|
82
|
+
"@types/react-dom": "19.2.3",
|
|
83
|
+
"@types/turndown": "^5.0.6",
|
|
84
|
+
"autoprefixer": "^10.4.20",
|
|
85
|
+
"class-variance-authority": "^0.7.1",
|
|
86
|
+
"clsx": "^2.1.1",
|
|
87
|
+
"cmdk": "^1.1.1",
|
|
88
|
+
"docx-preview": "^0.3.7",
|
|
89
|
+
"gray-matter": "^4.0.3",
|
|
90
|
+
"hast-util-to-html": "^9.0.5",
|
|
91
|
+
"highlight.js": "^11.11.1",
|
|
92
|
+
"jszip": "^3.10.1",
|
|
93
|
+
"lowlight": "^3.3.0",
|
|
94
|
+
"lucide-react": "^0.525.0",
|
|
95
|
+
"mammoth": "^1.12.0",
|
|
96
|
+
"mermaid": "^11.14.0",
|
|
97
|
+
"next": "16.2.4",
|
|
98
|
+
"next-themes": "^0.4.6",
|
|
99
|
+
"papaparse": "^5.5.3",
|
|
100
|
+
"postcss": "^8.5.6",
|
|
101
|
+
"pptx-preview": "^1.0.7",
|
|
102
|
+
"react": "19.2.5",
|
|
103
|
+
"react-dom": "19.2.5",
|
|
104
|
+
"react-markdown": "^10.1.0",
|
|
105
|
+
"react-pdf": "^10.4.1",
|
|
106
|
+
"rehype-stringify": "^10.0.1",
|
|
107
|
+
"remark-gfm": "^4.0.1",
|
|
108
|
+
"remark-parse": "^11.0.0",
|
|
109
|
+
"remark-rehype": "^11.1.2",
|
|
110
|
+
"sonner": "^2.0.7",
|
|
111
|
+
"tailwind-merge": "^3.3.0",
|
|
112
|
+
"tailwindcss": "^3.4.17",
|
|
113
|
+
"turndown": "^7.2.4",
|
|
114
|
+
"turndown-plugin-gfm": "^1.0.2",
|
|
115
|
+
"typescript": "^5.8.0",
|
|
116
|
+
"unified": "^11.0.5",
|
|
117
|
+
"xlsx": "^0.18.5",
|
|
118
|
+
"zustand": "^5.0.12"
|
|
119
|
+
}
|
|
120
|
+
}
|