Skip to content

Commit 56b60fc

Browse files
committed
Refactor: Rewrite extension using Plasmo, React and TailwindCSS
1 parent eddafc6 commit 56b60fc

25 files changed

Lines changed: 6893 additions & 1983 deletions

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
build
2+
node_modules
3+
.DS_Store
4+
.plasmo
5+
.env

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Flomo Code 是一款 Chrome 扩展程序,为您的 Flomo 笔记提供代码语
99
您可以从 [Release](https://github.com/greycodee/flomo-code/releases/) 下载源码压缩包,然后解压文件。在 **Chrome 扩展程序**页面打开右上角的**开发者模式**,选择**加载已解压的扩展程序**,选择解压的文件路径,就可加载flomo-code
1010
## 使用
1111

12-
要使用 Flomo Code,只需在 Flomo 笔记中添加 Markdown 代码块,点击 flomo 页面右下角`绿色按钮`,即可突出显示语法
12+
要使用 Flomo Code,只需在 Flomo 笔记中添加 Markdown 代码块,扩展即会自动识别并在笔记渲染时高亮语法内容(也可以在插件弹窗中随时更换主题,享受实时预览和动态更新)
1313

1414
### before
1515
![](./before.png)
File renamed without changes.
File renamed without changes.

background.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Storage } from "@plasmohq/storage"
2+
3+
const storage = new Storage()
4+
5+
// Initialize default theme
6+
chrome.runtime.onInstalled.addListener(async () => {
7+
const currentTheme = await storage.get("chooseTheme")
8+
if (!currentTheme) {
9+
await storage.set("chooseTheme", "stackoverflow-dark.css")
10+
}
11+
})
12+
13+
// Listen for network requests to Flomo notes API
14+
chrome.webRequest.onCompleted.addListener(
15+
(details) => {
16+
if (details.url.includes("https://h5.udrig.com/app/v1")) {
17+
console.log("Captured target request:", details.url)
18+
19+
if (details.tabId >= 0) {
20+
chrome.tabs.sendMessage(details.tabId, { action: "highlightCode" }).catch(() => {
21+
// Ignore error if tab is not ready or content script not injected
22+
})
23+
}
24+
}
25+
},
26+
{ urls: ["https://h5.udrig.com/app/v1*"] }
27+
)

choose_theme.gif

-179 KB
Binary file not shown.

