Replace marked.min.js with the marked npm package and update all references accordingly.

This commit is contained in:
2026-03-19 12:28:14 +01:00
parent 2b8659499a
commit ff14061dfd
7 changed files with 23 additions and 157 deletions

View File

@@ -18,7 +18,7 @@ Three-part Chrome extension:
- **Runtime**: Chrome MV3 (Manifest V3) - **Runtime**: Chrome MV3 (Manifest V3)
- **Language**: Vanilla JavaScript (ES6+), no frontend framework - **Language**: Vanilla JavaScript (ES6+), no frontend framework
- **Styling**: Tailwind CSS v4 + PostCSS + Autoprefixer; CSS variables for theming; dark mode via `prefers-color-scheme` - **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**: Vite v8 with `vite-plugin-static-copy`
## Build & Development ## Build & Development
@@ -66,7 +66,6 @@ src/
├── settings.html/.js/.css # Options page ├── settings.html/.js/.css # Options page
├── content.js # Page content extractor ├── content.js # Page content extractor
├── background.js # Service worker ├── background.js # Service worker
├── marked.min.js # Bundled Markdown renderer
└── icons/ # 16, 32, 48, 128px PNG icons └── icons/ # 16, 32, 48, 128px PNG icons
dist/ # Build output (gitignored) dist/ # Build output (gitignored)
vite.config.js vite.config.js

15
package-lock.json generated
View File

@@ -8,6 +8,9 @@
"name": "memos-extension", "name": "memos-extension",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": {
"marked": "^17.0.4"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.2.1", "@tailwindcss/postcss": "^4.2.1",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
@@ -1614,6 +1617,18 @@
"@jridgewell/sourcemap-codec": "^1.5.5" "@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": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",

View File

@@ -19,5 +19,8 @@
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"vite": "^8.0.0", "vite": "^8.0.0",
"vite-plugin-static-copy": "^3.3.0" "vite-plugin-static-copy": "^3.3.0"
},
"dependencies": {
"marked": "^17.0.4"
} }
} }

146
src/marked.min.js vendored
View File

