Skip to content

Latest commit

 

History

History
374 lines (276 loc) · 11.2 KB

File metadata and controls

374 lines (276 loc) · 11.2 KB

Developer Guide

This document provides architecture overview and platform-specific build information for contributors.

Architecture Overview

mdconv is a multi-platform Markdown converter with shared core logic and platform-specific adapters:

┌─────────────────────────────────────────┐
│         Core Conversion Logic           │
│    (src/core/converter.ts)              │
│                                         │
│  • HTML → Markdown via Turndown         │
│  • Word heading normalization           │
│  • Google Docs cleanup                  │
│  • Monospace detection → code blocks    │
│  • Bidirectional rich text conversion   │
└──────────────┬──────────────────────────┘
               │
       ┌───────┴────────┐
       │   Adapters     │
       │   (Interfaces) │
       └───────┬────────┘
               │
    ┌──────────┼──────────┐
    │          │          │
┌───▼────┐ ┌──▼─────┐ ┌──▼─────┐
│ Chrome │ │Firefox │ │Raycast │
│Platform│ │Platform│ │Platform│
└────────┘ └────────┘ └────────┘

Core Components

  • src/core/converter.ts - Main conversion engine using Turndown
  • src/core/adapters/ - Platform abstraction interfaces:
    • clipboard.ts - Clipboard read/write interface
    • dom-parser.ts - HTML parsing interface
  • src/core/env.ts - Centralized environment configuration
  • src/core/logging.ts - Debug logging utilities

Platform Implementations

Each platform lives in src/platforms/<platform>/ with:

  • Entry points (popup.ts, background.ts, content-script.ts)
  • Platform-specific converter wrapper
  • Adapters implementing core interfaces

Chrome/Edge/Brave Extension

Location: src/platforms/chrome/

How It Works

  1. Popup UI (popup.ts) - Main interface for manual paste & convert
  2. Context Menu (background.ts) - Right-click "Copy as Markdown" on selections
  3. Content Script (content-script.ts) - Extracts HTML from page selections
  4. Adapters (adapters/) - Use browser clipboard APIs and jsdom for parsing

Building

# Development (watch mode)
npm run dev

# Production build
npm run build

# Create distributable ZIP
npm run build:zip

Output: dist/ directory contains unpacked extension, mdconv-extension.zip for store submission

Static Assets: static/ contains manifest.json, popup HTML/CSS, and icons - copied to dist/ during build

Loading in Browser

Chrome:

  1. Navigate to chrome://extensions
  2. Enable "Developer mode"
  3. Click "Load unpacked" → select dist/

Edge:

  1. Navigate to edge://extensions
  2. Enable "Developer mode"
  3. Click "Load unpacked" → select dist/

Firefox Extension

Location: src/platforms/firefox/

How It Works

Firefox implementation uses the proxy adapter pattern - it re-exports Chrome adapters because both use standard Web APIs:

// src/platforms/firefox/adapters/firefox-clipboard.ts
export * from "../../chrome/adapters/chrome-clipboard.js";

Why this works: Both Chrome and Firefox support:

  • Standard navigator.clipboard API
  • Standard DOM parsing via jsdom
  • Manifest V3 (with minor differences handled at manifest level)

Key Differences from Chrome

  1. Manifest: Uses static/manifest.firefox.json with Firefox-specific fields:

    • browser_specific_settings.gecko for add-on ID
    • data_collection_permissions: {required: ["none"]}
  2. Browser API Access: Uses globalThis.browser fallback:

    const browser = globalThis.browser || chrome;
  3. Build Target: ESBuild uses --target=firefox109 (vs chrome115)

Building

# Development
npm run dev:firefox

# Production build
npm run build:firefox

# Create distributable ZIP
npm run build:firefox:zip

# Create source package for Mozilla reviewers
npm run build:firefox:source

Outputs:

  • dist-firefox/ - Unpacked extension
  • mdconv-firefox.zip - Store submission package
  • mdconv-firefox-source.zip - Source code for Mozilla review

Store Submission

See FIREFOX_BUILD.md for detailed build instructions provided to Mozilla reviewers.

Raycast Extension

Location: src/platforms/raycast/ (source) + raycast/ (extension package)

How It Works

Raycast's store requires self-contained extensions. A prebuild script copies shared source into raycast/src/ with import path rewriting, making the extension fully self-contained:

Source (monorepo):
├── src/core/                          ← Shared conversion logic
├── src/types/                         ← Type declarations
├── src/platforms/raycast/
│   ├── convert-clipboard.tsx          ← Command entry points
│   ├── raycast-converter.ts           ← Raycast-specific converter
│   └── adapters/                      ← Platform adapters
└── scripts/prepare-raycast-build.mjs  ← Prebuild: copies + rewrites imports

Generated (self-contained):
└── raycast/src/                       ← All generated by prebuild
    ├── core/                          ← Copied from src/core/
    ├── types/                         ← Copied from src/types/
    ├── adapters/                      ← Copied with imports rewritten
    ├── convert-*.tsx                  ← Copied with imports rewritten
    └── raycast-converter.ts           ← Copied with imports rewritten

