ework-web 0.10.37 → 0.10.39
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/package.json +1 -1
- package/src/config.ts +2 -2
- package/src/remote-file.ts +81 -13
- package/src/store.ts +25 -6
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -64,8 +64,8 @@ export const configSchema = z.object({
|
|
|
64
64
|
collapseLines: z.coerce.number().int().positive().default(16),
|
|
65
65
|
fileRoots: z
|
|
66
66
|
.string()
|
|
67
|
-
.
|
|
68
|
-
.
|
|
67
|
+
.default("/tmp")
|
|
68
|
+
.transform((s) => s.split(",").map((x) => x.trim()).filter(Boolean)),
|
|
69
69
|
fileMaxLines: z.coerce.number().int().positive().default(2000),
|
|
70
70
|
fileMaxBytes: z.coerce.number().int().positive().default(512 * 1024),
|
|
71
71
|
translateUrl: z.string().default(""),
|
package/src/remote-file.ts
CHANGED
|
@@ -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;
|
|
@@ -50,7 +82,8 @@ async function fetchDaemon(
|
|
|
50
82
|
apiPath: string,
|
|
51
83
|
params: Record<string, string>
|
|
52
84
|
): Promise<Response> {
|
|
53
|
-
const
|
|
85
|
+
const base = /^https?:\/\//.test(endpoint) ? endpoint : `http://${endpoint}`;
|
|
86
|
+
const url = new URL(`${base}${apiPath}`);
|
|
54
87
|
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
|
|
55
88
|
const resp = await fetch(url.toString(), { signal: AbortSignal.timeout(10_000) });
|
|
56
89
|
return resp;
|
|
@@ -153,31 +186,66 @@ function renderFileContent(
|
|
|
153
186
|
const enc = encodeURIComponent;
|
|
154
187
|
const daemonParam = `&daemon=${enc(endpoint)}`;
|
|
155
188
|
const user = viewerLogin ? { login: viewerLogin, is_admin: 0 } : undefined;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
189
|
+
|
|
190
|
+
const ext = (data.path.split(".").pop() || "").toLowerCase();
|
|
191
|
+
const isMd = ext === "md" || ext === "markdown";
|
|
192
|
+
const lang = extToLang(data.path);
|
|
193
|
+
const fullText = data.rows.map((r) => r.t).join("\n");
|
|
194
|
+
const shownBytes = data.rows.reduce((s, r) => s + r.t.length + 1, 0);
|
|
195
|
+
|
|
196
|
+
let body: string;
|
|
197
|
+
if (isMd) {
|
|
198
|
+
body = `<div class="md-render">${renderMarkdown(fullText, "")}</div>`;
|
|
199
|
+
} else if (lang && shownBytes <= 100000) {
|
|
200
|
+
const nums = data.rows.map((r) => r.n).join("\n");
|
|
201
|
+
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>`;
|
|
202
|
+
} else {
|
|
203
|
+
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>`;
|
|
204
|
+
}
|
|
159
205
|
|
|
160
206
|
return `<!doctype html>
|
|
161
207
|
<html lang="zh"><head><meta charset="utf-8">
|
|
162
208
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
163
209
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
164
210
|
<title>ework-web · ${escapeHtml(data.path)}</title>
|
|
211
|
+
<link rel="stylesheet" href="/static/highlight.css">
|
|
165
212
|
<style>${THEME_CSS}
|
|
166
213
|
.nav{display:flex;align-items:center;gap:.5rem;padding:.55rem 1rem;background:var(--header-bg);color:var(--header-text);font-size:13px}
|
|
167
214
|
.nav a{color:var(--header-text);opacity:.95}
|
|
168
|
-
.wrap{max-width:
|
|
215
|
+
.wrap{max-width:1100px;margin:0 auto;padding:1rem}
|
|
169
216
|
h1{font-size:14px;margin:0 0 .3rem;font-family:ui-monospace,monospace;word-break:break-all}
|
|
170
217
|
.remote-badge{display:inline-block;background:#1f6feb33;color:#58a6ff;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;margin-left:.5rem}
|
|
171
218
|
.note{color:var(--text-muted);font-size:12px;margin:0 0 .5rem}
|
|
172
219
|
.parent-link{font-size:13px;margin-bottom:.4rem}
|
|
173
220
|
.parent-link a{color:var(--text-muted)}
|
|
174
|
-
.
|
|
175
|
-
.
|
|
176
|
-
.
|
|
177
|
-
.
|
|
178
|
-
.
|
|
179
|
-
.
|
|
180
|
-
.
|
|
221
|
+
.fv-code{display:flex;border:1px solid var(--border);border-radius:8px;overflow:auto;font-size:12.5px;line-height:1.5}
|
|
222
|
+
.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}
|
|
223
|
+
.fv-src{margin:0;flex:1;overflow:auto;background:var(--bg-elev);padding:.6rem .8rem;border:none}
|
|
224
|
+
.fv-src code{background:none;padding:0;white-space:pre;font-size:12.5px}
|
|
225
|
+
.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}
|
|
226
|
+
.fv-plain .ln{display:flex}
|
|
227
|
+
.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)}
|
|
228
|
+
.fv-plain .lnc{padding:0 .8rem;white-space:pre;overflow-wrap:anywhere}
|
|
229
|
+
.md-render{max-width:820px;margin:0 auto;padding:1rem 1.5rem 5rem;font-size:14.5px;line-height:1.7;color:var(--text)}
|
|
230
|
+
.md-render h1,.md-render h2,.md-render h3,.md-render h4{line-height:1.3;margin:1.4em 0 .6em;font-weight:600}
|
|
231
|
+
.md-render h1{font-size:1.7em;border-bottom:1px solid var(--border);padding-bottom:.3em}
|
|
232
|
+
.md-render h2{font-size:1.4em;border-bottom:1px solid var(--border);padding-bottom:.3em}
|
|
233
|
+
.md-render h3{font-size:1.2em}
|
|
234
|
+
.md-render h4{font-size:1.05em}
|
|
235
|
+
.md-render p{margin:.6em 0}
|
|
236
|
+
.md-render ul,.md-render ol{margin:.6em 0;padding-left:1.6em}
|
|
237
|
+
.md-render li{margin:.25em 0}
|
|
238
|
+
.md-render a{color:var(--accent);text-decoration:none}
|
|
239
|
+
.md-render a:hover{text-decoration:underline}
|
|
240
|
+
.md-render code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.88em;background:var(--bg-muted);padding:.15em .35em;border-radius:4px}
|
|
241
|
+
.md-render pre{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:.7rem 1rem;overflow:auto;margin:.8em 0}
|
|
242
|
+
.md-render pre code{background:none;padding:0;font-size:13px}
|
|
243
|
+
.md-render blockquote{border-left:3px solid var(--border);margin:.8em 0;padding:.2em 0 .2em 1em;color:var(--text-muted)}
|
|
244
|
+
.md-render table{border-collapse:collapse;margin:.8em 0;width:100%}
|
|
245
|
+
.md-render th,.md-render td{border:1px solid var(--border);padding:.4em .7em;text-align:left}
|
|
246
|
+
.md-render th{background:var(--bg-muted);font-weight:600}
|
|
247
|
+
.md-render img{max-width:100%}
|
|
248
|
+
.md-render hr{border:none;border-top:1px solid var(--border);margin:1.4em 0}
|
|
181
249
|
</style></head><body>
|
|
182
250
|
<header class="nav"><a href="/" style="color:var(--header-text)">🏠 ework-web</a></header>
|
|
183
251
|
${tabNavHTML("sessions", user)}
|
|
@@ -185,7 +253,7 @@ ${tabNavHTML("sessions", user)}
|
|
|
185
253
|
<div class="parent-link">📁 <a href="/file?path=${enc(data.path.split("/").slice(0, -1).join("/") || "/")}${daemonParam}">..</a></div>
|
|
186
254
|
<h1>${escapeHtml(data.path)}<span class="remote-badge">远程 ${daemonLabel}</span></h1>
|
|
187
255
|
${data.note ? `<p class="note">${escapeHtml(data.note)}</p>` : ""}
|
|
188
|
-
|
|
256
|
+
${body}
|
|
189
257
|
</main></body></html>`;
|
|
190
258
|
}
|
|
191
259
|
|
package/src/store.ts
CHANGED
|
@@ -5,6 +5,22 @@
|
|
|
5
5
|
import { getDB } from "./db";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
|
|
8
|
+
let _visibilityChecked = false;
|
|
9
|
+
let _hasVisibilityColumn = true;
|
|
10
|
+
|
|
11
|
+
async function visibilitySupported(): Promise<boolean> {
|
|
12
|
+
if (_visibilityChecked) return _hasVisibilityColumn;
|
|
13
|
+
_visibilityChecked = true;
|
|
14
|
+
try {
|
|
15
|
+
await getDB().get("SELECT visibility FROM {{projects}} LIMIT 0");
|
|
16
|
+
_hasVisibilityColumn = true;
|
|
17
|
+
} catch {
|
|
18
|
+
_hasVisibilityColumn = false;
|
|
19
|
+
console.warn("[store] projects.visibility column missing — treating all projects as public");
|
|
20
|
+
}
|
|
21
|
+
return _hasVisibilityColumn;
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
export type UserKind = "human" | "bot" | "system";
|
|
9
25
|
|
|
10
26
|
export interface UserRow {
|
|
@@ -181,12 +197,15 @@ export async function listProjectsWithCounts(
|
|
|
181
197
|
): Promise<ProjectWithCounts[]> {
|
|
182
198
|
const isAdmin = viewer?.is_admin === 1;
|
|
183
199
|
const login = viewer?.login;
|
|
184
|
-
const
|
|
200
|
+
const hasVis = isAdmin ? true : await visibilitySupported();
|
|
201
|
+
const visibilityFilter = !hasVis
|
|
185
202
|
? ""
|
|
186
|
-
:
|
|
187
|
-
?
|
|
188
|
-
:
|
|
189
|
-
|
|
203
|
+
: isAdmin
|
|
204
|
+
? ""
|
|
205
|
+
: login
|
|
206
|
+
? ` AND (p.visibility != 'private' OR EXISTS (SELECT 1 FROM {{project_members}} pm WHERE pm.project_id = p.id AND pm.user_login = ?))`
|
|
207
|
+
: ` AND p.visibility != 'private'`;
|
|
208
|
+
const args = hasVis && !isAdmin && login ? [login] : [];
|
|
190
209
|
return await getDB().all<ProjectWithCounts>(
|
|
191
210
|
`SELECT p.*,
|
|
192
211
|
COALESCE(SUM(CASE WHEN i.state = 'open' THEN 1 ELSE 0 END), 0) AS open_count,
|
|
@@ -404,7 +423,7 @@ export async function listAllIssues(opts: ListIssuesOpts = {}): Promise<IssueWit
|
|
|
404
423
|
sql += " AND EXISTS (SELECT 1 FROM {{issue_labels}} il JOIN {{labels}} l ON l.id = il.label_id WHERE il.issue_id = i.id AND l.name = ?)";
|
|
405
424
|
args.push(label);
|
|
406
425
|
}
|
|
407
|
-
if (!opts.viewerIsAdmin) {
|
|
426
|
+
if (!opts.viewerIsAdmin && await visibilitySupported()) {
|
|
408
427
|
sql += " AND (p.visibility != 'private'";
|
|
409
428
|
if (opts.viewerLogin) {
|
|
410
429
|
sql += " OR EXISTS (SELECT 1 FROM {{project_members}} pm WHERE pm.project_id = p.id AND pm.user_login = ?)";
|