|
1 | 1 | <script lang="ts"> |
| 2 | + import xss from 'xss'; |
| 3 | +
|
2 | 4 | import { |
3 | 5 | Label, |
4 | 6 | Table, |
|
9 | 11 | TableHeadCell, |
10 | 12 | } from 'flowbite-svelte'; |
11 | 13 |
|
| 14 | + import TrashBinOutline from 'flowbite-svelte-icons/TrashBinOutline.svelte'; |
| 15 | +
|
12 | 16 | import GradeLabel from '$lib/components/GradeLabel.svelte'; |
13 | 17 | import ExternalLinkWrapper from '$lib/components/ExternalLinkWrapper.svelte'; |
14 | 18 |
|
|
31 | 35 | const target = event.target as HTMLElement; |
32 | 36 |
|
33 | 37 | if (target && target instanceof HTMLElement) { |
34 | | - const newComment = target.innerText as string; |
| 38 | + const newComment = xss(target.innerText as string); |
35 | 39 |
|
36 | 40 | // HACK: 代替手段として、50文字以下の場合のみ更新 |
37 | 41 | if (newComment.length <= 50) { |
|
104 | 108 |
|
105 | 109 | return task; |
106 | 110 | } |
| 111 | +
|
| 112 | + let isDeleting = false; |
107 | 113 | </script> |
108 | 114 |
|
109 | 115 | {#if workBookTasksForTable.length} |
110 | 116 | <Label class="space-y-2"> |
111 | 117 | <span>問題一覧({workBookTasksForTable.length} 問)</span> |
112 | 118 | </Label> |
113 | 119 |
|
114 | | - <Table shadow class="text-md"> |
| 120 | + <Table shadow class="text-md table-fixed w-full" aria-label="Workbook tasks"> |
| 121 | + <caption class="sr-only">List of workbook tasks with their grades and comments</caption> |
115 | 122 | <TableHead class="text-sm bg-gray-100"> |
116 | | - <TableHeadCell class="min-w-[18px] pl-2 md:pl-4 pr-0 text-center">#</TableHeadCell> |
117 | | - <TableHeadCell class="text-center px-0" aria-label="Task grade">グレード</TableHeadCell> |
118 | | - <TableHeadCell class="min-w-[240px] pl-0 truncate">問題名</TableHeadCell> |
119 | | - <TableHeadCell class="min-w-[120px] max-w-[150px] truncate">出典</TableHeadCell> |
120 | | - <TableHeadCell class="min-w-[120px] max-w-[150px] px-0 truncate"> |
| 123 | + <TableHeadCell class="w-6 pl-2 md:pl-4 pr-0 text-center">#</TableHeadCell> |
| 124 | + <TableHeadCell class="w-20 xs:w-24 text-center px-0" aria-label="Task grade"> |
| 125 | + グレード |
| 126 | + </TableHeadCell> |
| 127 | + <TableHeadCell class="w-1/2 pl-0 truncate">問題名</TableHeadCell> |
| 128 | + <TableHeadCell class="w-1/3 hidden sm:table-cell truncate">出典</TableHeadCell> |
| 129 | + <TableHeadCell class="w-24 md:w-64 hidden sm:table-cell px-0 truncate"> |
121 | 130 | 一言(50文字以下) |
122 | 131 | </TableHeadCell> |
123 | | - <TableHeadCell class="min-w-[24px] px-0 text-center"> |
| 132 | + <TableHeadCell class="w-12 xs:w-16 text-center"> |
124 | 133 | <span class="sr-only">編集</span> |
125 | 134 | </TableHeadCell> |
126 | 135 | </TableHead> |
|
140 | 149 | </TableBodyCell> |
141 | 150 |
|
142 | 151 | <!-- グレード --> |
143 | | - <TableBodyCell> |
144 | | - <div class="flex items-center justify-center min-w-[54px] max-w-fit"> |
| 152 | + <TableBodyCell class="w-20 xs:w-24"> |
| 153 | + <div class="flex items-center justify-center"> |
145 | 154 | <GradeLabel taskGrade={getTaskGrade(tasksMapByIds, task.taskId)} /> |
146 | 155 | </div> |
147 | 156 | </TableBodyCell> |
|
158 | 167 | </TableBodyCell> |
159 | 168 |
|
160 | 169 | <!-- 出典 --> |
161 | | - <TableBodyCell class="xs:text-lg text-gray-700 dark:text-gray-300 truncate"> |
| 170 | + <TableBodyCell |
| 171 | + class="xs:text-lg hidden sm:table-cell text-gray-700 dark:text-gray-300 truncate" |
| 172 | + aria-hidden={true} |
| 173 | + > |
162 | 174 | {addContestNameToTaskIndex( |
163 | 175 | task.contestId, |
164 | 176 | getTaskTableIndex(tasksMapByIds, task.taskId), |
|
169 | 181 | <!-- Note: <TableBodyCell>コンポーネントだとon:inputが動作しない --> |
170 | 182 | <td |
171 | 183 | contenteditable={true} |
172 | | - class="xs:text-lg text-gray-700 dark:text-gray-300 truncate" |
| 184 | + class="xs:text-lg hidden sm:table-cell text-gray-700 dark:text-gray-300 truncate" |
173 | 185 | on:input={(event) => updateComment(index, event)} |
174 | 186 | on:focus={handleFocus} |
175 | 187 | on:blur={handleBlur} |
|
179 | 191 | </td> |
180 | 192 |
|
181 | 193 | <!-- 削除 --> |
182 | | - <TableBodyCell class="px-0" on:click={() => removeWorkBookTask(task)}> |
183 | | - <div class="flex justify-center items-center px-0">削除</div> |
| 194 | + <TableBodyCell class="w-12 xs:w-16"> |
| 195 | + <button |
| 196 | + type="button" |
| 197 | + class="flex justify-center items-center" |
| 198 | + on:click={() => { |
| 199 | + if (confirm('本当に削除しますか?')) { |
| 200 | + try { |
| 201 | + isDeleting = true; |
| 202 | + removeWorkBookTask(task); |
| 203 | + } finally { |
| 204 | + isDeleting = false; |
| 205 | + } |
| 206 | + } |
| 207 | + }} |
| 208 | + disabled={isDeleting} |
| 209 | + > |
| 210 | + <TrashBinOutline class="w-5 h-5 xs:w-6 xs:h-6" /> |
| 211 | + <span class="sr-only">削除</span> |
| 212 | + </button> |
184 | 213 | </TableBodyCell> |
185 | 214 | </TableBodyRow> |
186 | 215 | {/each} |
|
0 commit comments