Skip to content

Commit 6816849

Browse files
authored
Merge: Support write for problem && Support export all notes as Markdown file
Feat: Support write for problem && Support export all notes as Markdown file
2 parents afce976 + 163abd2 commit 6816849

File tree

6 files changed

+514
-22
lines changed

6 files changed

+514
-22
lines changed

manifest.base.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
},
4848
{
4949
"matches": [
50-
"https://leetcode.com/*"
50+
"https://leetcode.com/problems/*"
5151
],
5252
"js": [
5353
"dist/leetcode.js"
@@ -56,7 +56,7 @@
5656
},
5757
{
5858
"matches": [
59-
"https://leetcode.cn/*"
59+
"https://leetcode.cn/problems/*"
6060
],
6161
"js": [
6262
"dist/leetcodecn.js"

popup.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,23 @@ <h3>Add Review Card</h3>
304304
>
305305
<small class="fa-solid fa-arrow-rotate-left fa-2xs"></small>
306306
</button>
307+
308+
<!-- 导出按钮 -->
309+
<button type="button" class="btn btn-outline-warning custom-btn export-ops-btn" id="exportNotesBtn"
310+
data-bs-toggle="tooltip" data-bs-title="export all notes" data-bs-placement="left"
311+
style="
312+
display: flex;
313+
justify-content: center;
314+
align-items: center;
315+
padding: 0.25rem 0.5rem;
316+
font-size: 0.875rem;
317+
margin-left: 0.5rem;
318+
"
319+
>
320+
<small class="fa-solid fa-file-export fa-2xs"></small>
321+
</button>
322+
323+
307324
</div>
308325
</nav>
309326
</div>

src/popup/handler/handlerRegister.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { setModeSwitchHandlers } from "./modeSwitchHandler";
33
import { setPageJumpHandlers } from "./pageJumpHandler"
44
import { setPopupUnloadHandler } from "./popupUnloadHandler";
55
import { setRecordOperationHandlers } from "./recordOperationHandler";
6+
import { setNoteHandlers } from "./noteHandler";
67

78
export const registerAllHandlers = () => {
89
setPageJumpHandlers();
910
setModeSwitchHandlers();
1011
setRecordOperationHandlers();
1112
setConfigJumpHandlers();
1213
setPopupUnloadHandler();
14+
setNoteHandlers();
1315
}

src/popup/handler/noteHandler.js

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
import { getLocalStorageData, setLocalStorageData } from "../delegate/localStorageDelegate";
2+
import { getAllProblems } from "../service/problemService";
3+
import { renderScheduledTableContent } from "../view/view";
4+
import { store } from "../store";
5+
6+
// 获取所有笔记
7+
const getAllNotes = async () => {
8+
try {
9+
const notes = await getLocalStorageData("notes");
10+
return notes || {};
11+
} catch (e) {
12+
console.error("获取笔记数据失败", e);
13+
return {}; // 返回空对象而不是抛出错误
14+
}
15+
};
16+
17+
// 同步笔记到存储
18+
const syncNotes = async (notes) => {
19+
if (!notes) {
20+
notes = await getAllNotes();
21+
}
22+
await setLocalStorageData("notes", notes);
23+
return notes;
24+
};
25+
26+
// 注册笔记相关事件处理
27+
export const setNoteHandlers = () => {
28+
console.log("注册笔记处理程序");
29+
30+
// 使用事件委托来处理笔记按钮点击
31+
document.removeEventListener('click', handleNoteButtonClick); // 先移除之前的监听器,避免重复
32+
document.addEventListener('click', handleNoteButtonClick);
33+
34+
// 注册保存笔记按钮事件
35+
const saveNoteBtn = document.getElementById('saveNoteBtn');
36+
if (saveNoteBtn) {
37+
console.log("找到保存按钮");
38+
saveNoteBtn.addEventListener('click', saveNote);
39+
} else {
40+
console.error("找不到保存按钮");
41+
}
42+
43+
// 注册取消按钮事件
44+
const cancelBtns = document.querySelectorAll('[data-bs-dismiss="modal"]');
45+
if (cancelBtns.length > 0) {
46+
console.log("找到取消按钮");
47+
cancelBtns.forEach(btn => {
48+
btn.addEventListener('click', () => {
49+
// 关闭模态框
50+
const noteModal = document.getElementById('noteModal');
51+
if (noteModal) {
52+
noteModal.style.display = 'none';
53+
noteModal.classList.remove('show');
54+
}
55+
});
56+
});
57+
} else {
58+
console.error("找不到取消按钮");
59+
}
60+
61+
// 注册导出笔记按钮事件
62+
const exportNotesBtn = document.getElementById('exportNotesBtn');
63+
if (exportNotesBtn) {
64+
console.log("找到导出按钮");
65+
exportNotesBtn.addEventListener('click', exportAllNotes);
66+
} else {
67+
console.error("找不到导出按钮");
68+
}
69+
70+
// 初始化工具提示
71+
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
72+
tooltipTriggerList.map(function (tooltipTriggerEl) {
73+
return new bootstrap.Tooltip(tooltipTriggerEl);
74+
});
75+
}
76+
77+
// 单独定义处理函数,便于移除
78+
const handleNoteButtonClick = (e) => {
79+
const noteButton = e.target.closest('.note-btn-mark');
80+
if (noteButton) {
81+
console.log("点击了笔记按钮", noteButton);
82+
console.log("按钮元素:", noteButton);
83+
console.log("data-id属性:", noteButton.getAttribute('data-id'));
84+
85+
const problemIndex = noteButton.getAttribute('data-id');
86+
if (problemIndex) {
87+
openNoteModal(problemIndex);
88+
} else {
89+
console.error("笔记按钮没有 data-id 属性");
90+
}
91+
}
92+
};
93+
94+
// 打开笔记模态框
95+
const openNoteModal = async (problemIndex) => {
96+
try {
97+
console.log("打开笔记模态框,问题索引:", problemIndex);
98+
99+
// 使用 getAllProblems 获取问题数据
100+
const problems = await getAllProblems();
101+
const problem = problems[problemIndex];
102+
103+
// 如果没有找到问题数据
104+
if (!problem) {
105+
console.error("找不到问题数据:", problemIndex);
106+
return;
107+
}
108+
109+
// 获取笔记数据
110+
const notes = await getAllNotes();
111+
const noteData = notes[problemIndex];
112+
113+
console.log("问题数据:", problem);
114+
115+
// 使用自定义方式打开模态框
116+
const noteModal = document.getElementById('noteModal');
117+
if (!noteModal) {
118+
console.error("找不到模态框元素");
119+
return;
120+
}
121+
122+
// 显示模态框
123+
noteModal.style.display = 'block';
124+
noteModal.classList.add('show');
125+
126+
// 设置问题索引到隐藏字段
127+
const problemIndexInput = document.getElementById('problemIndex');
128+
if (problemIndexInput) {
129+
problemIndexInput.value = problemIndex;
130+
} else {
131+
console.error("找不到问题索引输入框");
132+
}
133+
134+
// 设置问题名称 - 使用 innerHTML 直接设置
135+
const problemNameContainer = document.querySelector('.modal-body .mb-3:first-of-type');
136+
if (problemNameContainer) {
137+
// 如果有自定义名称,优先使用自定义名称
138+
const customName = noteData && typeof noteData === 'object' ? noteData.customName : undefined;
139+
const problemName = customName || problem.name || "未知问题";
140+
141+
problemNameContainer.innerHTML = `
142+
<label for="noteProblemName" class="form-label">问题名称 (Problem Name)</label>
143+
<input type="text" class="form-control" id="noteProblemName" value="${problemName}" placeholder="${problem.name || '未知问题'}" style="color: #000 !important; background-color: #fff !important;">
144+
`;
145+
console.log("重新创建了问题名称输入框,值为:", problemName);
146+
} else {
147+
console.error("找不到问题名称容器");
148+
}
149+
150+
// 设置笔记内容
151+
const noteContentTextarea = document.getElementById('noteContent');
152+
if (noteContentTextarea) {
153+
noteContentTextarea.value = noteData ? (typeof noteData === 'object' ? noteData.content : noteData) : '';
154+
} else {
155+
console.error("找不到笔记内容文本框");
156+
}
157+
158+
// 设置焦点到文本区域
159+
setTimeout(() => {
160+
if (document.getElementById('noteContent')) {
161+
document.getElementById('noteContent').focus();
162+
}
163+
}, 100);
164+
} catch (e) {
165+
console.error("打开笔记模态框失败", e);
166+
alert("打开笔记失败,请查看控制台获取详细错误信息");
167+
}
168+
}
169+
170+
// 保存笔记
171+
const saveNote = async () => {
172+
try {
173+
const problemIndex = document.getElementById('problemIndex').value;
174+
const problemNameInput = document.getElementById('noteProblemName');
175+
const noteContent = document.getElementById('noteContent').value;
176+
177+
// 获取用户输入的问题名称,如果输入框为空则使用占位符
178+
let problemName = "";
179+
if (problemNameInput) {
180+
problemName = problemNameInput.value.trim() || problemNameInput.getAttribute('placeholder') || "";
181+
}
182+
183+
console.log("保存笔记,问题索引:", problemIndex);
184+
console.log("保存笔记,问题名称:", problemName);
185+
186+
const notes = await getAllNotes();
187+
188+
// 使用 getAllProblems 获取问题数据
189+
const problems = await getAllProblems();
190+
const problem = problems[problemIndex];
191+
192+
if (!problem) {
193+
console.error("找不到问题数据:", problemIndex);
194+
return;
195+
}
196+
197+
console.log("原问题名称:", problem.name);
198+
199+
// 如果笔记为空,则删除该条目
200+
if (noteContent.trim() === '') {
201+
delete notes[problemIndex];
202+
} else {
203+
// 保存笔记内容和用户输入的问题名称
204+
notes[problemIndex] = {
205+
content: noteContent,
206+
customName: problemName !== problem.name ? problemName : undefined
207+
};
208+
}
209+
210+
// 保存到本地存储
211+
await syncNotes(notes);
212+
213+
// 清除焦点
214+
document.activeElement?.blur();
215+
216+
// 关闭模态框
217+
const noteModal = document.getElementById('noteModal');
218+
noteModal.style.display = 'none';
219+
noteModal.classList.remove('show');
220+
221+
// 获取最新的问题数据
222+
const allProblems = await getAllProblems();
223+
224+
// 先销毁所有现有的工具提示
225+
const existingTooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
226+
existingTooltips.forEach(el => {
227+
const tooltip = bootstrap.Tooltip.getInstance(el);
228+
if (tooltip) {
229+
tooltip.dispose();
230+
}
231+
});
232+
233+
// 刷新表格以更新笔记图标和问题名称
234+
await renderScheduledTableContent(store.reviewScheduledProblems, store.scheduledPage);
235+
236+
// 重新初始化工具提示
237+
setTimeout(() => {
238+
// 确保先销毁所有可能存在的工具提示实例
239+
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
240+
tooltipTriggerList.forEach(el => {
241+
// 创建新的工具提示实例
242+
new bootstrap.Tooltip(el, {
243+
trigger: 'hover', // 只在悬停时显示
244+
container: 'body', // 将工具提示附加到 body
245+
boundary: 'window' // 确保工具提示不会超出窗口边界
246+
});
247+
});
248+
249+
// 重新注册事件监听器
250+
setNoteHandlers();
251+
}, 200); // 增加延迟时间确保 DOM 完全更新
252+
253+
console.log("笔记已保存");
254+
} catch (e) {
255+
console.error("保存笔记失败", e);
256+
alert("保存笔记失败,请查看控制台获取详细错误信息");
257+
}
258+
}
259+
260+
// 导出所有笔记
261+
const exportAllNotes = async () => {
262+
try {
263+
// 使用 getAllProblems 获取问题数据
264+
const problems = await getAllProblems();
265+
const notes = await getAllNotes();
266+
let notesContent = "# LeetCode Mastery Scheduler notes\n\n";
267+
notesContent += "开源仓库链接/repo url: https://github.com/xiaohajiayou/Leetcode-Mastery-Scheduler" + "\n\n";
268+
269+
// 筛选有笔记的问题
270+
const problemIndicesWithNotes = Object.keys(notes).filter(index =>
271+
problems[index] && !problems[index].isDeleted &&
272+
(typeof notes[index] === 'string' ? notes[index].trim().length > 0 :
273+
(notes[index].content && notes[index].content.trim().length > 0))
274+
);
275+
276+
if (problemIndicesWithNotes.length === 0) {
277+
alert("没有找到任何笔记!");
278+
return;
279+
}
280+
281+
// 按问题名称排序
282+
problemIndicesWithNotes.sort((a, b) =>
283+
(problems[a].name || "").localeCompare(problems[b].name || "")
284+
);
285+
286+
// 生成markdown格式的笔记内容
287+
problemIndicesWithNotes.forEach(index => {
288+
const problem = problems[index];
289+
const noteData = notes[index];
290+
const noteContent = typeof noteData === 'string' ? noteData : noteData.content;
291+
const problemName = (typeof noteData === 'object' && noteData.customName) || problem.name || "未命名问题";
292+
293+
notesContent += `## ${problemName}\n\n`;
294+
notesContent += `- 难度: ${problem.level || '未知'}\n`;
295+
notesContent += `- 链接: ${problem.url || '#'}\n\n`;
296+
notesContent += `### 笔记\n\n${noteContent}\n\n---\n\n`;
297+
});
298+
299+
// 创建下载链接
300+
const blob = new Blob([notesContent], { type: 'text/markdown' });
301+
const url = URL.createObjectURL(blob);
302+
const a = document.createElement('a');
303+
a.href = url;
304+
a.download = `leetcode_notes_${new Date().toISOString().slice(0, 10)}.md`;
305+
document.body.appendChild(a);
306+
a.click();
307+
document.body.removeChild(a);
308+
URL.revokeObjectURL(url);
309+
310+
console.log("笔记已导出");
311+
} catch (e) {
312+
console.error("导出笔记失败", e);
313+
alert("导出笔记失败,请查看控制台获取详细错误信息");
314+
}
315+
}

0 commit comments

Comments
 (0)