lossless-group_perplexed-pl.../src/modals/CitationModal.ts
mpstaton 6d616c9a63 improve(modal): isolate final reference for conversion to hex
On branch development
 Changes to be committed:
	modified:   src/modals/CitationModal.ts
	modified:   src/services/citationService.ts
	modified:   src/styles/citations.css
2025-07-08 03:29:52 +03:00

274 lines
No EOL
9.5 KiB
TypeScript

// cite-wide/src/modals/CitationModal.ts
import { App, Modal, Notice, Editor } from 'obsidian';
import { citationService, type CitationGroup } from '../services/citationService';
export class CitationModal extends Modal {
private editor: Editor;
private content: string;
private citationGroups: CitationGroup[] = [];
constructor(app: App, editor: Editor) {
super(app);
this.editor = editor;
this.content = editor.getValue();
}
async onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass('cite-wide-modal');
// Find all citation groups
this.citationGroups = citationService.findCitations(this.content);
if (this.citationGroups.length === 0) {
contentEl.createEl('p', {
text: 'No citations found in the current document.'
});
return;
}
// Create a container for citation groups
const container = contentEl.createDiv('cite-wide-container');
// Add a title
container.createEl('h2', {
text: 'Citations in Document',
cls: 'cite-wide-title'
});
// Add each citation group
for (const group of this.citationGroups) {
this.renderCitationGroup(container, group);
}
// Add convert all button
const footer = contentEl.createDiv('cite-wide-footer');
const convertAllBtn = footer.createEl('button', {
text: 'Convert All to Hex',
cls: 'mod-cta'
});
convertAllBtn.addEventListener('click', () => this.convertAllCitations());
}
private renderCitationGroup(container: HTMLElement, group: CitationGroup) {
const groupEl = container.createDiv('cite-wide-group');
const header = groupEl.createDiv('cite-wide-group-header');
// Create a collapsible header
const headerContent = header.createDiv('cite-wide-group-header-content');
headerContent.createEl('h3', {
text: `Citation [${group.number}] (${group.matches.length} instances)`,
cls: 'cite-wide-group-title'
});
if (group.url) {
headerContent.createEl('a', {
href: group.url,
text: 'Source',
cls: 'cite-wide-source-link',
attr: { target: '_blank' }
});
}
// Add convert button
const convertBtn = header.createEl('button', {
text: 'Convert to Hex',
cls: 'mod-cta cite-wide-convert-btn'
});
convertBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.convertCitationGroup(group);
});
// Create collapsible content
const content = groupEl.createDiv('cite-wide-group-content');
content.style.display = 'none'; // Start collapsed
// Toggle content on header click
header.addEventListener('click', () => {
content.style.display = content.style.display === 'none' ? 'block' : 'none';
});
// Add each citation instance
group.matches.forEach((match, matchIndex) => {
const isRefSource = match.isReferenceSource === true;
const instanceEl = content.createDiv(`cite-wide-instance ${isRefSource ? 'cite-wide-reference-source' : ''}`);
// Show line number and preview
const lineInfo = instanceEl.createDiv('cite-wide-line-info');
// Add a special badge for reference sources
if (isRefSource) {
lineInfo.createEl('span', {
text: 'Reference',
cls: 'cite-wide-badge cite-wide-badge-reference'
});
lineInfo.createEl('span', { text: ' • ' });
}
lineInfo.createEl('span', {
text: `Line ${match.lineNumber}: `,
cls: 'cite-wide-line-number'
});
// Create a preview of the line content
const preview = match.lineContent.trim();
const previewText = preview.length > 100
? `${preview.substring(0, 100)}...`
: preview;
lineInfo.createEl('span', {
text: previewText,
cls: 'cite-wide-line-preview'
});
// Add view and convert buttons
const btnContainer = instanceEl.createDiv('cite-wide-instance-actions');
// Only add view/convert buttons for non-reference entries
if (!isRefSource) {
const viewBtn = btnContainer.createEl('button', {
text: 'View',
cls: 'mod-cta-outline cite-wide-view-btn'
});
viewBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.scrollToLine(match.lineNumber);
});
}
// Only add convert button for non-reference entries
if (!isRefSource) {
const convertBtn = btnContainer.createEl('button', {
text: 'Convert',
cls: 'mod-cta cite-wide-convert-btn'
});
convertBtn.addEventListener('click', async (e) => {
e.stopPropagation();
await this.convertCitationInstance(group, matchIndex);
});
}
});
}
private async convertCitationGroup(group: CitationGroup) {
try {
const result = citationService.convertCitation(
this.content,
group.number
);
if (result.changed) {
await this.saveChanges(result.content);
new Notice(`Converted citation [${group.number}] to hex format`);
this.close();
} else {
new Notice('No changes were made to the document');
}
} catch (error) {
console.error('Error converting citation:', error);
new Notice('Error converting citation. See console for details.');
}
}
private async convertCitationInstance(group: CitationGroup, matchIndex: number) {
try {
const match = group.matches[matchIndex];
if (!match) return;
// Get current cursor position
const cursor = this.editor.getCursor();
// Convert just this specific instance
const result = citationService.convertCitation(
this.content,
group.number,
undefined, // Let it generate a new hex ID
matchIndex // Convert only this specific instance
);
if (result.changed) {
// Update the content
await this.saveChanges(result.content);
// Calculate new cursor position
const offsetChange = result.content.length - this.content.length;
const newCursor = {
line: cursor.line,
ch: cursor.ch + (match.index <= cursor.ch ? offsetChange : 0)
};
// Update content and restore cursor
this.content = result.content;
this.editor.setCursor(newCursor);
new Notice(`Converted citation [${group.number}] to hex format`);
this.close();
} else {
new Notice('No changes were made to the document');
}
} catch (error) {
console.error('Error converting citation instance:', error);
new Notice('Error converting citation. See console for details.');
}
}
private async convertAllCitations() {
try {
let updatedContent = this.content;
let totalConverted = 0;
// Process each group
for (const group of this.citationGroups) {
const result = citationService.convertCitation(
updatedContent,
group.number
);
if (result.changed) {
updatedContent = result.content;
totalConverted += result.stats.citationsConverted;
}
}
if (totalConverted > 0) {
await this.saveChanges(updatedContent);
new Notice(`Converted ${totalConverted} citations to hex format`);
this.close();
} else {
new Notice('No citations were converted');
}
} catch (error) {
console.error('Error converting citations:', error);
new Notice('Error converting citations. See console for details.');
}
}
private async saveChanges(newContent: string) {
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) {
throw new Error('No active file');
}
await this.app.vault.modify(activeFile, newContent);
}
private scrollToLine(lineNumber: number) {
this.editor.setCursor({ line: lineNumber, ch: 0 });
this.editor.scrollIntoView({
from: { line: Math.max(0, lineNumber - 2), ch: 0 },
to: { line: lineNumber + 2, ch: 0 }
}, true);
this.close();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}