Skip to content

Commit be01cc0

Browse files
committed
Feature: support add blank card for ertra problems
1 parent 53a6840 commit be01cc0

File tree

3 files changed

+260
-30
lines changed

3 files changed

+260
-30
lines changed

popup.html

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,47 @@
111111
<!-- 添加新建题目的弹窗 -->
112112
<div id="addProblemDialog" class="modal" style="display: none;">
113113
<div class="modal-content">
114-
<h3>Add New Problem</h3>
115-
<div class="form-group">
116-
<label for="problemUrl">Problem URL:</label>
117-
<input type="text" id="problemUrl" class="form-control"
118-
placeholder="https://leetcode.com/problems/..." />
114+
<h3>Add Review Card</h3>
115+
116+
<!-- 选项卡切换 -->
117+
<div class="tab-container">
118+
<div class="tab-buttons">
119+
<button id="urlTabButton" class="tab-button active">LeetCode Card</button>
120+
<button id="manualTabButton" class="tab-button">Blank card</button>
121+
</div>
122+
</div>
123+
124+
<!-- URL 输入表单 -->
125+
<div id="urlTab" class="tab-content active">
126+
<div class="form-group">
127+
<label for="problemUrl">Leetcode URL:</label>
128+
<input type="text" id="problemUrl" class="form-control"
129+
placeholder="https://leetcode.com/problems/..." />
130+
</div>
131+
</div>
132+
133+
<!-- 手动输入表单 -->
134+
<div id="manualTab" class="tab-content">
135+
<div class="form-group">
136+
<label for="problemName">Problem Name:</label>
137+
<input type="text" id="problemName" class="form-control"
138+
placeholder="例如: Two Sum" />
139+
</div>
140+
<div class="form-group">
141+
<label for="problemLevel">Level:</label>
142+
<select id="problemLevel" class="form-control">
143+
<option value="Easy">Easy</option>
144+
<option value="Medium">Medium</option>
145+
<option value="Hard">Hard</option>
146+
</select>
147+
</div>
148+
<div class="form-group">
149+
<label for="customUrl">Custom URL (Optional):</label>
150+
<input type="text" id="customUrl" class="form-control"
151+
placeholder="https://..." />
152+
</div>
119153
</div>
154+
120155
<div class="button-group">
121156
<button id="confirmAdd" class="btn btn-outline-warning custom-btn">Add</button>
122157
<button id="cancelAdd" class="btn btn-outline-secondary custom-btn">Cancel</button>

src/popup/daily-review.js

Lines changed: 152 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { renderAll } from './view/view.js';
22
import { getAllProblems, syncProblems } from "./service/problemService.js";
33
import { getLevelColor,getCurrentRetrievability } from './util/utils.js';
4-
import { handleFeedbackSubmission } from './script/submission.js';
4+
import { handleFeedbackSubmission, handleAddBlankProblem } from './script/submission.js';
55
import './popup.css';
66
import { isCloudSyncEnabled, loadConfigs, setCloudSyncEnabled, setProblemSorter,setDefaultCardLimit,setReminderEnabled } from "./service/configService";
77
import { store,daily_store } from './store';
@@ -640,21 +640,36 @@ async function loadProblemList() {
640640
}
641641

642642

