@shaxpir/duiduidui-models 1.3.1 → 1.3.3
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/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/package.json +5 -1
- package/lib/index.ts +0 -7
- package/lib/models/ArrayView.ts +0 -203
- package/lib/models/BayesianScore.ts +0 -32
- package/lib/models/ChangeModel.ts +0 -94
- package/lib/models/Content.ts +0 -107
- package/lib/models/ContentKind.ts +0 -19
- package/lib/models/Device.ts +0 -90
- package/lib/models/GeoLocation.ts +0 -4
- package/lib/models/Hanzi.ts +0 -16
- package/lib/models/Manifest.ts +0 -171
- package/lib/models/Media.ts +0 -125
- package/lib/models/Metric.ts +0 -233
- package/lib/models/Model.ts +0 -325
- package/lib/models/Operation.ts +0 -205
- package/lib/models/Permissions.ts +0 -19
- package/lib/models/Phrase.ts +0 -53
- package/lib/models/Profile.ts +0 -117
- package/lib/models/Progress.ts +0 -101
- package/lib/models/Review.ts +0 -18
- package/lib/models/Session.ts +0 -149
- package/lib/models/Term.ts +0 -202
- package/lib/models/User.ts +0 -97
- package/lib/models/Workspace.ts +0 -129
- package/lib/models/index.ts +0 -24
- package/lib/repo/ConnectionListener.ts +0 -25
- package/lib/repo/PermissiveJson1.ts +0 -14
- package/lib/repo/ShareSync.ts +0 -383
- package/lib/repo/TextEditOps.ts +0 -50
- package/lib/repo/index.ts +0 -6
- package/lib/util/Encryption.ts +0 -5
- package/lib/util/Logging.ts +0 -568
- package/lib/util/index.ts +0 -4
- package/tsconfig.json +0 -25
- package/tslint.json +0 -46
package/lib/models/Profile.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { CachingHasher, MultiClock } from '@shaxpir/shaxpir-common';
|
|
3
|
-
import { ShareSync, ShareSyncFactory } from '../repo';
|
|
4
|
-
import { Content, ContentBody, ContentId, ContentMeta, ContentRef } from "./Content";
|
|
5
|
-
import { ContentKind } from './ContentKind';
|
|
6
|
-
import { MediaCropping } from './Media';
|
|
7
|
-
import { BatchOperation } from './Operation';
|
|
8
|
-
|
|
9
|
-
export interface ProfilePayload {
|
|
10
|
-
username:string;
|
|
11
|
-
full_name:string;
|
|
12
|
-
avatar_media_ref:ContentId;
|
|
13
|
-
avatar_cropping:MediaCropping;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ProfileBody extends ContentBody {
|
|
17
|
-
meta:ContentMeta;
|
|
18
|
-
payload:ProfilePayload;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class Profile extends Content {
|
|
22
|
-
|
|
23
|
-
public static makeProfileId(userId:ContentId):ContentId {
|
|
24
|
-
return CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind.PROFILE) as ContentId;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public static create(userId:ContentId):Profile {
|
|
28
|
-
const now = MultiClock.now();
|
|
29
|
-
const profileId = Profile.makeProfileId(userId);
|
|
30
|
-
return ShareSyncFactory.get().createContent(
|
|
31
|
-
{
|
|
32
|
-
meta : {
|
|
33
|
-
ref : profileId,
|
|
34
|
-
kind : ContentKind.PROFILE,
|
|
35
|
-
id : profileId,
|
|
36
|
-
owner : userId,
|
|
37
|
-
created_at : now,
|
|
38
|
-
updated_at : now
|
|
39
|
-
},
|
|
40
|
-
payload : {
|
|
41
|
-
username : null,
|
|
42
|
-
full_name : '',
|
|
43
|
-
avatar_media_ref : null as ContentRef,
|
|
44
|
-
avatar_cropping : null
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
) as Profile;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
51
|
-
super(doc, shouldAcquire, shareSync);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public get payload():ProfilePayload {
|
|
55
|
-
this.checkDisposed("Profile.payload");
|
|
56
|
-
return this.doc.data.payload as ProfilePayload;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public get username():string {
|
|
60
|
-
this.checkDisposed("Profile.username");
|
|
61
|
-
return this.payload.username;
|
|
62
|
-
}
|
|
63
|
-
public setUsername(value:string) {
|
|
64
|
-
this.checkDisposed("Profile.setUsername");
|
|
65
|
-
if (this.username != value) {
|
|
66
|
-
const batch = new BatchOperation(this);
|
|
67
|
-
batch.setPathValue([ 'payload', 'username' ] , value);
|
|
68
|
-
batch.commit();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public get fullName():string {
|
|
73
|
-
this.checkDisposed("Profile.fullName");
|
|
74
|
-
return this.payload.full_name;
|
|
75
|
-
}
|
|
76
|
-
public setFullName(value:string) {
|
|
77
|
-
this.checkDisposed("Profile.setFullName");
|
|
78
|
-
if (this.fullName !== value) {
|
|
79
|
-
const batch = new BatchOperation(this);
|
|
80
|
-
batch.editPathText([ 'payload', 'full_name' ] , value);
|
|
81
|
-
batch.commit();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
public get avatarMediaRef():ContentId {
|
|
86
|
-
this.checkDisposed("Profile.avatarMediaRef");
|
|
87
|
-
return this.payload.avatar_media_ref;
|
|
88
|
-
}
|
|
89
|
-
public setAvatarMediaRef(value:ContentId) {
|
|
90
|
-
this.checkDisposed("Profile.setAvatarMediaRef");
|
|
91
|
-
if (this.avatarMediaRef !== value) {
|
|
92
|
-
const batch = new BatchOperation(this);
|
|
93
|
-
batch.setPathValue([ 'payload', 'avatar_media_ref' ] , value);
|
|
94
|
-
batch.commit();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public get avatarCropping():MediaCropping {
|
|
99
|
-
this.checkDisposed("Profile.avatarCropping");
|
|
100
|
-
return this.payload.avatar_cropping;
|
|
101
|
-
}
|
|
102
|
-
public setAvatarCropping(value:MediaCropping) {
|
|
103
|
-
this.checkDisposed("Profile.setAvatarCropping");
|
|
104
|
-
if (this.avatarCropping !== value) {
|
|
105
|
-
const batch = new BatchOperation(this);
|
|
106
|
-
batch.setPathValue([ 'payload', 'avatar_cropping' ] , value);
|
|
107
|
-
batch.commit();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
public static async findByUsername(username:string):Promise<Profile[]> {
|
|
112
|
-
const shareSync = ShareSyncFactory.get();
|
|
113
|
-
return shareSync.findAndAcquire(
|
|
114
|
-
ContentKind.PROFILE, { "payload.username" : username }
|
|
115
|
-
) as Promise<Profile[]>;
|
|
116
|
-
}
|
|
117
|
-
}
|
package/lib/models/Progress.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { CachingHasher, CompactDateTime, MultiClock } from "@shaxpir/shaxpir-common";
|
|
3
|
-
import { ShareSync, ShareSyncFactory } from '../repo';
|
|
4
|
-
import { BayesianScore } from './BayesianScore';
|
|
5
|
-
import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
|
|
6
|
-
import { ContentKind } from './ContentKind';
|
|
7
|
-
import { BatchOperation } from './Operation';
|
|
8
|
-
|
|
9
|
-
export interface PhraseProgress extends BayesianScore {
|
|
10
|
-
text:string;
|
|
11
|
-
sense_rank:number;
|
|
12
|
-
learn_rank: number;
|
|
13
|
-
last_review_utc?: CompactDateTime;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// The Proficiency model represents our assessment of the user's overall language level (user_rank)
|
|
17
|
-
// as well as a map of phrases with their learn_rank, alpha, beta, and pr
|
|
18
|
-
export interface ProgressPayload {
|
|
19
|
-
user_rank:number;
|
|
20
|
-
phrases:Record<string, PhraseProgress>;
|
|
21
|
-
cognitive_load:number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ProgressBody extends ContentBody {
|
|
25
|
-
meta:ContentMeta;
|
|
26
|
-
payload:ProgressPayload;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class Progress extends Content {
|
|
30
|
-
|
|
31
|
-
public static makeProgressId(userId:ContentId):ContentId {
|
|
32
|
-
return CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind.PROGRESS) as ContentId;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public static create(
|
|
36
|
-
userId:ContentId
|
|
37
|
-
):Progress {
|
|
38
|
-
const now = MultiClock.now();
|
|
39
|
-
const progressId = Progress.makeProgressId(userId);
|
|
40
|
-
return ShareSyncFactory.get().createContent(
|
|
41
|
-
{
|
|
42
|
-
meta : {
|
|
43
|
-
ref : progressId,
|
|
44
|
-
kind : ContentKind.PROGRESS,
|
|
45
|
-
id : progressId,
|
|
46
|
-
owner : userId,
|
|
47
|
-
created_at : now,
|
|
48
|
-
updated_at : now
|
|
49
|
-
},
|
|
50
|
-
payload : {
|
|
51
|
-
user_rank : 0,
|
|
52
|
-
phrases : {},
|
|
53
|
-
cognitive_load : 0
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
) as Progress;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
60
|
-
super(doc, shouldAcquire, shareSync);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public get payload():ProgressPayload {
|
|
64
|
-
this.checkDisposed("Progress.payload");
|
|
65
|
-
return this.doc.data.payload as ProgressPayload;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public setUserRank(value: number): void {
|
|
69
|
-
this.checkDisposed("Progress.setUserRank");
|
|
70
|
-
if (this.payload.user_rank !== value) {
|
|
71
|
-
const batch = new BatchOperation(this);
|
|
72
|
-
batch.setPathValue(['payload', 'user_rank'], value);
|
|
73
|
-
batch.commit();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
public setCognitiveLoad(value: number): void {
|
|
78
|
-
this.checkDisposed("Progress.setCognitiveLoad");
|
|
79
|
-
if (this.payload.cognitive_load !== value) {
|
|
80
|
-
const batch = new BatchOperation(this);
|
|
81
|
-
batch.setPathValue(['payload', 'cognitive_load'], value);
|
|
82
|
-
batch.commit();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
public updatePhraseProgress(phraseId: string, progress: PhraseProgress): void {
|
|
87
|
-
this.checkDisposed("Progress.updatePhraseProgress");
|
|
88
|
-
const batch = new BatchOperation(this);
|
|
89
|
-
batch.setPathValue(['payload', 'phrases', phraseId], progress);
|
|
90
|
-
batch.commit();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public updateMultiplePhrases(updates: Record<string, PhraseProgress>): void {
|
|
94
|
-
this.checkDisposed("Progress.updateMultiplePhrases");
|
|
95
|
-
const batch = new BatchOperation(this);
|
|
96
|
-
for (const [phraseId, progress] of Object.entries(updates)) {
|
|
97
|
-
batch.setPathValue(['payload', 'phrases', phraseId], progress);
|
|
98
|
-
}
|
|
99
|
-
batch.commit();
|
|
100
|
-
}
|
|
101
|
-
}
|
package/lib/models/Review.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { MultiTime } from "@shaxpir/shaxpir-common";
|
|
2
|
-
|
|
3
|
-
export type ReviewResult = 'FAIL' | 'HARD' | 'GOOD' | 'EASY';
|
|
4
|
-
|
|
5
|
-
export interface ReviewLike {
|
|
6
|
-
result: ReviewResult;
|
|
7
|
-
at: MultiTime;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface Review extends ReviewLike {
|
|
11
|
-
text: string;
|
|
12
|
-
sense_rank: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ImpliedReview extends ReviewLike {
|
|
16
|
-
context: string;
|
|
17
|
-
weight: number;
|
|
18
|
-
}
|
package/lib/models/Session.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { CachingHasher, CompactDateTime, MultiClock, MultiTime, Time } from "@shaxpir/shaxpir-common";
|
|
3
|
-
import { ShareSync, ShareSyncFactory } from '../repo';
|
|
4
|
-
import { Content, ContentBody, ContentId, ContentMeta, ContentRef } from "./Content";
|
|
5
|
-
import { ContentKind } from './ContentKind';
|
|
6
|
-
import { Metric, MetricName } from './Metric';
|
|
7
|
-
import { BatchOperation } from './Operation';
|
|
8
|
-
import { Review } from './Review';
|
|
9
|
-
import { Workspace } from './Workspace';
|
|
10
|
-
import { ArrayView } from './ArrayView';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export interface SessionPayload {
|
|
14
|
-
start:MultiTime;
|
|
15
|
-
end:MultiTime;
|
|
16
|
-
tz_offset:number;
|
|
17
|
-
device_id:ContentId;
|
|
18
|
-
duration_minutes:number;
|
|
19
|
-
reviews:Review[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface SessionBody extends ContentBody {
|
|
23
|
-
meta:ContentMeta;
|
|
24
|
-
payload:SessionPayload;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class Session extends Content {
|
|
28
|
-
|
|
29
|
-
private _reviewsView:ArrayView<Review>;
|
|
30
|
-
|
|
31
|
-
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
32
|
-
super(doc, shouldAcquire, shareSync);
|
|
33
|
-
this._reviewsView = new ArrayView(this, [ 'payload', 'reviews' ]);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public static makeSessionId(userId:ContentId):ContentId {
|
|
37
|
-
return CachingHasher.makeMd5Base62Hash(userId + "-" + ContentKind.SESSION) as ContentId;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public static makeSessionRef(userId:ContentId, at:MultiTime):ContentRef {
|
|
41
|
-
let sessionId = Session.makeSessionId(userId);
|
|
42
|
-
return `${sessionId}_${at.utc_time}` as ContentRef;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public static create(
|
|
46
|
-
userId:ContentId,
|
|
47
|
-
deviceId:ContentId
|
|
48
|
-
):Session {
|
|
49
|
-
|
|
50
|
-
let now:MultiTime = MultiClock.now();
|
|
51
|
-
let sessionId = Session.makeSessionId(userId);
|
|
52
|
-
let sessionRef = Session.makeSessionRef(userId, now);
|
|
53
|
-
|
|
54
|
-
return ShareSyncFactory.get().createContent(
|
|
55
|
-
{
|
|
56
|
-
meta : {
|
|
57
|
-
ref : sessionRef,
|
|
58
|
-
kind : ContentKind.SESSION,
|
|
59
|
-
id : sessionId,
|
|
60
|
-
owner : userId,
|
|
61
|
-
created_at : now,
|
|
62
|
-
updated_at : now
|
|
63
|
-
},
|
|
64
|
-
payload : {
|
|
65
|
-
start : now,
|
|
66
|
-
end: now,
|
|
67
|
-
tz_offset : (new Date()).getTimezoneOffset(),
|
|
68
|
-
duration_minutes : 0,
|
|
69
|
-
device_id : deviceId,
|
|
70
|
-
reviews : []
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
) as Session;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public get payload():SessionPayload {
|
|
77
|
-
this.checkDisposed("Session.payload");
|
|
78
|
-
return this.doc.data.payload;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
public touch():void {
|
|
82
|
-
this.checkDisposed("Session.touch");
|
|
83
|
-
|
|
84
|
-
const batch = new BatchOperation(this);
|
|
85
|
-
|
|
86
|
-
let nowUtc:CompactDateTime = batch.at.utc_time;
|
|
87
|
-
let nowLocal:CompactDateTime = batch.at.local_time;
|
|
88
|
-
let minutes:number = Time.absDiffMinutes(nowUtc, this.start.utc_time);
|
|
89
|
-
|
|
90
|
-
batch.setPathValue([ 'payload', 'duration_minutes' ] , minutes);
|
|
91
|
-
batch.setPathValue([ 'payload', 'end', 'utc_time' ] , nowUtc);
|
|
92
|
-
batch.setPathValue([ 'payload', 'end', 'local_time' ] , nowLocal);
|
|
93
|
-
|
|
94
|
-
batch.commit();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
public get start():MultiTime {
|
|
98
|
-
this.checkDisposed("Session.start");
|
|
99
|
-
return this.payload.start;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public get end():MultiTime {
|
|
103
|
-
this.checkDisposed("Session.end");
|
|
104
|
-
return this.payload.end;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public get tzOffset():number {
|
|
108
|
-
this.checkDisposed("Session.tzOffset");
|
|
109
|
-
return this.payload.tz_offset;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public get durationMinutes():number {
|
|
113
|
-
this.checkDisposed("Session.durationMinutes");
|
|
114
|
-
return this.payload.duration_minutes;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
public get deviceId():ContentId {
|
|
118
|
-
this.checkDisposed("Session.deviceId");
|
|
119
|
-
return this.payload.device_id;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
public async modelUpdated():Promise<void> {
|
|
123
|
-
this.checkDisposed("Session.modelUpdated");
|
|
124
|
-
await super.modelUpdated();
|
|
125
|
-
const shareSync = this.shareSync;
|
|
126
|
-
const sessionId = this.id;
|
|
127
|
-
const userId = this.owner;
|
|
128
|
-
const date = Time.dateFrom(this.createdAt.local_time);
|
|
129
|
-
const workspaceId = Workspace.makeWorkspaceId(userId);
|
|
130
|
-
const workspace = await this.shareSync.acquire(ContentKind.WORKSPACE, workspaceId) as Workspace;
|
|
131
|
-
const sessionsOnDate:Session[] = await workspace.acquireSessionsFromDate(sessionId, date);
|
|
132
|
-
workspace.release();
|
|
133
|
-
let minutesWritingOnDate:number = 0;
|
|
134
|
-
for (let i = 0, len = sessionsOnDate.length; i < len; i++) {
|
|
135
|
-
const sessionOnDate = sessionsOnDate[i] as Session;
|
|
136
|
-
minutesWritingOnDate += sessionOnDate.durationMinutes;
|
|
137
|
-
sessionOnDate.release();
|
|
138
|
-
}
|
|
139
|
-
const minutesWritingMetricId = Metric.makeMetricId(userId, MetricName.MINUTES_WRITING);
|
|
140
|
-
const minutesWritingMetric = await shareSync.acquire(ContentKind.METRIC, minutesWritingMetricId) as Metric;
|
|
141
|
-
minutesWritingMetric.setDateAmount(date, minutesWritingOnDate);
|
|
142
|
-
minutesWritingMetric.release();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
public addReview(review: Review): void {
|
|
146
|
-
this.checkDisposed("Session.addReview");
|
|
147
|
-
this._reviewsView.push(review);
|
|
148
|
-
}
|
|
149
|
-
}
|
package/lib/models/Term.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { CachingHasher, CompactDateTime, MultiClock } from "@shaxpir/shaxpir-common";
|
|
3
|
-
import { BayesianScore } from './BayesianScore';
|
|
4
|
-
import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
|
|
5
|
-
import { BuiltInPhrase, Phrase, PhraseExample } from './Phrase';
|
|
6
|
-
import { ReviewLike } from "./Review";
|
|
7
|
-
import { ContentKind } from './ContentKind';
|
|
8
|
-
import { ShareSync, ShareSyncFactory } from '../repo';
|
|
9
|
-
import { BatchOperation } from './Operation';
|
|
10
|
-
import { ArrayView } from './ArrayView';
|
|
11
|
-
|
|
12
|
-
export interface TermPayload extends BayesianScore {
|
|
13
|
-
|
|
14
|
-
// These fields are always populated. Note that for non-builtin entries, the sense_rank is always zero
|
|
15
|
-
text: string;
|
|
16
|
-
sense_rank: number;
|
|
17
|
-
learn_rank: number;
|
|
18
|
-
phrase_id: number | null;
|
|
19
|
-
starred_at: CompactDateTime | null;
|
|
20
|
-
|
|
21
|
-
// these phrase fields are omitted for builtin entries (e.g., when phrase_id is not null)
|
|
22
|
-
// TODO: what about server-provided entries with numeric 'phrase_id' (e.g., if we split the *huge* local
|
|
23
|
-
// db into a smaller local db with all beginner content, and a bigger cloud db with more advanced content)
|
|
24
|
-
hanzi_count?: number;
|
|
25
|
-
pinyin?: string;
|
|
26
|
-
pinyin_tokenized?: string;
|
|
27
|
-
transliteration?: string;
|
|
28
|
-
translation?: string;
|
|
29
|
-
notes?: string;
|
|
30
|
-
components?: PhraseExample[];
|
|
31
|
-
examples?: PhraseExample[];
|
|
32
|
-
keywords?: string[];
|
|
33
|
-
|
|
34
|
-
// if this is a builtin entry, these tags overlay the builtin tags
|
|
35
|
-
tags: string[];
|
|
36
|
-
|
|
37
|
-
reviews: ReviewLike[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface TermBody extends ContentBody {
|
|
41
|
-
meta:ContentMeta;
|
|
42
|
-
payload:TermPayload;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export class Term extends Content {
|
|
46
|
-
|
|
47
|
-
public static makeTermId(userId:ContentId, text:string, senseRank:number):ContentId {
|
|
48
|
-
return CachingHasher.makeMd5Base62Hash(`${ContentKind.TERM}-${userId}-${text}-${senseRank}`) as ContentId;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private _reviewsView:ArrayView<ReviewLike>;
|
|
52
|
-
|
|
53
|
-
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
54
|
-
super(doc, shouldAcquire, shareSync);
|
|
55
|
-
this._reviewsView = new ArrayView(this, [ 'payload', 'reviews' ]);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public static forUserPhrase(
|
|
59
|
-
userId:ContentId,
|
|
60
|
-
text:string,
|
|
61
|
-
senseRank:number,
|
|
62
|
-
learnRank:number,
|
|
63
|
-
phraseId?:number
|
|
64
|
-
):Term;
|
|
65
|
-
public static forUserPhrase(
|
|
66
|
-
userId:ContentId,
|
|
67
|
-
phrase:Phrase
|
|
68
|
-
):Term;
|
|
69
|
-
public static forUserPhrase(
|
|
70
|
-
userId:ContentId,
|
|
71
|
-
textOrPhrase:string | Phrase,
|
|
72
|
-
senseRank?:number,
|
|
73
|
-
learnRank?:number,
|
|
74
|
-
phraseId?:number
|
|
75
|
-
):Term {
|
|
76
|
-
const now = MultiClock.now();
|
|
77
|
-
|
|
78
|
-
// Handle overloaded parameters
|
|
79
|
-
let phrase: Phrase;
|
|
80
|
-
if (typeof textOrPhrase === 'string') {
|
|
81
|
-
// Create a minimal Phrase object from individual parameters
|
|
82
|
-
phrase = {
|
|
83
|
-
text: textOrPhrase,
|
|
84
|
-
sense_rank: senseRank!,
|
|
85
|
-
learn_rank: learnRank!,
|
|
86
|
-
hanzi_count: textOrPhrase.length,
|
|
87
|
-
pinyin: '',
|
|
88
|
-
pinyin_tokenized: '',
|
|
89
|
-
transliteration: '',
|
|
90
|
-
translation: '',
|
|
91
|
-
notes: '',
|
|
92
|
-
examples: [],
|
|
93
|
-
keywords: [],
|
|
94
|
-
tags: []
|
|
95
|
-
} as Phrase;
|
|
96
|
-
} else {
|
|
97
|
-
phrase = textOrPhrase;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const termId = CachingHasher.makeMd5Base62Hash(`${userId}-${phrase.text}-${phrase.sense_rank}`) as ContentId;
|
|
101
|
-
return ShareSyncFactory.get().createContent(
|
|
102
|
-
{
|
|
103
|
-
meta : {
|
|
104
|
-
ref : termId,
|
|
105
|
-
kind : ContentKind.TERM,
|
|
106
|
-
id : termId,
|
|
107
|
-
owner : userId,
|
|
108
|
-
created_at : now,
|
|
109
|
-
updated_at : now
|
|
110
|
-
},
|
|
111
|
-
payload : {
|
|
112
|
-
|
|
113
|
-
phrase_id : (typeof textOrPhrase === 'string' && phraseId) ? phraseId : null,
|
|
114
|
-
text : phrase.text,
|
|
115
|
-
sense_rank : phrase.sense_rank,
|
|
116
|
-
hanzi_count : phrase.hanzi_count,
|
|
117
|
-
learn_rank : phrase.learn_rank,
|
|
118
|
-
pinyin : phrase.pinyin,
|
|
119
|
-
pinyin_tokenized : phrase.pinyin_tokenized,
|
|
120
|
-
transliteration : phrase.transliteration,
|
|
121
|
-
translation : phrase.translation,
|
|
122
|
-
notes : phrase.notes,
|
|
123
|
-
|
|
124
|
-
examples : phrase.examples,
|
|
125
|
-
keywords : phrase.keywords,
|
|
126
|
-
tags : phrase.tags,
|
|
127
|
-
|
|
128
|
-
starred_at : null,
|
|
129
|
-
|
|
130
|
-
alpha: 1,
|
|
131
|
-
beta: 1,
|
|
132
|
-
theta: 0.5,
|
|
133
|
-
reviews: []
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
) as Term;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
public static forBuiltinPhrase(
|
|
140
|
-
userId:ContentId,
|
|
141
|
-
phrase:BuiltInPhrase
|
|
142
|
-
):Term {
|
|
143
|
-
const now = MultiClock.now();
|
|
144
|
-
const termId = CachingHasher.makeMd5Base62Hash(`${userId}-${phrase.text}-${phrase.sense_rank}`) as ContentId;
|
|
145
|
-
return ShareSyncFactory.get().createContent(
|
|
146
|
-
{
|
|
147
|
-
meta : {
|
|
148
|
-
ref : termId,
|
|
149
|
-
kind : ContentKind.TERM,
|
|
150
|
-
id : termId,
|
|
151
|
-
owner : userId,
|
|
152
|
-
created_at : now,
|
|
153
|
-
updated_at : now
|
|
154
|
-
},
|
|
155
|
-
payload : {
|
|
156
|
-
|
|
157
|
-
text: phrase.text,
|
|
158
|
-
sense_rank: phrase.sense_rank,
|
|
159
|
-
learn_rank: phrase.learn_rank,
|
|
160
|
-
phrase_id : phrase.id,
|
|
161
|
-
starred_at: null,
|
|
162
|
-
|
|
163
|
-
tags : [],
|
|
164
|
-
|
|
165
|
-
alpha: 1,
|
|
166
|
-
beta: 1,
|
|
167
|
-
theta: 0.5,
|
|
168
|
-
|
|
169
|
-
reviews: []
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
) as Term;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
public get payload():TermPayload {
|
|
176
|
-
this.checkDisposed("Term.payload");
|
|
177
|
-
return this.doc.data.payload as TermPayload;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
public setStarredAt(value: CompactDateTime | null): void {
|
|
181
|
-
this.checkDisposed("Term.setStarredAt");
|
|
182
|
-
if (this.payload.starred_at !== value) {
|
|
183
|
-
const batch = new BatchOperation(this);
|
|
184
|
-
batch.setPathValue(['payload', 'starred_at'], value);
|
|
185
|
-
batch.commit();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
public updateBayesianScore(alpha: number, beta: number, theta: number): void {
|
|
190
|
-
this.checkDisposed("Term.updateBayesianScore");
|
|
191
|
-
const batch = new BatchOperation(this);
|
|
192
|
-
batch.setPathValue(['payload', 'alpha'], alpha);
|
|
193
|
-
batch.setPathValue(['payload', 'beta'], beta);
|
|
194
|
-
batch.setPathValue(['payload', 'theta'], theta);
|
|
195
|
-
batch.commit();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
public addReview(review: ReviewLike): void {
|
|
199
|
-
this.checkDisposed("Term.addReview");
|
|
200
|
-
this._reviewsView.push(review);
|
|
201
|
-
}
|
|
202
|
-
}
|
package/lib/models/User.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { CompactDateTime, MultiClock, MultiTime } from '@shaxpir/shaxpir-common';
|
|
3
|
-
import { ArrayView } from './ArrayView';
|
|
4
|
-
import { Content, ContentId, ContentMeta } from "./Content";
|
|
5
|
-
import { ContentKind } from './ContentKind';
|
|
6
|
-
import { BatchOperation } from './Operation';
|
|
7
|
-
import { ShareSync, ShareSyncFactory } from '../repo';
|
|
8
|
-
|
|
9
|
-
export interface VerifiablePhone {
|
|
10
|
-
phone:string;
|
|
11
|
-
verified_at:CompactDateTime;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface VerifiableEmail {
|
|
15
|
-
email:string;
|
|
16
|
-
verified_at:CompactDateTime;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface UserPayload {
|
|
20
|
-
emails:VerifiableEmail[];
|
|
21
|
-
phones:VerifiablePhone[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface UserBody {
|
|
25
|
-
meta:ContentMeta;
|
|
26
|
-
payload:UserPayload;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class User extends Content {
|
|
30
|
-
|
|
31
|
-
private _emails:ArrayView<VerifiableEmail>;
|
|
32
|
-
private _phones:ArrayView<VerifiablePhone>;
|
|
33
|
-
|
|
34
|
-
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
35
|
-
super(doc, shouldAcquire, shareSync);
|
|
36
|
-
this._emails = new ArrayView<VerifiableEmail>(this, [ 'payload', 'emails' ]);
|
|
37
|
-
this._phones = new ArrayView<VerifiablePhone>(this, [ 'payload', 'phones' ]);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public get payload():UserPayload {
|
|
41
|
-
return this.doc.data.payload as UserPayload;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public static create(
|
|
45
|
-
userId:ContentId,
|
|
46
|
-
payload:UserPayload,
|
|
47
|
-
createdAt?:MultiTime
|
|
48
|
-
):User {
|
|
49
|
-
// User creation always happens in UTC time
|
|
50
|
-
const now = MultiClock.utcNow();
|
|
51
|
-
// If the createdAt time is not provided, use the current time
|
|
52
|
-
createdAt ??= now;
|
|
53
|
-
return ShareSyncFactory.get().createContent(
|
|
54
|
-
{
|
|
55
|
-
meta : {
|
|
56
|
-
ref : userId,
|
|
57
|
-
kind : ContentKind.USER,
|
|
58
|
-
id : userId,
|
|
59
|
-
owner : userId,
|
|
60
|
-
created_at : createdAt,
|
|
61
|
-
updated_at : now
|
|
62
|
-
},
|
|
63
|
-
payload : payload
|
|
64
|
-
}
|
|
65
|
-
) as User;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public get emails():ArrayView<VerifiableEmail> {
|
|
69
|
-
return this._emails;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public get phones():ArrayView<VerifiablePhone> {
|
|
73
|
-
return this._phones;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public verifyEmail(email:string, time:CompactDateTime):void {
|
|
77
|
-
const idx = this._emails.firstIndexWhere(e => e.email === email);
|
|
78
|
-
if (idx >= 0 && this._emails.get(idx).verified_at !== time) {
|
|
79
|
-
const batch = new BatchOperation(this);
|
|
80
|
-
batch.setPathValue([ 'payload', 'emails', idx, 'verified_at' ], time);
|
|
81
|
-
batch.commit();
|
|
82
|
-
} else if (idx < 0) {
|
|
83
|
-
this._emails.push({ email, verified_at: time });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
public verifyPhone(phone:string, time:CompactDateTime):void {
|
|
88
|
-
const idx = this._phones.firstIndexWhere(p => p.phone === phone);
|
|
89
|
-
if (idx >= 0 && this._phones.get(idx).verified_at !== time) {
|
|
90
|
-
const batch = new BatchOperation(this);
|
|
91
|
-
batch.setPathValue([ 'payload', 'phones', idx, 'verified_at' ], time);
|
|
92
|
-
batch.commit();
|
|
93
|
-
} else if (idx < 0) {
|
|
94
|
-
this._phones.push({ phone, verified_at: time });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|