@rimori/client 1.1.9 → 1.2.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.
Files changed (151) hide show
  1. package/README.md +128 -45
  2. package/dist/cli/scripts/init/dev-registration.d.ts +35 -0
  3. package/dist/cli/scripts/init/dev-registration.js +175 -0
  4. package/dist/cli/scripts/init/env-setup.d.ts +9 -0
  5. package/dist/cli/scripts/init/env-setup.js +43 -0
  6. package/dist/cli/scripts/init/file-operations.d.ts +4 -0
  7. package/dist/cli/scripts/init/file-operations.js +51 -0
  8. package/dist/cli/scripts/init/html-cleaner.d.ts +4 -0
  9. package/dist/cli/scripts/init/html-cleaner.js +38 -0
  10. package/dist/cli/scripts/init/main.d.ts +2 -0
  11. package/dist/cli/scripts/init/main.js +159 -0
  12. package/dist/cli/scripts/init/package-setup.d.ts +32 -0
  13. package/dist/cli/scripts/init/package-setup.js +75 -0
  14. package/dist/cli/scripts/init/router-transformer.d.ts +6 -0
  15. package/dist/cli/scripts/init/router-transformer.js +254 -0
  16. package/dist/cli/scripts/init/tailwind-config.d.ts +4 -0
  17. package/dist/cli/scripts/init/tailwind-config.js +56 -0
  18. package/dist/cli/scripts/init/vite-config.d.ts +20 -0
  19. package/dist/cli/scripts/init/vite-config.js +54 -0
  20. package/dist/cli/scripts/release/release-config-upload.d.ts +7 -0
  21. package/dist/cli/scripts/release/release-config-upload.js +116 -0
  22. package/dist/cli/scripts/release/release-db-update.d.ts +6 -0
  23. package/dist/cli/scripts/release/release-db-update.js +100 -0
  24. package/dist/cli/scripts/release/release-file-upload.d.ts +6 -0
  25. package/dist/cli/scripts/release/release-file-upload.js +136 -0
  26. package/dist/cli/scripts/release/release.d.ts +23 -0
  27. package/dist/cli/scripts/release/release.js +70 -0
  28. package/dist/cli/types/DatabaseTypes.d.ts +103 -0
  29. package/dist/cli/types/DatabaseTypes.js +2 -0
  30. package/dist/components/ai/Assistant.js +4 -4
  31. package/dist/components/ai/Avatar.d.ts +3 -2
  32. package/dist/components/ai/Avatar.js +10 -5
  33. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -1
  34. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +1 -0
  35. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +12 -6
  36. package/dist/components/ai/utils.js +0 -1
  37. package/dist/components/audio/Playbutton.js +3 -3
  38. package/dist/{core → components}/components/ContextMenu.js +2 -2
  39. package/dist/components.d.ts +5 -5
  40. package/dist/components.js +5 -5
  41. package/dist/core/controller/AIController.d.ts +15 -0
  42. package/dist/core/controller/AIController.js +120 -0
  43. package/dist/{controller → core/controller}/ObjectController.d.ts +8 -0
  44. package/dist/{controller → core/controller}/SettingsController.d.ts +12 -4
  45. package/dist/{controller → core/controller}/SettingsController.js +0 -25
  46. package/dist/{controller → core/controller}/SharedContentController.d.ts +10 -19
  47. package/dist/{controller → core/controller}/SharedContentController.js +11 -11
  48. package/dist/core/core.d.ts +13 -0
  49. package/dist/core/core.js +8 -0
  50. package/dist/{plugin/fromRimori → fromRimori}/EventBus.d.ts +3 -3
  51. package/dist/{plugin/fromRimori → fromRimori}/EventBus.js +25 -8
  52. package/dist/fromRimori/PluginTypes.d.ts +171 -0
  53. package/dist/hooks/UseChatHook.d.ts +2 -1
  54. package/dist/hooks/UseChatHook.js +3 -3
  55. package/dist/index.d.ts +5 -3
  56. package/dist/index.js +4 -3
  57. package/dist/plugin/AccomplishmentHandler.d.ts +1 -1
  58. package/dist/plugin/AccomplishmentHandler.js +1 -1
  59. package/dist/plugin/PluginController.d.ts +16 -3
  60. package/dist/plugin/PluginController.js +24 -18
  61. package/dist/plugin/RimoriClient.d.ts +22 -17
  62. package/dist/plugin/RimoriClient.js +35 -25
  63. package/dist/plugin/StandaloneClient.js +11 -8
  64. package/dist/plugin/ThemeSetter.d.ts +1 -0
  65. package/dist/plugin/ThemeSetter.js +9 -6
  66. package/dist/providers/PluginProvider.d.ts +3 -0
  67. package/dist/providers/PluginProvider.js +4 -4
  68. package/dist/utils/Language.d.ts +2 -1
  69. package/dist/utils/Language.js +4 -2
  70. package/dist/utils/difficultyConverter.js +1 -1
  71. package/dist/utils/endpoint.d.ts +2 -0
  72. package/dist/utils/endpoint.js +2 -0
  73. package/dist/worker/WorkerSetup.js +3 -1
  74. package/example/docs/devdocs.md +231 -0
  75. package/example/docs/overview.md +29 -0
  76. package/example/docs/userdocs.md +123 -0
  77. package/example/rimori.config.ts +89 -0
  78. package/example/worker/vite.config.ts +23 -0
  79. package/example/worker/worker.ts +11 -0
  80. package/package.json +15 -9
  81. package/src/cli/scripts/init/dev-registration.ts +193 -0
  82. package/src/cli/scripts/init/env-setup.ts +44 -0
  83. package/src/cli/scripts/init/file-operations.ts +58 -0
  84. package/src/cli/scripts/init/html-cleaner.ts +48 -0
  85. package/src/cli/scripts/init/main.ts +171 -0
  86. package/src/cli/scripts/init/package-setup.ts +117 -0
  87. package/src/cli/scripts/init/router-transformer.ts +329 -0
  88. package/src/cli/scripts/init/tailwind-config.ts +75 -0
  89. package/src/cli/scripts/init/vite-config.ts +73 -0
  90. package/src/cli/scripts/release/release-config-upload.ts +114 -0
  91. package/src/cli/scripts/release/release-db-update.ts +97 -0
  92. package/src/cli/scripts/release/release-file-upload.ts +138 -0
  93. package/src/cli/scripts/release/release.ts +69 -0
  94. package/src/cli/types/DatabaseTypes.ts +117 -0
  95. package/src/components/ai/Assistant.tsx +4 -4
  96. package/src/components/ai/Avatar.tsx +24 -7
  97. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +1 -1
  98. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +16 -8
  99. package/src/components/ai/utils.ts +0 -2
  100. package/src/components/audio/Playbutton.tsx +3 -3
  101. package/src/{core → components}/components/ContextMenu.tsx +3 -3
  102. package/src/components.ts +6 -6
  103. package/src/core/controller/AIController.ts +122 -0
  104. package/src/core/controller/ObjectController.ts +115 -0
  105. package/src/{controller → core/controller}/SettingsController.ts +13 -29
  106. package/src/{controller → core/controller}/SharedContentController.ts +18 -28
  107. package/src/core/core.ts +15 -0
  108. package/src/{plugin/fromRimori → fromRimori}/EventBus.ts +28 -10
  109. package/src/fromRimori/PluginTypes.ts +203 -0
  110. package/src/hooks/UseChatHook.ts +5 -4
  111. package/src/index.ts +5 -3
  112. package/src/plugin/AccomplishmentHandler.ts +1 -1
  113. package/src/plugin/PluginController.ts +35 -23
  114. package/src/plugin/RimoriClient.ts +48 -41
  115. package/src/plugin/StandaloneClient.ts +11 -8
  116. package/src/plugin/ThemeSetter.ts +12 -8
  117. package/src/providers/PluginProvider.tsx +7 -4
  118. package/src/utils/Language.ts +4 -2
  119. package/src/utils/difficultyConverter.ts +3 -3
  120. package/src/utils/endpoint.ts +2 -0
  121. package/src/worker/WorkerSetup.ts +4 -2
  122. package/dist/components/PluginController.d.ts +0 -21
  123. package/dist/components/PluginController.js +0 -116
  124. package/dist/controller/AIController.d.ts +0 -23
  125. package/dist/controller/AIController.js +0 -93
  126. package/dist/controller/SidePluginController.d.ts +0 -3
  127. package/dist/controller/SidePluginController.js +0 -31
  128. package/dist/core.d.ts +0 -7
  129. package/dist/core.js +0 -7
  130. package/dist/plugin/ContextMenu.d.ts +0 -17
  131. package/dist/plugin/ContextMenu.js +0 -45
  132. package/dist/plugin/fromRimori/PluginTypes.d.ts +0 -48
  133. package/dist/plugin/fromRimori/SupabaseHandler.d.ts +0 -13
  134. package/dist/plugin/fromRimori/SupabaseHandler.js +0 -55
  135. package/dist/providers/PluginController.d.ts +0 -21
  136. package/dist/providers/PluginController.js +0 -116
  137. package/dist/types/Actions.d.ts +0 -4
  138. package/dist/types/Actions.js +0 -1
  139. package/src/controller/AIController.ts +0 -112
  140. package/src/controller/ObjectController.ts +0 -107
  141. package/src/controller/SidePluginController.ts +0 -25
  142. package/src/core.ts +0 -8
  143. package/src/plugin/fromRimori/PluginTypes.ts +0 -64
  144. package/src/types/Actions.ts +0 -6
  145. /package/dist/{core → components}/components/ContextMenu.d.ts +0 -0
  146. /package/dist/{controller → core/controller}/ObjectController.js +0 -0
  147. /package/dist/{controller → core/controller}/VoiceController.d.ts +0 -0
  148. /package/dist/{controller → core/controller}/VoiceController.js +0 -0
  149. /package/dist/{plugin/fromRimori → fromRimori}/PluginTypes.js +0 -0
  150. /package/src/{controller → core/controller}/VoiceController.ts +0 -0
  151. /package/src/{plugin/fromRimori → fromRimori}/readme.md +0 -0
