diff --git a/messages/en-us.json b/messages/en-us.json index faf661bd4..c1b2c7f85 100644 --- a/messages/en-us.json +++ b/messages/en-us.json @@ -1895,8 +1895,8 @@ "helperMonth": "Due within a month", "helperSemesterEnd": "Due before semester end", "helperSemesterStart": "Semester start", - "advancedShow": "More settings", - "advancedHide": "Hide more settings", + "advancedShow": "Other optional settings", + "advancedHide": "Hide optional settings", "tagMajor": "Major assignment", "tagTeam": "Team required", "tagDefault": "Standard", diff --git a/messages/zh-cn.json b/messages/zh-cn.json index fdae1614f..7b6fa6bb1 100644 --- a/messages/zh-cn.json +++ b/messages/zh-cn.json @@ -1861,8 +1861,8 @@ "helperMonth": "一月内提交", "helperSemesterEnd": "学期末前提交", "helperSemesterStart": "学期开始", - "advancedShow": "更多设置", - "advancedHide": "收起更多设置", + "advancedShow": "其他可选设置", + "advancedHide": "收起其他可选设置", "tagMajor": "大作业", "tagTeam": "需要组队", "tagDefault": "常规作业", diff --git a/src/features/admin/components/AdminBusVersionsMobileList.svelte b/src/features/admin/components/AdminBusVersionsMobileList.svelte index 84e8f41db..abae404be 100644 --- a/src/features/admin/components/AdminBusVersionsMobileList.svelte +++ b/src/features/admin/components/AdminBusVersionsMobileList.svelte @@ -3,6 +3,7 @@ import * as Empty from "$lib/components/ui/empty/index.js"; import * as Item from "$lib/components/ui/item/index.js"; import AdminBusVersionActions from "./AdminBusVersionActions.svelte"; import AdminBusVersionStatusBadge from "./AdminBusVersionStatusBadge.svelte"; +import AdminListShell from "./AdminListShell.svelte"; import type { AdminBusCopy, AdminBusEnhancedAction, @@ -27,14 +28,15 @@ export let versions: AdminBusVersion[]; {:else} - - {#each versions as version, index (version.id)} - + + + {#each versions as version, index (version.id)} + - {version.title} - {version.key} + {version.title} + {version.key} {#if version.sourceMessage} - {version.sourceMessage} + {version.sourceMessage} {/if} @@ -71,8 +73,9 @@ export let versions: AdminBusVersion[]; {/if} - - {#if index < versions.length - 1}{/if} - {/each} - + + {#if index < versions.length - 1}{/if} + {/each} + + {/if} diff --git a/src/features/admin/components/AdminBusVersionsTable.svelte b/src/features/admin/components/AdminBusVersionsTable.svelte index 8524ebfb1..4a7b55a13 100644 --- a/src/features/admin/components/AdminBusVersionsTable.svelte +++ b/src/features/admin/components/AdminBusVersionsTable.svelte @@ -4,6 +4,7 @@ import * as Empty from "$lib/components/ui/empty/index.js"; import * as Table from "$lib/components/ui/table/index.js"; import AdminBusVersionActions from "./AdminBusVersionActions.svelte"; import AdminBusVersionStatusBadge from "./AdminBusVersionStatusBadge.svelte"; +import AdminTableShell from "./AdminTableShell.svelte"; import type { AdminBusCopy, AdminBusEnhancedAction, @@ -22,65 +23,73 @@ export let versions: AdminBusVersion[]; diff --git a/src/features/admin/components/AdminListShell.svelte b/src/features/admin/components/AdminListShell.svelte new file mode 100644 index 000000000..2f39b3cde --- /dev/null +++ b/src/features/admin/components/AdminListShell.svelte @@ -0,0 +1,15 @@ + + +
+ {@render children()} +
diff --git a/src/features/admin/components/AdminModerationCommentTableRow.svelte b/src/features/admin/components/AdminModerationCommentTableRow.svelte index 1114be954..a838dcbbf 100644 --- a/src/features/admin/components/AdminModerationCommentTableRow.svelte +++ b/src/features/admin/components/AdminModerationCommentTableRow.svelte @@ -39,13 +39,15 @@ export let targetLabel: AdminModerationCommentFormatter; {/if} - - {commentAuthorLabel(comment)} + + {@const author = commentAuthorLabel(comment)} + {author} @@ -59,7 +61,7 @@ export let targetLabel: AdminModerationCommentFormatter; status={comment.status} /> - + - - {#each comments as comment, index (comment.id)} - - - {targetLabel(comment)} - - {commentAuthorLabel(comment)} · {formatDate(comment.createdAt)} - - - {comment.body} - - - - - - - - {#if index < comments.length - 1}{/if} - {/each} - + + + {#each comments as comment, index (comment.id)} + + + {targetLabel(comment)} + + {commentAuthorLabel(comment)} · {formatDate(comment.createdAt)} + + + {comment.body} + + + + + + + + {#if index < comments.length - 1}{/if} + {/each} + + diff --git a/src/features/admin/components/AdminModerationCommentsTable.svelte b/src/features/admin/components/AdminModerationCommentsTable.svelte index 61701e5d9..20e9cf2c1 100644 --- a/src/features/admin/components/AdminModerationCommentsTable.svelte +++ b/src/features/admin/components/AdminModerationCommentsTable.svelte @@ -1,6 +1,7 @@ diff --git a/src/features/admin/components/AdminModerationDescriptionCards.svelte b/src/features/admin/components/AdminModerationDescriptionCards.svelte index 23a861937..5eb093379 100644 --- a/src/features/admin/components/AdminModerationDescriptionCards.svelte +++ b/src/features/admin/components/AdminModerationDescriptionCards.svelte @@ -2,6 +2,7 @@ import ChevronRightIcon from "@lucide/svelte/icons/chevron-right"; import { Button } from "$lib/components/ui/button/index.js"; import * as Item from "$lib/components/ui/item/index.js"; +import AdminListShell from "./AdminListShell.svelte"; import { adminModerationDescriptionEditedAt, adminModerationDescriptionLastEditor, @@ -23,11 +24,12 @@ export let onManage: (description: AdminModerationDescription) => void; export let targetLabel: (description: AdminModerationDescription) => string; - - {#each descriptions as description, index (description.id)} - + + + {#each descriptions as description, index (description.id)} + - {targetLabel(description)} + {targetLabel(description)} {formatDate(adminModerationDescriptionEditedAt(description))} @@ -52,7 +54,8 @@ export let targetLabel: (description: AdminModerationDescription) => string; - {#if index < descriptions.length - 1}{/if} - {/each} - + + {#if index < descriptions.length - 1}{/if} + {/each} + + diff --git a/src/features/admin/components/AdminModerationDescriptionTable.svelte b/src/features/admin/components/AdminModerationDescriptionTable.svelte index 096940e9b..c817a26a0 100644 --- a/src/features/admin/components/AdminModerationDescriptionTable.svelte +++ b/src/features/admin/components/AdminModerationDescriptionTable.svelte @@ -4,6 +4,7 @@ import TableIconButton from "$lib/components/TableIconButton.svelte"; import TableRowActions from "$lib/components/TableRowActions.svelte"; import TruncatedText from "$lib/components/TruncatedText.svelte"; import * as Table from "$lib/components/ui/table/index.js"; +import AdminTableShell from "./AdminTableShell.svelte"; import { adminModerationDescriptionEditedAt, adminModerationDescriptionLastEditor, @@ -24,59 +25,63 @@ export let targetLabel: (description: AdminModerationDescription) => string; diff --git a/src/features/admin/components/AdminModerationHomeworks.svelte b/src/features/admin/components/AdminModerationHomeworks.svelte index 8b5487bc6..4ed05b2d3 100644 --- a/src/features/admin/components/AdminModerationHomeworks.svelte +++ b/src/features/admin/components/AdminModerationHomeworks.svelte @@ -8,6 +8,8 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as Empty from "$lib/components/ui/empty/index.js"; import * as Item from "$lib/components/ui/item/index.js"; import * as Table from "$lib/components/ui/table/index.js"; +import AdminListShell from "./AdminListShell.svelte"; +import AdminTableShell from "./AdminTableShell.svelte"; type ModerationHomework = { createdAt: string | Date; @@ -49,11 +51,12 @@ export let onDelete: (homework: ModerationHomework) => void; {:else} - + + {#each homeworks as homework, index (homework.id)} - {homework.title} + {homework.title} {homework.section.course.nameCn} · {homework.section.code} @@ -86,18 +89,20 @@ export let onDelete: (homework: ModerationHomework) => void; {#if index < homeworks.length - 1}{/if} {/each} - + + -
+ {#each clients as client (client.clientId)} {client.name ?? copy.unnamedClient} - + {client.clientId}
@@ -165,6 +171,6 @@ export let scopeLabel: (scope: string) => string; {/each} -
+
{/if} diff --git a/src/features/admin/components/AdminTableShell.svelte b/src/features/admin/components/AdminTableShell.svelte new file mode 100644 index 000000000..bcae8d9f7 --- /dev/null +++ b/src/features/admin/components/AdminTableShell.svelte @@ -0,0 +1,26 @@ + + + +
[data-slot=table-container]]:overflow-visible", + className, + )} + role={label ? "region" : undefined} + data-slot="admin-table-shell" + tabindex={label ? 0 : undefined} +> + {@render children()} +
diff --git a/src/features/admin/components/AdminUsersDesktopTable.svelte b/src/features/admin/components/AdminUsersDesktopTable.svelte index 1c195dcee..c1585a95c 100644 --- a/src/features/admin/components/AdminUsersDesktopTable.svelte +++ b/src/features/admin/components/AdminUsersDesktopTable.svelte @@ -5,6 +5,7 @@ import TableRowActions from "$lib/components/TableRowActions.svelte"; import TruncatedText from "$lib/components/TruncatedText.svelte"; import { Badge } from "$lib/components/ui/badge/index.js"; import * as Table from "$lib/components/ui/table/index.js"; +import AdminTableShell from "./AdminTableShell.svelte"; import type { AdminUserFormatter, AdminUserRow, @@ -20,70 +21,81 @@ export let users: AdminUserRow[]; diff --git a/src/features/admin/components/AdminUsersMobileList.svelte b/src/features/admin/components/AdminUsersMobileList.svelte index 8a2107581..59fce5b87 100644 --- a/src/features/admin/components/AdminUsersMobileList.svelte +++ b/src/features/admin/components/AdminUsersMobileList.svelte @@ -3,6 +3,7 @@ import ChevronRightIcon from "@lucide/svelte/icons/chevron-right"; import { Badge } from "$lib/components/ui/badge/index.js"; import { Button } from "$lib/components/ui/button/index.js"; import * as Item from "$lib/components/ui/item/index.js"; +import AdminListShell from "./AdminListShell.svelte"; import type { AdminUserFormatter, AdminUserRow, @@ -17,60 +18,63 @@ export let suspensionLabel: AdminUserFormatter; export let users: AdminUserRow[]; - - {#each users as user, index (user.id)} - - - {displayName(user)} - - @{user.username ?? copy.noUsername} - - - {user.email ?? copy.noVerifiedEmail} - - -
-
-
{copy.role}
-
- - {user.isAdmin ? copy.adminRole : copy.userRole} - -
-
-
-
{copy.createdAt}
-
{formatDate(user.createdAt)}
-
-
-
{copy.suspension}
-
- {#if user.activeSuspension} - {copy.suspendedStatus} - - {suspensionLabel(user)} - - {:else} - {copy.clearStatus} - {/if} -
-
-
-
-
- - - -
- {#if index < users.length - 1}{/if} - {/each} -
+ + + {#each users as user, index (user.id)} + + + {displayName(user)} + + @{user.username ?? copy.noUsername} + + {@const email = user.email ?? copy.noVerifiedEmail} + + {email} + + +
+
+
{copy.role}
+
+ + {user.isAdmin ? copy.adminRole : copy.userRole} + +
+
+
+
{copy.createdAt}
+
{formatDate(user.createdAt)}
+
+
+
{copy.suspension}
+
+ {#if user.activeSuspension} + {copy.suspendedStatus} + + {suspensionLabel(user)} + + {:else} + {copy.clearStatus} + {/if} +
+
+
+
+
+ + + +
+ {#if index < users.length - 1}{/if} + {/each} +
+
diff --git a/src/features/admin/components/AdminWorkspace.svelte b/src/features/admin/components/AdminWorkspace.svelte index edff95cd9..de7e3d304 100644 --- a/src/features/admin/components/AdminWorkspace.svelte +++ b/src/features/admin/components/AdminWorkspace.svelte @@ -12,8 +12,11 @@ type Props = { let { children, controls, feedback, header, summary }: Props = $props(); -
-
+
+
{@render header()} {#if feedback}{@render feedback()}{/if} {#if summary}{@render summary()}{/if} diff --git a/src/features/dashboard/components/HomeworkCreateFormFields.svelte b/src/features/dashboard/components/HomeworkCreateFormFields.svelte index 5b9dc11b8..1fe8bb2fd 100644 --- a/src/features/dashboard/components/HomeworkCreateFormFields.svelte +++ b/src/features/dashboard/components/HomeworkCreateFormFields.svelte @@ -49,13 +49,16 @@ $: homeworkTimestampCapabilities = { }; - + {#if createHomeworkError} {createHomeworkError} {/if} - + {homeworksCopy.sectionLabel} diff --git a/src/features/homeworks/components/HomeworkDeadlineSummary.svelte b/src/features/homeworks/components/HomeworkDeadlineSummary.svelte index fa0afef4b..a3a526d2c 100644 --- a/src/features/homeworks/components/HomeworkDeadlineSummary.svelte +++ b/src/features/homeworks/components/HomeworkDeadlineSummary.svelte @@ -1,14 +1,11 @@
-
-
-
+
{copy.submissionDue}
+
{dueLabel}
-
- -
-
-
{copy.relativeTime} - -
- {relativeLabel} + + {relativeLabel}
-
-
diff --git a/src/features/homeworks/components/HomeworkDetailDialog.svelte b/src/features/homeworks/components/HomeworkDetailDialog.svelte index 2d740f813..a54918762 100644 --- a/src/features/homeworks/components/HomeworkDetailDialog.svelte +++ b/src/features/homeworks/components/HomeworkDetailDialog.svelte @@ -78,7 +78,7 @@ onMount(() => { class="inset-0 flex h-dvh max-h-dvh w-screen max-w-none translate-x-0 translate-y-0 flex-col gap-0 overflow-clip rounded-none p-0 sm:top-1/2 sm:left-1/2 sm:h-[min(72vh,42rem)] sm:max-h-[min(72vh,42rem)] sm:w-[calc(100%-2rem)] sm:max-w-3xl sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl" data-homework-id={homework.id} > - + {homework.title} @@ -96,7 +96,7 @@ onMount(() => {
diff --git a/src/features/homeworks/components/HomeworkEditorFields.svelte b/src/features/homeworks/components/HomeworkEditorFields.svelte index a9a825e48..5788e5afe 100644 --- a/src/features/homeworks/components/HomeworkEditorFields.svelte +++ b/src/features/homeworks/components/HomeworkEditorFields.svelte @@ -57,12 +57,14 @@ export let title = ""; {styleGuidePrefix} /> {/snippet} + {#snippet optionalSettings()} + + {/snippet} - diff --git a/src/features/homeworks/components/HomeworkStyleGuide.svelte b/src/features/homeworks/components/HomeworkStyleGuide.svelte index cfffb7034..e318d1788 100644 --- a/src/features/homeworks/components/HomeworkStyleGuide.svelte +++ b/src/features/homeworks/components/HomeworkStyleGuide.svelte @@ -4,18 +4,26 @@ import * as Accordion from "$lib/components/ui/accordion/index.js"; export let copy: HomeworkStyleGuideCopy; export let testIdPrefix: string; + +let guideValue = "homework-style-guide"; - - + { + guideValue = value; + }} +> + {copy.styleGuideTitle}

{copy.styleGuideTitleGuidance}

diff --git a/src/features/homeworks/components/HomeworkTimestampFields.svelte b/src/features/homeworks/components/HomeworkTimestampFields.svelte index effd8deee..5a6dc8b7f 100644 --- a/src/features/homeworks/components/HomeworkTimestampFields.svelte +++ b/src/features/homeworks/components/HomeworkTimestampFields.svelte @@ -20,6 +20,7 @@ export let copy: HomeworkTimestampCopy; export let disabled = false; export let details: Snippet | undefined = undefined; export let idPrefix = "homework"; +export let optionalSettings: Snippet | undefined = undefined; export let publishedAt = ""; export let submissionDueAt = ""; export let submissionStartAt = ""; @@ -98,11 +99,11 @@ $: hasAdvancedShortcuts = Boolean( advancedOpen = value === "advanced"; }} > - + {advancedOpen ? copy.advancedHide : copy.advancedShow} - + @@ -187,6 +188,9 @@ $: hasAdvancedShortcuts = Boolean( + {#if optionalSettings} + {@render optionalSettings()} + {/if}
diff --git a/src/features/homeworks/components/homework-detail-types.ts b/src/features/homeworks/components/homework-detail-types.ts index 6fab18dc6..944a2e25c 100644 --- a/src/features/homeworks/components/homework-detail-types.ts +++ b/src/features/homeworks/components/homework-detail-types.ts @@ -25,6 +25,7 @@ export type HomeworkDetailCopy = { }; export type HomeworkDetailCommentsPanel = Component<{ + heading?: string | null; permalinkBaseHref?: string | null; targetId: string; targetType: "homework"; diff --git a/src/features/section-detail/components/SectionCalendarTab.svelte b/src/features/section-detail/components/SectionCalendarTab.svelte index a01796946..57d4ec394 100644 --- a/src/features/section-detail/components/SectionCalendarTab.svelte +++ b/src/features/section-detail/components/SectionCalendarTab.svelte @@ -1,13 +1,12 @@ {#if datedEvents.length > 0 || unscheduledCalendarEvents.length > 0} - - {#snippet mobile()} - - {#each mobileEvents as event (event.id)} - {@const dateLabel = event.dateKey - ? formatYyyyMmDd(event.dateKey) - : sectionCopy.dateTBD} - {@const weekLabel = detailValue(event, sectionCopy.week) || "—"} - {@const timeLabel = eventTime(event) || "—"} - {@const locationLabel = eventLocation(event) || "—"} - - - - - {lectureLabel(event)} - - {#if event.kind === "exam"} - - {sectionCopy.examEvent} - - {/if} - - -
-
-
-
-
{dateLabel}
-
-
-
- - {sectionCopy.week} -
-
{weekLabel}
-
-
-
-
-
{timeLabel}
-
-
-
-
-
{locationLabel}
-
-
-
-
- {/each} -
- {/snippet} - {#snippet desktop()} -
- - {sectionCopy.lecture} - - - {sectionCopy.lecture} - {sectionCopy.date} - {sectionCopy.week} - {sectionCopy.time} - {sectionCopy.location} - - - - {#each datedEvents as event (event.id)} - - - {lectureLabel(event)} - - - {formatYyyyMmDd(event.dateKey ?? event.date)} - - - {detailValue(event, sectionCopy.week)} - - {eventTime(event)} - {eventLocation(event)} - - {/each} - {#each unscheduledCalendarEvents as event (event.id)} - - - {event.kind === "exam" - ? sectionCopy.examEvent - : formatMessage(sectionCopy.lectureNumber, { num: "?" })} - - - {sectionCopy.dateTBD} - - - {detailValue(event, sectionCopy.week)} - - {eventTime(event)} - {eventLocation(event)} - - {/each} - - -
- {/snippet} -
+
+ + {sectionCopy.lecture} + + + {sectionCopy.lecture} + {sectionCopy.date} + {sectionCopy.week} + {sectionCopy.time} + {sectionCopy.location} + + + + {#each datedEvents as event (event.id)} + + {lectureLabel(event)} + + {formatYyyyMmDd(event.dateKey ?? event.date) || sectionCopy.dateTBD} + + + {calendarEventDetail(event, sectionCopy.week, "—")} + + + {calendarEventTime(event, "—")} + + + {calendarEventLocation(event, "—")} + + + {/each} + {#each unscheduledCalendarEvents as event (event.id)} + + {lectureLabel(event)} + {sectionCopy.dateTBD} + + {calendarEventDetail(event, sectionCopy.week, "—")} + + + {calendarEventTime(event, "—")} + + + {calendarEventLocation(event, "—")} + + + {/each} + + +
{:else} diff --git a/src/features/section-detail/components/SectionCalendarUnscheduledEvents.svelte b/src/features/section-detail/components/SectionCalendarUnscheduledEvents.svelte deleted file mode 100644 index b47e2dde7..000000000 --- a/src/features/section-detail/components/SectionCalendarUnscheduledEvents.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - -
-

{sectionCopy.dateTBD}

- - {#each events as event} - - - {event.title} - - - {event.meta} - - - {/each} - -
diff --git a/src/features/section-detail/components/SectionCreateHomeworkDialog.svelte b/src/features/section-detail/components/SectionCreateHomeworkDialog.svelte index 52c620a4e..48906bd4a 100644 --- a/src/features/section-detail/components/SectionCreateHomeworkDialog.svelte +++ b/src/features/section-detail/components/SectionCreateHomeworkDialog.svelte @@ -25,6 +25,7 @@ export let homeworkMessage: string; export let openAuditDialog: (() => void) | null = null; export let publishedAt: string; export let sectionCopy: SectionCreateHomeworkSectionCopy; +export let sectionLabel: string; export let show: boolean; export let submissionDueAt: string; export let submissionStartAt: string; @@ -38,13 +39,13 @@ export let submissionStartAt: string; }} >
- +
{homeworkCopy.createTitle} @@ -70,12 +71,13 @@ export let submissionStartAt: string; {hasSemesterStart} {homeworkCopy} {homeworkMessage} + {sectionLabel} bind:publishedAt bind:submissionDueAt bind:submissionStartAt /> - + diff --git a/src/features/section-detail/components/SectionCreateHomeworkFields.svelte b/src/features/section-detail/components/SectionCreateHomeworkFields.svelte index 706d19774..3de15d097 100644 --- a/src/features/section-detail/components/SectionCreateHomeworkFields.svelte +++ b/src/features/section-detail/components/SectionCreateHomeworkFields.svelte @@ -18,6 +18,7 @@ export let hasSemesterEnd: boolean; export let hasSemesterStart: boolean; export let homeworkCopy: SectionCreateHomeworkFieldsCopy; export let homeworkMessage: string; +export let sectionLabel: string; export let publishedAt: string; export let submissionDueAt: string; export let submissionStartAt: string; @@ -38,7 +39,11 @@ $: homeworkTimestampCapabilities = { }; - + + + {homeworkCopy.sectionLabel} +

{sectionLabel}

+
-
+

{sectionCopy.tabs.calendar} @@ -188,7 +188,7 @@ $: sectionExamEvents = sectionCalendarEvents.filter( {/if}

-
+

{sectionCopy.tabs.exams}

diff --git a/src/features/section-detail/components/SectionExamSection.svelte b/src/features/section-detail/components/SectionExamSection.svelte index 706115488..65a44af7f 100644 --- a/src/features/section-detail/components/SectionExamSection.svelte +++ b/src/features/section-detail/components/SectionExamSection.svelte @@ -1,10 +1,10 @@ {#if events.length > 0} - - {#snippet mobile()} - +
+ + {heading} + + + {sectionCopy.examBatch} + {sectionCopy.examDate} + {sectionCopy.examTime} + {sectionCopy.location} + {sectionCopy.examMode} + {sectionCopy.examCount} + + + {#each events as event (event.id)} - {@const batchLabel = detailValue(event, sectionCopy.examBatch)} - {@const dateLabel = event.date - ? fmtDate(event.date) - : sectionCopy.dateTBD} - {@const timeLabel = examTime(event)} - {@const locationLabel = detailValue(event, sectionCopy.location) || examLocation(event)} - {@const modeLabel = event.title || "—"} - {@const countLabel = detailValue(event, sectionCopy.examCount)} - - - - - {modeLabel} - - - {batchLabel} - - - -
-
-
-
-
{dateLabel}
-
-
-
-
-
{timeLabel}
-
-
-
-
-
{locationLabel}
-
-
- - - - {sectionCopy.examBatch}: - {batchLabel} - - - {sectionCopy.examCount}: - {countLabel} - - -
-
+ + + {calendarEventDetail(event, sectionCopy.examBatch, "—")} + + + {#if event.date}{fmtDate(event.date)}{:else}{sectionCopy.dateTBD}{/if} + + + {calendarEventTime(event, "—")} + + + {calendarEventDetail( + event, + sectionCopy.location, + calendarEventLocation(event, "—"), + )} + + {event.title || "—"} + + {calendarEventDetail(event, sectionCopy.examCount, "—")} + + {/each} - - {/snippet} - {#snippet desktop()} -
- - {heading} - - - {sectionCopy.examBatch} - {sectionCopy.examDate} - {sectionCopy.examTime} - {sectionCopy.location} - {sectionCopy.examMode} - {sectionCopy.examCount} - - - - {#each events as event (event.id)} - - - {detailValue(event, sectionCopy.examBatch)} - - - {#if event.date}{fmtDate(event.date)}{:else}{sectionCopy.dateTBD}{/if} - - {examTime(event)} - - {detailValue(event, sectionCopy.location)} - - {event.title} - - {detailValue(event, sectionCopy.examCount)} - - - {/each} - - -
- {/snippet} - +
+
+
{:else} diff --git a/src/features/section-detail/components/SectionHomeworkDialogs.svelte b/src/features/section-detail/components/SectionHomeworkDialogs.svelte index d797f718f..3fd52d7f7 100644 --- a/src/features/section-detail/components/SectionHomeworkDialogs.svelte +++ b/src/features/section-detail/components/SectionHomeworkDialogs.svelte @@ -83,6 +83,7 @@ export let updateHomework: SectionDetailHomeworkDialogsProps["updateHomework"]; openAuditDialog={() => setHomeworkAuditDialogOpen(true)} bind:publishedAt={createHomeworkPublishedAt} {sectionCopy} + {sectionLabel} show={showCreateHomework} bind:submissionDueAt={createHomeworkSubmissionDueAt} bind:submissionStartAt={createHomeworkSubmissionStartAt} diff --git a/src/features/section-detail/components/SectionHomeworkEditForm.svelte b/src/features/section-detail/components/SectionHomeworkEditForm.svelte index 9ce1affb2..9aa63bb67 100644 --- a/src/features/section-detail/components/SectionHomeworkEditForm.svelte +++ b/src/features/section-detail/components/SectionHomeworkEditForm.svelte @@ -45,7 +45,7 @@ $: homeworkTimestampCapabilities = { {editHomeworkMessage} {/if} -
+
diff --git a/src/features/section-detail/components/section-create-homework-types.ts b/src/features/section-detail/components/section-create-homework-types.ts index d32b8ce47..5e55f9f4f 100644 --- a/src/features/section-detail/components/section-create-homework-types.ts +++ b/src/features/section-detail/components/section-create-homework-types.ts @@ -8,6 +8,7 @@ export type SectionCreateHomeworkFieldsCopy = HomeworkFormCopy & HomeworkTagCopy & HomeworkTimestampCopy & { helperSemesterStart: string; + sectionLabel: string; }; export type SectionCreateHomeworkCopy = SectionCreateHomeworkFieldsCopy & { diff --git a/src/features/section-detail/lib/section-calendar-display.ts b/src/features/section-detail/lib/section-calendar-display.ts new file mode 100644 index 000000000..b983f678e --- /dev/null +++ b/src/features/section-detail/lib/section-calendar-display.ts @@ -0,0 +1,25 @@ +import type { SectionCalendarEvent } from "./calendar"; + +type CalendarDisplayEvent = Pick; + +export function calendarEventDetail( + event: CalendarDisplayEvent, + label: string, + fallback = "", +) { + return ( + event.details.find((detail) => detail.label === label)?.value ?? fallback + ); +} + +export function calendarEventTime(event: CalendarDisplayEvent, fallback = "") { + const [time] = event.meta.split(" · "); + return time?.trim() || fallback; +} + +export function calendarEventLocation( + event: CalendarDisplayEvent, + fallback = "", +) { + return event.meta.split(" · ").slice(1).join(" · ").trim() || fallback; +} diff --git a/src/features/section-detail/lib/section-detail-controller-types.ts b/src/features/section-detail/lib/section-detail-controller-types.ts index 05a6edb67..9b1a85555 100644 --- a/src/features/section-detail/lib/section-detail-controller-types.ts +++ b/src/features/section-detail/lib/section-detail-controller-types.ts @@ -194,6 +194,7 @@ export type SectionDetailCopy = { relativeTime: string; saveChanges: string; saving: string; + sectionLabel: string; showCreate: string; submissionDue: string; submissionStart: string; diff --git a/src/routes/admin/analytics/+page.svelte b/src/routes/admin/analytics/+page.svelte index 12d33fb75..6b7dc09e2 100644 --- a/src/routes/admin/analytics/+page.svelte +++ b/src/routes/admin/analytics/+page.svelte @@ -185,21 +185,21 @@ function failureLabel(count: number) {
-
+

{data.copy.analytics.rankings}

-
+
{#each [ { title: data.copy.analytics.topFeatures, kind: "feature", rows: data.rankings.features }, { title: data.copy.analytics.topChannels, kind: "channel", rows: data.rankings.channels }, { title: data.copy.analytics.topClients, kind: "client", rows: data.rankings.clients }, ] as ranking} -
+

{ranking.title}

-
    +
      {#each ranking.rows as row} -
    1. -
      - {rankingLabel(ranking.kind as "channel" | "client" | "feature", row.label)} +
    2. +
      + {rankingLabel(ranking.kind as "channel" | "client" | "feature", row.label)} {numberFormatter.format(row.count)}
      import ChevronRightIcon from "@lucide/svelte/icons/chevron-right"; +import AdminListShell from "@/features/admin/components/AdminListShell.svelte"; +import AdminTableShell from "@/features/admin/components/AdminTableShell.svelte"; import AdminWorkspace from "@/features/admin/components/AdminWorkspace.svelte"; import { auditActionLabel, @@ -174,7 +176,8 @@ function displayValue(value: unknown) { {:else} - + + {#each data.rows as row, index (row.id)} {@const actor = identity(row.user)} {@const subject = identity(row.subjectUser)} @@ -190,10 +193,10 @@ function displayValue(value: unknown) {
      - {#if actor}
      {data.copy.audit.actorColumn}
      {actor.label} {actor.id}
      {/if} - {#if subject}
      {data.copy.audit.subjectColumn}
      {subject.label} {subject.id}
      {/if} - {#if row.oauthClientId}
      {data.copy.audit.clientColumn}
      {row.clientName ?? row.oauthClientId}
      {/if} - {#if row.targetType}
      {data.copy.audit.target}
      {auditTargetLabel(data.locale, row.targetType)}{row.targetId ? ` · ${row.targetId}` : ""}
      {/if} + {#if actor}
      {data.copy.audit.actorColumn}
      {actor.label} {actor.id}
      {/if} + {#if subject}
      {data.copy.audit.subjectColumn}
      {subject.label} {subject.id}
      {/if} + {#if row.oauthClientId}
      {data.copy.audit.clientColumn}
      {row.clientName ?? row.oauthClientId}
      {/if} + {#if row.targetType}
      {data.copy.audit.target}
      {auditTargetLabel(data.locale, row.targetType)}{row.targetId ? ` · ${row.targetId}` : ""}
      {/if}
      {#if row.metadata}
      @@ -213,16 +216,11 @@ function displayValue(value: unknown) { {#if index < data.rows.length - 1}{/if} {/each} - + + - - + {/if}
      diff --git a/tests/e2e/src/app/dashboard/homeworks/test.ts b/tests/e2e/src/app/dashboard/homeworks/test.ts index 2721c233d..33abe8c04 100644 --- a/tests/e2e/src/app/dashboard/homeworks/test.ts +++ b/tests/e2e/src/app/dashboard/homeworks/test.ts @@ -634,7 +634,7 @@ test.describe("仪表盘作业", () => { await captureStepScreenshot(page, testInfo, "homeworks/created"); }); - test("新建作业展示可折叠的英文填写规范", async ({ page }, testInfo) => { + test("新建作业默认展开英文填写规范", async ({ page }, testInfo) => { await signInAsDebugUser(page, "/workspace/homeworks"); await ensureSeedSectionSubscription(page); const localeResponse = await page.request.post("/api/account/preferences", { @@ -660,8 +660,7 @@ test.describe("仪表盘作业", () => { const trigger = createDialog.getByTestId( "dashboard-homework-style-guide-trigger", ); - await expect(trigger).toHaveAttribute("aria-expanded", "false"); - await trigger.click(); + await expect(trigger).toHaveAttribute("aria-expanded", "true"); const guide = createDialog.getByTestId( "dashboard-homework-style-guide-content", ); @@ -714,7 +713,7 @@ test.describe("仪表盘作业", () => { let homeworkId: string | undefined; const createDialog = page.locator('[data-slot="dialog-content"]').first(); const advancedSettings = createDialog.getByRole("button", { - name: /更多设置|More settings|收起更多设置|Hide more settings/i, + name: /其他可选设置|Other optional settings|收起其他可选设置|Hide optional settings/i, }); await expect(advancedSettings).toHaveAttribute("aria-expanded", "false"); await expect( @@ -724,10 +723,6 @@ test.describe("仪表盘作业", () => { await expect( createDialog.getByRole("textbox", { name: /发布日期|Published/i }), ).toBeVisible(); - await advancedSettings.click(); - await expect( - createDialog.getByRole("textbox", { name: /发布日期|Published/i }), - ).toHaveCount(0); await titleInput.fill(title); await createDialog .getByRole("textbox", { name: /Details|说明/i }) diff --git a/tests/e2e/src/app/sections/[jwId]/test.ts b/tests/e2e/src/app/sections/[jwId]/test.ts index 0ea057cf1..094a6fba7 100644 --- a/tests/e2e/src/app/sections/[jwId]/test.ts +++ b/tests/e2e/src/app/sections/[jwId]/test.ts @@ -363,6 +363,33 @@ test.describe("/catalog/sections/[jwId] 班级详情页", () => { await captureStepScreenshot(page, testInfo, "section/calendar-today"); }); + test("移动端日历使用可横向滚动的紧凑表格", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await gotoAndWaitForReady(page, SECTION_URL); + await jumpToSection(page, /日历|Calendar/i, "#calendar"); + + const calendar = page.locator("#calendar"); + const table = calendar.getByTestId("section-calendar-table"); + const container = table.locator( + 'xpath=ancestor::*[@data-slot="table-container"][1]', + ); + await expect(table).toBeVisible(); + await expect(container).toHaveAttribute("role", "region"); + await expect(container).toHaveAttribute("tabindex", "0"); + await expect(container).toHaveAttribute("aria-label", /.+/); + await expect(calendar.getByTestId("section-calendar-items")).toHaveCount(0); + const dimensions = await container.evaluate((element) => ({ + clientWidth: element.clientWidth, + scrollWidth: element.scrollWidth, + })); + expect(dimensions.scrollWidth).toBeGreaterThan(dimensions.clientWidth); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + ).toBe(true); + }); + test("日历区块显示考试信息(examBatch、examRooms)", async ({ page, }, testInfo) => { @@ -1024,7 +1051,7 @@ test.describe("/catalog/sections/[jwId] 班级详情页", () => { .click(); const createDialog = page.locator('[data-slot="dialog-content"]').first(); const advancedSettings = createDialog.getByRole("button", { - name: /更多设置|More settings|收起更多设置|Hide more settings/i, + name: /其他可选设置|Other optional settings|收起其他可选设置|Hide optional settings/i, }); await expect(advancedSettings).toHaveAttribute("aria-expanded", "false"); await expect( @@ -1041,8 +1068,7 @@ test.describe("/catalog/sections/[jwId] 班级详情页", () => { const createTrigger = createDialog.getByTestId( "section-create-homework-style-guide-trigger", ); - await expect(createTrigger).toHaveAttribute("aria-expanded", "false"); - await createTrigger.click(); + await expect(createTrigger).toHaveAttribute("aria-expanded", "true"); const createGuide = createDialog.getByTestId( "section-create-homework-style-guide-content", ); @@ -1087,8 +1113,7 @@ test.describe("/catalog/sections/[jwId] 班级详情页", () => { const editTrigger = detailDialog.getByTestId( "section-edit-homework-style-guide-trigger", ); - await expect(editTrigger).toHaveAttribute("aria-expanded", "false"); - await editTrigger.click(); + await expect(editTrigger).toHaveAttribute("aria-expanded", "true"); const editGuide = detailDialog.getByTestId( "section-edit-homework-style-guide-content", ); @@ -1391,6 +1416,11 @@ test.describe("/catalog/sections/[jwId] 班级详情页", () => { await editForm .getByRole("textbox", { name: /Submission due|提交截止/i }) .fill(dueAt); + const advancedSettings = editForm.getByRole("button", { + name: /其他可选设置|Other optional settings|收起其他可选设置|Hide optional settings/i, + }); + await expect(advancedSettings).toHaveAttribute("aria-expanded", "false"); + await advancedSettings.click(); await editForm .getByRole("checkbox", { name: /Major assignment|大作业/i }) .click();