Skip to content

Commit 0f577ae

Browse files
authored
add confirmation step to bulk inputs (#698)
2 parents 4ec3d2f + 915bf6c commit 0f577ae

File tree

13 files changed

+963
-176
lines changed

13 files changed

+963
-176
lines changed

packages/common-ui/src/components/SkMouseTrap.vue

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
<template>
22
<v-dialog v-if="display" max-width="500px" transition="dialog-transition">
33
<template #activator="{ props }">
4-
<v-btn icon color="primary" v-bind="props"> ? </v-btn>
4+
<v-btn icon color="primary" v-bind="props">
5+
<v-icon>mdi-keyboard</v-icon>
6+
</v-btn>
57
</template>
68

79
<v-card>
8-
<v-toolbar color="teal" dark>
9-
<v-toolbar-title>Shortcut keys for this card:</v-toolbar-title>
10-
<v-spacer></v-spacer>
10+
<v-toolbar color="teal" dark dense>
11+
<v-toolbar-title class="text-subtitle-1">Shortcut keys:</v-toolbar-title>
1112
</v-toolbar>
12-
<v-list>
13-
<v-list-item v-for="hk in commands" :key="Array.isArray(hk.hotkey) ? hk.hotkey.join(',') : hk.hotkey">
14-
<v-btn variant="outlined" color="black">
13+
<v-list dense>
14+
<v-list-item
15+
v-for="hk in commands"
16+
:key="Array.isArray(hk.hotkey) ? hk.hotkey.join(',') : hk.hotkey"
17+
class="py-1"
18+
>
19+
<v-btn variant="outlined" color="primary" class="text-white" size="small">
1520
{{ Array.isArray(hk.hotkey) ? hk.hotkey[0] : hk.hotkey }}
1621
</v-btn>
1722
<v-spacer></v-spacer>
18-
<span class="text-right">
19-
{{ hk.command }}
20-
</span>
23+
<span class="text-caption ml-2">{{ hk.command }}</span>
2124
</v-list-item>
2225
</v-list>
2326
</v-card>

packages/platform-ui/src/utils/bulkImport/cardParser.ts renamed to packages/common/src/bulkImport/cardParser.ts

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ParsedCard } from './types';
1+
import { ParsedCard } from './types.js';
22

