mirror of
https://github.com/doocs/md.git
synced 2025-01-22 11:54:43 +08:00
feat: add support for wxt as to build chrome extension (#458)
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
This commit is contained in:
parent
1e887fe87e
commit
57829696dd
4
.gitignore
vendored
4
.gitignore
vendored
@ -52,3 +52,7 @@ yarn.lock
|
||||
pnpm-lock.yaml
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
|
||||
.wxt
|
||||
.output
|
||||
web-ext.config.ts
|
@ -86,7 +86,7 @@
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
MathJax = {
|
||||
window.MathJax = {
|
||||
loader: { load: ['[tex]/ams'] },
|
||||
tex: { packages: { '[+]': ['ams'] }, tags: 'ams' },
|
||||
svg: { fontCache: 'none' },
|
||||
|
4606
package-lock.json
generated
4606
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,9 +14,11 @@
|
||||
"build:analyze": "cross-env ANALYZE=true vite build",
|
||||
"preview": "npm run build && vite preview",
|
||||
"release:cli": "node ./bin/release.js",
|
||||
"ext:dev": "wxt",
|
||||
"ext:zip": "wxt zip",
|
||||
"lint": "eslint . --fix",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"postinstall": "simple-git-hooks"
|
||||
"postinstall": "simple-git-hooks && wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
@ -77,7 +79,8 @@
|
||||
"vite": "^5.4.7",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-vue-devtools": "^7.6.5",
|
||||
"vue-tsc": "^2.1.10"
|
||||
"vue-tsc": "^2.1.10",
|
||||
"wxt": "^0.19.19"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "npx lint-staged"
|
||||
|
BIN
public/mpmd/icon-256.png
Normal file
BIN
public/mpmd/icon-256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 257 KiB |
36
public/mpmd/logo.svg
Normal file
36
public/mpmd/logo.svg
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="72"
|
||||
height="72"
|
||||
id="svg3"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs3" /><g
|
||||
id="g4"
|
||||
transform="matrix(0.89070732,0,0,0.89070732,3.9003714,5.4983284)"><path
|
||||
d="M 0,0 C 0.99,0.33 1.98,0.66 3,1 3.7382813,2.7851563 3.7382813,2.7851563 4.3125,5.0625 6.0622761,10.836761 9.0921103,16.042265 14.175781,19.464844 16,21 16,21 16.390625,23.042969 16.260417,25.028646 16.130208,27.014323 16,29 16.827578,28.351602 17.655156,27.703203 18.507812,27.035156 22.139055,24.616813 25.605347,24.453162 29.875,24.4375 35.870349,24.257564 40.524681,23.469262 46,21 c 0.99,0 1.98,0 3,0 -2.061302,7.973985 -5.265779,13.949118 -12.375,18.4375 -7.043602,3.115439 -16.325927,2.722302 -23.636719,0.75 C 5.8581906,37.042835 1.2546288,31.546961 -2.1679688,24.644531 -4.7216005,16.528059 -3.6280724,7.5584841 0,0 Z"
|
||||
fill="#07c060"
|
||||
transform="translate(5,28)"
|
||||
id="path1" /><path
|
||||
d="M 0,0 C 4.8892635,2.9504177 9.7332481,6.9497443 11.5625,12.4375 10.854805,12.278945 10.147109,12.120391 9.4179688,11.957031 -0.4326775,10.16478 -9.0324953,11.282238 -17.472656,16.757812 -23.701001,21.597673 -26.217108,28.036194 -28.4375,35.4375 c -0.66,0 -1.32,0 -2,0 -4.191048,-6.900261 -6.497995,-12.876728 -5,-21 2.597739,-6.6259514 7.958231,-11.9894438 14.292969,-15.140625 C -14.663002,-2.7607534 -6.2555903,-2.8579462 0,0 Z"
|
||||
fill="#07c060"
|
||||
transform="translate(47.4375,4.5625)"
|
||||
id="path2" /><path
|
||||
d="m 0,0 c 5.4175769,3.8087207 9.3081619,8.166598 10.6875,14.8125 0.948039,8.109576 -0.267493,14.278469 -5,21 -4.3896225,4.958988 -8.4852635,7.778487 -15,9 0.3493359,-0.603281 0.6986719,-1.206562 1.0585937,-1.828125 C -7.7988672,42.185156 -7.3438281,41.385937 -6.875,40.5625 -6.4225391,39.773594 -5.9700781,38.984687 -5.5039062,38.171875 -2.1468435,31.52379 -2.0393497,22.997339 -4.1835937,15.949219 -7.3870448,9.0276638 -11.630773,4.2851908 -18.75,1.4375 c -0.845625,-0.20625 -1.69125,-0.4125 -2.5625,-0.625 0,-0.66 0,-1.32 0,-2 C -13.984552,-3.8956547 -6.8997006,-4.0356739 0,0 Z"
|
||||
fill="#07c060"
|
||||
transform="translate(60.3125,22.1875)"
|
||||
id="path3" /></g><g
|
||||
id="layer1"><g
|
||||
style="fill:#07c060;fill-opacity:1"
|
||||
id="g3"
|
||||
transform="translate(55.918367,-0.87921667)"><path
|
||||
d="M 14.4184,12.699 12.1173,15.0002 9.99599,12.8788 12.2629,10.6119 C 12.6579,10.2169 13,9.5652 13,8.99613 V 7.99611 l -4,-2e-5 V 0.996095 h 7 V 8.99613 c 0,1.48047 -0.6056,2.72697 -1.5816,3.70287 z"
|
||||
fill="#000000"
|
||||
id="path1-8"
|
||||
style="fill:#07c060;fill-opacity:1" /><path
|
||||
d="M 5.41845,12.699 3.11731,15.0002 0.99599,12.8788 3.26295,10.6119 C 3.65789,10.2169 4,9.5652 4,8.99612 V 7.99611 L 0,7.99609 6.11959e-7,0.996094 H 7 V 8.99613 C 7,10.4766 6.39441,11.7231 5.41845,12.699 Z"
|
||||
fill="#000000"
|
||||
id="path2-4"
|
||||
style="fill:#07c060;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 3.0 KiB |
@ -62,6 +62,11 @@ const minioOSS = ref({
|
||||
secretKey: ``,
|
||||
})
|
||||
|
||||
const formMp = ref({
|
||||
appID: ``,
|
||||
appsecret: ``,
|
||||
})
|
||||
const mpDisabled = ref(window.location.href.startsWith(`http`))
|
||||
const formCustom = ref<{ code: string, editor: CodeMirror.EditorFromTextArea | null }>({
|
||||
code:
|
||||
localStorage.getItem(`formCustomConfig`)
|
||||
@ -109,6 +114,11 @@ const options = [
|
||||
value: `minio`,
|
||||
label: `MinIO`,
|
||||
},
|
||||
{
|
||||
value: `mp`,
|
||||
label: `公众号素材`,
|
||||
disabled: mpDisabled.value,
|
||||
},
|
||||
{
|
||||
value: `formCustom`,
|
||||
label: `自定义代码`,
|
||||
@ -160,6 +170,9 @@ onBeforeMount(() => {
|
||||
if (localStorage.getItem(`imgHost`)) {
|
||||
imgHost.value = localStorage.getItem(`imgHost`)!
|
||||
}
|
||||
if (localStorage.getItem(`mpConfig`)) {
|
||||
formMp.value = JSON.parse(localStorage.getItem(`mpConfig`)!)
|
||||
}
|
||||
})
|
||||
|
||||
function changeImgHost() {
|
||||
@ -249,6 +262,20 @@ function saveQiniuConfiguration() {
|
||||
ElMessage.success(`保存成功`)
|
||||
}
|
||||
|
||||
function saveMpConfiguration() {
|
||||
if (
|
||||
!(
|
||||
formMp.value.appID
|
||||
&& formMp.value.appsecret
|
||||
)
|
||||
) {
|
||||
ElMessage.error(`公众号图床 参数配置不全`)
|
||||
return
|
||||
}
|
||||
localStorage.setItem(`mpConfig`, JSON.stringify(formMp.value))
|
||||
ElMessage.success(`保存成功`)
|
||||
}
|
||||
|
||||
function formCustomSave() {
|
||||
const str = formCustom.value.editor!.getValue()
|
||||
localStorage.setItem(`formCustomConfig`, str)
|
||||
@ -301,6 +328,7 @@ function uploadImage(params: { file: any }) {
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item?.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
<el-upload
|
||||
@ -614,6 +642,57 @@ function uploadImage(params: { file: any }) {
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane class="github-panel" label="公众号 图床" name="mp">
|
||||
<template #label>
|
||||
<el-tooltip placement="top">
|
||||
<template #content>
|
||||
由于接口请求跨域问题请在浏览器插件形式中使用
|
||||
</template>
|
||||
<div>公众号 图床</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-form
|
||||
class="setting-form"
|
||||
:model="formMp"
|
||||
label-position="right"
|
||||
label-width="150px"
|
||||
:disabled="mpDisabled"
|
||||
>
|
||||
<el-form-item label="appID" :required="true">
|
||||
<el-input
|
||||
v-model.trim="formMp.appID"
|
||||
placeholder="如:wx6e1234567890efa3"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="appsecret">
|
||||
<el-input
|
||||
v-model.trim="formMp.appsecret"
|
||||
placeholder="如:d9f1abcdef01234567890abcdef82397"
|
||||
/>
|
||||
<el-link
|
||||
type="primary"
|
||||
href="https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html"
|
||||
target="_blank"
|
||||
>
|
||||
如何开启公众号开发者模式并获取应用账号密钥?
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-top: -26px;">
|
||||
<el-link
|
||||
type="primary"
|
||||
href="https://mpmd.pages.dev/tutorial/"
|
||||
target="_blank"
|
||||
>
|
||||
如何在浏览器插件中使用公众号图床?
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveMpConfiguration">
|
||||
保存配置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane class="github-panel formCustom" label="自定义代码" name="formCustom">
|
||||
<el-form class="setting-form" :model="formCustom" label-position="right">
|
||||
<el-form-item label="" :required="true">
|
||||
|
20
src/entrypoints/background.ts
Normal file
20
src/entrypoints/background.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { browser } from 'wxt/browser'
|
||||
import { defineBackground } from 'wxt/sandbox'
|
||||
|
||||
export default defineBackground({
|
||||
type: `module`,
|
||||
main() {
|
||||
browser.runtime.onInstalled.addListener((detail) => {
|
||||
if (import.meta.env.COMMAND === `serve`) {
|
||||
browser.runtime.openOptionsPage()
|
||||
return
|
||||
}
|
||||
if (detail.reason === `install`) {
|
||||
browser.tabs.create({ url: `https://mpmd.pages.dev/welcome` })
|
||||
}
|
||||
else if (detail.reason === `update`) {
|
||||
browser.runtime.openOptionsPage()
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
64
src/entrypoints/popup/App.vue
Normal file
64
src/entrypoints/popup/App.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { browser } from 'wxt/browser'
|
||||
import logo from '/mpmd/logo.svg'
|
||||
|
||||
function onOpenOption() {
|
||||
browser.runtime.openOptionsPage()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container popup-body">
|
||||
<div
|
||||
class="title"
|
||||
style="height: 40px; display: inline-flex; padding-left: 60px;"
|
||||
>
|
||||
<img style="height: 40px" :src="logo">
|
||||
<span
|
||||
style="
|
||||
font-size: 16px;
|
||||
line-height: 40px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
"
|
||||
>使用必读</span>
|
||||
</div>
|
||||
<section style="margin-top: 12px; line-height: 28px">
|
||||
<div>如果您希望使用微信公众号素材库作为图床功能,需要进行以下配置:</div>
|
||||
<div>
|
||||
1.开启公众号开发者模式
|
||||
<span><el-link
|
||||
type="primary"
|
||||
href="https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html"
|
||||
target="_blank"
|
||||
>查看文档</el-link></span>
|
||||
</div>
|
||||
<div>
|
||||
2.配置IP白名单<span><el-link type="primary" href="https://mpmd.pages.dev/tutorial" target="_blank">使用教程</el-link></span>
|
||||
</div>
|
||||
<div>
|
||||
<el-link type="primary" href="#" @click="onOpenOption">
|
||||
开始使用
|
||||
</el-link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.popup-body {
|
||||
min-width: 300px;
|
||||
scroll-behavior: auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
padding-bottom: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
13
src/entrypoints/popup/index.html
Normal file
13
src/entrypoints/popup/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MpMd编辑器</title>
|
||||
<meta name="manifest.type" content="browser_action" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./popup.ts"></script>
|
||||
</body>
|
||||
</html>
|
10
src/entrypoints/popup/popup.ts
Normal file
10
src/entrypoints/popup/popup.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
import 'virtual:uno.css'
|
||||
|
||||
/* 每个页面公共css */
|
||||
import '@/assets/index.css'
|
||||
import '@/assets/less/theme.less'
|
||||
|
||||
createApp(App).mount(`#app`)
|
228
src/modules/build-extension.ts
Normal file
228
src/modules/build-extension.ts
Normal file
@ -0,0 +1,228 @@
|
||||
import type * as vite from 'vite'
|
||||
import type * as wxt from 'wxt'
|
||||
import { writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { parseHTML } from 'linkedom'
|
||||
import { murmurHash } from 'ohash'
|
||||
import {
|
||||
addViteConfig,
|
||||
defineWxtModule,
|
||||
} from 'wxt/modules'
|
||||
|
||||
export default defineWxtModule({
|
||||
async setup(wxt) {
|
||||
wxt.config.alias[`/src/main.ts`] = `./src/main.ts`
|
||||
wxt.config.manifest.options_page = `options.html`
|
||||
wxt.hook(`entrypoints:grouped`, (_, groups) => {
|
||||
groups.push([{
|
||||
type: `options`,
|
||||
name: `options`,
|
||||
options: { openInTab: true },
|
||||
inputPath: path.resolve(wxt.config.root, `./index.html`),
|
||||
outputDir: wxt.config.outDir,
|
||||
skipped: false,
|
||||
}])
|
||||
})
|
||||
|
||||
addViteConfig(wxt, () => ({
|
||||
plugins: [
|
||||
htmlScriptToVirtual(wxt.config, () => wxt.server),
|
||||
vueDevtoolsHack(wxt.config, () => wxt.server),
|
||||
wxt.config.command === `build`
|
||||
? htmlScriptToLocal(wxt)
|
||||
: undefined,
|
||||
],
|
||||
}))
|
||||
},
|
||||
})
|
||||
|
||||
// Stored outside the plugin to effect all instances of the htmlScriptToVirtual plugin.
|
||||
const inlineScriptContents: Record<number, string> = {}
|
||||
export function htmlScriptToVirtual(
|
||||
config: wxt.ResolvedConfig,
|
||||
getWxtDevServer: () => wxt.WxtDevServer | undefined,
|
||||
): vite.PluginOption {
|
||||
const virtualInlineScript = `virtual:md-inline-script`
|
||||
const resolvedVirtualInlineScript = `\0${virtualInlineScript}`
|
||||
|
||||
const server = getWxtDevServer?.()
|
||||
return [
|
||||
{
|
||||
name: `md:dev-html-prerender`,
|
||||
apply: `build`,
|
||||
async transform(code, id) {
|
||||
if (
|
||||
server == null
|
||||
|| !id.endsWith(`.html`)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const { document } = parseHTML(code)
|
||||
// Replace inline script with virtual module served via dev server.
|
||||
// Extension CSP blocks inline scripts, so that's why we're pulling them out.
|
||||
const promises: Promise<void>[] = []
|
||||
const inlineScripts = document.querySelectorAll(`script[src^=http]`)
|
||||
inlineScripts.forEach(async (script) => {
|
||||
promises.push(new Promise<void>((resolve) => {
|
||||
const url = script.getAttribute(`src`) ?? ``
|
||||
doFetch(url).then((textContent) => {
|
||||
const hash = murmurHash(textContent)
|
||||
inlineScriptContents[hash] = textContent
|
||||
script.setAttribute(`src`, `${server.origin}/@id/${virtualInlineScript}?${hash}`)
|
||||
if (script.hasAttribute(`id`)) {
|
||||
script.setAttribute(`type`, `module`)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}))
|
||||
})
|
||||
await Promise.all(promises)
|
||||
const newHtml = document.toString()
|
||||
config.logger.debug(`transform ${id}`)
|
||||
config.logger.debug(`Old HTML:\n${code}`)
|
||||
config.logger.debug(`New HTML:\n${newHtml}`)
|
||||
return newHtml
|
||||
},
|
||||
},
|
||||
{
|
||||
name: `md:virtualize-react-refresh`,
|
||||
apply: `serve`,
|
||||
resolveId(id) {
|
||||
// Resolve inline scripts
|
||||
if (id.startsWith(virtualInlineScript)) {
|
||||
return `\0${id}`
|
||||
}
|
||||
|
||||
// Ignore chunks during HTML file pre-rendering
|
||||
if (id.startsWith(`/chunks/`)) {
|
||||
return `\0noop`
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
// Resolve virtualized inline scripts
|
||||
if (id.startsWith(resolvedVirtualInlineScript)) {
|
||||
// id="virtual:md-inline-script?<hash>"
|
||||
const hash = Number(id.substring(id.indexOf(`?`) + 1))
|
||||
return inlineScriptContents[hash]
|
||||
}
|
||||
|
||||
// Ignore chunks during HTML file pre-rendering
|
||||
if (id === `\0noop`) {
|
||||
return ``
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function htmlScriptToLocal(
|
||||
wxt: wxt.Wxt,
|
||||
): vite.Plugin {
|
||||
return {
|
||||
name: `md:build-html-prerender`,
|
||||
apply: `build`,
|
||||
transformIndexHtml: {
|
||||
order: `pre`,
|
||||
async handler(html) {
|
||||
const { document } = parseHTML(html)
|
||||
const promises: Promise<void>[] = []
|
||||
const httpScripts = document.querySelectorAll(`script[src^=http]`)
|
||||
if (httpScripts.length > 0) {
|
||||
httpScripts.forEach(async (script) => {
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
promises.push(new Promise<void>(async (resolve) => {
|
||||
const url = script.getAttribute(`src`) ?? ``
|
||||
if (url?.startsWith(`http://localhost`)) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const textContent = await doFetch(url)
|
||||
const hash = murmurHash(textContent)
|
||||
const jsName = url.match(/\/([^/]+)\.js$/)?.[1] ?? `.js`
|
||||
const fileName = `${jsName.split(`.`)[0]}-${hash}.js`
|
||||
// write to file
|
||||
const outFile = path.resolve(wxt.config.outDir, `./${fileName}`)
|
||||
await writeFile(outFile, textContent, `utf8`)
|
||||
script.setAttribute(`src`, `/${fileName}`)
|
||||
// script.setAttribute(`type`, `module`)
|
||||
resolve()
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
// Replace inline script with virtual module served via dev server.
|
||||
// Extension CSP blocks inline scripts, so that's why we're pulling them
|
||||
// out.
|
||||
const inlineScripts = document.querySelectorAll(`script:not([src])`)
|
||||
if (inlineScripts.length > 0) {
|
||||
inlineScripts.forEach(async (script) => {
|
||||
promises.push(new Promise<void>(async (resolve) => {
|
||||
// Save the text content for later
|
||||
const textContent = script.textContent ?? ``
|
||||
const hash = murmurHash(textContent)
|
||||
const fileName = `md-inline-${hash}.js`
|
||||
// write to file
|
||||
const outFile = path.resolve(wxt.config.outDir, `./${fileName}`)
|
||||
await writeFile(outFile, textContent, `utf8`)
|
||||
// Replace unsafe inline script
|
||||
const virtualScript = document.createElement(`script`)
|
||||
// virtualScript.type = `module`
|
||||
virtualScript.src = `/${fileName}`
|
||||
script.replaceWith(virtualScript)
|
||||
resolve()
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
await Promise.all(promises)
|
||||
const newHtml = document.toString()
|
||||
wxt.config.logger.debug(`Old HTML:\n${html}`)
|
||||
wxt.config.logger.debug(`New HTML:\n${newHtml}`)
|
||||
return newHtml
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
export function vueDevtoolsHack(
|
||||
config: wxt.ResolvedConfig,
|
||||
getWxtDevServer: () => wxt.WxtDevServer | undefined,
|
||||
): vite.Plugin {
|
||||
const server = getWxtDevServer?.()
|
||||
return {
|
||||
name: `md:vue-devtools-hack`,
|
||||
apply: `build`,
|
||||
transformIndexHtml: {
|
||||
order: `post`,
|
||||
handler(html) {
|
||||
const { document } = parseHTML(html)
|
||||
const inlineScripts = document.querySelectorAll(`script[src^='/@id/virtual:']`)
|
||||
inlineScripts.forEach((script) => {
|
||||
const src = script.getAttribute(`src`)
|
||||
const newSrc = `${server?.origin}${src}`
|
||||
script.setAttribute(`src`, newSrc)
|
||||
})
|
||||
const newHtml = document.toString()
|
||||
config.logger.debug(`Old HTML:\n${html}`)
|
||||
config.logger.debug(`New HTML:\n${newHtml}`)
|
||||
|
||||
return newHtml
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function doFetch(
|
||||
url: string,
|
||||
): Promise<string> {
|
||||
let content: string = ``
|
||||
const res = await fetch(url)
|
||||
if (res.status < 300) {
|
||||
content = await res.text()
|
||||
}
|
||||
else {
|
||||
throw new Error(
|
||||
`Failed to fetch "${url}". `,
|
||||
)
|
||||
}
|
||||
return content
|
||||
}
|
@ -299,6 +299,73 @@ async function minioFileUpload(content: string, filename: string) {
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// mp File Upload
|
||||
// -----------------------------------------------------------------------
|
||||
interface MpResponse {
|
||||
access_token: string
|
||||
expires_in: number
|
||||
errcode: number
|
||||
errmsg: string
|
||||
}
|
||||
async function getMpToken(appID: string, appsecret: string) {
|
||||
const data = localStorage.getItem(`mpToken:${appID}`)
|
||||
if (data) {
|
||||
const token = JSON.parse(data)
|
||||
if (token.expire && token.expire > new Date().getTime()) {
|
||||
return token.access_token
|
||||
}
|
||||
}
|
||||
const requestOptions = {
|
||||
method: `POST`,
|
||||
data: {
|
||||
grant_type: `client_credential`,
|
||||
appid: appID,
|
||||
secret: appsecret,
|
||||
},
|
||||
}
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/stable_token`
|
||||
const res = await fetch<any, MpResponse>(url, requestOptions)
|
||||
if (res.access_token) {
|
||||
const tokenInfo = {
|
||||
...res,
|
||||
expire: new Date().getTime() + res.expires_in * 1000,
|
||||
}
|
||||
localStorage.setItem(`mpToken:${appID}`, JSON.stringify(tokenInfo))
|
||||
return res.access_token
|
||||
}
|
||||
return ``
|
||||
}
|
||||
async function mpFileUpload(file: File) {
|
||||
const { appID, appsecret } = JSON.parse(
|
||||
localStorage.getItem(`mpConfig`)!,
|
||||
)
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
try {
|
||||
const access_token = await getMpToken(appID, appsecret).catch(e => console.error(e))
|
||||
if (!access_token) {
|
||||
reject(new Error(`获取 access_token 失败,请检查console日志`))
|
||||
return
|
||||
}
|
||||
const formdata = new FormData()
|
||||
formdata.append(`media`, file, file.name)
|
||||
const requestOptions = {
|
||||
method: `POST`,
|
||||
data: formdata,
|
||||
}
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${access_token}&type=image`
|
||||
const res = await fetch<any, {
|
||||
url: string
|
||||
}>(url, requestOptions)
|
||||
resolve(res.url)
|
||||
}
|
||||
catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// formCustom File Upload
|
||||
// -----------------------------------------------------------------------
|
||||
@ -354,6 +421,8 @@ function fileUpload(content: string, file: File) {
|
||||
return giteeUpload(content, file.name)
|
||||
case `github`:
|
||||
return ghFileUpload(content, file.name)
|
||||
case `mp`:
|
||||
return mpFileUpload(file)
|
||||
case `formCustom`:
|
||||
return formCustomUpload(content, file)
|
||||
default:
|
||||
|
37
wxt.config.ts
Normal file
37
wxt.config.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { defineConfig } from 'wxt'
|
||||
import ViteConfig from './vite.config'
|
||||
|
||||
export default defineConfig({
|
||||
srcDir: `src`,
|
||||
publicDir: `../public`,
|
||||
extensionApi: `chrome`,
|
||||
manifest: {
|
||||
name: `公众号文章编辑器`,
|
||||
version: `0.0.6`,
|
||||
icons: {
|
||||
256: `/mpmd/icon-256.png`,
|
||||
},
|
||||
permissions: [`storage`],
|
||||
host_permissions: [
|
||||
`https://*.github.com/*`,
|
||||
`https://*.githubusercontent.com/*`,
|
||||
`https://*.gitee.com/*`,
|
||||
`https://*.weixin.qq.com/*`,
|
||||
// 微信公众号图片
|
||||
`https://*.qpic.cn/*`,
|
||||
],
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: [`*.png`, `*.svg`],
|
||||
matches: [`<all_urls>`],
|
||||
},
|
||||
],
|
||||
},
|
||||
analysis: {
|
||||
open: true,
|
||||
},
|
||||
vite: () => ({
|
||||
...ViteConfig,
|
||||
base: `/`,
|
||||
}),
|
||||
})
|
Loading…
Reference in New Issue
Block a user