@shaxpir/duiduidui-models 1.9.18 → 1.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/models/Collection.d.ts +22 -20
- package/dist/models/Collection.js +38 -26
- package/dist/util/ConditionMatcher.d.ts +57 -0
- package/dist/util/ConditionMatcher.js +200 -0
- package/dist/util/SenseRankEncoder.d.ts +33 -0
- package/dist/util/SenseRankEncoder.js +84 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +2 -0
- package/package.json +1 -1
|
@@ -13,15 +13,6 @@ export interface CollectionDisplay {
|
|
|
13
13
|
icon?: string;
|
|
14
14
|
sort_order: number;
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
* A snapshot of a term's proficiency within this collection.
|
|
18
|
-
* Stored in term_scores map, keyed by termId.
|
|
19
|
-
*/
|
|
20
|
-
export interface CollectionTermScore {
|
|
21
|
-
text: string;
|
|
22
|
-
sense_rank: number;
|
|
23
|
-
theta: number;
|
|
24
|
-
}
|
|
25
16
|
/**
|
|
26
17
|
* The payload for a Collection document.
|
|
27
18
|
*
|
|
@@ -34,7 +25,7 @@ export interface CollectionPayload {
|
|
|
34
25
|
display: CollectionDisplay;
|
|
35
26
|
proficiency: UncertainValue;
|
|
36
27
|
term_scores: {
|
|
37
|
-
[
|
|
28
|
+
[termKey: string]: number;
|
|
38
29
|
};
|
|
39
30
|
total_cards: number;
|
|
40
31
|
last_activity_utc: CompactDateTime | null;
|
|
@@ -61,6 +52,18 @@ export declare class Collection extends Content {
|
|
|
61
52
|
* The same user + collectionKey always produces the same ID.
|
|
62
53
|
*/
|
|
63
54
|
static makeCollectionId(userId: ContentId, collectionKey: string): ContentId;
|
|
55
|
+
/**
|
|
56
|
+
* Encode a term key from text and sense_rank.
|
|
57
|
+
* Uses the standard SenseRankEncoder format: "text [N+1]" or "text [radical]"
|
|
58
|
+
*/
|
|
59
|
+
static encodeTermKey(text: string, senseRank: number): string;
|
|
60
|
+
/**
|
|
61
|
+
* Decode a term key back to text and sense_rank.
|
|
62
|
+
*/
|
|
63
|
+
static decodeTermKey(termKey: string): {
|
|
64
|
+
text: string;
|
|
65
|
+
senseRank: number;
|
|
66
|
+
};
|
|
64
67
|
constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
|
|
65
68
|
/**
|
|
66
69
|
* Create a new Collection for a user.
|
|
@@ -79,16 +82,20 @@ export declare class Collection extends Content {
|
|
|
79
82
|
get proficiencyRatingDeviation(): number;
|
|
80
83
|
get proficiencyVolatility(): number;
|
|
81
84
|
get termScores(): {
|
|
82
|
-
[
|
|
85
|
+
[termKey: string]: number;
|
|
83
86
|
};
|
|
84
87
|
/**
|
|
85
88
|
* Get the number of unique terms the user has reviewed in this collection.
|
|
86
89
|
*/
|
|
87
90
|
get termsReviewedCount(): number;
|
|
88
91
|
/**
|
|
89
|
-
* Get the
|
|
92
|
+
* Get the theta score for a specific term, or undefined if not reviewed.
|
|
93
|
+
*/
|
|
94
|
+
getTermScore(text: string, senseRank: number): number | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Get the theta score by encoded term key, or undefined if not reviewed.
|
|
90
97
|
*/
|
|
91
|
-
|
|
98
|
+
getTermScoreByKey(termKey: string): number | undefined;
|
|
92
99
|
get totalCards(): number;
|
|
93
100
|
get lastActivityUtc(): CompactDateTime | null;
|
|
94
101
|
get isVisible(): boolean;
|
|
@@ -124,16 +131,11 @@ export declare class Collection extends Content {
|
|
|
124
131
|
/**
|
|
125
132
|
* Update or add a term score in the collection.
|
|
126
133
|
*/
|
|
127
|
-
setTermScore(
|
|
128
|
-
/**
|
|
129
|
-
* Update just the theta value for an existing term score.
|
|
130
|
-
* Does nothing if the term doesn't exist in term_scores.
|
|
131
|
-
*/
|
|
132
|
-
updateTermTheta(termId: string, theta: number): void;
|
|
134
|
+
setTermScore(text: string, senseRank: number, theta: number): void;
|
|
133
135
|
/**
|
|
134
136
|
* Remove a term score from the collection.
|
|
135
137
|
*/
|
|
136
|
-
removeTermScore(
|
|
138
|
+
removeTermScore(text: string, senseRank: number): void;
|
|
137
139
|
/**
|
|
138
140
|
* Update the cached total card count for this collection.
|
|
139
141
|
*/
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Collection = void 0;
|
|
4
4
|
const shaxpir_common_1 = require("@shaxpir/shaxpir-common");
|
|
5
5
|
const repo_1 = require("../repo");
|
|
6
|
+
const SenseRankEncoder_1 = require("../util/SenseRankEncoder");
|
|
6
7
|
const Content_1 = require("./Content");
|
|
7
8
|
const ContentKind_1 = require("./ContentKind");
|
|
8
9
|
const Operation_1 = require("./Operation");
|
|
@@ -14,6 +15,19 @@ class Collection extends Content_1.Content {
|
|
|
14
15
|
static makeCollectionId(userId, collectionKey) {
|
|
15
16
|
return shaxpir_common_1.CachingHasher.makeMd5Base62Hash(`${ContentKind_1.ContentKind.COLLECTION}-${userId}-${collectionKey}`);
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Encode a term key from text and sense_rank.
|
|
20
|
+
* Uses the standard SenseRankEncoder format: "text [N+1]" or "text [radical]"
|
|
21
|
+
*/
|
|
22
|
+
static encodeTermKey(text, senseRank) {
|
|
23
|
+
return (0, SenseRankEncoder_1.encodeSenseRank)(text, senseRank);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Decode a term key back to text and sense_rank.
|
|
27
|
+
*/
|
|
28
|
+
static decodeTermKey(termKey) {
|
|
29
|
+
return (0, SenseRankEncoder_1.decodeSenseRank)(termKey);
|
|
30
|
+
}
|
|
17
31
|
constructor(doc, shouldAcquire, shareSync) {
|
|
18
32
|
super(doc, shouldAcquire, shareSync);
|
|
19
33
|
}
|
|
@@ -131,11 +145,19 @@ class Collection extends Content_1.Content {
|
|
|
131
145
|
return Object.keys(this.payload.term_scores).length;
|
|
132
146
|
}
|
|
133
147
|
/**
|
|
134
|
-
* Get the
|
|
148
|
+
* Get the theta score for a specific term, or undefined if not reviewed.
|
|
135
149
|
*/
|
|
136
|
-
getTermScore(
|
|
150
|
+
getTermScore(text, senseRank) {
|
|
137
151
|
this.checkDisposed("Collection.getTermScore");
|
|
138
|
-
|
|
152
|
+
const termKey = Collection.encodeTermKey(text, senseRank);
|
|
153
|
+
return this.payload.term_scores[termKey];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the theta score by encoded term key, or undefined if not reviewed.
|
|
157
|
+
*/
|
|
158
|
+
getTermScoreByKey(termKey) {
|
|
159
|
+
this.checkDisposed("Collection.getTermScoreByKey");
|
|
160
|
+
return this.payload.term_scores[termKey];
|
|
139
161
|
}
|
|
140
162
|
// ============================================================
|
|
141
163
|
// Progress statistics getters
|
|
@@ -178,7 +200,7 @@ class Collection extends Content_1.Content {
|
|
|
178
200
|
const scores = Object.values(this.payload.term_scores);
|
|
179
201
|
if (scores.length === 0)
|
|
180
202
|
return 0;
|
|
181
|
-
const mastered = scores.filter(
|
|
203
|
+
const mastered = scores.filter(theta => theta >= 0.8).length;
|
|
182
204
|
return Math.round((mastered / scores.length) * 100);
|
|
183
205
|
}
|
|
184
206
|
/**
|
|
@@ -199,11 +221,11 @@ class Collection extends Content_1.Content {
|
|
|
199
221
|
this.checkDisposed("Collection.getGradeDistribution");
|
|
200
222
|
const scores = Object.values(this.payload.term_scores);
|
|
201
223
|
return {
|
|
202
|
-
A: scores.filter(
|
|
203
|
-
B: scores.filter(
|
|
204
|
-
C: scores.filter(
|
|
205
|
-
D: scores.filter(
|
|
206
|
-
F: scores.filter(
|
|
224
|
+
A: scores.filter(theta => theta >= 0.8).length,
|
|
225
|
+
B: scores.filter(theta => theta >= 0.6 && theta < 0.8).length,
|
|
226
|
+
C: scores.filter(theta => theta >= 0.4 && theta < 0.6).length,
|
|
227
|
+
D: scores.filter(theta => theta >= 0.2 && theta < 0.4).length,
|
|
228
|
+
F: scores.filter(theta => theta < 0.2).length
|
|
207
229
|
};
|
|
208
230
|
}
|
|
209
231
|
// ============================================================
|
|
@@ -225,32 +247,22 @@ class Collection extends Content_1.Content {
|
|
|
225
247
|
/**
|
|
226
248
|
* Update or add a term score in the collection.
|
|
227
249
|
*/
|
|
228
|
-
setTermScore(
|
|
250
|
+
setTermScore(text, senseRank, theta) {
|
|
229
251
|
this.checkDisposed("Collection.setTermScore");
|
|
252
|
+
const termKey = Collection.encodeTermKey(text, senseRank);
|
|
230
253
|
const batch = new Operation_1.BatchOperation(this);
|
|
231
|
-
batch.setPathValue(['payload', 'term_scores',
|
|
254
|
+
batch.setPathValue(['payload', 'term_scores', termKey], theta);
|
|
232
255
|
batch.commit();
|
|
233
256
|
}
|
|
234
|
-
/**
|
|
235
|
-
* Update just the theta value for an existing term score.
|
|
236
|
-
* Does nothing if the term doesn't exist in term_scores.
|
|
237
|
-
*/
|
|
238
|
-
updateTermTheta(termId, theta) {
|
|
239
|
-
this.checkDisposed("Collection.updateTermTheta");
|
|
240
|
-
if (this.payload.term_scores[termId]) {
|
|
241
|
-
const batch = new Operation_1.BatchOperation(this);
|
|
242
|
-
batch.setPathValue(['payload', 'term_scores', termId, 'theta'], theta);
|
|
243
|
-
batch.commit();
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
257
|
/**
|
|
247
258
|
* Remove a term score from the collection.
|
|
248
259
|
*/
|
|
249
|
-
removeTermScore(
|
|
260
|
+
removeTermScore(text, senseRank) {
|
|
250
261
|
this.checkDisposed("Collection.removeTermScore");
|
|
251
|
-
|
|
262
|
+
const termKey = Collection.encodeTermKey(text, senseRank);
|
|
263
|
+
if (this.payload.term_scores[termKey] !== undefined) {
|
|
252
264
|
const batch = new Operation_1.BatchOperation(this);
|
|
253
|
-
batch.removeValueAtPath(['payload', 'term_scores',
|
|
265
|
+
batch.removeValueAtPath(['payload', 'term_scores', termKey]);
|
|
254
266
|
batch.commit();
|
|
255
267
|
}
|
|
256
268
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CompactDateTime } from "@shaxpir/shaxpir-common";
|
|
2
|
+
import { AnyCondition, Conditions } from '../models/Condition';
|
|
3
|
+
/**
|
|
4
|
+
* Interface for objects that can be matched against Conditions.
|
|
5
|
+
* This is the minimum set of fields needed for condition matching.
|
|
6
|
+
*
|
|
7
|
+
* Implementers include AnnotatedPhrase (in the app) and potentially
|
|
8
|
+
* other phrase-like objects that need condition checking.
|
|
9
|
+
*/
|
|
10
|
+
export interface ConditionMatchable {
|
|
11
|
+
tags?: string[];
|
|
12
|
+
starred_at?: CompactDateTime | null;
|
|
13
|
+
theta?: number | null;
|
|
14
|
+
difficulty: number;
|
|
15
|
+
content_id?: string;
|
|
16
|
+
last_review_utc?: CompactDateTime | null;
|
|
17
|
+
created_at?: CompactDateTime;
|
|
18
|
+
updated_at?: CompactDateTime;
|
|
19
|
+
review_count?: number;
|
|
20
|
+
implied_review_count?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Utility for checking if objects match Conditions.
|
|
24
|
+
*
|
|
25
|
+
* This is used by:
|
|
26
|
+
* - UnifiedSearchQuery to filter search results
|
|
27
|
+
* - CollectionProficiencyService to determine which Collections contain a phrase
|
|
28
|
+
*/
|
|
29
|
+
export declare const ConditionMatcher: {
|
|
30
|
+
/**
|
|
31
|
+
* Check if a single condition matches a matchable object.
|
|
32
|
+
*/
|
|
33
|
+
conditionMatches(condition: AnyCondition, item: ConditionMatchable): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Check if an item matches all the provided conditions.
|
|
36
|
+
*
|
|
37
|
+
* Conditions use three logical groups:
|
|
38
|
+
* - all: Item must match ALL of these conditions
|
|
39
|
+
* - any: Item must match AT LEAST ONE of these conditions
|
|
40
|
+
* - none: Item must match NONE of these conditions
|
|
41
|
+
*/
|
|
42
|
+
matchesConditions(item: ConditionMatchable, conditions: Conditions | undefined): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Filter an array of items to only those matching the conditions.
|
|
45
|
+
*/
|
|
46
|
+
filterByConditions<T extends ConditionMatchable>(items: T[], conditions: Conditions | undefined): T[];
|
|
47
|
+
/**
|
|
48
|
+
* Check if an item matches any of the provided Conditions sets.
|
|
49
|
+
* Useful for checking membership in multiple Collections at once.
|
|
50
|
+
*/
|
|
51
|
+
matchesAnyConditions(item: ConditionMatchable, conditionsList: Conditions[]): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Find all Conditions sets that an item matches.
|
|
54
|
+
* Returns indices of matching conditions in the input array.
|
|
55
|
+
*/
|
|
56
|
+
findMatchingConditions(item: ConditionMatchable, conditionsList: Conditions[]): number[];
|
|
57
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConditionMatcher = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Utility for checking if objects match Conditions.
|
|
6
|
+
*
|
|
7
|
+
* This is used by:
|
|
8
|
+
* - UnifiedSearchQuery to filter search results
|
|
9
|
+
* - CollectionProficiencyService to determine which Collections contain a phrase
|
|
10
|
+
*/
|
|
11
|
+
exports.ConditionMatcher = {
|
|
12
|
+
/**
|
|
13
|
+
* Check if a single condition matches a matchable object.
|
|
14
|
+
*/
|
|
15
|
+
conditionMatches(condition, item) {
|
|
16
|
+
switch (condition.type) {
|
|
17
|
+
case 'tag': {
|
|
18
|
+
const tagCond = condition;
|
|
19
|
+
const allTags = item.tags || [];
|
|
20
|
+
return allTags.includes(tagCond.tag);
|
|
21
|
+
}
|
|
22
|
+
case 'starred': {
|
|
23
|
+
const starredCond = condition;
|
|
24
|
+
const isStarred = item.starred_at !== null && item.starred_at !== undefined;
|
|
25
|
+
return starredCond.value === isStarred;
|
|
26
|
+
}
|
|
27
|
+
case 'theta': {
|
|
28
|
+
const thetaCond = condition;
|
|
29
|
+
if (item.theta === null || item.theta === undefined)
|
|
30
|
+
return false;
|
|
31
|
+
if (thetaCond.min !== undefined && item.theta < thetaCond.min)
|
|
32
|
+
return false;
|
|
33
|
+
if (thetaCond.max !== undefined && item.theta > thetaCond.max)
|
|
34
|
+
return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
case 'grade': {
|
|
38
|
+
const gradeCond = condition;
|
|
39
|
+
if (item.theta === null || item.theta === undefined)
|
|
40
|
+
return false;
|
|
41
|
+
// Map theta to grade: A (0.8-1), B (0.6-0.8), C (0.4-0.6), D (0.2-0.4), F (0-0.2)
|
|
42
|
+
let itemGrade;
|
|
43
|
+
if (item.theta >= 0.8)
|
|
44
|
+
itemGrade = 'A';
|
|
45
|
+
else if (item.theta >= 0.6)
|
|
46
|
+
itemGrade = 'B';
|
|
47
|
+
else if (item.theta >= 0.4)
|
|
48
|
+
itemGrade = 'C';
|
|
49
|
+
else if (item.theta >= 0.2)
|
|
50
|
+
itemGrade = 'D';
|
|
51
|
+
else
|
|
52
|
+
itemGrade = 'F';
|
|
53
|
+
return itemGrade === gradeCond.grade;
|
|
54
|
+
}
|
|
55
|
+
case 'difficulty': {
|
|
56
|
+
const diffCond = condition;
|
|
57
|
+
if (diffCond.min !== undefined && item.difficulty < diffCond.min)
|
|
58
|
+
return false;
|
|
59
|
+
if (diffCond.max !== undefined && item.difficulty > diffCond.max)
|
|
60
|
+
return false;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
case 'has_term': {
|
|
64
|
+
const hasTermCond = condition;
|
|
65
|
+
const hasTerm = item.content_id !== undefined;
|
|
66
|
+
return hasTermCond.value === hasTerm;
|
|
67
|
+
}
|
|
68
|
+
case 'temporal': {
|
|
69
|
+
const tempCond = condition;
|
|
70
|
+
let fieldValue;
|
|
71
|
+
switch (tempCond.field) {
|
|
72
|
+
case 'last_review_utc':
|
|
73
|
+
fieldValue = item.last_review_utc;
|
|
74
|
+
break;
|
|
75
|
+
case 'starred_at':
|
|
76
|
+
fieldValue = item.starred_at;
|
|
77
|
+
break;
|
|
78
|
+
case 'created_at':
|
|
79
|
+
fieldValue = item.created_at;
|
|
80
|
+
break;
|
|
81
|
+
case 'updated_at':
|
|
82
|
+
fieldValue = item.updated_at;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
if (fieldValue === null || fieldValue === undefined) {
|
|
86
|
+
// No value means condition can't be satisfied for most operators
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
// For temporal conditions, we'd need to compare against current time
|
|
90
|
+
// This is complex because we need access to ClockService
|
|
91
|
+
// For now, return true (don't filter out) - these conditions are
|
|
92
|
+
// typically used in SQL queries rather than in-memory filtering
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
case 'count': {
|
|
96
|
+
const countCond = condition;
|
|
97
|
+
let fieldValue;
|
|
98
|
+
switch (countCond.field) {
|
|
99
|
+
case 'review_count':
|
|
100
|
+
fieldValue = item.review_count ?? 0;
|
|
101
|
+
break;
|
|
102
|
+
case 'implied_review_count':
|
|
103
|
+
fieldValue = item.implied_review_count ?? 0;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
switch (countCond.operator) {
|
|
107
|
+
case '>': return fieldValue > countCond.value;
|
|
108
|
+
case '<': return fieldValue < countCond.value;
|
|
109
|
+
case '>=': return fieldValue >= countCond.value;
|
|
110
|
+
case '<=': return fieldValue <= countCond.value;
|
|
111
|
+
case '=': return fieldValue === countCond.value;
|
|
112
|
+
case '!=': return fieldValue !== countCond.value;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
case 'component': {
|
|
117
|
+
// Component conditions require database lookups and are not
|
|
118
|
+
// suitable for in-memory filtering. Return true (don't filter out).
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
// For unsupported conditions, default to true (don't filter out)
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
/**
|
|
127
|
+
* Check if an item matches all the provided conditions.
|
|
128
|
+
*
|
|
129
|
+
* Conditions use three logical groups:
|
|
130
|
+
* - all: Item must match ALL of these conditions
|
|
131
|
+
* - any: Item must match AT LEAST ONE of these conditions
|
|
132
|
+
* - none: Item must match NONE of these conditions
|
|
133
|
+
*/
|
|
134
|
+
matchesConditions(item, conditions) {
|
|
135
|
+
if (!conditions)
|
|
136
|
+
return true;
|
|
137
|
+
// Check 'all' conditions - item must match ALL of these
|
|
138
|
+
if (conditions.all && conditions.all.length > 0) {
|
|
139
|
+
for (const cond of conditions.all) {
|
|
140
|
+
if (!exports.ConditionMatcher.conditionMatches(cond, item)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Check 'any' conditions - item must match AT LEAST ONE of these
|
|
146
|
+
if (conditions.any && conditions.any.length > 0) {
|
|
147
|
+
let matchedAny = false;
|
|
148
|
+
for (const cond of conditions.any) {
|
|
149
|
+
if (exports.ConditionMatcher.conditionMatches(cond, item)) {
|
|
150
|
+
matchedAny = true;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!matchedAny)
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// Check 'none' conditions - item must match NONE of these
|
|
158
|
+
if (conditions.none && conditions.none.length > 0) {
|
|
159
|
+
for (const cond of conditions.none) {
|
|
160
|
+
if (exports.ConditionMatcher.conditionMatches(cond, item)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
},
|
|
167
|
+
/**
|
|
168
|
+
* Filter an array of items to only those matching the conditions.
|
|
169
|
+
*/
|
|
170
|
+
filterByConditions(items, conditions) {
|
|
171
|
+
if (!conditions)
|
|
172
|
+
return items;
|
|
173
|
+
return items.filter(item => exports.ConditionMatcher.matchesConditions(item, conditions));
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* Check if an item matches any of the provided Conditions sets.
|
|
177
|
+
* Useful for checking membership in multiple Collections at once.
|
|
178
|
+
*/
|
|
179
|
+
matchesAnyConditions(item, conditionsList) {
|
|
180
|
+
for (const conditions of conditionsList) {
|
|
181
|
+
if (exports.ConditionMatcher.matchesConditions(item, conditions)) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* Find all Conditions sets that an item matches.
|
|
189
|
+
* Returns indices of matching conditions in the input array.
|
|
190
|
+
*/
|
|
191
|
+
findMatchingConditions(item, conditionsList) {
|
|
192
|
+
const matches = [];
|
|
193
|
+
for (let i = 0; i < conditionsList.length; i++) {
|
|
194
|
+
if (exports.ConditionMatcher.matchesConditions(item, conditionsList[i])) {
|
|
195
|
+
matches.push(i);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return matches;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for encoding and decoding sense_rank into search text.
|
|
3
|
+
*
|
|
4
|
+
* Encoding format (1-based for user display):
|
|
5
|
+
* - sense_rank >= 0: "text [N+1]" where N is the sense_rank (displayed as 1-based)
|
|
6
|
+
* - sense_rank = -1: "text [radical]"
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* - "王 [1]" -> sense_rank: 0 (displayed as 1, stored as 0)
|
|
10
|
+
* - "王 [2]" -> sense_rank: 1 (displayed as 2, stored as 1)
|
|
11
|
+
* - "王 [radical]" -> sense_rank: -1
|
|
12
|
+
*
|
|
13
|
+
* Note: [0] and negative numbers in brackets are invalid and default to sense_rank 0.
|
|
14
|
+
*/
|
|
15
|
+
export interface DecodedSearchText {
|
|
16
|
+
text: string;
|
|
17
|
+
senseRank: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Encodes a text and sense_rank into a search string.
|
|
21
|
+
*
|
|
22
|
+
* @param text The Chinese text
|
|
23
|
+
* @param senseRank The sense rank (-1, 0, 1, 2, ...)
|
|
24
|
+
* @returns Encoded search string
|
|
25
|
+
*/
|
|
26
|
+
export declare function encodeSenseRank(text: string, senseRank: number): string;
|
|
27
|
+
/**
|
|
28
|
+
* Decodes a search string into text and sense_rank.
|
|
29
|
+
*
|
|
30
|
+
* @param searchText The search string (possibly encoded)
|
|
31
|
+
* @returns Decoded text and sense_rank
|
|
32
|
+
*/
|
|
33
|
+
export declare function decodeSenseRank(searchText: string): DecodedSearchText;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utilities for encoding and decoding sense_rank into search text.
|
|
4
|
+
*
|
|
5
|
+
* Encoding format (1-based for user display):
|
|
6
|
+
* - sense_rank >= 0: "text [N+1]" where N is the sense_rank (displayed as 1-based)
|
|
7
|
+
* - sense_rank = -1: "text [radical]"
|
|
8
|
+
*
|
|
9
|
+
* Examples:
|
|
10
|
+
* - "王 [1]" -> sense_rank: 0 (displayed as 1, stored as 0)
|
|
11
|
+
* - "王 [2]" -> sense_rank: 1 (displayed as 2, stored as 1)
|
|
12
|
+
* - "王 [radical]" -> sense_rank: -1
|
|
13
|
+
*
|
|
14
|
+
* Note: [0] and negative numbers in brackets are invalid and default to sense_rank 0.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.encodeSenseRank = encodeSenseRank;
|
|
18
|
+
exports.decodeSenseRank = decodeSenseRank;
|
|
19
|
+
/**
|
|
20
|
+
* Encodes a text and sense_rank into a search string.
|
|
21
|
+
*
|
|
22
|
+
* @param text The Chinese text
|
|
23
|
+
* @param senseRank The sense rank (-1, 0, 1, 2, ...)
|
|
24
|
+
* @returns Encoded search string
|
|
25
|
+
*/
|
|
26
|
+
function encodeSenseRank(text, senseRank) {
|
|
27
|
+
if (senseRank === -1) {
|
|
28
|
+
return `${text} [radical]`;
|
|
29
|
+
}
|
|
30
|
+
else if (senseRank >= 0) {
|
|
31
|
+
// Convert to 1-based indexing for user display
|
|
32
|
+
return `${text} [${senseRank + 1}]`;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// For any other negative values (shouldn't happen), treat as radical
|
|
36
|
+
return `${text} [radical]`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decodes a search string into text and sense_rank.
|
|
41
|
+
*
|
|
42
|
+
* @param searchText The search string (possibly encoded)
|
|
43
|
+
* @returns Decoded text and sense_rank
|
|
44
|
+
*/
|
|
45
|
+
function decodeSenseRank(searchText) {
|
|
46
|
+
// Trim the input first to handle leading/trailing whitespace
|
|
47
|
+
const trimmed = searchText.trim();
|
|
48
|
+
// Pattern to match "[N]" or "[radical]" at the end of the string
|
|
49
|
+
// Whitespace before the bracket is optional
|
|
50
|
+
// Note: If multiple bracket groups exist (e.g., "要[1][2]"), the regex will match
|
|
51
|
+
// the last valid one due to backtracking, treating "要[1]" as text with senseRank=1
|
|
52
|
+
const pattern = /^(.+?)\s*\[(\d+|radical)\]$/;
|
|
53
|
+
const match = trimmed.match(pattern);
|
|
54
|
+
if (!match) {
|
|
55
|
+
// No encoding found, treat as sense_rank = 0
|
|
56
|
+
return {
|
|
57
|
+
text: searchText.trim(),
|
|
58
|
+
senseRank: 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const text = match[1].trim();
|
|
62
|
+
const senseRankStr = match[2];
|
|
63
|
+
if (senseRankStr === 'radical') {
|
|
64
|
+
return {
|
|
65
|
+
text,
|
|
66
|
+
senseRank: -1
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Convert from 1-based display indexing back to 0-based storage
|
|
71
|
+
const displayIndex = parseInt(senseRankStr, 10);
|
|
72
|
+
// [0] and negative numbers are invalid, default to sense_rank 0
|
|
73
|
+
if (displayIndex <= 0) {
|
|
74
|
+
return {
|
|
75
|
+
text,
|
|
76
|
+
senseRank: 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
text,
|
|
81
|
+
senseRank: displayIndex - 1
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/util/index.d.ts
CHANGED
package/dist/util/index.js
CHANGED
|
@@ -16,6 +16,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
__exportStar(require("./AvatarUri"), exports);
|
|
19
|
+
__exportStar(require("./ConditionMatcher"), exports);
|
|
19
20
|
__exportStar(require("./Database"), exports);
|
|
20
21
|
__exportStar(require("./Encryption"), exports);
|
|
21
22
|
__exportStar(require("./Logging"), exports);
|
|
23
|
+
__exportStar(require("./SenseRankEncoder"), exports);
|