content.tsx

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import type { PlasmoCSConfig } from "plasmo"
2+
import { useStorage } from "@plasmohq/storage/hook"
3+
import { useEffect } from "react"
4+
import hljs from "highlight.js"
5+
import { Code2 } from "lucide-react"
6+
7+
import stackoverflowDark from "data-text:highlight.js/styles/stackoverflow-dark.css"
8+
import stackoverflowLight from "data-text:highlight.js/styles/stackoverflow-light.css"
9+
import githubDark from "data-text:highlight.js/styles/github-dark.css"
10+
import githubLight from "data-text:highlight.js/styles/github.css"
11+
import atomOneDark from "data-text:highlight.js/styles/atom-one-dark.css"
12+
13+
import tailwindCss from "data-text:./style.css"
14+
15+
const themeMap: Record<string, string> = {
16+
"stackoverflow-dark.css": stackoverflowDark,
17+
"stackoverflow-light.css": stackoverflowLight,
18+
"github-dark.css": githubDark,
19+
"github.css": githubLight,
20+
"atom-one-dark.css": atomOneDark
21+
}
22+
23+
export const config: PlasmoCSConfig = {
24+
matches: ["https://v.flomoapp.com/*", "https://flomoapp.com/*"],
25+
all_frames: true
26+
}
27+
28+
export const getStyle = () => {
29+
const style = document.createElement("style")
30+
style.textContent = tailwindCss
31+
return style
32+
}
33+
34+
function highlightCodeBlocks() {
35+
const memos = document.getElementsByClassName("richText")
36+
const memos_array = Array.from(memos)
37+
38+
memos_array.forEach((memo) => {
39+
let children = memo.children
40+
const memo_p_array = Array.from(children)
41+
let languageFlag = false
42+
let codeContentArr: string[] = []
43+
let languageType = ""
44+
45+
for (let i = 0; i < memo_p_array.length; i++) {
46+
const pNode = memo_p_array[i]
47+
const innerHTML = pNode.innerHTML
48+
49+
if (innerHTML.startsWith("```") && innerHTML.substring(3) !== "") {
50+
languageType = "language-" + innerHTML.substring(3).trim()
51+
languageFlag = true
52+
if (memo.contains(pNode)) memo.removeChild(pNode)
53+
continue
54+
} else if (innerHTML.startsWith("```") && innerHTML.substring(3) === "") {
55+
languageFlag = false
56+
const codeForCopy = [...codeContentArr]
57+
58+
let pre = document.createElement("pre")
59+
let code = document.createElement("code")
60+
code.innerHTML = codeContentArr.join("\n")
61+
if (languageType) code.className = languageType
62+
63+
let wrapper = document.createElement("div")
64+
wrapper.style.position = "relative"
65+
wrapper.style.margin = "10px 0"
66+
wrapper.style.borderRadius = "8px"
67+
wrapper.style.overflow = "hidden"
68+
wrapper.style.border = "1px solid rgba(136, 148, 168, 0.2)"
69+
70+
let copyBtn = document.createElement("button")
71+
copyBtn.innerHTML = "Copy"
72+
copyBtn.style.position = "absolute"
73+
copyBtn.style.top = "8px"
74+
copyBtn.style.right = "8px"
75+
copyBtn.style.fontSize = "12px"
76+
copyBtn.style.padding = "4px 8px"
77+
copyBtn.style.background = "#0f172a" // slate-900
78+
copyBtn.style.color = "#f8fafc" // slate-50
79+
copyBtn.style.border = "1px solid #334155" // slate-700
80+
copyBtn.style.borderRadius = "4px"
81+
copyBtn.style.cursor = "pointer"
82+
copyBtn.style.transition = "all 0.2s ease"
83+
84+
// Handlers for hover effects to make it feel premium
85+
copyBtn.onmouseenter = () => {
86+
copyBtn.style.background = "#1e293b" // slate-800
87+
}
88+
copyBtn.onmouseleave = () => {
89+
copyBtn.style.background = "#0f172a"
90+
}
91+
92+
copyBtn.addEventListener("click", function (e) {
93+
e.stopPropagation()
94+
const tempElement = document.createElement("div")
95+
let decodedContent = codeForCopy.map(c => {
96+
tempElement.innerHTML = c
97+
return tempElement.textContent || tempElement.innerText
98+
})
99+
navigator.clipboard.writeText(decodedContent.join("\n")).then(() => {
100+
copyBtn.innerHTML = "Copied!"
101+
copyBtn.style.color = "#34d399" // emerald-400
102+
setTimeout(() => {
103+
copyBtn.innerHTML = "Copy"
104+
copyBtn.style.color = "#f8fafc"
105+
}, 1500)
106+
})
107+
})
108+
109+
pre.appendChild(code)
110+
wrapper.appendChild(pre)
111+
wrapper.appendChild(copyBtn)
112+
113+
pNode.innerHTML = ""
114+
pNode.appendChild(wrapper)
115+
116+
codeContentArr = []
117+
languageType = ""
118+
continue
119+
} else {
120+
if (languageFlag) {
121+
let content = innerHTML
122+
const tempDiv = document.createElement("div")
123+
tempDiv.innerHTML = content
124+
const links = tempDiv.querySelectorAll("a")
125+
links.forEach((link) => {
126+
const href = link.getAttribute("href")
127+
if (href) {
128+
const textNode = document.createTextNode(href)
129+
link.parentNode?.replaceChild(textNode, link)
130+
}
131+
})
132+
codeContentArr.push(tempDiv.innerHTML)
133+
if (memo.contains(pNode)) memo.removeChild(pNode)
134+
}
135+
}
136+
}
137+
})
138+
hljs.highlightAll()
139+
}
140+
141+
export default function FlomoCodeOverlay() {
142+
const [theme] = useStorage("chooseTheme", "stackoverflow-dark.css")
143+
144+
// Sync theme
145+
useEffect(() => {
146+
if (!theme) return
147+
const cssText = themeMap[theme] || themeMap["stackoverflow-dark.css"]
148+
let styleEl = document.getElementById("flomo-code-theme")
149+
if (!styleEl) {
150+
styleEl = document.createElement("style")
151+
styleEl.id = "flomo-code-theme"
152+
document.head.appendChild(styleEl)
153+
}
154+
styleEl.textContent = cssText
155+
}, [theme])
156+
157+
// Listen for message from background layer & Initial Highlight
158+
useEffect(() => {
159+
setTimeout(() => {
160+
highlightCodeBlocks()
161+
}, 1000) // initial attempt
162+
163+
const listener = (request: any, sender: any, sendResponse: any) => {
164+
if (request.action === "highlightCode") {
165+
setTimeout(highlightCodeBlocks, 300) // slight delay to ensure DOM is updated
166+
sendResponse({ status: "OK" })
167+
}
168+
}
169+
chrome.runtime.onMessage.addListener(listener)
170+
return () => chrome.runtime.onMessage.removeListener(listener)
171+
}, [])
172+
173+
return null
174+
}

include/highlight.min.css

Lines changed: 0 additions & 86 deletions
This file was deleted.

0 commit comments

Comments
 (0)