Skip to content

Instantly share code, notes, and snippets.

@Quorafind
Last active October 14, 2024 08:01
Show Gist options
  • Save Quorafind/16202a6b07319a63846e7e534e64d8b2 to your computer and use it in GitHub Desktop.
Save Quorafind/16202a6b07319a63846e7e534e64d8b2 to your computer and use it in GitHub Desktop.
Obsidian Jump to/Hover Specific Content
// Interface defining the options for showing content
interface ShowContentOptions {
start: number; // Starting position of the content
end: number; // Ending position of the content
content: string; // The full content string
view: MyView; // The view object
file: TFile; // The file object
plugin: MyPlugin; // The plugin instance
}
// Debounced function to show content on hover
const showContent = debounce((event: MouseEvent, options: ShowContentOptions) => {
const { view, file, plugin, start, content } = options;
// Create a state object with the calculated scroll position
let state = {
scroll: calculateLineNumber(content, start),
};
// Trigger the hover-link event with the necessary information
plugin.app.workspace.trigger('hover-link', {
event,
source: 'my-source',
hoverParent: view.containerEl,
targetEl: event.target as HTMLElement,
linktext: file.path,
sourcePath: file.path,
state,
});
}, 100); // Debounce delay of 100ms
// Function to calculate the line number based on a position in the content
const calculateLineNumber = (content: string, position: number): number => {
let lineNumber = 0;
let index = 0;
// Iterate through the content, counting newlines until we reach the position
while (index < position && index !== -1) {
index = content.indexOf('\n', index);
if (index !== -1 && index < position) {
index++;
lineNumber++;
}
}
return lineNumber;
};
import { TFile, WorkspaceLeaf, MarkdownView, App } from 'obsidian';
// Define custom types for different navigation scenarios
type NavigationType = 'image' | 'table' | 'normal';
// Determine the current mode of the view
export function getMode(viewState: ViewState) {
if (viewState.state.mode === 'source' && !viewState.state.source) return 'live-preview';
if (viewState.state.mode === 'source' && viewState.state.source) return 'source';
return 'preview';
}
export function navigateTo({
pos,
plugin,
file,
content,
leaf,
type,
offset = 0,
}: {
pos: { start: number; end: number };
offset: number;
plugin: MyPlugin;
file: TFile;
content: string;
leaf: WorkspaceLeaf;
type: NavigationType;
}) {
const { start, end } = pos;
// Check if the target leaf still exists
const ifLeafExisted = plugin.app.workspace.getLeafById(leaf.id);
if (!ifLeafExisted || file.path !== (ifLeafExisted.view as MarkdownView).file.path) return;
const view = ifLeafExisted.view as MarkdownView;
const editor = view.editor;
// Convert character offsets to editor positions, accounting for the offset
const posStart = editor.offsetToPos(start + offset);
const posEnd = editor.offsetToPos(end + offset);
// Set the selection range in the editor
editor?.setSelection(posStart, posEnd);
const state = ifLeafExisted.getViewState();
const mode = getMode(state);
// Extract the matched content, accounting for the offset
const matchedContent = content.slice(start + offset, end + offset);
// Split the matched content into words and calculate their positions
const splitMatches = matchedContent.split(/\s+/).reduce(
(acc, word, _, array) => {
const wordStart = matchedContent.indexOf(word, acc.lastIndex);
if (wordStart !== -1) {
acc.matches.push([start + offset + wordStart, start + offset + wordStart + word.length]);
acc.lastIndex = wordStart + word.length;
}
return acc;
},
{ matches: [] as number[][], lastIndex: 0 },
).matches;
// Create a match object with the content and word positions
const matchObject = {
content: content,
matches: splitMatches,
};
// Perform different navigation actions based on current mode and navigation type
if (mode === 'preview' || mode === 'source') {
ifLeafExisted.setViewState(state, { match: matchObject });
} else {
switch (type) {
case 'image':
ifLeafExisted.openFile(file, { eState: { match: matchObject } });
break;
case 'table':
ifLeafExisted.setViewState(state, { line: posStart.line - 1 });
break;
default:
ifLeafExisted.setViewState(state, { match: matchObject });
}
}
}
import { Plugin, WorkspaceLeaf } from 'obsidian';
export default class MyPlugin extends Plugin {
async onload(): Promise<void> {
// You need to register hover link source first.
this.registerHoverLinkSource('my-plugin', {
display: 'My plugin',
defaultMod: true,
});
}
onunload() {
super.onunload();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment