From a1a7a6c8297e40a137d3dee3d5a9e9d9961ccc53 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Mon, 27 Apr 2026 08:25:34 +0100 Subject: [PATCH] forgejo: handle ifc:// markdown links in JS, eliminate required Go compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ifcurl.js: scan a[href^="ifc://"] at page load and replace with preview figures — works with [title](ifc://...) syntax without any Go extension - viewer.js: Issue button now emits [title](ifc://...) markdown link syntax instead of a bare URL, so shared views render as previews without the patch - README: document no-rebuild quick setup path; Go patch section is now optional Co-Authored-By: Claude Sonnet 4.6 --- .beads/issues.jsonl | 3 ++- README.md | 35 +++++++++++++++--------- forgejo/README.md | 23 +++++++++------- forgejo/custom/public/assets/ifcurl.js | 37 ++++++++++++++++++++++++++ forgejo/custom/public/assets/viewer.js | 3 ++- 5 files changed, 77 insertions(+), 24 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 766c7d4..4c0aa9e 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -2,6 +2,7 @@ {"id":"ifcurl-p60","title":"Viewer: no element click-to-identify — can't get GlobalId or properties","description":"There is no way to click on a mesh in the viewer and see which IFC element it represents. Reviewers can see geometry but cannot reference specific elements — they can't discover a GlobalId to put in a selector URL, can't see the element name/type/Psets, and can't say 'this specific wall' in a BCF or Forgejo comment. This breaks the collaboration workflow at its foundation: every useful feedback comment needs to identify specific elements. Need: click or hover on a surface to highlight it and show a properties panel (name, type, GlobalId, key Psets). The GlobalId should be copyable into the selector field or URL. The ThatOpen components library provides the tools for this (OBC.IfcRelationsIndexer, element property queries).","status":"closed","priority":1,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T11:02:33Z","created_by":"Bruno Postle","updated_at":"2026-04-24T11:47:23Z","started_at":"2026-04-24T11:14:57Z","closed_at":"2026-04-24T11:47:23Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-4l7","title":"viewer.html: scale= URLs don't load in orthographic mode","description":"applyCameraParam() reads the camera= and fov= parameters from the URL and applies them, but never reads scale= or switches the camera to orthographic (parallel) projection. URLs like ?camera=...\u0026scale=50 will load in perspective mode instead of orthographic. The OBC OrthoPerspectiveCamera can switch modes, but the code path to set parallel projection and apply the scale value is missing from applyCameraParam.","status":"closed","priority":1,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T09:04:51Z","created_by":"Bruno Postle","updated_at":"2026-04-24T10:56:29Z","started_at":"2026-04-24T10:50:19Z","closed_at":"2026-04-24T10:56:29Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-362","title":"Add missing server-config files: gitconfig-ifcmerge and gitattributes","description":"forgejo/README.md documents deploying two files that don't exist in the repo: forgejo/server-config/gitconfig-ifcmerge and forgejo/server-config/gitattributes. Both are required for the ifcmerge git merge driver to work in bare Forgejo repos. The README even includes 'sudo cp forgejo/server-config/gitattributes /etc/gitattributes' and 'sudo git config --system include.path .../gitconfig-ifcmerge', but the files themselves are absent. Anyone following the deployment instructions will hit a 'file not found' error. Need to create both files with the content described in the README.","status":"closed","priority":1,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T09:04:31Z","created_by":"Bruno Postle","updated_at":"2026-04-24T10:51:47Z","started_at":"2026-04-24T10:50:15Z","closed_at":"2026-04-24T10:51:47Z","close_reason":"Files already exist: forgejo/server-config/gitconfig-ifcmerge and gitattributes are both present and correct. Missed in initial review.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"id":"ifcurl-962","title":"forgejo: replace Go markdown extension with JS, eliminate required Go compile","description":"ifc_url.go is a Goldmark AST extension that requires patching and recompiling Forgejo to work. This is painful when dev environment (Fedora) differs from deployment (Docker) — compiler flags, static linking, build tags must all match. The goal is to make the ifc:// preview feature work with zero Go compilation, via pure JS asset deployment.\n\nPlan:\n1. Add querySelectorAll('a[href^=\"ifc://\"]') scanning to ifcurl.js — replace matched anchors with figure elements (preview image + viewer link + ifc:// URL). Goldmark already renders [title](ifc://...) as \u003ca href=\"ifc://...\"\u003etitle\u003c/a\u003e without any extension, so this covers all markdown link syntax.\n2. Change the Issue button in viewer.js to emit [view](ifc://...) markdown link syntax instead of a bare URL — so that user-generated issue bodies produce linkable anchors Goldmark will render, not plain text.\n3. The existing Go extension (ifc_url.go) and its markdown.go registration can be removed on the next Forgejo upgrade; the current patched binary continues to work in the meantime.\n\nAcceptance: after this change, deploying new ifcurl features only requires copying JS assets — no Forgejo rebuild needed.","status":"closed","priority":2,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-27T06:58:15Z","created_by":"Bruno Postle","updated_at":"2026-04-27T07:25:09Z","started_at":"2026-04-27T06:58:48Z","closed_at":"2026-04-27T07:25:09Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-22n","title":"viewer: add ifc:// URL and clickable image link to embedded snapshots","description":"Forgejo commit pages and pull requests embed IFC snapshots as visual diffs. Each snapshot should have two additions: (1) the ifc:// URL that addresses this exact view at the commit hash displayed as text alongside the image, and (2) the snapshot image itself should be a clickable link that opens the viewer at this view. This makes snapshots navigable and shareable.","status":"closed","priority":2,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-27T05:36:16Z","created_by":"Bruno Postle","updated_at":"2026-04-27T05:59:52Z","started_at":"2026-04-27T05:59:51Z","closed_at":"2026-04-27T05:59:52Z","close_reason":"Closed","dependencies":[{"issue_id":"ifcurl-22n","depends_on_id":"ifcurl-5a6","type":"blocks","created_at":"2026-04-27T06:37:10Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"id":"ifcurl-dkz","title":"Viewer bundle: upgrade @thatopen/components 3.3.1 → 3.4.2 to fix fast-xml-parser CVEs","description":"npm audit reports 2 critical CVEs in fast-xml-parser (a transitive dep of @thatopen/components 3.3.1). These are DoS/injection issues in XML parsing. Risk in the viewer context is low (client-side, user's own files) but should be addressed. Fix: bump @thatopen/components to 3.4.2 in forgejo/package.json, run npm install \u0026\u0026 npm run build, and commit the regenerated bundle.","status":"closed","priority":2,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-25T17:12:37Z","created_by":"Bruno Postle","updated_at":"2026-04-25T17:59:50Z","started_at":"2026-04-25T17:31:59Z","closed_at":"2026-04-25T17:59:50Z","close_reason":"Upgraded to @thatopen/components 3.4.2 + three 0.184.0 + web-ifc 0.0.77. Severity reduced from 2 critical to 1 high + 1 low. Remaining fast-xml-parser CVEs are upstream in @thatopen \u003e= 3.3.3 with no fix released yet.","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-bue","title":"Split service into ifcurl-api and ifcurl-render for RCE isolation","description":"The ifcopenshell C++ bindings are the main RCE attack surface (memory corruption on malformed IFC). The git/token code is not at risk but currently shares the same process and systemd security profile.\n\nSplit the service into two systemd units with different security profiles:\n\nifcurl-api (renamed from ifcurl-preview): handles HTTP, git fetch, caching, auth token lookup. Needs execve (git CLI via GitPython) and AF_INET/AF_INET6. No ifcopenshell.\n\nifcurl-render: handles IFC parsing and PNG rendering only. Listens on Unix socket /run/ifcurl-render/render.sock. Has SystemCallFilter ~execve ~execveat (confirmed safe: strace shows pyvista and ifcopenshell geom iterator use fork/clone only, never exec). Has RestrictAddressFamilies=AF_UNIX only. No access to /etc/ifcurl tokens. Runs as separate user ifcurl-render.\n\nCommunication: API service calls render service via httpx over the Unix socket. Both service users belong to a shared ifcurl group for socket access.\n\nInterface (three endpoints on render service):\n POST /render multipart(ifc, params JSON) → JSON {png: base64, guids: list|null}\n POST /select multipart(ifc, params JSON) → JSON {guids: list}\n POST /render_diff multipart(head_ifc, base_ifc, params JSON) → image/png binary\n\nsandbox.py crash isolation is kept inside the render service for per-request segfault containment.\n\nBackward compat: if IFCURL_RENDER_SOCKET is unset, API service falls back to direct in-process rendering (current sandbox.py path).\n\nImplementation steps:\n1. ifcurl/render_service.py — FastAPI app with the three endpoints wrapping sandboxed functions\n2. ifcurl/service.py — delegate to render service when IFCURL_RENDER_SOCKET is set\n3. ifcurl/__main__.py — add 'render-service' CLI subcommand\n4. pyproject.toml — add httpx to service optional deps\n5. forgejo/server-config/ifcurl-api.service (rename + update)\n6. forgejo/server-config/ifcurl-render.service (new, hardened unit)\n7. forgejo/README.md — update deployment docs for two-service setup\n8. tests/test_render_service.py — integration tests for the render service endpoints","acceptance_criteria":"render service cannot exec /bin/sh even with RCE (verified by strace showing no execve during normal render); normal renders still produce correct PNG; API service correctly delegates to render service when socket is configured; fallback to in-process rendering works when socket is unset","status":"closed","priority":2,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-25T08:32:27Z","created_by":"Bruno Postle","updated_at":"2026-04-25T08:57:22Z","started_at":"2026-04-25T08:32:57Z","closed_at":"2026-04-25T08:57:22Z","close_reason":"Two-service split implemented: render_service.py with /render /select /render_diff endpoints, service.py delegates via httpx when IFCURL_RENDER_SOCKET is set, render-service CLI subcommand added, two systemd units created","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -44,7 +45,7 @@ {"id":"ifcurl-ei8","title":"Fix syncCameraUrl: convert WebGL coords to IFC world space","description":"syncCameraUrl in viewer.py currently records camera.position/direction/up directly from Three.js, which are in WebGL/Three.js coordinate space (Y-up, different scale/orientation than IFC). The ifc:// URL spec expects IFC world coordinates (Z-up, matching BCF viewpoint conventions). Need to find the IFC→Three.js transform that @thatopen/components applies when loading a model, then apply its inverse in syncCameraUrl before serialising the camera params. Repos cloned for reference: ~/src/engine_components and ~/src/engine_fragment.","status":"closed","priority":2,"issue_type":"bug","owner":"bruno@postle.net","created_at":"2026-04-23T05:53:06Z","created_by":"Bruno Postle","updated_at":"2026-04-23T06:14:36Z","closed_at":"2026-04-23T06:14:36Z","close_reason":"Split into atomic tasks: ifcurl-ldk, ifcurl-j2s, ifcurl-cmd","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-i1s","title":"Test Phase 3b web viewer end-to-end","description":"After deploying the footer template (ifcurl-ach), test the full flow: open an .ifc file in Forgejo at localhost:3000, click 'View in 3D', verify the viewer loads the model. CDN versions (@thatopen/components 2.4.0, three 0.160.0, web-ifc 0.0.66) may need adjustment if the API has changed.","status":"closed","priority":2,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-23T05:48:01Z","created_by":"Bruno Postle","updated_at":"2026-04-23T20:05:04Z","started_at":"2026-04-23T19:54:56Z","closed_at":"2026-04-23T20:05:04Z","close_reason":"Closed","dependencies":[{"issue_id":"ifcurl-i1s","depends_on_id":"ifcurl-49o","type":"blocks","created_at":"2026-04-23T07:14:12Z","created_by":"Bruno Postle","metadata":"{}"},{"issue_id":"ifcurl-i1s","depends_on_id":"ifcurl-4yu","type":"blocks","created_at":"2026-04-23T07:14:11Z","created_by":"Bruno Postle","metadata":"{}"},{"issue_id":"ifcurl-i1s","depends_on_id":"ifcurl-ach","type":"blocks","created_at":"2026-04-23T06:48:10Z","created_by":"Bruno Postle","metadata":"{}"},{"issue_id":"ifcurl-i1s","depends_on_id":"ifcurl-ahd","type":"blocks","created_at":"2026-04-23T07:02:54Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":4,"dependent_count":1,"comment_count":0} {"id":"ifcurl-ach","title":"Deploy Phase 3b footer template to Forgejo","description":"The footer.tmpl file that injects a 'View in 3D' button on .ifc file pages has been written but not yet deployed. Deploy it to /etc/forgejo/templates/custom/footer.tmpl and verify the button appears when viewing an .ifc file on localhost:3000.","status":"closed","priority":2,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-04-23T05:47:53Z","created_by":"Bruno Postle","updated_at":"2026-04-23T06:14:35Z","closed_at":"2026-04-23T06:14:35Z","close_reason":"Split into atomic tasks: ifcurl-ohq, ifcurl-49o","dependencies":[{"issue_id":"ifcurl-ach","depends_on_id":"ifcurl-ahd","type":"blocks","created_at":"2026-04-23T07:02:40Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0} -{"id":"ifcurl-5u6","title":"Evaluate replacing ifc_url.go markdown extension with JS injection","description":"ifc_url.go is a Goldmark AST extension that replaces ifc:// URLs in rendered markdown with preview figure elements (img + link). This requires patching and recompiling Forgejo. A JS alternative in footer.tmpl could handle the [title](ifc://...) markdown link case by scanning document.querySelectorAll('a[href^=\"ifc://\"]') and replacing matched anchors with figure elements at runtime — no Go changes needed. The limitation: bare ifc://... text in markdown won't be auto-linked by Goldmark (unknown scheme renders as plain text), so bare URLs would need text-node scanning in JS, which is fiddly and causes a render flash. Decision: replace with JS if users can commit to always using [title](ifc://...) link syntax; keep Go extension if bare URLs in markdown are a real use case.","status":"open","priority":3,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-04-27T06:05:32Z","created_by":"Bruno Postle","updated_at":"2026-04-27T06:05:32Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"id":"ifcurl-5u6","title":"Evaluate replacing ifc_url.go markdown extension with JS injection","description":"ifc_url.go is a Goldmark AST extension that replaces ifc:// URLs in rendered markdown with preview figure elements (img + link). This requires patching and recompiling Forgejo. A JS alternative in footer.tmpl could handle the [title](ifc://...) markdown link case by scanning document.querySelectorAll('a[href^=\"ifc://\"]') and replacing matched anchors with figure elements at runtime — no Go changes needed. The limitation: bare ifc://... text in markdown won't be auto-linked by Goldmark (unknown scheme renders as plain text), so bare URLs would need text-node scanning in JS, which is fiddly and causes a render flash. Decision: replace with JS if users can commit to always using [title](ifc://...) link syntax; keep Go extension if bare URLs in markdown are a real use case.","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-27T06:05:32Z","created_by":"Bruno Postle","updated_at":"2026-04-27T06:40:02Z","started_at":"2026-04-27T06:39:11Z","closed_at":"2026-04-27T06:40:02Z","close_reason":"Keep Go extension. Issue button (viewer.js:261) emits bare ifc:// URLs into markdown, making bare-URL rendering essential. JS text-node scanning works but causes a render flash. A clean alternative would be registering ifc:// as a Goldmark autolink scheme so bare URLs become \u003ca\u003e elements JS can pick up — worth revisiting if upgrade burden grows, but not worth doing now.","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-71f","title":"Extract footer.tmpl JS into a separate ifcurl.js asset file","description":"footer.tmpl is 200 lines of JavaScript wrapped in a single \u003cscript\u003e tag. Moving it to /var/lib/forgejo/custom/public/assets/ifcurl.js (served at /assets/ifcurl.js) would reduce footer.tmpl to a one-liner \u003cscript src='/assets/ifcurl.js'\u003e\u003c/script\u003e. Benefits: proper editor syntax highlighting and linting, no HTML-escaping concerns, cleaner separation of concerns. Two files to deploy instead of one, but that's manageable.","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-27T06:05:05Z","created_by":"Bruno Postle","updated_at":"2026-04-27T06:36:55Z","started_at":"2026-04-27T06:33:57Z","closed_at":"2026-04-27T06:36:55Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-5a6","title":"viewer: wrap ifc:// text in anchor tags for future browser URL handler","description":"At some point we will register a browser protocol handler for ifc:// URLs so they can open desktop tools (Bonsai etc). In preparation, all ifc:// URLs generated alongside embedded snapshots should be wrapped in \u003ca href=\"ifc://...\"\u003e anchor tags. Currently they will not do anything (no handler registered), but having them as links now means the UI is ready when the handler exists, and users can at least see and copy the URL.","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-27T05:36:42Z","created_by":"Bruno Postle","updated_at":"2026-04-27T05:59:25Z","started_at":"2026-04-27T05:39:30Z","closed_at":"2026-04-27T05:59:25Z","close_reason":"Closed","dependency_count":0,"dependent_count":1,"comment_count":0} {"id":"ifcurl-tga","title":"docs: reorganise README to lead with Forgejo features","description":"The project is now primarily a Forgejo mod that enables IFC previews in commit and PR pages. The README should lead with what this enables for users (embedded snapshots, visual diffs, clickable model links), then move into the ifc:// URL specification as the technical foundation. The current README leads with the URL spec which buries the headline feature.","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-27T05:36:41Z","created_by":"Bruno Postle","updated_at":"2026-04-27T06:31:47Z","started_at":"2026-04-27T06:07:49Z","closed_at":"2026-04-27T06:31:47Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/README.md b/README.md index 10d4d82..0f334c8 100644 --- a/README.md +++ b/README.md @@ -59,21 +59,19 @@ A self-contained WebGL IFC viewer (`viewer.html`) served as a Forgejo asset at ` ## Forgejo integration -A source patch and set of assets for Forgejo. See [`forgejo/README.md`](forgejo/README.md) for full apply, build, and deployment instructions. +A set of JS assets and an optional Go patch for Forgejo. Most features work with +asset deployment only — no Forgejo rebuild required. See +[`forgejo/README.md`](forgejo/README.md) for full details. -### Quick setup +### Quick setup (no rebuild) + +Deploy the assets and the preview service. All features work except bare +`ifc://...` text in markdown (use `[title](ifc://...)` link syntax instead, which +the viewer's Issue button produces automatically). ```bash -# Apply the Go source patch -cp forgejo/modules/markup/markdown/ifc_url{,_test}.go /path/to/forgejo/modules/markup/markdown/ -cd /path/to/forgejo && git apply /path/to/ifcurl/forgejo/go.patch - -# Build Forgejo -go build -tags 'sqlite sqlite_unlock_notify' \ - -ldflags "-X 'forgejo.org/modules/setting.StaticRootPath=/usr/share/forgejo'" \ - -o forgejo . && sudo cp forgejo /usr/bin/forgejo - -# Deploy assets (no rebuild needed) +# Deploy assets +sudo mkdir -p /var/lib/forgejo/custom/public/assets/ /var/lib/forgejo/custom/templates/custom/ sudo cp forgejo/custom/public/assets/viewer*.* /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/custom/public/assets/ifcurl.js /var/lib/forgejo/custom/public/assets/ sudo cp forgejo/templates/custom/footer.tmpl /var/lib/forgejo/custom/templates/custom/ @@ -85,6 +83,19 @@ sudo cp forgejo/server-config/ifcurl-preview.service /etc/systemd/system/ sudo systemctl enable --now ifcurl-preview ``` +### Optional: Go patch for bare URL rendering + +To also render bare `ifc://...` text in markdown (without `[title](...)` syntax), +apply the Go patch and rebuild Forgejo: + +```bash +cp forgejo/modules/markup/markdown/ifc_url{,_test}.go /path/to/forgejo/modules/markup/markdown/ +cd /path/to/forgejo && git apply /path/to/ifcurl/forgejo/go.patch +go build -tags 'sqlite sqlite_unlock_notify' \ + -ldflags "-X 'forgejo.org/modules/setting.StaticRootPath=/usr/share/forgejo'" \ + -o forgejo . && sudo cp forgejo /usr/bin/forgejo +``` + Add to `/etc/forgejo/conf/app.ini`: ```ini diff --git a/forgejo/README.md b/forgejo/README.md index 1471b92..36ff5aa 100644 --- a/forgejo/README.md +++ b/forgejo/README.md @@ -7,19 +7,22 @@ split into two tiers by what infrastructure they require. | Feature | What it needs | Forgejo rebuild? | |---|---|---| -| "View in 3D" button on file/history pages | ifcurl service + `footer.tmpl` | No | +| "View in 3D" button on file/history pages | `ifcurl.js` + ifcurl service | No | | Browser IFC viewer | `viewer.html` static asset | No | -| PR diff 3D render | ifcurl service + `footer.tmpl` + **Nginx proxy** | No | -| ifc:// links in markdown → inline preview | Go patch + `PREVIEW_SERVICE_URL` in app.ini | **Yes** | +| PR diff 3D render | `ifcurl.js` + ifcurl service + **Nginx proxy** | No | +| `[title](ifc://...)` links in markdown → inline preview | `ifcurl.js` + ifcurl service + **Nginx proxy** | No | +| Bare `ifc://...` text in markdown → inline preview | Go patch + `PREVIEW_SERVICE_URL` in app.ini | **Yes** | -The first three features are implemented entirely as static assets (`footer.tmpl`, -`viewer.html`). They survive Forgejo upgrades without touching the Go patch and -require no Forgejo rebuild. +All features except bare-URL markdown rendering work with static asset deployment +only — no Forgejo rebuild required. `ifcurl.js` detects `` links +produced by Goldmark's standard link parser and replaces them with preview figures +at page load. -The Go patch is needed **only** for rendering ifc:// links that appear in -markdown (issue bodies, comments, wiki). Minimising the patch surface is -deliberate — every line of Go code must be re-verified against each Forgejo -release. +The Go patch is needed **only** if you want bare `ifc://...` text in markdown +(without `[title](...)` syntax) to render as a preview. The Issue button in the +viewer emits `[title](ifc://...)` link syntax, so normal issue/PR workflows work +without the patch. Minimising the patch surface is deliberate — every line of Go +code must be re-verified against each Forgejo release. The PR diff feature requires a **reverse proxy** (e.g. Nginx) to expose the ifcurl service at the same origin as Forgejo. This is because the diff image is diff --git a/forgejo/custom/public/assets/ifcurl.js b/forgejo/custom/public/assets/ifcurl.js index b161073..843fa3c 100644 --- a/forgejo/custom/public/assets/ifcurl.js +++ b/forgejo/custom/public/assets/ifcurl.js @@ -33,9 +33,46 @@ return a; } + // Replace links in rendered markdown with preview figures. + // Handles [title](ifc://...) links produced by Goldmark's standard link parser — + // no Go extension required. Skips anchors already inside .ifcurl-preview (those + // were rendered server-side by ifc_url.go when the patched binary is in use). + function replaceIfcAnchors() { + var origin = window.location.origin; + document.querySelectorAll('a[href^="ifc://"]').forEach(function(a) { + if (a.closest(".ifcurl-preview")) return; + var ifcUrl = a.getAttribute("href"); + var img = document.createElement("img"); + img.src = origin + "/preview?url=" + encodeURIComponent(ifcUrl); + img.alt = "IFC preview"; + img.loading = "lazy"; + img.style.maxWidth = "100%"; + var imgLink = document.createElement("a"); + imgLink.href = VIEWER_BASE + "?url=" + encodeURIComponent(ifcUrl); + imgLink.title = ifcUrl; + imgLink.target = "_blank"; + imgLink.rel = "noopener noreferrer"; + imgLink.appendChild(img); + var code = document.createElement("code"); + code.textContent = ifcUrl; + var urlLink = document.createElement("a"); + urlLink.href = ifcUrl; + urlLink.appendChild(code); + var caption = document.createElement("figcaption"); + caption.appendChild(urlLink); + var figure = document.createElement("figure"); + figure.className = "ifcurl-preview"; + figure.appendChild(imgLink); + figure.appendChild(caption); + a.parentNode.replaceChild(figure, a); + }); + } + function init() { var host = window.location.host; + replaceIfcAnchors(); + // ----------------------------------------------------------------------- // Case 1: single file view — permalink button in .file-header-right. // ----------------------------------------------------------------------- diff --git a/forgejo/custom/public/assets/viewer.js b/forgejo/custom/public/assets/viewer.js index 434b365..05fc3de 100644 --- a/forgejo/custom/public/assets/viewer.js +++ b/forgejo/custom/public/assets/viewer.js @@ -257,8 +257,9 @@ newIssueBtn.addEventListener("click", () => { const p = parseIfcUrl(src); if (!p || !p.host || !p.repoSuffix) return; const title = p.path ? `IFC view: ${p.path}` : "IFC view"; + const body = `[${title}](${src})`; const issueUrl = `${window.location.origin}/${p.repoSuffix}/issues/new` + - `?title=${encodeURIComponent(title)}&body=${encodeURIComponent(src)}`; + `?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`; window.open(issueUrl, "_blank", "noopener"); });