httpath 1.1.0 → 1.3.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/dist/index.cjs CHANGED
@@ -43,9 +43,9 @@ Options:
43
43
 
44
44
  connect();
45
45
  })();
46
- <\/script>`,y=(e,t)=>{let n=v(t);return e.includes(`</body>`)?e.replace(`</body>`,`${n}\n</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${n}\n</html>`):e+n},b=async(e,t)=>{t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.write(`data: connected
46
+ <\/script>`,y=(e,t)=>{let n=v(t);return e.includes(`</body>`)?e.replace(`</body>`,`${n}\n</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${n}\n</html>`):e+n},b=(e,t)=>{t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.write(`data: connected
47
47
 
48
- `),f.add(t),e.on(`close`,()=>{f.delete(t)})},x=async(e=`change`)=>{if(f.size!==0){m(`Reloading ${f.size} clients (${e})`,`debug`);for(let e of f)try{e.write(`data: reload
48
+ `),f.add(t),e.on(`close`,()=>{f.delete(t)})},x=(e=`change`)=>{if(f.size!==0){m(`Reloading ${f.size} clients (${e})`,`debug`);for(let e of f)try{e.write(`data: reload
49
49
 
50
50
  `)}catch{}}},S=()=>`
51
51
  :root { --bg-page: #f2f2f2; --bg-article: #bbc3db; --color-title: #333; --color-paragraph: #333; --link-color: #1a0dab; --link-hover-color: #d93025; --toggle-color: #0f172b; --fill-icons: white; }
@@ -54,7 +54,226 @@ body { font-family: monospace; font-size: 1.3em; margin: 0.5em; padding: 1em; ba
54
54
  h1 { font-size: 2em; margin-bottom: 0.5em; }
55
55
  a { text-decoration: none; color: var(--link-color); &:hover { text-decoration: underline; color: var(--link-hover-color); } }
56
56
  .toggle { --width: 3em; --height: calc(var(--width) / 2); --border-radius: calc(var(--height) / 2); display: inline-block; cursor: pointer; .toggle__input { display: none; &:checked + .toggle__fill { background: #009578; } &:checked + .toggle__fill::after { transform: translateX(var(--height)); } } .toggle__fill { position: relative; width: var(--width); height: var(--height); border-radius: var(--border-radius); background-color: var(--toggle-color); transition: background-color 0.3s ease-in-out; &::after { content: ""; position: absolute; top: 0; left: 0; width: var(--height); height: var(--height); border-radius: var(--border-radius); background-color: var(--fill-icons); box-shadow: 0 0 0.2em rgba(0, 0, 0, 0.2); transition: transform 0.3s ease-in-out; } } }
57
- `,C=(e,t)=>{let n=t===`/`?``:`<a href="../">../</a><br>`,r=e.slice().sort((e,t)=>e.isDirectory===t.isDirectory?0:e.isDirectory?-1:1).sort((e,t)=>e.isDirectory===t.isDirectory?e.name.localeCompare(t.name):0).map(e=>{let t=e.isDirectory?`📁`:`📄`;return`<a href="${e.url}">${t} ${e.name}</a>`}).join(`<br>`);return`
57
+ :root {
58
+ --bg-page: #f6f8fb;
59
+ --bg-panel: #ffffff;
60
+ --muted: #6b7280;
61
+ --title: #0f172a;
62
+ --link-color: #2563eb;
63
+ --link-hover-color: #1e40af;
64
+ --accent: #10b981;
65
+ --card-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
66
+ --radius: 10px;
67
+ --gap: 12px;
68
+ --toggle-track: #e6e9ee;
69
+ --toggle-knob: #ffffff;
70
+ }
71
+
72
+ :root:has(#dark:checked) {
73
+ --bg-page: #0b1220;
74
+ --bg-panel: #0f1724;
75
+ --muted: #9ca3af;
76
+ --title: #e6eef8;
77
+ --link-color: #7c9cff;
78
+ --link-hover-color: #9fb7ff;
79
+ --accent: #34d399;
80
+ --card-shadow: 0 6px 18px rgba(2,6,23,0.6);
81
+ --toggle-track: #1f2937;
82
+ --toggle-knob: #0b1220;
83
+ }
84
+
85
+ * { box-sizing: border-box; }
86
+
87
+ body {
88
+ margin: 0;
89
+ padding: 2rem;
90
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
91
+ background-color: var(--bg-page);
92
+ color: var(--title);
93
+ -webkit-font-smoothing: antialiased;
94
+ -moz-osx-font-smoothing: grayscale;
95
+ line-height: 1.4;
96
+ display: flex;
97
+ justify-content: center;
98
+ align-items: flex-start;
99
+ min-height: 100vh;
100
+ }
101
+
102
+ .container {
103
+ width: 100%;
104
+ max-width: 980px;
105
+ background: linear-gradient(180deg, rgba(255,255,255,0.6), rgba(255,255,255,0.4));
106
+ background-color: var(--bg-panel);
107
+ border-radius: var(--radius);
108
+ box-shadow: var(--card-shadow);
109
+ padding: 1.25rem;
110
+ gap: var(--gap);
111
+ }
112
+
113
+ /* Header */
114
+ .header {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: space-between;
118
+ gap: 1rem;
119
+ margin-bottom: 0.75rem;
120
+ }
121
+ .header .title {
122
+ font-size: 1.6rem;
123
+ font-weight: 600;
124
+ }
125
+ .header .subtitle {
126
+ color: var(--muted);
127
+ font-size: 0.95rem;
128
+ }
129
+
130
+ /* Dark toggle */
131
+ .toggle {
132
+ --width: 48px;
133
+ --height: 24px;
134
+ display: inline-flex;
135
+ align-items: center;
136
+ cursor: pointer;
137
+ }
138
+ .toggle__input {
139
+ position: absolute;
140
+ opacity: 0;
141
+ pointer-events: none;
142
+ }
143
+ .toggle__fill {
144
+ width: var(--width);
145
+ height: var(--height);
146
+ background: var(--toggle-track);
147
+ border-radius: 999px;
148
+ position: relative;
149
+ transition: background 200ms ease;
150
+ }
151
+ .toggle__fill::after {
152
+ content: "";
153
+ position: absolute;
154
+ top: 3px;
155
+ left: 3px;
156
+ width: calc(var(--height) - 6px);
157
+ height: calc(var(--height) - 6px);
158
+ border-radius: 50%;
159
+ background: var(--toggle-knob);
160
+ box-shadow: 0 2px 6px rgba(2,6,23,0.08);
161
+ transition: transform 200ms ease;
162
+ }
163
+ .toggle__input:checked + .toggle__fill {
164
+ background: linear-gradient(90deg, rgba(16,185,129,0.9), rgba(34,197,94,0.9));
165
+ }
166
+ .toggle__input:checked + .toggle__fill::after {
167
+ transform: translateX(calc(var(--width) - var(--height)));
168
+ }
169
+
170
+ /* Listing - improved file/folder presentation */
171
+ .listing {
172
+ display: flex;
173
+ flex-direction: column;
174
+ gap: 0.5rem;
175
+ margin-top: 0.5rem;
176
+ width: 100%;
177
+ }
178
+
179
+ /* Each item becomes a clear row with icon, name, and meta */
180
+ .listing a.item {
181
+ display: grid;
182
+ grid-template-columns: 44px 1fr auto;
183
+ align-items: center;
184
+ gap: 0.75rem;
185
+ padding: 0.75rem 1rem;
186
+ border-radius: 10px;
187
+ color: var(--title);
188
+ text-decoration: none;
189
+ background: transparent;
190
+ transition: background 180ms ease, transform 150ms ease, box-shadow 180ms ease;
191
+ font-size: 1rem;
192
+ overflow: hidden;
193
+ border: 1px solid transparent;
194
+ }
195
+
196
+ /* Hover and focus styles */
197
+ .listing a.item:hover,
198
+ .listing a.item:focus {
199
+ background: color-mix(in srgb, var(--link-hover-color) 6%, transparent);
200
+ transform: translateY(-4px);
201
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--title) 6%, transparent);
202
+ color: var(--link-hover-color);
203
+ outline: none;
204
+ border-color: color-mix(in srgb, var(--link-hover-color) 12%, transparent);
205
+ }
206
+
207
+ /* Icon badge */
208
+ .listing a.item .icon {
209
+ display: inline-grid;
210
+ place-items: center;
211
+ width: 44px;
212
+ height: 44px;
213
+ border-radius: 10px;
214
+ font-size: 1.1rem;
215
+ flex: 0 0 auto;
216
+ background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(0,0,0,0.02));
217
+ box-shadow: 0 2px 8px rgba(2,6,23,0.04);
218
+ color: var(--fill-icons, #fff);
219
+ }
220
+
221
+ /* Different tones for directories vs files - still respecting variables */
222
+ .listing a.item.dir .icon {
223
+ background: linear-gradient(180deg, rgba(124,58,237,0.12), rgba(124,58,237,0.06));
224
+ color: white;
225
+ }
226
+ .listing a.item.file .icon {
227
+ background: linear-gradient(180deg, rgba(37,99,235,0.08), rgba(37,99,235,0.03));
228
+ color: white;
229
+ }
230
+
231
+ /* Name column */
232
+ .listing a.item .name {
233
+ font-weight: 600;
234
+ color: var(--title);
235
+ min-width: 0; /* allow ellipsis */
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: nowrap;
239
+ }
240
+
241
+ /* Meta column (size/type/date or just type for now) */
242
+ .listing a.item .meta {
243
+ color: var(--muted);
244
+ font-size: 0.82rem;
245
+ margin-left: 0.6rem;
246
+ text-align: right;
247
+ white-space: nowrap;
248
+ flex-shrink: 0;
249
+ background: color-mix(in srgb, var(--muted) 8%, transparent);
250
+ padding: 4px 8px;
251
+ border-radius: 999px;
252
+ }
253
+
254
+ /* Parent link styling - present as subtle pill */
255
+ .parent {
256
+ display: inline-flex;
257
+ align-items: center;
258
+ gap: 0.5rem;
259
+ padding: 0.35rem 0.6rem;
260
+ border-radius: 8px;
261
+ margin-bottom: 0.35rem;
262
+ color: var(--muted);
263
+ font-size: 0.95rem;
264
+ background: transparent;
265
+ }
266
+ .parent a { color: inherit; text-decoration: none; }
267
+
268
+ /* Small screens adjustments */
269
+ @media (max-width: 640px) {
270
+ .container { padding: 0.8rem; margin: 1rem; }
271
+ .header .title { font-size: 1.25rem; }
272
+ .listing a.item { padding: 0.5rem; font-size: 0.95rem; grid-template-columns: 36px 1fr auto; }
273
+ .listing a.item .icon { width: 36px; height: 36px; border-radius: 8px; }
274
+ }
275
+ `,C=(e,t)=>{let n=t===`/`?``:`<div class="parent"><a href="../">../</a></div>`,r=e.slice().sort((e,t)=>e.isDirectory===t.isDirectory?0:e.isDirectory?-1:1).sort((e,t)=>e.isDirectory===t.isDirectory?e.name.localeCompare(t.name):0).map(e=>{let t=e.isDirectory?`📁`:`📄`,n=e.url,r=e.isDirectory?`dir`:`file`,i=e.isDirectory?`Directory`:e.name.includes(`.`)?e.name.split(`.`).pop().toUpperCase():`File`;return`<a class="item ${r}" href="${n}"><span class="icon">${t}</span><span class="name">${e.name}</span><span class="meta">${i}</span></a>`}).join(`
276
+ `);return`
58
277
  <!DOCTYPE html>
59
278
  <html>
60
279
  <head>
@@ -65,7 +284,9 @@ a { text-decoration: none; color: var(--link-color); &:hover { text-decoration:
65
284
  <body>
66
285
  <label class="toggle" for="dark"><input type="checkbox" id="dark" class="toggle__input" checked><span class="toggle__fill"></span></label>
67
286
  <h1>Listing of ${t}</h1>
68
- ${n}
69
- ${r}
287
+ <div class="listing">
288
+ ${n}
289
+ ${r}
290
+ </div>
70
291
  </body>
71
- </html>`},w=async(e,t,n)=>{let a=_(e);if(n.enableLiveReload&&a.includes(`text/html`))try{let r=await(0,i.open)(e);try{let e=y(await r.readFile({encoding:`utf-8`}),n.port);t.writeHead(200,{"Content-Type":a}),t.end(e);return}finally{await r.close()}}catch{}t.writeHead(200,{"Content-Type":a});let o=(0,r.createReadStream)(e);o.pipe(t),o.on(`error`,e=>{t.writeHead(500),t.end(e.message)})},T=async(e,n,r,a)=>{try{let o=C((await(0,i.readdir)(e,{withFileTypes:!0})).filter(e=>!h(e.name,a.ignorePatterns)).map(e=>({name:e.name,isDirectory:e.isDirectory(),url:(0,t.join)(n,e.name).replace(/\\/g,`/`)})),n);a.enableLiveReload&&(o=y(o,a.port)),r.writeHead(200,{"Content-Type":`text/html`}),r.end(o)}catch{r.writeHead(500),r.end(`Error reading directory`)}},E=e=>async(n,r)=>{let a=n.headers.host||`localhost:${e.port}`,o=new URL(n.url||`/`,`http://${a}`),s=decodeURIComponent(o.pathname);if(e.enableLiveReload&&s===`/livereload`)return b(n,r);let c=(0,t.resolve)(e.directory,`.${s}`);if(!c.startsWith(e.directory))return r.writeHead(403),r.end(`Forbidden`);try{let n=await(0,i.stat)(c);if(n.isFile())return await w(c,r,e);if(n.isDirectory()){if(e.enableDirectoryListing)return await T(c,s,r,e);{let n=(0,t.join)(c,`index.html`);try{return await(0,i.stat)(n),await w(n,r,e)}catch{return r.writeHead(403),r.end(`Listing disabled`)}}}else return r.writeHead(404),r.end(`Not Found`)}catch(e){if(e&&e.code===`ENOENT`)return r.writeHead(404),r.end(`Not Found`);r.writeHead(500),r.end(e?.message||`Server error`)}},D=()=>{m(`Reloading server process...`),(0,o.spawn)(process.argv[0],process.argv.slice(1),{stdio:`inherit`,detached:!0}).unref(),process.exit(0)},O=async e=>{m(`Watching: ${e.directory}`);try{(0,r.watch)(e.directory,{recursive:!0},async(t,n)=>{if(!n||h(n,e.ignorePatterns)||d)return;d=!0,m(`Change detected: ${n} (${t})`),await g(500);let r=/\.(json|js|mjs|ts)$/.test(n),i=/\.(html|css|png|jpg|jpeg|gif|svg)$/.test(n);e.restartOnChange||r?(e.enableLiveReload&&x(`restart`),D()):e.enableLiveReload&&i&&x(`asset change`),setTimeout(()=>{d=!1},1e3)})}catch(e){m(`Watcher error: ${e?.message}`,`error`)}};(()=>{try{let e=p(process.argv.slice(2));(0,n.createServer)(E(e)).listen(e.port,()=>{m(`Server running at http://localhost:${e.port}`),m(`Root: ${e.directory}`),O(e)}),[`SIGINT`,`SIGTERM`].forEach(e=>{process.on(e,()=>{m(`Shutting down...`),process.exit(0)})})}catch(e){m(`Startup Error: ${e?.message}`,`error`),process.exit(1)}})();
292
+ </html>`},w=async(e,t,n)=>{let a=_(e);if(n.enableLiveReload&&a.includes(`text/html`))try{let r=await(0,i.open)(e);try{let e=y(await r.readFile({encoding:`utf-8`}),n.port);t.writeHead(200,{"Content-Type":a}),t.end(e);return}finally{await r.close()}}catch{}t.writeHead(200,{"Content-Type":a});let o=(0,r.createReadStream)(e);o.pipe(t),o.on(`error`,e=>{t.writeHead(500),t.end(e.message)})},T=async(e,n,r,a)=>{try{let o=C((await(0,i.readdir)(e,{withFileTypes:!0})).filter(e=>!h(e.name,a.ignorePatterns)).map(e=>({name:e.name,isDirectory:e.isDirectory(),url:(0,t.join)(n,e.name).replace(/\\/g,`/`)})),n);a.enableLiveReload&&(o=y(o,a.port)),r.writeHead(200,{"Content-Type":`text/html`}),r.end(o)}catch{r.writeHead(500),r.end(`Error reading directory`)}},E=e=>async(n,r)=>{let a=n.headers.host||`localhost:${e.port}`,o=new URL(n.url||`/`,`http://${a}`),s=decodeURIComponent(o.pathname);if(e.enableLiveReload&&s===`/livereload`)return b(n,r);let c=(0,t.resolve)(e.directory,`.${s}`);if(!c.startsWith(e.directory))return r.writeHead(403),r.end(`Forbidden`);try{let n=await(0,i.stat)(c);if(n.isFile())return await w(c,r,e);if(n.isDirectory()){if(e.enableDirectoryListing)return await T(c,s,r,e);{let n=(0,t.join)(c,`index.html`);try{return await(0,i.stat)(n),await w(n,r,e)}catch{return r.writeHead(403),r.end(`Listing disabled`)}}}else return r.writeHead(404),r.end(`Not Found`)}catch(e){if(e&&e.code===`ENOENT`)return r.writeHead(404),r.end(`Not Found`);r.writeHead(500),r.end(e?.message||`Server error`)}},D=()=>{m(`Reloading server process...`),(0,o.spawn)(process.argv[0],process.argv.slice(1),{stdio:`inherit`,detached:!0}).unref(),process.exit(0)},O=e=>{m(`Watching: ${e.directory}`);try{(0,r.watch)(e.directory,{recursive:!0},async(t,n)=>{if(!n||h(n,e.ignorePatterns)||d)return;d=!0,m(`Change detected: ${n} (${t})`),await g(500);let r=/\.(json|js|mjs|ts)$/.test(n),i=/\.(html|css|png|jpg|jpeg|gif|svg)$/.test(n);e.restartOnChange||r?(e.enableLiveReload&&x(`restart`),D()):e.enableLiveReload&&i&&x(`asset change`),setTimeout(()=>{d=!1},1e3)})}catch(e){m(`Watcher error: ${e?.message}`,`error`)}};(()=>{try{let e=p(process.argv.slice(2));(0,n.createServer)(E(e)).listen(e.port,()=>{m(`Server running at http://localhost:${e.port}`),m(`Root: ${e.directory}`),O(e)}),[`SIGINT`,`SIGTERM`].forEach(e=>{process.on(e,()=>{m(`Shutting down...`),process.exit(0)})})}catch(e){m(`Startup Error: ${e?.message}`,`error`),process.exit(1)}})();
package/dist/index.mjs CHANGED
@@ -43,9 +43,9 @@ Options:
43
43
 
44
44
  connect();
45
45
  })();
46
- <\/script>`,T=(e,t)=>{let n=w(t);return e.includes(`</body>`)?e.replace(`</body>`,`${n}\n</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${n}\n</html>`):e+n},E=async(e,t)=>{t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.write(`data: connected
46
+ <\/script>`,T=(e,t)=>{let n=w(t);return e.includes(`</body>`)?e.replace(`</body>`,`${n}\n</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${n}\n</html>`):e+n},E=(e,t)=>{t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.write(`data: connected
47
47
 
48
- `),v.add(t),e.on(`close`,()=>{v.delete(t)})},D=async(e=`change`)=>{if(v.size!==0){b(`Reloading ${v.size} clients (${e})`,`debug`);for(let e of v)try{e.write(`data: reload
48
+ `),v.add(t),e.on(`close`,()=>{v.delete(t)})},D=(e=`change`)=>{if(v.size!==0){b(`Reloading ${v.size} clients (${e})`,`debug`);for(let e of v)try{e.write(`data: reload
49
49
 
50
50
  `)}catch{}}},O=()=>`
51
51
  :root { --bg-page: #f2f2f2; --bg-article: #bbc3db; --color-title: #333; --color-paragraph: #333; --link-color: #1a0dab; --link-hover-color: #d93025; --toggle-color: #0f172b; --fill-icons: white; }
@@ -54,7 +54,226 @@ body { font-family: monospace; font-size: 1.3em; margin: 0.5em; padding: 1em; ba
54
54
  h1 { font-size: 2em; margin-bottom: 0.5em; }
55
55
  a { text-decoration: none; color: var(--link-color); &:hover { text-decoration: underline; color: var(--link-hover-color); } }
56
56
  .toggle { --width: 3em; --height: calc(var(--width) / 2); --border-radius: calc(var(--height) / 2); display: inline-block; cursor: pointer; .toggle__input { display: none; &:checked + .toggle__fill { background: #009578; } &:checked + .toggle__fill::after { transform: translateX(var(--height)); } } .toggle__fill { position: relative; width: var(--width); height: var(--height); border-radius: var(--border-radius); background-color: var(--toggle-color); transition: background-color 0.3s ease-in-out; &::after { content: ""; position: absolute; top: 0; left: 0; width: var(--height); height: var(--height); border-radius: var(--border-radius); background-color: var(--fill-icons); box-shadow: 0 0 0.2em rgba(0, 0, 0, 0.2); transition: transform 0.3s ease-in-out; } } }
57
- `,k=(e,t)=>{let n=t===`/`?``:`<a href="../">../</a><br>`,r=e.slice().sort((e,t)=>e.isDirectory===t.isDirectory?0:e.isDirectory?-1:1).sort((e,t)=>e.isDirectory===t.isDirectory?e.name.localeCompare(t.name):0).map(e=>{let t=e.isDirectory?`📁`:`📄`;return`<a href="${e.url}">${t} ${e.name}</a>`}).join(`<br>`);return`
57
+ :root {
58
+ --bg-page: #f6f8fb;
59
+ --bg-panel: #ffffff;
60
+ --muted: #6b7280;
61
+ --title: #0f172a;
62
+ --link-color: #2563eb;
63
+ --link-hover-color: #1e40af;
64
+ --accent: #10b981;
65
+ --card-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
66
+ --radius: 10px;
67
+ --gap: 12px;
68
+ --toggle-track: #e6e9ee;
69
+ --toggle-knob: #ffffff;
70
+ }
71
+
72
+ :root:has(#dark:checked) {
73
+ --bg-page: #0b1220;
74
+ --bg-panel: #0f1724;
75
+ --muted: #9ca3af;
76
+ --title: #e6eef8;
77
+ --link-color: #7c9cff;
78
+ --link-hover-color: #9fb7ff;
79
+ --accent: #34d399;
80
+ --card-shadow: 0 6px 18px rgba(2,6,23,0.6);
81
+ --toggle-track: #1f2937;
82
+ --toggle-knob: #0b1220;
83
+ }
84
+
85
+ * { box-sizing: border-box; }
86
+
87
+ body {
88
+ margin: 0;
89
+ padding: 2rem;
90
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
91
+ background-color: var(--bg-page);
92
+ color: var(--title);
93
+ -webkit-font-smoothing: antialiased;
94
+ -moz-osx-font-smoothing: grayscale;
95
+ line-height: 1.4;
96
+ display: flex;
97
+ justify-content: center;
98
+ align-items: flex-start;
99
+ min-height: 100vh;
100
+ }
101
+
102
+ .container {
103
+ width: 100%;
104
+ max-width: 980px;
105
+ background: linear-gradient(180deg, rgba(255,255,255,0.6), rgba(255,255,255,0.4));
106
+ background-color: var(--bg-panel);
107
+ border-radius: var(--radius);
108
+ box-shadow: var(--card-shadow);
109
+ padding: 1.25rem;
110
+ gap: var(--gap);
111
+ }
112
+
113
+ /* Header */
114
+ .header {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: space-between;
118
+ gap: 1rem;
119
+ margin-bottom: 0.75rem;
120
+ }
121
+ .header .title {
122
+ font-size: 1.6rem;
123
+ font-weight: 600;
124
+ }
125
+ .header .subtitle {
126
+ color: var(--muted);
127
+ font-size: 0.95rem;
128
+ }
129
+
130
+ /* Dark toggle */
131
+ .toggle {
132
+ --width: 48px;
133
+ --height: 24px;
134
+ display: inline-flex;
135
+ align-items: center;
136
+ cursor: pointer;
137
+ }
138
+ .toggle__input {
139
+ position: absolute;
140
+ opacity: 0;
141
+ pointer-events: none;
142
+ }
143
+ .toggle__fill {
144
+ width: var(--width);
145
+ height: var(--height);
146
+ background: var(--toggle-track);
147
+ border-radius: 999px;
148
+ position: relative;
149
+ transition: background 200ms ease;
150
+ }
151
+ .toggle__fill::after {
152
+ content: "";
153
+ position: absolute;
154
+ top: 3px;
155
+ left: 3px;
156
+ width: calc(var(--height) - 6px);
157
+ height: calc(var(--height) - 6px);
158
+ border-radius: 50%;
159
+ background: var(--toggle-knob);
160
+ box-shadow: 0 2px 6px rgba(2,6,23,0.08);
161
+ transition: transform 200ms ease;
162
+ }
163
+ .toggle__input:checked + .toggle__fill {
164
+ background: linear-gradient(90deg, rgba(16,185,129,0.9), rgba(34,197,94,0.9));
165
+ }
166
+ .toggle__input:checked + .toggle__fill::after {
167
+ transform: translateX(calc(var(--width) - var(--height)));
168
+ }
169
+
170
+ /* Listing - improved file/folder presentation */
171
+ .listing {
172
+ display: flex;
173
+ flex-direction: column;
174
+ gap: 0.5rem;
175
+ margin-top: 0.5rem;
176
+ width: 100%;
177
+ }
178
+
179
+ /* Each item becomes a clear row with icon, name, and meta */
180
+ .listing a.item {
181
+ display: grid;
182
+ grid-template-columns: 44px 1fr auto;
183
+ align-items: center;
184
+ gap: 0.75rem;
185
+ padding: 0.75rem 1rem;
186
+ border-radius: 10px;
187
+ color: var(--title);
188
+ text-decoration: none;
189
+ background: transparent;
190
+ transition: background 180ms ease, transform 150ms ease, box-shadow 180ms ease;
191
+ font-size: 1rem;
192
+ overflow: hidden;
193
+ border: 1px solid transparent;
194
+ }
195
+
196
+ /* Hover and focus styles */
197
+ .listing a.item:hover,
198
+ .listing a.item:focus {
199
+ background: color-mix(in srgb, var(--link-hover-color) 6%, transparent);
200
+ transform: translateY(-4px);
201
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--title) 6%, transparent);
202
+ color: var(--link-hover-color);
203
+ outline: none;
204
+ border-color: color-mix(in srgb, var(--link-hover-color) 12%, transparent);
205
+ }
206
+
207
+ /* Icon badge */
208
+ .listing a.item .icon {
209
+ display: inline-grid;
210
+ place-items: center;
211
+ width: 44px;
212
+ height: 44px;
213
+ border-radius: 10px;
214
+ font-size: 1.1rem;
215
+ flex: 0 0 auto;
216
+ background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(0,0,0,0.02));
217
+ box-shadow: 0 2px 8px rgba(2,6,23,0.04);
218
+ color: var(--fill-icons, #fff);
219
+ }
220
+
221
+ /* Different tones for directories vs files - still respecting variables */
222
+ .listing a.item.dir .icon {
223
+ background: linear-gradient(180deg, rgba(124,58,237,0.12), rgba(124,58,237,0.06));
224
+ color: white;
225
+ }
226
+ .listing a.item.file .icon {
227
+ background: linear-gradient(180deg, rgba(37,99,235,0.08), rgba(37,99,235,0.03));
228
+ color: white;
229
+ }
230
+
231
+ /* Name column */
232
+ .listing a.item .name {
233
+ font-weight: 600;
234
+ color: var(--title);
235
+ min-width: 0; /* allow ellipsis */
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: nowrap;
239
+ }
240
+
241
+ /* Meta column (size/type/date or just type for now) */
242
+ .listing a.item .meta {
243
+ color: var(--muted);
244
+ font-size: 0.82rem;
245
+ margin-left: 0.6rem;
246
+ text-align: right;
247
+ white-space: nowrap;
248
+ flex-shrink: 0;
249
+ background: color-mix(in srgb, var(--muted) 8%, transparent);
250
+ padding: 4px 8px;
251
+ border-radius: 999px;
252
+ }
253
+
254
+ /* Parent link styling - present as subtle pill */
255
+ .parent {
256
+ display: inline-flex;
257
+ align-items: center;
258
+ gap: 0.5rem;
259
+ padding: 0.35rem 0.6rem;
260
+ border-radius: 8px;
261
+ margin-bottom: 0.35rem;
262
+ color: var(--muted);
263
+ font-size: 0.95rem;
264
+ background: transparent;
265
+ }
266
+ .parent a { color: inherit; text-decoration: none; }
267
+
268
+ /* Small screens adjustments */
269
+ @media (max-width: 640px) {
270
+ .container { padding: 0.8rem; margin: 1rem; }
271
+ .header .title { font-size: 1.25rem; }
272
+ .listing a.item { padding: 0.5rem; font-size: 0.95rem; grid-template-columns: 36px 1fr auto; }
273
+ .listing a.item .icon { width: 36px; height: 36px; border-radius: 8px; }
274
+ }
275
+ `,k=(e,t)=>{let n=t===`/`?``:`<div class="parent"><a href="../">../</a></div>`,r=e.slice().sort((e,t)=>e.isDirectory===t.isDirectory?0:e.isDirectory?-1:1).sort((e,t)=>e.isDirectory===t.isDirectory?e.name.localeCompare(t.name):0).map(e=>{let t=e.isDirectory?`📁`:`📄`,n=e.url,r=e.isDirectory?`dir`:`file`,i=e.isDirectory?`Directory`:e.name.includes(`.`)?e.name.split(`.`).pop().toUpperCase():`File`;return`<a class="item ${r}" href="${n}"><span class="icon">${t}</span><span class="name">${e.name}</span><span class="meta">${i}</span></a>`}).join(`
276
+ `);return`
58
277
  <!DOCTYPE html>
59
278
  <html>
60
279
  <head>
@@ -65,7 +284,9 @@ a { text-decoration: none; color: var(--link-color); &:hover { text-decoration:
65
284
  <body>
66
285
  <label class="toggle" for="dark"><input type="checkbox" id="dark" class="toggle__input" checked><span class="toggle__fill"></span></label>
67
286
  <h1>Listing of ${t}</h1>
68
- ${n}
69
- ${r}
287
+ <div class="listing">
288
+ ${n}
289
+ ${r}
290
+ </div>
70
291
  </body>
71
- </html>`},A=async(e,t,n)=>{let r=C(e);if(n.enableLiveReload&&r.includes(`text/html`))try{let i=await c(e);try{let e=T(await i.readFile({encoding:`utf-8`}),n.port);t.writeHead(200,{"Content-Type":r}),t.end(e);return}finally{await i.close()}}catch{}t.writeHead(200,{"Content-Type":r});let i=o(e);i.pipe(t),i.on(`error`,e=>{t.writeHead(500),t.end(e.message)})},j=async(e,t,n,i)=>{try{let a=k((await l(e,{withFileTypes:!0})).filter(e=>!x(e.name,i.ignorePatterns)).map(e=>({name:e.name,isDirectory:e.isDirectory(),url:r(t,e.name).replace(/\\/g,`/`)})),t);i.enableLiveReload&&(a=T(a,i.port)),n.writeHead(200,{"Content-Type":`text/html`}),n.end(a)}catch{n.writeHead(500),n.end(`Error reading directory`)}},M=e=>async(t,n)=>{let a=t.headers.host||`localhost:${e.port}`,o=new URL(t.url||`/`,`http://${a}`),s=decodeURIComponent(o.pathname);if(e.enableLiveReload&&s===`/livereload`)return E(t,n);let c=i(e.directory,`.${s}`);if(!c.startsWith(e.directory))return n.writeHead(403),n.end(`Forbidden`);try{let t=await u(c);if(t.isFile())return await A(c,n,e);if(t.isDirectory()){if(e.enableDirectoryListing)return await j(c,s,n,e);{let t=r(c,`index.html`);try{return await u(t),await A(t,n,e)}catch{return n.writeHead(403),n.end(`Listing disabled`)}}}else return n.writeHead(404),n.end(`Not Found`)}catch(e){if(e&&e.code===`ENOENT`)return n.writeHead(404),n.end(`Not Found`);n.writeHead(500),n.end(e?.message||`Server error`)}},N=()=>{b(`Reloading server process...`),f(process.argv[0],process.argv.slice(1),{stdio:`inherit`,detached:!0}).unref(),process.exit(0)},P=async e=>{b(`Watching: ${e.directory}`);try{s(e.directory,{recursive:!0},async(t,n)=>{if(!n||x(n,e.ignorePatterns)||_)return;_=!0,b(`Change detected: ${n} (${t})`),await S(500);let r=/\.(json|js|mjs|ts)$/.test(n),i=/\.(html|css|png|jpg|jpeg|gif|svg)$/.test(n);e.restartOnChange||r?(e.enableLiveReload&&D(`restart`),N()):e.enableLiveReload&&i&&D(`asset change`),setTimeout(()=>{_=!1},1e3)})}catch(e){b(`Watcher error: ${e?.message}`,`error`)}};(()=>{try{let e=y(process.argv.slice(2));a(M(e)).listen(e.port,()=>{b(`Server running at http://localhost:${e.port}`),b(`Root: ${e.directory}`),P(e)}),[`SIGINT`,`SIGTERM`].forEach(e=>{process.on(e,()=>{b(`Shutting down...`),process.exit(0)})})}catch(e){b(`Startup Error: ${e?.message}`,`error`),process.exit(1)}})();export{};
292
+ </html>`},A=async(e,t,n)=>{let r=C(e);if(n.enableLiveReload&&r.includes(`text/html`))try{let i=await c(e);try{let e=T(await i.readFile({encoding:`utf-8`}),n.port);t.writeHead(200,{"Content-Type":r}),t.end(e);return}finally{await i.close()}}catch{}t.writeHead(200,{"Content-Type":r});let i=o(e);i.pipe(t),i.on(`error`,e=>{t.writeHead(500),t.end(e.message)})},j=async(e,t,n,i)=>{try{let a=k((await l(e,{withFileTypes:!0})).filter(e=>!x(e.name,i.ignorePatterns)).map(e=>({name:e.name,isDirectory:e.isDirectory(),url:r(t,e.name).replace(/\\/g,`/`)})),t);i.enableLiveReload&&(a=T(a,i.port)),n.writeHead(200,{"Content-Type":`text/html`}),n.end(a)}catch{n.writeHead(500),n.end(`Error reading directory`)}},M=e=>async(t,n)=>{let a=t.headers.host||`localhost:${e.port}`,o=new URL(t.url||`/`,`http://${a}`),s=decodeURIComponent(o.pathname);if(e.enableLiveReload&&s===`/livereload`)return E(t,n);let c=i(e.directory,`.${s}`);if(!c.startsWith(e.directory))return n.writeHead(403),n.end(`Forbidden`);try{let t=await u(c);if(t.isFile())return await A(c,n,e);if(t.isDirectory()){if(e.enableDirectoryListing)return await j(c,s,n,e);{let t=r(c,`index.html`);try{return await u(t),await A(t,n,e)}catch{return n.writeHead(403),n.end(`Listing disabled`)}}}else return n.writeHead(404),n.end(`Not Found`)}catch(e){if(e&&e.code===`ENOENT`)return n.writeHead(404),n.end(`Not Found`);n.writeHead(500),n.end(e?.message||`Server error`)}},N=()=>{b(`Reloading server process...`),f(process.argv[0],process.argv.slice(1),{stdio:`inherit`,detached:!0}).unref(),process.exit(0)},P=e=>{b(`Watching: ${e.directory}`);try{s(e.directory,{recursive:!0},async(t,n)=>{if(!n||x(n,e.ignorePatterns)||_)return;_=!0,b(`Change detected: ${n} (${t})`),await S(500);let r=/\.(json|js|mjs|ts)$/.test(n),i=/\.(html|css|png|jpg|jpeg|gif|svg)$/.test(n);e.restartOnChange||r?(e.enableLiveReload&&D(`restart`),N()):e.enableLiveReload&&i&&D(`asset change`),setTimeout(()=>{_=!1},1e3)})}catch(e){b(`Watcher error: ${e?.message}`,`error`)}};(()=>{try{let e=y(process.argv.slice(2));a(M(e)).listen(e.port,()=>{b(`Server running at http://localhost:${e.port}`),b(`Root: ${e.directory}`),P(e)}),[`SIGINT`,`SIGTERM`].forEach(e=>{process.on(e,()=>{b(`Shutting down...`),process.exit(0)})})}catch(e){b(`Startup Error: ${e?.message}`,`error`),process.exit(1)}})();export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "httpath",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "A lightweight, feature-rich static file server similar to Python's `python -m http.server` but with modern Node.js features and more.",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",