'use strict'; var obsidian = require('obsidian'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; const DEFAULT_SETTINGS = { statusPropertyName: 'status', showNotifications: false, debugMode: false // Default to false for better performance }; class KanbanStatusUpdaterPlugin extends obsidian.Plugin { constructor() { super(...arguments); // Track active observers to disconnect them when not needed this.currentObserver = null; this.isProcessing = false; this.activeKanbanBoard = null; } onload() { return __awaiter(this, void 0, void 0, function* () { console.log('Loading Kanban Status Updater plugin'); // Load settings yield this.loadSettings(); // Display startup notification if (this.settings.showNotifications) { new obsidian.Notice('Kanban Status Updater activated'); } this.log('Plugin loaded'); // Register DOM event listener for drag events - but only process if active leaf is Kanban this.registerDomEvent(document, 'dragend', this.onDragEnd.bind(this)); this.log('Registered drag event listener'); // Watch for active leaf changes to only observe the current Kanban board this.registerEvent(this.app.workspace.on('active-leaf-change', this.onActiveLeafChange.bind(this))); // Initial check for active Kanban board this.app.workspace.onLayoutReady(() => { this.checkForActiveKanbanBoard(); }); // Add settings tab this.addSettingTab(new KanbanStatusUpdaterSettingTab(this.app, this)); }); } onunload() { // Disconnect any active observers to prevent memory leaks this.disconnectObservers(); this.log('Plugin unloaded'); } loadSettings() { return __awaiter(this, void 0, void 0, function* () { this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData()); }); } saveSettings() { return __awaiter(this, void 0, void 0, function* () { yield this.saveData(this.settings); }); } // Log helper with debug mode check log(message) { if (this.settings.debugMode) { console.log(`[KSU] ${message}`); } } // Clean up observers when switching away from a Kanban board disconnectObservers() { if (this.currentObserver) { this.log('Disconnecting observer for performance'); this.currentObserver.disconnect(); this.currentObserver = null; } this.activeKanbanBoard = null; } // Check if the active leaf is a Kanban board onActiveLeafChange(leaf) { this.checkForActiveKanbanBoard(); } checkForActiveKanbanBoard() { var _a; // First disconnect any existing observers this.disconnectObservers(); // Get the active leaf using the non-deprecated API const activeLeaf = this.app.workspace.getLeaf(false); if (!activeLeaf) return; try { // Find the content element safely let contentEl = null; // Use type assertions to avoid TypeScript errors if (activeLeaf.view) { // Try to access the contentEl property using type assertion contentEl = activeLeaf.view.contentEl || null; } // If that didn't work, try another approach if (!contentEl) { // Try to get the Kanban board directly from the DOM // Leaf containers have 'view-content' elements that contain the actual view const viewContent = (_a = activeLeaf.containerEl) === null || _a === void 0 ? void 0 : _a.querySelector('.view-content'); if (viewContent) { contentEl = viewContent; } else { // Last resort - look for Kanban boards anywhere in the workspace contentEl = document.querySelector('.workspace-leaf.mod-active .view-content'); } } if (!contentEl) { this.log('Could not access content element for active leaf'); return; } // Check if this is a Kanban board const kanbanBoard = contentEl.querySelector('.kanban-plugin__board'); if (kanbanBoard) { this.log('Found active Kanban board, setting up observer'); // Store reference to active board this.activeKanbanBoard = kanbanBoard; // Set up observer only for this board this.setupObserverForBoard(kanbanBoard); } else { this.log('Active leaf is not a Kanban board'); } } catch (error) { this.log(`Error detecting Kanban board: ${error.message}`); } } setupObserverForBoard(boardElement) { // Create a new observer for this specific board this.currentObserver = new MutationObserver((mutations) => { if (this.isProcessing) return; // Simple debounce to prevent rapid-fire processing this.isProcessing = true; setTimeout(() => { this.handleMutations(mutations); this.isProcessing = false; }, 300); }); // Observe only this board with minimal options needed this.currentObserver.observe(boardElement, { childList: true, subtree: true, attributes: false // Don't need attribute changes for performance }); this.log('Observer set up for active Kanban board'); } handleMutations(mutations) { if (!this.activeKanbanBoard) return; try { const max_mutations = 10; // Only process a sample of mutations for performance const mutationsToProcess = mutations.length > max_mutations ? mutations.slice(0, max_mutations) : mutations; this.log(`Got ${mutationsToProcess.length} mutations of ${mutations.length}`); // Look for Kanban items in mutation let i = 0; for (const mutation of mutationsToProcess) { this.log(`Mutation #${++i} - Type: ${mutation.type}`); if (mutation.type === 'childList') { // Check added nodes for Kanban items for (const node of Array.from(mutation.addedNodes)) { try { // Check if node is any kind of Element (HTML or SVG) if (node instanceof Element) { this.log(`Processing Element of type: ${node.tagName}`); // Handle the node according to its type if (node instanceof HTMLElement || node instanceof HTMLDivElement) { // Direct processing for HTML elements this.log(`Found HTML element of type ${node.className}`); this.processElement(node); } else if (node instanceof SVGElement) { // For SVG elements, look for parent HTML element const parentElement = node.closest('.kanban-plugin__item'); if (parentElement) { this.log('Found Kanban item parent of SVG element'); this.processElement(parentElement); } else { // Look for any kanban items in the document that might have changed // This is for cases where the SVG update is related to a card movement const items = this.activeKanbanBoard.querySelectorAll('.kanban-plugin__item'); if (items.length > 0) { // Process only the most recently modified item const recentItems = Array.from(items).slice(-1); for (const item of recentItems) { this.log('Processing recent item after SVG change'); this.processElement(item); } } } } } else if (node.nodeType === Node.TEXT_NODE) { // For text nodes, check the parent element const parentElement = node.parentElement; if (parentElement && (parentElement.classList.contains('kanban-plugin__item-title') || parentElement.closest('.kanban-plugin__item'))) { this.log('Found text change in Kanban item'); const itemElement = parentElement.closest('.kanban-plugin__item'); if (itemElement) { this.processElement(itemElement); } } } else { this.log(`Skipping node type: ${node.nodeType}`); } } catch (nodeError) { this.log(`Error processing node: ${nodeError.message}`); // Continue with next node even if this one fails } } } else { this.log('Ignoring mutation type: ' + mutation.type); } } } catch (error) { this.log(`Error in handleMutations: ${error.message}`); } } onDragEnd(event) { // Only process if we have an active Kanban board if (!this.activeKanbanBoard || this.isProcessing) { this.log('Drag end detected but no active Kanban board or already processing'); this.log('activeKanbanBoard: ' + (this.activeKanbanBoard ? 'Yes' : 'No')); this.log('isProcessing: ' + (this.isProcessing ? 'Yes' : 'No')); return; } try { this.log('Drag end detected'); // Set processing flag to prevent multiple processing this.isProcessing = true; const target = event.target; if (!target) return; this.processElement(target); } catch (error) { this.log(`Error in onDragEnd: ${error.message}`); } finally { // Reset processing flag after a delay to debounce setTimeout(() => { this.isProcessing = false; }, 300); } } processElement(element) { try { // Only process if inside our active Kanban board if (!this.activeKanbanBoard || !element.closest('.kanban-plugin__board')) { this.log('Element NOT in active Kanban board. Skipping.'); return; } // Use different strategies to find the Kanban item this.log("👀 Looking for Kanban item element"); // Check if element is a Kanban item or contains one const kanbanItem = element.classList.contains('kanban-plugin__item') ? element : element.querySelector('.kanban-plugin__item'); if (kanbanItem) { this.log(`✅ Found Kanban item: ${kanbanItem}`); this.log('classList of kanbanItem: ' + kanbanItem.classList); this.processKanbanItem(kanbanItem); return; } this.log('Not a Kanban item, checking for parent'); // If element is inside a Kanban item, find the parent const parentItem = element.closest('.kanban-plugin__item'); this.log(`Parent item: ${parentItem ? parentItem : 'Not found'}`); if (parentItem) { this.processKanbanItem(parentItem); return; } } catch (error) { this.log(`Error in processElement: ${error.message}`); } } processKanbanItem(itemElement) { try { // TODO: Select the title const internalLink = itemElement.querySelector('.kanban-plugin__item-title .kanban-plugin__item-markdown a.internal-link'); if (!internalLink) { this.log('🚫 No internal link found in item'); return; } this.log(`Found internal link: ${internalLink.textContent}`); // Get the link path from data-href or href attribute const linkPath = internalLink.getAttribute('data-href') || internalLink.getAttribute('href'); if (!linkPath) return; this.log(`🔗 Link path: ${linkPath}`); // Find the lane (column) this item is in const lane = itemElement.closest('.kanban-plugin__lane'); if (!lane) { this.log('🚫 No lane found for item'); return; } // Get column name from the lane header const laneHeader = lane.querySelector('.kanban-plugin__lane-header-wrapper .kanban-plugin__lane-title'); if (!laneHeader) { this.log('🚫 No laneHeader found for item'); return; } const columnName = laneHeader.textContent.trim(); this.log(`✅ Got lane name: ${columnName}`); this.log(`Processing card with link to "${linkPath}" in column "${columnName}"`); // Update the linked note's status this.updateNoteStatus(linkPath, columnName); } catch (error) { this.log(`Error in processKanbanItem: ${error.message}`); } } updateNoteStatus(notePath, status) { return __awaiter(this, void 0, void 0, function* () { try { // Find the linked file const file = this.app.metadataCache.getFirstLinkpathDest(notePath, ''); if (!file) { if (this.settings.showNotifications) { new obsidian.Notice(`⚠️ Note "${notePath}" not found`, 3000); } return; } // Get current status if it exists const metadata = this.app.metadataCache.getFileCache(file); let oldStatus = null; if ((metadata === null || metadata === void 0 ? void 0 : metadata.frontmatter) && metadata.frontmatter[this.settings.statusPropertyName]) { oldStatus = metadata.frontmatter[this.settings.statusPropertyName]; } // Only update if status has changed if (oldStatus !== status) { // Use the processFrontMatter API to update the frontmatter yield this.app.fileManager.processFrontMatter(file, (frontmatter) => { // Set the status property frontmatter[this.settings.statusPropertyName] = status; }); // Show notification if enabled if (this.settings.showNotifications) { if (oldStatus) { new obsidian.Notice(`Updated ${this.settings.statusPropertyName}: "${oldStatus}" → "${status}" for ${file.basename}`, 3000); } else { new obsidian.Notice(`Set ${this.settings.statusPropertyName}: "${status}" for ${file.basename}`, 3000); } } this.log(`Updated status for ${file.basename} to "${status}"`); } else { this.log(`Status already set to "${status}" for ${file.basename}, skipping update`); } } catch (error) { this.log(`Error updating note status: ${error.message}`); if (this.settings.showNotifications) { new obsidian.Notice(`⚠️ Error updating status: ${error.message}`, 3000); } } }); } // Method for the test button to use runTest() { this.log('Running test...'); // Make sure we're using the current active board this.checkForActiveKanbanBoard(); if (!this.activeKanbanBoard) { new obsidian.Notice('⚠️ No active Kanban board found - open a Kanban board first', 5000); return; } // Find items in the active board const items = this.activeKanbanBoard.querySelectorAll('.kanban-plugin__item'); const count = items.length; new obsidian.Notice(`Found ${count} cards in active Kanban board`, 3000); if (count > 0) { // Process the first item with a link for (let i = 0; i < count; i++) { const item = items[i]; if (item.querySelector('a.internal-link')) { new obsidian.Notice(`Testing with card: "${item.textContent.substring(0, 20)}..."`, 3000); this.processKanbanItem(item); break; } } } } } class KanbanStatusUpdaterSettingTab extends obsidian.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); new obsidian.Setting(containerEl) .setName('Status property name') .setDesc('The name of the property to update when a card is moved') .addText(text => text .setPlaceholder('status') .setValue(this.plugin.settings.statusPropertyName) .onChange((value) => __awaiter(this, void 0, void 0, function* () { this.plugin.settings.statusPropertyName = value; yield this.plugin.saveSettings(); }))); new obsidian.Setting(containerEl) .setName('Show notifications') .setDesc('Show a notification when a status is updated') .addToggle(toggle => toggle .setValue(this.plugin.settings.showNotifications) .onChange((value) => __awaiter(this, void 0, void 0, function* () { this.plugin.settings.showNotifications = value; yield this.plugin.saveSettings(); }))); new obsidian.Setting(containerEl) .setName('Debug mode') .setDesc('Enable detailed logging (reduces performance)') .addToggle(toggle => toggle .setValue(this.plugin.settings.debugMode) .onChange((value) => __awaiter(this, void 0, void 0, function* () { this.plugin.settings.debugMode = value; yield this.plugin.saveSettings(); if (value) { new obsidian.Notice('Debug mode enabled - check console for logs', 3000); } else { new obsidian.Notice('Debug mode disabled', 3000); } }))); // Add a test button new obsidian.Setting(containerEl) .setName('Test plugin') .setDesc('Test with current Kanban board') .addButton(button => button .setButtonText('Run Test') .onClick(() => { this.plugin.runTest(); })); } } module.exports = KanbanStatusUpdaterPlugin; /* nosourcemap */