mirror of
https://github.com/lossless-group/perplexed-plugin.git
synced 2026-07-22 06:49:50 +00:00
On branch development Changes to be committed: modified: src/modals/CitationModal.ts modified: src/services/citationService.ts modified: src/styles/citations.css
274 lines
No EOL
9.5 KiB
TypeScript
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();
|
|
}
|
|
} |