ework-web 0.10.120 → 0.10.122

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/fileview.ts +29 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.120",
3
+ "version": "0.10.122",
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
@@ -1,5 +1,5 @@
1
1
  import { realpathSync, statSync, readFileSync, openSync, readSync, closeSync, readdirSync } from "fs";
2
- import { isAbsolute, relative, join, dirname } from "path";
2
+ import { isAbsolute, relative, join, dirname, resolve } from "path";
3
3
  import hljs from "highlight.js";
4
4
  import { THEME_CSS, escapeHtml, escapeAttr } from "./render/layout";
5
5
  import { renderMarkdown } from "./render/markdown";
@@ -472,6 +472,32 @@ 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
+ // (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;
479
+ const SKIP_REF_RE = /^(?:[a-z][a-z0-9+.-]*:|\/|#)/i;
480
+
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;
494
+ const abs = resolve(dir, ref);
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}`;
498
+ });
499
+ }
500
+
475
501
  export function buildFileView(
476
502
  cfg: Config,
477
503
  rawPath: string,
@@ -505,8 +531,9 @@ export function buildFileView(
505
531
  const lang = extToLang(rawPath);
506
532
  const shownBytes = chunk.rows.reduce((s, r) => s + r.t.length + 1, 0);
507
533
  const fullText = chunk.rows.map((r) => r.t).join("\n");
534
+ const dir = dirname(rawPath);
508
535
  const body = mdRender
509
- ? `<div class="md-render">${renderMarkdown(fullText, "")}</div>`
536
+ ? `<div class="md-render">${renderMarkdown(rewriteRelativeMdRefs(fullText, dir), dir)}</div>`
510
537
  : lang && shownBytes <= 100000
511
538
  ? renderHighlighted(chunk, lang)
512
539
  : `<pre><code>${chunk.rows.map((r) => lineRow(r.t, r.n)).join("")}</code></pre>`;