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.
- package/README.md +155 -0
- package/lib/adapters/vanilla/index.d.ts +3 -0
- package/lib/adapters/vanilla/index.d.ts.map +1 -0
- package/lib/config/languages.d.ts +21 -0
- package/lib/config/languages.d.ts.map +1 -0
- package/lib/config/runnerStrategies.d.ts +12 -0
- package/lib/config/runnerStrategies.d.ts.map +1 -0
- package/lib/config/runnerStrategies_v2.d.ts +31 -0
- package/lib/config/runnerStrategies_v2.d.ts.map +1 -0
- package/lib/config/themes.d.ts +54 -0
- package/lib/config/themes.d.ts.map +1 -0
- package/lib/core/BackendRunner.d.ts +78 -0
- package/lib/core/BackendRunner.d.ts.map +1 -0
- package/lib/core/CodeRunner.d.ts +32 -0
- package/lib/core/CodeRunner.d.ts.map +1 -0
- package/lib/core/LanguageManager.d.ts +41 -0
- package/lib/core/LanguageManager.d.ts.map +1 -0
- package/lib/core/LayoutManager.d.ts +59 -0
- package/lib/core/LayoutManager.d.ts.map +1 -0
- package/lib/core/MonacoWrapper.d.ts +63 -0
- package/lib/core/MonacoWrapper.d.ts.map +1 -0
- package/lib/core/SmartCodeEditor.d.ts +140 -0
- package/lib/core/SmartCodeEditor.d.ts.map +1 -0
- package/lib/dev-main.d.ts +2 -0
- package/lib/dev-main.d.ts.map +1 -0
- package/lib/index.cjs +242 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1369 -0
- package/lib/index.umd.cjs +242 -0
- package/lib/shims-vue.d.ts +4 -0
- package/lib/types/index.d.ts +101 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/language.d.ts +37 -0
- package/lib/types/language.d.ts.map +1 -0
- package/lib/types/question.d.ts +75 -0
- package/lib/types/question.d.ts.map +1 -0
- package/lib/utils/loader.d.ts +9 -0
- package/lib/utils/loader.d.ts.map +1 -0
- package/lib/utils/markdown.d.ts +2 -0
- package/lib/utils/markdown.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/adapters/vanilla/index.ts +7 -0
- package/src/adapters/vue/SmartCodeEditor.vue +1190 -0
- package/src/config/languages.ts +273 -0
- package/src/config/runnerStrategies.ts +261 -0
- package/src/config/runnerStrategies_v2.ts +182 -0
- package/src/config/themes.ts +37 -0
- package/src/core/BackendRunner.ts +329 -0
- package/src/core/CodeRunner.ts +107 -0
- package/src/core/LanguageManager.ts +108 -0
- package/src/core/LayoutManager.ts +268 -0
- package/src/core/MonacoWrapper.ts +173 -0
- package/src/core/SmartCodeEditor.ts +1015 -0
- package/src/dev-app.vue +488 -0
- package/src/dev-main.ts +7 -0
- package/src/index.ts +19 -0
- package/src/shims-vue.d.ts +4 -0
- package/src/types/index.ts +129 -0
- package/src/types/language.ts +44 -0
- package/src/types/question.ts +98 -0
- package/src/utils/loader.ts +69 -0
- package/src/utils/markdown.ts +89 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { UITestCase } from "./index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 题目难度级别
|
|
5
|
+
*/
|
|
6
|
+
export type QuestionDifficulty = "easy" | "medium" | "hard";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 题目配置
|
|
10
|
+
* 由外部调用者传入,包含题目信息、语言限制、代码模板等
|
|
11
|
+
*/
|
|
12
|
+
export interface QuestionConfig {
|
|
13
|
+
// ===== 题目信息 =====
|
|
14
|
+
|
|
15
|
+
/** 题目标题 */
|
|
16
|
+
title?: string;
|
|
17
|
+
|
|
18
|
+
/** 题目描述(支持 HTML) */
|
|
19
|
+
description?: string;
|
|
20
|
+
|
|
21
|
+
/** 题目描述 Markdown(可选) */
|
|
22
|
+
descriptionMarkdown?: string;
|
|
23
|
+
|
|
24
|
+
/** 题目难度(可选) */
|
|
25
|
+
difficulty?: QuestionDifficulty;
|
|
26
|
+
|
|
27
|
+
// ===== 语言配置 =====
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 支持的语言 ID 列表(可选)
|
|
31
|
+
* 如果不传,则显示所有语言
|
|
32
|
+
* @example ["python", "java", "cpp"]
|
|
33
|
+
*/
|
|
34
|
+
supportedLanguages?: string[];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 每种语言的代码模板
|
|
38
|
+
* key: 语言 ID
|
|
39
|
+
* value: 代码模板字符串
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* {
|
|
43
|
+
* python: "def twoSum(nums, target):\n pass",
|
|
44
|
+
* java: "class Solution { ... }"
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
languageTemplates: Record<string, string>;
|
|
48
|
+
|
|
49
|
+
/** 默认选中的语言(可选) */
|
|
50
|
+
defaultLanguage?: string;
|
|
51
|
+
|
|
52
|
+
// ===== 测试配置 =====
|
|
53
|
+
|
|
54
|
+
/** 测试用例列表(可选) */
|
|
55
|
+
testCases?: UITestCase[];
|
|
56
|
+
|
|
57
|
+
// ===== 函数结构(可选) =====
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 题目函数结构(用于根据参数名生成输入框)
|
|
61
|
+
*/
|
|
62
|
+
functionStructure?: {
|
|
63
|
+
args: Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
type?: string;
|
|
66
|
+
}>;
|
|
67
|
+
returnType?: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Markdown 题目配置(用于左侧题目区的编辑 + 预览)
|
|
73
|
+
*/
|
|
74
|
+
export interface QuestionMarkdownOptions {
|
|
75
|
+
/** 初始内容 */
|
|
76
|
+
value?: string;
|
|
77
|
+
|
|
78
|
+
/** 是否显示编辑区(默认 true) */
|
|
79
|
+
showEditor?: boolean;
|
|
80
|
+
|
|
81
|
+
/** 是否可编辑(默认 true) */
|
|
82
|
+
editable?: boolean;
|
|
83
|
+
|
|
84
|
+
/** 是否显示预览区(默认 true) */
|
|
85
|
+
showPreview?: boolean;
|
|
86
|
+
|
|
87
|
+
/** 编辑区占位文案 */
|
|
88
|
+
placeholder?: string;
|
|
89
|
+
|
|
90
|
+
/** 内容变化回调 */
|
|
91
|
+
onChange?: (value: string) => void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 语言模板映射
|
|
96
|
+
* 简化版配置,只包含语言到模板的映射
|
|
97
|
+
*/
|
|
98
|
+
export type LanguageTemplates = Record<string, string>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monaco Editor Loader configuration
|
|
3
|
+
*/
|
|
4
|
+
export function loadMonaco(): Promise<any> {
|
|
5
|
+
if (typeof (window as any).monaco !== "undefined") {
|
|
6
|
+
return Promise.resolve((window as any).monaco);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const globalAny = window as any;
|
|
11
|
+
const customLoaderUrl = globalAny.__SCE_MONACO_LOADER_URL__;
|
|
12
|
+
const customBasePath = globalAny.__SCE_MONACO_BASE_PATH__;
|
|
13
|
+
const fallbackUrls = ["/package/min/vs/loader.js", "/package/vs/loader.js"];
|
|
14
|
+
|
|
15
|
+
const loaderUrls = customLoaderUrl
|
|
16
|
+
? [customLoaderUrl]
|
|
17
|
+
: customBasePath
|
|
18
|
+
? [`${String(customBasePath).replace(/\/$/, "")}/loader.js`]
|
|
19
|
+
: fallbackUrls;
|
|
20
|
+
|
|
21
|
+
const tryLoad = (index: number) => {
|
|
22
|
+
if (index >= loaderUrls.length) {
|
|
23
|
+
reject(new Error("Monaco loader not found"));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const loaderUrl = loaderUrls[index];
|
|
28
|
+
const loaderScript = document.createElement("script");
|
|
29
|
+
loaderScript.src = loaderUrl;
|
|
30
|
+
|
|
31
|
+
loaderScript.onload = () => {
|
|
32
|
+
const vsBase = loaderUrl.replace(/\/loader\.js(\?.*)?$/, "");
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
window.require.config({
|
|
35
|
+
paths: {
|
|
36
|
+
vs: vsBase,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
window.require(["vs/editor/editor.main"], () => {
|
|
42
|
+
resolve((window as any).monaco);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
loaderScript.onerror = () => {
|
|
47
|
+
loaderScript.remove();
|
|
48
|
+
tryLoad(index + 1);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
document.body.appendChild(loaderScript);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
tryLoad(0);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 防抖函数
|
|
60
|
+
*/
|
|
61
|
+
export function debounce(func: Function, wait: number) {
|
|
62
|
+
let timeout: any;
|
|
63
|
+
return function (this: any, ...args: any[]) {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
timeout = setTimeout(() => {
|
|
66
|
+
func.apply(this, args);
|
|
67
|
+
}, wait);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
function escapeHtml(input: string): string {
|
|
2
|
+
return input
|
|
3
|
+
.replace(/&/g, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """)
|
|
7
|
+
.replace(/'/g, "'");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatInline(input: string): string {
|
|
11
|
+
let output = input;
|
|
12
|
+
output = output.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
13
|
+
output = output.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
14
|
+
output = output.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
15
|
+
output = output.replace(/_([^_]+)_/g, "<em>$1</em>");
|
|
16
|
+
output = output.replace(
|
|
17
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
18
|
+
'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>',
|
|
19
|
+
);
|
|
20
|
+
return output;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function renderMarkdown(markdown: string): string {
|
|
24
|
+
const source = markdown || "";
|
|
25
|
+
const codeBlocks: string[] = [];
|
|
26
|
+
|
|
27
|
+
let content = source.replace(
|
|
28
|
+
/```(?:[\w-]+)?\n([\s\S]*?)```/g,
|
|
29
|
+
(_match, code) => {
|
|
30
|
+
const escaped = escapeHtml(code.trimEnd());
|
|
31
|
+
const token = `@@CODEBLOCK_${codeBlocks.length}@@`;
|
|
32
|
+
codeBlocks.push(`<pre><code>${escaped}</code></pre>`);
|
|
33
|
+
return token;
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
content = escapeHtml(content);
|
|
38
|
+
|
|
39
|
+
const lines = content.split(/\r?\n/);
|
|
40
|
+
const blocks: string[] = [];
|
|
41
|
+
let inList = false;
|
|
42
|
+
|
|
43
|
+
const closeList = () => {
|
|
44
|
+
if (inList) {
|
|
45
|
+
blocks.push("</ul>");
|
|
46
|
+
inList = false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
for (const rawLine of lines) {
|
|
51
|
+
const line = rawLine.trim();
|
|
52
|
+
if (!line) {
|
|
53
|
+
closeList();
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*)$/);
|
|
58
|
+
if (headingMatch) {
|
|
59
|
+
closeList();
|
|
60
|
+
const level = headingMatch[1].length;
|
|
61
|
+
blocks.push(
|
|
62
|
+
`<h${level}>${formatInline(headingMatch[2])}</h${level}>`,
|
|
63
|
+
);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const listMatch = line.match(/^[-*+]\s+(.*)$/);
|
|
68
|
+
if (listMatch) {
|
|
69
|
+
if (!inList) {
|
|
70
|
+
blocks.push("<ul>");
|
|
71
|
+
inList = true;
|
|
72
|
+
}
|
|
73
|
+
blocks.push(`<li>${formatInline(listMatch[1])}</li>`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
closeList();
|
|
78
|
+
blocks.push(`<p>${formatInline(line)}</p>`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
closeList();
|
|
82
|
+
|
|
83
|
+
let html = blocks.join("\n");
|
|
84
|
+
codeBlocks.forEach((blockHtml, index) => {
|
|
85
|
+
html = html.replace(`@@CODEBLOCK_${index}@@`, blockHtml);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return html;
|
|
89
|
+
}
|