Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ <h1 id="headerTitle">📋 Task Manager</h1>
<option value="de">Deutsch</option>
<option value="zh_cn">简体中文</option>
</select>
<label id="autoReloadContainer" style="display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; cursor: pointer; user-select: none; color: var(--text-primary); margin-left: 0.25rem;">
<input type="checkbox" id="autoReloadCheckbox" checked style="cursor: pointer; width: 16px; height: 16px;">
<span id="autoReloadLabel">Auto-Reload</span>
</label>
<select id="projectSelector" style="display: none; padding: 0.6rem 1rem; border: 1px solid var(--border-color); border-radius: 6px; font-size: 0.9rem; background: white; cursor: pointer; min-width: 180px;">
</select>
<button id="renameProjectBtn" class="btn btn-secondary" style="display: none; padding: 0.6rem;" title="Renommer le projet" onclick="renameCurrentProject()">✏️</button>
Expand Down Expand Up @@ -112,6 +116,17 @@ <h3 style="margin-bottom: 1rem;">💡 Comment ça marche ?</h3>
<div id="kanbanView" style="display: none;">
<div id="debugInfo" class="debug-info" style="display: none;"></div>
<div class="kanban-board" id="kanbanBoard"></div>

<!-- Activity Log / History Section -->
<div id="activityLogSection" class="activity-log-section">
<div class="activity-log-header">
<h3 id="activityLogTitle">Recent Activity</h3>
<button id="clearActivityLogBtn" class="btn btn-secondary btn-sm" onclick="clearActivityLog()" style="padding: 0.4rem 0.8rem; font-size: 0.85rem;">Clear</button>
</div>
<div id="activityLogList" class="activity-log-list">
<!-- Log entries will be inserted here dynamically -->
</div>
</div>
</div>

<!-- Modal Task Detail -->
Expand Down
2 changes: 2 additions & 0 deletions src/scripts/01-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
let directoryHandle = null;
let kanbanFileHandle = null;
let currentKanbanContent = '';
let lastKanbanModified = 0;
let tasks = [];
let config = {};
let activeFilters = []; // Array of {type: 'tag'|'category'|'user', value: string}
Expand All @@ -14,3 +15,4 @@
let wasArchiveModalActive = false;
let loadedFormatV2 = false; // true once the loaded kanban.md is V2 (status carried by a **Status** field)
let loadedArchiveV2 = false; // true once the loaded archive.md carries the v2 format marker
let activityLogs = []; // Array of {time: Date, type: string, message: string}
75 changes: 75 additions & 0 deletions src/scripts/02-i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
'header.newTask': '➕ Task',
'header.archives': '📦 Archives',
'header.columns': '⚙️ Columns',
'header.autoReload': 'Auto-Reload',

// Filters
'filters.tags': 'Tags:',
Expand Down Expand Up @@ -137,13 +138,29 @@
'notif.projectDeleted': 'Project removed from list',
'notif.renameError': 'Error renaming',
'notif.projectRestored': 'Project restored automatically',
'notif.fileChangedReloaded': 'kanban.md changed on disk. Board reloaded.',
'notif.taskMoved': 'Task moved!',
'notif.taskEdited': 'Task {id} updated!',
'notif.taskCreated': 'Task {id} created!',
'notif.taskArchived': 'Task archived!',
'notif.taskDeleted': 'Task permanently deleted',
'notif.taskRestored': 'Task restored to its original column!',

// Activity logs
'activity.title': 'Recent Activity',
'activity.clear': 'Clear',
'activity.externalReload': 'Board reloaded automatically from disk.',
'activity.externalCreated': 'Task {id} ("{title}") was created externally.',
'activity.externalMoved': 'Task {id} ("{title}") was moved from "{from}" to "{to}" externally.',
'activity.externalEdited': 'Task {id} ("{title}") was modified externally.',
'activity.externalDeleted': 'Task {id} was deleted externally.',
'activity.localCreated': 'Task {id} ("{title}") created.',
'activity.localMoved': 'Task {id} ("{title}") moved from "{from}" to "{to}".',
'activity.localEdited': 'Task {id} ("{title}") updated.',
'activity.localArchived': 'Task {id} ("{title}") archived.',
'activity.localDeleted': 'Task {id} permanently deleted.',
'activity.localRestored': 'Task {id} ("{title}") restored from archive.',

// Prompts and confirmations
'prompt.projectName': 'Project name (leave empty to use "{name}"):',
'prompt.renameProject': 'New project name:',
Expand Down Expand Up @@ -193,6 +210,7 @@
'header.newTask': '➕ Tâche',
'header.archives': '📦 Archives',
'header.columns': '⚙️ Colonnes',
'header.autoReload': 'Rechargement auto',

