diff --git a/components/common/sidebar/sidebar.js b/components/common/sidebar/sidebar.js
index ddecc496..e66203eb 100644
--- a/components/common/sidebar/sidebar.js
+++ b/components/common/sidebar/sidebar.js
@@ -1,6 +1,6 @@
(() => {
const mobile = matchMedia("(max-width: 767.98px)");
- for (const page of document.querySelectorAll("[data-theme-sidebar]")) {
+ const init = page => {
let collapsed = mobile.matches;
try {
if (!mobile.matches) collapsed = localStorage.getItem(page.dataset.themeSidebar) === "true";
@@ -31,5 +31,13 @@
update();
});
update();
- }
+ };
+ for (const page of document.querySelectorAll("[data-theme-sidebar]")) init(page);
+ // Forgejo replaces the whole notifications page element after an action; set the new one up too.
+ if (typeof MutationObserver === "undefined" || !document.body) return;
+ new MutationObserver(records => {
+ for (const record of records)
+ for (const node of record.addedNodes)
+ if (node instanceof Element && node.matches("[data-theme-sidebar]")) init(node);
+ }).observe(document.body, { childList: true, subtree: true });
})();
diff --git a/public/assets/js/issue-list.js b/public/assets/js/issue-list.js
index 4f21ee3e..fc19f7c1 100644
--- a/public/assets/js/issue-list.js
+++ b/public/assets/js/issue-list.js
@@ -5,8 +5,11 @@
const form = page.querySelector(".issue-list-search");
const input = form?.querySelector('input[name="q"]');
if (!input) return;
- input.setAttribute("aria-label", isPullList ? "Search pull requests" : "Search Issues");
- input.placeholder = isPullList ? "Search all pull requests" : "Search Issues";
+ // The dashboard's milestone list reuses this layout and keeps its own translated placeholder.
+ if (!page.classList.contains("github-milestones")) {
+ input.setAttribute("aria-label", isPullList ? "Search pull requests" : "Search Issues");
+ input.placeholder = isPullList ? "Search all pull requests" : "Search Issues";
+ }
const toolbar = page.querySelector("#issue-filters");
const filterRow = toolbar?.querySelector(".issue-list-toolbar-right");
if (filterRow) {
diff --git a/scripts/assembleForgejo.ts b/scripts/assembleForgejo.ts
index af9af703..616ded81 100644
--- a/scripts/assembleForgejo.ts
+++ b/scripts/assembleForgejo.ts
@@ -43,7 +43,7 @@ for (const directory of ["common", "repository"]) {
}
}
-for (const file of ["organization.css", "profile-subpages.css", "user-profile.css"]) {
+for (const file of ["dashboard-lists.css", "organization.css", "profile-subpages.css", "user-profile.css"]) {
cpSync(join(root, "styles", "pages", file), join(css, "components", "pages", file));
}
for (const file of readdirSync(join(root, "public"))) {
diff --git a/styles/pages/dashboard-lists.css b/styles/pages/dashboard-lists.css
new file mode 100644
index 00000000..40705017
--- /dev/null
+++ b/styles/pages/dashboard-lists.css
@@ -0,0 +1,144 @@
+/* The dashboard's own lists: issues, pull requests, milestones and notifications. */
+
+/* The context switcher sits at the top of the sidebar instead of in a bar across the page. */
+.dashboard-list-context .secondary-nav {
+ padding: 0;
+ border: 0 !important;
+ background: transparent;
+}
+
+.dashboard-list-context .ui.secondary.menu {
+ flex-direction: column;
+ align-items: stretch;
+ min-height: 0;
+ margin: 0;
+ padding: 0 8px;
+}
+
+.dashboard-list-context .ui.secondary.menu > .item {
+ margin: 0;
+ padding: 6px 8px;
+}
+
+/* Counts sit at the right edge of a sidebar entry, as in GitHub's sidebars. */
+:is(.dashboard-issue-list, .github-notifications) .theme-sidebar nav a > .theme-counter {
+ margin-left: auto;
+}
+
+:is(.dashboard-issue-list, .github-notifications) .theme-sidebar nav a > span:not(.theme-counter) {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* Milestones: the cards form one bordered list under the Open/Closed bar. */
+.github-milestones .milestone-list {
+ margin: 0;
+ padding: 0;
+ border: 1px solid var(--color-light-border);
+ border-top: 0;
+ border-radius: 0 0 var(--border-radius) var(--border-radius);
+ list-style: none;
+}
+
+.github-milestones .milestone-list > .milestone-card {
+ margin: 0;
+ padding: 12px 16px;
+ border: 0;
+ border-radius: 0;
+}
+
+.github-milestones .milestone-list > .milestone-card + .milestone-card {
+ border-top: 1px solid var(--color-light-border);
+}
+
+/* Notifications */
+.github-notifications-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ min-height: 32px;
+ margin: 0 0 16px;
+}
+
+.github-notifications-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.github-notifications-title h1 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 28px;
+}
+
+.github-notifications #notification_table {
+ overflow: hidden;
+ border: 1px solid var(--color-light-border);
+ border-radius: var(--border-radius);
+}
+
+.github-notifications .notifications-item {
+ min-height: 55px;
+ padding: 8px 16px !important;
+ background: var(--color-box-header);
+}
+
+.github-notifications .notifications-item + .notifications-item {
+ border-top: 1px solid var(--color-light-border);
+}
+
+/* Read notifications recede: no tint and a muted title. */
+.github-notifications .notifications-item[data-status="2"] {
+ background: transparent;
+}
+
+.github-notifications .notifications-item[data-status="2"] .issue-title {
+ color: var(--color-text-light-1);
+}
+
+.github-notifications .notifications-item:hover {
+ background: var(--color-hover);
+}
+
+.github-notifications .notifications-top-row {
+ color: var(--color-text-light-1);
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.github-notifications .notifications-bottom-row {
+ padding: 0;
+ font-size: 14px;
+ line-height: 20px;
+}
+
+.github-notifications .notifications-updated {
+ color: var(--color-text-light-1);
+ font-size: 12px;
+}
+
+.github-notifications .notifications-buttons .btn {
+ display: grid;
+ place-items: center;
+ width: 28px;
+ height: 28px;
+ padding: 0;
+ border-radius: var(--border-radius);
+ color: var(--color-text-light-1);
+}
+
+.dashboard-list-context .ui.secondary.menu > .item {
+ align-self: flex-start;
+}
+
+/* Forgejo's milestone page gives the list header its own box; the shared heading has none. */
+.github-milestones .list-header.list-header-issues {
+ padding: 0 !important;
+ border: 0 !important;
+ background: transparent !important;
+}
diff --git a/templates/base/component_assets.tmpl b/templates/base/component_assets.tmpl
index b42b49df..702cf655 100644
--- a/templates/base/component_assets.tmpl
+++ b/templates/base/component_assets.tmpl
@@ -11,6 +11,7 @@
+
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl
new file mode 100644
index 00000000..f46cf036
--- /dev/null
+++ b/templates/user/dashboard/issues.tmpl
@@ -0,0 +1,94 @@
+{{template "base/head" .}}
+{{/*
+ The dashboard's issue and pull request lists use the repository list's layout and classes
+ ("repository issue-list") so they share its styles; nothing repository-specific is rendered.
+*/}}
+{{$keyword := StringUtils.RemoveAll $.Keyword "is:open" "-is:open" "is:closed" "-is:closed" "is:all"}}
+
+
+
+{{template "base/footer" .}}
diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl
new file mode 100644
index 00000000..50759d20
--- /dev/null
+++ b/templates/user/dashboard/milestones.tmpl
@@ -0,0 +1,150 @@
+{{template "base/head" .}}
+{{/* The dashboard's milestone list uses the issue list's layout and classes so it shares its styles. */}}
+
+
+
+{{template "base/footer" .}}
diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl
new file mode 100644
index 00000000..293911b0
--- /dev/null
+++ b/templates/user/notification/notification_div.tmpl
@@ -0,0 +1,125 @@
+