feat: 插件图标规范化——manifest/胶囊 icon 统一 lucide 图标名(白名单 101 个),未知名回落拼图图标;内联 svg 仅旧兼容
This commit is contained in:
@@ -12,7 +12,8 @@ import { SettingsModal } from './components/SettingsModal'
|
|||||||
import { Search } from './components/Search'
|
import { Search } from './components/Search'
|
||||||
import { BrowserPanel } from './components/browser/BrowserPanel'
|
import { BrowserPanel } from './components/browser/BrowserPanel'
|
||||||
import { SidePanel, type PanelTabIcon } from './components/SidePanel'
|
import { SidePanel, type PanelTabIcon } from './components/SidePanel'
|
||||||
import { PluginPageFrame, PluginIcon } from './plugins/PluginPageFrame'
|
import { PluginPageFrame } from './plugins/PluginPageFrame'
|
||||||
|
import { PluginIconValue } from './plugins/plugin-icons'
|
||||||
import {
|
import {
|
||||||
useExternalPluginComponents,
|
useExternalPluginComponents,
|
||||||
usePluginRendererLoader,
|
usePluginRendererLoader,
|
||||||
@@ -316,9 +317,9 @@ export default function App(): JSX.Element {
|
|||||||
const pluginTabIcons = useMemo(() => {
|
const pluginTabIcons = useMemo(() => {
|
||||||
const map = new Map<string, PanelTabIcon>()
|
const map = new Map<string, PanelTabIcon>()
|
||||||
for (const p of panelPlugins) {
|
for (const p of panelPlugins) {
|
||||||
const svg = p.manifest.icon
|
const icon = p.manifest.icon
|
||||||
const Icon = ({ size, className }: { size?: number; className?: string }): JSX.Element => (
|
const Icon = ({ size, className }: { size?: number; className?: string }): JSX.Element => (
|
||||||
<PluginIcon svg={svg} size={size ?? 16} className={className} />
|
<PluginIconValue icon={icon} size={size ?? 16} className={className} />
|
||||||
)
|
)
|
||||||
map.set(p.manifest.id, Icon)
|
map.set(p.manifest.id, Icon)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import type {
|
|||||||
import type { PluginSettingsSchema, PluginSettingsSchemas, PluginSettingValues, PluginSettingsValuesMap } from '@aso/plugin-sdk'
|
import type { PluginSettingsSchema, PluginSettingsSchemas, PluginSettingValues, PluginSettingsValuesMap } from '@aso/plugin-sdk'
|
||||||
import { THEME_MODES, THEME_PRESETS, applyThemeColor } from '../lib/theme'
|
import { THEME_MODES, THEME_PRESETS, applyThemeColor } from '../lib/theme'
|
||||||
import { usePluginList } from '../plugins/store'
|
import { usePluginList } from '../plugins/store'
|
||||||
import { PluginIcon } from '../plugins/PluginPageFrame'
|
import { PluginIconValue } from '../plugins/plugin-icons'
|
||||||
|
|
||||||
type PluginSettingsTab = `plugin:${string}`
|
type PluginSettingsTab = `plugin:${string}`
|
||||||
type Tab = 'general' | 'model' | 'prompt' | 'plugins' | 'skills' | 'commands' | PluginSettingsTab
|
type Tab = 'general' | 'model' | 'prompt' | 'plugins' | 'skills' | 'commands' | PluginSettingsTab
|
||||||
@@ -818,12 +818,16 @@ function PluginsPanel({
|
|||||||
|
|
||||||
// 插件设置导航图标(svg 字符串 → 组件;按 id+svg 缓存保证身份稳定,避免导航项重挂载)
|
// 插件设置导航图标(svg 字符串 → 组件;按 id+svg 缓存保证身份稳定,避免导航项重挂载)
|
||||||
const pluginSettingsIconCache = new Map<string, IconType>()
|
const pluginSettingsIconCache = new Map<string, IconType>()
|
||||||
function pluginSettingsIcon(id: string, svg: string | undefined): IconType {
|
function pluginSettingsIcon(id: string, icon: string | undefined): IconType {
|
||||||
const key = `${id}:${svg ?? ''}`
|
const key = `${id}:${icon ?? ''}`
|
||||||
const cached = pluginSettingsIconCache.get(key)
|
const cached = pluginSettingsIconCache.get(key)
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
const Icon: IconType = (props) => (
|
const Icon: IconType = (props) => (
|
||||||
<PluginIcon svg={svg} size={typeof props.size === 'number' ? props.size : 16} className={props.className} />
|
<PluginIconValue
|
||||||
|
icon={icon}
|
||||||
|
size={typeof props.size === 'number' ? props.size : 16}
|
||||||
|
className={props.className}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
pluginSettingsIconCache.set(key, Icon)
|
pluginSettingsIconCache.set(key, Icon)
|
||||||
return Icon
|
return Icon
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
import { LuPanelLeft } from 'react-icons/lu'
|
import { LuPanelLeft } from 'react-icons/lu'
|
||||||
import { Button } from '@/components/atoms/Button'
|
import { Button } from '@/components/atoms/Button'
|
||||||
import { GlideMenu } from '@/components/GlideMenu'
|
import { GlideMenu } from '@/components/GlideMenu'
|
||||||
import { PluginIcon } from '@/plugins/PluginPageFrame'
|
import { PluginIconValue } from '@/plugins/plugin-icons'
|
||||||
import type { PluginInfo, SessionMeta, SessionState, WorkspaceMeta } from '@shared/types'
|
import type { PluginInfo, SessionMeta, SessionState, WorkspaceMeta } from '@shared/types'
|
||||||
|
|
||||||
/** 可折叠工作区导航(beautifului.dev Sidebar Nav)。
|
/** 可折叠工作区导航(beautifului.dev Sidebar Nav)。
|
||||||
@@ -445,7 +445,7 @@ export function SidebarNav({
|
|||||||
className={`${navRowClass} ${active ? 'bg-hover-2 text-ink' : ''}`}
|
className={`${navRowClass} ${active ? 'bg-hover-2 text-ink' : ''}`}
|
||||||
>
|
>
|
||||||
<span className="flex size-5 shrink-0 items-center justify-center">
|
<span className="flex size-5 shrink-0 items-center justify-center">
|
||||||
<PluginIcon svg={plugin.manifest.icon} size={18} />
|
<PluginIconValue icon={plugin.manifest.icon} size={18} />
|
||||||
</span>
|
</span>
|
||||||
<span className="sidebar-copy ml-1.5 min-w-0 flex-1 truncate text-[13px] font-medium text-ink-3">
|
<span className="sidebar-copy ml-1.5 min-w-0 flex-1 truncate text-[13px] font-medium text-ink-3">
|
||||||
{plugin.manifest.name}
|
{plugin.manifest.name}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import type { PluginStatusEntry } from '@shared/types'
|
import type { PluginStatusEntry } from '@shared/types'
|
||||||
import { PluginIcon } from './PluginPageFrame'
|
import { PluginIconValue } from './plugin-icons'
|
||||||
|
|
||||||
const TONE_CLASS: Record<string, string> = {
|
const TONE_CLASS: Record<string, string> = {
|
||||||
default: 'border-line text-ink-2',
|
default: 'border-line text-ink-2',
|
||||||
@@ -39,7 +39,7 @@ function StatusPopover({ entry, onOpenView }: { entry: PluginStatusEntry; onOpen
|
|||||||
const clickable = Boolean(row.openView)
|
const clickable = Boolean(row.openView)
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
{row.icon && <PluginIcon svg={row.icon} size={14} className="shrink-0 text-ink-2" />}
|
{row.icon && <PluginIconValue icon={row.icon} size={14} className="shrink-0 text-ink-2" />}
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block truncate text-[12px] leading-4 text-ink">{row.label}</span>
|
<span className="block truncate text-[12px] leading-4 text-ink">{row.label}</span>
|
||||||
{row.detail && <span className="mt-0.5 block truncate text-[11px] leading-4 text-ink-3">{row.detail}</span>}
|
{row.detail && <span className="mt-0.5 block truncate text-[11px] leading-4 text-ink-3">{row.detail}</span>}
|
||||||
@@ -130,7 +130,7 @@ export function PluginStatusCapsules({
|
|||||||
TONE_CLASS[tone] ?? TONE_CLASS.default
|
TONE_CLASS[tone] ?? TONE_CLASS.default
|
||||||
} ${clickable ? 'hover:opacity-80 active:scale-[0.97]' : 'cursor-default'} ${open ? 'opacity-80' : ''}`}
|
} ${clickable ? 'hover:opacity-80 active:scale-[0.97]' : 'cursor-default'} ${open ? 'opacity-80' : ''}`}
|
||||||
>
|
>
|
||||||
{item.icon && <PluginIcon svg={item.icon} size={12} className="shrink-0" />}
|
{item.icon && <PluginIconValue icon={item.icon} size={12} className="shrink-0" />}
|
||||||
<span className="truncate">{item.text}</span>
|
<span className="truncate">{item.text}</span>
|
||||||
</button>
|
</button>
|
||||||
{open && item.detail && <StatusPopover entry={{ pluginId, itemId, item }} onOpenView={onOpenView} />}
|
{open && item.detail && <StatusPopover entry={{ pluginId, itemId, item }} onOpenView={onOpenView} />}
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* 插件图标规范:manifest.icon / 状态胶囊 icon 统一填 **lucide 图标名**
|
||||||
|
* (PascalCase 如 "Globe",kebab-case "layout-grid" 亦可),宿主经 react-icons/lu
|
||||||
|
* 白名单渲染——线条风格、粗细、圆角与宿主完全一致(DESIGN.md 图标规范)。
|
||||||
|
* 旧版内联 `<svg>` 字符串仍兼容(sanitizeSvg 防线继续生效);
|
||||||
|
* 名单之外或缺失的名字一律回落拼图图标,杜绝来历不明的自绘图形。
|
||||||
|
*/
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import type { IconType } from 'react-icons'
|
||||||
|
import {
|
||||||
|
LuActivity,
|
||||||
|
LuAppWindow,
|
||||||
|
LuArchive,
|
||||||
|
LuAtom,
|
||||||
|
LuAward,
|
||||||
|
LuBadgeCheck,
|
||||||
|
LuBan,
|
||||||
|
LuBell,
|
||||||
|
LuBlocks,
|
||||||
|
LuBookOpen,
|
||||||
|
LuBot,
|
||||||
|
LuBoxes,
|
||||||
|
LuBriefcase,
|
||||||
|
LuCamera,
|
||||||
|
LuCalendar,
|
||||||
|
LuChartPie,
|
||||||
|
LuChartBar,
|
||||||
|
LuCheck,
|
||||||
|
LuCircleCheck,
|
||||||
|
LuCircle,
|
||||||
|
LuCircleDot,
|
||||||
|
LuClock,
|
||||||
|
LuCloud,
|
||||||
|
LuCode,
|
||||||
|
LuCompass,
|
||||||
|
LuCpu,
|
||||||
|
LuDatabase,
|
||||||
|
LuHardDrive,
|
||||||
|
LuDownload,
|
||||||
|
LuEye,
|
||||||
|
LuEyeOff,
|
||||||
|
LuFile,
|
||||||
|
LuFileText,
|
||||||
|
LuFilter,
|
||||||
|
LuFlag,
|
||||||
|
LuFolder,
|
||||||
|
LuFolderOpen,
|
||||||
|
LuGitBranch,
|
||||||
|
LuGitCommitHorizontal,
|
||||||
|
LuGithub,
|
||||||
|
LuGitPullRequest,
|
||||||
|
LuGlobe,
|
||||||
|
LuHammer,
|
||||||
|
LuHash,
|
||||||
|
LuHeadphones,
|
||||||
|
LuHouse,
|
||||||
|
LuImage,
|
||||||
|
LuInbox,
|
||||||
|
LuKeyboard,
|
||||||
|
LuLayers,
|
||||||
|
LuLayoutGrid,
|
||||||
|
LuLink,
|
||||||
|
LuListChecks,
|
||||||
|
LuListTodo,
|
||||||
|
LuLock,
|
||||||
|
LuMail,
|
||||||
|
LuMap,
|
||||||
|
LuMessageCircle,
|
||||||
|
LuMic,
|
||||||
|
LuMonitor,
|
||||||
|
LuMoon,
|
||||||
|
LuMusic,
|
||||||
|
LuPackage,
|
||||||
|
LuPalette,
|
||||||
|
LuPaperclip,
|
||||||
|
LuPenLine,
|
||||||
|
LuPhone,
|
||||||
|
LuPlay,
|
||||||
|
LuPlug,
|
||||||
|
LuPlugZap,
|
||||||
|
LuPuzzle,
|
||||||
|
LuRocket,
|
||||||
|
LuSave,
|
||||||
|
LuScan,
|
||||||
|
LuSearch,
|
||||||
|
LuServer,
|
||||||
|
LuSettings,
|
||||||
|
LuSettings2,
|
||||||
|
LuShare2,
|
||||||
|
LuShield,
|
||||||
|
LuShoppingCart,
|
||||||
|
LuSlidersHorizontal,
|
||||||
|
LuSparkles,
|
||||||
|
LuSquareStack,
|
||||||
|
LuStar,
|
||||||
|
LuSun,
|
||||||
|
LuTable,
|
||||||
|
LuTag,
|
||||||
|
LuTarget,
|
||||||
|
LuTerminal,
|
||||||
|
LuThumbsUp,
|
||||||
|
LuTimer,
|
||||||
|
LuTrash2,
|
||||||
|
LuTrendingUp,
|
||||||
|
LuUpload,
|
||||||
|
LuUser,
|
||||||
|
LuUsers,
|
||||||
|
LuVideo,
|
||||||
|
LuWallet,
|
||||||
|
LuWebhook,
|
||||||
|
LuWrench,
|
||||||
|
LuZap
|
||||||
|
} from 'react-icons/lu'
|
||||||
|
import { PluginIcon } from './PluginPageFrame'
|
||||||
|
|
||||||
|
/** 宿主核准的 lucide 图标白名单(manifest.icon / 胶囊 icon 可填的名字) */
|
||||||
|
export const PLUGIN_LUCIDE_ICONS = {
|
||||||
|
Activity: LuActivity,
|
||||||
|
AppWindow: LuAppWindow,
|
||||||
|
Archive: LuArchive,
|
||||||
|
Atom: LuAtom,
|
||||||
|
Award: LuAward,
|
||||||
|
BadgeCheck: LuBadgeCheck,
|
||||||
|
Ban: LuBan,
|
||||||
|
Bell: LuBell,
|
||||||
|
Blocks: LuBlocks,
|
||||||
|
BookOpen: LuBookOpen,
|
||||||
|
Bot: LuBot,
|
||||||
|
Boxes: LuBoxes,
|
||||||
|
Briefcase: LuBriefcase,
|
||||||
|
Camera: LuCamera,
|
||||||
|
Calendar: LuCalendar,
|
||||||
|
ChartPie: LuChartPie,
|
||||||
|
ChartBar: LuChartBar,
|
||||||
|
Check: LuCheck,
|
||||||
|
CircleCheck: LuCircleCheck,
|
||||||
|
Circle: LuCircle,
|
||||||
|
CircleDot: LuCircleDot,
|
||||||
|
Clock: LuClock,
|
||||||
|
Cloud: LuCloud,
|
||||||
|
Code: LuCode,
|
||||||
|
Compass: LuCompass,
|
||||||
|
Cpu: LuCpu,
|
||||||
|
Database: LuDatabase,
|
||||||
|
HardDrive: LuHardDrive,
|
||||||
|
Download: LuDownload,
|
||||||
|
Eye: LuEye,
|
||||||
|
EyeOff: LuEyeOff,
|
||||||
|
File: LuFile,
|
||||||
|
FileText: LuFileText,
|
||||||
|
Filter: LuFilter,
|
||||||
|
Flag: LuFlag,
|
||||||
|
Folder: LuFolder,
|
||||||
|
FolderOpen: LuFolderOpen,
|
||||||
|
GitBranch: LuGitBranch,
|
||||||
|
GitCommit: LuGitCommitHorizontal,
|
||||||
|
Github: LuGithub,
|
||||||
|
GitPullRequest: LuGitPullRequest,
|
||||||
|
Globe: LuGlobe,
|
||||||
|
Hammer: LuHammer,
|
||||||
|
Hash: LuHash,
|
||||||
|
Headphones: LuHeadphones,
|
||||||
|
House: LuHouse,
|
||||||
|
Image: LuImage,
|
||||||
|
Inbox: LuInbox,
|
||||||
|
Keyboard: LuKeyboard,
|
||||||
|
Layers: LuLayers,
|
||||||
|
LayoutGrid: LuLayoutGrid,
|
||||||
|
Link: LuLink,
|
||||||
|
ListChecks: LuListChecks,
|
||||||
|
ListTodo: LuListTodo,
|
||||||
|
Lock: LuLock,
|
||||||
|
Mail: LuMail,
|
||||||
|
Map: LuMap,
|
||||||
|
MessageCircle: LuMessageCircle,
|
||||||
|
Mic: LuMic,
|
||||||
|
Monitor: LuMonitor,
|
||||||
|
Moon: LuMoon,
|
||||||
|
Music: LuMusic,
|
||||||
|
Package: LuPackage,
|
||||||
|
Palette: LuPalette,
|
||||||
|
Paperclip: LuPaperclip,
|
||||||
|
PenLine: LuPenLine,
|
||||||
|
Phone: LuPhone,
|
||||||
|
Play: LuPlay,
|
||||||
|
Plug: LuPlug,
|
||||||
|
PlugZap: LuPlugZap,
|
||||||
|
Puzzle: LuPuzzle,
|
||||||
|
Rocket: LuRocket,
|
||||||
|
Save: LuSave,
|
||||||
|
Scan: LuScan,
|
||||||
|
Search: LuSearch,
|
||||||
|
Server: LuServer,
|
||||||
|
Settings: LuSettings,
|
||||||
|
Settings2: LuSettings2,
|
||||||
|
Share2: LuShare2,
|
||||||
|
Shield: LuShield,
|
||||||
|
ShoppingCart: LuShoppingCart,
|
||||||
|
SlidersHorizontal: LuSlidersHorizontal,
|
||||||
|
Sparkles: LuSparkles,
|
||||||
|
SquareStack: LuSquareStack,
|
||||||
|
Star: LuStar,
|
||||||
|
Sun: LuSun,
|
||||||
|
Table: LuTable,
|
||||||
|
Tag: LuTag,
|
||||||
|
Target: LuTarget,
|
||||||
|
Terminal: LuTerminal,
|
||||||
|
ThumbsUp: LuThumbsUp,
|
||||||
|
Timer: LuTimer,
|
||||||
|
Trash2: LuTrash2,
|
||||||
|
TrendingUp: LuTrendingUp,
|
||||||
|
Upload: LuUpload,
|
||||||
|
User: LuUser,
|
||||||
|
Users: LuUsers,
|
||||||
|
Video: LuVideo,
|
||||||
|
Wallet: LuWallet,
|
||||||
|
Webhook: LuWebhook,
|
||||||
|
Wrench: LuWrench,
|
||||||
|
Zap: LuZap
|
||||||
|
} satisfies Record<string, IconType>
|
||||||
|
|
||||||
|
/** kebab/snake/全小写 → PascalCase;已是 Pascal 的名字原样通过 */
|
||||||
|
function toPascal(name: string): string {
|
||||||
|
return name
|
||||||
|
.trim()
|
||||||
|
.replace(/[-_\s]+(.)/g, (_, c: string) => c.toUpperCase())
|
||||||
|
.replace(/^./, (c) => c.toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按名字查白名单图标;未命中返回 null */
|
||||||
|
export function lucideIconByName(name: string): IconType | null {
|
||||||
|
const key = toPascal(name)
|
||||||
|
return (PLUGIN_LUCIDE_ICONS as Record<string, IconType>)[key] ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图标槽位统一渲染:lucide 名(规范用法)/ 内联 svg(旧兼容)/ 缺省与未知名回落拼图图标 */
|
||||||
|
export function PluginIconValue({ icon, size = 16, className }: { icon?: string; size?: number; className?: string }): ReactNode {
|
||||||
|
const v = icon?.trim()
|
||||||
|
if (v && v.startsWith('<svg')) return <PluginIcon svg={v} size={size} className={className} />
|
||||||
|
const Icon = (v ? lucideIconByName(v) : null) ?? LuPuzzle
|
||||||
|
return <Icon size={size} className={className} />
|
||||||
|
}
|
||||||
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
"version": "1.2.0", // semver(x.y.z)
|
"version": "1.2.0", // semver(x.y.z)
|
||||||
"minContract": "0.1.0",
|
"minContract": "0.1.0",
|
||||||
"description": "…",
|
"description": "…",
|
||||||
"icon": "<svg…/>", // 侧栏/面板图标(svg 字符串;宿主清除事件属性与 script)
|
"icon": "Globe", // 侧栏/面板/胶囊图标:lucide 图标名(白名单见 DESIGN.md;kebab 亦可);兼容旧 svg 字符串;未知名回落拼图图标
|
||||||
"order": 10, // 侧栏排序(只在插件间;核心入口恒在前)
|
"order": 10, // 侧栏排序(只在插件间;核心入口恒在前)
|
||||||
"panel": true, // 在右侧 SidePanel 出现一个 tab
|
"panel": true, // 在右侧 SidePanel 出现一个 tab
|
||||||
"entry": { "page": "index.html", "main": "main.mjs", "renderer": "renderer.mjs" }, // 内置插件省略 entry
|
"entry": { "page": "index.html", "main": "main.mjs", "renderer": "renderer.mjs" }, // 内置插件省略 entry
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
- 字号阶:`10px`(徽标)/ `11px`(辅助按钮)/ `11.5px`(次要按钮、说明)/ `12px`(正文说明) / `12.5px`(行标题/正文)/ `16px`(页面标题)。**不使用其他字号**;行高 `leading-4/5`。
|
- 字号阶:`10px`(徽标)/ `11px`(辅助按钮)/ `11.5px`(次要按钮、说明)/ `12px`(正文说明) / `12.5px`(行标题/正文)/ `16px`(页面标题)。**不使用其他字号**;行高 `leading-4/5`。
|
||||||
- 间距基数为 4px:卡片内边距 `px-3 py-2.5`,区块间 `space-y-1.5`,小节间 `pt-3`。
|
- 间距基数为 4px:卡片内边距 `px-3 py-2.5`,区块间 `space-y-1.5`,小节间 `pt-3`。
|
||||||
- 图标 12–16px,线条风格,`currentColor`。
|
- 图标一律 **lucide**(宿主白名单 `plugin-icons.tsx`,约 100 个常用名):`manifest.icon` / 状态胶囊 `icon` 填 lucide 图标名(如 `"Globe"`,kebab-case 亦可),12–16px、`currentColor` 由宿主保证;**禁止自绘 svg**(旧内联 svg 仅对存量插件兼容),未知名回落拼图图标
|
||||||
|
|
||||||
## 4. 标准模式(复制即用)
|
## 4. 标准模式(复制即用)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface PluginManifest {
|
|||||||
/** 最低 harness 契约版本(semver),不满足拒载 */
|
/** 最低 harness 契约版本(semver),不满足拒载 */
|
||||||
minContract: string
|
minContract: string
|
||||||
description?: string
|
description?: string
|
||||||
/** 侧栏入口图标:svg 字符串;缺省用宿主默认字形 */
|
/** 侧栏入口图标:lucide 图标名(如 "Globe",白名单见 docs/plugins/DESIGN.md);兼容旧内联 svg 字符串;缺省/未知名回落拼图图标 */
|
||||||
icon?: string
|
icon?: string
|
||||||
/** 侧栏入口排序(升序,只在插件之间排序;核心入口恒在前) */
|
/** 侧栏入口排序(升序,只在插件之间排序;核心入口恒在前) */
|
||||||
order?: number
|
order?: number
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"description": "TODO:一句话描述插件做什么",
|
"description": "TODO:一句话描述插件做什么",
|
||||||
"order": 50,
|
"order": 50,
|
||||||
"panel": true,
|
"panel": true,
|
||||||
"icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/></svg>",
|
"icon": "Puzzle",
|
||||||
"entry": {
|
"entry": {
|
||||||
"main": "main.mjs",
|
"main": "main.mjs",
|
||||||
"renderer": "renderer.mjs"
|
"renderer": "renderer.mjs"
|
||||||
|
|||||||
Reference in New Issue
Block a user