smart-code-editor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +155 -0
  2. package/lib/adapters/vanilla/index.d.ts +3 -0
  3. package/lib/adapters/vanilla/index.d.ts.map +1 -0
  4. package/lib/config/languages.d.ts +21 -0
  5. package/lib/config/languages.d.ts.map +1 -0
  6. package/lib/config/runnerStrategies.d.ts +12 -0
  7. package/lib/config/runnerStrategies.d.ts.map +1 -0
  8. package/lib/config/runnerStrategies_v2.d.ts +31 -0
  9. package/lib/config/runnerStrategies_v2.d.ts.map +1 -0
  10. package/lib/config/themes.d.ts +54 -0
  11. package/lib/config/themes.d.ts.map +1 -0
  12. package/lib/core/BackendRunner.d.ts +78 -0
  13. package/lib/core/BackendRunner.d.ts.map +1 -0
  14. package/lib/core/CodeRunner.d.ts +32 -0
  15. package/lib/core/CodeRunner.d.ts.map +1 -0
  16. package/lib/core/LanguageManager.d.ts +41 -0
  17. package/lib/core/LanguageManager.d.ts.map +1 -0
  18. package/lib/core/LayoutManager.d.ts +59 -0
  19. package/lib/core/LayoutManager.d.ts.map +1 -0
  20. package/lib/core/MonacoWrapper.d.ts +63 -0
  21. package/lib/core/MonacoWrapper.d.ts.map +1 -0
  22. package/lib/core/SmartCodeEditor.d.ts +140 -0
  23. package/lib/core/SmartCodeEditor.d.ts.map +1 -0
  24. package/lib/dev-main.d.ts +2 -0
  25. package/lib/dev-main.d.ts.map +1 -0
  26. package/lib/index.cjs +242 -0
  27. package/lib/index.d.ts +5 -0
  28. package/lib/index.d.ts.map +1 -0
  29. package/lib/index.js +1369 -0
  30. package/lib/index.umd.cjs +242 -0
  31. package/lib/shims-vue.d.ts +4 -0
  32. package/lib/types/index.d.ts +101 -0
  33. package/lib/types/index.d.ts.map +1 -0
  34. package/lib/types/language.d.ts +37 -0
  35. package/lib/types/language.d.ts.map +1 -0
  36. package/lib/types/question.d.ts +75 -0
  37. package/lib/types/question.d.ts.map +1 -0
  38. package/lib/utils/loader.d.ts +9 -0
  39. package/lib/utils/loader.d.ts.map +1 -0
  40. package/lib/utils/markdown.d.ts +2 -0
  41. package/lib/utils/markdown.d.ts.map +1 -0
  42. package/package.json +72 -0
  43. package/src/adapters/vanilla/index.ts +7 -0
  44. package/src/adapters/vue/SmartCodeEditor.vue +1190 -0
  45. package/src/config/languages.ts +273 -0
  46. package/src/config/runnerStrategies.ts +261 -0
  47. package/src/config/runnerStrategies_v2.ts +182 -0
  48. package/src/config/themes.ts +37 -0
  49. package/src/core/BackendRunner.ts +329 -0
  50. package/src/core/CodeRunner.ts +107 -0
  51. package/src/core/LanguageManager.ts +108 -0
  52. package/src/core/LayoutManager.ts +268 -0
  53. package/src/core/MonacoWrapper.ts +173 -0
  54. package/src/core/SmartCodeEditor.ts +1015 -0
  55. package/src/dev-app.vue +488 -0
  56. package/src/dev-main.ts +7 -0
  57. package/src/index.ts +19 -0
  58. package/src/shims-vue.d.ts +4 -0
  59. package/src/types/index.ts +129 -0
  60. package/src/types/language.ts +44 -0
  61. package/src/types/question.ts +98 -0
  62. package/src/utils/loader.ts +69 -0
  63. package/src/utils/markdown.ts +89 -0