643-
// 显示/隐藏弹窗
644-
function toggleAddProblemDialog(show = true) {
643+
// 显示/隐藏添加题目弹窗
644+
function toggleAddProblemDialog(show) {
645645
const dialog = document.getElementById('addProblemDialog');
646-
if (!dialog) {
647-
console.error('找不到添加题目弹窗');
648-
return;
649-
}
650-
651-
dialog.style.display = show ? 'block' : 'none';
646+
if (!dialog) return;
652647

653-
if (!show) {
654-
// 重置表单
655-
const urlInput = document.getElementById('problemUrl');
656-
if (urlInput) {
657-
urlInput.value = '';
648+
if (show) {
649+
dialog.style.display = 'block';
650+
} else {
651+
dialog.style.display = 'none';
652+
653+
// 清除所有输入字段
654+
const problemUrl = document.getElementById('problemUrl');
655+
const problemName = document.getElementById('problemName');
656+
const customUrl = document.getElementById('customUrl');
657+
658+
if (problemUrl) problemUrl.value = '';
659+
if (problemName) problemName.value = '';
660+
if (customUrl) customUrl.value = '';
661+
662+
// 重置选项卡到默认状态
663+
const urlTabButton = document.getElementById('urlTabButton');
664+
const manualTabButton = document.getElementById('manualTabButton');
665+
const urlTab = document.getElementById('urlTab');
666+
const manualTab = document.getElementById('manualTab');
667+
668+
if (urlTabButton && manualTabButton && urlTab && manualTab) {
669+
urlTabButton.classList.add('active');
670+
manualTabButton.classList.remove('active');
671+
urlTab.classList.add('active');
672+
manualTab.classList.remove('active');
658673
}
659674
}
660675
}
@@ -666,11 +681,98 @@ function initializeAddProblem() {
666681
const addButton = document.querySelector('.gear-button.add-problem');
667682
if (!addButton) return;
668683

684+
// 添加选项卡切换样式
685+
const style = document.createElement('style');
686+
style.textContent = `
687+
.tab-container {
688+
margin-bottom: 15px;
689+
}
690+
691+
.tab-buttons {
692+
display: flex;
693+
border-bottom: 1px solid #3a4a5c;
694+
margin-bottom: 15px;
695+
}
696+
697+
.tab-button {
698+
background: none;
699+
border: none;
700+
padding: 8px 15px;
701+
color: #a0aec0;
702+
cursor: pointer;
703+
transition: all 0.3s;
704+
border-bottom: 2px solid transparent;
705+
}
706+
707+
.tab-button.active {
708+
color: #4a9d9c;
709+
border-bottom: 2px solid #4a9d9c;
710+
}
711+
712+
.tab-content {
713+
display: none;
714+
}
715+
716+
.tab-content.active {
717+
display: block;
718+
}
719+
720+
/* 修复弹窗背景色 - 使用更强的选择器 */
721+
#addProblemDialog .modal-content {
722+
background-color: #1d2e3d !important;
723+
color: #ffffff !important;
724+
}
725+
726+
#addProblemDialog .tab-content,
727+
#addProblemDialog .form-group {
728+
background-color: #1d2e3d !important;
729+
color: #ffffff !important;
730+
}
731+
732+
#addProblemDialog input.form-control,
733+
#addProblemDialog select.form-control {
734+
background-color: #2d3e4d !important;
735+
color: #ffffff !important;
736+
border: 1px solid #3a4a5c !important;
737+
}
738+
739+
#addProblemDialog input.form-control::placeholder {
740+
color: #8096a8 !important;
741+
}
742+
743+
#addProblemDialog label {
744+
color: #a0aec0 !important;
745+
}
746+
`;
747+
document.head.appendChild(style);
748+
669749
// 点击添加按钮显示弹窗
670750
addButton.addEventListener('click', () => {
671751
toggleAddProblemDialog(true);
672752
});
673753

754+
// 选项卡切换功能
755+
const urlTabButton = document.getElementById('urlTabButton');
756+
const manualTabButton = document.getElementById('manualTabButton');
757+
const urlTab = document.getElementById('urlTab');
758+
const manualTab = document.getElementById('manualTab');
759+
760+
if (urlTabButton && manualTabButton) {
761+
urlTabButton.addEventListener('click', () => {
762+
urlTabButton.classList.add('active');
763+
manualTabButton.classList.remove('active');
764+
urlTab.classList.add('active');
765+
manualTab.classList.remove('active');
766+
});
767+
768+
manualTabButton.addEventListener('click', () => {
769+
manualTabButton.classList.add('active');
770+
urlTabButton.classList.remove('active');
771+
manualTab.classList.add('active');
772+
urlTab.classList.remove('active');
773+
});
774+
}
775+
674776
// 取消按钮
675777
const cancelButton = document.getElementById('cancelAdd');
676778
if (cancelButton) {
@@ -683,27 +785,54 @@ function initializeAddProblem() {
683785
const confirmButton = document.getElementById('confirmAdd');
684786
if (confirmButton) {
685787
confirmButton.addEventListener('click', async () => {
686-
const urlInput = document.getElementById('problemUrl');
687-
688-
const url = urlInput.value.trim();
689-
690788
try {
691-
await handleAddProblem(url);
789+
let result;
790+
791+
// 判断当前激活的是哪个选项卡
792+
if (urlTab.classList.contains('active')) {
793+
// 从URL添加
794+
const url = document.getElementById('problemUrl').value.trim();
795+
if (!url) {
796+
throw new Error('Please enter a valid problem URL.');
797+
}
798+
result = await handleAddProblem(url);
799+
} else {
800+
// 创建空白卡片
801+
const name = document.getElementById('problemName').value.trim();
802+
const level = document.getElementById('problemLevel').value;
803+
const customUrl = document.getElementById('customUrl').value.trim();
804+
805+
if (!name) {
806+
throw new Error('Please enter the problem name.');
807+
}
808+
809+
if (!level) {
810+
throw new Error('Please select a difficulty level.');
811+
}
812+
813+
// 如果提供了URL,检查其格式是否有效
814+
if (customUrl && !customUrl.match(/^https?:\/\/.+/)) {
815+
throw new Error('Please enter a valid URL starting with http:// or https://');
816+
}
817+
818+
result = await handleAddBlankProblem(name, level, customUrl);
819+
}
820+
692821
toggleAddProblemDialog(false);
693822
await loadDailyReviewData();
694823
updateCardDisplay();
695824

696825
// 显示成功提示
697826
Swal.fire({
698827
icon: 'success',
699-
title: '添加成功',
700-
text: '题目已成功添加到复习列表',
828+
title: 'SUCCESS',
829+
text: 'Problem added to review list.',
701830
showConfirmButton: false,
702831
timer: 1500,
703832
background: '#1d2e3d',
704833
color: '#ffffff',
705834
toast: true,
706-
position: 'top-end',
835+
position: 'center-end',
707836
customClass: {
708837
popup: 'colored-toast'
709838
}
@@ -712,7 +841,7 @@ function initializeAddProblem() {
712841
// 显示错误提示
713842
Swal.fire({
714843
icon: 'error',
715-
title: '添加失败',
844+
title: 'ADD FAIL',
716845
text: error.message,
717846
background: '#1d2e3d',
718847
color: '#ffffff',

src/popup/script/submission.js

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export async function handleAddProblem(url) {
467467

468468
// 检查是否已存在
469469
if (problems[problemIndex] && !problems[problemIndex].isDeleted) {
470-
throw new Error('该题目已存在');
470+
throw new Error('Duplicate problem name exists.');
471471
}
472472

473473
const now = Date.now();
@@ -504,7 +504,73 @@ export async function handleAddProblem(url) {
504504

505505
return problem;
506506
} catch (error) {
507-
console.error('添加题目失败:', error);
507+
console.error('Failed to add card:', error);
508+
throw error;
509+
}
510+
}
511+
512+
// 处理添加空白卡片
513+
export async function handleAddBlankProblem(name, level, customUrl = '') {
514+
try {
515+
await syncProblems(); // 同步云端数据
516+
const problems = await getAllProblems();
517+
518+
// 获取当前自定义题目的数量,用于生成递增的索引
519+
const customProblems = Object.values(problems).filter(p =>
520+
p.index && p.index.startsWith('custom_') && !p.isDeleted);
521+
const customCount = customProblems.length + 1;
522+
523+
// 生成有规律的索引: custom_年月日_序号
524+
const today = new Date();
525+
const dateStr = `${today.getFullYear()}${String(today.getMonth() + 1).padStart(2, '0')}${String(today.getDate()).padStart(2, '0')}`;
526+
const customIndex = `custom_${dateStr}_${String(customCount).padStart(3, '0')}`;
527+
528+
// 检查名称是否已存在
529+
const existingProblem = Object.values(problems).find(p =>
530+
p.name === name && !p.isDeleted);
531+
532+
if (existingProblem) {
533+
throw new Error('Duplicate problem name exists.');
534+
}
535+
536+
const now = Date.now();
537+
// 创建新问题,在名称前添加索引前缀
538+
const formattedName = `Ext-${customCount}. ${name}`;
539+
540+
const problem = new Problem(
541+
customIndex,
542+
formattedName, // 名称前添加索引前缀
543+
level,
544+
customUrl,
545+
now, // createTime
546+
0, // nextStep
547+
null // lastReviewTime
548+
);
549+
550+
// 设置初始状态
551+
problem.proficiency = 0;
552+
problem.isDeleted = false;
553+
problem.modificationTime = now;
554+
problem.isCustom = true; // 标记为自定义题目
555+
556+
// 设置初始 FSRS 状态 - 设置 nextReview 为今天
557+
problem.fsrsState = {
558+
difficulty: null,
559+
stability: null,
560+
state: 'New',
561+
lastReview: null,
562+
nextReview: now, // 设置为当前时间,使其显示在今天的待复习列表中
563+
reviewCount: 0,
564+
lapses: 0,
565+
quality: null
566+
};
567+
568+
await createOrUpdateProblem(problem);
569+
await syncProblems();
570+
571+
return problem;
572+
} catch (error) {
573+
console.error('Failed to add blank card:', error);
508574
throw error;
509575
}
510576
}

0 commit comments

Comments
 (0)