mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-09-03 06:36:46 +08:00
feat(extension): 浏览器插件 P1 MVP
新建 BillNote_extension/ 工作空间(基于 vitesse-webext 骨架,Vue 3 + Vite + UnoCSS + MV3)。 P1 MVP 范围: - popup:自动读当前 tab URL,识别 Bilibili / YouTube / 抖音 / 快手;提交 /generate_note 后轮询 /task_status;展示 markdown,复制 + 下载 .md - options:后端地址输入与连通性测试;从 /get_all_providers + /get_models_by_provider 拉供应商/模型列表;默认画质、截图/跳转、笔记风格 - chrome.storage.local 持久化设置与最近 30 个任务,popup 重开恢复进行中任务 - markdown 里的 /static/screenshots 路径在渲染前重写为绝对地址 后端:CORS 改用 regex,新增允许 chrome-extension:// 与 moz-extension:// 源(同时保留 localhost / 127.0.0.1 / tauri.localhost)。无新增 backend endpoint。 P2-P4(content script 悬浮按钮、cookie 直通、side panel、思维导图、RAG 问答)保留 stub 文件,不在本次范围。 去掉 vitesse-webext 自带的 simple-git-hooks postinstall 配置——它会在仓库根装 pre-commit 钩子去跑 pnpm lint-staged,但仓库根没有 package.json,会破坏所有提交流。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
108ad270bf
commit
b8f359e7e7
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<a class="icon-btn mx-2 text-2xl" rel="noreferrer" href="https://github.com/antfu/vitesse-webext" target="_blank" title="GitHub">
|
||||
<pixelarticons-power />
|
||||
</a>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import { absolutizeMarkdownImages } from '~/logic/api'
|
||||
|
||||
const props = defineProps<{ markdown: string, title?: string }>()
|
||||
|
||||
const md = new MarkdownIt({ html: false, linkify: true, breaks: true })
|
||||
|
||||
const html = computed(() => md.render(absolutizeMarkdownImages(props.markdown || '')))
|
||||
|
||||
async function copy() {
|
||||
await navigator.clipboard.writeText(props.markdown)
|
||||
}
|
||||
|
||||
function download() {
|
||||
const blob = new Blob([props.markdown], { type: 'text/markdown;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${props.title || 'bilinote'}.md`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button class="btn-secondary" @click="copy">复制 Markdown</button>
|
||||
<button class="btn-secondary" @click="download">下载 .md</button>
|
||||
</div>
|
||||
<div class="prose prose-sm max-w-none border rounded p-3 bg-gray-50 max-h-[400px] overflow-auto" v-html="html" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.prose img { max-width: 100%; }
|
||||
.prose h1, .prose h2, .prose h3 { font-weight: 600; margin-top: 0.8em; margin-bottom: 0.4em; }
|
||||
.prose p { margin-bottom: 0.5em; line-height: 1.55; }
|
||||
.prose ul, .prose ol { padding-left: 1.4em; margin-bottom: 0.5em; }
|
||||
.prose code { background: #eee; padding: 0 4px; border-radius: 3px; font-size: 0.9em; }
|
||||
.prose a { color: #2563eb; text-decoration: underline; }
|
||||
</style>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { Platform } from '~/logic/types'
|
||||
import { PLATFORM_LABELS } from '~/logic/platform'
|
||||
|
||||
const props = defineProps<{ platform: Platform | null }>()
|
||||
|
||||
const colorMap: Record<Platform, string> = {
|
||||
bilibili: 'bg-pink-100 text-pink-700',
|
||||
youtube: 'bg-red-100 text-red-700',
|
||||
douyin: 'bg-zinc-200 text-zinc-800',
|
||||
kuaishou: 'bg-orange-100 text-orange-700',
|
||||
local: 'bg-gray-100 text-gray-600',
|
||||
}
|
||||
|
||||
const cls = computed(() => (props.platform ? colorMap[props.platform] : 'bg-gray-100 text-gray-500'))
|
||||
const label = computed(() => (props.platform ? PLATFORM_LABELS[props.platform] : '未识别'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium" :class="cls">
|
||||
{{ label }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
## Components
|
||||
|
||||
Components in this dir will be auto-registered and on-demand, powered by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components).
|
||||
|
||||
Components can be shared in all views.
|
||||
|
||||
### Icons
|
||||
|
||||
You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/).
|
||||
|
||||
It will only bundle the icons you use. Check out [unplugin-icons](https://github.com/unplugin/unplugin-icons) for more details.
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<p class="mt-2 opacity-50">
|
||||
This is the {{ $app.context }} page
|
||||
</p>
|
||||
</template>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { TaskStatus } from '~/logic/types'
|
||||
|
||||
const props = defineProps<{ status: TaskStatus, message?: string }>()
|
||||
|
||||
const STAGE_ORDER: TaskStatus[] = ['PENDING', 'PARSING', 'DOWNLOADING', 'TRANSCRIBING', 'SUMMARIZING', 'FORMATTING', 'SAVING', 'SUCCESS']
|
||||
const STAGE_LABELS: Record<TaskStatus, string> = {
|
||||
PENDING: '排队中',
|
||||
PARSING: '解析中',
|
||||
DOWNLOADING: '下载中',
|
||||
TRANSCRIBING: '转写中',
|
||||
SUMMARIZING: '总结中',
|
||||
FORMATTING: '格式化',
|
||||
SAVING: '保存中',
|
||||
SUCCESS: '完成',
|
||||
FAILED: '失败',
|
||||
}
|
||||
|
||||
const currentIdx = computed(() => STAGE_ORDER.indexOf(props.status))
|
||||
const isFailed = computed(() => props.status === 'FAILED')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span :class="isFailed ? 'text-red-600' : 'text-blue-600'" class="font-medium">
|
||||
{{ STAGE_LABELS[status] }}
|
||||
</span>
|
||||
<span v-if="message" class="text-gray-500 text-xs truncate">{{ message }}</span>
|
||||
</div>
|
||||
<div v-if="!isFailed" class="flex gap-1">
|
||||
<div
|
||||
v-for="(s, i) in STAGE_ORDER"
|
||||
:key="s"
|
||||
class="h-1 flex-1 rounded-full"
|
||||
:class="i <= currentIdx ? 'bg-blue-500' : 'bg-gray-200'"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="h-1 rounded-full bg-red-500" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Logo from '../Logo.vue'
|
||||
|
||||
describe('logo component', () => {
|
||||
it('should render', () => {
|
||||
const wrapper = mount(Logo)
|
||||
|
||||
expect(wrapper.html()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user