✨ Improve extension management
This commit is contained in:
@@ -1,368 +0,0 @@
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
||||
import './styles.css';
|
||||
|
||||
// 粒子接口定义
|
||||
interface Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
alpha: number;
|
||||
size: number;
|
||||
color: number[];
|
||||
theta?: number;
|
||||
drag?: number;
|
||||
wander?: number;
|
||||
}
|
||||
|
||||
// 配置接口
|
||||
interface CodeBlastConfig {
|
||||
effect?: 1 | 2; // effect 1: 随机粒子, effect 2: 追逐粒子
|
||||
shake?: boolean; // 启用震动效果
|
||||
maxParticles?: number; // 最大粒子数
|
||||
particleRange?: { min: number; max: number }; // 粒子大小范围
|
||||
shakeIntensity?: number; // 震动强度
|
||||
gravity?: number; // 重力加速度 (仅 effect: 1)
|
||||
alphaFadeout?: number; // 粒子透明度衰减
|
||||
velocityRange?: { // 粒子速度范围
|
||||
x: [number, number]; // x轴方向速度范围
|
||||
y: [number, number]; // y轴方向速度范围
|
||||
};
|
||||
}
|
||||
|
||||
class CodeBlastEffect {
|
||||
private canvas: HTMLCanvasElement | null = null;
|
||||
private ctx: CanvasRenderingContext2D | null = null;
|
||||
private particles: Particle[] = [];
|
||||
private particlePointer = 0;
|
||||
private isActive = false;
|
||||
private lastTime = 0;
|
||||
private shakeTime = 0;
|
||||
private shakeTimeMax = 0;
|
||||
private animationId: number | null = null;
|
||||
|
||||
// 配置参数
|
||||
private config: Required<CodeBlastConfig> = {
|
||||
effect: 2,
|
||||
shake: true,
|
||||
maxParticles: 500,
|
||||
particleRange: { min: 5, max: 10 },
|
||||
shakeIntensity: 5,
|
||||
gravity: 0.08,
|
||||
alphaFadeout: 0.96,
|
||||
velocityRange: {
|
||||
x: [-1, 1],
|
||||
y: [-3.5, -1.5]
|
||||
}
|
||||
};
|
||||
|
||||
constructor(config?: CodeBlastConfig) {
|
||||
if (config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
}
|
||||
this.particles = new Array(this.config.maxParticles);
|
||||
}
|
||||
|
||||
private getRGBComponents(element: Element): number[] {
|
||||
try {
|
||||
const style = getComputedStyle(element);
|
||||
const color = style.color;
|
||||
if (color) {
|
||||
const match = color.match(/(\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (match) {
|
||||
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to get RGB components:', e);
|
||||
}
|
||||
return [255, 255, 255]; // 默认白色
|
||||
}
|
||||
|
||||
private random(min: number, max?: number): number {
|
||||
if (max === undefined) {
|
||||
max = min;
|
||||
min = 0;
|
||||
}
|
||||
return min + Math.floor(Math.random() * (max - min + 1));
|
||||
}
|
||||
|
||||
private createParticle(x: number, y: number, color: number[]): Particle {
|
||||
const particle: Particle = {
|
||||
x,
|
||||
y: y + 10,
|
||||
alpha: 1,
|
||||
color,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
size: 0
|
||||
};
|
||||
|
||||
if (this.config.effect === 1) {
|
||||
particle.size = this.random(2, 4);
|
||||
particle.vx = this.config.velocityRange.x[0] +
|
||||
Math.random() * (this.config.velocityRange.x[1] - this.config.velocityRange.x[0]);
|
||||
particle.vy = this.config.velocityRange.y[0] +
|
||||
Math.random() * (this.config.velocityRange.y[1] - this.config.velocityRange.y[0]);
|
||||
} else if (this.config.effect === 2) {
|
||||
particle.size = this.random(2, 8);
|
||||
particle.drag = 0.92;
|
||||
particle.vx = this.random(-3, 3);
|
||||
particle.vy = this.random(-3, 3);
|
||||
particle.wander = 0.15;
|
||||
particle.theta = this.random(0, 360) * Math.PI / 180;
|
||||
}
|
||||
|
||||
return particle;
|
||||
}
|
||||
|
||||
private spawnParticles(view: EditorView): void {
|
||||
if (!this.ctx) return;
|
||||
|
||||
try {
|
||||
// 获取光标位置
|
||||
const selection = view.state.selection.main;
|
||||
const coords = view.coordsAtPos(selection.head);
|
||||
if (!coords) return;
|
||||
|
||||
// 获取光标处的元素来确定颜色
|
||||
const element = document.elementFromPoint(coords.left, coords.top);
|
||||
const color = element ? this.getRGBComponents(element) : [255, 255, 255];
|
||||
|
||||
const numParticles = this.random(
|
||||
this.config.particleRange.min,
|
||||
this.config.particleRange.max
|
||||
);
|
||||
|
||||
for (let i = 0; i < numParticles; i++) {
|
||||
this.particles[this.particlePointer] = this.createParticle(
|
||||
coords.left + 10,
|
||||
coords.top,
|
||||
color
|
||||
);
|
||||
this.particlePointer = (this.particlePointer + 1) % this.config.maxParticles;
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果在更新期间无法读取坐标,静默忽略
|
||||
console.warn('Failed to spawn particles:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private effect1(particle: Particle): void {
|
||||
if (!this.ctx) return;
|
||||
|
||||
particle.vy += this.config.gravity;
|
||||
particle.x += particle.vx;
|
||||
particle.y += particle.vy;
|
||||
particle.alpha *= this.config.alphaFadeout;
|
||||
|
||||
this.ctx.fillStyle = `rgba(${particle.color[0]}, ${particle.color[1]}, ${particle.color[2]}, ${particle.alpha})`;
|
||||
this.ctx.fillRect(
|
||||
Math.round(particle.x - 1),
|
||||
Math.round(particle.y - 1),
|
||||
particle.size,
|
||||
particle.size
|
||||
);
|
||||
}
|
||||
|
||||
private effect2(particle: Particle): void {
|
||||
if (!this.ctx || particle.theta === undefined || particle.drag === undefined) return;
|
||||
|
||||
particle.x += particle.vx;
|
||||
particle.y += particle.vy;
|
||||
particle.vx *= particle.drag;
|
||||
particle.vy *= particle.drag;
|
||||
particle.theta += this.random(-0.5, 0.5);
|
||||
particle.vx += Math.sin(particle.theta) * 0.1;
|
||||
particle.vy += Math.cos(particle.theta) * 0.1;
|
||||
particle.size *= 0.96;
|
||||
|
||||
this.ctx.fillStyle = `rgba(${particle.color[0]}, ${particle.color[1]}, ${particle.color[2]}, ${particle.alpha})`;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(
|
||||
Math.round(particle.x - 1),
|
||||
Math.round(particle.y - 1),
|
||||
particle.size,
|
||||
0,
|
||||
2 * Math.PI
|
||||
);
|
||||
this.ctx.fill();
|
||||
}
|
||||
|
||||
private drawParticles(): void {
|
||||
if (!this.ctx) return;
|
||||
|
||||
for (let i = 0; i < this.particles.length; i++) {
|
||||
const particle = this.particles[i];
|
||||
if (!particle || particle.alpha < 0.01 || particle.size <= 0.5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.config.effect === 1) {
|
||||
this.effect1(particle);
|
||||
} else if (this.config.effect === 2) {
|
||||
this.effect2(particle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private shake(view: EditorView, duration: number): void {
|
||||
if (!this.config.shake) return;
|
||||
|
||||
this.shakeTime = this.shakeTimeMax = duration;
|
||||
const editorElement = view.dom;
|
||||
|
||||
const shakeAnimation = () => {
|
||||
if (this.shakeTime <= 0) {
|
||||
editorElement.style.transform = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const magnitude = (this.shakeTime / this.shakeTimeMax) * this.config.shakeIntensity;
|
||||
const shakeX = this.random(-magnitude, magnitude);
|
||||
const shakeY = this.random(-magnitude, magnitude);
|
||||
editorElement.style.transform = `translate(${shakeX}px, ${shakeY}px)`;
|
||||
|
||||
this.shakeTime -= 0.016; // ~60fps
|
||||
requestAnimationFrame(shakeAnimation);
|
||||
};
|
||||
|
||||
requestAnimationFrame(shakeAnimation);
|
||||
}
|
||||
|
||||
private loop = (): void => {
|
||||
if (!this.isActive || !this.ctx || !this.canvas) return;
|
||||
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.drawParticles();
|
||||
this.animationId = requestAnimationFrame(this.loop);
|
||||
};
|
||||
|
||||
public init(view: EditorView): void {
|
||||
if (this.isActive) return;
|
||||
|
||||
this.isActive = true;
|
||||
|
||||
if (!this.canvas) {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
|
||||
if (!this.ctx) {
|
||||
console.error('Failed to get canvas context');
|
||||
return;
|
||||
}
|
||||
|
||||
this.canvas.id = 'code-blast-canvas';
|
||||
this.canvas.style.position = 'absolute';
|
||||
this.canvas.style.top = '0';
|
||||
this.canvas.style.left = '0';
|
||||
this.canvas.style.zIndex = '1';
|
||||
this.canvas.style.pointerEvents = 'none';
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
|
||||
document.body.appendChild(this.canvas);
|
||||
this.loop();
|
||||
}
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.isActive = false;
|
||||
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
|
||||
if (this.canvas) {
|
||||
this.canvas.remove();
|
||||
this.canvas = null;
|
||||
this.ctx = null;
|
||||
}
|
||||
}
|
||||
|
||||
public onDocumentChange(view: EditorView): void {
|
||||
if (this.config.shake) {
|
||||
this.shake(view, 0.3);
|
||||
}
|
||||
// 使用 requestIdleCallback 或 setTimeout 延迟执行粒子生成
|
||||
// 确保在 DOM 更新完成后再读取坐标
|
||||
if (window.requestIdleCallback) {
|
||||
window.requestIdleCallback(() => {
|
||||
this.spawnParticles(view);
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.spawnParticles(view);
|
||||
}, 16); // ~60fps
|
||||
}
|
||||
}
|
||||
|
||||
public updateCanvasSize(): void {
|
||||
if (this.canvas) {
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 节流函数
|
||||
function throttle<T extends (...args: any[]) => void>(
|
||||
func: T,
|
||||
limit: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let inThrottle: boolean;
|
||||
return function(this: any, ...args: Parameters<T>) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 创建 CodeBlast 扩展
|
||||
export function createCodeBlastExtension(config?: CodeBlastConfig): Extension {
|
||||
let effect: CodeBlastEffect | null = null;
|
||||
|
||||
const plugin = ViewPlugin.fromClass(class {
|
||||
private throttledOnChange: (view: EditorView) => void;
|
||||
|
||||
constructor(private view: EditorView) {
|
||||
effect = new CodeBlastEffect(config);
|
||||
effect.init(view);
|
||||
|
||||
this.throttledOnChange = throttle((view: EditorView) => {
|
||||
effect?.onDocumentChange(view);
|
||||
}, 100);
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (update.docChanged) {
|
||||
// 延迟执行,确保更新完成后再触发效果
|
||||
setTimeout(() => {
|
||||
this.throttledOnChange(this.view);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
effect?.destroy();
|
||||
effect = null;
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
private handleResize = () => {
|
||||
effect?.updateCanvasSize();
|
||||
};
|
||||
});
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
// 默认导出
|
||||
export const codeBlast = createCodeBlastExtension();
|
||||
@@ -1,52 +0,0 @@
|
||||
#code-blast-canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 确保编辑器在震动时不会影响布局 */
|
||||
.cm-editor {
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
/* 打字机效果的粒子样式 */
|
||||
.code-blast-particle {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-radius: 50%;
|
||||
animation: particle-fade 2s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes particle-fade {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* 震动效果的缓动 */
|
||||
.code-blast-shake {
|
||||
animation: shake 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translate(0, 0); }
|
||||
10% { transform: translate(-2px, -1px); }
|
||||
20% { transform: translate(2px, 1px); }
|
||||
30% { transform: translate(-1px, 2px); }
|
||||
40% { transform: translate(1px, -2px); }
|
||||
50% { transform: translate(-2px, 1px); }
|
||||
60% { transform: translate(2px, -1px); }
|
||||
70% { transform: translate(-1px, -2px); }
|
||||
80% { transform: translate(1px, 2px); }
|
||||
90% { transform: translate(-2px, -1px); }
|
||||
}
|
||||
@@ -122,6 +122,14 @@ const minimapClass = ViewPlugin.fromClass(
|
||||
}
|
||||
|
||||
if (now) {
|
||||
if (prev && this.dom && prev.autohide !== now.autohide) {
|
||||
if (now.autohide) {
|
||||
this.dom.classList.add('cm-minimap-autohide');
|
||||
} else {
|
||||
this.dom.classList.remove('cm-minimap-autohide');
|
||||
}
|
||||
}
|
||||
|
||||
this.text.update(update);
|
||||
this.selection.update(update);
|
||||
this.diagnostic.update(update);
|
||||
@@ -279,7 +287,7 @@ const minimapClass = ViewPlugin.fromClass(
|
||||
}
|
||||
);
|
||||
|
||||
// 使用type定义,而不是interface
|
||||
// 使用type定义
|
||||
export type MinimapConfig = Omit<Options, "enabled"> & {
|
||||
/**
|
||||
* A function that creates the element that contains the minimap
|
||||
|
||||
@@ -47,6 +47,8 @@ export class ExtensionManager {
|
||||
private view: EditorView | null = null
|
||||
private compartments = new Map<ExtensionID, ExtensionCompartment>()
|
||||
private extensionFactories = new Map<ExtensionID, ExtensionFactory>()
|
||||
private updateQueue = new Map<ExtensionID, { enabled: boolean, config: any, timestamp: number }>()
|
||||
private debounceTimeout: number | null = null
|
||||
|
||||
/**
|
||||
* 注册扩展工厂
|
||||
@@ -110,7 +112,7 @@ export class ExtensionManager {
|
||||
compartmentInfo.currentConfig = config.config
|
||||
compartmentInfo.enabled = config.enabled
|
||||
} catch (error) {
|
||||
console.error(`Failed to create extension ${config.id}:`, error)
|
||||
console.error(`[ExtensionManager] Failed to create extension ${config.id}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,13 +128,33 @@ export class ExtensionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态更新单个扩展
|
||||
* 动态更新单个扩展(带防抖)
|
||||
* @param id 扩展ID
|
||||
* @param enabled 是否启用
|
||||
* @param config 扩展配置
|
||||
*/
|
||||
updateExtension(id: ExtensionID, enabled: boolean, config: any = {}): void {
|
||||
// 添加到更新队列
|
||||
this.updateQueue.set(id, {enabled, config, timestamp: Date.now()})
|
||||
|
||||
// 清除之前的防抖定时器
|
||||
if (this.debounceTimeout) {
|
||||
clearTimeout(this.debounceTimeout)
|
||||
}
|
||||
|
||||
// 设置新的防抖定时器
|
||||
this.debounceTimeout = window.setTimeout(() => {
|
||||
this.flushUpdateQueue()
|
||||
}, 100) // 100ms 防抖
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即更新扩展(无防抖)
|
||||
* @param id 扩展ID
|
||||
* @param enabled 是否启用
|
||||
* @param config 扩展配置
|
||||
*/
|
||||
updateExtensionImmediate(id: ExtensionID, enabled: boolean, config: any = {}): void {
|
||||
if (!this.view) {
|
||||
return
|
||||
}
|
||||
@@ -166,6 +188,52 @@ export class ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理更新队列中的所有更新
|
||||
*/
|
||||
private flushUpdateQueue(): void {
|
||||
if (!this.view) {
|
||||
return
|
||||
}
|
||||
|
||||
const effects: StateEffect<any>[] = []
|
||||
|
||||
for (const [id, update] of this.updateQueue) {
|
||||
const compartmentInfo = this.compartments.get(id)
|
||||
if (!compartmentInfo) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证配置
|
||||
if (compartmentInfo.factory.validateConfig &&
|
||||
!compartmentInfo.factory.validateConfig(update.config)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const extension = update.enabled
|
||||
? compartmentInfo.factory.create(update.config)
|
||||
: []
|
||||
|
||||
effects.push(compartmentInfo.compartment.reconfigure(extension))
|
||||
|
||||
// 更新状态
|
||||
compartmentInfo.currentConfig = update.config
|
||||
compartmentInfo.enabled = update.enabled
|
||||
} catch (error) {
|
||||
console.error(`[ExtensionManager] Failed to update extension ${id}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
if (effects.length > 0) {
|
||||
this.view.dispatch({effects})
|
||||
}
|
||||
|
||||
// 清空更新队列
|
||||
this.updateQueue.clear()
|
||||
this.debounceTimeout = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新扩展
|
||||
* @param updates 更新配置数组
|
||||
@@ -251,8 +319,15 @@ export class ExtensionManager {
|
||||
* 销毁管理器
|
||||
*/
|
||||
destroy(): void {
|
||||
// 清除防抖定时器
|
||||
if (this.debounceTimeout) {
|
||||
clearTimeout(this.debounceTimeout)
|
||||
this.debounceTimeout = null
|
||||
}
|
||||
|
||||
this.view = null
|
||||
this.compartments.clear()
|
||||
this.extensionFactories.clear()
|
||||
this.updateQueue.clear()
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import i18n from '@/i18n'
|
||||
// 导入现有扩展的创建函数
|
||||
import rainbowBracketsExtension from '../extensions/rainbowBracket/rainbowBracketsExtension'
|
||||
import {createTextHighlighter} from '../extensions/textHighlight/textHighlightExtension'
|
||||
import {createCodeBlastExtension} from '../extensions/codeblast'
|
||||
|
||||
import {color} from '../extensions/colorSelector'
|
||||
import {hyperLink} from '../extensions/hyperlink'
|
||||
import {minimap} from '../extensions/minimap'
|
||||
@@ -73,35 +73,7 @@ export const minimapFactory: ExtensionFactory = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码爆炸效果扩展工厂
|
||||
*/
|
||||
export const codeBlastFactory: ExtensionFactory = {
|
||||
create(config: any) {
|
||||
const options = {
|
||||
effect: config.effect || 1,
|
||||
shake: config.shake !== false,
|
||||
maxParticles: config.maxParticles || 300,
|
||||
shakeIntensity: config.shakeIntensity || 3
|
||||
}
|
||||
return createCodeBlastExtension(options)
|
||||
},
|
||||
getDefaultConfig() {
|
||||
return {
|
||||
effect: 1,
|
||||
shake: true,
|
||||
maxParticles: 300,
|
||||
shakeIntensity: 3
|
||||
}
|
||||
},
|
||||
validateConfig(config: any) {
|
||||
return typeof config === 'object' &&
|
||||
(!config.effect || [1, 2].includes(config.effect)) &&
|
||||
(!config.shake || typeof config.shake === 'boolean') &&
|
||||
(!config.maxParticles || typeof config.maxParticles === 'number') &&
|
||||
(!config.shakeIntensity || typeof config.shakeIntensity === 'number')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 超链接扩展工厂
|
||||
@@ -213,11 +185,7 @@ const EXTENSION_CONFIGS = {
|
||||
displayNameKey: 'extensions.minimap.name',
|
||||
descriptionKey: 'extensions.minimap.description'
|
||||
},
|
||||
[ExtensionID.ExtensionCodeBlast]: {
|
||||
factory: codeBlastFactory,
|
||||
displayNameKey: 'extensions.codeBlast.name',
|
||||
descriptionKey: 'extensions.codeBlast.description'
|
||||
},
|
||||
|
||||
|
||||
// 工具扩展
|
||||
[ExtensionID.ExtensionSearch]: {
|
||||
@@ -272,6 +240,35 @@ export function getExtensionDescription(id: ExtensionID): string {
|
||||
return config?.descriptionKey ? i18n.global.t(config.descriptionKey) : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展工厂实例
|
||||
* @param id 扩展ID
|
||||
* @returns 扩展工厂实例
|
||||
*/
|
||||
export function getExtensionFactory(id: ExtensionID): ExtensionFactory | undefined {
|
||||
return EXTENSION_CONFIGS[id as ExtensionID]?.factory
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展的默认配置
|
||||
* @param id 扩展ID
|
||||
* @returns 默认配置对象
|
||||
*/
|
||||
export function getExtensionDefaultConfig(id: ExtensionID): any {
|
||||
const factory = getExtensionFactory(id)
|
||||
return factory?.getDefaultConfig() || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查扩展是否有配置项
|
||||
* @param id 扩展ID
|
||||
* @returns 是否有配置项
|
||||
*/
|
||||
export function hasExtensionConfig(id: ExtensionID): boolean {
|
||||
const defaultConfig = getExtensionDefaultConfig(id)
|
||||
return Object.keys(defaultConfig).length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用扩展的ID列表
|
||||
* @returns 扩展ID数组
|
||||
|
||||
Reference in New Issue
Block a user