// Filters
'filters.tags': 'Tags:',
Expand Down Expand Up @@ -316,13 +334,29 @@
'notif.projectDeleted': 'Projet retiré de la liste',
'notif.renameError': 'Erreur lors du renommage',
'notif.projectRestored': 'Projet restauré automatiquement',
'notif.fileChangedReloaded': 'kanban.md a été modifié sur le disque. Tableau rechargé.',
'notif.taskMoved': 'Tâche déplacée !',
'notif.taskEdited': 'Tâche {id} modifiée !',
'notif.taskCreated': 'Tâche {id} créée !',
'notif.taskArchived': 'Tâche archivée !',
'notif.taskDeleted': 'Tâche supprimée définitivement',
'notif.taskRestored': 'Tâche restaurée dans sa colonne d\'origine !',

// Activity logs
'activity.title': 'Activité Récente',
'activity.clear': 'Effacer',
'activity.externalReload': 'Tableau rechargé automatiquement depuis le disque.',
'activity.externalCreated': 'La tâche {id} ("{title}") a été créée de l\'extérieur.',
'activity.externalMoved': 'La tâche {id} ("{title}") a été déplacée de "{from}" à "{to}" de l\'extérieur.',
'activity.externalEdited': 'La tâche {id} ("{title}") a été modifiée de l\'extérieur.',
'activity.externalDeleted': 'La tâche {id} a été supprimée de l\'extérieur.',
'activity.localCreated': 'Tâche {id} ("{title}") créée.',
'activity.localMoved': 'Tâche {id} ("{title}") déplacée de "{from}" à "{to}".',
'activity.localEdited': 'Tâche {id} ("{title}") mise à jour.',
'activity.localArchived': 'Tâche {id} ("{title}") archivée.',
'activity.localDeleted': 'Tâche {id} supprimée définitivement.',
'activity.localRestored': 'Tâche {id} ("{title}") restaurée depuis l\'archive.',

// Prompts and confirmations
'prompt.projectName': 'Nom du projet (laisser vide pour utiliser "{name}") :',
'prompt.renameProject': 'Nouveau nom du projet :',
Expand Down Expand Up @@ -372,6 +406,7 @@
'header.newTask': '➕ Aufgabe',
'header.archives': '📦 Archiv',
'header.columns': '⚙️ Spalten',
'header.autoReload': 'Auto-Reload',

// Filters
'filters.tags': 'Tags:',
Expand Down Expand Up @@ -495,13 +530,29 @@
'notif.projectDeleted': 'Projekt aus Liste gelöscht',
'notif.renameError': 'Problem beim Umbenennen',
'notif.projectRestored': 'Projekt erfolgreich wiederhergestellt',
'notif.fileChangedReloaded': 'kanban.md auf dem Datenträger geändert. Board neu geladen.',
'notif.taskMoved': 'Aufgabe verschoben!',
'notif.taskEdited': 'Aufgabe {id} aktualisiert!',
'notif.taskCreated': 'Aufgabe {id} erstellt!',
'notif.taskArchived': 'Aufgabe archiviert!',
'notif.taskDeleted': 'Aufgabe endgültig gelöscht',
'notif.taskRestored': 'Aufgabe in ursprünglicher Spalte zurückgesetzt!',

// Activity logs
'activity.title': 'Letzte Aktivitäten',
'activity.clear': 'Leeren',
'activity.externalReload': 'Board automatisch vom Datenträger neu geladen.',
'activity.externalCreated': 'Aufgabe {id} ("{title}") wurde extern erstellt.',
'activity.externalMoved': 'Aufgabe {id} ("{title}") wurde extern von "{from}" nach "{to}" verschoben.',
'activity.externalEdited': 'Aufgabe {id} ("{title}") wurde extern geändert.',
'activity.externalDeleted': 'Aufgabe {id} wurde extern gelöscht.',
'activity.localCreated': 'Aufgabe {id} ("{title}") erstellt.',
'activity.localMoved': 'Aufgabe {id} ("{title}") von "{from}" nach "{to}" verschoben.',
'activity.localEdited': 'Aufgabe {id} ("{title}") aktualisiert.',
'activity.localArchived': 'Aufgabe {id} ("{title}") archiviert.',
'activity.localDeleted': 'Aufgabe {id} endgültig gelöscht.',
'activity.localRestored': 'Aufgabe {id} ("{title}") aus dem Archiv wiederhergestellt.',

// Prompts and confirmations
'prompt.projectName': 'Projektname (leer lassen, um "{name}" zu verwenden):',
'prompt.renameProject': 'Neuer Projektname:',
Expand Down Expand Up @@ -550,6 +601,7 @@
'header.newTask': '➕ 任务',
'header.archives': '📦 归档',
'header.columns': '⚙️ 列设置',
'header.autoReload': '自动重新加载',

