任务管理系统
++ 任务管理系统 +
+
+
+
@@ -43,9 +55,9 @@
id="searchBtn"
class="btn btn-outline-secondary"
type="button"
- aria-label="Search"
+ data-key-aria="search"
>
-
+
@@ -53,18 +65,17 @@
-
+
- (0) + + 0
@@ -73,13 +84,11 @@
-
+
-
- (0)
+
@@ -87,19 +96,17 @@
- (0)
+
+
+ 0
-
+
-
- (0)
+
${createTaskActions(task)} `;
+ return `${createTaskActions(task)}${description}${createProgress(task)}${createPeople(task)}${createDueDateBlock(task, due)} `;
}
export function updateTaskCounts() {
const tasks = getTasks();
@@ -151,7 +154,8 @@ export function updateTaskCounts() {
const count = tasks.filter((task) => task.status === status && isHiddenTask(task)).length;
button.hidden = count === 0;
button.title = t(getShowHidden(status) ? 'hideHiddenTitle' : 'showHiddenTitle');
- button.querySelector('i').className = `fas fa-${getShowHidden(status) ? 'eye-slash' : 'eye'}`;
+ button.querySelector('i').className =
+ `fa-solid fa-fw fa-${getShowHidden(status) ? 'eye-slash' : 'eye'}`;
});
}
function updateSearchEmptyState(list) {
- (0)
+
+
+ 0
diff --git a/src/js/i18n/en.js b/src/js/i18n/en.js
index 44075df..bfbaf4e 100644
--- a/src/js/i18n/en.js
+++ b/src/js/i18n/en.js
@@ -8,12 +8,14 @@ export default {
exportData: 'Export Data',
importData: 'Import Data',
searchPlaceholder: 'Search tasks...',
+ search: 'Search',
taskTitleLabel: 'Task Title *',
statusLabel: 'Status',
progressNotesLabel: 'Progress Notes',
descriptionLabel: 'Task Description',
priorityLabel: 'Priority',
languageLabel: 'Language',
+ switchLanguage: 'Switch language',
assigneeLabel: 'Assignee',
dueDateLabel: 'Due Date',
collaboratorsLabel: 'Collaborators',
diff --git a/src/js/i18n/index.js b/src/js/i18n/index.js
index 8a3b565..a646c60 100644
--- a/src/js/i18n/index.js
+++ b/src/js/i18n/index.js
@@ -19,6 +19,15 @@ export function applyTranslations(root = document) {
root.querySelectorAll('[data-key-placeholder]').forEach((element) => {
element.placeholder = t(element.dataset.keyPlaceholder);
});
+ // aria-label replaces the accessible name outright, so only use it on icon-only controls.
+ root.querySelectorAll('[data-key-aria]').forEach((element) => {
+ element.setAttribute('aria-label', t(element.dataset.keyAria));
+ });
+ // title only fills in as the accessible name when nothing else provides one, so it is the
+ // safe choice for controls that already show visible text (WCAG 2.5.3 Label in Name).
+ root.querySelectorAll('[data-key-title]').forEach((element) => {
+ element.title = t(element.dataset.keyTitle);
+ });
const button = document.querySelector('#langBtn');
if (button) button.textContent = language === 'zh' ? 'EN' : '中文';
}
diff --git a/src/js/i18n/zh.js b/src/js/i18n/zh.js
index 8187f02..5e86b1c 100644
--- a/src/js/i18n/zh.js
+++ b/src/js/i18n/zh.js
@@ -8,12 +8,14 @@ export default {
exportData: '导出数据',
importData: '导入数据',
searchPlaceholder: '搜索任务...',
+ search: '搜索',
taskTitleLabel: '任务标题 *',
statusLabel: '状态',
progressNotesLabel: '任务进展',
descriptionLabel: '任务描述',
priorityLabel: '优先级',
languageLabel: '语言',
+ switchLanguage: '切换语言',
assigneeLabel: '负责人',
dueDateLabel: '到期日期',
collaboratorsLabel: '协助人员',
diff --git a/src/js/render.js b/src/js/render.js
index 1bec4b5..6ddae2a 100644
--- a/src/js/render.js
+++ b/src/js/render.js
@@ -57,17 +57,20 @@ function createTimeline(task) {
? items
.map(
(item) =>
- `${escapeHtml(item.date)} - ${escapeHtml(item.userType === 'system' ? t('systemUser') : item.user)}${escapeHtml(timelineContent(item))}${t('keyboardHint')}`,
+ `${escapeHtml(item.date)} - ${escapeHtml(item.userType === 'system' ? t('systemUser') : item.user)}${escapeHtml(timelineContent(item))}${t('keyboardHint')}`,
)
.join('')
: `
${escapeHtml(item.date)} - ${escapeHtml(item.userType === 'system' ? t('systemUser') : item.user)}
${escapeHtml(timelineContent(item))}
${t('keyboardHint')}
${escapeHtml(item.date)} - ${escapeHtml(item.userType === 'system' ? t('systemUser') : item.user)}
${escapeHtml(timelineContent(item))}
${t('keyboardHint')}
${t('noTimelineEntries')}
`; - return `${rows}`; + return `${rows}`; } function createTaskActions(task) { const hiddenAction = HIDEABLE_STATUSES.includes(task.status) - ? `` + ? `` : ''; - return `${hiddenAction}
`;
+ const pinClasses = ['btn', 'btn-sm', 'btn-link', 'pin-button', task.isPinned ? 'pinned' : '']
+ .filter(Boolean)
+ .join(' ');
+ return `${hiddenAction}
`;
}
function createPriorityBadge(task) {
return `${t(task.priority)}`;
@@ -134,7 +137,7 @@ function createCard(task) {
const description = task.description
? `${escapeHtml(task.description)}
` : ''; - return `${description}${createProgress(task)}${createPeople(task)}${createDueDateBlock(task, due)}
${t('timeline')} (${task.timeline.length})
${createTimeline(task)}
${escapeHtml(task.title)}
${createPriorityBadge(task)} ${t('timeline')} ${task.timeline.length}
${createTimeline(task)}