Skip to content

Commit 2a1334b

Browse files
🐛 fix: Fix hotlog
1 parent bdd325e commit 2a1334b

4 files changed

Lines changed: 144 additions & 10 deletions

File tree

src/editor-kernel/kernel.ts

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { createEmptyEditorState } from './utils';
2727
templateSettings.interpolate = /{{([\S\s]+?)}}/g;
2828

2929
export class Kernel extends EventEmitter implements IEditorKernel {
30+
// Global hot reload flag
31+
private static globalHotReloadMode: boolean | undefined = undefined;
3032
private dataTypeMap: Map<string, DataSource>;
3133
private plugins: Array<IEditorPluginConstructor<any> & { __config: any }> = [];
3234
private pluginsInstances: Array<IEditorPlugin<any>> = [];
@@ -47,7 +49,61 @@ export class Kernel extends EventEmitter implements IEditorKernel {
4749
super();
4850
this.dataTypeMap = new Map<string, DataSource>();
4951
// Enable hot reload mode in development
50-
this.hotReloadMode = typeof process !== 'undefined' && process.env?.NODE_ENV === 'development';
52+
this.hotReloadMode = this.detectDevelopmentMode();
53+
}
54+
55+
private detectDevelopmentMode(): boolean {
56+
// Check global override first
57+
if (Kernel.globalHotReloadMode !== undefined) {
58+
return Kernel.globalHotReloadMode;
59+
}
60+
61+
// Multiple ways to detect development mode
62+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {
63+
return true;
64+
}
65+
66+
// Check for common development indicators
67+
if (typeof window !== 'undefined') {
68+
// Webpack HMR
69+
if ((window as any).webpackHotUpdate) {
70+
return true;
71+
}
72+
// Vite HMR
73+
if ((window as any).__vite_plugin_react_preamble_installed__) {
74+
return true;
75+
}
76+
// Next.js development
77+
if ((window as any).__NEXT_DATA__?.buildId === 'development') {
78+
return true;
79+
}
80+
}
81+
82+
// Check for localhost or development URLs
83+
if (typeof window !== 'undefined' && window.location) {
84+
const hostname = window.location.hostname;
85+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
86+
return true;
87+
}
88+
}
89+
90+
// Default to development mode for better DX
91+
return true;
92+
}
93+
94+
/**
95+
* Globally enable or disable hot reload mode for all kernel instances
96+
* @param enabled Whether to enable hot reload mode globally
97+
*/
98+
static setGlobalHotReloadMode(enabled: boolean): void {
99+
Kernel.globalHotReloadMode = enabled;
100+
}
101+
102+
/**
103+
* Reset global hot reload mode to automatic detection
104+
*/
105+
static resetGlobalHotReloadMode(): void {
106+
Kernel.globalHotReloadMode = undefined;
51107
}
52108

