Integrate Features into an Obsidian Plugin
Context
Objective:
The primary objective is to integrate important functionality for automating content management currently kept in scripts.
Citation Conversion System
Overview
The citation conversion system standardizes citation formats across documents by converting numeric citations to a consistent hexadecimal format and ensuring proper footnote definitions. This system is integrated into the Obsidian plugin to provide real-time citation management.
Goal:
The goal is to have a command that will convert all citations on a particular page (Markdown file) to our desired format.
Considerations:
We will reuse code and patterns that work from the "tidyverse" submodule, and the "observer" system. However, we do not need to use the observer and watcher functionality as this is a simple command that will run on a single file.
We will also not implement the "citations registry" functionality as it is not necessary for this simple command at this time. Step by step.
Implementation Details
Important Considerations
"Pairing" the citation inline and in the footnote
Because the citation hexcode needs to "pair" with the footnote definition, the code needs to alter the same "numeric" or undesired citation inline and in the footnote at the same time. It should not iterate to the next citation without altering the footnote definition. Otherwise, the program will lose track and not know which footnote definition goes with which citation inline.
Citation Formats
Our Desired, Standard Format:
- When cited inline:
[^hexcode]
where hexcode is a 6-character hexadecimal (e.g.,[^1a2b3c]
). Notice the space before the bracket. - When added to Footnotes:
[^hexcode]: ${Citation details}
where hexcode is a 6-character hexadecimal (e.g.,[^1a2b3c]: Citation details
). Notice the colon and then a space after the bracket.
Undesired Formats:
- Numeric Format:
[^123]
(automatically convert to hex with assuring a space beforehand.) - LLM Generated Format:
[1]
(automatically converted to hex with caret) - Footnote Definitions:
[^hexcode]: Citation details
(automatically converted to its "hex pair" with caret and colon and space)
Core Components
- Processing Pipeline
- Extracts and preserves code blocks
- Converts numeric citations to hex format
- Ensures proper spacing around citations
- Validates and creates missing footnote definitions
- Updates the citation registry
- Citation Registry -- IMPORTANT: DO NOT IMPLEMENT NOW.
- Manages all citations across files
- Tracks citation usage and metadata
- Persists to
citation-registry.json
Command Implementation
typescript
// In main.ts
this.addCommand({
id: 'convert-all-citations',
name: 'Convert All Citations to Hex Format',
editorCallback: async (editor: Editor) => {
try {
const content = editor.getValue();
const result = await processCitations(content, this.app.workspace.getActiveFile()?.path || '');
if (result.changed) {
editor.setValue(result.updatedContent);
new Notice(`Updated ${result.stats.citationsConverted} citations`);
} else {
new Notice('No citations needed conversion');
}
} catch (error) {
new Notice('Error processing citations: ' + (error instanceof Error ? error.message : String(error)));
console.error('Error in convert-all-citations:', error);
}
}
});
Error Handling
- Preserves original content on error
- Does not "stop" on a single error, instead continues to the next citation.
- Provides user feedback via Obsidian notices
- Logs detailed errors to console
Usage
- Place cursor in the target document
- Open command palette (Ctrl/Cmd + P)
- Search for "Convert All Citations to Hex Format"
- Command will process the document and show a summary of changes
Future Enhancements
- Batch Processing: Process multiple files at once
- Reformat Footnotes: Parses the LLM generated footnote and rewrites it in our desired format.
- Citation Manager UI: Visual interface for managing citations
- Citation Registry: The Plugin is aware, in realtime, of all citations and can reuse the same unique hex code for the same citation across files and content collections.
- Citation Registry Audience Value: A "site" UI in the site submodule that is our content site can display "articles that use this citation" and have a "citations" page that lists all the articles that use a citation.
Source of Inspiration:
Because we have a loosely coupled monorepo, we should not use modules from one submodule in another. Therefore, we just need to recreate the functionality of the citation alterations in this plugin.
For reference:
citationService.ts
: Core citation processing logiccitation-registry.json
: Central citation database- Obsidian API: For editor integration
Image Uploads to and Image Service
Implementation Details
Not Working Yet so I'm removing the code to get back to work.
File Drop and Paste Handlers
The plugin implements both drag-and-drop and paste functionality for handling image files. When a file is detected, it inserts a temporary placeholder and processes the file asynchronously.
Paste Handler
typescript
private handlePaste(evt: ClipboardEvent, view: EditorView): boolean {
const items = Array.from(evt.clipboardData?.items || []);
const files = items
.filter(item => item.kind === 'file')
.map(item => item.getAsFile())
.filter((file): file is File => file !== null);
if (files.length > 0) {
evt.preventDefault();
const cursorPos = view.state.selection.main.head;
const transaction = view.state.update({
changes: { from: cursorPos, insert: '![Uploading...]()' },
selection: { anchor: cursorPos + 16 }
});
view.dispatch(transaction);
this.processFiles(files, view);
return true;
}
return false;
}
Drop Handler
typescript
private handleDrop(evt: DragEvent, view: EditorView): boolean {
if (evt.dataTransfer?.files.length) {
evt.preventDefault();
const files = Array.from(evt.dataTransfer.files);
const pos = view.posAtCoords({ x: evt.clientX, y: evt.clientY });
if (pos !== null) {
const transaction = view.state.update({
changes: { from: pos, insert: '![Uploading...]()' },
selection: { anchor: pos + 16 }
});
view.dispatch(transaction);
this.processFiles(files, view);
return true;
}
}
return false;
}
File Processing
The
processFiles
method handles the actual file processing and link insertion: typescript
private async processFiles(files: File[], view: EditorView): Promise<void> {
const imageFiles = files.filter(file => file.name.endsWith('.png'));
if (imageFiles.length === 0) return;
for (const file of imageFiles) {
try {
const markdownLink = `![[Visuals/${file.name}]]`;
const doc = view.state.doc.toString();
const placeholderIndex = doc.lastIndexOf('![Uploading...]()');
if (placeholderIndex !== -1) {
view.dispatch({
changes: {
from: placeholderIndex,
to: placeholderIndex + '![Uploading...]()'.length,
insert: markdownLink
},
selection: { anchor: placeholderIndex + markdownLink.length }
});
}
new Notice(`Added image link: ${markdownLink}`);
} catch (error) {
console.error('Error processing file:', error);
new Notice(`Error processing ${file.name}: ${error.message}`);
}
}
}
CodeMirror Integration
The plugin uses CodeMirror's
EditorView
for precise text manipulation. The editor extensions are registered in the plugin's onload
method: typescript
this.registerEditorExtension([
EditorView.domEventHandlers({
paste: (event, view) => this.handlePaste(event, view),
drop: (event, view) => this.handleDrop(event, view)
})
]);
This implementation provides a seamless experience for users to add images to their notes by either pasting from clipboard or dragging and dropping files into the editor. The plugin currently supports PNG files and creates Obsidian-style wiki links in the format
![[Visuals/Filename.png]]
.