ework-web 0.10.38 → 0.10.40

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/remote-file.ts +93 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.38",
3
+ "version": "0.10.40",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
@@ -1,4 +1,36 @@
1
1
  import { THEME_CSS, escapeHtml, tabNavHTML } from "./render/layout";
2
+ import hljs from "highlight.js";
3
+ import { renderMarkdown } from "./render/markdown";
4
+
5
+ // Extension → hljs language map (mirrors fileview.ts for parity).
6
+ const EXT_LANG: Record<string, string> = {
7
+ js: "javascript", mjs: "javascript", cjs: "javascript", jsx: "javascript",
8
+ ts: "typescript", tsx: "typescript", mts: "typescript", cts: "typescript",
9
+ py: "python", rb: "ruby", go: "go", rs: "rust", java: "java", kt: "kotlin",
10
+ c: "c", h: "c", cpp: "cpp", cc: "cpp", cxx: "cpp", hpp: "cpp", hxx: "cpp",
11
+ cs: "csharp", php: "php", swift: "swift", scala: "scala", sh: "bash",
12
+ bash: "bash", zsh: "bash", fish: "bash", yml: "yaml", yaml: "yaml",
13
+ json: "json", json5: "json", toml: "toml", xml: "xml", html: "xml",
14
+ htm: "xml", css: "css", scss: "scss", less: "less", sql: "sql",
15
+ dockerfile: "dockerfile", makefile: "makefile", lua: "lua", pl: "perl",
16
+ };
17
+
18
+ function extToLang(p: string): string {
19
+ const base = (p.split("/").pop() || "").toLowerCase();
20
+ if (base === "dockerfile" || base.endsWith(".dockerfile")) return "dockerfile";
21
+ if (base === "makefile" || base.endsWith(".mk")) return "makefile";
22
+ const m = base.match(/\.([a-z0-9]+)$/);
23
+ const ext = m && m[1];
24
+ return ext ? (EXT_LANG[ext] || "") : "";
25
+ }
26
+
27
+ function hljsHighlight(text: string, lang: string): string {
28
+ try {
29
+ return hljs.highlight(text, { language: lang }).value;
30
+ } catch {
31
+ return escapeHtml(text);
32
+ }
33
+ }
2
34
 
3
35
  export class RemoteFileError extends Error {
4
36
  status: number;
@@ -86,7 +118,7 @@ export async function browseRemoteFile(
86
118
  throw new RemoteFileError(err.error ?? `daemon returned ${readResp.status}`, readResp.status);
87
119
  }
88
120
  const data = (await readResp.json()) as FileContent;
89
- return { html: renderFileContent(data, endpoint, daemonLabel, viewerLogin) };
121
+ return { html: renderFileContent(data, rawPath, endpoint, daemonLabel, viewerLogin) };
90
122
  }
91
123
 
