diff --git a/CLAUDE.md b/CLAUDE.md index c294af4..a5f1309 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ Three-part Chrome extension: - **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 +- **Markdown**: Custom HTML→Markdown converter in `content.js`; `marked` npm package for preview rendering - **Build**: Vite v8 with `vite-plugin-static-copy` ## Build & Development @@ -66,7 +66,6 @@ src/ ├── 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 diff --git a/package-lock.json b/package-lock.json index e290a2f..17a358f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "memos-extension", "version": "1.0.0", "license": "ISC", + "dependencies": { + "marked": "^17.0.4" + }, "devDependencies": { "@tailwindcss/postcss": "^4.2.1", "@tailwindcss/typography": "^0.5.19", @@ -1614,6 +1617,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/marked": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz", + "integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/package.json b/package.json index aa76870..404c666 100644 --- a/package.json +++ b/package.json @@ -19,5 +19,8 @@ "tailwindcss": "^4.2.1", "vite": "^8.0.0", "vite-plugin-static-copy": "^3.3.0" + }, + "dependencies": { + "marked": "^17.0.4" } } diff --git a/src/marked.min.js b/src/marked.min.js deleted file mode 100644 index 0826999..0000000 --- a/src/marked.min.js +++ /dev/null @@ -1,146 +0,0 @@ -/*! - * marked-mini - minimal Markdown renderer for Memos Clipper - * Supports: headings, bold, italic, strike, code, pre, blockquote, - * links, images, ul, ol, hr, tables, line breaks - */ -(function(global){ -"use strict"; -function escape(s){return s.replace(/&/g,"&").replace(//g,">").replace(/"/g,""");} - -function parseInline(s){ - // code spans - s = s.replace(/`([^`]+)`/g,(_,c)=>`${escape(c)}`); - // bold+italic - s = s.replace(/\*\*\*(.+?)\*\*\*/g,"$1"); - // bold - s = s.replace(/\*\*(.+?)\*\*/g,"$1"); - s = s.replace(/__(.+?)__/g,"$1"); - // italic - s = s.replace(/\*(.+?)\*/g,"$1"); - s = s.replace(/_([^_]+)_/g,"$1"); - // strikethrough - s = s.replace(/~~(.+?)~~/g,"$1"); - // images - s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(_,alt,src)=>`${escape(alt)}`); - // links - s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(_,txt,href)=>`${txt}`); - // auto-link - s = s.replace(/(?)]+)/g,url=>`${escape(url)}`); - // hard break - s = s.replace(/ \n/g,"
"); - return s; -} - -function parse(src){ - const lines = src.split("\n"); - const out = []; - let i = 0; - - while(i < lines.length){ - const line = lines[i]; - - // Fenced code block - const fenceM = line.match(/^```(\w*)/); - if(fenceM){ - const lang = fenceM[1]||""; - const code = []; - i++; - while(i${escape(code.join("\n"))}`); - continue; - } - - // Headings - const hM = line.match(/^(#{1,6})\s+(.*)/); - if(hM){ - const lvl = hM[1].length; - out.push(`${parseInline(hM[2])}`); - i++; continue; - } - - // HR - if(/^[-*_]{3,}\s*$/.test(line)){ - out.push("
"); - i++; continue; - } - - // Blockquote - if(line.startsWith("> ")){ - const bq = []; - while(i ")){ - bq.push(lines[i].slice(2)); - i++; - } - out.push(`
${parse(bq.join("\n"))}
`); - continue; - } - - // Unordered list - if(/^[-*+]\s/.test(line)){ - const items = []; - while(i${parseInline(lines[i].replace(/^[-*+]\s/,""))}`); - i++; - } - out.push(`
    ${items.join("")}
`); - continue; - } - - // Ordered list - if(/^\d+\.\s/.test(line)){ - const items = []; - while(i${parseInline(lines[i].replace(/^\d+\.\s/,""))}`); - i++; - } - out.push(`
    ${items.join("")}
`); - continue; - } - - // Table (| col | col |) - if(line.startsWith("|") && i+1idx>0&&idx`${parseInline(h.trim())}`); - i+=2; // skip header + separator - const rows = []; - while(iidx>0&&idx`${parseInline(c.trim())}`); - rows.push(`${cells.join("")}`); - i++; - } - out.push(`${headers.join("")}${rows.join("")}
`); - continue; - } - - // Empty line - if(line.trim()===""){ - i++; continue; - } - - // Paragraph — collect until blank line - const para = []; - while(i|[-*+]\s|\d+\.\s|[-*_]{3,}\s*$)/.test(lines[i])) break; - para.push(lines[i]); - i++; - } - if(para.length) out.push(`

${parseInline(para.join(" "))}

`); - } - return out.join("\n"); -} - -global.marked = { - parse: function(src, opts) { - const breaks = opts && opts.breaks; - // With breaks:true, single newlines in paragraphs become
- if (breaks) { - // Pre-process: single \n inside paragraph text → two spaces + \n (markdown hard break) - src = src.replace(/([^\n])\n([^\n])/g, "$1 \n$2"); - } - return parse(src); - } -};})(window); diff --git a/src/popup.html b/src/popup.html index 35cba89..d51560a 100644 --- a/src/popup.html +++ b/src/popup.html @@ -133,7 +133,6 @@ - diff --git a/src/popup.js b/src/popup.js index 18147da..80ac640 100644 --- a/src/popup.js +++ b/src/popup.js @@ -1,6 +1,8 @@ // popup.js "use strict"; +import { marked } from "marked"; + // ── State ─────────────────────────────────────────────────────────────────── const state = { markdown: "", @@ -172,12 +174,7 @@ function sanitizeHtml(html) { function renderPreview() { state.markdown = mdEditor.value; - if (typeof marked !== "undefined") { - mdPreview.innerHTML = sanitizeHtml(marked.parse(state.markdown, { breaks: true })); - } else { - // Fallback: basic escaping - mdPreview.innerHTML = `
${escHtml(state.markdown)}
`; - } + mdPreview.innerHTML = sanitizeHtml(marked.parse(state.markdown, { breaks: true })); } function escHtml(s) { diff --git a/vite.config.js b/vite.config.js index 11343b5..6c93949 100644 --- a/vite.config.js +++ b/vite.config.js @@ -24,8 +24,7 @@ export default defineConfig({ viteStaticCopy({ targets: [ { src: 'manifest.json', dest: '.' }, - { src: 'icons/*', dest: 'icons' }, - { src: 'marked.min.js', dest: '.' } + { src: 'icons/*', dest: 'icons' } ] }) ]