@rimori/client 1.0.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/CRUDModal.d.ts +16 -0
- package/dist/CRUDModal.js +31 -0
- package/dist/MarkdownEditor.d.ts +8 -0
- package/dist/MarkdownEditor.js +46 -0
- package/dist/audio/Playbutton.d.ts +14 -0
- package/dist/audio/Playbutton.js +73 -0
- package/dist/components/CRUDModal.d.ts +17 -0
- package/dist/components/CRUDModal.js +25 -0
- package/dist/components/MarkdownEditor.d.ts +8 -0
- package/dist/components/MarkdownEditor.js +46 -0
- package/dist/components/Spinner.d.ts +8 -0
- package/dist/components/Spinner.js +5 -0
- package/dist/components/audio/Playbutton.d.ts +15 -0
- package/dist/components/audio/Playbutton.js +78 -0
- package/dist/components/hooks/UseChatHook.d.ts +15 -0
- package/dist/components/hooks/UseChatHook.js +21 -0
- package/dist/controller/AIController.d.ts +22 -0
- package/dist/controller/AIController.js +68 -0
- package/dist/controller/ObjectController.d.ts +34 -0
- package/dist/controller/ObjectController.js +77 -0
- package/dist/controller/SettingsController.d.ts +24 -0
- package/dist/controller/SettingsController.js +72 -0
- package/dist/controller/SharedContentController.d.ts +22 -0
- package/dist/controller/SharedContentController.js +56 -0
- package/dist/controller/VoiceController.d.ts +10 -0
- package/dist/controller/VoiceController.js +28 -0
- package/dist/hooks/UseChatHook.d.ts +9 -0
- package/dist/hooks/UseChatHook.js +21 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/plugin/AIController copy.d.ts +22 -0
- package/dist/plugin/AIController copy.js +68 -0
- package/dist/plugin/AIController.d.ts +22 -0
- package/dist/plugin/AIController.js +68 -0
- package/dist/plugin/ObjectController.d.ts +34 -0
- package/dist/plugin/ObjectController.js +77 -0
- package/dist/plugin/PluginController.d.ts +29 -0
- package/dist/plugin/PluginController.js +138 -0
- package/dist/plugin/RimoriClient.d.ts +91 -0
- package/dist/plugin/RimoriClient.js +163 -0
- package/dist/plugin/SettingController.d.ts +13 -0
- package/dist/plugin/SettingController.js +55 -0
- package/dist/plugin/ThemeSetter.d.ts +1 -0
- package/dist/plugin/ThemeSetter.js +13 -0
- package/dist/plugin/VoiceController.d.ts +2 -0
- package/dist/plugin/VoiceController.js +27 -0
- package/dist/providers/EventEmitter.d.ts +11 -0
- package/dist/providers/EventEmitter.js +41 -0
- package/dist/providers/EventEmitterContext.d.ts +6 -0
- package/dist/providers/EventEmitterContext.js +19 -0
- package/dist/providers/PluginProvider.d.ts +8 -0
- package/dist/providers/PluginProvider.js +52 -0
- package/dist/style.css +110 -0
- package/dist/style.css.map +1 -0
- package/dist/utils/DifficultyConverter.d.ts +3 -0
- package/dist/utils/DifficultyConverter.js +7 -0
- package/dist/utils/PluginUtils.d.ts +2 -0
- package/dist/utils/PluginUtils.js +23 -0
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.js +12 -0
- package/dist/utils/difficultyConverter.d.ts +3 -0
- package/dist/utils/difficultyConverter.js +7 -0
- package/dist/utils/plugin/Client.d.ts +72 -0
- package/dist/utils/plugin/Client.js +118 -0
- package/dist/utils/plugin/PluginController.d.ts +36 -0
- package/dist/utils/plugin/PluginController.js +119 -0
- package/dist/utils/plugin/PluginUtils.d.ts +2 -0
- package/dist/utils/plugin/PluginUtils.js +23 -0
- package/dist/utils/plugin/RimoriClient.d.ts +72 -0
- package/dist/utils/plugin/RimoriClient.js +118 -0
- package/dist/utils/plugin/ThemeSetter.d.ts +1 -0
- package/dist/utils/plugin/ThemeSetter.js +13 -0
- package/dist/utils/plugin/WhereClauseBuilder.d.ts +24 -0
- package/dist/utils/plugin/WhereClauseBuilder.js +79 -0
- package/dist/utils/plugin/providers/EventEmitter.d.ts +11 -0
- package/dist/utils/plugin/providers/EventEmitter.js +41 -0
- package/dist/utils/plugin/providers/EventEmitterContext.d.ts +6 -0
- package/dist/utils/plugin/providers/EventEmitterContext.js +19 -0
- package/dist/utils/plugin/providers/PluginProvider.d.ts +8 -0
- package/dist/utils/plugin/providers/PluginProvider.js +49 -0
- package/package.json +30 -0
- package/src/components/CRUDModal.tsx +61 -0
- package/src/components/MarkdownEditor.tsx +111 -0
- package/src/components/Spinner.tsx +24 -0
- package/src/components/audio/Playbutton.tsx +119 -0
- package/src/controller/AIController.ts +87 -0
- package/src/controller/ObjectController.ts +109 -0
- package/src/controller/SettingsController.ts +87 -0
- package/src/controller/SharedContentController.ts +71 -0
- package/src/controller/VoiceController.ts +26 -0
- package/src/hooks/UseChatHook.ts +25 -0
- package/src/index.ts +14 -0
- package/src/plugin/PluginController.ts +158 -0
- package/src/plugin/RimoriClient.ts +207 -0
- package/src/plugin/ThemeSetter.ts +17 -0
- package/src/providers/EventEmitter.ts +48 -0
- package/src/providers/EventEmitterContext.tsx +27 -0
- package/src/providers/PluginProvider.tsx +68 -0
- package/src/style.scss +136 -0
- package/src/utils/PluginUtils.ts +26 -0
- package/src/utils/constants.ts +18 -0
- package/src/utils/difficultyConverter.ts +11 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
import { GenericSchema } from "@supabase/supabase-js/dist/module/lib/types";
|
|
3
|
+
import { PostgrestQueryBuilder, PostgrestFilterBuilder } from "@supabase/postgrest-js";
|
|
4
|
+
import { PluginController, Tool, ToolInvocation } from "./PluginController";
|
|
5
|
+
import { LanguageLevel } from "../DifficultyConverter";
|
|
6
|
+
export declare class RimoriClient {
|
|
7
|
+
private static instance;
|
|
8
|
+
private superbase;
|
|
9
|
+
private plugin;
|
|
10
|
+
functions: SupabaseClient["functions"];
|
|
11
|
+
storage: SupabaseClient["storage"];
|
|
12
|
+
private constructor();
|
|
13
|
+
static getInstance(pluginController: PluginController): Promise<RimoriClient>;
|
|
14
|
+
from<TableName extends string & keyof GenericSchema['Tables'], Table extends GenericSchema['Tables'][TableName]>(relation: TableName): PostgrestQueryBuilder<GenericSchema, Table, GenericSchema, Table extends {
|
|
15
|
+
Relationships: infer R;
|
|
16
|
+
} ? R : unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Perform a function call.
|
|
19
|
+
*
|
|
20
|
+
* @param fn - The function name to call
|
|
21
|
+
* @param args - The arguments to pass to the function call
|
|
22
|
+
* @param options - Named parameters
|
|
23
|
+
* @param options.head - When set to `true`, `data` will not be returned.
|
|
24
|
+
* Useful if you only need the count.
|
|
25
|
+
* @param options.get - When set to `true`, the function will be called with
|
|
26
|
+
* read-only access mode.
|
|
27
|
+
* @param options.count - Count algorithm to use to count rows returned by the
|
|
28
|
+
* function. Only applicable for [set-returning
|
|
29
|
+
* functions](https://www.postgresql.org/docs/current/functions-srf.html).
|
|
30
|
+
*
|
|
31
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
32
|
+
* hood.
|
|
33
|
+
*
|
|
34
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
35
|
+
* statistics under the hood.
|
|
36
|
+
*
|
|
37
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
38
|
+
* numbers.
|
|
39
|
+
*/
|
|
40
|
+
rpc<FnName extends string & keyof GenericSchema['Functions'], Fn extends GenericSchema['Functions'][FnName]>(fn: FnName, args?: Fn['Args'], options?: {
|
|
41
|
+
head?: boolean;
|
|
42
|
+
get?: boolean;
|
|
43
|
+
count?: 'exact' | 'planned' | 'estimated';
|
|
44
|
+
}): PostgrestFilterBuilder<GenericSchema, Fn['Returns'] extends any[] ? Fn['Returns'][number] extends Record<string, unknown> ? Fn['Returns'][number] : never : never, Fn['Returns'], FnName, null>;
|
|
45
|
+
subscribe(eventName: string, callback: (_id: number, data: any) => void): void;
|
|
46
|
+
request<T>(eventName: string, data: any): Promise<T>;
|
|
47
|
+
emit(eventName: string, data: any): void;
|
|
48
|
+
/**
|
|
49
|
+
* Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
|
|
50
|
+
* @param defaultSettings The default settings to use if no settings are found.
|
|
51
|
+
* @param genericSettings The type of settings to get.
|
|
52
|
+
* @returns The settings for the plugin.
|
|
53
|
+
*/
|
|
54
|
+
getSettings<T>(defaultSettings: T, genericSettings?: "user" | "system"): Promise<T>;
|
|
55
|
+
setSettings(settings: any, genericSettings?: "user" | "system"): Promise<void>;
|
|
56
|
+
getAIResponse(messages: {
|
|
57
|
+
role: string;
|
|
58
|
+
content: string;
|
|
59
|
+
}[]): Promise<string>;
|
|
60
|
+
getAIResponseStream(messages: {
|
|
61
|
+
role: string;
|
|
62
|
+
content: string;
|
|
63
|
+
}[], onMessage: (id: string, message: string, finished: boolean, toolInvocations?: ToolInvocation[]) => void, tools?: Tool[]): Promise<void>;
|
|
64
|
+
getVoiceResponse(text: string, voice?: string, speed?: number, language?: string): Promise<Blob>;
|
|
65
|
+
getVoiceToTextResponse(file: Blob): Promise<string>;
|
|
66
|
+
}
|
|
67
|
+
export interface UserSettings {
|
|
68
|
+
motherTongue: string;
|
|
69
|
+
languageLevel: LanguageLevel;
|
|
70
|
+
}
|
|
71
|
+
export interface SystemSettings {
|
|
72
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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 RimoriClient {
|
|
11
|
+
constructor(pluginController, superbase) {
|
|
12
|
+
this.superbase = superbase;
|
|
13
|
+
this.plugin = pluginController;
|
|
14
|
+
this.functions = this.superbase.functions;
|
|
15
|
+
this.storage = this.superbase.storage;
|
|
16
|
+
}
|
|
17
|
+
static getInstance(pluginController) {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
if (!RimoriClient.instance) {
|
|
20
|
+
const superbase = yield pluginController.getClient();
|
|
21
|
+
RimoriClient.instance = new RimoriClient(pluginController, superbase);
|
|
22
|
+
}
|
|
23
|
+
return RimoriClient.instance;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
from(relation) {
|
|
27
|
+
return this.superbase.from(relation);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Perform a function call.
|
|
31
|
+
*
|
|
32
|
+
* @param fn - The function name to call
|
|
33
|
+
* @param args - The arguments to pass to the function call
|
|
34
|
+
* @param options - Named parameters
|
|
35
|
+
* @param options.head - When set to `true`, `data` will not be returned.
|
|
36
|
+
* Useful if you only need the count.
|
|
37
|
+
* @param options.get - When set to `true`, the function will be called with
|
|
38
|
+
* read-only access mode.
|
|
39
|
+
* @param options.count - Count algorithm to use to count rows returned by the
|
|
40
|
+
* function. Only applicable for [set-returning
|
|
41
|
+
* functions](https://www.postgresql.org/docs/current/functions-srf.html).
|
|
42
|
+
*
|
|
43
|
+
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
|
44
|
+
* hood.
|
|
45
|
+
*
|
|
46
|
+
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
|
47
|
+
* statistics under the hood.
|
|
48
|
+
*
|
|
49
|
+
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
|
50
|
+
* numbers.
|
|
51
|
+
*/
|
|
52
|
+
rpc(fn, args = {}, options = {}) {
|
|
53
|
+
return this.superbase.rpc(fn, args, options);
|
|
54
|
+
}
|
|
55
|
+
subscribe(eventName, callback) {
|
|
56
|
+
this.plugin.subscribe(eventName, callback);
|
|
57
|
+
}
|
|
58
|
+
request(eventName, data) {
|
|
59
|
+
return this.plugin.request(eventName, data);
|
|
60
|
+
}
|
|
61
|
+
emit(eventName, data) {
|
|
62
|
+
this.plugin.emit(eventName, data);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
|
|
66
|
+
* @param defaultSettings The default settings to use if no settings are found.
|
|
67
|
+
* @param genericSettings The type of settings to get.
|
|
68
|
+
* @returns The settings for the plugin.
|
|
69
|
+
*/
|
|
70
|
+
getSettings(defaultSettings, genericSettings) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
const response = yield this.plugin.request("get_settings", { genericSettings });
|
|
73
|
+
if (response === null) {
|
|
74
|
+
this.setSettings(defaultSettings, genericSettings);
|
|
75
|
+
return defaultSettings;
|
|
76
|
+
//if the settings are not the same, merge the settings
|
|
77
|
+
}
|
|
78
|
+
else if (Object.keys(response).length !== Object.keys(defaultSettings).length) {
|
|
79
|
+
const existingKeys = Object.fromEntries(Object.entries(response).filter(([k]) => k in defaultSettings));
|
|
80
|
+
const mergedSettings = Object.assign(Object.assign({}, defaultSettings), existingKeys);
|
|
81
|
+
console.warn("Settings mismatch", { response, defaultSettings, mergedSettings });
|
|
82
|
+
this.setSettings(mergedSettings, genericSettings);
|
|
83
|
+
return mergedSettings;
|
|
84
|
+
}
|
|
85
|
+
return response;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
setSettings(settings, genericSettings) {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
yield this.plugin.request("set_settings", { settings, genericSettings });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
getAIResponse(messages) {
|
|
94
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
95
|
+
return this.plugin.request("getAIResponse", messages);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
getAIResponseStream(messages, onMessage, tools) {
|
|
99
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
throw new Error("Not implemented");
|
|
101
|
+
// let triggered = false;
|
|
102
|
+
// console.log("getAIResponseStream", messages);
|
|
103
|
+
// const id = Math.random();
|
|
104
|
+
// this.internalEmit("getAIResponseStream", id, { messages, tools: tools || [] });
|
|
105
|
+
// this.subscribe("getAIResponseStream", (_id: number, data: { id: string, response: string, finished: boolean, toolInvocations?: ToolInvocation[] }) => {
|
|
106
|
+
// if (triggered || (_id !== id && _id !== 0)) return;
|
|
107
|
+
// triggered = data.finished;
|
|
108
|
+
// onMessage(data.id, data.response, data.finished, data.toolInvocations);
|
|
109
|
+
// })
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
getVoiceResponse(text, voice = "alloy", speed = 1, language) {
|
|
113
|
+
return this.plugin.request("getVoiceResponse", { text, voice, speed, language });
|
|
114
|
+
}
|
|
115
|
+
getVoiceToTextResponse(file) {
|
|
116
|
+
return this.plugin.request("getSTTResponse", file);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setTheme(): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function setTheme() {
|
|
2
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
3
|
+
let theme = urlParams.get('theme');
|
|
4
|
+
const isSidebar = urlParams.get('applicationMode') === "sidebar";
|
|
5
|
+
if (!theme || theme === 'system') {
|
|
6
|
+
theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
7
|
+
}
|
|
8
|
+
document.documentElement.classList.add("dark:text-gray-200", "bg-white");
|
|
9
|
+
if (theme === 'dark') {
|
|
10
|
+
document.documentElement.classList.add('dark', isSidebar ? "bg-gray-920" : "bg-gray-950");
|
|
11
|
+
document.documentElement.style.background = isSidebar ? "rgb(6, 12, 30)" : "rgb(3, 7, 18)";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type Condition = {
|
|
2
|
+
column?: string;
|
|
3
|
+
operator?: string;
|
|
4
|
+
value?: any;
|
|
5
|
+
AND?: Condition[];
|
|
6
|
+
OR?: Condition[];
|
|
7
|
+
};
|
|
8
|
+
export declare class WhereClauseBuilder {
|
|
9
|
+
conditions: Condition[];
|
|
10
|
+
constructor();
|
|
11
|
+
eq(column: string, value: any): WhereClauseBuilder;
|
|
12
|
+
neq(column: string, value: any): WhereClauseBuilder;
|
|
13
|
+
gt(column: string, value: any): WhereClauseBuilder;
|
|
14
|
+
lt(column: string, value: any): WhereClauseBuilder;
|
|
15
|
+
gte(column: string, value: any): WhereClauseBuilder;
|
|
16
|
+
lte(column: string, value: any): WhereClauseBuilder;
|
|
17
|
+
like(column: string, pattern: string): WhereClauseBuilder;
|
|
18
|
+
in(column: string, values: any[]): WhereClauseBuilder;
|
|
19
|
+
isNull(column: string): WhereClauseBuilder;
|
|
20
|
+
isNotNull(column: string): WhereClauseBuilder;
|
|
21
|
+
between(column: string, start: any, end: any): WhereClauseBuilder;
|
|
22
|
+
build(): Condition;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export class WhereClauseBuilder {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.conditions = [];
|
|
4
|
+
}
|
|
5
|
+
// Add AND condition
|
|
6
|
+
// and(): WhereClauseBuilder {
|
|
7
|
+
// const builder = new WhereClauseBuilder();
|
|
8
|
+
// this.conditions.push({ AND: builder.conditions });
|
|
9
|
+
// return builder;
|
|
10
|
+
// }
|
|
11
|
+
// Add OR condition
|
|
12
|
+
// or(): WhereClauseBuilder {
|
|
13
|
+
// const builder = new WhereClauseBuilder();
|
|
14
|
+
// this.conditions.push({ OR: builder.conditions });
|
|
15
|
+
// return builder;
|
|
16
|
+
// }
|
|
17
|
+
// Equality check
|
|
18
|
+
eq(column, value) {
|
|
19
|
+
this.conditions.push({ column, operator: "eq", value });
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
// Not equal check
|
|
23
|
+
neq(column, value) {
|
|
24
|
+
this.conditions.push({ column, operator: "neq", value });
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
// Greater than
|
|
28
|
+
gt(column, value) {
|
|
29
|
+
this.conditions.push({ column, operator: "gt", value });
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
// Less than
|
|
33
|
+
lt(column, value) {
|
|
34
|
+
this.conditions.push({ column, operator: "lt", value });
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
// Greater than or equal to
|
|
38
|
+
gte(column, value) {
|
|
39
|
+
this.conditions.push({ column, operator: "gte", value });
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
// Less than or equal to
|
|
43
|
+
lte(column, value) {
|
|
44
|
+
this.conditions.push({ column, operator: "lte", value });
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
// LIKE (pattern match)
|
|
48
|
+
like(column, pattern) {
|
|
49
|
+
this.conditions.push({ column, operator: "like", value: pattern });
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
// IN (for multiple values)
|
|
53
|
+
in(column, values) {
|
|
54
|
+
this.conditions.push({ column, operator: "in", value: values });
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
// IS NULL
|
|
58
|
+
isNull(column) {
|
|
59
|
+
this.conditions.push({ column, operator: "is", value: null });
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
// IS NOT NULL
|
|
63
|
+
isNotNull(column) {
|
|
64
|
+
this.conditions.push({ column, operator: "is", value: { not: null } });
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
// BETWEEN (for range checking)
|
|
68
|
+
between(column, start, end) {
|
|
69
|
+
this.conditions.push({ column, operator: "between", value: [start, end] });
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
// Build and return the final condition object
|
|
73
|
+
build() {
|
|
74
|
+
// if (this.conditions.length === 1) {
|
|
75
|
+
// return this.conditions; // Direct return if only one condition
|
|
76
|
+
// }
|
|
77
|
+
return { AND: this.conditions }; // If multiple conditions, combine using AND
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Listener<T = any> = (event: T) => void;
|
|
2
|
+
export declare class EventEmitter {
|
|
3
|
+
private events;
|
|
4
|
+
constructor();
|
|
5
|
+
on<T = any>(eventName: string, listener: Listener<T>): void;
|
|
6
|
+
once<T = any>(eventName: string, listener: Listener<T>): void;
|
|
7
|
+
removeListener<T = any>(eventName: string, listener: Listener<T>): void;
|
|
8
|
+
emit<T = any>(eventName: string, data?: T): void;
|
|
9
|
+
}
|
|
10
|
+
export declare const EmitterSingleton: EventEmitter;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class EventEmitter {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.events = new Map();
|
|
4
|
+
this.on = this.on.bind(this);
|
|
5
|
+
this.once = this.once.bind(this);
|
|
6
|
+
this.emit = this.emit.bind(this);
|
|
7
|
+
this.removeListener = this.removeListener.bind(this);
|
|
8
|
+
}
|
|
9
|
+
// Subscribe to an event
|
|
10
|
+
on(eventName, listener) {
|
|
11
|
+
if (!this.events.has(eventName)) {
|
|
12
|
+
this.events.set(eventName, []);
|
|
13
|
+
}
|
|
14
|
+
this.events.get(eventName).push(listener);
|
|
15
|
+
}
|
|
16
|
+
// Subscribe to an event for a single invocation
|
|
17
|
+
once(eventName, listener) {
|
|
18
|
+
const onceWrapper = (event) => {
|
|
19
|
+
this.removeListener(eventName, onceWrapper);
|
|
20
|
+
listener(event);
|
|
21
|
+
};
|
|
22
|
+
this.on(eventName, onceWrapper);
|
|
23
|
+
}
|
|
24
|
+
// Remove a specific listener
|
|
25
|
+
removeListener(eventName, listener) {
|
|
26
|
+
const listeners = this.events.get(eventName);
|
|
27
|
+
if (!listeners)
|
|
28
|
+
return;
|
|
29
|
+
this.events.set(eventName, listeners.filter((l) => l !== listener));
|
|
30
|
+
}
|
|
31
|
+
// Emit an event
|
|
32
|
+
emit(eventName, data) {
|
|
33
|
+
const listeners = this.events.get(eventName);
|
|
34
|
+
console.log("emit", eventName, data, listeners);
|
|
35
|
+
if (!listeners)
|
|
36
|
+
return;
|
|
37
|
+
listeners.forEach((listener) => listener(data));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const emitter = new EventEmitter();
|
|
41
|
+
export const EmitterSingleton = emitter;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useRef } from "react";
|
|
4
|
+
import { EmitterSingleton } from "./EventEmitter";
|
|
5
|
+
// Create the Context
|
|
6
|
+
const EventEmitterContext = createContext(null);
|
|
7
|
+
// Provider Component
|
|
8
|
+
export const EventEmitterProvider = ({ children }) => {
|
|
9
|
+
const eventEmitterRef = useRef(EmitterSingleton);
|
|
10
|
+
return (_jsx(EventEmitterContext.Provider, { value: eventEmitterRef.current, children: children }));
|
|
11
|
+
};
|
|
12
|
+
// Hook to use the EventEmitter
|
|
13
|
+
export const useEventEmitter = () => {
|
|
14
|
+
const context = useContext(EventEmitterContext);
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error("useEventEmitter must be used within an EventEmitterProvider");
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { RimoriClient } from '../RimoriClient';
|
|
3
|
+
interface PluginProviderProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export declare const PluginProvider: React.FC<PluginProviderProps>;
|
|
7
|
+
export declare const usePlugin: () => RimoriClient;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
3
|
+
import { PluginController } from '../PluginController';
|
|
4
|
+
const PluginContext = createContext(null);
|
|
5
|
+
export const PluginProvider = ({ children }) => {
|
|
6
|
+
const [plugin, setPlugin] = useState(null);
|
|
7
|
+
//route change
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
let lastHash = window.location.hash;
|
|
10
|
+
setInterval(() => {
|
|
11
|
+
if (lastHash !== window.location.hash) {
|
|
12
|
+
lastHash = window.location.hash;
|
|
13
|
+
console.log('url changed:', lastHash);
|
|
14
|
+
plugin === null || plugin === void 0 ? void 0 : plugin.emit('urlChange', window.location.hash);
|
|
15
|
+
}
|
|
16
|
+
}, 100);
|
|
17
|
+
PluginController.getInstance().then(setPlugin);
|
|
18
|
+
}, []);
|
|
19
|
+
//context menu
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
let isOpen = false;
|
|
22
|
+
const handleContextMenu = (e) => {
|
|
23
|
+
var _a;
|
|
24
|
+
const selection = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString().trim();
|
|
25
|
+
if (selection) {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
// console.log('context menu', selection);
|
|
28
|
+
plugin === null || plugin === void 0 ? void 0 : plugin.emit('contextMenu', { text: selection, x: e.clientX, y: e.clientY, open: true });
|
|
29
|
+
isOpen = true;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
// Hide the menu on click outside
|
|
33
|
+
const handleClick = () => isOpen && (plugin === null || plugin === void 0 ? void 0 : plugin.emit('contextMenu', { text: '', x: 0, y: 0, open: false }));
|
|
34
|
+
document.addEventListener("click", handleClick);
|
|
35
|
+
document.addEventListener('contextmenu', handleContextMenu);
|
|
36
|
+
return () => {
|
|
37
|
+
document.removeEventListener("click", handleClick);
|
|
38
|
+
document.removeEventListener('contextmenu', handleContextMenu);
|
|
39
|
+
};
|
|
40
|
+
}, [plugin]);
|
|
41
|
+
return (_jsx(PluginContext.Provider, { value: plugin, children: children }));
|
|
42
|
+
};
|
|
43
|
+
export const usePlugin = () => {
|
|
44
|
+
const context = useContext(PluginContext);
|
|
45
|
+
if (context === null) {
|
|
46
|
+
throw new Error('usePlugin must be used within an PluginProvider');
|
|
47
|
+
}
|
|
48
|
+
return context;
|
|
49
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rimori/client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc && sass src/style.scss:dist/style.css",
|
|
8
|
+
"dev": "tsc -w",
|
|
9
|
+
"css-dev": "sass --watch src/style.scss:dist/style.css"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"react": "^18.0.0",
|
|
13
|
+
"react-dom": "^18.0.0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@supabase/supabase-js": "^2.48.1",
|
|
17
|
+
"@tiptap/react": "2.10.3",
|
|
18
|
+
"@tiptap/starter-kit": "2.10.3",
|
|
19
|
+
"ibridge-flex": "0.0.1",
|
|
20
|
+
"uuid": "11.1.0",
|
|
21
|
+
"react-icons": "^5.4.0",
|
|
22
|
+
"tiptap-markdown": "^0.8.10",
|
|
23
|
+
"typescript": "^5.7.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/react": "^18.0.1",
|
|
27
|
+
"@types/react-dom": "^18.0.1",
|
|
28
|
+
"sass": "^1.82.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useRef } from "react";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
title: string;
|
|
7
|
+
show?: boolean;
|
|
8
|
+
className?: string;
|
|
9
|
+
closeAble?: boolean;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
actionbuttons: ActionButton[];
|
|
12
|
+
buttonText?: string | React.ReactNode;
|
|
13
|
+
onClose?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ActionButton {
|
|
17
|
+
text: string;
|
|
18
|
+
onClick: () => void;
|
|
19
|
+
closeModal?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function CRUDModal({ actionbuttons, children, title, buttonText, className, closeAble = true, show = false, onClose }: Props) {
|
|
23
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (show) {
|
|
27
|
+
dialogRef.current?.showModal();
|
|
28
|
+
} else {
|
|
29
|
+
dialogRef.current?.close();
|
|
30
|
+
}
|
|
31
|
+
}, [show]);
|
|
32
|
+
|
|
33
|
+
const handleClose = () => {
|
|
34
|
+
dialogRef.current?.close();
|
|
35
|
+
onClose?.();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
{!!buttonText && <button className={className} onClick={() => dialogRef.current?.showModal()}>{buttonText}</button>}
|
|
41
|
+
<dialog ref={dialogRef} className="bg-gray-400 rounded-lg font-normal" onClose={handleClose}>
|
|
42
|
+
<div className="bg-gray-500 text-xl flex flex-row justify-between p-3 items-start font-bold">
|
|
43
|
+
<h2>{title}</h2>
|
|
44
|
+
{closeAble && <button onClick={handleClose}>×</button>}
|
|
45
|
+
</div>
|
|
46
|
+
<div className="modal-body p-2">
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
<div className="modal-footer px-2 py-2 flex flex-row gap-2 border-t-2">
|
|
50
|
+
{actionbuttons.map(({ onClick, text, closeModal = true }, index) => (
|
|
51
|
+
<button key={index} className="bg-blue-500 hover:bg-blue-600 dark:border-gray-900 rounded-md py-2 px-4 dark:text-white font-bold"
|
|
52
|
+
onClick={() => {
|
|
53
|
+
if (closeModal) handleClose();
|
|
54
|
+
onClick();
|
|
55
|
+
}}>{text}</button>
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
</dialog>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Markdown } from 'tiptap-markdown';
|
|
2
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
3
|
+
import { PiCodeBlock } from "react-icons/pi";
|
|
4
|
+
import { TbBlockquote } from "react-icons/tb";
|
|
5
|
+
import { GoListOrdered } from "react-icons/go";
|
|
6
|
+
import { AiOutlineUnorderedList } from "react-icons/ai";
|
|
7
|
+
import { EditorProvider, useCurrentEditor } from "@tiptap/react";
|
|
8
|
+
import { LuHeading1, LuHeading2, LuHeading3 } from "react-icons/lu";
|
|
9
|
+
import { FaBold, FaCode, FaItalic, FaParagraph, FaStrikethrough } from "react-icons/fa";
|
|
10
|
+
|
|
11
|
+
// This inplementation is rooted in the Tiptap editor basic example https://codesandbox.io/p/devbox/editor-9x9dkd
|
|
12
|
+
|
|
13
|
+
interface EditorButtonProps {
|
|
14
|
+
action: string;
|
|
15
|
+
isActive?: boolean;
|
|
16
|
+
label: string | React.ReactNode;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const EditorButton = ({ action, isActive, label, disabled }: EditorButtonProps) => {
|
|
21
|
+
const { editor } = useCurrentEditor() as any;
|
|
22
|
+
|
|
23
|
+
if (!editor) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (action.includes("heading")) {
|
|
28
|
+
const level = parseInt(action[action.length - 1]);
|
|
29
|
+
return (
|
|
30
|
+
<button
|
|
31
|
+
onClick={() => editor.chain().focus().toggleHeading({ level: level }).run()}
|
|
32
|
+
className={`pl-2 ${isActive ? "is-active" : ""}`}
|
|
33
|
+
>
|
|
34
|
+
{label}
|
|
35
|
+
</button>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
onClick={() => editor.chain().focus()[action]().run()}
|
|
42
|
+
disabled={disabled ? !editor.can().chain().focus()[action]().run() : false}
|
|
43
|
+
className={`pl-2 ${isActive ? "is-active" : ""}`}
|
|
44
|
+
>
|
|
45
|
+
{label}
|
|
46
|
+
</button>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const MenuBar = () => {
|
|
51
|
+
const { editor } = useCurrentEditor();
|
|
52
|
+
|
|
53
|
+
if (!editor) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="bg-gray-400 dark:bg-gray-800 dark:text-white text-lg flex flex-row flex-wrap items-center p-1">
|
|
59
|
+
<EditorButton action="toggleBold" isActive={editor.isActive("bold")} label={<FaBold />} disabled />
|
|
60
|
+
<EditorButton action="toggleItalic" isActive={editor.isActive("italic")} label={<FaItalic />} disabled />
|
|
61
|
+
<EditorButton action="toggleStrike" isActive={editor.isActive("strike")} label={<FaStrikethrough />} disabled />
|
|
62
|
+
<EditorButton action="toggleCode" isActive={editor.isActive("code")} label={<FaCode />} disabled />
|
|
63
|
+
<EditorButton action="setParagraph" isActive={editor.isActive("paragraph")} label={<FaParagraph />} />
|
|
64
|
+
<EditorButton action='setHeading1' isActive={editor.isActive("heading", { level: 1 })} label={<LuHeading1 size={"24px"} />} />
|
|
65
|
+
<EditorButton action='setHeading2' isActive={editor.isActive("heading", { level: 2 })} label={<LuHeading2 size={"24px"} />} />
|
|
66
|
+
<EditorButton action='setHeading3' isActive={editor.isActive("heading", { level: 3 })} label={<LuHeading3 size={"24px"} />} />
|
|
67
|
+
<EditorButton action="toggleBulletList" isActive={editor.isActive("bulletList")} label={<AiOutlineUnorderedList size={"24px"} />} />
|
|
68
|
+
<EditorButton action="toggleOrderedList" isActive={editor.isActive("orderedList")} label={<GoListOrdered size={"24px"} />} />
|
|
69
|
+
<EditorButton action="toggleCodeBlock" isActive={editor.isActive("codeBlock")} label={<PiCodeBlock size={"24px"} />} />
|
|
70
|
+
<EditorButton action="toggleBlockquote" isActive={editor.isActive("blockquote")} label={<TbBlockquote size={"24px"} />} />
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const extensions = [
|
|
76
|
+
StarterKit.configure({
|
|
77
|
+
bulletList: {
|
|
78
|
+
keepMarks: true,
|
|
79
|
+
keepAttributes: false,
|
|
80
|
+
},
|
|
81
|
+
orderedList: {
|
|
82
|
+
keepMarks: true,
|
|
83
|
+
keepAttributes: false,
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
Markdown,
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
interface Props {
|
|
90
|
+
content?: string;
|
|
91
|
+
editable: boolean;
|
|
92
|
+
className?: string;
|
|
93
|
+
onUpdate?: (content: string) => void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const MarkdownEditor = (props: Props) => {
|
|
97
|
+
return (
|
|
98
|
+
<div className={"text-md border border-gray-800 overflow-hidden " + props.className} style={{ borderWidth: props.editable ? 1 : 0 }}>
|
|
99
|
+
<EditorProvider
|
|
100
|
+
key={(props.editable ? "editable" : "readonly") + props.content}
|
|
101
|
+
slotBefore={props.editable ? <MenuBar /> : null}
|
|
102
|
+
extensions={extensions}
|
|
103
|
+
content={props.content}
|
|
104
|
+
editable={props.editable}
|
|
105
|
+
onUpdate={(e) => {
|
|
106
|
+
props.onUpdate && props.onUpdate(e.editor.storage.markdown.getMarkdown());
|
|
107
|
+
}}
|
|
108
|
+
></EditorProvider>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|