Skip to content

Commit 781e0cf

Browse files
committed
refactor: bulk card processing logic to own files
1 parent 72f6c4b commit 781e0cf

File tree

5 files changed

+377
-111
lines changed

5 files changed

+377
-111
lines changed

packages/platform-ui/src/components/Edit/BulkImportView.vue

Lines changed: 41 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,7 @@ import { BlanksCardDataShapes } from '@vue-skuilder/courses';
7575
import { getCurrentUser } from '@vue-skuilder/common-ui';
7676
import { getDataLayer, CourseDBInterface } from '@vue-skuilder/db';
7777
import { alertUser } from '@vue-skuilder/common-ui'; // For user feedback
78-
79-
interface ImportResult {
80-
originalText: string;
81-
status: 'success' | 'error';
82-
message: string;
83-
cardId?: string;
84-
}
85-
86-
interface ParsedCard {
87-
markdown: string;
88-
tags: string[];
89-
}
78+
import { ImportResult, processBulkCards, validateProcessorConfig, isValidBulkFormat } from '@/utils/bulkImport';
9079
9180
export default defineComponent({
9281
name: 'BulkImportView',
@@ -125,36 +114,6 @@ export default defineComponent({
125114
}
126115
},
127116
methods: {
128-
parseCard(cardString: string): ParsedCard | null {
129-
const trimmedCardString = cardString.trim();
130-
if (!trimmedCardString) {
131-
return null;
132-
}
133-
134-
const lines = trimmedCardString.split('\n');
135-
let tags: string[] = [];
136-
const markdownLines = [...lines];
137-
138-
if (lines.length > 0) {
139-
const lastLine = lines[lines.length - 1].trim();
140-
if (lastLine.toLowerCase().startsWith('tags:')) {
141-
tags = lastLine
142-
.substring(5)
143-
.split(',')
144-
.map((tag) => tag.trim())
145-
.filter((tag) => tag);
146-
markdownLines.pop(); // Remove the tags line
147-
}
148-
}
149-
150-
const markdown = markdownLines.join('\n').trim();
151-
if (!markdown) {
152-
// Card must have some markdown content
153-
return null;
154-
}
155-
return { markdown, tags };
156-
},
157-
158117
async processCards() {
159118
if (!this.courseDB) {
160119
alertUser({
@@ -164,7 +123,11 @@ export default defineComponent({
164123
this.processing = false;
165124
return;
166125
}
167-
if (!this.bulkText.trim()) return;
126+
127+
if (!isValidBulkFormat(this.bulkText)) {
128+
this.processing = false;
129+
return;
130+
}
168131
169132
// Validate that we have datashapes in the course config
170133
if (!this.courseCfg?.dataShapes || this.courseCfg.dataShapes.length === 0) {
@@ -179,8 +142,6 @@ export default defineComponent({
179142
this.processing = true;
180143
this.results = [];
181144
182-
const cardDelimiter = '\n---\n---\n';
183-
const cardStrings = this.bulkText.split(cardDelimiter);
184145
const currentUser = await getCurrentUser();
185146
const userName = currentUser.getUsername();
186147
@@ -205,80 +166,49 @@ export default defineComponent({
205166
dataShapeToUse: dataShapeToUse.name,
206167
});
207168
208-
for (const cardString of cardStrings) {
209-
const originalText = cardString.trim();
210-
if (!originalText) continue;
211-
212-
const parsed = this.parseCard(originalText);
213-
214-
if (!parsed) {
215-
this.results.push({
216-
originalText,
217-
status: 'error',
218-
message: 'Failed to parse card: Empty content after tag removal or invalid format.',
219-
});
220-
continue;
221-
}
222-
223-
const { markdown, tags } = parsed;
224-
225-
// The BlanksCardDataShapes expects an 'Input' field for markdown
226-
// and an 'Uploads' field for media.
227-
const cardData = {
228-
Input: markdown,
229-
Uploads: [], // As per requirement, no uploads for bulk import
230-
};
231-
232-
try {
233-
// Extract course code from first dataShape in course config
234-
const configDataShape = this.courseCfg?.dataShapes?.[0];
235-
if (!configDataShape) {
236-
throw new Error('No data shapes found in course configuration');
237-
}
169+
// Extract course code from first dataShape in course config
170+
const configDataShape = this.courseCfg?.dataShapes?.[0];
171+
if (!configDataShape) {
172+
this.results.push({
173+
originalText: 'N/A - Configuration Error',
174+
status: 'error',
175+
message: 'No data shapes found in course configuration',
176+
});
177+
this.processing = false;
178+
return;
179+
}
238180
239-
const codeCourse = NameSpacer.getDataShapeDescriptor(configDataShape.name).course;
240-
console.log(`[BulkImportView] Using codeCourse: ${codeCourse} for note addition`);
181+
const codeCourse = NameSpacer.getDataShapeDescriptor(configDataShape.name).course;
182+
console.log(`[BulkImportView] Using codeCourse: ${codeCourse} for note addition`);
241183
242-
const result = await this.courseDB.addNote(
243-
codeCourse,
244-
dataShapeToUse,
245-
cardData,
246-
userName,
247-
tags,
248-
undefined, // deck
249-
undefined // elo
250-
);
184+
// Prepare processor configuration
185+
const config = {
186+
dataShape: dataShapeToUse,
187+
courseCode: codeCourse,
188+
userName: userName,
189+
};
251190
252-
if (result.status === Status.ok) {
253-
this.results.push({
254-
originalText,
255-
status: 'success',
256-
message: 'Card added successfully.',
257-
cardId: result.id ? result.id : '(unknown)',
258-
});
259-
} else {
260-
this.results.push({
261-
originalText,
262-
status: 'error',
263-
message: result.message || 'Failed to add card to database. Unknown error.',
264-
});
265-
}
266-
} catch (error) {
267-
console.error('Error adding note:', error);
268-
this.results.push({
269-
originalText,
270-
status: 'error',
271-
message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,
272-
});
273-
}
191+
// Validate processor configuration
192+
const validation = validateProcessorConfig(config);
193+
if (!validation.isValid) {
194+
this.results.push({
195+
originalText: 'N/A - Configuration Error',
196+
status: 'error',
197+
message: validation.errorMessage || 'Invalid processor configuration',
198+
});
199+
this.processing = false;
200+
return;
274201
}
275202
276-
if (this.results.length === 0 && cardStrings.length > 0 && cardStrings.every((s) => !s.trim())) {
277-
// This case handles if bulkText only contained delimiters or whitespace
203+
// Process the cards
204+
try {
205+
this.results = await processBulkCards(this.bulkText, this.courseDB, config);
206+
} catch (error) {
207+
console.error('[BulkImportView] Error processing cards:', error);
278208
this.results.push({
279209
originalText: this.bulkText,
280210
status: 'error',
281-
message: 'No valid card data found. Please check your input and delimiters.',
211+
message: `Error processing cards: ${error instanceof Error ? error.message : 'Unknown error'}`,
282212
});
283213
}
284214
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { ParsedCard } from './types';
2+
3+
/**
4+
* Configuration for the bulk card parser
5+
*/
6+
export interface CardParserConfig {
7+
/** Custom tag identifier (defaults to 'tags:') */
8+
tagIdentifier?: string;
9+
}
10+
11+
/**
12+
* Default configuration for the card parser
13+
*/
14+
const DEFAULT_PARSER_CONFIG: CardParserConfig = {
15+
tagIdentifier: 'tags:',
16+
};
17+
18+
/**
19+
* Card delimiter used to separate cards in bulk input
20+
*/
21+
export const CARD_DELIMITER = '\n---\n---\n';
22+
23+
/**
24+
* Parses a single card string into a structured object
25+
*
26+
* @param cardString - Raw string containing card content
27+
* @param config - Optional parser configuration
28+
* @returns ParsedCard object or null if parsing fails
29+
*/
30+
export function parseCard(cardString: string, config: CardParserConfig = DEFAULT_PARSER_CONFIG): ParsedCard | null {
31+
const trimmedCardString = cardString.trim();
32+
if (!trimmedCardString) {
33+
return null;
34+
}
35+
36+
const lines = trimmedCardString.split('\n');
37+
let tags: string[] = [];
38+
const markdownLines = [...lines];
39+
40+
if (lines.length > 0) {
41+
const lastLine = lines[lines.length - 1].trim();
42+
const tagId = config.tagIdentifier || DEFAULT_PARSER_CONFIG.tagIdentifier;
43+
44+
if (lastLine.toLowerCase().startsWith(tagId!.toLowerCase())) {
45+
tags = lastLine
46+
.substring(tagId!.length)
47+
.split(',')
48+
.map((tag) => tag.trim())
49+
.filter((tag) => tag);
50+
markdownLines.pop(); // Remove the tags line
51+
}
52+
}
53+
54+
const markdown = markdownLines.join('\n').trim();
55+
if (!markdown) {
56+
// Card must have some markdown content
57+
return null;
58+
}
59+
60+
return { markdown, tags };
61+
}
62+
63+
/**
64+
* Splits a bulk text input into individual card strings
65+
*
66+
* @param bulkText - Raw string containing multiple cards
67+
* @returns Array of card strings
68+
*/
69+
export function splitCardsText(bulkText: string): string[] {
70+
return bulkText.split(CARD_DELIMITER)
71+
.map(card => card.trim())
72+
.filter(card => card); // Filter out empty strings
73+
}
74+
75+
/**
76+
* Validates if a bulk text input has valid format
77+
*
78+
* @param bulkText - Raw string containing multiple cards
79+
* @returns true if valid, false otherwise
80+
*/
81+
export function isValidBulkFormat(bulkText: string): boolean {
82+
const cardStrings = splitCardsText(bulkText);
83+
return cardStrings.length > 0 && cardStrings.some(card => !!card.trim());
84+
}

0 commit comments

Comments
 (0)