md-review 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ *{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.file-tree{height:100%;display:flex;flex-direction:column;background-color:#f6f8fa}.file-tree-header{padding:16px;border-bottom:1px solid #d0d7de;background-color:#fff;display:flex;align-items:center;justify-content:space-between}.file-tree-header-content{flex:1}.file-tree-header h3{margin:0 0 4px;font-size:14px;font-weight:600;color:#24292f}.file-count{font-size:12px;color:#57606a}.sidebar-collapse-button{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:6px;cursor:pointer;color:#57606a;transition:background-color .2s,color .2s;padding:0;flex-shrink:0;margin-left:8px}.sidebar-collapse-button:hover{background-color:#f6f8fa;color:#24292f}.sidebar-collapse-button:active{background-color:#eaeef2}.search-container{padding:12px 16px;border-bottom:1px solid #d0d7de;background-color:#fff}.search-input-wrapper{position:relative;display:flex;align-items:center;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;padding:6px 8px;transition:border-color .2s,background-color .2s}.search-input-wrapper:focus-within{background-color:#fff;border-color:#0969da;box-shadow:0 0 0 3px #0969da1a}.search-icon{font-size:14px;margin-right:6px;color:#57606a;flex-shrink:0}.search-input{flex:1;border:none;background:transparent;outline:none;font-size:14px;color:#24292f;padding:0;min-width:0}.search-input::placeholder{color:#57606a}.search-clear{background:none;border:none;cursor:pointer;padding:0 4px;color:#57606a;font-size:16px;line-height:1;transition:color .2s;flex-shrink:0}.search-clear:hover{color:#24292f}.search-shortcut{margin-left:8px;padding:2px 6px;background-color:#fff;border:1px solid #d0d7de;border-radius:4px;font-size:11px;color:#57606a;font-family:system-ui,-apple-system,sans-serif;flex-shrink:0}.search-input-wrapper:focus-within .search-shortcut{display:none}.file-tree-content{flex:1;overflow-y:auto;padding:8px 0}.tree-item{display:flex;align-items:center;padding:4px 8px;cursor:pointer;font-size:14px;-webkit-user-select:none;user-select:none;transition:background-color .1s}.tree-item:hover{background-color:#eaeef2}.tree-item.file{color:#24292f}.tree-item.file.selected{background-color:#ddf4ff;border-left:2px solid #0969da}.tree-item.directory{color:#24292f;font-weight:500}.chevron-icon{margin-right:4px;color:#57606a;flex-shrink:0;transition:transform .2s ease-in-out}.file-icon,.folder-icon{margin-right:6px;font-size:16px;flex-shrink:0}.tree-item.file .file-icon{margin-left:20px}.file-name,.folder-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-tree-content::-webkit-scrollbar{width:8px}.file-tree-content::-webkit-scrollbar-track{background:#f6f8fa}.file-tree-content::-webkit-scrollbar-thumb{background:#d0d7de;border-radius:4px}.file-tree-content::-webkit-scrollbar-thumb:hover{background:#afb8c1}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2
+ Theme: GitHub
3
+ Description: Light theme as seen on github.com
4
+ Author: github.com
5
+ Maintainer: @Hirse
6
+ Updated: 2021-05-15
7
+
8
+ Outdated base version: https://github.com/primer/github-syntax-light
9
+ Current colors taken from GitHub's CSS
10
+ */.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}.markdown-with-comments{display:flex;gap:0;min-height:100vh}.markdown-container{flex:1;max-width:900px;padding:2rem;padding-right:320px;position:relative;margin:0 auto}.comments-sidebar{width:300px;flex-shrink:0;border-left:1px solid #e1e4e8;background:#f6f8fa;padding:1rem;height:100vh;position:fixed;top:0;right:0;overflow-y:auto}.markdown-header{border-bottom:2px solid #e1e4e8;padding-bottom:1rem;margin-bottom:2rem}.markdown-header h1{font-size:1.5rem;color:#24292e;margin:0}.markdown-content{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;font-size:16px;line-height:1.6;color:#24292e;position:relative;padding-left:0}.markdown-content h1{font-size:2em;border-bottom:1px solid #eaecef;padding-bottom:.3em;margin-top:1.5em;margin-bottom:.5em}.markdown-content h2{font-size:1.5em;border-bottom:1px solid #eaecef;padding-bottom:.3em;margin-top:1.5em;margin-bottom:.5em}.markdown-content h3{font-size:1.25em;margin-top:1.5em;margin-bottom:.5em}.markdown-content h4{font-size:1em;margin-top:1.5em;margin-bottom:.5em}.markdown-content pre{background-color:#f6f8fa;border-radius:6px;padding:16px;overflow:auto;font-size:14px;line-height:1.45}.markdown-content code{background-color:#f6f8fa;padding:.2em .4em;border-radius:3px;font-size:85%;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace}.markdown-content pre code{background-color:transparent;padding:0;font-size:100%}.markdown-content ul,.markdown-content ol{padding-left:2em;margin-top:0;margin-bottom:16px}.markdown-content li{margin-top:.25em}.markdown-content blockquote{padding:0 1em;color:#6a737d;border-left:4px solid #dfe2e5;margin:0 0 16px}.markdown-content table{border-collapse:collapse;width:100%;margin-bottom:16px}.markdown-content table th,.markdown-content table td{padding:6px 13px;border:1px solid #dfe2e5}.markdown-content table th{font-weight:600;background-color:#f6f8fa}.markdown-content table tr{background-color:#fff;border-top:1px solid #c6cbd1}.markdown-content table tr:nth-child(2n){background-color:#f6f8fa}.markdown-content a{color:#0366d6;text-decoration:none}.markdown-content a:hover{text-decoration:underline}.markdown-content hr{height:.25em;padding:0;margin:24px 0;background-color:#e1e4e8;border:0}.markdown-content input[type=checkbox]{margin-right:.5em}.line-range{font-size:.75rem;font-weight:400;color:#6a737d;margin-left:.5rem}.selection-popover{display:flex;gap:4px;background:#1f2937;border-radius:6px;padding:4px;box-shadow:0 4px 12px #0000004d;z-index:1000}.popover-button{background:transparent;border:none;color:#e5e7eb;font-size:12px;padding:6px 10px;border-radius:4px;cursor:pointer;white-space:nowrap;transition:background-color .15s}.popover-button:hover{background:#374151}.popover-button:active{background:#4b5563}.comment-form{display:flex;flex-direction:column;gap:8px;min-width:280px}.comment-input{background:#374151;border:1px solid #4b5563;border-radius:4px;color:#e5e7eb;font-size:13px;padding:8px;resize:vertical;min-height:60px;font-family:inherit}.comment-input:focus{outline:none;border-color:#6b7280}.comment-input::placeholder{color:#9ca3af}.comment-actions{display:flex;justify-content:flex-end;gap:8px}.comment-cancel,.comment-submit{font-size:12px;padding:6px 12px;border-radius:4px;cursor:pointer;border:none;transition:background-color .15s}.comment-cancel{background:transparent;color:#9ca3af}.comment-cancel:hover{background:#374151;color:#e5e7eb}.comment-submit{background:#3b82f6;color:#fff}.comment-submit:hover{background:#2563eb}.comment-submit:disabled{background:#4b5563;color:#9ca3af;cursor:not-allowed}.selection-highlight{background-color:#0000001a;border-radius:2px}.comment-list-empty{text-align:center;color:#6a737d;padding:2rem 1rem}.comment-list-empty p{margin:.5rem 0}.comment-list-hint{font-size:.85rem;color:#959da5}.comment-list-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid #e1e4e8}.comment-list-title{font-size:.9rem;font-weight:600;color:#24292e;margin:0}.comment-list-copy-all{display:flex;align-items:center;gap:.25rem;background:none;border:1px solid #e1e4e8;border-radius:4px;padding:.25rem .5rem;font-size:.75rem;color:#6a737d;cursor:pointer;transition:all .15s}.comment-list-copy-all:hover{background:#f6f8fa;color:#0366d6;border-color:#0366d6}.comment-list-copy-all.copied{color:#22863a;border-color:#22863a}.comment-list-actions{display:flex;gap:.5rem}.comment-list-delete-all{display:flex;align-items:center;gap:.25rem;background:none;border:1px solid #e1e4e8;border-radius:4px;padding:.25rem .5rem;font-size:.75rem;color:#6a737d;cursor:pointer;transition:all .15s}.comment-list-delete-all:hover{background:#ffeef0;color:#d73a49;border-color:#d73a49}.comment-list-items{display:flex;flex-direction:column;gap:.75rem}.comment-item{background:#fff;border:1px solid #e1e4e8;border-radius:6px;padding:.75rem;font-size:.85rem}.comment-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}.comment-item-lines{font-size:.75rem;color:#6a737d;font-weight:500}.comment-item-actions{display:flex;gap:.25rem;align-items:center}.comment-item-copy,.comment-item-delete{background:none;border:none;color:#959da5;cursor:pointer;padding:.125rem .25rem;display:flex;align-items:center;justify-content:center}.comment-item-delete{font-size:1rem;line-height:1}.comment-item-copy:hover{color:#0366d6}.comment-item-copy.copied{color:#22863a}.comment-item-delete:hover{color:#d73a49}.comment-item-selection{font-size:.8rem;color:#6a737d;font-style:italic;margin-bottom:.5rem;padding:.25rem .5rem;background:#f6f8fa;border-radius:3px;overflow:hidden;text-overflow:ellipsis}.comment-item-text{color:#24292e;line-height:1.4;white-space:pre-wrap}.dev-container{display:flex;height:100vh;overflow:hidden;position:relative}.dev-sidebar{flex-shrink:0;height:100%;overflow:hidden;transition:width .3s ease-in-out;background-color:#f6f8fa;border-right:1px solid #d0d7de}.dev-sidebar:not(.closed){width:300px}.dev-sidebar.closed{width:56px}.sidebar-icon-bar{width:56px;height:100%;display:flex;flex-direction:column;align-items:center;padding:12px 0;gap:8px;background-color:#fff}.icon-bar-item{width:40px;height:40px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:6px;cursor:pointer;color:#57606a;transition:background-color .2s,color .2s;padding:0}.icon-bar-item:hover{background-color:#f6f8fa;color:#24292f}.icon-bar-item:active{background-color:#eaeef2}.sidebar-content{width:300px;height:100%;overflow:hidden}.dev-main{flex:1;height:100%;overflow-y:auto;background-color:#fff}.dev-loading,.dev-placeholder,.dev-empty{display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;color:#57606a}.dev-placeholder h2,.dev-empty h2{margin:0 0 8px;font-size:24px;color:#24292f}.dev-placeholder p,.dev-empty p{margin:4px 0;font-size:14px}.dev-placeholder .file-count{margin-top:16px;padding:8px 16px;background-color:#f6f8fa;border-radius:6px;font-weight:500;color:#24292f}@media(max-width:768px){.dev-sidebar{width:250px}.dev-sidebar.closed{width:56px}}@media(max-width:480px){.dev-container{flex-direction:column}.dev-sidebar{width:100%;height:auto;max-height:40%;border-right:none;border-bottom:1px solid #d0d7de;flex-direction:row}.dev-sidebar.closed{width:100%;height:56px}.sidebar-icon-bar{width:100%;height:56px;flex-direction:row;border-right:none;border-bottom:1px solid #d0d7de;padding:0 12px}.sidebar-content{display:none}.dev-sidebar:not(.closed) .sidebar-content{display:flex}.dev-main{flex:1}}
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Markdown Review</title>
7
+ <script type="module" crossorigin src="/assets/index-BIHwAaLg.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-D1IQM189.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "md-review",
3
+ "version": "0.0.1",
4
+ "description": "Review Markdown files in your browser with inline comments",
5
+ "type": "module",
6
+ "bin": {
7
+ "md-review": "./bin/md-review.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "dist",
12
+ "server"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/ryo-manba/md-review.git"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/ryo-manba/md-review/issues"
20
+ },
21
+ "homepage": "https://github.com/ryo-manba/md-review#readme",
22
+ "scripts": {
23
+ "dev": "concurrently 'pnpm run server' 'pnpm run client'",
24
+ "client": "vite",
25
+ "build": "tsc && vite build",
26
+ "preview": "vite preview",
27
+ "server": "node server/index.js"
28
+ },
29
+ "keywords": [
30
+ "markdown",
31
+ "review"
32
+ ],
33
+ "author": "ryo-manba",
34
+ "license": "MIT",
35
+ "packageManager": "pnpm@10.23.0",
36
+ "dependencies": {
37
+ "@hono/node-server": "^1.19.6",
38
+ "highlight.js": "^11.11.1",
39
+ "hono": "^4.10.6",
40
+ "mri": "^1.2.0",
41
+ "open": "^11.0.0",
42
+ "react": "^19.0.0",
43
+ "react-dom": "^19.0.0",
44
+ "react-markdown": "^10.1.0",
45
+ "rehype-highlight": "^7.0.2",
46
+ "remark-gfm": "^4.0.1"
47
+ },
48
+ "devDependencies": {
49
+ "@types/hast": "^3.0.4",
50
+ "@types/node": "^22.0.0",
51
+ "@types/react": "^19.0.0",
52
+ "@types/react-dom": "^19.0.0",
53
+ "@vitejs/plugin-react": "^4.3.4",
54
+ "concurrently": "^9.2.1",
55
+ "typescript": "^5.6.3",
56
+ "vite": "^6.0.3"
57
+ }
58
+ }
@@ -0,0 +1,135 @@
1
+ // server/index.js
2
+ import { Hono } from 'hono';
3
+ import { cors } from 'hono/cors';
4
+ import { serve } from '@hono/node-server';
5
+ import { readFile, readdir, stat } from 'fs/promises';
6
+ import { basename, join, relative, resolve } from 'path';
7
+
8
+ // Port validation function
9
+ function validatePort(value) {
10
+ const port = parseInt(value, 10);
11
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
12
+ throw new Error(`Invalid port: ${value}`);
13
+ }
14
+ return port;
15
+ }
16
+
17
+ const app = new Hono();
18
+ const PORT = validatePort(process.env.API_PORT || 3030);
19
+ const VITE_PORT = process.env.VITE_PORT || 6060;
20
+ const CORS_ORIGIN = process.env.CORS_ORIGIN || `http://localhost:${VITE_PORT}`;
21
+ const MARKDOWN_FILE_PATH = process.env.MARKDOWN_FILE_PATH;
22
+ const BASE_DIR = process.env.BASE_DIR || process.cwd();
23
+
24
+ // Check if file has markdown extension
25
+ function isMarkdownFile(filename) {
26
+ return filename.endsWith('.md') || filename.endsWith('.markdown');
27
+ }
28
+
29
+ // Helper function to scan markdown files recursively
30
+ async function scanMarkdownFiles(dir, baseDir = dir) {
31
+ const files = [];
32
+ const entries = await readdir(dir, { withFileTypes: true });
33
+
34
+ for (const entry of entries) {
35
+ const fullPath = join(dir, entry.name);
36
+ const relativePath = relative(baseDir, fullPath);
37
+
38
+ // Skip node_modules, .git, dist, and hidden directories
39
+ if (entry.name.startsWith('.') ||
40
+ entry.name === 'node_modules' ||
41
+ entry.name === 'dist' ||
42
+ entry.name === 'dist-ssr') {
43
+ continue;
44
+ }
45
+
46
+ if (entry.isDirectory()) {
47
+ const subFiles = await scanMarkdownFiles(fullPath, baseDir);
48
+ files.push(...subFiles);
49
+ } else if (entry.isFile() && isMarkdownFile(entry.name)) {
50
+ files.push({
51
+ name: entry.name,
52
+ path: relativePath,
53
+ dir: relative(baseDir, dir) || '.'
54
+ });
55
+ }
56
+ }
57
+
58
+ return files;
59
+ }
60
+
61
+ // CORS設定
62
+ app.use('/*', cors({
63
+ origin: CORS_ORIGIN
64
+ }));
65
+
66
+ // ヘルスチェック
67
+ app.get('/api/health', (c) => {
68
+ return c.json({ status: 'ok' });
69
+ });
70
+
71
+ // Get list of all markdown files
72
+ app.get('/api/files', async (c) => {
73
+ try {
74
+ const files = await scanMarkdownFiles(BASE_DIR);
75
+ return c.json({ files, baseDir: BASE_DIR });
76
+ } catch (err) {
77
+ console.error('Error scanning markdown files:', err.message);
78
+ return c.json({
79
+ error: 'Failed to scan markdown files'
80
+ }, 500);
81
+ }
82
+ });
83
+
84
+ // Markdownファイル取得API (CLI mode)
85
+ app.get('/api/markdown', async (c) => {
86
+ if (!MARKDOWN_FILE_PATH) {
87
+ return c.json({
88
+ error: 'Markdown file path not specified'
89
+ }, 500);
90
+ }
91
+
92
+ try {
93
+ const data = await readFile(MARKDOWN_FILE_PATH, 'utf-8');
94
+ const filename = basename(MARKDOWN_FILE_PATH);
95
+ return c.json({ content: data, filename });
96
+ } catch (err) {
97
+ console.error('Error reading markdown:', err.message);
98
+ return c.json({
99
+ error: 'Failed to read markdown file'
100
+ }, 500);
101
+ }
102
+ });
103
+
104
+ // Get specific markdown file by path (Dev mode)
105
+ app.get('/api/markdown/:path{.+}', async (c) => {
106
+ const requestedPath = c.req.param('path');
107
+
108
+ try {
109
+ // Security check: prevent path traversal
110
+ const fullPath = resolve(BASE_DIR, requestedPath);
111
+ if (!fullPath.startsWith(resolve(BASE_DIR))) {
112
+ return c.json({
113
+ error: 'Invalid file path'
114
+ }, 403);
115
+ }
116
+
117
+ const data = await readFile(fullPath, 'utf-8');
118
+ const filename = basename(fullPath);
119
+ return c.json({ content: data, filename, path: requestedPath });
120
+ } catch (err) {
121
+ console.error('Error reading markdown:', err.message);
122
+ return c.json({
123
+ error: 'Failed to read markdown file'
124
+ }, 500);
125
+ }
126
+ });
127
+
128
+ const SERVER_READY_MESSAGE = 'md-review server started';
129
+ console.log(`API Server running on http://localhost:${PORT}`);
130
+ console.log(SERVER_READY_MESSAGE);
131
+
132
+ serve({
133
+ fetch: app.fetch,
134
+ port: PORT
135
+ });