33
/**
44
* Configuration for the bulk card parser
@@ -25,12 +25,15 @@ export const CARD_DELIMITER = '\n---\n---\n';
2525

2626
/**
2727
* Parses a single card string into a structured object
28-
*
28+
*
2929
* @param cardString - Raw string containing card content
3030
* @param config - Optional parser configuration
3131
* @returns ParsedCard object or null if parsing fails
3232
*/
33-
export function parseCard(cardString: string, config: CardParserConfig = DEFAULT_PARSER_CONFIG): ParsedCard | null {
33+
export function parseCard(
34+
cardString: string,
35+
config: CardParserConfig = DEFAULT_PARSER_CONFIG
36+
): ParsedCard | null {
3437
const trimmedCardString = cardString.trim();
3538
if (!trimmedCardString) {
3639
return null;
@@ -40,18 +43,18 @@ export function parseCard(cardString: string, config: CardParserConfig = DEFAULT
4043
let tags: string[] = [];
4144
let elo: number | undefined = undefined;
4245
const markdownLines = [...lines];
43-
46+
4447
// Process the lines from bottom to top to handle metadata
4548
let metadataLines = 0;
46-
49+
4750
// Get the configured identifiers
4851
const tagId = config.tagIdentifier || DEFAULT_PARSER_CONFIG.tagIdentifier;
4952
const eloId = config.eloIdentifier || DEFAULT_PARSER_CONFIG.eloIdentifier;
50-
53+
5154
// Check the last few lines for metadata (tags and elo)
5255
for (let i = lines.length - 1; i >= 0 && i >= lines.length - 2; i--) {
5356
const line = lines[i].trim();
54-
57+
5558
// Check for tags
5659
if (line.toLowerCase().startsWith(tagId!.toLowerCase())) {
5760
tags = line
@@ -71,7 +74,7 @@ export function parseCard(cardString: string, config: CardParserConfig = DEFAULT
7174
metadataLines++;
7275
}
7376
}
74-
77+
7578
// Remove metadata lines from the end of the content
7679
if (metadataLines > 0) {
7780
markdownLines.splice(markdownLines.length - metadataLines);
@@ -82,29 +85,53 @@ export function parseCard(cardString: string, config: CardParserConfig = DEFAULT
8285
// Card must have some markdown content
8386
return null;
8487
}
85-
88+
8689
return { markdown, tags, elo };
8790
}
8891

8992
/**
9093
* Splits a bulk text input into individual card strings
91-
*
94+
*
9295
* @param bulkText - Raw string containing multiple cards
9396
* @returns Array of card strings
9497
*/
9598
export function splitCardsText(bulkText: string): string[] {
96-
return bulkText.split(CARD_DELIMITER)
97-
.map(card => card.trim())
98-
.filter(card => card); // Filter out empty strings
99+
return bulkText
100+
.split(CARD_DELIMITER)
101+
.map((card) => card.trim())
102+
.filter((card) => card); // Filter out empty strings
103+
}
104+
105+
/**
106+
* Parses a bulk text input into an array of structured ParsedCard objects.
107+
*
108+
* @param bulkText - Raw string containing multiple cards.
109+
* @param config - Optional parser configuration.
110+
* @returns Array of ParsedCard objects. Filters out cards that fail to parse.
111+
*/
112+
export function parseBulkTextToCards(
113+
bulkText: string,
114+
config: CardParserConfig = DEFAULT_PARSER_CONFIG
115+
): ParsedCard[] {
116+
const cardStrings = splitCardsText(bulkText);
117+
const parsedCards: ParsedCard[] = [];
118+
119+
for (const cardString of cardStrings) {
120+
const parsedCard = parseCard(cardString, config);
121+
if (parsedCard) {
122+
parsedCards.push(parsedCard);
123+
}
124+
}
125+
return parsedCards;
99126
}
100127

101128
/**
102129
* Validates if a bulk text input has valid format
103-
*
130+
*
104131
* @param bulkText - Raw string containing multiple cards
105132
* @returns true if valid, false otherwise
106133
*/
107134
export function isValidBulkFormat(bulkText: string): boolean {
108135
const cardStrings = splitCardsText(bulkText);
109-
return cardStrings.length > 0 && cardStrings.some(card => !!card.trim());
110-
}
136+
return cardStrings.length > 0 && cardStrings.some((card) => !!card.trim());
137+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// We no longer need to import DataShape since we've moved the interfaces that used it
2+
// import { DataShape } from '@vue-skuilder/common';
3+
4+
/**
5+
* Interface representing a parsed card from bulk import
6+
*/
7+
export interface ParsedCard {
8+
/** The markdown content of the card */
9+
markdown: string;
10+
/** Tags associated with the card */
11+
tags: string[];
12+
/** ELO rating for the card (optional) */
13+
elo?: number;
14+
}
15+
16+
/**
17+
* Interface for card data ready to be stored in the database
18+
*/
19+
export interface CardData {
20+
/** Card markdown content */
21+
Input: string;
22+
/** Card media uploads */
23+
Uploads: any[];
24+
/** Any additional fields can be added as needed */
25+
[key: string]: any;
26+
}
27+
28+
// ImportResult and BulkCardProcessorConfig have been moved to @vue-skuilder/db

packages/common/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ export * from './logshim.js';
66
export * from './validators.js';
77
export * from './fieldConverters.js';
88

9+
export * from './bulkImport/cardParser.js';
10+
export * from './bulkImport/types.js';
11+
912
// interfaces
1013
export * from './interfaces/index.js';
1114

packages/platform-ui/src/utils/bulkImport/cardProcessor.ts renamed to packages/db/src/core/bulkImport/cardProcessor.ts

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,41 @@
1-
import { Status } from '@vue-skuilder/common';
2-
import { CourseDBInterface } from '@vue-skuilder/db';
3-
import { ParsedCard, ImportResult, BulkCardProcessorConfig, CardData } from './types';
4-
import { parseCard, splitCardsText } from './cardParser';
1+
import { CourseElo, Status, ParsedCard, CardData } from '@vue-skuilder/common';
2+
import { CourseDBInterface } from '../../core/interfaces/courseDB';
3+
import { ImportResult, BulkCardProcessorConfig } from './types';
54

65
/**
76
* Processes multiple cards from bulk text input
87
*
9-
* @param bulkText - Raw text containing multiple cards
8+
* @param parsedCards - Array of parsed cards to import
109
* @param courseDB - Course database interface
1110
* @param config - Configuration for the card processor
1211
* @returns Array of import results
1312
*/
14-
export async function processBulkCards(
15-
bulkText: string,
13+
export async function importParsedCards(
14+
parsedCards: ParsedCard[],
1615
courseDB: CourseDBInterface,
1716
config: BulkCardProcessorConfig
1817
): Promise<ImportResult[]> {
1918
const results: ImportResult[] = [];
2019

21-
if (!bulkText.trim()) {
22-
return results;
23-
}
24-
25-
const cardStrings = splitCardsText(bulkText);
26-
if (cardStrings.length === 0) {
27-
results.push({
28-
originalText: bulkText,
29-
status: 'error',
30-
message: 'No valid card data found. Please check your input and delimiters.',
31-
});
32-
return results;
33-
}
34-
35-
for (const cardString of cardStrings) {
36-
const originalText = cardString.trim();
37-
if (!originalText) continue;
38-
39-
const parsed = parseCard(originalText);
40-
41-
if (!parsed) {
42-
results.push({
43-
originalText,
44-
status: 'error',
45-
message: 'Failed to parse card: Empty content after tag removal or invalid format.',
46-
});
47-
continue;
48-
}
49-
20+
for (const parsedCard of parsedCards) {
5021
try {
51-
const result = await processCard(parsed, courseDB, config);
22+
// processCard takes a ParsedCard and returns an ImportResult
23+
const result = await processCard(parsedCard, courseDB, config);
5224
results.push(result);
5325
} catch (error) {
5426
console.error('Error processing card:', error);
27+
// Reconstruct originalText from parsedCard for this specific catch block
28+
// This is a fallback if processCard itself throws an unhandled error.
29+
// Normally, processCard should return an ImportResult with status 'error'.
30+
let errorOriginalText = parsedCard.markdown;
31+
if (parsedCard.tags && parsedCard.tags.length > 0) {
32+
errorOriginalText += `\ntags: ${parsedCard.tags.join(', ')}`;
33+
}
34+
if (parsedCard.elo !== undefined) {
35+
errorOriginalText += `\nelo: ${parsedCard.elo}`;
36+
}
5537
results.push({
56-
originalText,
38+
originalText: errorOriginalText,
5739
status: 'error',
5840
message: `Error processing card: ${
5941
error instanceof Error ? error.message : 'Unknown error'
@@ -95,7 +77,7 @@ async function processCard(
9577
Uploads: [], // No uploads for bulk import
9678
};
9779

98-
const tagsElo = {};
80+
const tagsElo: CourseElo['tags'] = {};
9981
for (const tag of tags) {
10082
tagsElo[tag] = {
10183
score: elo || 0,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './cardProcessor.js';
2+
export * from './types.js';

packages/platform-ui/src/utils/bulkImport/types.ts renamed to packages/db/src/core/bulkImport/types.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,6 @@ export interface ImportResult {
1414
cardId?: string;
1515
}
1616

17-
/**
18-
* Interface representing a parsed card from bulk import
19-
*/
20-
export interface ParsedCard {
21-
/** The markdown content of the card */
22-
markdown: string;
23-
/** Tags associated with the card */
24-
tags: string[];
25-
/** ELO rating for the card (optional) */
26-
elo?: number;
27-
}
28-
29-
/**
30-
* Interface for card data ready to be stored in the database
31-
*/
32-
export interface CardData {
33-
/** Card markdown content */
34-
Input: string;
35-
/** Card media uploads */
36-
Uploads: any[];
37-
/** Any additional fields can be added as needed */
38-
[key: string]: any;
39-
}
40-
4117
/**
4218
* Configuration for the bulk card processor
4319
*/
@@ -48,4 +24,4 @@ export interface BulkCardProcessorConfig {
4824
courseCode: string;
4925
/** The username of the current user */
5026
userName: string;
51-
}
27+
}

packages/db/src/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './types/types-legacy';
55
export * from './types/user';
66
export * from '../util/Loggable';
77
export * from './util';
8+
export * from './bulkImport';

0 commit comments

Comments
 (0)