@@ -0,0 +1,268 @@
1
+ /**
2
+ * 布局管理器
3
+ * 负责管理左右分栏布局和拖拽调整
4
+ */
5
+ export class LayoutManager {
6
+ private container: HTMLElement;
7
+ private leftPanel: HTMLElement;
8
+ private rightPanel: HTMLElement;
9
+ private splitter: HTMLElement;
10
+
11
+ private isDragging: boolean = false;
12
+ private startX: number = 0;
13
+ private startLeftWidth: number = 0;
14
+
15
+ constructor(
16
+ container: HTMLElement,
17
+ defaultRatio: number = 0.48,
18
+ theme: string = "vs-dark",
19
+ ) {
20
+ this.container = container;
21
+
22
+ // 创建布局
23
+ this.leftPanel = document.createElement("div");
24
+ this.splitter = document.createElement("div");
25
+ this.rightPanel = document.createElement("div");
26
+
27
+ this.initLayout(defaultRatio, theme);
28
+ this.initResizable();
29
+ }
30
+
31
+ /**
32
+ * 初始化布局
33
+ */
34
+ private initLayout(ratio: number, theme: string): void {
35
+ // 设置容器样式
36
+ this.container.style.display = "flex";
37
+ this.container.style.height = "100%";
38
+ this.container.style.position = "relative";
39
+
40
+ // Create a wrapper for the editor content
41
+ const editorWrapper = document.createElement("div");
42
+ editorWrapper.className = "sce-editor-wrapper";
43
+ editorWrapper.style.display = "flex";
44
+ editorWrapper.style.height = "100%";
45
+ editorWrapper.style.width = "100%";
46
+ editorWrapper.style.overflow = "hidden";
47
+
48
+ // 左侧面板(试题区)
49
+ this.leftPanel.className = "sce-question-panel";
50
+ this.leftPanel.style.width = `${ratio * 100}%`;
51
+ this.leftPanel.style.minWidth = "200px";
52
+ this.leftPanel.style.overflow = "auto";
53
+ this.leftPanel.style.padding = "14px 6px 14px 14px";
54
+ this.leftPanel.style.boxSizing = "border-box";
55
+
56
+ // 分隔条
57
+ this.splitter.className = "sce-splitter";
58
+ this.splitter.style.width = "2px";
59
+ this.splitter.style.cursor = "col-resize";
60
+ this.splitter.style.flexShrink = "0";
61
+ this.splitter.style.userSelect = "none";
62
+ this.splitter.style.margin = "0 6px";
63
+
64
+ // Create a container for right side (will contain sce-editor-panel and terminal-panel)
65
+ const rightContainer = document.createElement("div");
66
+ rightContainer.className = "sce-right-container";
67
+ rightContainer.style.flex = "1";
68
+ rightContainer.style.minWidth = "400px";
69
+ rightContainer.style.minHeight = "0";
70
+ rightContainer.style.display = "flex";
71
+ rightContainer.style.flexDirection = "column";
72
+ rightContainer.style.overflow = "hidden";
73
+
74
+ // 右侧面板(编辑器区 - 将与 terminal-panel 平级)
75
+ this.rightPanel.className = "sce-editor-panel";
76
+ this.rightPanel.style.flex = "1";
77
+ this.rightPanel.style.minHeight = "0";
78
+ this.rightPanel.style.overflow = "hidden";
79
+ this.rightPanel.style.position = "relative";
80
+ this.rightPanel.style.display = "flex";
81
+ this.rightPanel.style.flexDirection = "column";
82
+
83
+ // 应用主题
84
+ this.setTheme(theme);
85
+
86
+ // Add editor panel to right container
87
+ rightContainer.appendChild(this.rightPanel);
88
+
89
+ // 添加到 wrapper
90
+ editorWrapper.appendChild(this.leftPanel);
91
+ editorWrapper.appendChild(this.splitter);
92
+ editorWrapper.appendChild(rightContainer);
93
+
94
+ // 将 wrapper 添加到容器
95
+ this.container.appendChild(editorWrapper);
96
+ }
97
+
98
+ /**
99
+ * 初始化拖拽功能
100
+ */
101
+ private initResizable(): void {
102
+ this.splitter.addEventListener("mousedown", this.handleMouseDown);
103
+ document.addEventListener("mousemove", this.handleMouseMove);
104
+ document.addEventListener("mouseup", this.handleMouseUp);
105
+ }
106
+
107
+ /**
108
+ * 鼠标按下
109
+ */
110
+ private handleMouseDown = (e: MouseEvent): void => {
111
+ this.isDragging = true;
112
+ this.startX = e.clientX;
113
+ this.startLeftWidth = this.leftPanel.offsetWidth;
114
+
115
+ // 添加拖拽样式
116
+ this.splitter.style.background = "#4CAF50";
117
+ document.body.style.cursor = "col-resize";
118
+ document.body.style.userSelect = "none";
119
+
120
+ e.preventDefault();
121
+ };
122
+
123
+ /**
124
+ * 鼠标移动
125
+ */
126
+ private handleMouseMove = (e: MouseEvent): void => {
127
+ if (!this.isDragging) return;
128
+
129
+ const deltaX = e.clientX - this.startX;
130
+ const newLeftWidth = this.startLeftWidth + deltaX;
131
+ const containerWidth = this.container.offsetWidth;
132
+
133
+ // 限制最小和最大宽度
134
+ const minWidth = 200;
135
+ const maxWidth = containerWidth - 400 - 8; // 右侧最小400px,分隔条8px
136
+
137
+ if (newLeftWidth >= minWidth && newLeftWidth <= maxWidth) {
138
+ const ratio = newLeftWidth / containerWidth;
139
+ this.leftPanel.style.width = `${ratio * 100}%`;
140
+ }
141
+ };
142
+
143
+ /**
144
+ * 鼠标释放
145
+ */
146
+ private handleMouseUp = (): void => {
147
+ if (!this.isDragging) return;
148
+
149
+ this.isDragging = false;
150
+ this.splitter.style.background = "#f5f5f5";
151
+ document.body.style.cursor = "";
152
+ document.body.style.userSelect = "";
153
+ };
154
+
155
+ /**
156
+ * 设置左侧内容
157
+ */
158
+ setLeftContent(content: string | HTMLElement): void {
159
+ if (typeof content === "string") {
160
+ this.leftPanel.innerHTML = content;
161
+ this.leftPanel.style.overflow = "auto";
162
+ } else {
163
+ this.leftPanel.innerHTML = "";
164
+ this.leftPanel.appendChild(content);
165
+ if (content.classList.contains("sce-question-markdown")) {
166
+ this.leftPanel.style.overflow = "hidden";
167
+ } else {
168
+ this.leftPanel.style.overflow = "auto";
169
+ }
170
+ }
171
+ }
172
+
173
+ /**
174
+ * 获取右侧容器(用于放置编辑器)
175
+ */
176
+ getRightContainer(): HTMLElement {
177
+ return this.rightPanel;
178
+ }
179
+
180
+ /**
181
+ * 显示/隐藏左侧面板
182
+ */
183
+ setQuestionPanelVisible(visible: boolean): void {
184
+ if (visible) {
185
+ this.leftPanel.style.display = "block";
186
+ this.splitter.style.display = "block";
187
+ } else {
188
+ this.leftPanel.style.display = "none";
189
+ this.splitter.style.display = "none";
190
+ }
191
+ }
192
+
193
+ /**
194
+ * 设置分割比例
195
+ */
196
+ setSplitRatio(ratio: number): void {
197
+ this.leftPanel.style.width = `${ratio * 100}%`;
198
+ }
199
+
200
+ /**
201
+ * 设置主题
202
+ */
203
+ setTheme(theme: string): void {
204
+ const isDark = theme === "vs-dark";
205
+
206
+ if (isDark) {
207
+ this.leftPanel.style.background = "#1e1e1e";
208
+ this.leftPanel.style.color = "#d4d4d4";
209
+ this.splitter.style.background = "#252526";
210
+ } else {
211
+ this.leftPanel.style.background = "#ffffff";
212
+ this.leftPanel.style.color = "#333333";
213
+ this.splitter.style.background = "#f5f5f5";
214
+ }
215
+
216
+ const editor = this.leftPanel.querySelector(
217
+ ".sce-markdown-editor",
218
+ ) as HTMLTextAreaElement | null;
219
+ const preview = this.leftPanel.querySelector(
220
+ ".sce-markdown-preview",
221
+ ) as HTMLElement | null;
222
+ const labels = this.leftPanel.querySelectorAll(".sce-markdown-label");
223
+ const tabs = this.leftPanel.querySelectorAll(".sce-markdown-tab");
224
+
225
+ if (editor) {
226
+ editor.style.background = isDark ? "#1e1e1e" : "#ffffff";
227
+ editor.style.color = isDark ? "#d4d4d4" : "#333333";
228
+ editor.style.border = isDark ? "1px solid #333" : "1px solid #ddd";
229
+ }
230
+
231
+ if (preview) {
232
+ preview.style.background = isDark ? "#1b1b1b" : "#ffffff";
233
+ preview.style.color = isDark ? "#d4d4d4" : "#333333";
234
+ // preview.style.border = isDark ? "1px solid #333" : "1px solid #ddd";
235
+ }
236
+
237
+ labels.forEach((label) => {
238
+ (label as HTMLElement).style.color = isDark ? "#c5c5c5" : "#666666";
239
+ });
240
+
241
+ tabs.forEach((tab) => {
242
+ const btn = tab as HTMLButtonElement;
243
+ const isActive = btn.classList.contains("active");
244
+ btn.style.border = isDark ? "1px solid #3a3a3a" : "1px solid #ddd";
245
+ btn.style.background = isActive
246
+ ? isDark
247
+ ? "#2d2d30"
248
+ : "#f3f3f3"
249
+ : isDark
250
+ ? "#1f1f1f"
251
+ : "#ffffff";
252
+ btn.style.color = isDark ? "#d4d4d4" : "#333333";
253
+ btn.style.borderRadius = "6px";
254
+ btn.style.padding = "4px 10px";
255
+ btn.style.fontSize = "12px";
256
+ btn.style.cursor = "pointer";
257
+ });
258
+ }
259
+
260
+ /**
261
+ * 销毁
262
+ */
263
+ destroy(): void {
264
+ document.removeEventListener("mousemove", this.handleMouseMove);
265
+ document.removeEventListener("mouseup", this.handleMouseUp);
266
+ this.container.innerHTML = "";
267
+ }
268
+ }
@@ -0,0 +1,173 @@
1
+ import type { MonacoEditorOptions } from "../types";
2
+ import { loadMonaco } from "../utils/loader";
3
+
4
+ /**
5
+ * Monaco Editor 封装
6
+ */
7
+ export class MonacoWrapper {
8
+ private editor: any | null = null;
9
+ private monaco: any | null = null;
10
+ private container: HTMLElement;
11
+
12
+ constructor(container: HTMLElement) {
13
+ this.container = container;
14
+ }
15
+
16
+ /**
17
+ * 初始化编辑器
18
+ */
19
+ async initialize(options: MonacoEditorOptions = {}): Promise<void> {
20
+ // 加载 Monaco
21
+ this.monaco = await loadMonaco();
22
+
23
+ // 创建编辑器实例
24
+ this.editor = this.monaco.editor.create(this.container, {
25
+ value: options.value || "",
26
+ language: options.language || "javascript",
27
+ theme: options.theme || "vs-dark",
28
+ readOnly: options.readOnly || false,
29
+ fontSize: options.fontSize || 14,
30
+ minimap: options.minimap || { enabled: true },
31
+ lineNumbers: options.lineNumbers || "on",
32
+ automaticLayout: options.automaticLayout !== false,
33
+ scrollBeyondLastLine: false,
34
+ roundedSelection: true,
35
+ padding: { top: 16 },
36
+ suggestOnTriggerCharacters:
37
+ options.suggestOnTriggerCharacters !== undefined
38
+ ? options.suggestOnTriggerCharacters
39
+ : true,
40
+ quickSuggestions:
41
+ options.quickSuggestions !== undefined
42
+ ? options.quickSuggestions
43
+ : true,
44
+ tabSize: 2,
45
+ });
46
+ }
47
+
48
+ /**
49
+ * 设置值
50
+ */
51
+ setValue(code: string): void {
52
+ if (this.editor) {
53
+ this.editor.setValue(code);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * 获取值
59
+ */
60
+ getValue(): string {
61
+ return this.editor ? this.editor.getValue() : "";
62
+ }
63
+
64
+ /**
65
+ * 设置语言
66
+ */
67
+ /**
68
+ * 更新模型(切换语言时重建模型以确保语法提示正确)
69
+ */
70
+ updateModel(
71
+ code: string,
72
+ language: string,
73
+ extension: string = "",
74
+ instanceId: string = "default",
75
+ ): void {
76
+ if (this.editor && this.monaco) {
77
+ const oldModel = this.editor.getModel();
78
+
79
+ // 构造虚拟文件 URI
80
+ const ext = extension.startsWith(".") ? extension : `.${extension}`;
81
+ const uri = this.monaco.Uri.parse(`file:///${instanceId}/main${ext}`);
82
+
83
+ // 查找是否已存在该 URI 的模型
84
+ let newModel = this.monaco.editor.getModel(uri);
85
+
86
+ if (!newModel) {
87
+ newModel = this.monaco.editor.createModel(code, language, uri);
88
+ } else {
89
+ newModel.setValue(code);
90
+ this.monaco.editor.setModelLanguage(newModel, language);
91
+ }
92
+
93
+ this.editor.setModel(newModel);
94
+
95
+ // 销毁旧模型(如果不再使用,避免内存泄漏。实际项目中可能需要缓存)
96
+ // 这里为了简单直接销毁旧的(除了新模型就是旧模型的情况)
97
+ if (oldModel && oldModel !== newModel) {
98
+ oldModel.dispose();
99
+ }
100
+ }
101
+ }
102
+
103
+ /**
104
+ * 设置主题
105
+ */
106
+ setTheme(theme: string): void {
107
+ if (this.editor && this.monaco) {
108
+ this.monaco.editor.setTheme(theme);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 设置只读
114
+ */
115
+ setReadOnly(readOnly: boolean): void {
116
+ if (this.editor) {
117
+ this.editor.updateOptions({ readOnly });
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 更新配置
123
+ */
124
+ updateOptions(options: any): void {
125
+ if (this.editor) {
126
+ this.editor.updateOptions(options);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * 监听内容变化
132
+ */
133
+ onDidChangeContent(callback: (value: string) => void): void {
134
+ if (this.editor) {
135
+ this.editor.onDidChangeModelContent(() => {
136
+ callback(this.getValue());
137
+ });
138
+ }
139
+ }
140
+
141
+ /**
142
+ * 调整布局
143
+ */
144
+ layout(): void {
145
+ if (this.editor) {
146
+ this.editor.layout();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * 获取 Monaco 实例
152
+ */
153
+ getMonaco() {
154
+ return this.monaco;
155
+ }
156
+
157
+ /**
158
+ * 获取编辑器实例
159
+ */
160
+ getEditor() {
161
+ return this.editor;
162
+ }
163
+
164
+ /**
165
+ * 销毁
166
+ */
167
+ dispose(): void {
168
+ if (this.editor) {
169
+ this.editor.dispose();
170
+ this.editor = null;
171
+ }
172
+ }
173
+ }