Skip to content

Commit 9038c0d

Browse files
committed
feat: update web feature to remove prefix from code
1 parent bcbf529 commit 9038c0d

File tree

3 files changed

+228
-2
lines changed

3 files changed

+228
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ __pycache__/
55
package-lock.json
66
node_modules/
77
dist/
8+
/web/assets/
9+
810
/readme_sync/
911
/readme_snatcher/python-cheatsheet.md

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Python Standard Libraries Cheatsheet
22

3-
Depend on Python v3.10.11
3+
Depend on Python v3.13.7
44

55
All code snippets have been tested to ensure they work properly.
66

web/src/main.ts

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,191 @@ type StyleType = 'atom-one-dark' | 'base16/edge-dark' | 'base16/tomorrow-night'
1111
// 定义语言类型
1212
type LanguageType = 'english' | 'chinese';
1313

14+
// 定义代码前缀显示状态
15+
type PrefixMode = 'with-prefix' | 'without-prefix';
16+
17+
// 存储原始代码(带前缀)
18+
const originalCodeCache = new Map<string, string>();
19+
20+
// 存储当前前缀模式
21+
let currentPrefixMode: PrefixMode = 'with-prefix';
22+
23+
// 从代码中移除Python交互式前缀(保留前缀后的缩进,去除行首多余空格)
24+
function removePythonPrefixes(code: string): string {
25+
// 分割代码为行
26+
const lines = code.split('\n');
27+
// 存储处理后的代码行
28+
const resultLines: string[] = [];
29+
30+
// 处理每一行
31+
for (const line of lines) {
32+
// 检查是否是只有前缀的行(需要保留为空行)
33+
const trimmedLine = line.trim();
34+
if (trimmedLine === '>>>' || trimmedLine === '&gt;&gt;&gt;' || trimmedLine === '...') {
35+
resultLines.push(''); // 保留为空行
36+
continue;
37+
}
38+
39+
// 检查是否包含前缀
40+
const hasPythonPrefix = line.includes('>>>') || line.includes('&gt;&gt;&gt;') || line.includes('...');
41+
42+
// 输出行:前面不含>>>或者...的行
43+
if (!hasPythonPrefix) {
44+
continue; // 移除输出行
45+
}
46+
47+
// 处理包含前缀的代码行
48+
let cleanedLine = line;
49+
50+
// 不使用正则表达式处理前缀,保留前缀后的缩进
51+
if (line.includes('>>>') && !line.includes('&gt;&gt;&gt;')) {
52+
// 处理 >>> 前缀
53+
const prefixIndex = line.indexOf('>>>');
54+
// 从prefixIndex+3开始,获取前缀后的内容
55+
cleanedLine = line.substring(prefixIndex + 3);
56+
} else if (line.includes('&gt;&gt;&gt;')) {
57+
// 处理HTML转义的 &gt;&gt;&gt; 前缀
58+
const prefixIndex = line.indexOf('&gt;&gt;&gt;');
59+
cleanedLine = line.substring(prefixIndex + 10); // '&gt;&gt;&gt;'长度为10
60+
} else if (line.includes('...')) {
61+
// 处理 ... 前缀
62+
const prefixIndex = line.indexOf('...');
63+
cleanedLine = line.substring(prefixIndex + 3);
64+
}
65+
66+
// 去除行首可能存在的多余空格
67+
if (cleanedLine.startsWith(' ')) {
68+
cleanedLine = cleanedLine.substring(1);
69+
}
70+
71+
// 只添加非空的代码行
72+
if (cleanedLine.trim() !== '') {
73+
resultLines.push(cleanedLine);
74+
}
75+
}
76+
77+
return resultLines.join('\n');
78+
}
79+
80+
// 为代码块添加复制按钮和处理前缀切换
81+
function setupCodeBlocks(): void {
82+
const codeBlocks = document.querySelectorAll('pre code');
83+
84+
codeBlocks.forEach((block, index) => {
85+
// 确保这是一个Python代码块
86+
if (block.textContent && (block.className.includes('python') || block.textContent.includes('>>>'))) {
87+
const blockId = `code-block-${index}`;
88+
block.id = blockId;
89+
90+
// 存储原始代码(带前缀)
91+
const originalCode = block.textContent;
92+
originalCodeCache.set(blockId, originalCode);
93+
94+
// 获取父元素pre
95+
const preElement = block.parentElement;
96+
if (preElement && preElement.tagName === 'PRE') {
97+
// 设置pre元素的相对定位,以便按钮可以绝对定位
98+
preElement.style.position = 'relative';
99+
100+
// 创建复制按钮
101+
const copyButton = document.createElement('button');
102+
copyButton.className = 'button is-small mt-2 mr-2 is-white';
103+
104+
const buttonIcon = document.createElement('span');
105+
copyButton.appendChild(buttonIcon)
106+
buttonIcon.innerHTML = '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M256 938.666667c-12.8 0-21.333333-4.266667-29.866667-12.8l-128-128c-17.066667-17.066667-17.066667-42.666667 0-59.733334l640-640c17.066667-17.066667 42.666667-17.066667 59.733334 0l128 128c17.066667 17.066667 17.066667 42.666667 0 59.733334l-640 640c-8.533333 8.533333-17.066667 12.8-29.866667 12.8z m-68.266667-170.666667L256 836.266667 836.266667 256 768 187.733333 187.733333 768z" fill="#000000"></path><path d="M768 426.666667c-12.8 0-21.333333-4.266667-29.866667-12.8l-128-128c-17.066667-17.066667-17.066667-42.666667 0-59.733334s42.666667-17.066667 59.733334 0l128 128c17.066667 17.066667 17.066667 42.666667 0 59.733334-8.533333 8.533333-17.066667 12.8-29.866667 12.8zM384 341.333333c-25.6 0-42.666667-17.066667-42.666667-42.666666s-17.066667-42.666667-42.666666-42.666667-42.666667-17.066667-42.666667-42.666667 17.066667-42.666667 42.666667-42.666666 42.666667-17.066667 42.666666-42.666667 17.066667-42.666667 42.666667-42.666667 42.666667 17.066667 42.666667 42.666667 17.066667 42.666667 42.666666 42.666667 42.666667 17.066667 42.666667 42.666666-17.066667 42.666667-42.666667 42.666667-42.666667 17.066667-42.666666 42.666667-17.066667 42.666667-42.666667 42.666666zM810.666667 768c-25.6 0-42.666667-17.066667-42.666667-42.666667s-17.066667-42.666667-42.666667-42.666666-42.666667-17.066667-42.666666-42.666667 17.066667-42.666667 42.666666-42.666667 42.666667-17.066667 42.666667-42.666666 17.066667-42.666667 42.666667-42.666667 42.666667 17.066667 42.666666 42.666667 17.066667 42.666667 42.666667 42.666666 42.666667 17.066667 42.666667 42.666667-17.066667 42.666667-42.666667 42.666667-42.666667 17.066667-42.666667 42.666666-17.066667 42.666667-42.666666 42.666667z" fill="#000000"></path></svg>';
107+
buttonIcon.className = 'icon is-small'
108+
109+
copyButton.title = 'Remove prefix';
110+
copyButton.style.position = 'absolute';
111+
copyButton.style.top = '17px';
112+
copyButton.style.right = '20px';
113+
copyButton.style.cursor = 'pointer';
114+
115+
// 添加点击事件
116+
copyButton.addEventListener('click', function() {
117+
// 切换当前代码块的前缀显示
118+
toggleCodePrefix(blockId);
119+
});
120+
121+
// 添加按钮到pre元素
122+
preElement.appendChild(copyButton);
123+
}
124+
}
125+
});
126+
127+
// 应用当前前缀模式
128+
applyPrefixMode(currentPrefixMode);
129+
}
130+
131+
// 切换单个代码块的前缀显示
132+
function toggleCodePrefix(blockId: string): void {
133+
const codeBlock = document.getElementById(blockId);
134+
if (codeBlock && originalCodeCache.has(blockId)) {
135+
const originalCode = originalCodeCache.get(blockId)!;
136+
const currentText = codeBlock.textContent || '';
137+
138+
// 判断当前状态并切换(检查更多可能的前缀模式)
139+
if (currentText.includes('>>>') || currentText.includes('&gt;&gt;&gt;') || currentText.includes('...')) {
140+
codeBlock.textContent = removePythonPrefixes(originalCode);
141+
console.log(removePythonPrefixes(originalCode))
142+
} else {
143+
codeBlock.textContent = originalCode;
144+
}
145+
146+
147+
// 确保元素类型正确并重新高亮代码
148+
if ((window as any).hljs && typeof (window as any).hljs.highlightElement === 'function' && codeBlock instanceof HTMLElement) {
149+
try {
150+
// 先移除已高亮标记,以便重新高亮
151+
delete codeBlock.dataset.highlighted;
152+
153+
(window as any).hljs.highlightElement(codeBlock);
154+
} catch (error) {
155+
console.error('Error highlighting code:', error);
156+
}
157+
}
158+
}
159+
}
160+
161+
// 应用全局前缀模式
162+
function applyPrefixMode(mode: PrefixMode): void {
163+
currentPrefixMode = mode;
164+
165+
document.querySelectorAll('pre code[id^="code-block-"]').forEach(block => {
166+
const blockId = block.id;
167+
if (originalCodeCache.has(blockId)) {
168+
const originalCode = originalCodeCache.get(blockId)!;
169+
170+
if (mode === 'without-prefix') {
171+
block.textContent = removePythonPrefixes(originalCode);
172+
} else {
173+
block.textContent = originalCode;
174+
}
175+
}
176+
177+
// 移除已高亮标记,以便重新高亮
178+
if (block instanceof HTMLElement) {
179+
delete block.dataset.highlighted;
180+
}
181+
});
182+
183+
// 重新高亮所有代码
184+
if ((window as any).hljs && typeof (window as any).hljs.highlightAll === 'function') {
185+
try {
186+
(window as any).hljs.highlightAll();
187+
} catch (error) {
188+
console.error('Error highlighting code:', error);
189+
}
190+
}
191+
192+
// 更新全局切换按钮的文本
193+
const prefixToggleButton = document.getElementById('prefix-toggle-button');
194+
if (prefixToggleButton) {
195+
prefixToggleButton.textContent = mode === 'with-prefix' ? 'Show without prefix' : 'Show with prefix';
196+
}
197+
}
198+
14199
// 动态加载HTML内容的函数
15200
function loadContent(language: LanguageType): void {
16201
// 设置当前语言显示
@@ -19,6 +204,9 @@ function loadContent(language: LanguageType): void {
19204
languageTextElement.textContent = language === 'english' ? 'English' : '中文';
20205
}
21206

207+
// 清空代码缓存
208+
originalCodeCache.clear();
209+
22210
// 更新URL参数但不刷新页面
23211
const style: StyleType = (getURLParameter('hl_style') as StyleType) || 'atom-one-dark';
24212
const url = new URL(window.location.href);
@@ -48,6 +236,9 @@ function loadContent(language: LanguageType): void {
48236
contentContainer.innerHTML = html;
49237
}
50238

239+
// 设置代码块(添加按钮和处理前缀)
240+
setupCodeBlocks();
241+
51242
// 高亮代码
52243
if ((window as any).hljs && typeof (window as any).hljs.highlightAll === 'function') {
53244
(window as any).hljs.highlightAll();
@@ -65,6 +256,32 @@ function loadContent(language: LanguageType): void {
65256

66257
// 等待DOM完全加载后执行代码
67258
document.addEventListener('DOMContentLoaded', function() {
259+
// 创建全局前缀切换按钮
260+
function createGlobalPrefixToggleButton() {
261+
const buttonContainer = document.createElement('div');
262+
buttonContainer.className = 'prefix-toggle-container';
263+
buttonContainer.style.display = 'inline-block';
264+
buttonContainer.style.marginRight = '1rem';
265+
266+
const toggleButton = document.createElement('button');
267+
toggleButton.id = 'prefix-toggle-button';
268+
toggleButton.className = 'button';
269+
toggleButton.textContent = 'Show without prefix';
270+
toggleButton.addEventListener('click', function() {
271+
// 切换全局前缀模式
272+
const newMode = currentPrefixMode === 'with-prefix' ? 'without-prefix' : 'with-prefix';
273+
applyPrefixMode(newMode);
274+
});
275+
276+
buttonContainer.appendChild(toggleButton);
277+
278+
// 将按钮添加到下拉菜单旁边
279+
const languageDropdown = document.getElementById('language');
280+
if (languageDropdown && languageDropdown.parentNode) {
281+
languageDropdown.parentNode.insertBefore(buttonContainer, languageDropdown);
282+
}
283+
}
284+
68285
// 初始化主题选择器
69286
const hlStyleParam: StyleType = (getURLParameter('hl_style') as StyleType) || 'atom-one-dark';
70287
const hlStyleNameMap: Record<StyleType, string> = {
@@ -188,6 +405,13 @@ document.addEventListener('DOMContentLoaded', function() {
188405
}
189406
};
190407

408+
// 创建全局前缀切换按钮
409+
createGlobalPrefixToggleButton();
410+
191411
// 初始加载内容
192412
loadContent(languageParam);
193-
});
413+
});
414+
415+
// 暴露全局函数供外部调用
416+
(window as any).applyPrefixMode = applyPrefixMode;
417+
(window as any).toggleCodePrefix = toggleCodePrefix;

0 commit comments

Comments
 (0)