Skip to content

Commit ff4a8b0

Browse files
author
HugoFara
committed
feat(multi-word): working multi-word system based on selection (#142)
1 parent 48d723d commit ff4a8b0

File tree

7 files changed

+77
-9
lines changed

7 files changed

+77
-9
lines changed

src/backend/Api/V1/Handlers/TermHandler.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -606,11 +606,38 @@ public function getMultiWordForEdit(int $textId, int $position, ?string $text =
606606
];
607607
}
608608

609-
// New multi-word expression
609+
// Check if text is provided
610610
if ($text === null || $text === '') {
611611
return ['error' => 'Multi-word text is required for new expressions'];
612612
}
613613

614+
// Try to find existing term by text (case-insensitive)
615+
$textLc = mb_strtolower($text, 'UTF-8');
616+
$existingWord = QueryBuilder::table('words')
617+
->select(['WoID', 'WoText', 'WoTranslation', 'WoRomanization', 'WoSentence', 'WoStatus', 'WoWordCount'])
618+
->where('WoTextLC', '=', $textLc)
619+
->where('WoLgID', '=', $lgid)
620+
->where('WoWordCount', '>', 1)
621+
->firstPrepared();
622+
623+
if ($existingWord) {
624+
// Found existing multi-word term
625+
return [
626+
'id' => (int) $existingWord['WoID'],
627+
'text' => $existingWord['WoText'],
628+
'textLc' => $textLc,
629+
'translation' => $existingWord['WoTranslation'] ?? '',
630+
'romanization' => $existingWord['WoRomanization'] ?? '',
631+
'sentence' => $existingWord['WoSentence'] ?? '',
632+
'notes' => '',
633+
'status' => (int) $existingWord['WoStatus'],
634+
'langId' => $lgid,
635+
'wordCount' => (int) $existingWord['WoWordCount'],
636+
'isNew' => false
637+
];
638+
}
639+
640+
// New multi-word expression
614641
// Get sentence at position
615642
$sentence = $this->wordService->getSentenceTextAtPosition($textId, $position);
616643

@@ -620,7 +647,7 @@ public function getMultiWordForEdit(int $textId, int $position, ?string $text =
620647
return [
621648
'id' => null,
622649
'text' => $text,
623-
'textLc' => mb_strtolower($text, 'UTF-8'),
650+
'textLc' => $textLc,
624651
'translation' => '',
625652
'romanization' => '',
626653
'sentence' => $sentence ?? '',

src/backend/Api/V1/Handlers/TextHandler.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@ public function getWords(int $textId): array
346346
'WoText',
347347
'WoStatus',
348348
'WoTranslation',
349-
'WoRomanization'
349+
'WoRomanization',
350+
'WoNotes'
350351
])
351352
->leftJoin('words', 'textitems2.Ti2WoID', '=', 'words.WoID')
352353
->where('textitems2.Ti2TxID', '=', $textId)
@@ -400,6 +401,7 @@ public function getWords(int $textId): array
400401
$wordData['status'] = (int)$record['WoStatus'];
401402
$wordData['translation'] = ExportService::replaceTabNewline($record['WoTranslation'] ?? '');
402403
$wordData['romanization'] = $record['WoRomanization'] ?? '';
404+
$wordData['notes'] = $record['WoNotes'] ?? '';
403405

404406
// Get tags
405407
$tags = TagService::getWordTagList((int)$record['WoID'], false);
@@ -412,6 +414,7 @@ public function getWords(int $textId): array
412414
$wordData['status'] = 0;
413415
$wordData['translation'] = '';
414416
$wordData['romanization'] = '';
417+
$wordData['notes'] = '';
415418
}
416419

417420
// Add multiword references