// Filters
'filters.tags': '标签:',
Expand Down Expand Up @@ -673,13 +725,29 @@
'notif.projectDeleted': '项目已从列表中移除',
'notif.renameError': '重命名时出错',
'notif.projectRestored': '项目已自动恢复',
'notif.fileChangedReloaded': 'kanban.md 已在磁盘上更改。看板已重新加载。',
'notif.taskMoved': '任务已移动!',
'notif.taskEdited': '任务 {id} 已更新!',
'notif.taskCreated': '任务 {id} 已创建!',
'notif.taskArchived': '任务已归档!',
'notif.taskDeleted': '任务已永久删除',
'notif.taskRestored': '任务已恢复至原列!',

// Activity logs
'activity.title': '最近动态',
'activity.clear': '清空',
'activity.externalReload': '看板已自动从磁盘重新加载。',
'activity.externalCreated': '任务 {id} ("{title}") 已在外部创建。',
'activity.externalMoved': '任务 {id} ("{title}") 已在外部从 "{from}" 移动到 "{to}"。',
'activity.externalEdited': '任务 {id} ("{title}") 已在外部修改。',
'activity.externalDeleted': '任务 {id} 已在外部删除。',
'activity.localCreated': '任务 {id} ("{title}") 已创建。',
'activity.localMoved': '任务 {id} ("{title}") 已从 "{from}" 移动到 "{to}"。',
'activity.localEdited': '任务 {id} ("{title}") 已更新。',
'activity.localArchived': '任务 {id} ("{title}") 已归档。',
'activity.localDeleted': '任务 {id} 已永久删除。',
'activity.localRestored': '任务 {id} ("{title}") 已从归档中恢复。',

// Prompts and confirmations
'prompt.projectName': '项目名称 (留空则使用 "{name}"):',
'prompt.renameProject': '新项目名称:',
Expand Down Expand Up @@ -840,6 +908,13 @@
document.getElementById('newTaskBtn').innerHTML = t('header.newTask');
document.getElementById('archiveBtn').innerHTML = t('header.archives');
document.getElementById('manageColsBtn').innerHTML = t('header.columns');
document.getElementById('autoReloadLabel').textContent = t('header.autoReload');

// Update activity log static texts
const activityLogTitle = document.getElementById('activityLogTitle');
if (activityLogTitle) activityLogTitle.textContent = t('activity.title');
const clearActivityLogBtn = document.getElementById('clearActivityLogBtn');
if (clearActivityLogBtn) clearActivityLogBtn.textContent = t('activity.clear');

// Update language selector to match current language
document.getElementById('languageSelector').value = currentLanguage;
Expand Down
78 changes: 78 additions & 0 deletions src/scripts/03-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,45 @@
await switchProject(projectIndex);
}
});

// Start background poller to auto-reload kanban.md when it changes externally
setInterval(async () => {
if (!kanbanFileHandle) return;

// Only poll if auto-reload is enabled
const autoReloadCheckbox = document.getElementById('autoReloadCheckbox');
if (autoReloadCheckbox && !autoReloadCheckbox.checked) return;

try {
const file = await kanbanFileHandle.getFile();
if (file.lastModified > lastKanbanModified) {
const newText = await file.text();
if (newText !== currentKanbanContent) {
console.log('kanban.md changed on disk. Reloading...');
await loadKanbanFile();

// If the details modal is open, refresh it with the updated task data
const modal = document.getElementById('taskModal');
if (modal && modal.classList.contains('active') && currentDetailTask) {
const updatedTask = tasks.find(t => t.id === currentDetailTask.id);
if (updatedTask) {
showTaskDetail(updatedTask);
} else {
closeModal();
}
}

showNotification(t('notif.fileChangedReloaded'), 'info');
} else {
// Only lastModified changed (e.g. metadata touched), content is same.
lastKanbanModified = file.lastModified;
}
}
} catch (error) {
// Suppress check errors since they might be temporary lock/permission prompts
console.debug('Error checking kanban.md changes:', error);
}
}, 1000);
});

// Select folder
Expand Down Expand Up @@ -347,4 +386,43 @@
}
});

function logActivity(type, message) {
const entry = {
time: new Date(),
type: type,
message: message
};
activityLogs.unshift(entry);
if (activityLogs.length > 50) {
activityLogs.pop();
}
renderActivityLog();
}

function renderActivityLog() {
const listEl = document.getElementById('activityLogList');
if (!listEl) return;

listEl.innerHTML = activityLogs.map(log => {
const timeStr = log.time.toLocaleTimeString();
let borderClass = '';
if (log.type === 'reload') borderClass = 'reload';
else if (log.type === 'create') borderClass = 'create';
else if (log.type === 'delete' || log.type === 'archive') borderClass = 'delete';
else if (log.type === 'edit' || log.type === 'move') borderClass = 'edit';

return `
<div class="activity-log-item ${borderClass}">
<span class="activity-log-time">[${timeStr}]</span>
<span class="activity-log-msg">${escapeHtml(log.message)}</span>
</div>
`;
}).join('');
}

function clearActivityLog() {
activityLogs = [];
renderActivityLog();
}

// Load kanban.md file
Loading