Replace marked.min.js with the marked npm package and update all references accordingly.
This commit is contained in:
@@ -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
15
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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
146
src/marked.min.js
vendored
@@ -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,">").replace(/"/g,""");}
|
|
||||||
|
|
||||||
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);
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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: '.' }
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user