src/frontend/js/api/terms.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface Term {
2323
textLc: string;
2424
translation: string;
2525
romanization?: string;
26+
notes?: string;
2627
status: number;
2728
langId: number;
2829
sentence?: string;

src/frontend/js/api/texts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface TextWord {
5454
status?: number;
5555
translation?: string;
5656
romanization?: string;
57+
notes?: string;
5758
tags?: string;
5859
// Multiword references (mw2, mw3, etc.)
5960
[key: `mw${number}`]: string | undefined;

src/frontend/js/reading/stores/multi_word_form_store.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,38 @@ function createMultiWordFormStore(): MultiWordFormStoreState {
228228
// For now, show if there's existing romanization or language supports it
229229
this.showRomanization = data.romanization !== '';
230230

231+
// Add curly braces around the term in sentence if not present
232+
let sentence = data.sentence;
233+
if (sentence && !sentence.includes('{') && data.text) {
234+
const termText = data.text.trim();
235+
// Escape regex special characters and allow flexible whitespace matching
236+
const escapedText = termText
237+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
238+
.replace(/\s+/g, '\\s+'); // Allow any whitespace to match any whitespace
239+
240+
try {
241+
// Case-insensitive match with flexible whitespace
242+
const regex = new RegExp('(' + escapedText + ')', 'iu');
243+
sentence = sentence.replace(regex, '{$1}');
244+
} catch {
245+
// If regex fails, try simple case-insensitive indexOf
246+
const lowerSentence = sentence.toLowerCase();
247+
const lowerTerm = termText.toLowerCase();
248+
const idx = lowerSentence.indexOf(lowerTerm);
249+
if (idx !== -1) {
250+
const matchedText = sentence.substring(idx, idx + termText.length);
251+
sentence = sentence.substring(0, idx) + '{' + matchedText + '}' + sentence.substring(idx + termText.length);
252+
}
253+
}
254+
}
255+
231256
// Set form data
232257
this.formData = {
233258
text: data.text,
234259
textLc: data.textLc,
235260
translation: data.translation === '*' ? '' : data.translation,
236261
romanization: data.romanization,
237-
sentence: data.sentence,
262+
sentence: sentence,
238263
status: data.status || 1,
239264
wordCount: data.wordCount || wordCount
240265
};

src/frontend/js/reading/stores/word_store.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface WordData {
3030
status: number;
3131
translation: string;
3232
romanization: string;
33+
notes?: string;
3334
tags?: string;
3435
}
3536

@@ -154,7 +155,12 @@ function createWordStore(): WordStoreState {
154155
showAll: this.showAll,
155156
showTranslations: this.showTranslations,
156157
rightToLeft: this.rightToLeft,
157-
textSize: this.textSize
158+
textSize: this.textSize,
159+
// Annotation settings for Markdown-rendered annotations
160+
showLearning: this.showLearning,
161+
displayStatTrans: this.displayStatTrans,
162+
modeTrans: this.modeTrans,
163+
annTextSize: this.annTextSize
158164
};
159165

160166
return renderText(this.words, settings);
@@ -233,6 +239,7 @@ function createWordStore(): WordStoreState {
233239
status: word.status ?? 0,
234240
translation: word.translation ?? '',
235241
romanization: word.romanization ?? '',
242+
notes: word.notes ?? '',
236243
tags: word.tags
237244
};
238245

tests/frontend/reading/text_multiword_selection.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ describe('text_multiword_selection.ts', () => {
105105
});
106106

107107
it('opens modal when multiple words are selected', () => {
108+
// In the real database, words are stored consecutively without space elements
109+
// The getSelectedText function adds spaces between consecutive word elements
108110
document.body.innerHTML = `
109111
<div id="thetext">
110112
<span id="sent_1">
@@ -136,17 +138,18 @@ describe('text_multiword_selection.ts', () => {
136138
expect(mockStore.loadForEdit).toHaveBeenCalledWith(
137139
1, // textId from URL
138140
1, // position (first word's data_order)
139-
expect.any(String), // text
141+
'Hello World', // text extracted with space added between consecutive words
140142
2 // word count
141143
);
142144
});
143145

144146
it('shows alert when selected text is too long', () => {
147+
// Words are stored consecutively without space elements
145148
document.body.innerHTML = `
146149
<div id="thetext">
147150
<span id="sent_1">
148-
<span class="wsty" data_order="1">${'A'.repeat(200)}</span>
149-
<span class="wsty" data_order="2">${'B'.repeat(100)}</span>
151+
<span id="ID-1-1" class="wsty" data_order="1">${'A'.repeat(200)}</span>
152+
<span id="ID-2-1" class="wsty" data_order="2">${'B'.repeat(100)}</span>
150153
</span>
151154
</div>
152155
`;
@@ -216,6 +219,7 @@ describe('text_multiword_selection.ts', () => {
216219
writable: true
217220
});
218221

222+
// Words are stored consecutively without space elements
219223
document.body.innerHTML = `
220224
<div id="thetext">
221225
<span id="sent_1">
@@ -245,7 +249,7 @@ describe('text_multiword_selection.ts', () => {
245249
expect(mockStore.loadForEdit).toHaveBeenCalledWith(
246250
42, // textId extracted from URL
247251
1,
248-
expect.any(String),
252+
'Hello World',
249253
2
250254
);
251255
});

0 commit comments

Comments
 (0)