Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
04c755d
fix(core): improve mermaid cache race handling and renderer robustness
jamesainslie Mar 20, 2026
5fd8135
chore: add workspace structure and @mdview/core package skeleton
jamesainslie Mar 20, 2026
e299cde
chore: configure vitest workspace for monorepo
jamesainslie Mar 20, 2026
31fe07e
refactor(core): move type definitions to @mdview/core
jamesainslie Mar 20, 2026
995c97f
refactor(core): move theme data modules to @mdview/core
jamesainslie Mar 20, 2026
a96ec94
refactor(core): move pure utility modules to @mdview/core
jamesainslie Mar 20, 2026
8c6a3c9
refactor(core): move markdown-converter, cache-manager, frontmatter t…
jamesainslie Mar 20, 2026
f2db9ab
refactor(core): move comment parsers and serializers to @mdview/core
jamesainslie Mar 20, 2026
6b2e88b
refactor(core): add barrel export for @mdview/core
jamesainslie Mar 20, 2026
994190f
feat(core): define platform adapter interfaces
jamesainslie Mar 20, 2026
76eb366
refactor(core): extract file-scanner with MessagingAdapter
jamesainslie Mar 20, 2026
055c462
refactor(core): extract file-scanner with MessagingAdapter
jamesainslie Mar 20, 2026
4610406
refactor(core): add FileScanner to barrel export
jamesainslie Mar 20, 2026
34d7c8e
refactor(core): extract theme-engine with StorageAdapter
jamesainslie Mar 20, 2026
df11f56
refactor(core): extract render-pipeline with MessagingAdapter
jamesainslie Mar 20, 2026
0d2287e
refactor(core): extract debug-logger with StorageAdapter
jamesainslie Mar 20, 2026
b05200f
refactor(core): extract comment-manager with FileAdapter and Identity…
jamesainslie Mar 20, 2026
341982f
refactor(core): move DOM utilities to @mdview/core
jamesainslie Mar 20, 2026
58ff331
refactor(core): move comment UI modules to @mdview/core
jamesainslie Mar 20, 2026
d07db38
refactor(core): move export modules to @mdview/core
jamesainslie Mar 20, 2026
5b9d5e7
refactor(core): move worker modules to @mdview/core
jamesainslie Mar 20, 2026
f3b48c6
refactor(core): move renderers to @mdview/core
jamesainslie Mar 20, 2026
306ca21
refactor(core): add barrel exports for TOC renderer, lazy section ren…
jamesainslie Mar 20, 2026
f0253d0
refactor: move Chrome extension to packages/chrome-ext
jamesainslie Mar 20, 2026
235293b
fix(tests): repair vi.mock factories for Node built-ins and class con…
jamesainslie Mar 20, 2026
b9678ee
fix(chrome-ext): add build and dev scripts to package.json
jamesainslie Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 139 additions & 163 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 13 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,30 @@
"version": "0.3.4",
"description": "Beautiful Chrome extension for viewing Markdown files with themes, syntax highlighting, and interactive Mermaid diagrams",
"type": "module",
"workspaces": [
"packages/*"
],
"scripts": {
"dev": "vite build --watch",
"build": "node scripts/update-manifest-version.js && tsc && vite build && node scripts/fix-manifest.js",
"dev": "npm -w @mdview/chrome-ext run dev",
"build": "npm -w @mdview/chrome-ext run build",
"build:ext": "npm -w @mdview/chrome-ext run build",
"export:docx": "tsx scripts/export-docx-mermaid-cli.ts",
"preview": "vite preview",
"test": "vitest",
"test:ci": "vitest --run",
"test:e2e": "playwright test",
"lint": "eslint src --ext ts,tsx",
"format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
"lint": "eslint packages --ext ts,tsx",
"format": "prettier --write \"packages/*/src/**/*.{ts,tsx,css}\"",
"prepare": "husky"
},
"dependencies": {
"@jamesainslie/docx": "^9.5.1-svg.1",
"dompurify": "^3.0.6",
"highlight.js": "^11.9.0",
"markdown-it-anchor": "^8.6.7",
"markdown-it-attrs": "^4.1.6",
"markdown-it-emoji": "^3.0.0",
"markdown-it-footnote": "^4.0.0",
"markdown-it-task-lists": "^2.1.1",
"panzoom": "^9.4.3"
"markdown-it-task-lists": "^2.1.1"
},
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0",
Expand All @@ -41,7 +43,6 @@
"jsdom": "^27.2.0",
"lint-staged": "^16.2.7",
"markdown-it": "^14.1.0",
"mermaid": "^11.12.1",
"playwright": "^1.48.0",
"prettier": "^3.1.0",
"sharp": "^0.34.5",
Expand All @@ -65,9 +66,12 @@
"author": "James Ainslie",
"license": "MIT",
"lint-staged": {
"{src,tests}/**/*.{ts,tsx}": [
"packages/*/src/**/*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"tests/**/*.{ts,tsx}": [
"prettier --write"
]
}
}
17 changes: 17 additions & 0 deletions packages/chrome-ext/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@mdview/chrome-ext",
"version": "0.3.4",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"@mdview/core": "*"
},
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0-beta.28",
"@types/chrome": "^0.0.287"
}
}
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Handles state management, message passing, coordination, and cache management
*/

