@industry-theme/backlogmd-kanban-panel 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KanbanPanel.d.ts","sourceRoot":"","sources":["../../src/panels/KanbanPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAGrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"KanbanPanel.d.ts","sourceRoot":"","sources":["../../src/panels/KanbanPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAGrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AA6LpD;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAMrD,CAAC"}
|
|
@@ -7,29 +7,39 @@ export interface ColumnState {
|
|
|
7
7
|
hasMore: boolean;
|
|
8
8
|
isLoadingMore: boolean;
|
|
9
9
|
}
|
|
10
|
+
/** Source column names (directory-based) */
|
|
11
|
+
export type SourceColumn = 'tasks' | 'completed';
|
|
12
|
+
/** Display labels for source columns */
|
|
13
|
+
export declare const SOURCE_DISPLAY_LABELS: Record<SourceColumn, string>;
|
|
10
14
|
export interface UseKanbanDataResult {
|
|
11
15
|
tasks: Task[];
|
|
12
|
-
|
|
16
|
+
/** Source columns: "tasks" and "completed" */
|
|
17
|
+
sources: SourceColumn[];
|
|
13
18
|
isLoading: boolean;
|
|
14
19
|
error: string | null;
|
|
15
20
|
isBacklogProject: boolean;
|
|
16
|
-
|
|
21
|
+
tasksBySource: Map<string, Task[]>;
|
|
17
22
|
/** Per-column pagination state */
|
|
18
23
|
columnStates: Map<string, ColumnState>;
|
|
19
|
-
/** Load more tasks for a specific
|
|
20
|
-
loadMore: (
|
|
24
|
+
/** Load more tasks for a specific source column */
|
|
25
|
+
loadMore: (source: SourceColumn) => Promise<void>;
|
|
21
26
|
refreshData: () => Promise<void>;
|
|
22
27
|
updateTaskStatus: (taskId: string, newStatus: string) => Promise<void>;
|
|
23
28
|
}
|
|
24
29
|
interface UseKanbanDataOptions {
|
|
25
30
|
context?: PanelContextValue;
|
|
26
31
|
actions?: PanelActions;
|
|
27
|
-
/** Number of tasks to load
|
|
28
|
-
|
|
32
|
+
/** Number of active tasks to load (default: 20) */
|
|
33
|
+
tasksLimit?: number;
|
|
34
|
+
/** Number of completed tasks to load (default: 5) */
|
|
35
|
+
completedLimit?: number;
|
|
29
36
|
}
|
|
30
37
|
/**
|
|
31
|
-
* Hook for managing kanban board data with
|
|
32
|
-
*
|
|
38
|
+
* Hook for managing kanban board data with lazy loading
|
|
39
|
+
*
|
|
40
|
+
* Uses 2-column view (Active/Completed) based on directory structure.
|
|
41
|
+
* Only loads task content for displayed items (no file reads on init).
|
|
42
|
+
* Completed tasks are sorted by ID descending (most recent first).
|
|
33
43
|
*/
|
|
34
44
|
export declare function useKanbanData(options?: UseKanbanDataOptions): UseKanbanDataResult;
|
|
35
45
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useKanbanData.d.ts","sourceRoot":"","sources":["../../../../src/panels/kanban/hooks/useKanbanData.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,IAAI,EAAwB,MAAM,kBAAkB,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,
|
|
1
|
+
{"version":3,"file":"useKanbanData.d.ts","sourceRoot":"","sources":["../../../../src/panels/kanban/hooks/useKanbanData.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,IAAI,EAAwB,MAAM,kBAAkB,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,WAAW,CAAC;AAEjD,wCAAwC;AACxC,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAG9D,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,8CAA8C;IAC9C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,kCAAkC;IAClC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,mDAAmD;IACnD,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CAoUrB"}
|
package/dist/panels.bundle.js
CHANGED
|
@@ -526,10 +526,32 @@ function extractIdFromPath(filePath) {
|
|
|
526
526
|
}
|
|
527
527
|
return filename.replace(/\.md$/, "");
|
|
528
528
|
}
|
|
529
|
+
function extractTitleFromPath(filePath) {
|
|
530
|
+
const filename = filePath.split("/").pop() || "";
|
|
531
|
+
const match = filename.match(/^(?:task-)?\d+(?:\.\d+)?\s*-\s*(.+)\.md$/);
|
|
532
|
+
if (match) {
|
|
533
|
+
return match[1].trim();
|
|
534
|
+
}
|
|
535
|
+
return filename.replace(/\.md$/, "");
|
|
536
|
+
}
|
|
537
|
+
function extractSourceFromPath(filePath) {
|
|
538
|
+
if (filePath.includes("/completed/") || filePath.includes("\\completed\\")) {
|
|
539
|
+
return "completed";
|
|
540
|
+
}
|
|
541
|
+
return "tasks";
|
|
542
|
+
}
|
|
543
|
+
function extractTaskIndexFromPath(filePath) {
|
|
544
|
+
return {
|
|
545
|
+
id: extractIdFromPath(filePath),
|
|
546
|
+
filePath,
|
|
547
|
+
title: extractTitleFromPath(filePath),
|
|
548
|
+
source: extractSourceFromPath(filePath)
|
|
549
|
+
};
|
|
550
|
+
}
|
|
529
551
|
function escapeRegex(str) {
|
|
530
552
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
531
553
|
}
|
|
532
|
-
const DEFAULT_STATUSES
|
|
554
|
+
const DEFAULT_STATUSES = ["To Do", "In Progress", "Done"];
|
|
533
555
|
function parseBacklogConfig(content) {
|
|
534
556
|
const config = {};
|
|
535
557
|
const lines = content.split("\n");
|
|
@@ -609,10 +631,10 @@ function parseBacklogConfig(content) {
|
|
|
609
631
|
}
|
|
610
632
|
return {
|
|
611
633
|
projectName: config.projectName || "Backlog",
|
|
612
|
-
statuses: config.statuses || [...DEFAULT_STATUSES
|
|
634
|
+
statuses: config.statuses || [...DEFAULT_STATUSES],
|
|
613
635
|
labels: config.labels || [],
|
|
614
636
|
milestones: config.milestones || [],
|
|
615
|
-
defaultStatus: config.defaultStatus || DEFAULT_STATUSES
|
|
637
|
+
defaultStatus: config.defaultStatus || DEFAULT_STATUSES[0],
|
|
616
638
|
dateFormat: config.dateFormat || "YYYY-MM-DD",
|
|
617
639
|
defaultAssignee: config.defaultAssignee,
|
|
618
640
|
defaultReporter: config.defaultReporter,
|
|
@@ -747,6 +769,9 @@ class Core {
|
|
|
747
769
|
__publicField(this, "config", null);
|
|
748
770
|
__publicField(this, "tasks", /* @__PURE__ */ new Map());
|
|
749
771
|
__publicField(this, "initialized", false);
|
|
772
|
+
/** Lightweight task index for lazy loading (no file reads) */
|
|
773
|
+
__publicField(this, "taskIndex", /* @__PURE__ */ new Map());
|
|
774
|
+
__publicField(this, "lazyInitialized", false);
|
|
750
775
|
this.projectRoot = options.projectRoot;
|
|
751
776
|
this.fs = options.adapters.fs;
|
|
752
777
|
}
|
|
@@ -828,6 +853,181 @@ class Core {
|
|
|
828
853
|
}
|
|
829
854
|
this.initialized = true;
|
|
830
855
|
}
|
|
856
|
+
/**
|
|
857
|
+
* Initialize with lazy loading (no file content reads)
|
|
858
|
+
*
|
|
859
|
+
* Only loads config and builds task index from file paths.
|
|
860
|
+
* Task content is loaded on-demand via loadTask().
|
|
861
|
+
* Use this for web/panel contexts where file reads are expensive.
|
|
862
|
+
*
|
|
863
|
+
* @param filePaths - Array of all file paths in the project
|
|
864
|
+
*/
|
|
865
|
+
async initializeLazy(filePaths) {
|
|
866
|
+
if (this.lazyInitialized)
|
|
867
|
+
return;
|
|
868
|
+
const configPath = this.fs.join(this.projectRoot, "backlog", "config.yml");
|
|
869
|
+
const configExists = await this.fs.exists(configPath);
|
|
870
|
+
if (!configExists) {
|
|
871
|
+
throw new Error(`Not a Backlog.md project: config.yml not found at ${configPath}`);
|
|
872
|
+
}
|
|
873
|
+
const configContent = await this.fs.readFile(configPath);
|
|
874
|
+
this.config = parseBacklogConfig(configContent);
|
|
875
|
+
this.taskIndex.clear();
|
|
876
|
+
for (const filePath of filePaths) {
|
|
877
|
+
if (!filePath.endsWith(".md"))
|
|
878
|
+
continue;
|
|
879
|
+
const isTaskFile = filePath.includes("backlog/tasks/") || filePath.includes("backlog\\tasks\\");
|
|
880
|
+
const isCompletedFile = filePath.includes("backlog/completed/") || filePath.includes("backlog\\completed\\");
|
|
881
|
+
if (!isTaskFile && !isCompletedFile)
|
|
882
|
+
continue;
|
|
883
|
+
if (filePath.endsWith("config.yml"))
|
|
884
|
+
continue;
|
|
885
|
+
const indexEntry = extractTaskIndexFromPath(filePath);
|
|
886
|
+
this.taskIndex.set(indexEntry.id, indexEntry);
|
|
887
|
+
}
|
|
888
|
+
this.lazyInitialized = true;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Check if lazy initialization is complete
|
|
892
|
+
*/
|
|
893
|
+
isLazyInitialized() {
|
|
894
|
+
return this.lazyInitialized;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Get the task index (lightweight entries)
|
|
898
|
+
*/
|
|
899
|
+
getTaskIndex() {
|
|
900
|
+
if (!this.lazyInitialized) {
|
|
901
|
+
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
902
|
+
}
|
|
903
|
+
return this.taskIndex;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Load a single task by ID (on-demand loading)
|
|
907
|
+
*
|
|
908
|
+
* @param id - Task ID
|
|
909
|
+
* @returns Task or undefined if not found
|
|
910
|
+
*/
|
|
911
|
+
async loadTask(id) {
|
|
912
|
+
if (this.tasks.has(id)) {
|
|
913
|
+
return this.tasks.get(id);
|
|
914
|
+
}
|
|
915
|
+
const indexEntry = this.taskIndex.get(id);
|
|
916
|
+
if (!indexEntry) {
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
const content = await this.fs.readFile(indexEntry.filePath);
|
|
921
|
+
const task = parseTaskMarkdown(content, indexEntry.filePath);
|
|
922
|
+
task.source = indexEntry.source === "completed" ? "completed" : "local";
|
|
923
|
+
this.tasks.set(task.id, task);
|
|
924
|
+
return task;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
console.warn(`Failed to load task ${id} from ${indexEntry.filePath}:`, error);
|
|
927
|
+
return void 0;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Load multiple tasks by ID (on-demand loading)
|
|
932
|
+
*
|
|
933
|
+
* @param ids - Array of task IDs to load
|
|
934
|
+
* @returns Array of loaded tasks (undefined entries filtered out)
|
|
935
|
+
*/
|
|
936
|
+
async loadTasks(ids) {
|
|
937
|
+
const tasks = await Promise.all(ids.map((id) => this.loadTask(id)));
|
|
938
|
+
return tasks.filter((t) => t !== void 0);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Get tasks by source (tasks/completed) with pagination
|
|
942
|
+
*
|
|
943
|
+
* This is a lazy-loading alternative to getTasksByStatusPaginated().
|
|
944
|
+
* Groups by directory (tasks/ or completed/) instead of status.
|
|
945
|
+
* Only loads task content for items in the requested page.
|
|
946
|
+
*
|
|
947
|
+
* @param options - Per-source pagination options
|
|
948
|
+
* @returns Paginated tasks grouped by source
|
|
949
|
+
*/
|
|
950
|
+
async getTasksBySourcePaginated(options) {
|
|
951
|
+
if (!this.lazyInitialized) {
|
|
952
|
+
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
953
|
+
}
|
|
954
|
+
const tasksLimit = (options == null ? void 0 : options.tasksLimit) ?? 10;
|
|
955
|
+
const completedLimit = (options == null ? void 0 : options.completedLimit) ?? 10;
|
|
956
|
+
const offset = (options == null ? void 0 : options.offset) ?? 0;
|
|
957
|
+
const tasksSortDirection = (options == null ? void 0 : options.tasksSortDirection) ?? "asc";
|
|
958
|
+
const completedSortByIdDesc = (options == null ? void 0 : options.completedSortByIdDesc) ?? true;
|
|
959
|
+
const sources = ["tasks", "completed"];
|
|
960
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
961
|
+
for (const source of sources) {
|
|
962
|
+
let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source);
|
|
963
|
+
const limit = source === "tasks" ? tasksLimit : completedLimit;
|
|
964
|
+
if (source === "completed" && completedSortByIdDesc) {
|
|
965
|
+
entries = entries.sort((a, b) => {
|
|
966
|
+
const aNum = parseInt(a.id.replace(/\D/g, ""), 10) || 0;
|
|
967
|
+
const bNum = parseInt(b.id.replace(/\D/g, ""), 10) || 0;
|
|
968
|
+
return bNum - aNum;
|
|
969
|
+
});
|
|
970
|
+
} else {
|
|
971
|
+
entries = entries.sort((a, b) => {
|
|
972
|
+
const cmp = a.title.localeCompare(b.title);
|
|
973
|
+
return tasksSortDirection === "asc" ? cmp : -cmp;
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
const total = entries.length;
|
|
977
|
+
const pageEntries = entries.slice(offset, offset + limit);
|
|
978
|
+
const items = await this.loadTasks(pageEntries.map((e) => e.id));
|
|
979
|
+
bySource.set(source, {
|
|
980
|
+
items,
|
|
981
|
+
total,
|
|
982
|
+
hasMore: offset + limit < total,
|
|
983
|
+
offset,
|
|
984
|
+
limit
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
bySource,
|
|
989
|
+
sources
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Load more tasks for a specific source (lazy loading)
|
|
994
|
+
*
|
|
995
|
+
* @param source - Source to load more from ("tasks" or "completed")
|
|
996
|
+
* @param currentOffset - Current offset (items already loaded)
|
|
997
|
+
* @param options - Pagination options
|
|
998
|
+
* @returns Paginated result for the source
|
|
999
|
+
*/
|
|
1000
|
+
async loadMoreForSource(source, currentOffset, options) {
|
|
1001
|
+
if (!this.lazyInitialized) {
|
|
1002
|
+
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
1003
|
+
}
|
|
1004
|
+
const limit = (options == null ? void 0 : options.limit) ?? 10;
|
|
1005
|
+
const sortDirection = (options == null ? void 0 : options.sortDirection) ?? "asc";
|
|
1006
|
+
const completedSortByIdDesc = (options == null ? void 0 : options.completedSortByIdDesc) ?? true;
|
|
1007
|
+
let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source);
|
|
1008
|
+
if (source === "completed" && completedSortByIdDesc) {
|
|
1009
|
+
entries = entries.sort((a, b) => {
|
|
1010
|
+
const aNum = parseInt(a.id.replace(/\D/g, ""), 10) || 0;
|
|
1011
|
+
const bNum = parseInt(b.id.replace(/\D/g, ""), 10) || 0;
|
|
1012
|
+
return bNum - aNum;
|
|
1013
|
+
});
|
|
1014
|
+
} else {
|
|
1015
|
+
entries = entries.sort((a, b) => {
|
|
1016
|
+
const cmp = a.title.localeCompare(b.title);
|
|
1017
|
+
return sortDirection === "asc" ? cmp : -cmp;
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
const total = entries.length;
|
|
1021
|
+
const pageEntries = entries.slice(currentOffset, currentOffset + limit);
|
|
1022
|
+
const items = await this.loadTasks(pageEntries.map((e) => e.id));
|
|
1023
|
+
return {
|
|
1024
|
+
items,
|
|
1025
|
+
total,
|
|
1026
|
+
hasMore: currentOffset + limit < total,
|
|
1027
|
+
offset: currentOffset,
|
|
1028
|
+
limit
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
831
1031
|
/**
|
|
832
1032
|
* Get the loaded configuration
|
|
833
1033
|
*
|
|
@@ -1155,16 +1355,26 @@ class PanelFileSystemAdapter {
|
|
|
1155
1355
|
return path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/");
|
|
1156
1356
|
}
|
|
1157
1357
|
}
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1358
|
+
const SOURCE_DISPLAY_LABELS = {
|
|
1359
|
+
tasks: "Active",
|
|
1360
|
+
completed: "Completed"
|
|
1361
|
+
};
|
|
1362
|
+
const DEFAULT_SOURCES = ["tasks", "completed"];
|
|
1363
|
+
const DEFAULT_TASKS_LIMIT = 20;
|
|
1364
|
+
const DEFAULT_COMPLETED_LIMIT = 5;
|
|
1160
1365
|
function useKanbanData(options) {
|
|
1161
|
-
const {
|
|
1366
|
+
const {
|
|
1367
|
+
context,
|
|
1368
|
+
actions,
|
|
1369
|
+
tasksLimit = DEFAULT_TASKS_LIMIT,
|
|
1370
|
+
completedLimit = DEFAULT_COMPLETED_LIMIT
|
|
1371
|
+
} = options || {};
|
|
1162
1372
|
const [tasks, setTasks] = useState([]);
|
|
1163
|
-
const [
|
|
1373
|
+
const [sources] = useState(DEFAULT_SOURCES);
|
|
1164
1374
|
const [isLoading, setIsLoading] = useState(true);
|
|
1165
1375
|
const [error, setError] = useState(null);
|
|
1166
1376
|
const [isBacklogProject, setIsBacklogProject] = useState(false);
|
|
1167
|
-
const [
|
|
1377
|
+
const [tasksBySource, setTasksBySource] = useState(
|
|
1168
1378
|
/* @__PURE__ */ new Map()
|
|
1169
1379
|
);
|
|
1170
1380
|
const [columnStates, setColumnStates] = useState(
|
|
@@ -1213,8 +1423,7 @@ function useKanbanData(options) {
|
|
|
1213
1423
|
console.log("[useKanbanData] No context provided");
|
|
1214
1424
|
setIsBacklogProject(false);
|
|
1215
1425
|
setTasks([]);
|
|
1216
|
-
|
|
1217
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1426
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1218
1427
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1219
1428
|
setIsLoading(false);
|
|
1220
1429
|
coreRef.current = null;
|
|
@@ -1228,8 +1437,7 @@ function useKanbanData(options) {
|
|
|
1228
1437
|
console.log("[useKanbanData] FileTree not available");
|
|
1229
1438
|
setIsBacklogProject(false);
|
|
1230
1439
|
setTasks([]);
|
|
1231
|
-
|
|
1232
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1440
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1233
1441
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1234
1442
|
coreRef.current = null;
|
|
1235
1443
|
return;
|
|
@@ -1249,31 +1457,30 @@ function useKanbanData(options) {
|
|
|
1249
1457
|
console.log("[useKanbanData] Not a Backlog.md project");
|
|
1250
1458
|
setIsBacklogProject(false);
|
|
1251
1459
|
setTasks([]);
|
|
1252
|
-
|
|
1253
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1460
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1254
1461
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1255
1462
|
coreRef.current = null;
|
|
1256
1463
|
return;
|
|
1257
1464
|
}
|
|
1258
|
-
console.log("[useKanbanData] Loading Backlog.md data with
|
|
1465
|
+
console.log("[useKanbanData] Loading Backlog.md data with lazy loading...");
|
|
1259
1466
|
setIsBacklogProject(true);
|
|
1260
|
-
await core.
|
|
1467
|
+
await core.initializeLazy(filePaths);
|
|
1261
1468
|
coreRef.current = core;
|
|
1262
|
-
const
|
|
1263
|
-
|
|
1264
|
-
|
|
1469
|
+
const paginatedResult = await core.getTasksBySourcePaginated({
|
|
1470
|
+
tasksLimit,
|
|
1471
|
+
completedLimit,
|
|
1265
1472
|
offset: 0,
|
|
1266
|
-
|
|
1267
|
-
|
|
1473
|
+
tasksSortDirection: "asc",
|
|
1474
|
+
completedSortByIdDesc: true
|
|
1268
1475
|
});
|
|
1269
|
-
const
|
|
1476
|
+
const newTasksBySource = /* @__PURE__ */ new Map();
|
|
1270
1477
|
const newColumnStates = /* @__PURE__ */ new Map();
|
|
1271
1478
|
let allTasks = [];
|
|
1272
|
-
for (const
|
|
1273
|
-
const columnResult = paginatedResult.
|
|
1479
|
+
for (const source of paginatedResult.sources) {
|
|
1480
|
+
const columnResult = paginatedResult.bySource.get(source);
|
|
1274
1481
|
if (columnResult) {
|
|
1275
|
-
|
|
1276
|
-
newColumnStates.set(
|
|
1482
|
+
newTasksBySource.set(source, columnResult.items);
|
|
1483
|
+
newColumnStates.set(source, {
|
|
1277
1484
|
tasks: columnResult.items,
|
|
1278
1485
|
total: columnResult.total,
|
|
1279
1486
|
hasMore: columnResult.hasMore,
|
|
@@ -1281,8 +1488,8 @@ function useKanbanData(options) {
|
|
|
1281
1488
|
});
|
|
1282
1489
|
allTasks = allTasks.concat(columnResult.items);
|
|
1283
1490
|
} else {
|
|
1284
|
-
|
|
1285
|
-
newColumnStates.set(
|
|
1491
|
+
newTasksBySource.set(source, []);
|
|
1492
|
+
newColumnStates.set(source, {
|
|
1286
1493
|
tasks: [],
|
|
1287
1494
|
total: 0,
|
|
1288
1495
|
hasMore: false,
|
|
@@ -1290,72 +1497,71 @@ function useKanbanData(options) {
|
|
|
1290
1497
|
});
|
|
1291
1498
|
}
|
|
1292
1499
|
}
|
|
1293
|
-
const totalTasks = Array.from(paginatedResult.
|
|
1500
|
+
const totalTasks = Array.from(paginatedResult.bySource.values()).reduce(
|
|
1294
1501
|
(sum, col) => sum + col.total,
|
|
1295
1502
|
0
|
|
1296
1503
|
);
|
|
1297
1504
|
console.log(
|
|
1298
|
-
`[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks
|
|
1505
|
+
`[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks (active: ${tasksLimit}, completed: ${completedLimit})`
|
|
1299
1506
|
);
|
|
1300
|
-
setStatuses(config.statuses);
|
|
1301
1507
|
setTasks(allTasks);
|
|
1302
|
-
|
|
1508
|
+
setTasksBySource(newTasksBySource);
|
|
1303
1509
|
setColumnStates(newColumnStates);
|
|
1304
1510
|
} catch (err) {
|
|
1305
1511
|
console.error("[useKanbanData] Failed to load Backlog.md data:", err);
|
|
1306
1512
|
setError(err instanceof Error ? err.message : "Failed to load backlog data");
|
|
1307
1513
|
setIsBacklogProject(false);
|
|
1308
1514
|
setTasks([]);
|
|
1309
|
-
|
|
1310
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1515
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1311
1516
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1312
1517
|
coreRef.current = null;
|
|
1313
1518
|
} finally {
|
|
1314
1519
|
setIsLoading(false);
|
|
1315
1520
|
}
|
|
1316
|
-
}, [context, actions, fetchFileContent,
|
|
1521
|
+
}, [context, actions, fetchFileContent, tasksLimit, completedLimit]);
|
|
1317
1522
|
useEffect(() => {
|
|
1318
1523
|
loadBacklogData();
|
|
1319
1524
|
}, [loadBacklogData]);
|
|
1320
1525
|
const loadMore = useCallback(
|
|
1321
|
-
async (
|
|
1526
|
+
async (source) => {
|
|
1322
1527
|
const core = coreRef.current;
|
|
1323
1528
|
if (!core) {
|
|
1324
1529
|
console.warn("[useKanbanData] Core not available for loadMore");
|
|
1325
1530
|
return;
|
|
1326
1531
|
}
|
|
1327
|
-
const currentState = columnStates.get(
|
|
1532
|
+
const currentState = columnStates.get(source);
|
|
1328
1533
|
if (!currentState || !currentState.hasMore || currentState.isLoadingMore) {
|
|
1329
1534
|
return;
|
|
1330
1535
|
}
|
|
1331
1536
|
setColumnStates((prev) => {
|
|
1332
1537
|
const newStates = new Map(prev);
|
|
1333
|
-
const state = newStates.get(
|
|
1538
|
+
const state = newStates.get(source);
|
|
1334
1539
|
if (state) {
|
|
1335
|
-
newStates.set(
|
|
1540
|
+
newStates.set(source, { ...state, isLoadingMore: true });
|
|
1336
1541
|
}
|
|
1337
1542
|
return newStates;
|
|
1338
1543
|
});
|
|
1339
1544
|
try {
|
|
1340
1545
|
const currentOffset = currentState.tasks.length;
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1546
|
+
const limit = source === "tasks" ? tasksLimit : completedLimit;
|
|
1547
|
+
const result = await core.loadMoreForSource(
|
|
1548
|
+
source,
|
|
1343
1549
|
currentOffset,
|
|
1344
1550
|
{
|
|
1345
|
-
limit
|
|
1346
|
-
|
|
1347
|
-
|
|
1551
|
+
limit,
|
|
1552
|
+
sortDirection: "asc",
|
|
1553
|
+
completedSortByIdDesc: source === "completed"
|
|
1348
1554
|
}
|
|
1349
1555
|
);
|
|
1350
1556
|
console.log(
|
|
1351
|
-
`[useKanbanData] Loaded ${result.items.length} more tasks for "${
|
|
1557
|
+
`[useKanbanData] Loaded ${result.items.length} more tasks for "${source}" (${currentOffset + result.items.length}/${result.total})`
|
|
1352
1558
|
);
|
|
1353
1559
|
setColumnStates((prev) => {
|
|
1354
1560
|
const newStates = new Map(prev);
|
|
1355
|
-
const state = newStates.get(
|
|
1561
|
+
const state = newStates.get(source);
|
|
1356
1562
|
if (state) {
|
|
1357
1563
|
const newTasks = [...state.tasks, ...result.items];
|
|
1358
|
-
newStates.set(
|
|
1564
|
+
newStates.set(source, {
|
|
1359
1565
|
tasks: newTasks,
|
|
1360
1566
|
total: result.total,
|
|
1361
1567
|
hasMore: result.hasMore,
|
|
@@ -1364,27 +1570,27 @@ function useKanbanData(options) {
|
|
|
1364
1570
|
}
|
|
1365
1571
|
return newStates;
|
|
1366
1572
|
});
|
|
1367
|
-
|
|
1573
|
+
setTasksBySource((prev) => {
|
|
1368
1574
|
const newMap = new Map(prev);
|
|
1369
|
-
const currentTasks = newMap.get(
|
|
1370
|
-
newMap.set(
|
|
1575
|
+
const currentTasks = newMap.get(source) || [];
|
|
1576
|
+
newMap.set(source, [...currentTasks, ...result.items]);
|
|
1371
1577
|
return newMap;
|
|
1372
1578
|
});
|
|
1373
1579
|
setTasks((prev) => [...prev, ...result.items]);
|
|
1374
1580
|
} catch (err) {
|
|
1375
|
-
console.error(`[useKanbanData] Failed to load more for "${
|
|
1581
|
+
console.error(`[useKanbanData] Failed to load more for "${source}":`, err);
|
|
1376
1582
|
setError(err instanceof Error ? err.message : "Failed to load more tasks");
|
|
1377
1583
|
setColumnStates((prev) => {
|
|
1378
1584
|
const newStates = new Map(prev);
|
|
1379
|
-
const state = newStates.get(
|
|
1585
|
+
const state = newStates.get(source);
|
|
1380
1586
|
if (state) {
|
|
1381
|
-
newStates.set(
|
|
1587
|
+
newStates.set(source, { ...state, isLoadingMore: false });
|
|
1382
1588
|
}
|
|
1383
1589
|
return newStates;
|
|
1384
1590
|
});
|
|
1385
1591
|
}
|
|
1386
1592
|
},
|
|
1387
|
-
[columnStates,
|
|
1593
|
+
[columnStates, tasksLimit, completedLimit]
|
|
1388
1594
|
);
|
|
1389
1595
|
const refreshData = useCallback(async () => {
|
|
1390
1596
|
await loadBacklogData();
|
|
@@ -1401,11 +1607,11 @@ function useKanbanData(options) {
|
|
|
1401
1607
|
);
|
|
1402
1608
|
return {
|
|
1403
1609
|
tasks,
|
|
1404
|
-
|
|
1610
|
+
sources,
|
|
1405
1611
|
isLoading,
|
|
1406
1612
|
error,
|
|
1407
1613
|
isBacklogProject,
|
|
1408
|
-
|
|
1614
|
+
tasksBySource,
|
|
1409
1615
|
columnStates,
|
|
1410
1616
|
loadMore,
|
|
1411
1617
|
refreshData,
|
|
@@ -1944,10 +2150,11 @@ const KanbanPanelContent = ({
|
|
|
1944
2150
|
var _a, _b;
|
|
1945
2151
|
const { theme: theme2 } = useTheme();
|
|
1946
2152
|
const [_selectedTask, setSelectedTask] = useState(null);
|
|
1947
|
-
const {
|
|
2153
|
+
const { sources, tasksBySource, columnStates, loadMore, error, isBacklogProject, refreshData } = useKanbanData({
|
|
1948
2154
|
context,
|
|
1949
2155
|
actions,
|
|
1950
|
-
|
|
2156
|
+
tasksLimit: 20,
|
|
2157
|
+
completedLimit: 5
|
|
1951
2158
|
});
|
|
1952
2159
|
const handleTaskClick = (task) => {
|
|
1953
2160
|
setSelectedTask(task);
|
|
@@ -2091,21 +2298,21 @@ const KanbanPanelContent = ({
|
|
|
2091
2298
|
WebkitOverflowScrolling: "touch"
|
|
2092
2299
|
// Smooth scrolling on iOS
|
|
2093
2300
|
},
|
|
2094
|
-
children:
|
|
2095
|
-
const columnTasks =
|
|
2096
|
-
const columnState = columnStates.get(
|
|
2301
|
+
children: sources.map((source) => {
|
|
2302
|
+
const columnTasks = tasksBySource.get(source) || [];
|
|
2303
|
+
const columnState = columnStates.get(source);
|
|
2097
2304
|
return /* @__PURE__ */ jsx(
|
|
2098
2305
|
KanbanColumn,
|
|
2099
2306
|
{
|
|
2100
|
-
status,
|
|
2307
|
+
status: SOURCE_DISPLAY_LABELS[source],
|
|
2101
2308
|
tasks: columnTasks,
|
|
2102
2309
|
total: columnState == null ? void 0 : columnState.total,
|
|
2103
2310
|
hasMore: columnState == null ? void 0 : columnState.hasMore,
|
|
2104
2311
|
isLoadingMore: columnState == null ? void 0 : columnState.isLoadingMore,
|
|
2105
|
-
onLoadMore: () => loadMore(
|
|
2312
|
+
onLoadMore: () => loadMore(source),
|
|
2106
2313
|
onTaskClick: handleTaskClick
|
|
2107
2314
|
},
|
|
2108
|
-
|
|
2315
|
+
source
|
|
2109
2316
|
);
|
|
2110
2317
|
})
|
|
2111
2318
|
}
|