diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..941f381 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +## Project Overview + +**Memos Clipper** is a Chrome Extension (Manifest V3) that clips web pages or text selections and saves them to a [Memos](https://usememos.com) instance in Markdown format. + +## Architecture + +Three-part Chrome extension: + +- **`src/content.js`** — Content script injected into pages; extracts HTML and converts it to Markdown +- **`src/popup.html` / `src/popup.js`** — Main popup UI: editor, preview, image gallery, send button +- **`src/settings.html` / `src/settings.js`** — Options page: Memos URL, API token, defaults +- **`src/background.js`** — Service worker (minimal; onInstalled listener only) + +## Tech Stack + +- **Runtime**: Chrome MV3 (Manifest V3) +- **Language**: Vanilla JavaScript (ES6+), no frontend framework +- **Styling**: Tailwind CSS v4 + PostCSS + Autoprefixer; CSS variables for theming; dark mode via `prefers-color-scheme` +- **Markdown**: Custom HTML→Markdown converter in `content.js`; `marked.min.js` for preview rendering +- **Build**: Vite v8 with `vite-plugin-static-copy` + +## Build & Development + +```bash +npm install # Install dependencies +npm run dev # Dev server with hot reload +npm run build # Production build → dist/ +npm run preview # Preview built output +``` + +Load the extension in Chrome: **Extensions → Load unpacked → select `dist/`** + +## Key Implementation Details + +### API Integration +- Memos API v1: `/api/v1/memos`, `/api/v1/attachments` +- Requires Memos v0.22+ +- Bearer token auth via `chrome.storage.sync` +- Attachment flow: upload via `POST /api/v1/attachments` (JSON + base64 `content`), create memo, then link each attachment to the memo via `PATCH /api/v1/attachments/{id}` with `{ memo: "memos/{id}" }` + +### Content Extraction +- Removes boilerplate: nav, ads, sidebars, cookie banners (45+ selectors) +- Supports full-page and selection-only modes +- Converts: headings, lists, tables, code blocks, links, images, blockquotes + +### Image Handling +- Filters images smaller than 32px (icons/tracking pixels) +- Deduplicates images +- Supports data URIs +- Uploads images as attachments (`POST /api/v1/attachments`) with base64-encoded content +- After memo creation, links each attachment to the memo via `PATCH /api/v1/attachments/{id}` +- Attachment file URL pattern: `{memosUrl}/file/attachments/{id}` + +### Storage +- `chrome.storage.sync` for cross-device settings (URL, token, defaults) + +## Project Structure + +``` +src/ +├── manifest.json # MV3 config (permissions, entry points) +├── popup.html/.js/.css # Main extension popup +├── settings.html/.js/.css # Options page +├── content.js # Page content extractor +├── background.js # Service worker +├── marked.min.js # Bundled Markdown renderer +└── icons/ # 16, 32, 48, 128px PNG icons +dist/ # Build output (gitignored) +vite.config.js +package.json +``` + +## Permissions + +- `activeTab`, `scripting`, `storage` +- Host permissions: `` (needed for cross-origin image fetching and API calls) diff --git a/src/icons/icon128.png b/src/icons/icon128.png index 0c871e0..185f39d 100644 Binary files a/src/icons/icon128.png and b/src/icons/icon128.png differ diff --git a/src/icons/icon16.png b/src/icons/icon16.png index 570a006..70268d8 100644 Binary files a/src/icons/icon16.png and b/src/icons/icon16.png differ diff --git a/src/icons/icon32.png b/src/icons/icon32.png index d75b167..0e7e824 100644 Binary files a/src/icons/icon32.png and b/src/icons/icon32.png differ diff --git a/src/icons/icon48.png b/src/icons/icon48.png index f2bf68d..4544c1a 100644 Binary files a/src/icons/icon48.png and b/src/icons/icon48.png differ diff --git a/src/manifest.json b/src/manifest.json index a5c4693..7aa08bd 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -15,14 +15,14 @@ "default_popup": "popup.html", "default_icon": { "16": "icons/icon16.png", - "32": "icons/logo.svg", + "32": "icons/icon32.png", "48": "icons/icon48.png", "128": "icons/icon128.png" } }, "icons": { "16": "icons/icon16.png", - "32": "icons/logo.svg", + "32": "icons/icon32.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }, diff --git a/src/popup.css b/src/popup.css index c4f6ecf..87ee198 100644 --- a/src/popup.css +++ b/src/popup.css @@ -3,23 +3,37 @@ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { - --bg: #0f0f11; - --surface: #1a1a1f; - --surface2: #22222a; - --border: #2a2a35; + --bg: #ffffff; + --surface: #ffffff; + --surface2: #f9fafb; + --border: #f3f4f6; --accent: #10b981; --accent-dim: #10b98120; --accent-hover: #059669; - --text: #e8e8f0; - --text-dim: #777788; - --text-muted: #444455; - --success: #4ade80; - --error: #f87171; + --text: #111827; + --text-dim: #4b5563; + --text-muted: #9ca3af; + --success: #10b981; + --error: #ef4444; --radius: 6px; --font: 'IBM Plex Sans', sans-serif; --mono: 'IBM Plex Mono', monospace; } +@media (prefers-color-scheme: dark) { + :root { + --bg: #0f0f11; + --surface: #1a1a1f; + --surface2: #22222a; + --border: #2a2a35; + --text: #e8e8f0; + --text-dim: #777788; + --text-muted: #444455; + --success: #4ade80; + --error: #f87171; + } +} + html, body { width: 440px; min-height: 200px; diff --git a/src/popup.html b/src/popup.html index 2e23282..c263ee5 100644 --- a/src/popup.html +++ b/src/popup.html @@ -28,16 +28,10 @@