@rimori/client 2.5.5 → 2.5.6-next.0
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/cli/types/DatabaseTypes.d.ts +36 -3
- package/dist/fromRimori/EventBus.d.ts +11 -0
- package/dist/fromRimori/EventBus.js +37 -2
- package/dist/fromRimori/PluginTypes.d.ts +10 -1
- package/dist/index.d.ts +1 -1
- package/dist/plugin/RimoriClient.d.ts +3 -90
- package/dist/plugin/RimoriClient.js +2 -103
- package/dist/plugin/module/EventModule.d.ts +3 -3
- package/dist/plugin/module/EventModule.js +2 -2
- package/dist/plugin/module/SharedContentController.d.ts +142 -0
- package/dist/plugin/module/SharedContentController.js +310 -0
- package/package.json +1 -1
- package/dist/controller/SharedContentController.d.ts +0 -106
- package/dist/controller/SharedContentController.js +0 -288
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export class SharedContentController {
|
|
11
|
+
constructor(supabase, rimoriClient) {
|
|
12
|
+
this.supabase = supabase;
|
|
13
|
+
this.rimoriClient = rimoriClient;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get new shared content. First searches for existing content matching filters that hasn't been completed,
|
|
17
|
+
* then falls back to AI generation if nothing suitable is found.
|
|
18
|
+
* @param params - Parameters object
|
|
19
|
+
* @param params.table - Name of the shared content table (without plugin prefix)
|
|
20
|
+
* @param params.skillType - Type of skill this content is for (grammar, reading, writing, speaking, listening, understanding)
|
|
21
|
+
* @param params.placeholders - Placeholders for instructions template for AI generation (e.g., {topicAreas: "history"})
|
|
22
|
+
* @param params.filter - Filter to find existing content:
|
|
23
|
+
* - `exact`: Match field value exactly (e.g., {topic_category: {filterType: "exact", value: "history"}})
|
|
24
|
+
* - `exclude`: Exclude specific field value (e.g., {difficulty: {filterType: "exclude", value: "hard"}})
|
|
25
|
+
* - `rag`: Use semantic similarity search (e.g., {topic: {filterType: "rag", value: "japanese culture"}})
|
|
26
|
+
* @param params.customFields - Custom field values for AI-generated content (e.g., {topic_category: "history"})
|
|
27
|
+
* @param params.skipDbSave - If true, don't save generated content to database
|
|
28
|
+
* @param params.isPrivate - If true, content is guild-specific
|
|
29
|
+
* @returns Existing or newly generated shared content item
|
|
30
|
+
*/
|
|
31
|
+
getNew(params) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
// Generate new content via backend endpoint
|
|
34
|
+
const response = yield this.rimoriClient.runtime.fetchBackend('/shared-content/generate', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
tableName: params.table,
|
|
39
|
+
skillType: params.skillType,
|
|
40
|
+
placeholders: params.placeholders,
|
|
41
|
+
filter: params.filter,
|
|
42
|
+
customFields: params.customFields,
|
|
43
|
+
tool: params.tool,
|
|
44
|
+
options: {
|
|
45
|
+
skipDbSave: params.skipDbSave,
|
|
46
|
+
isPrivate: params.isPrivate,
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`Failed to generate shared content: ${response.statusText}`);
|
|
52
|
+
}
|
|
53
|
+
return yield response.json();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Search for shared content by topic using RAG (semantic similarity).
|
|
58
|
+
* @param tableName - Name of the shared content table
|
|
59
|
+
* @param topic - Topic to search for
|
|
60
|
+
* @param limit - Maximum number of results
|
|
61
|
+
* @returns Array of similar shared content
|
|
62
|
+
*/
|
|
63
|
+
searchByTopic(tableName_1, topic_1) {
|
|
64
|
+
return __awaiter(this, arguments, void 0, function* (tableName, topic, limit = 10) {
|
|
65
|
+
const fullTableName = this.getTableName(tableName);
|
|
66
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
67
|
+
// Generate embedding for search topic
|
|
68
|
+
const response = yield this.rimoriClient.runtime.fetchBackend('/ai/embedding', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ text: topic }),
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Failed to generate embedding: ${response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
const { embedding } = yield response.json();
|
|
77
|
+
// RPC call for vector similarity search with completion filtering
|
|
78
|
+
const { data, error } = yield this.supabase.rpc('search_shared_content', {
|
|
79
|
+
p_table_name: fullTableName,
|
|
80
|
+
p_completed_table_name: completedTableName,
|
|
81
|
+
p_embedding: JSON.stringify(embedding),
|
|
82
|
+
p_limit: limit,
|
|
83
|
+
});
|
|
84
|
+
if (error) {
|
|
85
|
+
console.error('Error searching shared content:', error);
|
|
86
|
+
throw new Error('Error searching shared content');
|
|
87
|
+
}
|
|
88
|
+
return data || [];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get bookmarked shared content.
|
|
93
|
+
* @param tableName - Name of the shared content table
|
|
94
|
+
* @param limit - Maximum number of results
|
|
95
|
+
* @returns Array of bookmarked content
|
|
96
|
+
*/
|
|
97
|
+
getBookmarked(tableName_1) {
|
|
98
|
+
return __awaiter(this, arguments, void 0, function* (tableName, limit = 30) {
|
|
99
|
+
const fullTableName = this.getTableName(tableName);
|
|
100
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
101
|
+
const { data, error } = yield this.supabase
|
|
102
|
+
.from(fullTableName)
|
|
103
|
+
.select(`*, completed:${completedTableName}!inner(*)`)
|
|
104
|
+
.eq(`completed.bookmarked`, true)
|
|
105
|
+
.limit(limit);
|
|
106
|
+
if (error) {
|
|
107
|
+
console.error('Error fetching bookmarked content:', error);
|
|
108
|
+
throw new Error('Error fetching bookmarked content');
|
|
109
|
+
}
|
|
110
|
+
return (data || []);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get ongoing shared content.
|
|
115
|
+
* @param tableName - Name of the shared content table
|
|
116
|
+
* @param limit - Maximum number of results
|
|
117
|
+
* @returns Array of ongoing content
|
|
118
|
+
*/
|
|
119
|
+
getOngoing(tableName_1) {
|
|
120
|
+
return __awaiter(this, arguments, void 0, function* (tableName, limit = 30) {
|
|
121
|
+
const fullTableName = this.getTableName(tableName);
|
|
122
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
123
|
+
const { data, error } = yield this.supabase
|
|
124
|
+
.from(fullTableName)
|
|
125
|
+
.select(`*, completed:${completedTableName}!inner(*)`)
|
|
126
|
+
.eq(`completed.state`, 'ongoing')
|
|
127
|
+
.limit(limit);
|
|
128
|
+
if (error) {
|
|
129
|
+
console.error('Error fetching ongoing content:', error);
|
|
130
|
+
throw new Error('Error fetching ongoing content');
|
|
131
|
+
}
|
|
132
|
+
return (data || []);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get completed shared content.
|
|
137
|
+
* @param tableName - Name of the shared content table
|
|
138
|
+
* @param limit - Maximum number of results
|
|
139
|
+
* @returns Array of completed content
|
|
140
|
+
*/
|
|
141
|
+
getCompleted(tableName_1) {
|
|
142
|
+
return __awaiter(this, arguments, void 0, function* (tableName, limit = 30) {
|
|
143
|
+
const fullTableName = this.getTableName(tableName);
|
|
144
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
145
|
+
const { data, error } = yield this.supabase
|
|
146
|
+
.from(fullTableName)
|
|
147
|
+
.select(`*, completed:${completedTableName}!inner(*)`)
|
|
148
|
+
.eq(`completed.state`, 'completed')
|
|
149
|
+
.limit(limit);
|
|
150
|
+
if (error) {
|
|
151
|
+
console.error('Error fetching completed content:', error);
|
|
152
|
+
throw new Error('Error fetching completed content');
|
|
153
|
+
}
|
|
154
|
+
return (data || []);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Mark shared content as completed.
|
|
159
|
+
* @param tableName - Name of the shared content table
|
|
160
|
+
* @param contentId - ID of the content to mark as completed
|
|
161
|
+
*/
|
|
162
|
+
complete(tableName, contentId) {
|
|
163
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
165
|
+
const { error } = yield this.supabase.from(completedTableName).upsert({
|
|
166
|
+
content_id: contentId,
|
|
167
|
+
state: 'completed',
|
|
168
|
+
}, { onConflict: 'content_id,user_id' });
|
|
169
|
+
if (error) {
|
|
170
|
+
console.error('Error completing shared content:', error);
|
|
171
|
+
throw new Error('Error completing shared content');
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Update the state of shared content.
|
|
177
|
+
* @param tableName - Name of the shared content table
|
|
178
|
+
* @param contentId - ID of the content
|
|
179
|
+
* @param state - New state
|
|
180
|
+
*/
|
|
181
|
+
updateState(tableName, contentId, state) {
|
|
182
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
183
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
184
|
+
const { error } = yield this.supabase.from(completedTableName).upsert({
|
|
185
|
+
content_id: contentId,
|
|
186
|
+
state,
|
|
187
|
+
}, { onConflict: 'content_id,user_id' });
|
|
188
|
+
if (error) {
|
|
189
|
+
console.error('Error updating content state:', error);
|
|
190
|
+
throw new Error('Error updating content state');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Bookmark or unbookmark shared content.
|
|
196
|
+
* @param tableName - Name of the shared content table
|
|
197
|
+
* @param contentId - ID of the content
|
|
198
|
+
* @param bookmarked - Whether to bookmark or unbookmark
|
|
199
|
+
*/
|
|
200
|
+
bookmark(tableName, contentId, bookmarked) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
203
|
+
const { error } = yield this.supabase.from(completedTableName).upsert({
|
|
204
|
+
content_id: contentId,
|
|
205
|
+
bookmarked,
|
|
206
|
+
}, { onConflict: 'content_id,user_id' });
|
|
207
|
+
if (error) {
|
|
208
|
+
console.error('Error bookmarking content:', error);
|
|
209
|
+
throw new Error('Error bookmarking content');
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* React to shared content with like/dislike.
|
|
215
|
+
* @param tableName - Name of the shared content table
|
|
216
|
+
* @param contentId - ID of the content
|
|
217
|
+
* @param reaction - Reaction type or null to remove reaction
|
|
218
|
+
*/
|
|
219
|
+
react(tableName, contentId, reaction) {
|
|
220
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
221
|
+
const completedTableName = this.getCompletedTableName(tableName);
|
|
222
|
+
const { error } = yield this.supabase.from(completedTableName).upsert({
|
|
223
|
+
content_id: contentId,
|
|
224
|
+
reaction,
|
|
225
|
+
}, { onConflict: 'content_id,user_id' });
|
|
226
|
+
if (error) {
|
|
227
|
+
console.error('Error reacting to content:', error);
|
|
228
|
+
throw new Error('Error reacting to content');
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get a specific shared content item by ID.
|
|
234
|
+
* @param tableName - Name of the shared content table
|
|
235
|
+
* @param contentId - ID of the content
|
|
236
|
+
* @returns The shared content item
|
|
237
|
+
*/
|
|
238
|
+
get(tableName, contentId) {
|
|
239
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
const fullTableName = this.getTableName(tableName);
|
|
241
|
+
const { data, error } = yield this.supabase.from(fullTableName).select('*').eq('id', contentId).single();
|
|
242
|
+
if (error) {
|
|
243
|
+
console.error('Error fetching shared content:', error);
|
|
244
|
+
throw new Error('Error fetching shared content');
|
|
245
|
+
}
|
|
246
|
+
return data;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Create new shared content manually.
|
|
251
|
+
* @param tableName - Name of the shared content table
|
|
252
|
+
* @param content - Content to create
|
|
253
|
+
* @returns Created content
|
|
254
|
+
*/
|
|
255
|
+
create(tableName, content) {
|
|
256
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
257
|
+
const fullTableName = this.getTableName(tableName);
|
|
258
|
+
const { data, error } = yield this.supabase.from(fullTableName).insert(content).select().single();
|
|
259
|
+
if (error) {
|
|
260
|
+
console.error('Error creating shared content:', error);
|
|
261
|
+
throw new Error('Error creating shared content');
|
|
262
|
+
}
|
|
263
|
+
return data;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Update existing shared content.
|
|
268
|
+
* @param tableName - Name of the shared content table
|
|
269
|
+
* @param contentId - ID of the content to update
|
|
270
|
+
* @param updates - Updates to apply
|
|
271
|
+
* @returns Updated content
|
|
272
|
+
*/
|
|
273
|
+
update(tableName, contentId, updates) {
|
|
274
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
275
|
+
const fullTableName = this.getTableName(tableName);
|
|
276
|
+
const { data, error } = yield this.supabase
|
|
277
|
+
.from(fullTableName)
|
|
278
|
+
.update(updates)
|
|
279
|
+
.eq('id', contentId)
|
|
280
|
+
.select()
|
|
281
|
+
.single();
|
|
282
|
+
if (error) {
|
|
283
|
+
console.error('Error updating shared content:', error);
|
|
284
|
+
throw new Error('Error updating shared content');
|
|
285
|
+
}
|
|
286
|
+
return data;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Delete shared content.
|
|
291
|
+
* @param tableName - Name of the shared content table
|
|
292
|
+
* @param contentId - ID of the content to delete
|
|
293
|
+
*/
|
|
294
|
+
remove(tableName, contentId) {
|
|
295
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
const fullTableName = this.getTableName(tableName);
|
|
297
|
+
const { error } = yield this.supabase.from(fullTableName).delete().eq('id', contentId);
|
|
298
|
+
if (error) {
|
|
299
|
+
console.error('Error deleting shared content:', error);
|
|
300
|
+
throw new Error('Error deleting shared content');
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
getCompletedTableName(tableName) {
|
|
305
|
+
return this.getTableName(tableName) + '_completed';
|
|
306
|
+
}
|
|
307
|
+
getTableName(tableName) {
|
|
308
|
+
return `${this.rimoriClient.plugin.pluginId}_sc_${tableName}`;
|
|
309
|
+
}
|
|
310
|
+
}
|
package/package.json
CHANGED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { SupabaseClient } from '../plugin/CommunicationHandler';
|
|
2
|
-
import { RimoriClient } from '../plugin/RimoriClient';
|
|
3
|
-
import { ObjectRequest } from './ObjectController';
|
|
4
|
-
export interface SharedContentObjectRequest extends ObjectRequest {
|
|
5
|
-
fixedProperties?: Record<string, string | number | boolean>;
|
|
6
|
-
}
|
|
7
|
-
export type SharedContentFilter = Record<string, string | number | boolean>;
|
|
8
|
-
export declare class SharedContentController {
|
|
9
|
-
private supabase;
|
|
10
|
-
private rimoriClient;
|
|
11
|
-
constructor(supabase: SupabaseClient, rimoriClient: RimoriClient);
|
|
12
|
-
/**
|
|
13
|
-
* Fetch new shared content for a given content type.
|
|
14
|
-
* @param contentType - The type of content to fetch.
|
|
15
|
-
* @param generatorInstructions - The instructions for the generator. The object needs to have a tool property with a topic and keywords property to let a new unique topic be generated.
|
|
16
|
-
* @param filter - An optional filter to apply to the query.
|
|
17
|
-
* @param options - Optional options.
|
|
18
|
-
* @param options.privateTopic - If the topic should be private and only be visible to the user.
|
|
19
|
-
* @param options.skipDbSave - If true, do not persist a newly generated content to the DB (default false).
|
|
20
|
-
* @param options.alwaysGenerateNew - If true, always generate a new content even if there is already a content with the same filter.
|
|
21
|
-
* @param options.excludeIds - Optional list of shared_content ids to exclude from selection.
|
|
22
|
-
* @returns The new shared content.
|
|
23
|
-
*/
|
|
24
|
-
getNewSharedContent<T>(contentType: string, generatorInstructions: SharedContentObjectRequest, filter?: SharedContentFilter, options?: {
|
|
25
|
-
privateTopic?: boolean;
|
|
26
|
-
skipDbSave?: boolean;
|
|
27
|
-
alwaysGenerateNew?: boolean;
|
|
28
|
-
excludeIds?: string[];
|
|
29
|
-
}): Promise<SharedContent<T>>;
|
|
30
|
-
private generateNewAssignment;
|
|
31
|
-
private getGeneratorInstructions;
|
|
32
|
-
private getCompletedTopics;
|
|
33
|
-
getSharedContent<T>(contentType: string, id: string): Promise<SharedContent<T>>;
|
|
34
|
-
completeSharedContent(contentType: string, assignmentId: string): Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
* Update state details for a shared content entry in shared_content_completed.
|
|
37
|
-
* Assumes table has columns: state ('completed'|'ongoing'|'hidden'), reaction ('liked'|'disliked'|null), bookmarked boolean.
|
|
38
|
-
* Upserts per (id, content_type, user).
|
|
39
|
-
* @param param
|
|
40
|
-
* @param param.contentType - The content type.
|
|
41
|
-
* @param param.id - The shared content id.
|
|
42
|
-
* @param param.state - The state to set.
|
|
43
|
-
* @param param.reaction - Optional reaction.
|
|
44
|
-
* @param param.bookmarked - Optional bookmark flag.
|
|
45
|
-
*/
|
|
46
|
-
updateSharedContentState({ contentType, id, state, reaction, bookmarked, }: {
|
|
47
|
-
contentType: string;
|
|
48
|
-
id: string;
|
|
49
|
-
state?: 'completed' | 'ongoing' | 'hidden';
|
|
50
|
-
reaction?: 'liked' | 'disliked' | null;
|
|
51
|
-
bookmarked?: boolean;
|
|
52
|
-
}): Promise<void>;
|
|
53
|
-
/**
|
|
54
|
-
* Fetch shared content from the database based on optional filters.
|
|
55
|
-
* @param contentType - The type of content to fetch.
|
|
56
|
-
* @param filter - Optional filter to apply to the query.
|
|
57
|
-
* @param limit - Optional limit for the number of results.
|
|
58
|
-
* @returns Array of shared content matching the criteria.
|
|
59
|
-
*/
|
|
60
|
-
getSharedContentList<T>(contentType: string, filter?: SharedContentFilter, limit?: number): Promise<SharedContent<T>[]>;
|
|
61
|
-
/**
|
|
62
|
-
* Insert new shared content into the database.
|
|
63
|
-
* @param param
|
|
64
|
-
* @param param.contentType - The type of content to insert.
|
|
65
|
-
* @param param.title - The title of the content.
|
|
66
|
-
* @param param.keywords - Keywords associated with the content.
|
|
67
|
-
* @param param.data - The content data to store.
|
|
68
|
-
* @param param.privateTopic - Optional flag to indicate if the topic should be private.
|
|
69
|
-
* @returns The inserted shared content.
|
|
70
|
-
* @throws {Error} if insertion fails.
|
|
71
|
-
*/
|
|
72
|
-
createSharedContent<T>({ contentType, title, keywords, data, privateTopic, }: Omit<SharedContent<T>, 'id'>): Promise<SharedContent<T>>;
|
|
73
|
-
/**
|
|
74
|
-
* Update existing shared content in the database.
|
|
75
|
-
* @param id - The ID of the content to update.
|
|
76
|
-
* @param updates - The updates to apply to the shared content.
|
|
77
|
-
* @returns The updated shared content.
|
|
78
|
-
* @throws {Error} if update fails.
|
|
79
|
-
*/
|
|
80
|
-
updateSharedContent<T>(id: string, updates: Partial<SharedContent<T>>): Promise<SharedContent<T>>;
|
|
81
|
-
/**
|
|
82
|
-
* Soft delete shared content by setting the deleted_at timestamp.
|
|
83
|
-
* @param id - The ID of the content to delete.
|
|
84
|
-
* @returns The deleted shared content record.
|
|
85
|
-
* @throws {Error} if deletion fails or content not found.
|
|
86
|
-
*/
|
|
87
|
-
removeSharedContent(id: string): Promise<SharedContent<any>>;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Interface representing shared content in the system.
|
|
91
|
-
* @template T The type of data stored in the content
|
|
92
|
-
*/
|
|
93
|
-
export interface SharedContent<T> {
|
|
94
|
-
/** The id of the content */
|
|
95
|
-
id: string;
|
|
96
|
-
/** The type/category of the content (e.g. 'grammar_exercises', 'flashcards', etc.) */
|
|
97
|
-
contentType: string;
|
|
98
|
-
/** The human readable title of the content */
|
|
99
|
-
title: string;
|
|
100
|
-
/** Array of keywords/tags associated with the content for search and categorization */
|
|
101
|
-
keywords: string[];
|
|
102
|
-
/** The actual content data of type T */
|
|
103
|
-
data: T;
|
|
104
|
-
/** Whether this content should only be visible to the creator. Defaults to false if not specified */
|
|
105
|
-
privateTopic?: boolean;
|
|
106
|
-
}
|