import type { AppState, ThemeName } from '../types';
import { CacheManager } from '../core/cache-manager';
import type { AppState, ThemeName, CachedResult } from '@mdview/core';
import { CacheManager } from '@mdview/core';
import { debug } from '../utils/debug-logger';

// Default state
Expand Down Expand Up @@ -151,12 +151,14 @@ function setupContextMenu(): void {
// Handle context menu clicks
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'mdview-add-comment' && tab?.id) {
void chrome.tabs.sendMessage(tab.id, {
type: 'ADD_COMMENT',
payload: { selectionText: info.selectionText },
}).catch(() => {
// Tab may not have content script
});
void chrome.tabs
.sendMessage(tab.id, {
type: 'ADD_COMMENT',
payload: { selectionText: info.selectionText },
})
.catch(() => {
// Tab may not have content script
});
}
});

Expand Down Expand Up @@ -299,7 +301,7 @@ chrome.runtime.onMessage.addListener(
case 'CACHE_SET': {
const payload = message.payload as {
key: string;
result: import('../types').CachedResult;
result: CachedResult;
filePath: string;
contentHash: string;
theme: ThemeName;
Expand Down Expand Up @@ -367,7 +369,7 @@ chrome.runtime.onMessage.addListener(

case 'GET_USERNAME': {
try {
const result = await chrome.runtime.sendNativeMessage(
const result: unknown = await chrome.runtime.sendNativeMessage(
'com.mdview.filewriter',
{ action: 'get_username' }
);
Expand All @@ -382,7 +384,7 @@ chrome.runtime.onMessage.addListener(
case 'WRITE_FILE': {
const payload = message.payload as { path: string; content: string };
try {
const result = await chrome.runtime.sendNativeMessage(
const result: unknown = await chrome.runtime.sendNativeMessage(
'com.mdview.filewriter',
{
action: 'write',
Expand Down
92 changes: 92 additions & 0 deletions packages/chrome-ext/src/comments/comment-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Comment Manager — Chrome extension shim
*
* Re-exports the core CommentManager from @mdview/core, pre-configured
* with Chrome extension adapters that delegate to chrome.runtime.sendMessage
* for file writes (WRITE_FILE) and username lookup (GET_USERNAME).
*
* This shim will be removed once the extension is fully migrated to core.
*/

import {
CommentManager as CoreCommentManager,
type CommentManagerAdapters,
type FileAdapter,
type IdentityAdapter,
type FileWriteResult,
type FileChangeInfo,
} from '@mdview/core';

/**
* Type-safe helper to send a Chrome runtime message and parse the response.
*/
async function sendChromeMessage<T>(message: Record<string, unknown>): Promise<T | undefined> {
const result: unknown = await chrome.runtime.sendMessage(message);
return result as T | undefined;
}

/**
* Chrome extension FileAdapter — delegates to the service worker
* via chrome.runtime.sendMessage({ type: 'WRITE_FILE', ... }).
*/
class ChromeFileAdapter implements FileAdapter {
async writeFile(path: string, content: string): Promise<FileWriteResult> {
const response = await sendChromeMessage<{ success?: boolean; error?: string }>({
type: 'WRITE_FILE',
payload: { path, content },
});

if (response?.error) {
return { success: false, error: response.error };
}
return { success: true };
}

readFile(_path: string): Promise<string> {
return Promise.reject(new Error('readFile not supported via Chrome messaging'));
}

checkChanged(_url: string, _lastHash: string): Promise<FileChangeInfo> {
return Promise.resolve({ changed: false });
}

watch(_path: string, _callback: () => void): () => void {
return () => {};
}
}

/**
* Chrome extension IdentityAdapter — delegates to the native host
* via chrome.runtime.sendMessage({ type: 'GET_USERNAME' }).
*/
class ChromeIdentityAdapter implements IdentityAdapter {
async getUsername(): Promise<string> {
try {
const resp = await sendChromeMessage<{ username?: string }>({
type: 'GET_USERNAME',
});
return resp?.username ?? '';
} catch {
// Native host may not be installed
return '';
}
}
}

/**
* CommentManager pre-configured with Chrome extension adapters.
* Drop-in replacement for the previous monolithic CommentManager.
*/
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class CommentManager extends CoreCommentManager {
constructor(adapters?: CommentManagerAdapters) {
// If no adapters provided, use Chrome-backed defaults
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const resolved: CommentManagerAdapters = adapters ?? {
file: new ChromeFileAdapter(),
identity: new ChromeIdentityAdapter(),
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super(resolved);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
// CSS is imported but only activated when we add .mdview-active to body
import './content.css';
import { FileScanner } from '../utils/file-scanner';
import type { AppState } from '../types';
import type { AppState } from '@mdview/core';
import { debug } from '../utils/debug-logger';
import { TocRenderer } from '../ui/toc-renderer';
import { TocRenderer } from '@mdview/core';
import type { ExportUI } from '../ui/export-ui';
import type { CommentManager } from '../comments/comment-manager';

Expand Down Expand Up @@ -266,7 +266,10 @@ class MDViewContentScript {
}

// Setup Comments (only for local files, default enabled)
if (this.state?.preferences.commentsEnabled !== false && window.location.protocol === 'file:') {
if (
this.state?.preferences.commentsEnabled !== false &&
window.location.protocol === 'file:'
) {
await this.setupComments(content, filePath);
}

Expand Down Expand Up @@ -608,7 +611,7 @@ class MDViewContentScript {
debug.info('MDView', `[ContentScript] Received request to apply theme: ${themeName}`);
const { themeEngine } = await import('../core/theme-engine');
debug.debug('MDView', `[ContentScript] Theme engine imported, calling applyTheme`);
await themeEngine.applyTheme(themeName as import('../types').ThemeName);
await themeEngine.applyTheme(themeName as import('@mdview/core').ThemeName);
debug.info('MDView', '[ContentScript] Theme applied successfully via engine');
} catch (error) {
debug.error('MDView', '[ContentScript] Failed to apply theme:', error);
Expand Down Expand Up @@ -903,7 +906,12 @@ class MDViewContentScript {
debug.info('MDView', 'Setting up Comments...');
const { CommentManager } = await import('../comments/comment-manager');
this.commentManager = new CommentManager();
await this.commentManager.initialize(markdown, filePath, this.state!.preferences);
const preferences = this.state?.preferences;
if (!preferences) {
debug.warn('MDView', 'Cannot initialize comments: state not loaded');
return;
}
await this.commentManager.initialize(markdown, filePath, preferences);
debug.info('MDView', 'Comments initialized');
} catch (error) {
debug.error('MDView', 'Failed to setup comments:', error);
Expand Down
File renamed without changes.
53 changes: 53 additions & 0 deletions packages/chrome-ext/src/core/render-pipeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Render Pipeline (Chrome Extension Shim)
*
* Re-exports the platform-agnostic RenderPipeline from @mdview/core,
* pre-configured with a Chrome MessagingAdapter that delegates
* to chrome.runtime.sendMessage.
*/

import {
RenderPipeline as CoreRenderPipeline,
type RenderOptions,
type RenderProgress,
type ProgressCallback,
type RenderPipelineOptions,
type MessagingAdapter,
type IPCMessage,
} from '@mdview/core';

// ---------------------------------------------------------------------------
// Chrome MessagingAdapter — bridges core pipeline to chrome.runtime
// ---------------------------------------------------------------------------

class ChromeMessagingAdapter implements MessagingAdapter {
async send(message: IPCMessage): Promise<unknown> {
return chrome.runtime.sendMessage(message);
}
}

// ---------------------------------------------------------------------------
// Re-export the pipeline class (consumers can still `new RenderPipeline()`)
// ---------------------------------------------------------------------------

/**
* Chrome-flavored RenderPipeline.
*
* Extends the core pipeline by injecting a ChromeMessagingAdapter so that
* cache operations (CACHE_GENERATE_KEY, CACHE_GET, CACHE_SET) are routed
* through the Chrome extension service worker.
*/
export class RenderPipeline extends CoreRenderPipeline {
constructor(options?: RenderPipelineOptions) {
super({
messaging: new ChromeMessagingAdapter(),
...options,
});
}
}

// Re-export types so existing consumers keep working
export type { RenderOptions, RenderProgress, ProgressCallback };

// Export singleton
export const renderPipeline = new RenderPipeline();
Loading
Loading