@@ -7,8 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { EventBus } from "./fromRimori/EventBus";
11
10
  import { createClient } from "@supabase/supabase-js";
11
+ import { EventBus } from "../fromRimori/EventBus";
12
+ import { DEFAULT_ANON_KEY, DEFAULT_ENDPOINT } from "../utils/endpoint";
12
13
  export class StandaloneClient {
13
14
  constructor(config) {
14
15
  this.supabase = createClient(config.url, config.key);
@@ -17,14 +18,13 @@ export class StandaloneClient {
17
18
  static getInstance() {
18
19
  return __awaiter(this, void 0, void 0, function* () {
19
20
  if (!StandaloneClient.instance) {
20
- const config = yield fetch("http://localhost:3000/config.json").then(res => res.json()).catch(err => {
21
+ const config = yield fetch("https://app.rimori.se/config.json").then(res => res.json()).catch(err => {
21
22
  console.warn("Error fetching config.json, using default values", err);
22
- return {
23
- SUPABASE_URL: "https://pheptqdoqsdnadgoihvr.supabase.co",
24
- SUPABASE_ANON_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBoZXB0cWRvcXNkbmFkZ29paHZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzE2OTY2ODcsImV4cCI6MjA0NzI3MjY4N30.4GPFAXTF8685FaXISdAPNCIM-H3RGLo8GbyhQpu1mP0",
25
- };
26
23
  });
27
- StandaloneClient.instance = new StandaloneClient({ url: config.SUPABASE_URL, key: config.SUPABASE_ANON_KEY });
24
+ StandaloneClient.instance = new StandaloneClient({
25
+ url: (config === null || config === void 0 ? void 0 : config.SUPABASE_URL) || DEFAULT_ENDPOINT,
26
+ key: (config === null || config === void 0 ? void 0 : config.SUPABASE_ANON_KEY) || DEFAULT_ANON_KEY,
27
+ });
28
28
  }
29
29
  return StandaloneClient.instance;
30
30
  });
@@ -61,7 +61,10 @@ export class StandaloneClient {
61
61
  var _a;
62
62
  const session = yield supabase.auth.getSession();
63
63
  console.log("session", session);
64
- const { data, error } = yield supabase.functions.invoke("plugin-token", { headers: { authorization: `Bearer ${(_a = session.data.session) === null || _a === void 0 ? void 0 : _a.access_token}` } });
64
+ const { data, error } = yield supabase.functions.invoke("plugin-token", {
65
+ body: { pluginId },
66
+ headers: { authorization: `Bearer ${(_a = session.data.session) === null || _a === void 0 ? void 0 : _a.access_token}` },
67
+ });
65
68
  if (error) {
66
69
  throw new Error("Failed to get plugin token. " + error.message);
67
70
  }
@@ -1 +1,2 @@
1
1
  export declare function setTheme(): void;
2
+ export declare function isDarkTheme(): boolean;
@@ -1,13 +1,16 @@
1
1
  export function setTheme() {
2
- const urlParams = new URLSearchParams(window.location.search);
3
- let theme = urlParams.get('theme');
4
- if (!theme || theme === 'system') {
5
- theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
6
- }
7
2
  document.documentElement.classList.add("dark:text-gray-200");
8
- if (theme === 'dark') {
3
+ if (isDarkTheme()) {
9
4
  document.documentElement.setAttribute("data-theme", "dark");
10
5
  document.documentElement.classList.add('dark', "dark:bg-gray-950");
11
6
  document.documentElement.style.background = "hsl(var(--background))";
12
7
  }
13
8
  }
9
+ export function isDarkTheme() {
10
+ const urlParams = new URLSearchParams(window.location.search);
11
+ let theme = urlParams.get('theme');
12
+ if (!theme || theme === 'system') {
13
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
14
+ }
15
+ return theme === 'dark';
16
+ }
@@ -3,6 +3,9 @@ import { RimoriClient } from '../plugin/RimoriClient';
3
3
  interface PluginProviderProps {
4
4
  children: ReactNode;
5
5
  pluginId: string;
6
+ settings?: {
7
+ disableContextMenu?: boolean;
8
+ };
6
9
  }
7
10
  export declare const PluginProvider: React.FC<PluginProviderProps>;
8
11
  export declare const usePlugin: () => RimoriClient;
@@ -10,11 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import { createContext, useContext, useEffect, useState } from 'react';
12
12
  import { PluginController } from '../plugin/PluginController';
13
- import { EventBusHandler } from '../plugin/fromRimori/EventBus';
14
- import ContextMenu from '../core/components/ContextMenu';
13
+ import { EventBusHandler } from '../fromRimori/EventBus';
14
+ import ContextMenu from '../components/components/ContextMenu';
15
15
  import { StandaloneClient } from '../plugin/StandaloneClient';
16
16
  const PluginContext = createContext(null);
17
- export const PluginProvider = ({ children, pluginId }) => {
17
+ export const PluginProvider = ({ children, pluginId, settings }) => {
18
18
  const [plugin, setPlugin] = useState(null);
19
19
  const [standaloneClient, setStandaloneClient] = useState(false);
20
20
  useEffect(() => {
@@ -66,7 +66,7 @@ export const PluginProvider = ({ children, pluginId }) => {
66
66
  if (!plugin) {
67
67
  return "";
68
68
  }
69
- return (_jsxs(PluginContext.Provider, { value: plugin, children: [_jsx(ContextMenu, { client: plugin }), children] }));
69
+ return (_jsxs(PluginContext.Provider, { value: plugin, children: [!(settings === null || settings === void 0 ? void 0 : settings.disableContextMenu) && _jsx(ContextMenu, { client: plugin }), children] }));
70
70
  };
71
71
  export const usePlugin = () => {
72
72
  const context = useContext(PluginContext);
@@ -61,6 +61,7 @@ export type Language = keyof typeof languageKeys;
61
61
  /**
62
62
  * Get the language name from the language code
63
63
  * @param languageCode The code of the language
64
+ * @param capitalize Whether to capitalize the first letter of the language name
64
65
  * @returns The language name
65
66
  */
66
- export declare function getLanguageName(languageCode: Language): string;
67
+ export declare function getLanguageName(languageCode: Language, capitalize?: boolean): string;
@@ -60,8 +60,10 @@ export const languageKeys = {
60
60
  /**
61
61
  * Get the language name from the language code
62
62
  * @param languageCode The code of the language
63
+ * @param capitalize Whether to capitalize the first letter of the language name
63
64
  * @returns The language name
64
65
  */
65
- export function getLanguageName(languageCode) {
66
- return languageKeys[languageCode];
66
+ export function getLanguageName(languageCode, capitalize = false) {
67
+ const lang = languageKeys[languageCode];
68
+ return capitalize ? lang.charAt(0).toUpperCase() + lang.slice(1) : lang;
67
69
  }
@@ -6,5 +6,5 @@ export function getDifficultyLabel(difficulty) {
6
6
  return codes[difficulty];
7
7
  }
8
8
  export function getNeighborDifficultyLevel(difficulty, difficultyAdjustment) {
9
- return getDifficultyLabel(getDifficultyLevel(difficulty) + difficultyAdjustment);
9
+ return getDifficultyLabel(getDifficultyLevel(difficulty) + difficultyAdjustment - 1);
10
10
  }
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_ENDPOINT = "https://pheptqdoqsdnadgoihvr.supabase.co";
2
+ export declare const DEFAULT_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBoZXB0cWRvcXNkbmFkZ29paHZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzE2OTY2ODcsImV4cCI6MjA0NzI3MjY4N30.4GPFAXTF8685FaXISdAPNCIM-H3RGLo8GbyhQpu1mP0";
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_ENDPOINT = "https://pheptqdoqsdnadgoihvr.supabase.co";
2
+ export const DEFAULT_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBoZXB0cWRvcXNkbmFkZ29paHZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzE2OTY2ODcsImV4cCI6MjA0NzI3MjY4N30.4GPFAXTF8685FaXISdAPNCIM-H3RGLo8GbyhQpu1mP0";
@@ -7,8 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import { EventBus, EventBusHandler } from "../fromRimori/EventBus";
10
11
  import { PluginController } from "../plugin/PluginController";
11
- import { EventBus, EventBusHandler } from "../plugin/fromRimori/EventBus";
12
12
  let controller = null;
13
13
  const listeners = [];
14
14
  let debugEnabled = false;
@@ -35,6 +35,7 @@ export function setupWorker(init) {
35
35
  APP_CONFIG: {
36
36
  SUPABASE_URL: 'NOT_SET',
37
37
  SUPABASE_ANON_KEY: 'NOT_SET',
38
+ BACKEND_URL: 'NOT_SET',
38
39
  },
39
40
  };
40
41
  // Assign the mock to globalThis.
@@ -49,6 +50,7 @@ export function setupWorker(init) {
49
50
  if (!controller) {
50
51
  mockWindow.APP_CONFIG.SUPABASE_URL = event.data.supabaseUrl;
51
52
  mockWindow.APP_CONFIG.SUPABASE_ANON_KEY = event.data.supabaseAnonKey;
53
+ mockWindow.APP_CONFIG.BACKEND_URL = event.data.backendUrl;
52
54
  controller = yield PluginController.getInstance(event.data.pluginId);
53
55
  logIfDebug('Worker initialized.');
54
56
  yield init(controller);
@@ -0,0 +1,231 @@
1
+ # Simple Flashcards - Developer Guide
2
+
3
+ ## Overview
4
+
5
+ The Simple Flashcards plugin provides spaced repetition learning through an event-based API. Other plugins can create flashcards, request lookups, and trigger training sessions.
6
+
7
+ **Plugin ID:** `pl123456789`
8
+
9
+ ## Event-based Integration
10
+
11
+ All communication uses the Rimori event bus with the pattern: `<plugin_id>.<area>.<action>`
12
+
13
+ ### 1. Create Flashcards
14
+
15
+ #### Basic Creation
16
+ ```javascript
17
+ // Create a simple flashcard
18
+ plugin.event.emit("pl123456789.flashcard.create", {
19
+ front: "Hello",
20
+ back: "Hola",
21
+ deckId: "deck-123", // optional, uses default deck if omitted
22
+ frontTags: ["lang:en"],
23
+ backTags: ["lang:es"]
24
+ });
25
+ ```
26
+
27
+ #### Language-based Creation
28
+ ```javascript
29
+ // Create flashcard with automatic lookup
30
+ plugin.event.emit("pl123456789.flashcard.createLangCard", {
31
+ word: "perro",
32
+ language: "es", // optional, uses user's mother tongue if omitted
33
+ deckId: "spanish-nouns" // optional
34
+ });
35
+ ```
36
+
37
+ ### 2. Lookup Requests
38
+
39
+ #### Basic Translation
40
+ ```javascript
41
+ // Request word translation
42
+ const translation = await plugin.event.request("pl123456789.lookup.requestBasic", {
43
+ word: "laufen",
44
+ language: "de" // optional
45
+ });
46
+
47
+ // Returns:
48
+ // {
49
+ // input: "laufen",
50
+ // language: "de",
51
+ // type: "verb",
52
+ // swedish_translation: "att springa",
53
+ // translation: "to run"
54
+ // }
55
+ ```
56
+
57
+ #### Advanced Lookup
58
+ ```javascript
59
+ // Get detailed word information
60
+ const details = await plugin.event.request("pl123456789.lookup.request", {
61
+ word: "laufen",
62
+ language: "de"
63
+ });
64
+
65
+ // Returns extended object with grammar, examples, tenses, etc.
66
+ ```
67
+
68
+ ### 3. Deck Management
69
+
70
+ #### Get Available Decks
71
+ ```javascript
72
+ const decks = await plugin.event.request("pl123456789.deck.requestOpenToday");
73
+
74
+ // Returns:
75
+ // [
76
+ // {
77
+ // id: "deck-123",
78
+ // name: "Spanish Vocabulary",
79
+ // total_new: 5,
80
+ // total_learning: 12,
81
+ // total_review: 8
82
+ // }
83
+ // ]
84
+ ```
85
+
86
+ #### Create New Deck
87
+ ```javascript
88
+ const newDeck = await plugin.event.request("pl123456789.deck.create", {
89
+ name: "German Verbs"
90
+ });
91
+
92
+ // Returns: { id: "deck-456", name: "German Verbs", last_used: "2025-01-06T..." }
93
+ ```
94
+
95
+ ### 4. Trigger Training
96
+
97
+ #### Open Flashcard Training
98
+ ```javascript
99
+ // Open training in main panel
100
+ plugin.event.emit("global.mainPanel.triggerAction", {
101
+ pluginId: "pl123456789",
102
+ actionKey: "flashcards",
103
+ deck: "latest", // or "random", "oldest", "mix", deck ID
104
+ total_amount: 20 // or "default"
105
+ });
106
+ ```
107
+
108
+ ## Worker Function Example
109
+
110
+ ### Background Deck Sync
111
+ ```javascript
112
+ // worker/listeners/deck-sync.ts
113
+ import { WorkerEventListener } from '@rimori/client';
114
+
115
+ export const deckSyncListener: WorkerEventListener = {
116
+ eventName: 'pl123456789.deck.syncProgress',
117
+
118
+ async handler(data: { userId: string; deckId: string }) {
119
+ const { userId, deckId } = data;
120
+
121
+ // Calculate learning statistics
122
+ const stats = await calculateDeckProgress(deckId, userId);
123
+
124
+ // Update user's learning streak
125
+ await updateLearningStreak(userId, stats);
126
+
127
+ // Send progress update to main thread
128
+ self.postMessage({
129
+ type: 'progress-updated',
130
+ deckId,
131
+ stats: {
132
+ cardsReviewed: stats.reviewed,
133
+ accuracy: stats.accuracy,
134
+ streak: stats.streak
135
+ }
136
+ });
137
+ }
138
+ };
139
+
140
+ async function calculateDeckProgress(deckId: string, userId: string) {
141
+ // Database queries to calculate progress
142
+ const cards = await db.from('flashcards')
143
+ .select('*')
144
+ .eq('deck_id', deckId)
145
+ .eq('user_id', userId);
146
+
147
+ const reviewed = cards.filter(c => c.last_review_date >= getTodayStart());
148
+ const accuracy = reviewed.reduce((acc, c) => acc + c.ease_factor, 0) / reviewed.length;
149
+
150
+ return {
151
+ reviewed: reviewed.length,
152
+ accuracy: Math.round(accuracy * 100),
153
+ streak: await calculateStreak(userId)
154
+ };
155
+ }
156
+ ```
157
+
158
+ ## Database Schema Pattern
159
+
160
+ ```typescript
161
+ // Example table structure
162
+ interface Flashcard {
163
+ id: string;
164
+ user_id: string;
165
+ deck_id: string;
166
+ front: string;
167
+ back: string;
168
+ front_tags: string[];
169
+ back_tags: string[];
170
+ ease_factor: number;
171
+ interval: number;
172
+ due_date: string;
173
+ last_review_date?: string;
174
+ }
175
+
176
+ interface Deck {
177
+ id: string;
178
+ user_id: string;
179
+ name: string;
180
+ last_used: string;
181
+ created_at: string;
182
+ }
183
+ ```
184
+
185
+ ## Integration Examples
186
+
187
+ ### Dictionary Plugin → Flashcards
188
+ ```javascript
189
+ // When user looks up a word in dictionary
190
+ function onWordLookup(word: string, translation: string) {
191
+ // Automatically create flashcard
192
+ plugin.event.emit("pl123456789.flashcard.create", {
193
+ front: word,
194
+ back: translation,
195
+ frontTags: ["lang:sv"],
196
+ backTags: ["lang:en", "dictionary-lookup"]
197
+ });
198
+ }
199
+ ```
200
+
201
+ ### Study Planner → Flashcards
202
+ ```javascript
203
+ // Trigger daily flashcard session
204
+ function scheduleDailyReview() {
205
+ plugin.event.emit("global.mainPanel.triggerAction", {
206
+ pluginId: "pl123456789",
207
+ actionKey: "flashcards",
208
+ deck: "mix",
209
+ total_amount: 30
210
+ });
211
+ }
212
+ ```
213
+
214
+ ## Error Handling
215
+
216
+ ```javascript
217
+ try {
218
+ await plugin.event.request("pl123456789.flashcard.create", cardData);
219
+ } catch (error) {
220
+ console.error("Failed to create flashcard:", error);
221
+ // Handle error appropriately
222
+ }
223
+ ```
224
+
225
+ ## Best Practices
226
+
227
+ - Always provide meaningful error messages
228
+ - Use consistent tagging for language features
229
+ - Batch operations when creating multiple cards
230
+ - Respect user's daily review limits
231
+ - Include proper TypeScript types for payloads
@@ -0,0 +1,29 @@
1
+ # Simple Flashcards - Spaced Repetition Learning
2
+
3
+ **Transform your vocabulary learning with intelligent flashcards that adapt to your memory**
4
+
5
+ ## ✨ What it does
6
+
7
+ Simple Flashcards uses proven spaced repetition algorithms to help you memorize vocabulary, facts, and concepts efficiently. The plugin schedules your reviews at optimal intervals, showing cards just before you're likely to forget them.
8
+
9
+ ## 🎯 Perfect for
10
+
11
+ - **Language learners** building vocabulary with audio pronunciation
12
+ - **Students** memorizing facts, formulas, and definitions
13
+ - **Professionals** learning technical terms and concepts
14
+ - **Anyone** who wants to remember information long-term
15
+
16
+ ## 🔥 Key Features
17
+
18
+ - **Smart scheduling** - Cards appear when you need to review them most
19
+ - **Audio support** - Hear correct pronunciation for language learning
20
+ - **Multiple decks** - Organize cards by topic, subject, or difficulty
21
+ - **Simple rating** - Just rate how well you remembered (Again, Hard, Good, Easy)
22
+ - **Progress tracking** - See your learning streak and daily progress
23
+ - **Cross-plugin integration** - Other plugins can add cards automatically
24
+
25
+ ## 🚀 Get Started in 30 seconds
26
+
27
+ 1. Create your first deck
28
+ 2. Add some flashcards (front/back)
29
+ 3. Start reviewing - the algorithm handles the rest!
@@ -0,0 +1,123 @@
1
+ # Simple Flashcards - User Guide
2
+
3
+ ## Getting Started
4
+
5
+ Simple Flashcards uses spaced repetition to help you learn and retain information efficiently. The plugin automatically schedules your reviews at optimal intervals, showing cards just before you're likely to forget them.
6
+
7
+ ### Create Your First Deck
8
+
9
+ Before you can start learning, you'll need to create a deck to organize your flashcards. To create your first deck:
10
+
11
+ 1. Navigate to the main flashcards page where you'll see your deck overview
12
+ 2. Click the "Add deck" button located at the bottom of the page
13
+ 3. Enter a descriptive name for your deck (e.g., "Spanish Vocabulary" or "Biology Terms")
14
+ 4. Click save to create your new deck
15
+
16
+ Your new deck will appear in the deck overview, ready for you to add flashcards.
17
+
18
+ ### Adding Flashcards
19
+
20
+ Once you have a deck, you can start adding flashcards to build your learning material. To add new flashcards:
21
+
22
+ 1. Click on your deck name to enter the deck and access its training interface
23
+ 2. Click the "+" button to open the flashcard creation form
24
+ 3. Enter your question, word, or prompt on the **front** of the card
25
+ 4. Enter the corresponding answer, translation, or explanation on the **back**
26
+ 5. Optionally add tags like `lang:es` to enable features such as Spanish audio pronunciation
27
+ 6. Click the save icon to create your flashcard
28
+
29
+ The new card will be added to your deck and will appear in your learning session when you start reviewing.
30
+
31
+ ### Learning with Flashcards
32
+
33
+ The learning process follows a simple but effective pattern designed to maximize retention:
34
+
35
+ 1. **Start a session** by clicking on a deck name from the overview page
36
+ 2. **Study the front** of each card carefully and try to recall the answer from memory
37
+ 3. **Reveal the answer** by pressing the spacebar or clicking the "Show answer" button
38
+ 4. **Rate your recall** honestly using one of four options:
39
+ - **Again (1)** - You didn't remember or got it wrong; the card will reappear soon
40
+ - **Hard (2)** - You remembered but with significant difficulty; shorter interval before next review
41
+ - **Good (3)** - You remembered correctly with some effort; optimal spaced interval
42
+ - **Easy (4)** - You remembered effortlessly; longer interval before next review
43
+
44
+ Your ratings help the algorithm determine the best time to show each card again, optimizing your learning efficiency.
45
+
46
+ ## Key Features
47
+
48
+ ### Audio Pronunciation Support
49
+
50
+ For language learning, audio pronunciation is essential. The plugin supports text-to-speech functionality:
51
+
52
+ - **Enable audio** by adding language tags such as `lang` for generic audio or `lang:es` for Spanish-specific pronunciation
53
+ - **Play audio** by clicking the speaker icon that appears on cards with language tags
54
+ - **Keyboard shortcuts** for audio: press **5** to play the front audio or **h** to play the back audio
55
+
56
+ The audio feature helps you learn correct pronunciation while building vocabulary, making it especially valuable for language learners.
57
+
58
+ ### Efficient Keyboard Navigation
59
+
60
+ Speed up your review sessions with keyboard shortcuts that eliminate the need for mouse clicking:
61
+
62
+ - **Spacebar** - Reveal the answer when studying the front of a card
63
+ - **Number keys 1-4** - Rate your recall (Again, Hard, Good, Easy respectively)
64
+ - **5 key** - Play audio for the front of the card (if language tags are present)
65
+ - **h key** - Play audio for the back of the card (if language tags are present)
66
+ - **v key** - Edit the current flashcard during review
67
+
68
+ These shortcuts allow you to maintain focus and rhythm during your learning sessions.
69
+
70
+ ### Deck Organization and Progress Tracking
71
+
72
+ The deck overview provides clear visual indicators of your progress:
73
+
74
+ - **Orange numbers** next to deck names show cards you're actively learning (new or recently difficult cards)
75
+ - **Green numbers** indicate cards that are due for review today based on the spaced repetition schedule
76
+ - **Deck names** can be clicked to immediately start a study session with that deck
77
+
78
+ This system helps you quickly identify which decks need attention and track your daily progress.
79
+
80
+ ### Tagging System for Organization
81
+
82
+ Tags provide a flexible way to categorize and enhance your flashcards:
83
+
84
+ - **Organizational tags** like `grammar`, `verbs`, or `beginner` help you categorize content by topic or difficulty
85
+ - **Language tags** such as `lang:de` for German enable specific features like pronunciation
86
+ - **Multiple tags** can be applied to each card for comprehensive organization
87
+
88
+ Tags make it easier to manage large collections of flashcards and enable special features like audio playback.
89
+
90
+ ## Settings and Customization
91
+
92
+ The plugin offers several settings to personalize your learning experience:
93
+
94
+ - **Auto-play new cards** - When enabled, automatically plays audio pronunciation for new cards that have appropriate language tags, helping you learn pronunciation from the first encounter
95
+ - **Auto-add lookups** - Automatically creates flashcards from words you look up in other parts of the Rimori workspace, streamlining the process of building your vocabulary deck
96
+
97
+ These settings help integrate the flashcard system with your broader learning workflow.
98
+
99
+ ## Best Practices and Tips
100
+
101
+ To get the most out of your flashcard learning:
102
+
103
+ - **Keep cards simple** - Focus on one concept, word, or fact per card for optimal retention
104
+ - **Review consistently** - Even a brief 10-minute daily session is more effective than longer, infrequent sessions
105
+ - **Use audio features** - Take advantage of pronunciation features for language learning
106
+ - **Edit problematic cards** - If you consistently struggle with a card, rewrite it to be clearer or break it into smaller pieces
107
+ - **Be honest with ratings** - Accurate self-assessment helps the algorithm schedule reviews optimally
108
+
109
+ ## Troubleshooting Common Issues
110
+
111
+ **Audio not playing when expected?**
112
+ - Verify that your device volume is turned up and not muted
113
+ - Check that the flashcard has proper language tags (e.g., `lang:en` for English)
114
+ - Try refreshing the page if audio features seem unresponsive
115
+
116
+ **Flashcards not saving properly?**
117
+ - Ensure you have a stable internet connection for data synchronization
118
+ - Verify you clicked the save icon after entering card content
119
+ - Try creating the card again if the first attempt failed
120
+
121
+ **Progress not updating correctly?**
122
+ - Refresh the page to ensure you're seeing the latest data
123
+ - Check that you're rating cards rather than just viewing them
@@ -0,0 +1,89 @@
1
+ import { RimoriPluginConfig } from "@rimori/client";
2
+
3
+ /**
4
+ * This is an example of a Rimori plugin configuration file. It is based on the Rimori Flashcards plugin.
5
+ * It is used to configure the plugin and its pages, sidebar, settings, context menu actions, documentation, and worker.
6
+ */
7
+
8
+ const config: RimoriPluginConfig = {
9
+ id: "pl1234567890", // This is the plugin id. You can find it in the plugin's package.json file.
10
+ info: {
11
+ title: "Flashcards",
12
+ description: "The Rimori Flashcards Plugin is a powerful tool for learning and memorization using spaced repetition. It helps you efficiently review information, whether you're learning a language, preparing for exams, or mastering new skills. The plugin uses advanced algorithms to schedule your reviews at optimal intervals, maximizing retention while minimizing study time.",
13
+ logo: "logo.png",
14
+ website: "https://rimori.se",
15
+ },
16
+ pages: {
17
+ main: [
18
+ {
19
+ id: "1",
20
+ url: "#/",
21
+ show: true,
22
+ name: "Flashcards",
23
+ root: "vocabulary",
24
+ description: "Quickly memorizing info by using flashcards."
25
+ },
26
+ { // This is a page that is not shown in the navbar. It is used to trigger the flashcards action.
27
+ id: "2",
28
+ url: "#/deck/custom",
29
+ show: false,
30
+ root: "vocabulary",
31
+ name: "Latest flashcard deck training",
32
+ description: "Training the latest flashcards.",
33
+ action: {
34
+ key: "flashcards",
35
+ parameters: {
36
+ total_amount: {
37
+ type: 'number',
38
+ description: 'Number of flashcards to practice. Default is 70 (10 new + 20 reviewed + 40 forgotten).'
39
+ },
40
+ deck: {
41
+ type: 'string',
42
+ enum: ['latest', 'random', 'oldest', 'mix', 'best_known'],
43
+ description: 'Type of deck to practice from'
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ],
49
+ sidebar: [
50
+ {
51
+ id: "translate",
52
+ url: "#/sidebar/translate",
53
+ name: "Translate",
54
+ icon: "translate.png",
55
+ description: "Translate words."
56
+ },
57
+ {
58
+ id: "flashcard_quick_add",
59
+ url: "#/sidebar/add",
60
+ name: "Quick add",
61
+ icon: "logo.png",
62
+ description: "Quickly add a word to your flashcards."
63
+ }
64
+ ],
65
+ settings: "/settings",
66
+ },
67
+ context_menu_actions: [
68
+ {
69
+ text: "Translate",
70
+ plugin_id: "pl1234567890",
71
+ action_key: "translate"
72
+ },
73
+ {
74
+ text: "Quick add",
75
+ plugin_id: "pl1234567890",
76
+ action_key: "flashcard_quick_add"
77
+ }
78
+ ],
79
+ documentation: {
80
+ overview_path: "docs/overview.md",
81
+ user_path: "docs/userdocs.md",
82
+ developer_path: "docs/devdocs.md"
83
+ },
84
+ worker: {
85
+ url: "web-worker.js",
86
+ }
87
+ };
88
+
89
+ export default config;
@@ -0,0 +1,23 @@
1
+ import { defineConfig } from 'vite'
2
+ import path from 'path'
3
+
4
+ export default defineConfig({
5
+ root: __dirname,
6
+ build: {
7
+ minify: process.env.VITE_MINIFY === 'true',
8
+ lib: {
9
+ entry: 'worker.ts',
10
+ formats: ['iife'],
11
+ name: 'PluginFooWorker',
12
+ fileName: () => 'web-worker.js', // used in rollupOptions.entryFileNames
13
+ },
14
+ outDir: path.resolve(__dirname, '../public'),
15
+ emptyOutDir: false,
16
+ rollupOptions: {
17
+ output: {
18
+ inlineDynamicImports: true,
19
+ entryFileNames: 'web-worker.js',
20
+ },
21
+ },
22
+ },
23
+ })