53109
getLexicalEditor(): LexicalEditor | null {
@@ -72,10 +128,21 @@ export class Kernel extends EventEmitter implements IEditorKernel {
72128
}
73129

74130
setRootElement(dom: HTMLElement) {
75-
for (const plugin of this.plugins) {
76-
const instance = new plugin(this, plugin.__config);
77-
this.pluginsInstances.push(instance);
131+
// Check if editor is already initialized to prevent re-initialization
132+
if (this.editor) {
133+
console.warn('[Editor] Editor is already initialized, updating root element only');
134+
this.editor.setRootElement(dom);
135+
return this.editor;
78136
}
137+
138+
// Initialize plugins if not already done
139+
if (this.pluginsInstances.length === 0) {
140+
for (const plugin of this.plugins) {
141+
const instance = new plugin(this, plugin.__config);
142+
this.pluginsInstances.push(instance);
143+
}
144+
}
145+
79146
const editor = (this.editor = createEditor({
80147
// @ts-expect-error Inject into lexical editor instance
81148
__kernel: this,
@@ -164,13 +231,29 @@ export class Kernel extends EventEmitter implements IEditorKernel {
164231
const index = this.plugins.findIndex((p) => p.pluginName === plugin.pluginName);
165232
if (index !== -1) {
166233
this.plugins.splice(index, 1);
234+
// Also remove corresponding plugin instance if it exists
235+
const instanceIndex = this.pluginsInstances.findIndex((instance) => {
236+
return (instance.constructor as any).pluginName === plugin.pluginName;
237+
});
238+
if (instanceIndex !== -1) {
239+
const oldInstance = this.pluginsInstances[instanceIndex];
240+
if (oldInstance.destroy) {
241+
oldInstance.destroy();
242+
}
243+
this.pluginsInstances.splice(instanceIndex, 1);
244+
}
167245
}
168246
} else {
169247
throw new Error(
170248
`Plugin with name "${plugin.pluginName}" is already registered with a different implementation.`,
171249
);
172250
}
173251
} else {
252+
// Same plugin, just update config if provided
253+
if (config !== undefined) {
254+
// @ts-expect-error not error
255+
plugin.__config = config;
256+
}
174257
return this; // If plugin already exists, don't register again
175258
}
176259
}
@@ -197,15 +280,35 @@ export class Kernel extends EventEmitter implements IEditorKernel {
197280
}
198281

199282
registerService<T>(serviceId: IServiceID<T>, service: T): void {
200-
if (this.serviceMap.has(serviceId.__serviceId)) {
283+
const serviceIdString = serviceId.__serviceId;
284+
285+
if (this.serviceMap.has(serviceIdString)) {
201286
if (this.hotReloadMode) {
202287
// In hot reload mode, allow service override with warning
203-
console.warn(`[Hot Reload] Overriding service with ID "${serviceId.__serviceId}"`);
288+
console.warn(`[Hot Reload] Overriding service with ID "${serviceIdString}"`);
289+
this.serviceMap.set(serviceIdString, service);
290+
return;
204291
} else {
205-
throw new Error(`Service with ID "${serviceId.__serviceId}" is already registered.`);
292+
// Check if it's the same service instance
293+
const existingService = this.serviceMap.get(serviceIdString);
294+
if (existingService === service) {
295+
// Same service instance, no need to re-register
296+
console.warn(
297+
`[Editor] Service "${serviceIdString}" is already registered with the same instance`,
298+
);
299+
return;
300+
}
301+
302+
// Different service instance in production mode
303+
console.error(
304+
`[Editor] Attempting to register duplicate service "${serviceIdString}". Enable hot reload mode if this is intended.`,
305+
);
306+
throw new Error(`Service with ID "${serviceIdString}" is already registered.`);
206307
}
207308
}
208-
this.serviceMap.set(serviceId.__serviceId, service);
309+
310+
this.serviceMap.set(serviceIdString, service);
311+
console.debug(`[Editor] Registered service: ${serviceIdString}`);
209312
}
210313

211314
/**

src/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,29 @@ export * from './plugins/slash';
1212
export * from './plugins/table';
1313
export * from './plugins/upload';
1414
export type { IEditor } from './types';
15+
16+
// Hot reload utilities
17+
export { Kernel } from './editor-kernel/kernel';
18+
19+
/**
20+
* Enable hot reload mode globally for all editor instances
21+
* Call this in your app's entry point during development
22+
*/
23+
export function enableHotReload(): void {
24+
if (typeof window !== 'undefined') {
25+
const { Kernel } = require('./editor-kernel/kernel');
26+
Kernel.setGlobalHotReloadMode(true);
27+
console.log('[LobeHub Editor] Hot reload mode enabled globally');
28+
}
29+
}
30+
31+
/**
32+
* Disable hot reload mode globally
33+
*/
34+
export function disableHotReload(): void {
35+
if (typeof window !== 'undefined') {
36+
const { Kernel } = require('./editor-kernel/kernel');
37+
Kernel.setGlobalHotReloadMode(false);
38+
console.log('[LobeHub Editor] Hot reload mode disabled globally');
39+
}
40+
}

src/plugins/common/react/ReactPlainText.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const ReactPlainText = memo<ReactPlainTextProps>(
3737
editor.registerPlugin(CommonPlugin, {
3838
theme: restTheme ? { ...themeStyles, ...restTheme } : themeStyles,
3939
});
40-
}, []);
40+
}, [editor, restTheme, themeStyles]);
4141

4242
useEffect(() => {
4343
const container = editorContainerRef.current;
@@ -51,7 +51,7 @@ const ReactPlainText = memo<ReactPlainTextProps>(
5151
return editor.getLexicalEditor()?.registerUpdateListener(() => {
5252
onChange?.(editor);
5353
});
54-
}, []);
54+
}, [editor, type, content, onChange]);
5555

5656
return (
5757
<div className={cx(styles.root, styles.variant, className)} style={style}>

src/types/kernel.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,9 @@ export interface IEditorPluginConstructor<IConfig> {
219219
new (kernel: IEditorKernel, config?: IConfig): IEditorPlugin<IConfig>;
220220
}
221221

222+
export interface IKernelStatic {
223+
resetGlobalHotReloadMode(): void;
224+
setGlobalHotReloadMode(enabled: boolean): void;
225+
}
226+
222227
export type IPlugin<T = any> = IEditorPluginConstructor<T> | [IEditorPluginConstructor<T>, T?];

0 commit comments

Comments
 (0)