diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl
index b2443e82c4..bf668e1347 100644
--- a/templates/repo/view_file.tmpl
+++ b/templates/repo/view_file.tmpl
@@ -135,7 +135,7 @@
{{range $idx, $code := .FileContent}}
{{$line := Eval $idx "+" 1}}
- |
+ |
{{if $.EscapeStatus.Escaped}}
{{if (index $.LineEscapeStatus $idx).Escaped}}{{end}} |
{{end}}
diff --git a/tests/e2e/declare_repos_test.go b/tests/e2e/declare_repos_test.go
index f45687651c..83ee40c71a 100644
--- a/tests/e2e/declare_repos_test.go
+++ b/tests/e2e/declare_repos_test.go
@@ -53,6 +53,10 @@ func DeclareGitRepos(t *testing.T) func() {
CommitMsg: "Another commit which mentions @user1 in the title\nand @user2 in the text",
},
}),
+ newRepo(t, 2, "unicode-escaping", []FileChanges{{
+ Filename: "a-file",
+ Versions: []string{"{a}{а}"},
+ }}),
// add your repo declarations here
}
diff --git a/tests/e2e/repo-code.test.e2e.ts b/tests/e2e/repo-code.test.e2e.ts
index 11b710c956..9184c3ce67 100644
--- a/tests/e2e/repo-code.test.e2e.ts
+++ b/tests/e2e/repo-code.test.e2e.ts
@@ -1,7 +1,13 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// @watch start
-// web_src/js/features/repo-code.js
-// web_src/css/repo.css
// services/gitdiff/**
+// templates/repo/view_file.tmpl
+// web_src/css/repo.css
+// web_src/css/repo/file-view.css
+// web_src/js/features/repo-code.js
+// web_src/js/features/repo-unicode-escape.js
// @watch end
import {expect, type Page} from '@playwright/test';
@@ -16,7 +22,7 @@ async function assertSelectedLines(page: Page, nums: string[]) {
.toStrictEqual(nums);
// the first line selected has an action button
- if (nums.length > 0) await expect(page.locator(`#L${nums[0]} .code-line-button`)).toBeVisible();
+ if (nums.length > 0) await expect(page.locator(`.lines-num:has(#L${nums[0]}) .code-line-button`)).toBeVisible();
};
await pageAssertions();
@@ -99,3 +105,25 @@ test.describe('As authenticated user', () => {
await save_visual(page);
});
});
+
+test('Unicode escape highlight', async ({page}) => {
+ const unselectedBg = 'rgba(0, 0, 0, 0)';
+ const selectedBg = 'rgb(255, 237, 213)';
+
+ const response = await page.goto('/user2/unicode-escaping/src/branch/main/a-file');
+ expect(response?.status()).toBe(200);
+
+ await expect(page.locator('.unicode-escape-prompt')).toBeVisible();
+ expect(await page.locator('.lines-num').evaluate((el) => getComputedStyle(el).backgroundColor)).toBe(unselectedBg);
+ expect(await page.locator('.lines-escape').evaluate((el) => getComputedStyle(el).backgroundColor)).toBe(unselectedBg);
+ expect(await page.locator('.lines-code').evaluate((el) => getComputedStyle(el).backgroundColor)).toBe(unselectedBg);
+
+ await page.locator('#L1').click();
+ expect(await page.locator('.lines-num').evaluate((el) => getComputedStyle(el).backgroundColor)).toBe(selectedBg);
+ expect(await page.locator('.lines-escape').evaluate((el) => getComputedStyle(el).backgroundColor)).toBe(selectedBg);
+ expect(await page.locator('.lines-code').evaluate((el) => getComputedStyle(el).backgroundColor)).toBe(selectedBg);
+
+ await page.locator('.code-line-button').click();
+ await expect(page.locator('.tippy-box .view_git_blame[href$="/a-file#L1"]')).toBeVisible();
+ await expect(page.locator('.tippy-box .copy-line-permalink[data-url$="/a-file#L1"]')).toBeVisible();
+});
diff --git a/web_src/css/base.css b/web_src/css/base.css
index a962ea031a..bc9c8d3f39 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -1119,41 +1119,6 @@ svg.text.purple,
padding-left: 1ch;
}
-.lines-escape {
- width: 0;
-}
-
-.lines-code {
- padding-left: 5px;
-}
-
-.file-view tr.active {
- color: inherit !important;
- background: inherit !important;
-}
-
-.file-view tr.active .lines-num,
-.file-view tr.active .lines-code {
- background: var(--color-highlight-bg) !important;
-}
-
-.file-view tr.active:last-of-type .lines-code {
- border-bottom-right-radius: var(--border-radius);
-}
-
-.file-view tr.active .lines-num {
- position: relative;
-}
-
-.file-view tr.active .lines-num::before {
- content: "";
- position: absolute;
- left: 0;
- width: 2px;
- height: 100%;
- background: var(--color-highlight-fg);
-}
-
.code-inner {
font: 12px var(--fonts-monospace);
white-space: pre-wrap;
diff --git a/web_src/css/index.css b/web_src/css/index.css
index 88aa9bbf4a..0e9f2b173a 100644
--- a/web_src/css/index.css
+++ b/web_src/css/index.css
@@ -57,6 +57,7 @@
@import "./form.css";
@import "./repo.css";
+@import "./repo/file-view.css";
@import "./repo/release-tag.css";
@import "./repo/issue-card.css";
@import "./repo/issue-label.css";
diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css
index a70a32b227..2d3617fac3 100644
--- a/web_src/css/markup/content.css
+++ b/web_src/css/markup/content.css
@@ -561,6 +561,10 @@
border-top-right-radius: 0 !important;
}
+.file-view.markup {
+ padding: 2em;
+}
+
.file-view.markup.orgmode li.unchecked::before {
content: "[ ] ";
}
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 651923e60b..cf916f0361 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -1684,9 +1684,6 @@ details.repo-search-result summary::marker {
white-space: nowrap;
}
-.file-view.markup {
- padding: 2em;
-}
.repository .activity-header {
display: flex;
justify-content: space-between;
diff --git a/web_src/css/repo/file-view.css b/web_src/css/repo/file-view.css
new file mode 100644
index 0000000000..2ce7f3ec0f
--- /dev/null
+++ b/web_src/css/repo/file-view.css
@@ -0,0 +1,39 @@
+.file-view tr.active {
+ color: inherit !important;
+ background: inherit !important;
+}
+
+.lines-escape {
+ width: 0;
+}
+
+.lines-code {
+ padding-left: 5px;
+}
+
+.file-view tr.active .lines-num,
+.file-view tr.active .lines-escape,
+.file-view tr.active .lines-code {
+ background: var(--color-highlight-bg) !important;
+}
+
+.file-view tr.active:last-of-type .lines-code {
+ border-bottom-right-radius: var(--border-radius);
+}
+
+.file-view tr.active .lines-num {
+ position: relative;
+}
+
+.file-view tr.active .interact-bg:hover {
+ background: var(--color-primary-alpha-50) !important;
+}
+
+.file-view tr.active .lines-num::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ width: 2px;
+ height: 100%;
+ background: var(--color-highlight-fg);
+}