mirror of
https://github.com/vannamhh/progress-tracker.git
synced 2026-07-22 12:20:25 +00:00
fix: move wrong file in kanban
This commit is contained in:
parent
7f669cf415
commit
39bd95843a
3 changed files with 197 additions and 165 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -20,3 +20,6 @@ data.json
|
|||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
.cursor
|
||||
cursorkleosr
|
||||
357
main.ts
357
main.ts
|
|
@ -1206,30 +1206,83 @@ class TaskProgressBarView extends ItemView {
|
|||
const filePathWithoutExtension = filePath.replace(/\.md$/, "");
|
||||
const fileName = file.basename;
|
||||
|
||||
// Check for common reference patterns
|
||||
const patterns = [
|
||||
// Markdown link: [Note Title](path/to/note.md)
|
||||
new RegExp(`\\[.*?\\]\\(${this.escapeRegExp(filePath)}\\)`, "i"),
|
||||
// Obsidian link: [[path/to/note]]
|
||||
new RegExp(
|
||||
`\\[\\[${this.escapeRegExp(
|
||||
filePathWithoutExtension
|
||||
)}(\\|.*?)?\\]\\]`,
|
||||
"i"
|
||||
),
|
||||
// Obsidian link with just filename: [[Note Title]]
|
||||
new RegExp(
|
||||
`\\[\\[${this.escapeRegExp(fileName)}(\\|.*?)?\\]\\]`,
|
||||
"i"
|
||||
),
|
||||
// Plain text mention of filepath
|
||||
new RegExp(`\\b${this.escapeRegExp(filePath)}\\b`, "i"),
|
||||
// Plain text mention of filename
|
||||
new RegExp(`\\b${this.escapeRegExp(fileName)}\\b`, "i"),
|
||||
];
|
||||
// First try to find exact matches in Obsidian-style links
|
||||
const obsidianLinks = this.extractObsidianLinks(boardContent);
|
||||
for (const link of obsidianLinks) {
|
||||
const { path, alias } = link;
|
||||
if (path === fileName || path === filePathWithoutExtension || path === filePath) {
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(`Found exact Obsidian link match for ${fileName}: ${path}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if any pattern matches
|
||||
return patterns.some((pattern) => pattern.test(boardContent));
|
||||
// Then check for Markdown-style links
|
||||
const markdownLinks = this.extractMarkdownLinks(boardContent);
|
||||
for (const link of markdownLinks) {
|
||||
const { text, url } = link;
|
||||
if (url === filePath || url === filePathWithoutExtension) {
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(`Found exact Markdown link match for ${fileName}: ${url}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally check for exact filepath mentions (with strict boundaries)
|
||||
const filepathPattern = new RegExp(`(?:^|\\s|\\()${this.escapeRegExp(filePath)}(?:$|\\s|\\))`, "i");
|
||||
if (filepathPattern.test(boardContent)) {
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(`Found exact filepath match for ${filePath}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all Obsidian-style links from content
|
||||
* Returns array of {path, alias} objects
|
||||
*/
|
||||
private extractObsidianLinks(content: string): Array<{path: string, alias?: string}> {
|
||||
const links: Array<{path: string, alias?: string}> = [];
|
||||
const linkPattern = /\[\[(.*?)\]\]/g;
|
||||
let match;
|
||||
|
||||
while ((match = linkPattern.exec(content)) !== null) {
|
||||
const [_, linkContent] = match;
|
||||
const [path, alias] = linkContent.split("|").map(s => s.trim());
|
||||
links.push({ path, alias });
|
||||
}
|
||||
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log("Extracted Obsidian links:", links);
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all Markdown-style links from content
|
||||
* Returns array of {text, url} objects
|
||||
*/
|
||||
private extractMarkdownLinks(content: string): Array<{text: string, url: string}> {
|
||||
const links: Array<{text: string, url: string}> = [];
|
||||
const linkPattern = /\[(.*?)\]\((.*?)\)/g;
|
||||
let match;
|
||||
|
||||
while ((match = linkPattern.exec(content)) !== null) {
|
||||
const [_, text, url] = match;
|
||||
links.push({ text: text.trim(), url: url.trim() });
|
||||
}
|
||||
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log("Extracted Markdown links:", links);
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1303,168 +1356,85 @@ class TaskProgressBarView extends ItemView {
|
|||
);
|
||||
}
|
||||
|
||||
// Look for the card containing reference to our file in each column
|
||||
// Process each column to find and move the card
|
||||
let cardMoved = false;
|
||||
let newContent = boardContent;
|
||||
|
||||
// Build file reference pattern options for searching
|
||||
const filePath = fileToMove.path;
|
||||
const filePathWithoutExtension = filePath.replace(/\.md$/, "");
|
||||
const fileName = fileToMove.basename;
|
||||
// Split content into lines for more accurate processing
|
||||
const lines = newContent.split("\n");
|
||||
let currentColumn = "";
|
||||
let cardStartIndex = -1;
|
||||
let cardEndIndex = -1;
|
||||
|
||||
// Patterns to match different kinds of file references in cards
|
||||
const fileRefPatterns = [
|
||||
`\\[.*?\\]\\(${this.escapeRegExp(filePath)}\\)`,
|
||||
`\\[\\[${this.escapeRegExp(
|
||||
filePathWithoutExtension
|
||||
)}(\\|.*?)?\\]\\]`,
|
||||
`\\[\\[${this.escapeRegExp(fileName)}(\\|.*?)?\\]\\]`,
|
||||
`\\b${this.escapeRegExp(filePath)}\\b`,
|
||||
`\\b${this.escapeRegExp(fileName)}\\b`,
|
||||
];
|
||||
// First pass: Find the exact card that references our file
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// Join patterns with OR for combined search
|
||||
const fileRefRegex = new RegExp(fileRefPatterns.join("|"), "i");
|
||||
|
||||
// Process each column
|
||||
for (const columnName in kanbanColumns) {
|
||||
// Skip the target column - we don't need to move items already there
|
||||
if (
|
||||
columnName.toLowerCase() === targetColumnName.toLowerCase()
|
||||
) {
|
||||
// Check for column header
|
||||
if (line.startsWith("## ")) {
|
||||
currentColumn = line.substring(3).trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
const column = kanbanColumns[columnName];
|
||||
|
||||
// Check each card in the column
|
||||
for (let i = 0; i < column.items.length; i++) {
|
||||
const card = column.items[i];
|
||||
// Skip if we're already in target column
|
||||
if (currentColumn.toLowerCase() === targetColumnName.toLowerCase()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if line is a card (starts with "- ")
|
||||
if (line.trim().startsWith("- ")) {
|
||||
// Extract the card content
|
||||
const cardContent = this.getCompleteCardContent(lines, i);
|
||||
|
||||
// Check if this card references our file
|
||||
if (fileRefRegex.test(card.text)) {
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(
|
||||
`Found card in column "${columnName}" that references file ${fileToMove.path}`
|
||||
);
|
||||
}
|
||||
if (this.isExactCardForFile(cardContent.content, fileToMove)) {
|
||||
cardStartIndex = i;
|
||||
cardEndIndex = i + cardContent.lineCount - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip to end of card
|
||||
i += cardContent.lineCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this card from its current position
|
||||
// Find this card's position in the file content
|
||||
const columnRegex = new RegExp(
|
||||
`## ${this.escapeRegExp(columnName)}\\s*\\n`
|
||||
);
|
||||
const columnMatch = newContent.match(columnRegex);
|
||||
// If we found the card, move it
|
||||
if (cardStartIndex !== -1 && cardEndIndex !== -1) {
|
||||
// Extract the card content
|
||||
const cardLines = lines.slice(cardStartIndex, cardEndIndex + 1);
|
||||
|
||||
// Remove the card from its current position
|
||||
lines.splice(cardStartIndex, cardEndIndex - cardStartIndex + 1);
|
||||
|
||||
if (!columnMatch) continue;
|
||||
|
||||
const columnStart = columnMatch.index!;
|
||||
const columnHeaderEnd =
|
||||
columnStart + columnMatch[0].length;
|
||||
|
||||
// Find the next column or the end of file
|
||||
const nextColumnRegex = /^## /gm;
|
||||
nextColumnRegex.lastIndex = columnHeaderEnd;
|
||||
const nextColumnMatch =
|
||||
nextColumnRegex.exec(newContent);
|
||||
const columnEnd = nextColumnMatch
|
||||
? nextColumnMatch.index
|
||||
: newContent.length;
|
||||
|
||||
// Get this column's content
|
||||
const columnContent = newContent.substring(
|
||||
columnHeaderEnd,
|
||||
columnEnd
|
||||
);
|
||||
|
||||
// Try to find the card in the column
|
||||
const cleanCardText = this.escapeRegExp(
|
||||
card.text.trim()
|
||||
);
|
||||
const cardRegex = new RegExp(
|
||||
`(^|\\n)(${cleanCardText})(\\n|$)`
|
||||
);
|
||||
const cardMatch = columnContent.match(cardRegex);
|
||||
|
||||
if (!cardMatch) {
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(
|
||||
`Could not find card in column ${columnName}`
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate absolute positions in the whole content
|
||||
const cardStart =
|
||||
columnHeaderEnd +
|
||||
cardMatch.index! +
|
||||
(cardMatch[1] === "\n" ? 1 : 0);
|
||||
const cardEnd = cardStart + card.text.length;
|
||||
|
||||
// Safety check - don't remove a card if our indices are wrong
|
||||
if (
|
||||
cardEnd <= cardStart ||
|
||||
cardStart < 0 ||
|
||||
cardEnd > newContent.length
|
||||
) {
|
||||
console.error(
|
||||
`Invalid card position: start=${cardStart}, end=${cardEnd}, contentLength=${newContent.length}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove this card from its current column
|
||||
const beforeCard = newContent.substring(0, cardStart);
|
||||
const afterCard = newContent.substring(cardEnd);
|
||||
|
||||
// Handle newlines correctly
|
||||
newContent = beforeCard + afterCard;
|
||||
|
||||
// Find the target column
|
||||
const targetColumnRegex = new RegExp(
|
||||
`## ${this.escapeRegExp(targetColumnName)}\\s*\\n`
|
||||
);
|
||||
const targetColumnMatch =
|
||||
newContent.match(targetColumnRegex);
|
||||
|
||||
if (!targetColumnMatch) continue;
|
||||
|
||||
const targetInsertPosition =
|
||||
targetColumnMatch.index! +
|
||||
targetColumnMatch[0].length;
|
||||
|
||||
// Insert the card at the beginning of the target column
|
||||
newContent =
|
||||
newContent.substring(0, targetInsertPosition) +
|
||||
card.text +
|
||||
"\n" +
|
||||
newContent.substring(targetInsertPosition);
|
||||
|
||||
cardMoved = true;
|
||||
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(
|
||||
`Moved card for ${fileToMove.path} from "${columnName}" to "${targetColumnName}" in ${boardFile.path}`
|
||||
);
|
||||
}
|
||||
|
||||
// Break once we've found and moved the card
|
||||
// Find the target column and insert the card
|
||||
let targetInsertIndex = -1;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].startsWith(`## ${targetColumnName}`)) {
|
||||
targetInsertIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we've moved the card, no need to check other columns
|
||||
if (cardMoved) break;
|
||||
if (targetInsertIndex !== -1) {
|
||||
// Insert the card at the beginning of the target column
|
||||
lines.splice(targetInsertIndex, 0, ...cardLines);
|
||||
cardMoved = true;
|
||||
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(
|
||||
`Moved card for ${fileToMove.path} to column "${targetColumnName}" in ${boardFile.path}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the file if changes were made
|
||||
if (cardMoved && newContent !== boardContent) {
|
||||
// If card was moved, update the file
|
||||
if (cardMoved) {
|
||||
newContent = lines.join("\n");
|
||||
await this.plugin.app.vault.modify(boardFile, newContent);
|
||||
return newContent;
|
||||
}
|
||||
|
||||
return boardContent;
|
||||
return newContent;
|
||||
} catch (error) {
|
||||
console.error("Error moving card in Kanban board:", error);
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
|
|
@ -1474,6 +1444,65 @@ class TaskProgressBarView extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete content of a card, including any sub-items
|
||||
*/
|
||||
private getCompleteCardContent(lines: string[], startIndex: number): { content: string, lineCount: number } {
|
||||
let content = lines[startIndex];
|
||||
let lineCount = 1;
|
||||
|
||||
// Check subsequent lines for sub-items (indented)
|
||||
for (let i = startIndex + 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.trim() === "" || line.startsWith(" ") || line.startsWith("\t")) {
|
||||
content += "\n" + line;
|
||||
lineCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { content, lineCount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a card content exactly matches a file reference
|
||||
*/
|
||||
private isExactCardForFile(cardContent: string, file: TFile): boolean {
|
||||
// Extract all links from the card
|
||||
const obsidianLinks = this.extractObsidianLinks(cardContent);
|
||||
const markdownLinks = this.extractMarkdownLinks(cardContent);
|
||||
|
||||
const fileName = file.basename;
|
||||
const filePath = file.path;
|
||||
const filePathWithoutExtension = filePath.replace(/\.md$/, "");
|
||||
|
||||
// Check Obsidian links first
|
||||
for (const link of obsidianLinks) {
|
||||
const { path } = link;
|
||||
// Only match if it's an exact match with the full path or filename
|
||||
if (path === fileName || path === filePathWithoutExtension || path === filePath) {
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(`Found exact Obsidian link match in card: ${path} for file: ${fileName}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then check Markdown links
|
||||
for (const link of markdownLinks) {
|
||||
const { url } = link;
|
||||
if (url === filePath || url === filePathWithoutExtension) {
|
||||
if (this.plugin.settings.showDebugInfo) {
|
||||
console.log(`Found exact Markdown link match in card: ${url} for file: ${fileName}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the closest column name match for a given status
|
||||
* This helps when Kanban columns don't exactly match status names
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "progress-tracker",
|
||||
"name": "ProgressTracker",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"minAppVersion": "1.1.0",
|
||||
"description": "Track task completion with a visual progress bar in your sidebar, auto update column Kanban",
|
||||
"author": "VN",
|
||||
|
|
|
|||
Loading…
Reference in a new issue