@rimori/client 2.1.8 → 2.2.0-next.2
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/.github/workflows/pre-release.yml +126 -0
- package/dist/cli/scripts/init/main.js +0 -0
- package/dist/cli/scripts/release/release.js +0 -0
- package/dist/cli/types/DatabaseTypes.d.ts +12 -4
- package/dist/controller/AccomplishmentController.js +6 -2
- package/dist/controller/SettingsController.d.ts +6 -2
- package/dist/controller/SharedContentController.js +28 -25
- package/dist/controller/TranslationController.d.ts +4 -2
- package/dist/controller/TranslationController.js +40 -18
- package/dist/fromRimori/EventBus.js +45 -18
- package/dist/index.d.ts +2 -1
- package/dist/plugin/CommunicationHandler.d.ts +1 -0
- package/dist/plugin/CommunicationHandler.js +9 -7
- package/dist/plugin/Logger.d.ts +1 -0
- package/dist/plugin/Logger.js +15 -3
- package/dist/plugin/RimoriClient.js +19 -8
- package/package.json +15 -1
- package/src/cli/types/DatabaseTypes.ts +17 -16
- package/src/controller/AccomplishmentController.ts +7 -3
- package/src/controller/SettingsController.ts +7 -2
- package/src/controller/SharedContentController.ts +34 -32
- package/src/controller/TranslationController.ts +51 -22
- package/src/fromRimori/EventBus.ts +146 -49
- package/src/index.ts +2 -1
- package/src/plugin/CommunicationHandler.ts +10 -7
- package/src/plugin/Logger.ts +15 -3
- package/src/plugin/RimoriClient.ts +19 -8
|
@@ -101,9 +101,9 @@ export class RimoriClient {
|
|
|
101
101
|
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
102
102
|
this.event.emit('action.requestMain');
|
|
103
103
|
this.event.on('action.requestMain', ({ data }) => {
|
|
104
|
-
console.log('Received action ' + data.
|
|
105
|
-
console.log('Listening to actions', listeningActions);
|
|
106
|
-
if (
|
|
104
|
+
// console.log('Received action for main panel ' + data.action_key);
|
|
105
|
+
// console.log('Listening to actions', listeningActions);
|
|
106
|
+
if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
|
|
107
107
|
callback(data);
|
|
108
108
|
}
|
|
109
109
|
});
|
|
@@ -113,9 +113,10 @@ export class RimoriClient {
|
|
|
113
113
|
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
114
114
|
this.event.emit('action.requestSidebar');
|
|
115
115
|
this.event.on('action.requestSidebar', ({ data }) => {
|
|
116
|
-
console.log(
|
|
117
|
-
console.log('
|
|
118
|
-
|
|
116
|
+
// console.log("eventHandler .onSidePanelAction", data);
|
|
117
|
+
// console.log('Received action for sidebar ' + data.action);
|
|
118
|
+
// console.log('Listening to actions', listeningActions);
|
|
119
|
+
if (listeningActions.length === 0 || listeningActions.includes(data.action)) {
|
|
119
120
|
callback(data);
|
|
120
121
|
}
|
|
121
122
|
});
|
|
@@ -276,7 +277,8 @@ export class RimoriClient {
|
|
|
276
277
|
this.accomplishmentHandler = new AccomplishmentController(info.pluginId);
|
|
277
278
|
this.settingsController = new SettingsController(supabase, info.pluginId, info.guild);
|
|
278
279
|
this.sharedContentController = new SharedContentController(supabase, this);
|
|
279
|
-
|
|
280
|
+
const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
|
|
281
|
+
this.translator = new Translator(info.interfaceLanguage, (currentPlugin === null || currentPlugin === void 0 ? void 0 : currentPlugin.endpoint) || '');
|
|
280
282
|
//only init logger in workers and on main plugin pages
|
|
281
283
|
if (this.getQueryParam('applicationMode') !== 'sidebar') {
|
|
282
284
|
Logger.getInstance(this);
|
|
@@ -337,7 +339,16 @@ export class RimoriClient {
|
|
|
337
339
|
// relation: ViewName,
|
|
338
340
|
// ): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
|
|
339
341
|
from: (relation) => {
|
|
340
|
-
|
|
342
|
+
const tableName = this.db.getTableName(relation);
|
|
343
|
+
// Use plugins schema for plugin-specific tables (those with plugin prefix pattern pl[0-9]+_*)
|
|
344
|
+
// Global tables (starting with 'global_') remain in public schema
|
|
345
|
+
// Plugin tables always have the prefix, so use plugins schema for all prefixed tables
|
|
346
|
+
if (relation.startsWith('global_')) {
|
|
347
|
+
// Global tables stay in public schema
|
|
348
|
+
return this.superbase.from(tableName);
|
|
349
|
+
}
|
|
350
|
+
// All plugin tables go to plugins schema
|
|
351
|
+
return this.superbase.schema('plugins').from(tableName);
|
|
341
352
|
},
|
|
342
353
|
// storage: this.superbase.storage,
|
|
343
354
|
// functions: this.superbase.functions,
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimori/client",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0-next.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/rimori-org/rimori-client.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "apache-2.0",
|
|
6
11
|
"bin": {
|
|
7
12
|
"rimori-release": "./dist/cli/scripts/release/release.js",
|
|
8
13
|
"rimori-init": "./dist/cli/scripts/init/main.js"
|
|
@@ -26,6 +31,14 @@
|
|
|
26
31
|
"dotenv": "16.5.0",
|
|
27
32
|
"i18next": "^25.6.0"
|
|
28
33
|
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"html2canvas": "^1.4.1"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"html2canvas": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
29
42
|
"devDependencies": {
|
|
30
43
|
"@eslint/js": "^9.37.0",
|
|
31
44
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -33,6 +46,7 @@
|
|
|
33
46
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
34
47
|
"eslint-plugin-react-refresh": "^0.4.23",
|
|
35
48
|
"globals": "^16.4.0",
|
|
49
|
+
"html2canvas": "^1.4.1",
|
|
36
50
|
"prettier": "^3.6.2",
|
|
37
51
|
"typescript": "^5.7.2",
|
|
38
52
|
"typescript-eslint": "^8.46.0"
|
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Supported database column data types for table schema definitions.
|
|
5
5
|
*/
|
|
6
|
-
type DbColumnType =
|
|
7
|
-
| 'decimal'
|
|
8
|
-
| 'integer'
|
|
9
|
-
| 'text'
|
|
10
|
-
| 'boolean'
|
|
11
|
-
| 'json'
|
|
12
|
-
| 'timestamp'
|
|
13
|
-
| 'uuid';
|
|
6
|
+
type DbColumnType = 'decimal' | 'integer' | 'text' | 'boolean' | 'json' | 'timestamp' | 'uuid';
|
|
14
7
|
|
|
15
8
|
/**
|
|
16
9
|
* Foreign key relationship configuration with cascade delete support.
|
|
@@ -55,10 +48,12 @@ export interface DbColumnDefinition {
|
|
|
55
48
|
restrict?: {
|
|
56
49
|
/** Restrictions for the user */
|
|
57
50
|
user: Partial<Omit<DbPermissionDefinition, 'delete'>>;
|
|
58
|
-
/** Restrictions for the moderator */
|
|
59
|
-
|
|
51
|
+
/** Restrictions for the guild moderator */
|
|
52
|
+
guild_moderator?: Partial<Omit<DbPermissionDefinition, 'delete'>>;
|
|
53
|
+
/** Restrictions for the language moderator */
|
|
54
|
+
lang_moderator?: Partial<Omit<DbPermissionDefinition, 'delete'>>;
|
|
60
55
|
/** Restrictions for the maintainer */
|
|
61
|
-
// maintainer?: Partial<DbPermissionDefinition
|
|
56
|
+
// maintainer?: Partial<Omit<DbPermissionDefinition, 'delete'>>;
|
|
62
57
|
};
|
|
63
58
|
}
|
|
64
59
|
|
|
@@ -86,9 +81,14 @@ export interface DbTableDefinition {
|
|
|
86
81
|
description: string;
|
|
87
82
|
/** Permissions for the table */
|
|
88
83
|
permissions: {
|
|
84
|
+
/** Permissions for the user */
|
|
89
85
|
user: DbPermissionDefinition;
|
|
90
|
-
moderator
|
|
91
|
-
|
|
86
|
+
/** Permissions for the guild moderator */
|
|
87
|
+
guild_moderator?: DbPermissionDefinition;
|
|
88
|
+
/** Permissions for the language moderator */
|
|
89
|
+
lang_moderator?: DbPermissionDefinition;
|
|
90
|
+
/** Permissions for the maintainer */
|
|
91
|
+
// maintainer?: DbPermissionDefinition;
|
|
92
92
|
};
|
|
93
93
|
/** Column definitions for the table */
|
|
94
94
|
columns: {
|
|
@@ -100,11 +100,13 @@ export interface DbTableDefinition {
|
|
|
100
100
|
* Permission definition for a database table.
|
|
101
101
|
* NONE means the action is not allowed.
|
|
102
102
|
* OWN means only do the action on your own records.
|
|
103
|
+
* GUILD means do the action on all records in the guild.
|
|
104
|
+
* LANG means do the action on all records in the language.
|
|
103
105
|
* ALL means do the action on all records.
|
|
104
106
|
*
|
|
105
107
|
* Defines the permissions for a database table.
|
|
106
108
|
*/
|
|
107
|
-
export type DbPermission = 'NONE' | 'OWN' | 'ALL';
|
|
109
|
+
export type DbPermission = 'NONE' | 'OWN' | 'GUILD' | 'LANG' | 'ALL';
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
112
|
* Permission definition for a database table.
|
|
@@ -120,5 +122,4 @@ export interface DbPermissionDefinition {
|
|
|
120
122
|
/**
|
|
121
123
|
* Full table definition that includes automatically generated fields.
|
|
122
124
|
*/
|
|
123
|
-
export type FullTable<T extends Record<string, DbColumnDefinition>> = T &
|
|
124
|
-
BaseTableStructure;
|
|
125
|
+
export type FullTable<T extends Record<string, DbColumnDefinition>> = T & BaseTableStructure;
|
|
@@ -49,7 +49,9 @@ export class AccomplishmentController {
|
|
|
49
49
|
type: 'durationMinutes' in payload ? 'macro' : 'micro',
|
|
50
50
|
} as AccomplishmentPayload;
|
|
51
51
|
|
|
52
|
-
this.validateAccomplishment(accomplishmentPayload)
|
|
52
|
+
if (!this.validateAccomplishment(accomplishmentPayload)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
53
55
|
|
|
54
56
|
const sanitizedPayload = this.sanitizeAccomplishment(accomplishmentPayload);
|
|
55
57
|
|
|
@@ -58,7 +60,7 @@ export class AccomplishmentController {
|
|
|
58
60
|
EventBus.emit(this.pluginId, topic, sanitizedPayload);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
private validateAccomplishment(payload: AccomplishmentPayload) {
|
|
63
|
+
private validateAccomplishment(payload: AccomplishmentPayload): boolean {
|
|
62
64
|
if (!skillCategories.includes(payload.skillCategory)) {
|
|
63
65
|
throw new Error(`Invalid skill category: ${payload.skillCategory}`);
|
|
64
66
|
}
|
|
@@ -82,7 +84,8 @@ export class AccomplishmentController {
|
|
|
82
84
|
|
|
83
85
|
//durationMinutes is required
|
|
84
86
|
if (payload.type === 'macro' && payload.durationMinutes < 4) {
|
|
85
|
-
|
|
87
|
+
console.warn('The duration must be at least 4 minutes');
|
|
88
|
+
return false;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
//errorRatio is required
|
|
@@ -98,6 +101,7 @@ export class AccomplishmentController {
|
|
|
98
101
|
}
|
|
99
102
|
});
|
|
100
103
|
}
|
|
104
|
+
return true;
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
private sanitizeAccomplishment(payload: AccomplishmentPayload) {
|
|
@@ -19,6 +19,8 @@ export interface Language {
|
|
|
19
19
|
uppercase: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export type UserRole = 'user' | 'plugin_moderator' | 'lang_moderator' | 'admin';
|
|
23
|
+
|
|
22
24
|
export interface UserInfo {
|
|
23
25
|
skill_level_reading: LanguageLevel;
|
|
24
26
|
skill_level_writing: LanguageLevel;
|
|
@@ -32,8 +34,7 @@ export interface UserInfo {
|
|
|
32
34
|
story_genre: string;
|
|
33
35
|
study_duration: number;
|
|
34
36
|
/**
|
|
35
|
-
* The
|
|
36
|
-
* With the function getLanguageName, the language name can be retrieved.
|
|
37
|
+
* The language the user speaks natively.
|
|
37
38
|
*/
|
|
38
39
|
mother_tongue: Language;
|
|
39
40
|
/**
|
|
@@ -52,6 +53,10 @@ export interface UserInfo {
|
|
|
52
53
|
* Optional: nearest big city (>100,000) near user's location
|
|
53
54
|
*/
|
|
54
55
|
target_city?: string;
|
|
56
|
+
/**
|
|
57
|
+
* The user's role: 'user', 'plugin_moderator', 'lang_moderator', or 'admin'
|
|
58
|
+
*/
|
|
59
|
+
user_role: UserRole;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
export class SettingsController {
|
|
@@ -36,38 +36,40 @@ export class SharedContentController {
|
|
|
36
36
|
filter?: SharedContentFilter,
|
|
37
37
|
options?: { privateTopic?: boolean; skipDbSave?: boolean; alwaysGenerateNew?: boolean; excludeIds?: string[] },
|
|
38
38
|
): Promise<SharedContent<T>> {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
39
|
+
// The db cache of the shared content is temporary disabled until the new shared content implementation is completed
|
|
40
|
+
// if (false) {
|
|
41
|
+
// let query = this.supabase
|
|
42
|
+
// .from('shared_content')
|
|
43
|
+
// .select('*, scc:shared_content_completed(id, state)')
|
|
44
|
+
// .eq('content_type', contentType)
|
|
45
|
+
// .not('scc.state', 'in', '("completed","ongoing","hidden")')
|
|
46
|
+
// .is('deleted_at', null);
|
|
47
|
+
|
|
48
|
+
// if (options?.excludeIds?.length ?? 0 > 0) {
|
|
49
|
+
// const excludeIds = options.excludeIds.filter((id) => !id.startsWith('internal-temp-id-'));
|
|
50
|
+
// // Supabase expects raw PostgREST syntax like '("id1","id2")'.
|
|
51
|
+
// const excludeList = `(${excludeIds.map((id) => `"${id}"`).join(',')})`;
|
|
52
|
+
// query = query.not('id', 'in', excludeList);
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// if (filter) {
|
|
56
|
+
// query.contains('data', filter);
|
|
57
|
+
// }
|
|
58
|
+
|
|
59
|
+
// const { data: newAssignments, error } = await query.limit(30);
|
|
60
|
+
|
|
61
|
+
// if (error) {
|
|
62
|
+
// console.error('error fetching new assignments:', error);
|
|
63
|
+
// throw new Error('error fetching new assignments');
|
|
64
|
+
// }
|
|
65
|
+
|
|
66
|
+
// // console.log('newAssignments:', newAssignments);
|
|
67
|
+
|
|
68
|
+
// if (!options?.alwaysGenerateNew && newAssignments.length > 0) {
|
|
69
|
+
// const index = Math.floor(Math.random() * newAssignments.length);
|
|
70
|
+
// return newAssignments[index];
|
|
71
|
+
// }
|
|
72
|
+
// }
|
|
71
73
|
const instructions = await this.generateNewAssignment(contentType, generatorInstructions, filter);
|
|
72
74
|
|
|
73
75
|
console.log('instructions:', instructions);
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { createInstance, ThirdPartyModule, TOptions, i18n as i18nType } from 'i18next';
|
|
2
2
|
|
|
3
|
+
type InitializationState = 'not-inited' | 'initing' | 'finished';
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Translator class for handling internationalization
|
|
5
7
|
*/
|
|
6
8
|
export class Translator {
|
|
7
9
|
private currentLanguage: string;
|
|
8
|
-
private
|
|
10
|
+
private initializationState: InitializationState;
|
|
11
|
+
private initializationPromise: Promise<void> | null;
|
|
9
12
|
private i18n: i18nType | undefined;
|
|
13
|
+
private translationUrl: string;
|
|
10
14
|
|
|
11
|
-
constructor(initialLanguage: string) {
|
|
12
|
-
this.isInitialized = false;
|
|
15
|
+
constructor(initialLanguage: string, translationUrl: string) {
|
|
13
16
|
this.currentLanguage = initialLanguage;
|
|
17
|
+
this.initializationState = 'not-inited';
|
|
18
|
+
this.initializationPromise = null;
|
|
19
|
+
this.translationUrl = translationUrl;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
@@ -18,23 +24,46 @@ export class Translator {
|
|
|
18
24
|
* @param userLanguage - Language code from user info
|
|
19
25
|
*/
|
|
20
26
|
async initialize(): Promise<void> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
// If already finished, return immediately
|
|
28
|
+
if (this.initializationState === 'finished') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If currently initializing, wait for the existing initialization to complete
|
|
33
|
+
if (this.initializationState === 'initing' && this.initializationPromise) {
|
|
34
|
+
return this.initializationPromise;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Start initialization
|
|
38
|
+
this.initializationState = 'initing';
|
|
39
|
+
|
|
40
|
+
// Create a promise that will be resolved when initialization completes
|
|
41
|
+
this.initializationPromise = (async (): Promise<void> => {
|
|
42
|
+
try {
|
|
43
|
+
const translations = await this.fetchTranslations(this.currentLanguage);
|
|
44
|
+
|
|
45
|
+
const instance = createInstance({
|
|
46
|
+
lng: this.currentLanguage,
|
|
47
|
+
resources: {
|
|
48
|
+
[this.currentLanguage]: {
|
|
49
|
+
translation: translations,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
debug: window.location.hostname === 'localhost',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await instance.init();
|
|
56
|
+
this.i18n = instance;
|
|
57
|
+
this.initializationState = 'finished';
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Reset state on error so it can be retried
|
|
60
|
+
this.initializationState = 'not-inited';
|
|
61
|
+
this.initializationPromise = null;
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
return this.initializationPromise;
|
|
38
67
|
}
|
|
39
68
|
|
|
40
69
|
private getTranslationUrl(language: string): string {
|
|
@@ -45,7 +74,7 @@ export class Translator {
|
|
|
45
74
|
return `${window.location.origin}/locales/${filename}.json`;
|
|
46
75
|
}
|
|
47
76
|
|
|
48
|
-
return
|
|
77
|
+
return `${this.translationUrl}/locales/${language}.json`;
|
|
49
78
|
}
|
|
50
79
|
|
|
51
80
|
public usePlugin(plugin: ThirdPartyModule): void {
|
|
@@ -102,6 +131,6 @@ export class Translator {
|
|
|
102
131
|
* Check if translator is initialized
|
|
103
132
|
*/
|
|
104
133
|
isReady(): boolean {
|
|
105
|
-
return this.
|
|
134
|
+
return this.initializationState === 'finished';
|
|
106
135
|
}
|
|
107
136
|
}
|