Build Process

  1. Prebuild (scripts/prepare-raycast-build.mjs):

    • Copies src/core/raycast/src/core/
    • Copies src/types/raycast/src/types/
    • Copies adapters with import rewriting (../../../core/../core/)
    • Copies commands/converter with import rewriting (../../core/./core/)
  2. Build: Raycast CLI (ray build -e dist) compiles raycast/src/ entry points

  3. Key rule: Never edit raycast/src/ directly — changes are overwritten by prebuild. Edit source in src/platforms/raycast/ instead.

Key Files

Build scripts:

  • scripts/prepare-raycast-build.mjs — Copies shared source with import rewriting
  • scripts/sync-version.mjs — Syncs version from root package.json

Self-contained configs:

  • raycast/package.json — Includes all dependencies (turndown, linkedom, etc.)
  • raycast/tsconfig.json — Only override from Raycast template: adds DOM types for linkedom

Building

# From root (includes prebuild sync)
npm run build:raycast

# Development mode (auto-reload in Raycast)
npm run dev:raycast

# From raycast/ directory
cd raycast
npm run build  # prebuild runs automatically
npm run dev

Testing Locally

cd raycast
npm run dev

Opens extension in Raycast with hot-reload. Search any of the six commands (e.g. "Convert Clipboard to Markdown") to test.

Publishing to Raycast Store

Use the publish script which handles all the monorepo gymnastics automatically:

npm run publish:raycast

This script (scripts/raycast-publish.sh) does the following:

  1. Runs the prebuild to generate raycast/src/ from shared source
  2. Temporarily un-ignores the generated src/ files (they must be committed for Raycast CI)
  3. Strips the prebuild script from package.json (parent-relative paths won't exist in the monorepo)
  4. Runs npx @raycast/api@latest publish to open/update a PR on raycast/extensions
  5. Restores .gitignore and package.json to their original state

Development Workflow

1. Install Node.js

macOS:

brew install node

Windows:

  1. Download from nodejs.org (LTS version)
  2. Run installer
  3. Fix PowerShell execution policy if needed:
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Verify: node --version (should show v25.x or later)

2. Install Dependencies

npm install
npm run raycast:install  # If working on Raycast extension

3. Build Commands

# Type checking
npm run typecheck

# Chrome extension
npm run build        # Production
npm run build:zip    # + create ZIP
npm run dev          # Watch mode

# Firefox extension
npm run build:firefox
npm run build:firefox:zip
npm run build:firefox:source
npm run dev:firefox

# Raycast extension
npm run build:raycast
npm run dev:raycast

4. Testing

npm test  # Run all tests

Tests use fixtures in test/ covering:

  • Word desktop app HTML
  • Word Online HTML
  • Google Docs HTML
  • Outlook Web HTML
  • Image handling scenarios

5. Debugging

Chrome Extension:

// In popup DevTools console
localStorage.setItem('mdconv.debugClipboard', 'true');
// Paste again to see raw HTML logged
localStorage.removeItem('mdconv.debugClipboard');  // Disable

Raycast Extension:

# Check logs in Raycast development console
npm run dev:raycast
# Raycast shows errors/console.log output

Environment Variables:

  • MDCONV_DEBUG=1 - Enable all debug logging
  • MDCONV_DEBUG_CLIPBOARD=1 - Log clipboard contents
  • MDCONV_DEBUG_INLINE=1 - Log HTML→Markdown conversion details

See src/core/env.ts for complete environment variable definitions.

Code Quality Standards

Error Handling

  • Use error consistently in catch blocks (never err, messageError)
  • Pattern: } catch (error) { /* handle */ }

Documentation

  • Add JSDoc comments to all exported functions in src/core/
  • Include purpose, parameters, return value, and key behaviors

Configuration

  • Use centralized src/core/env.ts for all environment access
  • Avoid direct process.env access

Simplicity

  • Prefer simple functions over classes when possible
  • Regularly check for dead code: npx ts-unused-exports tsconfig.json

Quality Gate Checklist

Before committing substantial changes:

  1. ✅ All catch blocks use error consistently?
  2. ✅ Exported functions have JSDoc documentation?
  3. ✅ Environment access through src/core/env.ts?
  4. ✅ Checked for unused exports?
  5. ✅ Tests pass and builds work for all platforms?

Run: npm run typecheck && npm run build && npm run build:raycast && npm test

Platform-Specific Notes

Chrome/Firefox

  • Share 80% of code via proxy adapters
  • Only differ in manifest and build targets
  • Both use standard Web APIs

Raycast

  • macOS only (Raycast limitation)
  • Uses Node.js APIs for clipboard access
  • Prebuild script generates raycast/src/ from shared source with import rewriting
  • Development uses parent source; publishing uses self-contained copy in raycast/src/

Contributing

  1. Create a feature branch
  2. Make changes, following code quality standards above
  3. Run quality gate checks
  4. Update tests if needed
  5. Commit with clear messages
  6. For Raycast changes, edit source in src/platforms/raycast/ (not raycast/src/)

References