92
124
  function renderDirListing(
@@ -147,6 +179,7 @@ ${tabNavHTML("sessions", user)}
147
179
 
148
180
  function renderFileContent(
149
181
  data: FileContent,
182
+ rawPath: string,
150
183
  endpoint: string,
151
184
  daemonLabel: string,
152
185
  viewerLogin?: string
@@ -154,31 +187,78 @@ function renderFileContent(
154
187
  const enc = encodeURIComponent;
155
188
  const daemonParam = `&daemon=${enc(endpoint)}`;
156
189
  const user = viewerLogin ? { login: viewerLogin, is_admin: 0 } : undefined;
157
- const lines = data.rows.map((r) =>
158
- `<div class="l"><span class="ln">${r.n}</span><span class="lt">${escapeHtml(r.t)}</span></div>`
159
- ).join("");
190
+
191
+ const ext = (rawPath.split(".").pop() || "").toLowerCase();
192
+ const isMd = ext === "md" || ext === "markdown";
193
+ const lang = extToLang(rawPath);
194
+ const fullText = data.rows.map((r) => r.t).join("\n");
195
+ const shownBytes = data.rows.reduce((s, r) => s + r.t.length + 1, 0);
196
+
197
+ let body: string;
198
+ if (isMd) {
199
+ body = `<div class="md-render">${renderMarkdown(fullText, "")}</div>`;
200
+ } else if (lang && shownBytes <= 256000) {
201
+ const nums = data.rows.map((r) => r.n).join("\n");
202
+ body = `<div class="fv-code"><pre class="fv-gutter">${escapeHtml(nums)}</pre><pre class="fv-src"><code class="hljs language-${escapeHtml(lang)}">${hljsHighlight(fullText, lang)}</code></pre></div>`;
203
+ } else {
204
+ body = `<pre class="fv-plain"><code>${data.rows.map((r) => `<span class="ln"><span class="lnn">${r.n}</span><span class="lnc">${escapeHtml(r.t)}</span></span>`).join("")}</code></pre>`;
205
+ }
160
206
 
161
207
  return `<!doctype html>
162
208
  <html lang="zh"><head><meta charset="utf-8">
163
209
  <meta name="viewport" content="width=device-width,initial-scale=1">
164
210
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
165
211
  <title>ework-web · ${escapeHtml(data.path)}</title>
212
+ <link rel="stylesheet" href="/static/highlight.css">
166
213
  <style>${THEME_CSS}
214
+ .hljs{color:var(--text);background:transparent}
215
+ .hljs-comment,.hljs-quote{color:var(--text-muted);font-style:italic}
216
+ .hljs-keyword,.hljs-selector-tag,.hljs-built_in,.hljs-type{color:#d73a49;font-weight:600}
217
+ .hljs-string,.hljs-attr,.hljs-template-tag,.hljs-addition{color:#032f62}
218
+ .hljs-number,.hljs-literal,.hljs-variable,.hljs-template-variable{color:#005cc5}
219
+ .hljs-title,.hljs-section,.hljs-name,.hljs-function .hljs-title{color:#6f42c1;font-weight:600}
220
+ .hljs-tag{color:var(--text-muted)}
221
+ .hljs-attribute{color:#005cc5}
222
+ .hljs-regexp,.hljs-link{color:#032f62}
223
+ .hljs-deletion{color:#b31d28;background-color:#ffeef0}
224
+ .hljs-addition{background-color:#f0fff4}
225
+ @media(prefers-color-scheme:dark){.hljs-keyword,.hljs-selector-tag,.hljs-built_in,.hljs-type{color:#ff7b72}.hljs-string,.hljs-attr,.hljs-template-tag{color:#a5d6ff}.hljs-number,.hljs-literal,.hljs-variable{color:#79c0ff}.hljs-title,.hljs-section,.hljs-name,.hljs-function .hljs-title{color:#d2a8ff}.hljs-comment,.hljs-quote{color:#8b949e}.hljs-deletion{color:#ffa198;background:#67060c}.hljs-addition{background:#033a16}}
167
226
  .nav{display:flex;align-items:center;gap:.5rem;padding:.55rem 1rem;background:var(--header-bg);color:var(--header-text);font-size:13px}
168
227
  .nav a{color:var(--header-text);opacity:.95}
169
- .wrap{max-width:1000px;margin:0 auto;padding:1rem}
228
+ .wrap{max-width:1100px;margin:0 auto;padding:1rem}
170
229
  h1{font-size:14px;margin:0 0 .3rem;font-family:ui-monospace,monospace;word-break:break-all}
171
230
  .remote-badge{display:inline-block;background:#1f6feb33;color:#58a6ff;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;margin-left:.5rem}
172
231
  .note{color:var(--text-muted);font-size:12px;margin:0 0 .5rem}
173
232
  .parent-link{font-size:13px;margin-bottom:.4rem}
174
233
  .parent-link a{color:var(--text-muted)}
175
- .fview{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;overflow:auto;max-height:80vh;font-size:12.5px;font-family:ui-monospace,monospace}
176
- .l{display:flex}
177
- .ln{color:var(--text-muted);min-width:3.5rem;text-align:right;padding:0 .5rem;user-select:none;border-right:1px solid var(--border)}
178
- .lt{padding:0 .5rem;white-space:pre-wrap;word-break:break-all;flex:1}
179
- .tabs{display:flex;gap:.3rem;padding:.5rem 1rem;border-bottom:1px solid var(--border);font-size:13px}
180
- .tab{padding:.3rem .7rem;border-radius:6px 6px 0 0;text-decoration:none;color:var(--text-muted)}
181
- .tab.active{background:var(--accent);color:#fff}
234
+ .fv-code{display:flex;border:1px solid var(--border);border-radius:8px;overflow:auto;font-size:12.5px;line-height:1.5}
235
+ .fv-gutter{background:var(--bg-muted);color:var(--text-muted);text-align:right;padding:.6rem .7rem;user-select:none;white-space:pre;flex-shrink:0;border:none;margin:0}
236
+ .fv-src{margin:0;flex:1;overflow:auto;background:var(--bg-elev);padding:.6rem .8rem;border:none}
237
+ .fv-src code{background:none;padding:0;white-space:pre;font-size:12.5px}
238
+ .fv-plain{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:.6rem 0;overflow:auto;font-size:12.5px;line-height:1.5}
239
+ .fv-plain .ln{display:flex}
240
+ .fv-plain .lnn{user-select:none;text-align:right;color:var(--text-muted);padding:0 .7rem 0 .8rem;min-width:3.5em;flex-shrink:0;border-right:1px solid var(--border);background:var(--bg-muted)}
241
+ .fv-plain .lnc{padding:0 .8rem;white-space:pre;overflow-wrap:anywhere}
242
+ .md-render{max-width:820px;margin:0 auto;padding:1rem 1.5rem 5rem;font-size:14.5px;line-height:1.7;color:var(--text)}
243
+ .md-render h1,.md-render h2,.md-render h3,.md-render h4{line-height:1.3;margin:1.4em 0 .6em;font-weight:600}
244
+ .md-render h1{font-size:1.7em;border-bottom:1px solid var(--border);padding-bottom:.3em}
245
+ .md-render h2{font-size:1.4em;border-bottom:1px solid var(--border);padding-bottom:.3em}
246
+ .md-render h3{font-size:1.2em}
247
+ .md-render h4{font-size:1.05em}
248
+ .md-render p{margin:.6em 0}
249
+ .md-render ul,.md-render ol{margin:.6em 0;padding-left:1.6em}
250
+ .md-render li{margin:.25em 0}
251
+ .md-render a{color:var(--accent);text-decoration:none}
252
+ .md-render a:hover{text-decoration:underline}
253
+ .md-render code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.88em;background:var(--bg-muted);padding:.15em .35em;border-radius:4px}
254
+ .md-render pre{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:.7rem 1rem;overflow:auto;margin:.8em 0}
255
+ .md-render pre code{background:none;padding:0;font-size:13px}
256
+ .md-render blockquote{border-left:3px solid var(--border);margin:.8em 0;padding:.2em 0 .2em 1em;color:var(--text-muted)}
257
+ .md-render table{border-collapse:collapse;margin:.8em 0;width:100%}
258
+ .md-render th,.md-render td{border:1px solid var(--border);padding:.4em .7em;text-align:left}
259
+ .md-render th{background:var(--bg-muted);font-weight:600}
260
+ .md-render img{max-width:100%}
261
+ .md-render hr{border:none;border-top:1px solid var(--border);margin:1.4em 0}
182
262
  </style></head><body>
183
263
  <header class="nav"><a href="/" style="color:var(--header-text)">🏠 ework-web</a></header>
184
264
  ${tabNavHTML("sessions", user)}
@@ -186,7 +266,7 @@ ${tabNavHTML("sessions", user)}
186
266
  <div class="parent-link">📁 <a href="/file?path=${enc(data.path.split("/").slice(0, -1).join("/") || "/")}${daemonParam}">..</a></div>
187
267
  <h1>${escapeHtml(data.path)}<span class="remote-badge">远程 ${daemonLabel}</span></h1>
188
268
  ${data.note ? `<p class="note">${escapeHtml(data.note)}</p>` : ""}
189
- <div class="fview">${lines}</div>
269
+ ${body}
190
270
  </main></body></html>`;
191
271
  }
192
272