diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index b6b9517..ff0fe3b 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -104,5 +104,5 @@ {"id":"ifcurl-vam","title":"Phase 6: Bonsai offer ifc:// URL when saving linked model reference","description":"When saving an IFCDOCUMENTREFERENCE in Bonsai, offer the option to write the location as an ifc:// URL rather than a relative path. When a relative path would traverse above the repository root, prompt the user to convert it to an ifc:// URL. Requires Phase 4 federation (ifcurl-bqt).","status":"deferred","priority":4,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-04-23T05:49:16Z","created_by":"Bruno Postle","updated_at":"2026-04-23T06:15:13Z","defer_until":"2026-12-01T00:00:00Z","dependencies":[{"issue_id":"ifcurl-vam","depends_on_id":"ifcurl-bqt","type":"blocks","created_at":"2026-04-23T06:49:23Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"id":"ifcurl-9k2","title":"Phase 5: IFC Viewer integration (equivalent of Phase 4)","description":"Add ifc:// OS protocol handler and 'Copy view URL' button to the IFC Viewer desktop application, equivalent to the Bonsai Phase 4 integration. Depends on Phase 4 being complete as a reference implementation.","status":"deferred","priority":4,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-04-23T05:49:08Z","created_by":"Bruno Postle","updated_at":"2026-04-23T06:15:12Z","defer_until":"2026-12-01T00:00:00Z","dependencies":[{"issue_id":"ifcurl-9k2","depends_on_id":"ifcurl-0oj","type":"blocks","created_at":"2026-04-23T06:49:14Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"id":"ifcurl-prc","title":"Phase 3c: 3D diff view for IFC PR merges","description":"After a PR merge completes, surface a 'View merge in 3D' button using the Phase 3b viewer. Use @thatopen/components highlighter to colour added, removed, and modified elements differently, driven by comparing the merged IFC against either parent. Requires Phase 3b viewer (ifcurl-i1s) to be working.","status":"closed","priority":4,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-04-23T05:48:30Z","created_by":"Bruno Postle","updated_at":"2026-04-26T17:15:07Z","closed_at":"2026-04-26T17:15:07Z","close_reason":"Rendered diff already implemented in footer.tmpl Case 3; static PNG via /render_diff endpoint, not interactive 3D","dependencies":[{"issue_id":"ifcurl-prc","depends_on_id":"ifcurl-i1s","type":"blocks","created_at":"2026-04-23T06:48:36Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"forgejo-build-and-deploy-procedure-go-build-c","value":"Forgejo build and deploy procedure: go build -C /home/bruno/src/forgejo -tags 'sqlite sqlite_unlock_notify' -ldflags \"-X 'forgejo.org/modules/setting.StaticRootPath=/usr/share/forgejo'\" -o /home/bruno/src/forgejo/forgejo . — then sudo cp forgejo /usr/bin/forgejo. StaticRootPath=/usr/share/forgejo. CustomPath=/var/lib/forgejo/custom. Custom templates go to /var/lib/forgejo/custom/templates/custom/ (e.g. footer.tmpl → /var/lib/forgejo/custom/templates/custom/footer.tmpl). Custom static files go to /var/lib/forgejo/custom/public/assets/ (NOT /etc/forgejo/public/assets/) and are served at /assets/*."} {"_type":"memory","key":"torawurl-in-viewer-url-js-must-support-github","value":"toRawUrl in viewer-url.js must support GitHub, GitLab, Forgejo/Gitea (codeberg.org, self-hosted), and localhost. Tests in forgejo/tests/viewer-url.test.js cover all four. The localhost check (http vs https) is based on host.startsWith('localhost'), so subdomain.localhost gets https."} +{"_type":"memory","key":"forgejo-build-and-deploy-procedure-go-build-c","value":"Forgejo build and deploy procedure: go build -C /home/bruno/src/forgejo -tags 'sqlite sqlite_unlock_notify' -ldflags \"-X 'forgejo.org/modules/setting.StaticRootPath=/usr/share/forgejo'\" -o /home/bruno/src/forgejo/forgejo . — then sudo cp forgejo /usr/bin/forgejo. StaticRootPath=/usr/share/forgejo. CustomPath=/var/lib/forgejo/custom. Custom templates go to /var/lib/forgejo/custom/templates/custom/ (e.g. footer.tmpl → /var/lib/forgejo/custom/templates/custom/footer.tmpl). Custom static files go to /var/lib/forgejo/custom/public/assets/ (NOT /etc/forgejo/public/assets/) and are served at /assets/*."} diff --git a/forgejo/custom/public/assets/ifcurl.js b/forgejo/custom/public/assets/ifcurl.js index b95e9f5..2c2277f 100644 --- a/forgejo/custom/public/assets/ifcurl.js +++ b/forgejo/custom/public/assets/ifcurl.js @@ -69,10 +69,13 @@ function init() { // ----------------------------------------------------------------------- const btnGroup = document.querySelector(".file-header-right .ui.buttons"); if (btnGroup) { - // Permalink href or current URL when already at a commit. - const a = document.querySelector('a[href*="/src/commit/"]'); - const href = a ? a.getAttribute("href") : window.location.pathname; - const info = parseCommitHref(href); + // If already at a commit URL use the pathname directly; otherwise find + // the Permalink anchor (present on branch/tag file views). + let info = parseCommitHref(window.location.pathname); + if (!info) { + const a = document.querySelector('a[href*="/src/commit/"]'); + if (a) info = parseCommitHref(a.getAttribute("href")); + } if (info && info.treePath.toLowerCase().endsWith(".ifc")) { btnGroup.appendChild(makeViewerLink(info, "View in 3D")); } diff --git a/forgejo/tests/viewer-util.test.js b/forgejo/tests/viewer-util.test.js index f8572aa..91750bd 100644 --- a/forgejo/tests/viewer-util.test.js +++ b/forgejo/tests/viewer-util.test.js @@ -29,6 +29,16 @@ describe("parseCommitHref", () => { assert.equal(parseCommitHref(""), null); }); + test("returns null for a commit directory link (no file path)", () => { + // Breadcrumb anchors on a file-view page link to the directory at the + // commit, e.g. /org/repo/src/commit/{hash} with no trailing file path. + // These must not be mistaken for a file permalink. + assert.equal( + parseCommitHref("/org/repo/src/commit/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"), + null + ); + }); + test("returns null if hash is not 40 hex chars", () => { assert.equal(parseCommitHref("/org/repo/src/commit/abc123/model.ifc"), null); });