diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd275a4..b3184a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [Unreleased]
+
+### Added
+
+- **Link hover preview**: Hovering over a link in a rendered document now shows the target URL in the status bar
+
## [1.1.0] - 2025-01-21
### Added
diff --git a/index.html b/index.html
index 9bf9407..1a78fbc 100644
--- a/index.html
+++ b/index.html
@@ -132,6 +132,7 @@
Drop Markdown File Here
No file
+
100%
diff --git a/src/index.css b/src/index.css
index 2807997..1200c33 100644
--- a/src/index.css
+++ b/src/index.css
@@ -484,6 +484,18 @@ body {
opacity: 1;
}
+.status-link {
+ color: var(--link-color);
+ max-width: 60ch;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.status-link:empty {
+ display: none;
+}
+
.status-zoom {
font-variant-numeric: tabular-nums;
min-width: 45px;
diff --git a/src/renderer.ts b/src/renderer.ts
index 56c17e3..892ff3e 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -177,6 +177,8 @@ class App {
this.findService = new FindService(viewerContainer);
+ this.setupLinkHover(viewerContainer);
+
this.findBar = createFindBar(viewerElement, {
onFind: (text, { matchCase }) => {
const result = this.findService!.find(text, { matchCase });
@@ -286,6 +288,28 @@ class App {
});
}
+ /**
+ * Show the target URL in the status bar while hovering over a link
+ */
+ private setupLinkHover(container: HTMLElement): void {
+ const updateFromTarget = (target: EventTarget | null): void => {
+ const anchor =
+ target instanceof Element ? target.closest('a[href]') : null;
+ const href = anchor?.getAttribute('href') ?? null;
+ this.statusBar?.setLinkUrl(href);
+ };
+
+ container.addEventListener('mouseover', (e) => {
+ updateFromTarget(e.target);
+ });
+ container.addEventListener('mouseout', (e) => {
+ updateFromTarget(e.relatedTarget);
+ });
+ container.addEventListener('mouseleave', () => {
+ this.statusBar?.setLinkUrl(null);
+ });
+ }
+
/**
* Initialize theme from preferences
*/
diff --git a/src/renderer/components/StatusBar.ts b/src/renderer/components/StatusBar.ts
index 7b6cf0d..6b64090 100644
--- a/src/renderer/components/StatusBar.ts
+++ b/src/renderer/components/StatusBar.ts
@@ -10,6 +10,7 @@ export interface StatusBarState {
modifiedTime: Date | null;
isWatching: boolean;
zoomLevel: number;
+ linkUrl: string | null;
}
/**
@@ -22,11 +23,13 @@ export class StatusBar {
private watchElement: HTMLElement | null = null;
private watchTextElement: HTMLElement | null = null;
private zoomElement: HTMLElement | null = null;
+ private linkElement: HTMLElement | null = null;
private state: StatusBarState = {
filePath: null,
modifiedTime: null,
isWatching: false,
zoomLevel: 1.0,
+ linkUrl: null,
};
constructor(element: HTMLElement) {
@@ -43,6 +46,19 @@ export class StatusBar {
this.watchElement = this.element.querySelector('#status-watch');
this.watchTextElement = this.element.querySelector('#status-watch-text');
this.zoomElement = this.element.querySelector('#status-zoom');
+ this.linkElement = this.element.querySelector('#status-link');
+ }
+
+ /**
+ * Update the hovered link URL display
+ */
+ setLinkUrl(linkUrl: string | null): void {
+ this.state.linkUrl = linkUrl;
+
+ if (this.linkElement) {
+ this.linkElement.textContent = linkUrl ?? '';
+ this.linkElement.title = linkUrl ?? '';
+ }
}
/**
@@ -152,6 +168,7 @@ export class StatusBar {
this.setModifiedTime(null);
this.setWatching(false);
this.setZoomLevel(1.0);
+ this.setLinkUrl(null);
}
/**