diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9cc1eae0..ba29f2a8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -95,7 +95,7 @@ npm run dev # Should start watch mode and display "watching for changes..." │ │ ├── Column.tsx # Kanban column component │ │ ├── TaskItem.tsx # Individual task component (26k lines - complex) │ │ ├── KanbanBoard.tsx # Main board component -│ │ ├── TaskBoardViewContent.tsx # Main board content wrapper +│ │ ├── TaskBoardViewContainer.tsx # Main board content wrapper │ │ └── MapView.tsx # Map-based view component │ ├── interfaces/ # TypeScript interfaces (3 files) │ │ ├── TaskItem.ts # Task data structures diff --git a/README.md b/README.md index e866b6c2..d27ff79b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@
An Obsidian plugin to view and manage all your tasks, throughout the vault on a centralized board using various kinds of views. Use boards to manage small to large projects.
- + > YouTube playlist : [Task Board - Obsidian plugin](https://youtube.com/playlist?list=PLqEPxsDi1dtepfcaUO9r1BTGZN6IuhvvH&si=yohu1rczpRVq68D6) @@ -51,7 +51,7 @@ Join the forum top to share your thoughts, ideas or requests and hear from other **Step 3 :** Click on the **Scan vault modal** button from the top-right corner in the Task Board view header. Then click on the run button and it will scan all your files to look for tasks. If your vault contains thousands of notes, you can apply [scanning filters](https://tu2-atmanand.github.io/task-board-docs/docs/Features/Filters_for_Scanning/) to exclude certain files from scanning. -**Step 4 :** There are already three predefined board for your convenience as an example. Feel free to delete or edit the boards and [create your own boards](https://tu2-atmanand.github.io/task-board-docs/docs/How_To/HowToCreateNewBoard/) from the [Board configure modal](https://tu2-atmanand.github.io/task-board-docs/docs/How_To/HowToUseBoardSettings/). +**Step 4 :** There are already three predefined board for your convenience as an example. Feel free to delete or edit the boards and [create your own boards](https://tu2-atmanand.github.io/task-board-docs/docs/How_To/HowToCreateNewBoard/) from the [Board configure modal](https://tu2-atmanand.github.io/task-board-docs/docs/How_To/HowToUseBoardSettings/). Enjoy ! @@ -124,11 +124,11 @@ This advanced and dynamic filters will help you to use boards as separate projec You can contribute to this project by : -**1. Requesting a new feature, suggesting an improvement or reporting a Bug :** [How to create a new request](https://tu2-atmanand.github.io/task-board-docs/Advanced/HowToCreateRequest.html). +**1. Requesting a new feature, suggesting an improvement or reporting a Bug :** [How to create a new request](https://tu2-atmanand.github.io/task-board-docs/docs/Advanced/HowToCreateRequest). -**2. Improving the translated languages or add a new language :** [How to Contribute for Language Translation](https://tu2-atmanand.github.io/task-board-docs/Advanced/Contribution_For_Languages.html). +**2. Improving the translated languages or add a new language :** [How to Contribute for Language Translation](https://tu2-atmanand.github.io/task-board-docs/docs/Advanced/Contribution_For_Languages). -**3. Contribute to the Development of the plugin Code :** : [How to join the plugin development](https://tu2-atmanand.github.io/task-board-docs/Advanced/HowToJoinDevelopment.html). +**3. Contribute to the Development of the plugin Code :** : [How to join the plugin development](https://tu2-atmanand.github.io/task-board-docs/docs/Advanced/HowToJoinDevelopment). ## Motivation for the Project diff --git a/assets/MainThumbnail-3.jpg b/assets/MainThumbnail-3.jpg deleted file mode 100644 index cff0c156..00000000 Binary files a/assets/MainThumbnail-3.jpg and /dev/null differ diff --git a/assets/MainThumbnail-4.png b/assets/MainThumbnail-4.png new file mode 100644 index 00000000..fdffde10 Binary files /dev/null and b/assets/MainThumbnail-4.png differ diff --git a/backup-data.json b/backup-data.json new file mode 100644 index 00000000..9dc12541 --- /dev/null +++ b/backup-data.json @@ -0,0 +1,291 @@ +{ + "version": "2.0.0-beta-2", + "data": { + "lang": "en", + "openOnStartup": false, + "scanFilters": { + "files": { + "polarity": 3, + "values": [] + }, + "folders": { + "polarity": 3, + "values": [] + }, + "frontmatter": { + "polarity": 3, + "values": [] + }, + "tags": { + "polarity": 3, + "values": [] + } + }, + "firstDayOfWeek": "Mon", + "showTaskWithoutMetadata": true, + "ignoreFileNameDates": false, + "taskPropertyFormat": "2", + "dailyNotesPluginComp": false, + "dateFormat": "yyyy-MM-dd", + "dateTimeFormat": "yyyy-MM-dd'T'HH:mm:ss", + "defaultStartTime": "", + "taskCompletionInLocalTime": true, + "taskCompletionShowUtcOffset": false, + "autoAddUniversalDate": true, + "autoAddCreatedDate": false, + "autoAddCompletedDate": false, + "autoAddCancelledDate": false, + "showModifiedFilesNotice": true, + "scanMode": "automatic", + "columnWidth": "300px", + "visiblePropertiesList": [ + "title", + "tags", + "time", + "reminder", + "createdDate", + "startDate", + "scheduledDate", + "dueDate", + "completionDate", + "cancelledDate", + "dependsOn", + "status", + "id" + ], + "taskCardStyle": "emoji", + "showVerticalScroll": false, + "tagColors": [ + { + "name": "bug", + "color": "rgba(255, 0, 0, 0.55)", + "priority": 1 + }, + { + "name": "important", + "color": "rgba(246, 255, 0, 0.53)", + "priority": 2 + }, + { + "name": "wip", + "color": "rgba(0, 255, 0, 0.53)", + "priority": 2 + }, + { + "name": "review", + "color": "rgba(0, 0, 255, 0.49)", + "priority": 3 + } + ], + "editButtonAction": "popUp", + "doubleClickCardToEdit": "none", + "universalDate": "due", + "tagColorsType": "tagBg", + "customStatuses": [ + { + "symbol": " ", + "name": "Todo", + "nextStatusSymbol": "x", + "availableAsCommand": false, + "type": "TODO" + }, + { + "symbol": "<", + "name": "Ready to start", + "nextStatusSymbol": "x", + "availableAsCommand": false, + "type": "TODO" + }, + { + "symbol": "?", + "name": "In Review", + "nextStatusSymbol": "x", + "availableAsCommand": false, + "type": "TODO" + }, + { + "symbol": "/", + "name": "In Progress", + "nextStatusSymbol": "x", + "availableAsCommand": true, + "type": "IN_PROGRESS" + }, + { + "symbol": "x", + "name": "Done", + "nextStatusSymbol": " ", + "availableAsCommand": true, + "type": "IN_PROGRESS" + }, + { + "symbol": "X", + "name": "Completed", + "nextStatusSymbol": " ", + "availableAsCommand": true, + "type": "IN_PROGRESS" + }, + { + "symbol": "-", + "name": "Cancelled", + "nextStatusSymbol": "x", + "availableAsCommand": true, + "type": "CANCELLED" + }, + { + "symbol": "*", + "name": "Now", + "nextStatusSymbol": "r", + "availableAsCommand": false, + "type": "IN_PROGRESS" + } + ], + "compatiblePlugins": { + "dailyNotesPlugin": false, + "dayPlannerPlugin": false, + "tasksPlugin": false, + "reminderPlugin": false, + "quickAddPlugin": false + }, + "taskNoteIdentifierTag": "taskNote", + "preDefinedNote": "Meta/Task_Board/New_Tasks.md", + "archivedTasksFilePath": "", + "taskNoteDefaultLocation": "Meta/Task_Board/Task_Notes", + "archivedTBNotesFolderPath": "Meta/Task_Board/Archived_Task_Notes", + "quickAddPluginDefaultChoice": "", + "frontmatterFormatting": [ + { + "index": 0, + "property": "ID", + "key": "id", + "taskItemKey": "id" + }, + { + "index": 1, + "property": "Title", + "key": "title", + "taskItemKey": "title" + }, + { + "index": 2, + "property": "Status", + "key": "status", + "taskItemKey": "status" + }, + { + "index": 3, + "property": "Priority", + "key": "priority", + "taskItemKey": "priority" + }, + { + "index": 4, + "property": "Tags", + "key": "tags", + "taskItemKey": "tags" + }, + { + "index": 5, + "property": "Time", + "key": "time", + "taskItemKey": "time" + }, + { + "index": 6, + "property": "Reminder", + "key": "reminder", + "taskItemKey": "reminder" + }, + { + "index": 7, + "property": "Created date", + "key": "created-date", + "taskItemKey": "createdDate" + }, + { + "index": 8, + "property": "Start date", + "key": "start-date", + "taskItemKey": "startDate" + }, + { + "index": 9, + "property": "Scheduled date", + "key": "scheduled-date", + "taskItemKey": "scheduledDate" + }, + { + "index": 10, + "property": "Due date", + "key": "due-date", + "taskItemKey": "due" + }, + { + "index": 11, + "property": "Depends on", + "key": "depends-on", + "taskItemKey": "dependsOn" + }, + { + "index": 12, + "property": "Completed date", + "key": "cancelled-date", + "taskItemKey": "cancelledDate" + }, + { + "index": 13, + "key": "completed-date", + "taskItemKey": "completionDate" + } + ], + "showFrontmatterTagsOnCards": false, + "tasksCacheFilePath": "", + "notificationService": "none", + "actions": [ + { + "enabled": true, + "trigger": "Complete", + "type": "move", + "targetColumn": "Completed" + } + ], + "hiddenTaskProperties": [], + "autoAddUniqueID": false, + "uniqueIdCounter": 670, + "experimentalFeatures": false, + "safeGuardFeature": true, + "lastViewHistory": { + "boardFilePath": "Meta/Task_Board/Boards/Time Based Workflow.taskboard", + "settingTab": 2, + "taskId": "" + }, + "boundTaskCompletionToChildTasks": false, + "mapView": { + "background": "none", + "mapOrientation": "hor", + "optimizedRender": false, + "arrowDirection": "c2p", + "animatedEdges": false, + "scrollAction": "zoom", + "showMinimap": true, + "renderVisibleNodes": false, + "edgeType": "default" + }, + "taskBoardFilesRegistry": { + "901052398": { + "boardId": "901052398", + "filePath": "TaskBoard-Template-1774097758616.taskboard", + "boardName": "My Project", + "boardDescription": "This is my personal project. This is a default board created by Task Board for you to kick start your journey with Task Board. Feel free to edit or create new boards." + }, + "2908513791": { + "boardId": "2908513791", + "filePath": "My Project Board.taskboard", + "boardName": "My Project", + "boardDescription": "This is my personal project. This is a default board created by Task Board for you to kick start your journey with Task Board. Feel free to edit or create new boards." + } + }, + "loadAllBoards": false, + "searchQuery": "", + "dragAutoScrollEdgePercent": 20 + } +} \ No newline at end of file diff --git a/data.json b/data.json index c9c0770c..dd92d18e 100644 --- a/data.json +++ b/data.json @@ -1,1193 +1,334 @@ { - "version": "1.8.7", + "version": "2.0.0-beta-2", "data": { - "boardConfigs": [ + "lang": "en", + "openOnStartup": false, + "scanFilters": { + "files": { + "polarity": 3, + "values": [ + "A new file added.md", + "Task_board_note.md" + ] + }, + "folders": { + "polarity": 3, + "values": [ + "Daily_Notes" + ] + }, + "frontmatter": { + "polarity": 3, + "values": [ + "[\"publish\": true]" + ] + }, + "tags": { + "polarity": 3, + "values": [ + "#placeholder" + ] + } + }, + "firstDayOfWeek": "Mon", + "showTaskWithoutMetadata": true, + "ignoreFileNameDates": false, + "taskPropertyFormat": "2", + "dateFormat": "yyyy-MM-dd", + "dateTimeFormat": "yyyy-MM-dd'T'HH:mm:ss", + "dailyNotesPluginComp": false, + "defaultStartTime": "", + "taskCompletionInLocalTime": true, + "taskCompletionShowUtcOffset": false, + "autoAddUniversalDate": true, + "autoAddCreatedDate": true, + "autoAddCompletedDate": true, + "autoAddCancelledDate": false, + "showModifiedFilesNotice": true, + "scanMode": "automatic", + "columnWidth": "340px", + "visiblePropertiesList": [ + "checkbox", + "id", + "title", + "subTasksMinimized", + "descriptionMinimized", + "status", + "tags", + "time", + "reminder", + "priority", + "createdDate", + "startDate", + "scheduledDate", + "dueDate", + "completionDate", + "cancelledDate", + "dependsOn", + "filePath" + ], + "taskCardStyle": "bases", + "showVerticalScroll": false, + "dragAutoScrollEdgePercent": 20, + "tagColors": [ + { + "name": "bug", + "color": "rgba(167, 6, 6, 0.71)", + "priority": 1 + }, + { + "name": "important", + "color": "rgba(161, 167, 9, 0.79)", + "priority": 2 + }, + { + "name": "wip", + "color": "rgba(8, 149, 8, 0.78)", + "priority": 2 + }, + { + "name": "review", + "color": "rgba(22, 22, 181, 0.84)", + "priority": 3 + }, + { + "name": "feat", + "color": "rgba(69, 15, 183, 1)", + "priority": 5 + } + ], + "editButtonAction": "popUp", + "doubleClickCardToEdit": "noteInTab", + "universalDate": "due", + "tagColorsType": "tagBg", + "customStatuses": [ + { + "symbol": " ", + "name": "Todo", + "nextStatusSymbol": "x", + "availableAsCommand": false, + "type": "TODO" + }, + { + "symbol": "<", + "name": "Ready to start", + "nextStatusSymbol": "x", + "availableAsCommand": false, + "type": "TODO" + }, + { + "symbol": "?", + "name": "In Review", + "nextStatusSymbol": "x", + "availableAsCommand": false, + "type": "TODO" + }, + { + "symbol": "/", + "name": "In Progress", + "nextStatusSymbol": "x", + "availableAsCommand": true, + "type": "IN_PROGRESS" + }, { - "name": "Path filtered", - "index": 0, - "columns": [ - { - "id": 3061361157, - "index": 1, - "colType": "pathFiltered", - "active": true, - "collapsed": false, - "name": "TaskNotes", - "filePaths": "TaskNotes/", - "sortCriteria": [ - { - "criteria": "manualOrder", - "order": "asc", - "priority": 1, - "uid": "ur7dguyt" - } - ], - "tasksIdManualOrder": [ - "367", - "372", - "398696308", - "373", - "370", - "3077781059", - "4184100420", - "3052793295", - "1506909345", - 274, - "4031700447", - "362" - ] - }, - { - "id": 1312742179, - "index": 2, - "colType": "pathFiltered", - "active": true, - "collapsed": false, - "name": "Indented Bug", - "filePaths": "Testing Indented Task Bug.md", - "filters": { - "rootCondition": "any", - "filterGroups": [] - } - }, - { - "id": 2025781698, - "index": 3, - "colType": "pathFiltered", - "active": true, - "collapsed": false, - "name": "QMD", - "filePaths": "A QMD file.qmd" - }, - { - "id": 1623496994, - "index": 4, - "colType": "completed", - "active": true, - "collapsed": false, - "name": "Completed", - "limit": 20 - } - ], - "hideEmptyColumns": false, - "boardFilter": { - "rootCondition": "any", - "filterGroups": [] - }, - "taskCount": { - "pending": 65, - "completed": 19 - } + "symbol": "x", + "name": "Done", + "nextStatusSymbol": " ", + "availableAsCommand": true, + "type": "DONE" }, { - "columns": [ - { - "id": 1799377278, - "index": 1, - "colType": "undated", - "active": true, - "collapsed": false, - "name": "Backlog", - "datedBasedColumn": { - "from": 0, - "to": 0, - "dateType": "scheduledDate" - }, - "filters": { - "rootCondition": "any", - "filterGroups": [] - } - }, - { - "id": 3492, - "colType": "dated", - "active": true, - "collapsed": false, - "name": "Over Due", - "index": 2, - "datedBasedColumn": { - "dateType": "scheduledDate", - "from": -300, - "to": -1 - }, - "sortCriteria": [ - { - "criteria": "scheduledDate", - "order": "asc", - "priority": 1 - }, - { - "criteria": "time", - "order": "asc", - "priority": 2 - } - ], - "filters": { - "rootCondition": "any", - "filterGroups": [] - } - }, - { - "id": 3494, - "colType": "dated", - "active": true, - "collapsed": false, - "name": "Today", - "index": 3, - "datedBasedColumn": { - "dateType": "scheduledDate", - "from": 0, - "to": 0 - }, - "sortCriteria": [ - { - "criteria": "time", - "order": "asc", - "priority": 1 - } - ], - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "minimized": true - }, - { - "id": 3493, - "colType": "dated", - "active": true, - "collapsed": false, - "name": "Tomorrow", - "index": 4, - "datedBasedColumn": { - "dateType": "scheduledDate", - "from": 1, - "to": 1 - } - }, - { - "id": 3495, - "colType": "dated", - "active": true, - "collapsed": false, - "name": "Future", - "index": 5, - "datedBasedColumn": { - "dateType": "scheduledDate", - "from": 2, - "to": 300 - } - }, - { - "id": 3497, - "colType": "completed", - "active": true, - "collapsed": false, - "limit": 20, - "name": "Completed, updated", - "index": 6, - "filters": { - "rootCondition": "any", - "filterGroups": [] - } - } - ], - "filters": [ - "#Test", - "#working", - "#new" - ], - "filterPolarity": "0", - "filterScope": "Both", - "name": "Time based workflow", + "symbol": "X", + "name": "Completed", + "nextStatusSymbol": " ", + "availableAsCommand": true, + "type": "DONE" + }, + { + "symbol": "-", + "name": "Cancelled", + "nextStatusSymbol": "x", + "availableAsCommand": true, + "type": "CANCELLED" + } + ], + "compatiblePlugins": { + "dailyNotesPlugin": false, + "dayPlannerPlugin": false, + "tasksPlugin": false, + "reminderPlugin": false, + "quickAddPlugin": false + }, + "taskNoteIdentifierTag": "taskNote", + "preDefinedNote": "Meta/Task_Board/New_Tasks.md", + "archivedTasksFilePath": "", + "taskNoteDefaultLocation": "Meta/Task_Board/Task_Notes", + "archivedTBNotesFolderPath": "Meta/Task_Board/Archived_Task_Notes", + "quickAddPluginDefaultChoice": "", + "frontmatterFormatting": [ + { "index": 1, - "showColumnTags": true, - "showFilteredTags": false, - "hideEmptyColumns": false, - "boardFilter": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1763871967163-8a21jnuiq", - "groupCondition": "any", - "filters": [ - { - "id": "id-1763871967165-lct3l84v6", - "property": "tags", - "condition": "doesNotContain", - "value": "#task" - }, - { - "id": "id-1763871980225-nbzhu6dnv", - "property": "tags", - "condition": "doesNotContain", - "value": "#taskNote" - } - ] - } - ] - }, - "description": "Project to manage all the tasks related to plugin release.", - "taskCount": { - "pending": 4, - "completed": 1 - } + "property": "ID", + "key": "id", + "taskItemKey": "id" }, { - "columns": [ - { - "id": 3187486162, - "index": 1, - "colType": "otherTags", - "active": true, - "collapsed": false, - "name": "Other tags", - "minimized": false - }, - { - "colType": "untagged", - "active": true, - "collapsed": false, - "name": "Backlogs", - "index": 2, - "id": 226119, - "sortCriterias": [], - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "sortCriteria": [ - { - "criteria": "id", - "order": "desc", - "priority": 1, - "uid": "k6wjlc63" - } - ], - "minimized": false - }, - { - "id": 2485661779, - "index": 3, - "colType": "namedTag", - "active": false, - "collapsed": false, - "name": "#Task", - "coltag": "#Task", - "sortCriteria": [ - { - "criteria": "content", - "order": "asc", - "priority": 1, - "uid": "bmlpsogj" - } - ], - "tasksIdManualOrder": [ - "365", - 274, - "362" - ], - "minimized": false, - "filters": { - "rootCondition": "any", - "filterGroups": [] - } - }, - { - "colType": "namedTag", - "active": true, - "collapsed": false, - "name": "Can be implemented", - "index": 4, - "coltag": "#pending", - "id": 47626, - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "minimized": false, - "sortCriteria": [ - { - "criteria": "manualOrder", - "order": "asc", - "priority": 1, - "uid": "285zohor" - } - ], - "tasksIdManualOrder": [ - "224", - "276", - "389", - "378", - "355", - "258", - "375" - ] - }, - { - "id": 3130768414, - "index": 5, - "colType": "namedTag", - "active": true, - "collapsed": false, - "name": "In Progress", - "coltag": "wip" - }, - { - "colType": "namedTag", - "active": true, - "collapsed": false, - "name": "Ready to publish", - "index": 6, - "coltag": "done", - "id": 328227, - "minimized": false - }, - { - "id": 1936828579, - "index": 7, - "colType": "namedTag", - "active": false, - "collapsed": false, - "name": "*/seeding/*", - "coltag": "*/seeding/*", - "minimized": false - }, - { - "id": 3193261849, - "index": 8, - "colType": "completed", - "active": true, - "collapsed": false, - "name": "Completed", - "limit": 20, - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "minimized": false - } - ], - "filters": [ - "#Test", - "#working", - "*/seeding/*" - ], - "filterPolarity": "0", - "filterScope": "Both", - "name": "Tag based board", "index": 2, - "showColumnTags": true, - "showFilteredTags": true, - "hideEmptyColumns": false, - "boardFilter": { - "rootCondition": "all", - "filterGroups": [] - }, - "filterConfig": { - "enableSavedFilters": true, - "savedConfigs": [ - { - "id": "filter-config-1760103432145-fevybeh8j", - "name": "Destinatio", - "description": "Filtering all tasks from \"Destinatio\" folder", - "filterState": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1760029960520-z2a1g0ka4", - "groupCondition": "all", - "filters": [ - { - "id": "id-1760029960521-t33fwt542", - "property": "filePath", - "condition": "startsWith", - "value": "Destinatio" - } - ] - } - ] - }, - "createdAt": "2025-10-10T13:37:12.145Z", - "updatedAt": "2025-10-10T13:37:12.145Z" - }, - { - "id": "filter-config-1764176875594-8gv5ee69p", - "name": "This", - "description": "If \"This\" is present in the content.", - "filterState": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1760365709344-buyyxwhhh", - "groupCondition": "all", - "filters": [ - { - "id": "id-1760365709345-8uyz54hm3", - "property": "content", - "condition": "contains", - "value": "This" - } - ] - } - ] - }, - "createdAt": "2025-11-26T17:07:55.594Z", - "updatedAt": "2025-11-26T17:07:55.594Z" - }, - { - "id": "filter-config-1764176933714-tm6ozdue7", - "name": "This and No", - "description": "Content contains this and no", - "filterState": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1760029960520-z2a1g0ka4", - "groupCondition": "all", - "filters": [ - { - "id": "id-1760029960521-t33fwt542", - "property": "content", - "condition": "contains", - "value": "This" - } - ] - }, - { - "id": "id-1764176913802-bwvk75kr4", - "groupCondition": "all", - "filters": [ - { - "id": "id-1764176913803-1ugczxozs", - "property": "content", - "condition": "contains", - "value": "No" - } - ] - } - ] - }, - "createdAt": "2025-11-26T17:08:53.714Z", - "updatedAt": "2025-11-26T17:08:53.714Z" - }, - { - "id": "filter-config-1764177013691-5uj7krjmm", - "name": "This and No, both", - "description": "Both", - "filterState": { - "rootCondition": "all", - "filterGroups": [ - { - "id": "id-1760029960520-z2a1g0ka4", - "groupCondition": "all", - "filters": [ - { - "id": "id-1760029960521-t33fwt542", - "property": "content", - "condition": "contains", - "value": "This" - } - ] - }, - { - "id": "id-1764176913802-bwvk75kr4", - "groupCondition": "all", - "filters": [ - { - "id": "id-1764176913803-1ugczxozs", - "property": "content", - "condition": "contains", - "value": "No" - } - ] - } - ] - }, - "createdAt": "2025-11-26T17:10:13.691Z", - "updatedAt": "2025-11-26T17:10:13.691Z" - } - ] - }, - "taskCount": { - "pending": 63, - "completed": 14 - }, - "swimlanes": { - "enabled": true, - "hideEmptySwimlanes": false, - "property": "tags", - "maxHeight": "500px", - "sortCriteria": "custom", - "customSortOrder": [ - { - "value": "first", - "index": 1 - }, - { - "value": "second", - "index": 2 - }, - { - "value": "third", - "index": 3 - } - ], - "groupAllRest": true, - "verticalHeaderUI": true, - "minimized": [] - } + "property": "Title", + "key": "title", + "taskItemKey": "title" }, { - "columns": [ - { - "colType": "untagged", - "active": true, - "collapsed": false, - "name": "Backlogs", - "index": 1, - "id": 226119, - "sortCriterias": [], - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "sortCriteria": [ - { - "criteria": "content", - "order": "desc", - "priority": 1, - "uid": "k6wjlc63" - } - ], - "minimized": false - }, - { - "id": 2485661779, - "index": 2, - "colType": "namedTag", - "active": false, - "collapsed": false, - "name": "#Task", - "coltag": "#Task", - "sortCriteria": [ - { - "criteria": "content", - "order": "asc", - "priority": 1, - "uid": "bmlpsogj" - } - ], - "tasksIdManualOrder": [ - "365", - 274, - "362" - ], - "minimized": false, - "filters": { - "rootCondition": "any", - "filterGroups": [] - } - }, - { - "id": 1936828579, - "index": 3, - "colType": "namedTag", - "active": false, - "collapsed": false, - "name": "*/seeding/*", - "coltag": "*/seeding/*" - }, - { - "colType": "namedTag", - "active": true, - "collapsed": false, - "name": "Can be implemented", - "index": 4, - "coltag": "#pending", - "id": 47626, - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "minimized": false, - "sortCriteria": [ - { - "criteria": "manualOrder", - "order": "asc", - "priority": 1, - "uid": "pn3wvwal" - } - ], - "tasksIdManualOrder": [ - "375", - "389", - "224", - "355", - "276", - "258", - "378" - ] - }, - { - "colType": "namedTag", - "active": true, - "collapsed": false, - "name": "In Progress", - "index": 5, - "coltag": "working", - "id": 908753, - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "minimized": false, - "workLimit": 5 - }, - { - "colType": "namedTag", - "active": true, - "collapsed": false, - "name": "In Review", - "index": 6, - "coltag": "Test", - "id": 396902, - "minimized": false, - "workLimit": 3 - }, - { - "colType": "namedTag", - "active": true, - "collapsed": false, - "name": "Ready to publish", - "index": 7, - "coltag": "done", - "id": 328227, - "minimized": false - }, - { - "id": 3193261849, - "index": 8, - "colType": "completed", - "active": true, - "collapsed": false, - "name": "Completed", - "limit": 20, - "filters": { - "rootCondition": "any", - "filterGroups": [] - }, - "minimized": false - } - ], - "filters": [ - "#Test", - "#working", - "*/seeding/*" - ], - "filterPolarity": "0", - "filterScope": "Both", - "name": "Tag based board (copy)", "index": 3, - "showColumnTags": true, - "showFilteredTags": true, - "hideEmptyColumns": false, - "boardFilter": { - "rootCondition": "any", - "filterGroups": [] - }, - "taskCount": { - "pending": 60, - "completed": 15 - }, - "swimlanes": { - "enabled": false, - "showEmptySwimlanes": true, - "property": "tags", - "maxHeight": "500px", - "sortCriteria": "custom", - "customSortOrder": [ - { - "value": "first", - "index": 1 - }, - { - "value": "second", - "index": 2 - }, - { - "value": "third", - "index": 3 - } - ], - "groupAllRest": true, - "verticalHeaderUI": false - } + "property": "Status", + "key": "status", + "taskItemKey": "status" }, { - "name": "Only column filters", "index": 4, - "columns": [ - { - "id": 2050194674, - "index": 1, - "colType": "allPending", - "active": true, - "collapsed": false, - "name": "Backlogs", - "filters": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1768140055334-efs743ce1", - "groupCondition": "all", - "filters": [ - { - "id": "id-1768140055335-r98019pcb", - "property": "tags", - "condition": "isEmpty" - } - ] - } - ] - } - }, - { - "id": 850996788, - "index": 2, - "colType": "allPending", - "active": true, - "collapsed": false, - "name": "Important", - "filters": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1768140070366-ymlrpxfd5", - "groupCondition": "all", - "filters": [ - { - "id": "id-1768140070367-26fzfh3fi", - "property": "tags", - "condition": "contains", - "value": "#important" - } - ] - } - ] - } - }, - { - "id": 3370807172, - "index": 3, - "colType": "allPending", - "active": true, - "collapsed": false, - "name": "WIP", - "filters": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1768140095718-9u02uoxai", - "groupCondition": "all", - "filters": [ - { - "id": "id-1768140095719-sfcnddjpj", - "property": "tags", - "condition": "contains", - "value": "#wip" - } - ] - } - ] - } - }, - { - "id": 1957870296, - "index": 4, - "colType": "allPending", - "active": true, - "collapsed": false, - "name": "In Review", - "filters": { - "rootCondition": "any", - "filterGroups": [ - { - "id": "id-1768140118917-x50nyv0rm", - "groupCondition": "any", - "filters": [ - { - "id": "id-1768140118918-q6snn1uk7", - "property": "tags", - "condition": "contains", - "value": "#Test" - }, - { - "id": "id-1768140126093-wn5bwcpwz", - "property": "tags", - "condition": "contains", - "value": "#working" - } - ] - } - ] - }, - "workLimit": 3 - }, - { - "id": 4265650565, - "index": 5, - "colType": "completed", - "active": true, - "collapsed": false, - "name": "Completed", - "limit": 20 - } - ], - "hideEmptyColumns": false, - "showColumnTags": true, - "showFilteredTags": true, - "boardFilter": { - "rootCondition": "any", - "filterGroups": [] - }, - "swimlanes": { - "enabled": false, - "hideEmptySwimlanes": false, - "property": "tags", - "sortCriteria": "asc", - "minimized": [], - "maxHeight": "300px", - "verticalHeaderUI": false - }, - "taskCount": { - "pending": 63, - "completed": 14 - } + "property": "Priority", + "key": "priority", + "taskItemKey": "priority" + }, + { + "index": 5, + "property": "Tags", + "key": "tags", + "taskItemKey": "tags" + }, + { + "index": 6, + "property": "Time", + "key": "time", + "taskItemKey": "time" + }, + { + "index": 7, + "property": "Reminder", + "key": "reminder", + "taskItemKey": "reminder" + }, + { + "index": 8, + "property": "Created date", + "key": "created-date", + "taskItemKey": "createdDate" + }, + { + "index": 9, + "property": "Start date", + "key": "start-date", + "taskItemKey": "startDate" + }, + { + "index": 10, + "property": "Scheduled date", + "key": "scheduled-date", + "taskItemKey": "scheduledDate" + }, + { + "index": 11, + "property": "Due date", + "key": "due-date", + "taskItemKey": "due" + }, + { + "index": 12, + "property": "Depends on", + "key": "depends-on", + "taskItemKey": "dependsOn" + }, + { + "index": 13, + "property": "Completed date", + "key": "cancelled-date", + "taskItemKey": "cancelledDate" } ], - "globalSettings": { - "lang": "zh-TW", - "scanFilters": { - "files": { - "polarity": 3, - "values": [ - "Testing Indented Task Bug.md", - "/\\b2025-\\d{2}-\\d{2}\\b/" - ] - }, - "folders": { - "polarity": 3, - "values": [ - "Task Board/1.7.0", - "/Notes/", - "Bible" - ] - }, - "tags": { - "polarity": 3, - "values": [ - "#CS", - "#placeholder/author", - "*/seeding/*" - ] - }, - "frontMatter": { - "polarity": 3, - "values": [ - "[\"created\": 2025-02-27]", - "[\"tags\": #TEST]", - "[\"background\": yellow]" - ] - } + "showFrontmatterTagsOnCards": false, + "tasksCacheFilePath": "", + "notificationService": "none", + "actions": [ + { + "enabled": true, + "trigger": "Complete", + "type": "move", + "targetColumn": "Completed" + } + ], + "hiddenTaskProperties": [ + "createdDate", + "startDate", + "onCompletion", + "recurring" + ], + "autoAddUniqueID": true, + "uniqueIdCounter": 2258, + "experimentalFeatures": false, + "safeGuardFeature": true, + "lastViewHistory": { + "viewedType": "kanban", + "boardIndex": 1, + "settingTab": 5, + "taskId": "", + "boardFilePath": "Meta/Task_Board/Boards/Time Based Workflow.taskboard" + }, + "boundTaskCompletionToChildTasks": true, + "mapView": { + "background": "none", + "mapOrientation": "hor", + "optimizedRender": false, + "arrowDirection": "c2p", + "animatedEdges": false, + "scrollAction": "pan", + "showMinimap": true, + "renderVisibleNodes": false, + "edgeType": "smoothstep" + }, + "searchQuery": "", + "taskBoardFilesRegistry": { + "653162057": { + "boardId": "653162057", + "filePath": "My Project Board.taskboard", + "boardName": "My Project", + "boardDescription": "This is my personal project. This is a default board created by Task Board for you to kick start your journey with Task Board. Feel free to edit or create new boards." }, - "firstDayOfWeek": "2", - "ignoreFileNameDates": false, - "taskCompletionFormat": "2", - "taskCompletionDateTimePattern": "YYYY-MM-DD/HH:mm", - "dailyNotesPluginComp": true, - "universalDateFormat": "YYYY-MM-DD", - "taskCompletionInLocalTime": true, - "taskCompletionShowUtcOffset": false, - "autoAddDue": true, - "scanVaultAtStartup": false, - "dayPlannerPlugin": false, - "realTimeScanner": true, - "columnWidth": "300px", - "showHeader": true, - "showFooter": true, - "showVerticalScroll": false, - "tagColors": [ - { - "name": "Bug", - "color": "rgba(131, 10, 18, 0.84)", - "priority": 1 - }, - { - "name": "feat", - "color": "rgba(115, 15, 151, 0.9490196078431372)", - "priority": 3 - }, - { - "name": "Test", - "color": "rgba(142, 157, 24, 1)", - "priority": 4 - }, - { - "name": "working", - "color": "rgba(22, 85, 17, 1)", - "priority": 5 - }, - { - "name": "New", - "color": "rgba(16, 50, 117, 1)", - "priority": 6 - }, - { - "name": "pending", - "color": "rgba(64, 20, 144, 1)", - "priority": 7 - }, - { - "name": "done", - "color": "rgba(14, 117, 84, 1)", - "priority": 7 - }, - { - "name": "*/seeding/*", - "color": "rgba(15, 121, 110, 1)", - "priority": 8 - }, - { - "name": "first", - "color": "rgba(13, 103, 37, 1)", - "priority": 9 - } - ], - "editButtonAction": "popUp", - "openOnStartup": false, - "customStatuses": [ - { - "symbol": " ", - "name": "Incomplete", - "nextStatusSymbol": "x", - "availableAsCommand": false, - "type": "TODO" - }, - { - "symbol": "X", - "name": "Complete", - "nextStatusSymbol": " ", - "availableAsCommand": false, - "type": "DONE" - }, - { - "symbol": "/", - "name": "In Progress", - "nextStatusSymbol": "x", - "availableAsCommand": true, - "type": "IN_PROGRESS" - }, - { - "symbol": "-", - "name": "Cancelled", - "nextStatusSymbol": " ", - "availableAsCommand": true, - "type": "CANCELLED" - }, - { - "symbol": ">", - "name": "Deferred", - "nextStatusSymbol": "x", - "availableAsCommand": false, - "type": "TODO" - }, - { - "symbol": "!", - "name": "Important", - "nextStatusSymbol": "x", - "availableAsCommand": false, - "type": "TODO" - }, - { - "symbol": "x", - "name": "Finished", - "nextStatusSymbol": " ", - "availableAsCommand": false, - "type": "DONE" - } - ], - "showTaskWithoutMetadata": false, - "tagColorsType": "text", - "compatiblePlugins": { - "dailyNotesPlugin": false, - "tasksPlugin": false, - "reminderPlugin": false, - "dayPlannerPlugin": true, - "quickAddPlugin": false + "901052398": { + "boardId": "901052398", + "filePath": "TaskBoard-Template-1774097758616.taskboard", + "boardName": "My Project", + "boardDescription": "This is my personal project. This is a default board created by Task Board for you to kick start your journey with Task Board. Feel free to edit or create new boards." + }, + "1743944892": { + "boardId": "1743944892", + "filePath": "Meta/Task_Board/Boards/Time Based Workflow.taskboard", + "boardName": "Time Based Workflow", + "boardDescription": "" }, - "preDefinedNote": "Task_Board_Temp_Tasks.md", - "quickAddPluginDefaultChoice": "Task Board Temp Tasks", - "autoAddCreatedDate": true, - "autoAddUniversalDate": true, - "universalDate": "startDate", - "archivedTasksFilePath": "", - "showFileNameInCard": true, - "showFrontmatterTagsOnCards": true, - "tasksCacheFilePath": ".obsidian/plugins/task-board/tasks.json", - "notificationService": "obsiApp", - "frontmatterPropertyForReminder": "remind at", - "actions": [ - { - "enabled": true, - "trigger": "Complete", - "type": "move", - "targetColumn": "6" - } - ], - "cardSectionsVisibility": "hideBoth", - "hiddenTaskProperties": [], - "taskPropertyFormat": "3", - "taskNoteDefaultLocation": "", - "autoAddUniqueID": true, - "uniqueIdCounter": 586, - "experimentalFeatures": true, - "searchQuery": "", - "lastViewHistory": { - "viewedType": "kanban", - "boardIndex": 4, - "taskId": "", - "settingTab": 1 + "2248319344": { + "boardId": "2248319344", + "filePath": "Meta/Task_Board/Boards/Release 2.0.0.taskboard", + "boardName": "Release 2.0.0", + "boardDescription": "This board will be used to plan everything related to the next major version release of Task Board 2.0.0" }, - "taskNoteIdentifierTag": "task", - "doubleClickCardToEdit": "noteInTab", - "boundTaskCompletionToChildTasks": true, - "defaultStartTime": "23:59", - "archivedTBNotesFolderPath": "", - "frontmatterFormatting": [ - { - "index": 0, - "property": "ID", - "key": "id", - "taskItemKey": "id" - }, - { - "index": 1, - "property": "Title", - "key": "title", - "taskItemKey": "title" - }, - { - "index": 2, - "property": "Status", - "key": "status", - "taskItemKey": "status" - }, - { - "index": 3, - "property": "Priority", - "key": "priority", - "taskItemKey": "priority" - }, - { - "index": 4, - "property": "Tags", - "key": "tags", - "taskItemKey": "tags" - }, - { - "index": 5, - "property": "Time", - "key": "time", - "taskItemKey": "time" - }, - { - "index": 6, - "property": "Reminder", - "key": "reminder", - "taskItemKey": "reminder" - }, - { - "index": 7, - "property": "Created date", - "key": "created-date", - "taskItemKey": "createdDate" - }, - { - "index": 8, - "property": "Start date", - "key": "start-date", - "taskItemKey": "startDate" - }, - { - "index": 9, - "property": "Scheduled date", - "key": "scheduled", - "taskItemKey": "scheduledDate" - }, - { - "index": 10, - "property": "Due date", - "key": "due", - "taskItemKey": "due" - }, - { - "index": 11, - "property": "Depends on", - "key": "depends-on", - "taskItemKey": "dependsOn" - }, - { - "index": 12, - "property": "Cancelled date", - "key": "cancelled-date", - "taskItemKey": "cancelledDate" - }, - { - "index": 13, - "key": "completed-date", - "taskItemKey": "completionDate" - } - ], - "mapView": { - "background": "transparent", - "mapOrientation": "hor", - "optimizedRender": false, - "arrowDirection": "c2p", - "animatedEdges": false, - "scrollAction": "pan", - "showMinimap": true, - "renderVisibleNodes": true, - "edgeType": "default" + "2627237443": { + "boardId": "2627237443", + "filePath": "Meta/Task_Board/Boards/Status Based Workflow.taskboard", + "boardName": "Status Based Workflow", + "boardDescription": "" }, - "kanbanView": { - "lazyLoadingEnabled": true, - "initialTaskCount": 20, - "loadMoreCount": 10, - "scrollThresholdPercent": 80 + "3103563481": { + "boardId": "3103563481", + "filePath": "My Project Board.taskboard", + "boardName": "My Project", + "boardDescription": "This is my personal project. This is a default board created by Task Board for you to kick start your journey with Task Board. Feel free to edit or create new boards." }, - "safeGuardFeature": true, - "visiblePropertiesList": [ - "id", - "priority", - "tags", - "time", - "reminder", - "createdDate", - "startDate", - "scheduledDate", - "dueDate", - "completionDate", - "cancelledDate", - "dependsOn", - "filePath", - "status", - "checkbox" - ], - "taskCardStyle": "emoji", - "autoAddCompletedDate": true, - "autoAddCancelledDate": true, - "scanMode": "manual" + "3764827886": { + "boardId": "3764827886", + "filePath": "Meta/Task_Board/Boards/Tag Based Workflow.taskboard", + "boardName": "Tag Based Workflow", + "boardDescription": "" + } } } } \ No newline at end of file diff --git a/esbuild.config.mjs b/esbuild.config.mjs index cea70901..c6f57a88 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -1,6 +1,6 @@ import esbuild from "esbuild"; import process from "process"; -import builtins from "builtin-modules"; +import { builtinModules } from "node:module"; const banner = `/* @@ -32,7 +32,7 @@ const context = await esbuild.context({ "@lezer/common", "@lezer/highlight", "@lezer/lr", - ...builtins, + ...builtinModules, ], format: "cjs", loader: { diff --git a/main.ts b/main.ts index 458e923e..cde181e5 100644 --- a/main.ts +++ b/main.ts @@ -1,7 +1,14 @@ -// main.ts - +/** + * @name main.ts + * @path /main.ts + * The entry-point of this plugin. Initializes the plugin, initializes all the required + * internal managers and utils. + */ + +import { around } from "monkey-around"; import { App, + normalizePath, Notice, Plugin, PluginManifest, @@ -10,47 +17,65 @@ import { TFolder, WorkspaceLeaf, } from "obsidian"; +import { EmbedRegistry } from "obsidian-typings"; +import { parse } from "date-fns"; +import { t } from "i18next"; +import { + taskPropertyHidingExtension, + getTaskPropertyRegexPatterns, +} from "./src/editor-extensions/task-operations/property-hiding.js"; +import { + VIEW_TYPE_TASKBOARD, + TASKBOARD_FILE_EXTENSION, + OBSIDIAN_CLOSED_TIME_KEY, + DEFAULT_DATE_TIME_FORMAT, + CURRENT_PLUGIN_VERSION, + MANDATORY_SCAN_KEY, +} from "./src/interfaces/Constants.js"; +import { + taskPropertiesNames, + scanModeOptions, +} from "./src/interfaces/Enums.js"; import { - DEFAULT_SETTINGS, PluginDataJson, -} from "src/interfaces/GlobalSettings"; + DEFAULT_SETTINGS, +} from "./src/interfaces/GlobalSettings.js"; +import { TaskBoardIcon } from "./src/interfaces/Icons.js"; +import { bugReporterManagerInsatance } from "./src/managers/BugReporter.js"; +import { dragDropTasksManagerInsatance } from "./src/managers/DragDropTasksManager.js"; +import { RealTimeScanner } from "./src/managers/RealTimeScanner.js"; +import TaskBoardFileManager from "./src/managers/TaskBoardFileManager.js"; +import VaultScanner, { + fileTypeAllowedForScanning, +} from "./src/managers/VaultScanner.js"; +import { MergeBoardsModal } from "./src/modals/MergeBoardsModal.js"; +import { ModifiedFilesModal } from "./src/modals/ModifiedFilesModal.js"; +import { TaskBoardView } from "./src/obsidian_views/TaskBoardView.js"; +import { isReminderPluginInstalled } from "./src/services/CommunityPlugins.js"; +import { eventEmitter } from "./src/services/EventEmitter.js"; import { - openAddNewTaskInCurrentFileModal, openAddNewTaskModal, openAddNewTaskNoteModal, + openAddNewTaskInCurrentFileModal, + openBoardsExplorerModal, openScanVaultModal, -} from "src/services/OpenModals"; - -import { TaskBoardView } from "./src/views/TaskBoardView"; -import { RealTimeScanner } from "src/managers/RealTimeScanner"; -import VaultScanner, { - fileTypeAllowedForScanning, -} from "src/managers/VaultScanner"; -import { TaskBoardIcon } from "src/interfaces/Icons"; -import { TaskBoardSettingTab } from "./src/settings/TaskBoardSettingTab"; -import { ModifiedFilesModal } from "src/modals/ModifiedFilesModal"; -import { - newReleaseVersion, - VIEW_TYPE_TASKBOARD, -} from "src/interfaces/Constants"; -import { isReminderPluginInstalled } from "src/services/CommunityPlugins"; -import { loadTranslationsOnStartup, t } from "src/utils/lang/helper"; -import { TaskBoardApi } from "src/taskboardAPIs"; -import { TasksPluginApi } from "src/services/tasks-plugin/api"; -import { - getTaskPropertyRegexPatterns, - taskPropertyHidingExtension, -} from "src/editor-extensions/task-operations/property-hiding"; +} from "./src/services/OpenModals.js"; +import { TasksPluginApi } from "./src/services/tasks-plugin/api.js"; +import { isTasksPluginEnabled } from "./src/services/tasks-plugin/helpers.js"; import { - fetchTasksPluginCustomStatuses, - isTasksPluginEnabled, -} from "src/services/tasks-plugin/helpers"; -import { scanModeOptions, taskPropertiesNames } from "src/interfaces/Enums"; -import { migrateSettings } from "src/settings/SettingSynchronizer"; -import { dragDropTasksManagerInsatance } from "src/managers/DragDropTasksManager"; -import { eventEmitter } from "src/services/EventEmitter"; -import { bugReporterManagerInsatance } from "src/managers/BugReporter"; - + checkAndNotifyV2MigrationsRequired, + openMigrationModal, +} from "./src/settings/2_x_x_Migrations/MigrationUtils.js"; +import { migrateSettings } from "./src/settings/SettingSynchronizer.js"; +import { TaskBoardSettingTab } from "./src/settings/TaskBoardSettingTab.js"; +import { TaskBoardApi } from "./src/taskboardAPIs.js"; +import { getCurrentLocalDateTimeString } from "./src/utils/DateTimeCalculations.js"; +import { loadTranslationsOnStartup } from "./src/utils/lang/helper.js"; +import { DEFAULT_BOARD } from "./src/interfaces/BoardConfigs.js"; + +/** + * The entry-point of this project. + */ export default class TaskBoard extends Plugin { app: App; plugin: TaskBoard; @@ -58,14 +83,16 @@ export default class TaskBoard extends Plugin { settings: PluginDataJson = DEFAULT_SETTINGS; vaultScanner: VaultScanner; realTimeScanner: RealTimeScanner; + taskBoardFileManager: TaskBoardFileManager; // taskBoardFileStack: string[] = []; - private _editorModified: boolean = false; // Private backing field // currentModifiedFile: TFile | null; // fileUpdatedUsingModal: string; IstasksJsonDataChanged: boolean; isI18nInitialized: boolean; - private _leafIsActive: boolean; // Private property to track leaf state + private ribbonIconEl: HTMLElement | null; // Store ribbonIconEl globally for reference + private _editorModified: boolean = false; // Private backing field + private _leafIsActive: boolean; // Private property to track leaf state // Public getter/setter for editorModified that emits events get editorModified(): boolean { @@ -88,13 +115,16 @@ export default class TaskBoard extends Plugin { private deleteProcessingTimer: NodeJS.Timeout | null = null; private createProcessingTimer: NodeJS.Timeout | null = null; private currentProgressNotice: Notice | null = null; - private readonly QUEUE_DELAY = 1000; // Delay in ms before starting to process queue + private readonly QUEUE_DELAY = 2000; // Delay in ms before starting to process queue private readonly PROCESSING_INTERVAL = 100; // Delay between processing each file + v2MigrationsRequired = false; + constructor(app: App, menifest: PluginManifest) { super(app, menifest); this.plugin = this; - this.app = this.plugin.app; + this.app = app; + this.plugin.app = app; this.view = null; this.settings = DEFAULT_SETTINGS; this.vaultScanner = new VaultScanner(this.app, this.plugin); @@ -103,6 +133,7 @@ export default class TaskBoard extends Plugin { this.plugin, this.vaultScanner, ); + this.taskBoardFileManager = new TaskBoardFileManager(this.plugin); this.editorModified = false; // this.currentModifiedFile = null; // this.fileUpdatedUsingModal = ""; @@ -119,26 +150,32 @@ export default class TaskBoard extends Plugin { async onload() { console.log("Task Board : Loading..."); + // this.getLanguage(); + await loadTranslationsOnStartup(this); + // NOTE : I feel, if these singleton instances needs the latest version of 'this', then they might show some unexpected behavior as I am not updating the 'this' inside those singleton instances latest during the plugin life-cycle. - dragDropTasksManagerInsatance.setPlugin(this); bugReporterManagerInsatance.setPlugin(this); + // Migrations for updating from v1.x.x version series to v2.x.x series version + this.v2MigrationsRequired = + await checkAndNotifyV2MigrationsRequired(this); + // Loads settings data and creating the Settings Tab in main Setting await this.loadSettings(); - this.runOnPluginUpdate(); + if (!this.v2MigrationsRequired) await this.runOnPluginUpdate(); this.addSettingTab(new TaskBoardSettingTab(this.app, this)); - // this.getLanguage(); - - await loadTranslationsOnStartup(this); - await this.vaultScanner.initializeTasksCache(); + // Register the Kanban view + this.registerTaskBoardView(); + // Register events and commands only on Layout is ready this.app.workspace.onLayoutReady(() => { - console.log("Task Board : Running onLayoutReady..."); this.compatiblePluginsAvailabilityCheck(); + dragDropTasksManagerInsatance.setPlugin(this); + //Creates a Icon on Ribbon Bar (after i18n is initialized) this.getRibbonIcon(); @@ -151,9 +188,6 @@ export default class TaskBoard extends Plugin { // For non-realtime scanning and scanning last modified files this.createLocalStorageAndScanModifiedFiles(); - // Register the Kanban view - this.registerTaskBoardView(); - // Run openAtStartup if openOnStartup is true this.openAtStartup(); @@ -166,11 +200,10 @@ export default class TaskBoard extends Plugin { // Register markdown post processor for hiding task properties this.registerReadingModePostProcessor(); - setTimeout(() => this.findModifiedFilesOnAppAbsense(), 10000); + this.taskBoardFileManager.validateBoardFiles(); - console.log("Task Board : onLayoutReady FINISHED."); + setTimeout(() => this.findModifiedFilesOnAppAbsense(), 10000); }); - console.log("Task Board : onload funcion FINISHED."); } onunload() { @@ -178,61 +211,110 @@ export default class TaskBoard extends Plugin { // deleteAllLocalStorageKeys(); // TODO : Enable this while production build. This is disabled for testing purpose because the data from localStorage is required for testing. // onUnloadSave(this.plugin); + + // Obsidian already does this, no need to manually detach. // this.app.workspace.detachLeavesOfType(VIEW_TYPE_TASKBOARD); } - async activateView(leafLayout: string) { - let leaf: WorkspaceLeaf | null = null; - const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_TASKBOARD); + /** + * Opens the Task Board view using either the last viewed board file or opens the board file + * whose filePath has been passed. Most of the time, this function will try to find an existing + * leaf for the specific board file. If user specifically wants to have a duplicate leaf, pass + * the {@link duplicate} as true. + * + * @param leafLayout - Where to open the board leaf/tab. New tab or new window. + * @param duplicate - Whether to re-use already opened leaf or create a new one. + * This will be true in only special cases, when user wants to specifical open a duplicate. + * @param filePath (OPTIONAL) - The file path of the board to open. If no filePath has been + * provided then will open the last viewed board. + */ + async activateView( + leafLayout: string, + duplicate: boolean, + filePath?: string, + ) { + try { + let leaf: WorkspaceLeaf | null = null; + const leaves = + this.app.workspace.getLeavesOfType(VIEW_TYPE_TASKBOARD); + + function isFromMainWindow( + leaf: WorkspaceLeaf, + ): boolean | undefined { + if (filePath) { + const state = leaf.getViewState(); + if ( + state?.state?.filePath && + state?.state?.filePath !== filePath + ) { + return false; + } + } - function isFromMainWindow(leaf: WorkspaceLeaf): boolean | undefined { - if (!leaf.view.containerEl.ownerDocument.defaultView) return; - return "Notice" in leaf.view.containerEl.ownerDocument.defaultView; - } + if (!leaf.view.containerEl.ownerDocument.defaultView) return; + return ( + "Notice" in leaf.view.containerEl.ownerDocument.defaultView + ); + } - // Separate leaves into MainWindow and SeparateWindow categories - const mainWindowLeaf = leaves.find((leaf) => isFromMainWindow(leaf)); - const separateWindowLeaf = leaves.find( - (leaf) => !isFromMainWindow(leaf), - ); + // Separate leaves into MainWindow and SeparateWindow categories + const mainWindowLeaf = leaves.find((leaf) => + isFromMainWindow(leaf), + ); + const separateWindowLeaf = leaves.find( + (leaf) => !isFromMainWindow(leaf), + ); - if (leafLayout === "icon") { - // Focus on any existing leaf, prioritizing MainWindow - leaf = - mainWindowLeaf || - separateWindowLeaf || - this.app.workspace.getLeaf("tab"); - } else if (leafLayout === "tab") { - // Check if a leaf exists in MainWindow - if (mainWindowLeaf) { - // Prevent duplicate in MainWindow - leaf = mainWindowLeaf; + if (leafLayout === "icon") { + // Focus on any existing leaf, prioritizing MainWindow + leaf = + mainWindowLeaf || + separateWindowLeaf || + this.app.workspace.getLeaf("tab"); + } else if (leafLayout === "tab") { + // Check if a leaf exists in MainWindow + if (mainWindowLeaf && !duplicate) { + // Prevent duplicate in MainWindow + leaf = mainWindowLeaf; + } else { + // Allow opening a new leaf in MainWindow + leaf = this.app.workspace.getLeaf("tab"); + } + } else if (leafLayout === "window") { + // Check if a leaf exists in SeparateWindow + if (separateWindowLeaf) { + // Prevent duplicate in SeparateWindow + leaf = separateWindowLeaf; + } else { + // Allow opening a new leaf in SeparateWindow + leaf = this.app.workspace.getLeaf("window"); + } } else { - // Allow opening a new leaf in MainWindow + // Default behavior: open in MainWindow leaf = this.app.workspace.getLeaf("tab"); } - } else if (leafLayout === "window") { - // Check if a leaf exists in SeparateWindow - if (separateWindowLeaf) { - // Prevent duplicate in SeparateWindow - leaf = separateWindowLeaf; - } else { - // Allow opening a new leaf in SeparateWindow - leaf = this.app.workspace.getLeaf("window"); - } - } else { - // Default behavior: open in MainWindow - leaf = this.app.workspace.getLeaf("tab"); - } - // Open or focus the leaf - if (leaf) { - this.leafIsActive = true; - await leaf.setViewState({ - type: VIEW_TYPE_TASKBOARD, - active: true, - }); - this.app.workspace.revealLeaf(leaf); + // Open or focus the leaf + if (leaf) { + this.leafIsActive = true; + leaf.setEphemeralState({ taskboardFilePath: filePath ?? "" }); + + await leaf.setViewState({ + type: VIEW_TYPE_TASKBOARD, + active: true, + state: { + filePath: filePath ?? "", + }, + }); + + this.app.workspace.revealLeaf(leaf); + } + } catch (error) { + bugReporterManagerInsatance.addToLogs( + 202, + `Error opening the board: ${error}`, + "main.ts/activateView", + ); } } @@ -242,7 +324,7 @@ export default class TaskBoard extends Plugin { TaskBoardIcon, t("open-task-board") ?? "Open task board", () => { - this.activateView("icon"); + this.activateView("icon", false); // this.app.workspace.ensureSideLeaf(VIEW_TYPE_TASKBOARD, "right", { // active: true, @@ -274,11 +356,19 @@ export default class TaskBoard extends Plugin { } async saveSettings(newSetting?: PluginDataJson) { - if (newSetting) { - this.settings = newSetting; - await this.saveData(newSetting); - } else { - await this.saveData(this.settings); + try { + if (newSetting) { + this.settings = newSetting; + await this.saveData(newSetting); + } else { + await this.saveData(this.settings); + } + } catch (err) { + bugReporterManagerInsatance.addToLogs( + 140, + String(err), + "main.ts/saveSettings", + ); } } @@ -287,12 +377,12 @@ export default class TaskBoard extends Plugin { // if (obsidianLang && obsidianLang in langCodes) { // localStorage.setItem("taskBoardLang", obsidianLang); - // this.settings.data.globalSettings.lang = obsidianLang; + // this.settings.data.lang = obsidianLang; // this.saveSettings(); // } else { // localStorage.setItem( // "taskBoardLang", - // // this.settings.data.globalSettings.lang + // // this.settings.data.lang // "en" // ); // } @@ -310,6 +400,36 @@ export default class TaskBoard extends Plugin { return this.view; }); + this.registerExtensions( + [TASKBOARD_FILE_EXTENSION], + VIEW_TYPE_TASKBOARD, + ); + + // Monkey-patch WorkspaceLeaf.setViewState to intercept .taskboard file clicks + this.registerMonkeyPatchForTaskboardFiles(); + + if (this.settings.data.experimentalFeatures) { + // @ts-ignore + const embedRegistry = this.app.embedRegistry as EmbedRegistry; + if ( + !embedRegistry?.isExtensionRegistered(TASKBOARD_FILE_EXTENSION) + ) { + embedRegistry?.registerExtension( + TASKBOARD_FILE_EXTENSION, + (context, file, _) => { + // @ts-ignore + return new TaskBoardEmbedComponent( + context.containerEl, + this, + // @ts-ignore + file, + context.containerEl.getAttr("alt") || undefined, + ) as any; + }, + ); + } + } + // Register AddOrEditTask view (can be opened in tabs or popout windows) // this.registerView(VIEW_TYPE_ADD_OR_EDIT_TASK, (leaf) => { // console.log("Leaf returned by registerView :", leaf); @@ -328,37 +448,67 @@ export default class TaskBoard extends Plugin { // }); } + /** + * Monkey-patch WorkspaceLeaf.setViewState to intercept .taskboard file clicks + * When a user clicks on a .taskboard file in the File Navigator, this intercepts + * the default markdown view and opens it in the TaskBoard custom view instead, + * while preserving the file path in the view state + */ + private registerMonkeyPatchForTaskboardFiles() { + // Use monkey-around to safely patch WorkspaceLeaf.prototype.setViewState + // This allows multiple plugins to patch the same method without conflicts + const unregisterPatch = around(WorkspaceLeaf.prototype, { + setViewState: (next) => + function (this: WorkspaceLeaf, state: any, eState?: any) { + const isTaskBoardView = state.type === VIEW_TYPE_TASKBOARD; + const filePath = state.state?.file as string | undefined; + const isTaskboardFile = + filePath && filePath.endsWith(".taskboard"); + + if (isTaskBoardView && isTaskboardFile) { + // Store the file path directly on the leaf instance for immediate access + (this as any).taskboardFilePath = filePath; + + // Also set ephemeral state for safety + this.setEphemeralState({ taskboardFilePath: filePath }); + } + + // Call the next method in the chain (original or other patches) + return next.call(this, state, eState); + }, + }); + + // Register cleanup handler to unregister the patch when plugin unloads + // This prevents memory leaks and ensures the patch is properly removed + this.register(unregisterPatch); + } + registerEditorExtensions() { // TODO : The below editor extension will not going to be released in the upcoming version, will plan it for the next version. // Register task gutter extension // this.registerEditorExtension(taskGutterExtension(this.app, this)); // Register task property hiding extension - const hiddenProperties = - this.settings.data.globalSettings?.hiddenTaskProperties || []; + const hiddenProperties = this.settings.data?.hiddenTaskProperties || []; if (hiddenProperties.length > 0) { this.registerEditorExtension(taskPropertyHidingExtension(this)); } } registerReadingModePostProcessor() { - const hiddenProperties = - this.settings.data.globalSettings?.hiddenTaskProperties || []; + const hiddenProperties = this.settings.data?.hiddenTaskProperties || []; if (hiddenProperties.length === 0) { return; } const tasksPlugin = new TasksPluginApi(this); if (!tasksPlugin.isTasksPluginEnabled()) { this.registerMarkdownPostProcessor((element, context) => { - // console.log("Element : ", element, "\nContent :", context); // Only process if we have properties to hide - // Find all list items that could be tasks const listItems = element.querySelectorAll("li"); listItems.forEach((listItem) => { // const textContent = listItem.textContent || ""; - // console.log("Text Content :", textContent); // Check if this is a task (starts with checkbox syntax) if (listItem.querySelector(".contains-task-list")) { this.hidePropertiesInElement( @@ -538,7 +688,7 @@ export default class TaskBoard extends Plugin { hiddenProperties.forEach((property) => { const pattern = getTaskPropertyRegexPatterns( property, - this.settings.data.globalSettings?.taskPropertyFormat, + this.settings.data?.taskPropertyFormat, ); if (pattern.test(content)) { content = content.replace(pattern, (match) => { @@ -566,9 +716,9 @@ export default class TaskBoard extends Plugin { } openAtStartup() { - if (!this.settings.data.globalSettings.openOnStartup) return; + if (!this.settings.data.openOnStartup) return; - this.activateView("icon"); + this.activateView("icon", false); } registerTaskBoardStatusBar() { @@ -578,12 +728,12 @@ export default class TaskBoard extends Plugin { // statusBarItemEl.setText("Next task in # min"); } - registerCommands() { + async registerCommands() { this.addCommand({ id: "add-new-task", name: t("add-new-task"), callback: () => { - openAddNewTaskModal(this.app, this.plugin); + openAddNewTaskModal(this.plugin); }, }); this.addCommand({ @@ -619,25 +769,50 @@ export default class TaskBoard extends Plugin { id: "open-task-board", name: t("open-task-board"), callback: () => { - this.activateView("tab"); + this.activateView("tab", false); }, }); this.addCommand({ id: "open-task-board-new-window", name: t("open-task-board-in-new-window"), callback: () => { - this.activateView("window"); + this.activateView("window", false); + }, + }); + this.addCommand({ + id: "open-task-boards-explorer", + name: t("open-task-boards-explorer"), + callback: () => { + openBoardsExplorerModal(this); }, }); this.addCommand({ id: "open-scan-vault-modal", name: t("open-scan-vault-modal"), callback: () => { - openScanVaultModal(this.app, this.plugin); + openScanVaultModal(this.plugin); + }, + }); + this.addCommand({ + id: "merge-boards", + name: "Merge Boards", + callback: () => { + new MergeBoardsModal(this.app, { + plugin: this, + taskBoardFileManager: this.taskBoardFileManager, + }).open(); }, }); - // // TODO : Remove this command before publishing, DEV commands + if (this.v2MigrationsRequired) { + this.addCommand({ + id: "open-migration-modal", + name: "Open migration modal", + callback: () => { + openMigrationModal(this.plugin); + }, + }); + } // this.addCommand({ // id: "4", // name: "DEV : Save Data from sessionStorage to Disk", @@ -661,8 +836,8 @@ export default class TaskBoard extends Plugin { /** * Add a file to the rename queue and schedule processing * @private - * @param {TAbstractFile} file - The file to add to the queue - * @param {string} oldPath - The old path of the file + * @param file - The file to add to the queue + * @param oldPath - The old path of the file */ private queueFileForRename(file: TAbstractFile, oldPath: string) { // Only queue TFile objects (not folders) that are allowed for scanning @@ -690,61 +865,72 @@ export default class TaskBoard extends Plugin { return; } - const archivedPath = - this.settings.data.globalSettings.archivedTBNotesFolderPath; - const totalFiles = this.renameQueue.length; - - // Show progress notice - this.currentProgressNotice = new Notice( - `Processing renamed files: 0/${totalFiles}`, - 0, + const archivedPath = normalizePath( + this.settings.data.archivedTBNotesFolderPath, ); + let allowedFiles = this.renameQueue.filter((fileData) => + fileTypeAllowedForScanning(this.settings.data, fileData.file), + ); + const totalFilesLength = allowedFiles.length; - let processed = 0; - while (this.renameQueue.length > 0) { - const { file, oldPath } = this.renameQueue.shift()!; + // Empty the global queue + this.renameQueue = []; - try { - if ( - fileTypeAllowedForScanning( - this.plugin.settings.data.globalSettings, - file, - ) - ) { + if (totalFilesLength > 0) { + // Show progress notice + this.currentProgressNotice = new Notice( + `Processing renamed files: 0/${totalFilesLength}`, + 0, + ); + + let processed = 0; + while (allowedFiles.length > 0) { + const { file, oldPath } = allowedFiles.shift()!; + + try { this.realTimeScanner.onFileRenamed( file, oldPath, archivedPath, ); + processed++; + + // Update progress notice + this.currentProgressNotice.messageEl.textContent = `Task Board : Processing renamed files: ${processed}/${totalFilesLength}`; + } catch (error) { + this.currentProgressNotice?.hide(); + // this.currentProgressNotice = null; + bugReporterManagerInsatance.addToLogs( + 162, + String(error), + "main.ts/processRenameQueue", + ); + } + + // Add delay between processing each file to prevent blocking UI + if (allowedFiles.length > 0) { + await new Promise((resolve) => + setTimeout(resolve, this.PROCESSING_INTERVAL), + ); } - processed++; - - // Update progress notice - this.currentProgressNotice.messageEl.textContent = `Task Board : Processing renamed files: ${processed}/${totalFiles}`; - } catch (error) { - console.error( - `Error processing renamed file ${file.path}:`, - error, - ); } - // Add delay between processing each file to prevent blocking UI - if (this.renameQueue.length > 0) { - await new Promise((resolve) => - setTimeout(resolve, this.PROCESSING_INTERVAL), + // Hide progress notice after completion + this.currentProgressNotice?.hide(); + this.currentProgressNotice = null; + + this.plugin.vaultScanner.saveTasksToJsonCache(); + eventEmitter.emit("REFRESH_BOARD"); + + if (processed > 0) { + new Notice( + `✓ Task Board : Finished processing ${totalFilesLength} renamed file(s)`, ); } } - this.plugin.vaultScanner.saveTasksToJsonCache(); - eventEmitter.emit("REFRESH_BOARD"); - - // Hide progress notice after completion - this.currentProgressNotice?.hide(); - this.currentProgressNotice = null; - new Notice( - `✓ Task Board : Finished processing ${totalFiles} renamed file(s)`, - ); + if (this.renameProcessingTimer) + clearTimeout(this.renameProcessingTimer); } /** @@ -756,13 +942,14 @@ export default class TaskBoard extends Plugin { this.deleteQueue.push(file); // Clear existing timer and set a new one - if (this.deleteProcessingTimer) { - clearTimeout(this.deleteProcessingTimer); + if (!this.deleteProcessingTimer) { + this.deleteProcessingTimer = setTimeout(() => { + this.processDeleteQueue(); + }, this.QUEUE_DELAY); + } else { + // NOTE : I think there is no need to remove the Timout created, in 2 seconds, all the Obsidians triggers should finish, for the Task Board's processing to start. + // clearTimeout(this.deleteProcessingTimer); } - - this.deleteProcessingTimer = setTimeout(() => { - this.processDeleteQueue(); - }, this.QUEUE_DELAY); } } @@ -776,55 +963,58 @@ export default class TaskBoard extends Plugin { return; } - const totalFiles = this.deleteQueue.length; - - // Show progress notice - this.currentProgressNotice = new Notice( - `Processing deleted files: 0/${totalFiles}`, - 0, + let allowedFiles = this.deleteQueue.filter((file: TAbstractFile) => + fileTypeAllowedForScanning(this.settings.data, file), ); + const totalFilesLength = allowedFiles.length; - let processed = 0; - while (this.deleteQueue.length > 0) { - const file = this.deleteQueue.shift()!; + if (allowedFiles.length > 0) { + // Show progress notice + this.currentProgressNotice = new Notice( + `Processing deleted files: 0/${totalFilesLength}`, + 0, + ); - try { - if ( - fileTypeAllowedForScanning( - this.plugin.settings.data.globalSettings, - file, - ) - ) { + let processed = 0; + while (allowedFiles.length > 0) { + const file = allowedFiles.shift()!; + + try { this.realTimeScanner.onFileDeleted(file); + processed++; + + // Update progress notice + this.currentProgressNotice.messageEl.textContent = `Task Board : Processing deleted files: ${processed}/${totalFilesLength}`; + } catch (error) { + this.currentProgressNotice?.hide(); + // this.currentProgressNotice = null; + bugReporterManagerInsatance.addToLogs( + 163, + String(error), + "main.ts/processDeleteQueue", + ); + } + + // Add delay between processing each file to prevent blocking UI + if (allowedFiles.length > 0) { + await new Promise((resolve) => + setTimeout(resolve, this.PROCESSING_INTERVAL), + ); } - processed++; - - // Update progress notice - this.currentProgressNotice.messageEl.textContent = `Task Board : Processing deleted files: ${processed}/${totalFiles}`; - } catch (error) { - console.error( - `Error processing deleted file ${file.path}:`, - error, - ); } + // Hide progress notice after completion + this.currentProgressNotice?.hide(); + this.currentProgressNotice = null; + + this.plugin.vaultScanner.saveTasksToJsonCache(); + eventEmitter.emit("REFRESH_COLUMN"); - // Add delay between processing each file to prevent blocking UI - if (this.deleteQueue.length > 0) { - await new Promise((resolve) => - setTimeout(resolve, this.PROCESSING_INTERVAL), + if (processed > 0) { + new Notice( + `✓ Task Board : Finished processing ${totalFilesLength} deleted file(s)`, ); } } - - this.plugin.vaultScanner.saveTasksToJsonCache(); - eventEmitter.emit("REFRESH_COLUMN"); - - // Hide progress notice after completion - this.currentProgressNotice?.hide(); - this.currentProgressNotice = null; - new Notice( - `✓ Task Board : Finished processing ${totalFiles} deleted file(s)`, - ); } /** @@ -836,13 +1026,14 @@ export default class TaskBoard extends Plugin { this.createQueue.push(file); // Clear existing timer and set a new one - if (this.createProcessingTimer) { - clearTimeout(this.createProcessingTimer); + if (!this.createProcessingTimer) { + this.createProcessingTimer = setTimeout(() => { + this.processCreateQueue(); + }, this.QUEUE_DELAY); + } else { + // NOTE : I think there is no need to remove the Timout created, in 2 seconds, all the Obsidians triggers should finish, for the Task Board's processing to start. + // clearTimeout(this.createProcessingTimer); } - - this.createProcessingTimer = setTimeout(() => { - this.processCreateQueue(); - }, this.QUEUE_DELAY); } /** @@ -855,54 +1046,238 @@ export default class TaskBoard extends Plugin { return; } - const totalFiles = this.createQueue.length; - - // Show progress notice - this.currentProgressNotice = new Notice( - `Task Board : Processing created files: 0/${totalFiles}`, - 0, + let allowedFiles = this.createQueue.filter((file: TFile) => + fileTypeAllowedForScanning(this.settings.data, file), ); + const totalFilesLength = allowedFiles.length; - this.plugin.vaultScanner.refreshTasksFromFiles(this.createQueue, false); + this.plugin.vaultScanner.refreshTasksFromFiles(allowedFiles, false); - let processed = 0; - while (this.createQueue.length > 0) { - const file = this.createQueue.shift()!; + // Show progress notice only if the files are more than 10 + if (totalFilesLength > 10) { + this.currentProgressNotice = new Notice( + `Task Board : Processing created files: 0/${totalFilesLength}`, + 0, + ); + let processed = 0; + while (allowedFiles.length > 0) { + const file = allowedFiles.shift()!; + + try { + // if ( + // fileTypeAllowedForScanning( + // this.plugin.settings.data.globalSettings, + // file + // ) + // ) { + // await this.realTimeScanner.processAllUpdatedFiles(file); + // } + processed++; + + // Update progress notice + this.currentProgressNotice.messageEl.textContent = `Task Board : Processing created files: ${processed}/${totalFilesLength}`; + } catch (error) { + this.currentProgressNotice?.hide(); + // this.currentProgressNotice = null; + bugReporterManagerInsatance.addToLogs( + 164, + String(error), + "main.ts/processCreateQueue", + ); + } - try { - // if ( - // fileTypeAllowedForScanning( - // this.plugin.settings.data.globalSettings, - // file - // ) - // ) { - // await this.realTimeScanner.processAllUpdatedFiles(file); - // } - processed++; - - // Update progress notice - this.currentProgressNotice.messageEl.textContent = `Task Board : Processing created files: ${processed}/${totalFiles}`; - } catch (error) { - console.error( - `Error processing created file ${file.path}:`, - error, - ); + // Add delay between processing each file to prevent blocking UI + if (allowedFiles.length > 0) { + await new Promise((resolve) => + setTimeout(resolve, this.PROCESSING_INTERVAL), + ); + } } - // Add delay between processing each file to prevent blocking UI - if (this.createQueue.length > 0) { - await new Promise((resolve) => - setTimeout(resolve, this.PROCESSING_INTERVAL), + // Hide progress notice after completion + this.currentProgressNotice?.hide(); + this.currentProgressNotice = null; + if (processed > 0) { + new Notice( + `✓ Task Board : Finished processing ${totalFilesLength} created file(s)`, ); } } + } - // Hide progress notice after completion - this.currentProgressNotice?.hide(); - this.currentProgressNotice = null; - new Notice( - `✓ Task Board : Finished processing ${totalFiles} created file(s)`, - ); + /** + * Runs on plugin load/Obsidian startup time and find all the files which where + * modified (edited/renamed/deleted) between the time when Obsidian was last closed + * till now. + */ + async findModifiedFilesOnAppAbsense() { + const storedTime = this.app.loadLocalStorage( + OBSIDIAN_CLOSED_TIME_KEY, + ) as string | undefined; + + let OBSIDIAN_CLOSED_TIME: Date | undefined; + + if (storedTime) { + OBSIDIAN_CLOSED_TIME = parse( + storedTime, + DEFAULT_DATE_TIME_FORMAT, + new Date(), + ); + } else { + OBSIDIAN_CLOSED_TIME = parse( + this.vaultScanner.tasksCache.Modified_at, + DEFAULT_DATE_TIME_FORMAT, + new Date(), + ); + } + + if (OBSIDIAN_CLOSED_TIME) { + let filesScannedCount = 0; + const modifiedCreatedRenamedFiles = this.app.vault + .getFiles() + .filter((file) => { + filesScannedCount++; + return ( + file.stat.mtime > OBSIDIAN_CLOSED_TIME!.getTime() || + file.stat.ctime > OBSIDIAN_CLOSED_TIME!.getTime() + ); + }); + + // Find deleted files by comparing cache with current vault files + const currentFilesPaths = new Set( + this.app.vault.getFiles().map((file) => file.path), + ); + const cachedFilesPaths = Object.keys( + this.vaultScanner.tasksCache.Pending || {}, + ).concat(Object.keys(this.vaultScanner.tasksCache.Completed || {})); + const deletedFiles = new Set( + cachedFilesPaths.filter( + (filePath) => !currentFilesPaths.has(filePath), + ), + ); + const deletedFilesList = [...deletedFiles]; + + const changed_files = modifiedCreatedRenamedFiles.filter((file) => + fileTypeAllowedForScanning(this.plugin.settings.data, file), + ); + const totalFilesLength = + changed_files.length + deletedFilesList.length; + + if (totalFilesLength > 0) { + const scanAllModifiedFiles = () => { + this.plugin.vaultScanner + .refreshTasksFromFiles(changed_files, false) + .then(async () => { + if (deletedFilesList.length > 0) { + await this.plugin.vaultScanner.deleteCacheForFiles( + deletedFilesList, + ); + } + }); + }; + + if (this.settings.data.showModifiedFilesNotice) { + const modifiedFilesNotice = new Notice( + createFragment((f) => { + f.createDiv("bugReportNotice", (el) => { + el.createEl("p", { + text: `Task Board : ${totalFilesLength} files has been modified when Obsidian was inactive.`, + }); + el.createEl("button", { + text: t("show-me"), + cls: "reportBugButton", + onclick: () => { + // el.hide(); + + // Open a modal and show all these file names with their modified date-time in a nice UI. + const modifiedFilesModal = + new ModifiedFilesModal(this.app, { + modifiedFiles: changed_files, + deletedFiles: deletedFilesList, + }); + modifiedFilesModal.open(); + }, + }); + el.createEl("button", { + text: t("scan-them"), + cls: "ignoreBugButton", + onclick: async () => { + try { + modifiedFilesNotice.hide(); + + // Show progress notice + this.currentProgressNotice = + new Notice( + `Task Board : Processing modified files: 0/${totalFilesLength}`, + 0, + ); + + scanAllModifiedFiles(); + + let modifiedFilesQueueLength = + changed_files?.length ?? 0; + + let processed = 0; + while ( + modifiedFilesQueueLength > 0 + ) { + modifiedFilesQueueLength = + modifiedFilesQueueLength - + 1; + + processed++; + + // Update progress notice + this.currentProgressNotice.messageEl.textContent = `Task Board : Processing created files: ${processed}/${totalFilesLength}`; + + // Add delay between processing each file to prevent blocking UI + if ( + modifiedFilesQueueLength > 0 + ) { + await new Promise( + (resolve) => + setTimeout( + resolve, + this + .PROCESSING_INTERVAL, + ), + ); + } + } + + // Hide progress notice after completion + this.currentProgressNotice?.hide(); + this.currentProgressNotice = null; + new Notice( + `✓ Task Board : Finished processing ${totalFilesLength} created file(s)`, + ); + } catch (error) { + this.currentProgressNotice?.hide(); + bugReporterManagerInsatance.addToLogs( + 165, + String(error), + "main.ts/findModifiedFilesOnAppAbsense", + ); + } + }, + }); + }); + }), + 0, + ); + + modifiedFilesNotice.messageEl.onClickEvent((e) => { + if (e.target instanceof HTMLButtonElement) { + e.stopPropagation(); + e.preventDefault(); + e.stopImmediatePropagation(); + } + }); + } else { + scanAllModifiedFiles(); + } + } + } } /** @@ -911,17 +1286,13 @@ export default class TaskBoard extends Plugin { registerEvents() { this.registerEvent( this.app.vault.on("modify", async (file: TAbstractFile) => { - console.log("Modify event is fired..."); if ( - fileTypeAllowedForScanning( - this.plugin.settings.data.globalSettings, - file, - ) + fileTypeAllowedForScanning(this.plugin.settings.data, file) ) { if (file instanceof TFile) { if ( - this.plugin.settings.data.globalSettings - .scanMode === scanModeOptions.REAL_TIME + this.plugin.settings.data.scanMode === + scanModeOptions.REAL_TIME ) { this.vaultScanner.refreshTasksFromFiles( [file], @@ -938,21 +1309,18 @@ export default class TaskBoard extends Plugin { ); this.registerEvent( this.app.vault.on("rename", (file, oldPath) => { - console.log("Rename event is fired..."); // Queue the file for processing instead of processing immediately this.queueFileForRename(file, oldPath); }), ); this.registerEvent( this.app.vault.on("delete", (file) => { - console.log("Delete event is fired..."); // Queue the file for processing instead of processing immediately this.queueFileForDeletion(file); }), ); this.registerEvent( this.app.vault.on("create", (file) => { - console.log("Create event is fired..."); if (file instanceof TFile) { // Queue the file for processing instead of processing immediately this.queueFileForCreation(file); @@ -960,30 +1328,39 @@ export default class TaskBoard extends Plugin { }), ); - if ( - this.plugin.settings.data.globalSettings.scanMode !== - scanModeOptions.MANUAL - ) { + if (this.plugin.settings.data.scanMode !== scanModeOptions.MANUAL) { // Listen for editor-blur event and trigger scanning if the editor was modified this.registerEvent( this.app.workspace.on( "active-leaf-change", (leaf: WorkspaceLeaf | null) => { - console.log("On Active Leaf Change...\nLeaf =", leaf); this.onFileModifiedAndLostFocus(); + eventEmitter.emit("SAVE_MAP"); }, ), ); this.registerDomEvent(window, "blur", () => { this.onFileModifiedAndLostFocus(); - console.log("Focusing out of the window..."); + eventEmitter.emit("SAVE_MAP"); }); this.registerDomEvent(window, "focus", () => { - this.onFileModifiedAndLostFocus(); - console.log("Focusing in the window..."); + setTimeout(() => { + this.onFileModifiedAndLostFocus(); + eventEmitter.emit("SAVE_MAP"); + }, 200); }); } + this.registerEvent( + this.app.workspace.on("quit", () => { + const currentTime = getCurrentLocalDateTimeString(); + this.app.saveLocalStorage( + OBSIDIAN_CLOSED_TIME_KEY, + currentTime, + ); + }), + ); + // const closeButton = document.querySelector{t("no-tasks-available")}
- )} - > - ) - } -{t("loading-tasks")}
+{t("loading-tasks")}
- > - )} -{t('loading-tasks')}
-// > -// )} -//{t("scroll-to-load-more")} ({visibleTaskCount} / {allTasks.length ?? 0})
-{t("no-tasks-available")}
-{t("scroll-to-load-more")} ({visibleTaskCount} / {allTasks.length ?? 0})
+{t("no-tasks-available")}
+
@@ -138,23 +209,36 @@ export const TasksImporterPanel: React.FC
{t("no-views-in-board")}
+ +{boardData.description}
+{t("your-views")}
+{t("loading-tasks")}
+ > + )} +{t("loading-tasks")}
- > - )} -