ework-web 0.10.121 → 0.10.123

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.121",
3
+ "version": "0.10.123",
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",
package/src/fileview.ts CHANGED
@@ -472,17 +472,29 @@ function buildDownloadView(cfg: Config, rp: string): { html: string } {
472
472
  return { html };
473
473
  }
474
474
 
475
- export // Repo-relative refs (figures/x.png) in /file-rendered markdown 404 against /file;
476
- // rewritten targets re-pass the allowlist+denylist gate at request time.
477
- const REL_REF_RE = /(\s(?:src|href)=")([^"]+)(")/g;
475
+ export // Repo-relative refs (figures/x.png) in /file-rendered markdown 404 against /file
476
+ // (and DOMPurify strips relative src entirely); rewriting at markdown-source level
477
+ // yields rooted /file URLs that survive sanitization and re-pass the gate at request time.
478
+ const MD_REF_RE = /(\]\()([^)\s]+)([)\s])/g;
478
479
  const SKIP_REF_RE = /^(?:[a-z][a-z0-9+.-]*:|\/|#)/i;
479
480
 
480
- export function rewriteRelativeRefs(html: string, dir: string): string {
481
- return html.replace(REL_REF_RE, (full: string, pre: string, ref: string, post: string) => {
482
- if (SKIP_REF_RE.test(ref)) return full;
481
+ export function rewriteRelativeMdRefs(md: string, dir: string): string {
482
+ let out = "";
483
+ let last = 0;
484
+ for (const m of md.matchAll(/^(```|~~~)[^\n]*\n[\s\S]*?^\1[^\n]*$/gm)) {
485
+ out += rewriteRefs(md.slice(last, m.index!), dir) + m[0];
486
+ last = m.index! + m[0].length;
487
+ }
488
+ return out + rewriteRefs(md.slice(last), dir);
489
+ }
490
+
491
+ function rewriteRefs(text: string, dir: string): string {
492
+ return text.replace(MD_REF_RE, (full: string, pre: string, ref: string, post: string) => {
493
+ if (SKIP_REF_RE.test(ref) || ref.startsWith("/file")) return full;
483
494
  const abs = resolve(dir, ref);
484
- const q = encodeURIComponent(abs);
485
- return pre + (pre.includes("src") ? `/file/raw?path=${q}` : `/file?path=${q}`) + post;
495
+ const media = /\.(?:png|jpe?g|gif|webp|bmp|svg|mp3|mp4|webm|ogg|wav|pdf)$/i.test(abs);
496
+ const url = `${media ? "/file/raw" : "/file"}?path=${encodeURIComponent(abs)}`;
497
+ return `${pre}${url}${post}`;
486
498
  });
487
499
  }
488
500
 
@@ -521,7 +533,7 @@ export function buildFileView(
521
533
  const fullText = chunk.rows.map((r) => r.t).join("\n");
522
534
  const dir = dirname(rawPath);
523
535
  const body = mdRender
524
- ? `<div class="md-render">${rewriteRelativeRefs(renderMarkdown(fullText, dir), dir)}</div>`
536
+ ? `<div class="md-render">${renderMarkdown(rewriteRelativeMdRefs(fullText, dir), dir)}</div>`
525
537
  : lang && shownBytes <= 100000
526
538
  ? renderHighlighted(chunk, lang)
527
539
  : `<pre><code>${chunk.rows.map((r) => lineRow(r.t, r.n)).join("")}</code></pre>`;
package/src/index.ts CHANGED
@@ -82,6 +82,7 @@ import {
82
82
  getUpstreamSync,
83
83
  type ProjectRole,
84
84
  type UserRow,
85
+ getIssueAiStatusByNumber,
85
86
  } from "./store";
86
87
  import { startUpstreamSyncPoller } from "./upstream-sync";
87
88
  import {
@@ -2515,7 +2516,7 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
2515
2516
  }
2516
2517
  const since = url.searchParams.get("since") ?? new Date(0).toISOString();
2517
2518
  const views = await fetchIssueSince(owner, repo, number, since);
2518
- return json({ comments: views });
2519
+ return json({ comments: views, aiStatus: await getIssueAiStatusByNumber(owner, repo, number) });
2519
2520
  } catch (e) {
2520
2521
  return json({ error: errMsg(e) }, e instanceof StoreError ? e.status : 500);
2521
2522
  }
package/src/static/app.js CHANGED
@@ -562,10 +562,29 @@
562
562
  }
563
563
 
564
564
  let pollTimer = null;
565
+ const BADGES = {
566
+ "": ["ai-idle", "💤 空闲"],
567
+ processing: ["ai-processing", "🔄 AI 处理中"],
568
+ queued: ["ai-queued", "⏳ 排队中"],
569
+ completed: ["ai-completed", "✅ AI 已完成"],
570
+ failed: ["ai-failed", "⚠️ AI 失败"],
571
+ halted: ["ai-halted", "⏹ 已停止"],
572
+ dispatch_off: ["ai-dispatch-off", "🔕 不接单中"],
573
+ };
574
+ function syncBadge(status) {
575
+ const el = document.getElementById("aiStatusBadge");
576
+ if (!el || status === undefined || status === null) return;
577
+ if (el.dataset.status === status) return;
578
+ const m = BADGES[status] || BADGES[""];
579
+ el.dataset.status = status;
580
+ el.className = "ai-badge " + m[0];
581
+ el.textContent = m[1];
582
+ }
565
583
  async function poll() {
566
584
  try {
567
585
  const data = await api("since", { since: state.sinceISO });
568
586
  if (!data || data.error) return;
587
+ syncBadge(data.aiStatus);
569
588
  const views = data.comments || [];
570
589
  if (!views.length) return;
571
590
  mergeFront(views, true);
package/src/store.ts CHANGED
@@ -528,6 +528,14 @@ export async function updateIssueAiStatus(issueId: number, status: string): Prom
528
528
  await getDB().run("UPDATE {{issues}} SET ai_status = ? WHERE id = ?", [status, issueId]);
529
529
  }
530
530
 
531
+ export async function getIssueAiStatusByNumber(owner: string, repo: string, number: number): Promise<string> {
532
+ const row = await getDB().get<{ ai_status: string }>(
533
+ "SELECT i.ai_status FROM {{issues}} i JOIN {{projects}} p ON i.project_id = p.id WHERE p.owner = ? AND p.name = ? AND i.number = ?",
534
+ [owner, repo, number],
535
+ );
536
+ return row?.ai_status ?? "";
537
+ }
538
+
531
539
  export async function getIssueAiStatus(issueId: number): Promise<string> {
532
540
  const row = await getDB().get<{ ai_status: string }>("SELECT ai_status FROM {{issues}} WHERE id = ?", [issueId]);
533
541
  return row?.ai_status ?? "";