diff --git a/src/modals/CitationModal.ts b/src/modals/CitationModal.ts index 8a54429..0752c71 100644 --- a/src/modals/CitationModal.ts +++ b/src/modals/CitationModal.ts @@ -175,55 +175,55 @@ export class CitationModal extends Modal { private async convertCitationToHex(match: CitationMatch) { try { + console.log('Starting citation conversion for:', match); + const content = this.editor.getValue(); - const lines = content.split('\n'); - let updated = false; - // Generate hex ID for this citation - const hexId = citationService.generateHexId(parseInt(match.number)); + // Use the citation service to handle the conversion + const result = citationService.convertCitations( + content, + match.type === 'footnote' ? `[^${match.number}]` : `[${match.number}]` + ); - // Replace all instances of this citation - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (!line) continue; + if (result.changed) { + console.log('Changes detected, updating file...'); - // Handle footnote references [^1] - if (match.type === 'footnote') { - const footnoteRef = new RegExp(`\\[\\^${match.number}\\]`, 'g'); - if (footnoteRef.test(line)) { - lines[i] = line.replace(footnoteRef, `[^${hexId}]`); - updated = true; - } - - // Also check for footnote definitions - const footnoteDef = new RegExp(`^\\[\\^${match.number}\\]:`); - if (footnoteDef.test(line.trim())) { - lines[i] = line.replace(`[^${match.number}]:`, `[^${hexId}]:`); - updated = true; - } - } - // Handle regular citations [1] - else { - const citationRegex = new RegExp(`\\[${match.number}\\]`, 'g'); - if (citationRegex.test(line)) { - // Skip if it's part of a markdown link - if (!/\]\([^)]*$/.test(line.substring(0, line.indexOf(`[${match.number}]`)))) { - lines[i] = line.replace(citationRegex, `[${hexId}]`); - updated = true; - } - } + // Get the current file + const activeFile = this.app.workspace.getActiveFile(); + if (!activeFile) { + const msg = 'No active file found'; + console.error(msg); + new Notice(msg); + return; } - } - - if (updated) { - // Update the editor content - this.editor.setValue(lines.join('\n')); - new Notice(`Converted citation [${match.number}] to hex format`); + + console.log('New content prepared, saving to file...'); + + try { + await this.app.vault.modify(activeFile, result.updatedContent); + console.log('File saved successfully'); + + // Update the editor content to reflect the changes + this.editor.setValue(result.updatedContent); + new Notice(`✅ Successfully converted citation [${match.number}]`); + } catch (error) { + const saveError = error as Error; + const errorMsg = `Failed to save file: ${saveError.message || 'Unknown error'}`; + console.error('Save error:', saveError); + new Notice(`❌ ${errorMsg}`); + } + } else { + console.log('No changes were made to the file'); + new Notice(`No instances of [${match.number}] found to convert`); } } catch (error) { - console.error('Error converting citation to hex:', error); - new Notice('Error converting citation to hex. Check console for details.'); + const conversionError = error as Error; + const errorMsg = `Error converting citation: ${conversionError.message || 'Unknown error'}`; + console.error('Conversion error:', conversionError); + new Notice(`❌ ${errorMsg}`); + } finally { + console.log('Citation conversion completed'); } } diff --git a/src/services/citationService.ts b/src/services/citationService.ts index 5cc4ed1..3311204 100644 --- a/src/services/citationService.ts +++ b/src/services/citationService.ts @@ -21,64 +21,86 @@ export class CitationService { } /** - * Convert all citations to random hex format + * Convert citations to random hex format * @param content - The markdown content to process + * @param targetCitation - Optional specific citation to convert (e.g., '[1]' or '[^1]') * @returns Object with updated content and statistics */ - public convertCitations(content: string): CitationConversionResult { + public convertCitations(content: string, targetCitation?: string): CitationConversionResult { + if (!content) { + console.error('Content is empty or undefined'); + return { updatedContent: content || '', changed: false, stats: { citationsConverted: 0 } }; + } + let updatedContent = content; let citationsConverted = 0; - const citationMap = new Map(); // Maps original ID to hex ID - // First pass: collect all citations and generate hex IDs - const collectCitations = (match: string, _prefix: string, id: string) => { - // Only process if it's a numeric ID and we haven't seen it before - if (/^\d+$/.test(id) && !citationMap.has(id)) { - citationMap.set(id, this.generateHexId()); - } - return match; // Don't modify yet - }; + // If we have a target citation, we'll handle it specifically + if (targetCitation) { + console.warn('Targeted citation conversion not yet implemented for URL-based citations'); + return { updatedContent, changed: false, stats: { citationsConverted: 0 } }; + } - // Collect numeric citations [^1] - updatedContent = updatedContent.replace(/\[\^(\d+)\]/g, collectCitations); - - // Collect plain numeric citations [1] (only if not part of a link) - updatedContent = updatedContent.replace(/\[(\d+)\]/g, (match, id, offset) => { - // Only collect if it's a single digit and not part of a link - if (/^\d+$/.test(id) && !/\]\([^)]*$/.test(updatedContent.substring(0, offset))) { - return collectCitations(match, '', id); - } - return match; - }); - - // Second pass: replace all collected citations with their hex equivalents - citationMap.forEach((hexId, originalId) => { - try { - // Replace [^1] style citations - updatedContent = updatedContent.replace( - new RegExp(`\\[\\^${originalId}\\]`, 'g'), - `[^${hexId}]` - ); + try { + // Map to store URL to hex ID mappings + const urlToHexMap = new Map(); + const hexToUrlMap = new Map(); + + // First pass: Find all [number](url) patterns and replace with [^hex] + updatedContent = updatedContent.replace(/\[(\d+)\]\(([^)]+)\)/g, (match, number, url) => { + let hexId: string; - // Replace [1] style citations (only if not part of a link) - updatedContent = updatedContent.replace( - new RegExp(`(^|[^\\])\\[${originalId}\\]`, 'g'), - `$1[^${hexId}]` - ); + // If we've seen this URL before, use the existing hex ID + if (urlToHexMap.has(url)) { + hexId = urlToHexMap.get(url)!; + } else { + // Generate a new hex ID for this URL + hexId = this.generateHexId(); + urlToHexMap.set(url, hexId); + hexToUrlMap.set(hexId, url); + } citationsConverted++; - } catch (error) { - console.error(`Error processing citation ${originalId}:`, error); + return ` [^${hexId}]`; // Add space before citation for better formatting + }); + + // If we found any citations, add the footnotes section + if (citationsConverted > 0) { + // Check if footnotes section already exists + const hasFootnotesSection = /\n#+\s*Footnotes\s*\n/.test(updatedContent); + + // If no footnotes section exists, add one + if (!hasFootnotesSection) { + updatedContent += '\n\n# Footnotes\n'; + } else { + updatedContent += '\n'; + } + + // Add all footnote definitions + hexToUrlMap.forEach((url, hexId) => { + // Check if this footnote is already defined + const footnoteRegex = new RegExp(`\\[\\^${hexId}\\]:.*`, 'g'); + if (!footnoteRegex.test(updatedContent)) { + updatedContent += `\n[^${hexId}]: ${url}`; + } + }); } - }); - return { - updatedContent, - changed: citationsConverted > 0, - stats: { - citationsConverted - } - }; + return { + updatedContent, + changed: citationsConverted > 0, + stats: { + citationsConverted + } + }; + } catch (error) { + console.error('Error processing citations:', error); + return { + updatedContent: content, // Return original content on error + changed: false, + stats: { citationsConverted: 0 } + }; + } } }