@@ -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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");}
function parseInline(s){
// code spans
s = s.replace(/`([^`]+)`/g,(_,c)=>`<code>${escape(c)}</code>`);
// bold+italic
s = s.replace(/\*\*\*(.+?)\*\*\*/g,"<strong><em>$1</em></strong>");
// bold
s = s.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>");
s = s.replace(/__(.+?)__/g,"<strong>$1</strong>");
// italic
s = s.replace(/\*(.+?)\*/g,"<em>$1</em>");
s = s.replace(/_([^_]+)_/g,"<em>$1</em>");
// strikethrough
s = s.replace(/~~(.+?)~~/g,"<del>$1</del>");
// images
s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(_,alt,src)=>`<img src="${escape(src)}" alt="${escape(alt)}">`);
// links
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(_,txt,href)=>`<a href="${escape(href)}" target="_blank">${txt}</a>`);
// auto-link
s = s.replace(/(?<!["\(])(https?:\/\/[^\s<>)]+)/g,url=>`<a href="${escape(url)}" target="_blank">${escape(url)}</a>`);
// hard break
s = s.replace(/ \n/g,"<br>");
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<lines.length && !lines[i].startsWith("```")){
code.push(lines[i]);
i++;
}
i++; // skip closing ```
out.push(`<pre><code class="language-${escape(lang)}">${escape(code.join("\n"))}</code></pre>`);
continue;
}
// Headings
const hM = line.match(/^(#{1,6})\s+(.*)/);
if(hM){
const lvl = hM[1].length;
out.push(`<h${lvl}>${parseInline(hM[2])}</h${lvl}>`);
i++; continue;
}
// HR
if(/^[-*_]{3,}\s*$/.test(line)){
out.push("<hr>");
i++; continue;
}
// Blockquote
if(line.startsWith("> ")){
const bq = [];
while(i<lines.length && lines[i].startsWith("> ")){
bq.push(lines[i].slice(2));
i++;
}
out.push(`<blockquote>${parse(bq.join("\n"))}</blockquote>`);
continue;
}
// Unordered list
if(/^[-*+]\s/.test(line)){
const items = [];
while(i<lines.length && /^[-*+]\s/.test(lines[i])){
items.push(`<li>${parseInline(lines[i].replace(/^[-*+]\s/,""))}</li>`);
i++;
}
out.push(`<ul>${items.join("")}</ul>`);
continue;
}
// Ordered list
if(/^\d+\.\s/.test(line)){
const items = [];
while(i<lines.length && /^\d+\.\s/.test(lines[i])){
items.push(`<li>${parseInline(lines[i].replace(/^\d+\.\s/,""))}</li>`);
i++;
}
out.push(`<ol>${items.join("")}</ol>`);
continue;
}
// Table (| col | col |)
if(line.startsWith("|") && i+1<lines.length && /^\|[-| :]+\|/.test(lines[i+1])){
const headers = line.split("|").filter((_,idx,a)=>idx>0&&idx<a.length-1).map(h=>`<th>${parseInline(h.trim())}</th>`);
i+=2; // skip header + separator
const rows = [];
while(i<lines.length && lines[i].startsWith("|")){
const cells = lines[i].split("|").filter((_,idx,a)=>idx>0&&idx<a.length-1).map(c=>`<td>${parseInline(c.trim())}</td>`);
rows.push(`<tr>${cells.join("")}</tr>`);
i++;
}
out.push(`<table><thead><tr>${headers.join("")}</tr></thead><tbody>${rows.join("")}</tbody></table>`);
continue;
}
// Empty line
if(line.trim()===""){
i++; continue;
}
// Paragraph — collect until blank line
const para = [];
while(i<lines.length && lines[i].trim()!==""){
// stop on block elements
if(/^(#{1,6}\s|```|>|[-*+]\s|\d+\.\s|[-*_]{3,}\s*$)/.test(lines[i])) break;
para.push(lines[i]);
i++;
}
if(para.length) out.push(`<p>${parseInline(para.join(" "))}</p>`);
}
return out.join("\n");
}
global.marked = {
parse: function(src, opts) {
const breaks = opts && opts.breaks;
// With breaks:true, single newlines in paragraphs become <br>
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);

View File

@@ -133,7 +133,6 @@
</div> </div>
</div> </div>
<script src="/marked.min.js"></script>
<script src="/popup.js" type="module"></script> <script src="/popup.js" type="module"></script>
</body> </body>
</html> </html>

View File

@@ -1,6 +1,8 @@
// popup.js // popup.js
"use strict"; "use strict";
import { marked } from "marked";
// ── State ─────────────────────────────────────────────────────────────────── // ── State ───────────────────────────────────────────────────────────────────
const state = { const state = {
markdown: "", markdown: "",
@@ -172,12 +174,7 @@ function sanitizeHtml(html) {
function renderPreview() { function renderPreview() {
state.markdown = mdEditor.value; state.markdown = mdEditor.value;
if (typeof marked !== "undefined") { mdPreview.innerHTML = sanitizeHtml(marked.parse(state.markdown, { breaks: true }));
mdPreview.innerHTML = sanitizeHtml(marked.parse(state.markdown, { breaks: true }));
} else {
// Fallback: basic escaping
mdPreview.innerHTML = `<pre style="white-space:pre-wrap">${escHtml(state.markdown)}</pre>`;
}
} }
function escHtml(s) { function escHtml(s) {

View File

@@ -24,8 +24,7 @@ export default defineConfig({
viteStaticCopy({ viteStaticCopy({
targets: [ targets: [
{ src: 'manifest.json', dest: '.' }, { src: 'manifest.json', dest: '.' },
{ src: 'icons/*', dest: 'icons' }, { src: 'icons/*', dest: 'icons' }
{ src: 'marked.min.js', dest: '.' }
] ]
}) })
] ]