@rimori/react-client 0.2.7 → 0.3.0-next.1

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.
@@ -0,0 +1,129 @@
1
+ name: Pre-Release Rimori React Client
2
+
3
+ on:
4
+ push:
5
+ branches: [dev]
6
+ paths:
7
+ - '**'
8
+ - '!.github/workflows/**'
9
+
10
+ jobs:
11
+ pre-release:
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: write
15
+ id-token: write
16
+
17
+ steps:
18
+ - name: Checkout repository
19
+ uses: actions/checkout@v4
20
+ with:
21
+ token: ${{ secrets.GITHUB_TOKEN }}
22
+ fetch-depth: 0
23
+
24
+ - name: Setup Node.js
25
+ uses: actions/setup-node@v4
26
+ with:
27
+ node-version: '20'
28
+ registry-url: 'https://registry.npmjs.org'
29
+ cache: 'yarn'
30
+ cache-dependency-path: yarn.lock
31
+
32
+ - name: Update npm
33
+ run: npm install -g npm@latest
34
+
35
+ - name: Get latest @rimori/client@next version
36
+ id: client-version
37
+ run: |
38
+ VERSION=$(npm view @rimori/client@next version 2>/dev/null || echo "")
39
+ if [ -z "$VERSION" ]; then
40
+ echo "⚠️ Warning: No @rimori/client@next version found. Using current dependency version."
41
+ VERSION=$(node -p "require('./package.json').peerDependencies['@rimori/client'] || require('./package.json').devDependencies['@rimori/client']")
42
+ # Remove ^ prefix if present
43
+ VERSION="${VERSION#^}"
44
+ fi
45
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
46
+ echo "Using @rimori/client version: $VERSION"
47
+
48
+ - name: Update @rimori/client dependency
49
+ run: |
50
+ # Update both peerDependencies and devDependencies
51
+ yarn add "@rimori/client@${{ steps.client-version.outputs.version }}" --dev --exact
52
+ # Also update peerDependencies using node
53
+ node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); if (pkg.peerDependencies && pkg.peerDependencies['@rimori/client']) { pkg.peerDependencies['@rimori/client'] = '${{ steps.client-version.outputs.version }}'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); }"
54
+ echo "Updated @rimori/client to ${{ steps.client-version.outputs.version }}"
55
+
56
+ - name: Install dependencies
57
+ run: yarn install --frozen-lockfile
58
+
59
+ - name: Build react-client (TypeScript verification)
60
+ run: yarn build
61
+
62
+ - name: Calculate next pre-release version
63
+ id: version
64
+ run: |
65
+ # Read current version from package.json (may be base or pre-release)
66
+ CURRENT_VERSION=$(node -p "require('./package.json').version")
67
+
68
+ # Extract base version (strip any pre-release suffix)
69
+ # Examples: "0.3.0" -> "0.3.0", "0.3.0-next.5" -> "0.3.0"
70
+ if [[ "$CURRENT_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+) ]]; then
71
+ BASE_VERSION="${BASH_REMATCH[1]}"
72
+ else
73
+ BASE_VERSION="$CURRENT_VERSION"
74
+ fi
75
+
76
+ # Try to get latest next version from npm
77
+ PACKAGE_NAME="@rimori/react-client"
78
+ LATEST_NEXT=$(npm view ${PACKAGE_NAME}@next version 2>/dev/null || echo "none")
79
+
80
+ if [ "$LATEST_NEXT" != "none" ]; then
81
+ # Extract base version and pre-release number from latest next version
82
+ # Example: "0.3.0-next.5" -> extract "0.3.0" and "5"
83
+ if [[ "$LATEST_NEXT" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-next\.([0-9]+)$ ]]; then
84
+ LATEST_BASE="${BASH_REMATCH[1]}"
85
+ PRERELEASE_NUM="${BASH_REMATCH[2]}"
86
+
87
+ # If base version changed, reset to 1, otherwise increment
88
+ if [ "$LATEST_BASE" != "$BASE_VERSION" ]; then
89
+ NEW_NUM=1
90
+ else
91
+ NEW_NUM=$((PRERELEASE_NUM + 1))
92
+ fi
93
+ else
94
+ # Fallback: if format doesn't match, start at 1
95
+ NEW_NUM=1
96
+ fi
97
+ else
98
+ # First pre-release
99
+ NEW_NUM=1
100
+ fi
101
+
102
+ NEW_VERSION="${BASE_VERSION}-next.${NEW_NUM}"
103
+ echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
104
+ echo "Base version: $BASE_VERSION"
105
+ echo "Calculated next version: $NEW_VERSION"
106
+
107
+ - name: Update package.json version
108
+ run: |
109
+ # Use node to update version directly (yarn version creates git tags)
110
+ node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); pkg.version = '${{ steps.version.outputs.new_version }}'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');"
111
+
112
+ - name: Publish to npm
113
+ run: npm publish --tag next --access public
114
+ # Uses OIDC token automatically (no NODE_AUTH_TOKEN needed)
115
+ # Requires npm 11.5.1+ and id-token: write permission (already set)
116
+
117
+ - name: Commit version bump
118
+ run: |
119
+ git config --local user.email "action@github.com"
120
+ git config --local user.name "GitHub Action"
121
+ git add package.json yarn.lock
122
+ git commit -m "chore: bump @rimori/react-client to ${{ steps.version.outputs.new_version }} [skip ci]"
123
+ git push
124
+
125
+ - name: Output published version
126
+ run: |
127
+ echo "✅ Published @rimori/react-client@${{ steps.version.outputs.new_version }} to npm with @next tag"
128
+ echo "Using @rimori/client@${{ steps.client-version.outputs.version }}"
129
+
@@ -2,12 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useRef } from 'react';
3
3
  const ContextMenu = ({ client }) => {
4
4
  const [isOpen, setIsOpen] = useState(false);
5
- const [actions, setActions] = useState([]);
6
5
  const [position, setPosition] = useState({ x: 0, y: 0 });
7
- const [openOnTextSelect, setOpenOnTextSelect] = useState(false);
8
6
  const [menuWidth, setMenuWidth] = useState(0);
9
7
  const menuRef = useRef(null);
10
8
  const isMobile = window.innerWidth < 768;
9
+ const openOnTextSelect = client.plugin.getUserInfo().context_menu_on_select;
10
+ const actions = client.plugin
11
+ .getPluginInfo()
12
+ .installedPlugins.flatMap((p) => p.context_menu_actions)
13
+ .filter(Boolean);
11
14
  /**
12
15
  * Calculates position for mobile context menu based on selected text bounds.
13
16
  * Centers the menu horizontally over the selected text and positions it 30px below the text's end.
@@ -29,14 +32,8 @@ const ContextMenu = ({ client }) => {
29
32
  return { x: centerX, y: textEndY, text: selectedText };
30
33
  };
31
34
  useEffect(() => {
32
- const actions = client.plugin
33
- .getPluginInfo()
34
- .installedPlugins.flatMap((p) => p.context_menu_actions)
35
- .filter(Boolean);
36
- setActions(actions);
37
- setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
38
35
  client.event.on('global.contextMenu.createActions', ({ data }) => {
39
- setActions([...data.actions, ...actions]);
36
+ actions.push(...data.actions);
40
37
  });
41
38
  }, []);
42
39
  // Update menu width when menu is rendered
@@ -44,7 +41,7 @@ const ContextMenu = ({ client }) => {
44
41
  if (isOpen && menuRef.current) {
45
42
  setMenuWidth(menuRef.current.offsetWidth);
46
43
  }
47
- }, [isOpen, actions]);
44
+ }, [isOpen]);
48
45
  useEffect(() => {
49
46
  // Track mouse position globally
50
47
  const handleMouseMove = (e) => {
@@ -121,7 +118,7 @@ const ContextMenu = ({ client }) => {
121
118
  if (!isOpen) {
122
119
  return null;
123
120
  }
124
- return (_jsx("div", { ref: menuRef, className: "fixed bg-gray-400 dark:bg-gray-700 shadow-lg border border-gray-400 rounded-md overflow-hidden dark:text-white z-50", style: { top: position.y, left: position.x }, children: actions.map((action, index) => (_jsx(MenuEntryItem, { icon: action.icon, text: action.text, onClick: () => {
121
+ return (_jsx("div", { ref: menuRef, className: "fixed bg-gray-400 dark:bg-gray-700 shadow-lg border border-gray-400 rounded-md overflow-hidden dark:text-white z-50", style: { top: position.y, left: position.x }, children: actions.map((action, index) => (_jsx(MenuEntryItem, { iconUrl: action.iconUrl, text: action.text, onClick: () => {
125
122
  var _a;
126
123
  setIsOpen(false);
127
124
  (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
@@ -129,6 +126,6 @@ const ContextMenu = ({ client }) => {
129
126
  } }, index))) }));
130
127
  };
131
128
  function MenuEntryItem(props) {
132
- return (_jsxs("button", { onClick: props.onClick, className: "px-4 py-2 text-left hover:bg-gray-500 dark:hover:bg-gray-600 w-full flex flex-row", children: [_jsx("span", { className: "flex-grow", children: props.icon }), _jsx("span", { className: "flex-grow", children: props.text })] }));
129
+ return (_jsxs("button", { onClick: props.onClick, className: "px-4 py-2 text-left hover:bg-gray-500 dark:hover:bg-gray-600 w-full flex flex-row", children: [_jsx("span", { className: "flex-grow", children: props.iconUrl && _jsx("img", { src: props.iconUrl, alt: props.text, className: "w-4 h-4 mr-2" }) }), _jsx("span", { className: "flex-grow", children: props.text })] }));
133
130
  }
134
131
  export default ContextMenu;
@@ -12,6 +12,7 @@ export declare class ChunkedAudioPlayer {
12
12
  private currentIndex;
13
13
  private startedPlaying;
14
14
  private onEndOfSpeech;
15
+ private readonly backgroundNoiseLevel;
15
16
  constructor();
16
17
  private init;
17
18
  setOnLoudnessChange(callback: (value: number) => void): void;
@@ -19,6 +19,7 @@ export class ChunkedAudioPlayer {
19
19
  this.currentIndex = 0;
20
20
  this.startedPlaying = false;
21
21
  this.onEndOfSpeech = () => { };
22
+ this.backgroundNoiseLevel = 30; // Background noise level that should be treated as baseline (0)
22
23
  this.init();
23
24
  }
24
25
  init() {
@@ -162,9 +163,18 @@ export class ChunkedAudioPlayer {
162
163
  if (loudnessInDb > maxDb) {
163
164
  loudnessInDb = maxDb;
164
165
  }
165
- const loudnessScale = ((loudnessInDb - minDb) / (maxDb - minDb)) * 100;
166
- // console.log("root:corrent loudness", loudnessScale);
167
- this.loudnessCallback(loudnessScale);
166
+ let loudnessScale = ((loudnessInDb - minDb) / (maxDb - minDb)) * 100;
167
+ // Adjust loudness: shift zero level up by background noise amount
168
+ // Values below background noise level are set to 0
169
+ // Values above are remapped to 0-100 scale
170
+ if (loudnessScale < this.backgroundNoiseLevel) {
171
+ loudnessScale = 0;
172
+ }
173
+ else {
174
+ // Remap from [backgroundNoiseLevel, 100] to [0, 100]
175
+ loudnessScale = ((loudnessScale - this.backgroundNoiseLevel) / (100 - this.backgroundNoiseLevel)) * 100;
176
+ }
177
+ this.loudnessCallback(Math.round(loudnessScale));
168
178
  }
169
179
  // Call this method again at regular intervals if you want continuous loudness monitoring
170
180
  this.handle = requestAnimationFrame(() => this.monitorLoudness());
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@rimori/react-client",
3
- "version": "0.2.7",
3
+ "version": "0.3.0-next.1",
4
+ "license": "Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/rimori-org/react-client.git"
8
+ },
4
9
  "main": "dist/index.js",
5
10
  "types": "dist/index.d.ts",
6
11
  "type": "module",
@@ -18,17 +23,19 @@
18
23
  "format": "prettier --write ."
19
24
  },
20
25
  "peerDependencies": {
21
- "react": "^18.0.0",
22
- "react-dom": "^18.0.0",
23
- "@rimori/client": "^2.1.7"
26
+ "@rimori/client": "2.2.0-next.1",
27
+ "react": "^18.1.0",
28
+ "react-dom": "^18.1.0"
24
29
  },
25
30
  "dependencies": {
26
31
  "html2canvas": "1.4.1",
27
- "react-icons": "5.4.0"
32
+ "react-icons": "5.4.0",
33
+ "react-markdown": "^10.1.0"
28
34
  },
29
35
  "devDependencies": {
30
36
  "@eslint/js": "^9.37.0",
31
- "@rimori/client": "^2.1.7",
37
+ "@rimori/client": "2.2.0-next.1",
38
+ "@types/react": "^18.3.21",
32
39
  "eslint-config-prettier": "^10.1.8",
33
40
  "eslint-plugin-prettier": "^5.5.4",
34
41
  "eslint-plugin-react-hooks": "^7.0.0",
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useRef } from 'react';
1
+ import { useState, useEffect, useRef } from 'react';
2
2
  import { RimoriClient, MenuEntry } from '@rimori/client';
3
3
 
4
4
  export interface Position {
@@ -9,12 +9,15 @@ export interface Position {
9
9
 
10
10
  const ContextMenu = ({ client }: { client: RimoriClient }) => {
11
11
  const [isOpen, setIsOpen] = useState<boolean>(false);
12
- const [actions, setActions] = useState<MenuEntry[]>([]);
13
12
  const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
14
- const [openOnTextSelect, setOpenOnTextSelect] = useState(false);
15
13
  const [menuWidth, setMenuWidth] = useState<number>(0);
16
14
  const menuRef = useRef<HTMLDivElement>(null);
17
15
  const isMobile = window.innerWidth < 768;
16
+ const openOnTextSelect = client.plugin.getUserInfo().context_menu_on_select;
17
+ const actions = client.plugin
18
+ .getPluginInfo()
19
+ .installedPlugins.flatMap((p) => p.context_menu_actions)
20
+ .filter(Boolean);
18
21
 
19
22
  /**
20
23
  * Calculates position for mobile context menu based on selected text bounds.
@@ -23,7 +26,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
23
26
  * @param menuWidth - The width of the menu to center properly
24
27
  * @returns Position object with x and y coordinates
25
28
  */
26
- const calculateMobilePosition = (selectedText: string, menuWidth: number = 0): Position => {
29
+ const calculateMobilePosition = (selectedText: string, menuWidth = 0): Position => {
27
30
  const selection = window.getSelection();
28
31
  if (!selection || !selectedText) {
29
32
  return { x: 0, y: 0, text: selectedText };
@@ -42,15 +45,8 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
42
45
  };
43
46
 
44
47
  useEffect(() => {
45
- const actions = client.plugin
46
- .getPluginInfo()
47
- .installedPlugins.flatMap((p) => p.context_menu_actions)
48
- .filter(Boolean);
49
- setActions(actions);
50
- setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
51
-
52
48
  client.event.on<{ actions: MenuEntry[] }>('global.contextMenu.createActions', ({ data }) => {
53
- setActions([...data.actions, ...actions]);
49
+ actions.push(...data.actions);
54
50
  });
55
51
  }, []);
56
52
 
@@ -59,11 +55,11 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
59
55
  if (isOpen && menuRef.current) {
60
56
  setMenuWidth(menuRef.current.offsetWidth);
61
57
  }
62
- }, [isOpen, actions]);
58
+ }, [isOpen]);
63
59
 
64
60
  useEffect(() => {
65
61
  // Track mouse position globally
66
- const handleMouseMove = (e: MouseEvent) => {
62
+ const handleMouseMove = (e: MouseEvent): void => {
67
63
  const selectedText = window.getSelection()?.toString().trim();
68
64
  if (isOpen && selectedText === position.text) return;
69
65
 
@@ -74,7 +70,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
74
70
  }
75
71
  };
76
72
 
77
- const handleMouseUp = (e: MouseEvent) => {
73
+ const handleMouseUp = (e: MouseEvent): void => {
78
74
  const selectedText = window.getSelection()?.toString().trim();
79
75
  // Check if click is inside the context menu
80
76
  if (menuRef.current && menuRef.current.contains(e.target as Node)) {
@@ -112,7 +108,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
112
108
  };
113
109
 
114
110
  // Add selectionchange listener to close menu if selection is cleared and update position for mobile
115
- const handleSelectionChange = () => {
111
+ const handleSelectionChange = (): void => {
116
112
  const selectedText = window.getSelection()?.toString().trim();
117
113
  if (!selectedText && isOpen) {
118
114
  setIsOpen(false);
@@ -148,7 +144,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
148
144
  {actions.map((action, index) => (
149
145
  <MenuEntryItem
150
146
  key={index}
151
- icon={action.icon}
147
+ iconUrl={action.iconUrl}
152
148
  text={action.text}
153
149
  onClick={() => {
154
150
  setIsOpen(false);
@@ -161,13 +157,15 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
161
157
  );
162
158
  };
163
159
 
164
- function MenuEntryItem(props: { icon: React.ReactNode; text: string; onClick: () => void }) {
160
+ function MenuEntryItem(props: { iconUrl?: string; text: string; onClick: () => void }): JSX.Element {
165
161
  return (
166
162
  <button
167
163
  onClick={props.onClick}
168
164
  className="px-4 py-2 text-left hover:bg-gray-500 dark:hover:bg-gray-600 w-full flex flex-row"
169
165
  >
170
- <span className="flex-grow">{props.icon}</span>
166
+ <span className="flex-grow">
167
+ {props.iconUrl && <img src={props.iconUrl} alt={props.text} className="w-4 h-4 mr-2" />}
168
+ </span>
171
169
  <span className="flex-grow">{props.text}</span>
172
170
  {/* <span className="text-sm">Ctrl+Shift+xxxx</span> */}
173
171
  </button>
@@ -12,6 +12,7 @@ export class ChunkedAudioPlayer {
12
12
  private currentIndex = 0;
13
13
  private startedPlaying = false;
14
14
  private onEndOfSpeech: () => void = () => {};
15
+ private readonly backgroundNoiseLevel = 30; // Background noise level that should be treated as baseline (0)
15
16
 
16
17
  constructor() {
17
18
  this.init();
@@ -171,10 +172,19 @@ export class ChunkedAudioPlayer {
171
172
  loudnessInDb = maxDb;
172
173
  }
173
174
 
174
- const loudnessScale = ((loudnessInDb - minDb) / (maxDb - minDb)) * 100;
175
- // console.log("root:corrent loudness", loudnessScale);
175
+ let loudnessScale = ((loudnessInDb - minDb) / (maxDb - minDb)) * 100;
176
176
 
177
- this.loudnessCallback(loudnessScale);
177
+ // Adjust loudness: shift zero level up by background noise amount
178
+ // Values below background noise level are set to 0
179
+ // Values above are remapped to 0-100 scale
180
+ if (loudnessScale < this.backgroundNoiseLevel) {
181
+ loudnessScale = 0;
182
+ } else {
183
+ // Remap from [backgroundNoiseLevel, 100] to [0, 100]
184
+ loudnessScale = ((loudnessScale - this.backgroundNoiseLevel) / (100 - this.backgroundNoiseLevel)) * 100;
185
+ }
186
+
187
+ this.loudnessCallback(Math.round(loudnessScale));
178
188
  }
179
189
 
180
190
  // Call this method again at regular intervals if you want continuous loudness monitoring
@@ -1,8 +0,0 @@
1
- interface Props {
2
- content?: string;
3
- editable: boolean;
4
- className?: string;
5
- onUpdate?: (content: string) => void;
6
- }
7
- export declare const MarkdownEditor: (props: Props) => import("react/jsx-runtime").JSX.Element;
8
- export {};
@@ -1,48 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Markdown } from 'tiptap-markdown';
3
- import StarterKit from '@tiptap/starter-kit';
4
- import { PiCodeBlock } from 'react-icons/pi';
5
- import { TbBlockquote } from 'react-icons/tb';
6
- import { GoListOrdered } from 'react-icons/go';
7
- import { AiOutlineUnorderedList } from 'react-icons/ai';
8
- import { EditorProvider, useCurrentEditor } from '@tiptap/react';
9
- import { LuHeading1, LuHeading2, LuHeading3 } from 'react-icons/lu';
10
- import { FaBold, FaCode, FaItalic, FaParagraph, FaStrikethrough } from 'react-icons/fa';
11
- const EditorButton = ({ action, isActive, label, disabled }) => {
12
- const { editor } = useCurrentEditor();
13
- if (!editor) {
14
- return null;
15
- }
16
- if (action.includes('heading')) {
17
- const level = parseInt(action[action.length - 1]);
18
- return (_jsx("button", { onClick: () => editor.chain().focus().toggleHeading({ level: level }).run(), className: `pl-2 ${isActive ? 'is-active' : ''}`, children: label }));
19
- }
20
- return (_jsx("button", { onClick: () => editor.chain().focus()[action]().run(), disabled: disabled ? !editor.can().chain().focus()[action]().run() : false, className: `pl-2 ${isActive ? 'is-active' : ''}`, children: label }));
21
- };
22
- const MenuBar = () => {
23
- const { editor } = useCurrentEditor();
24
- if (!editor) {
25
- return null;
26
- }
27
- return (_jsxs("div", { className: "bg-gray-400 dark:bg-gray-800 dark:text-white text-lg flex flex-row flex-wrap items-center p-1", children: [_jsx(EditorButton, { action: "toggleBold", isActive: editor.isActive('bold'), label: _jsx(FaBold, {}), disabled: true }), _jsx(EditorButton, { action: "toggleItalic", isActive: editor.isActive('italic'), label: _jsx(FaItalic, {}), disabled: true }), _jsx(EditorButton, { action: "toggleStrike", isActive: editor.isActive('strike'), label: _jsx(FaStrikethrough, {}), disabled: true }), _jsx(EditorButton, { action: "toggleCode", isActive: editor.isActive('code'), label: _jsx(FaCode, {}), disabled: true }), _jsx(EditorButton, { action: "setParagraph", isActive: editor.isActive('paragraph'), label: _jsx(FaParagraph, {}) }), _jsx(EditorButton, { action: "setHeading1", isActive: editor.isActive('heading', { level: 1 }), label: _jsx(LuHeading1, { size: '24px' }) }), _jsx(EditorButton, { action: "setHeading2", isActive: editor.isActive('heading', { level: 2 }), label: _jsx(LuHeading2, { size: '24px' }) }), _jsx(EditorButton, { action: "setHeading3", isActive: editor.isActive('heading', { level: 3 }), label: _jsx(LuHeading3, { size: '24px' }) }), _jsx(EditorButton, { action: "toggleBulletList", isActive: editor.isActive('bulletList'), label: _jsx(AiOutlineUnorderedList, { size: '24px' }) }), _jsx(EditorButton, { action: "toggleOrderedList", isActive: editor.isActive('orderedList'), label: _jsx(GoListOrdered, { size: '24px' }) }), _jsx(EditorButton, { action: "toggleCodeBlock", isActive: editor.isActive('codeBlock'), label: _jsx(PiCodeBlock, { size: '24px' }) }), _jsx(EditorButton, { action: "toggleBlockquote", isActive: editor.isActive('blockquote'), label: _jsx(TbBlockquote, { size: '24px' }) })] }));
28
- };
29
- const extensions = [
30
- StarterKit.configure({
31
- bulletList: {
32
- HTMLAttributes: {
33
- class: 'list-disc list-inside dark:text-white p-1 mt-1 [&_li]:mb-1 [&_p]:inline m-0',
34
- },
35
- },
36
- orderedList: {
37
- HTMLAttributes: {
38
- className: 'list-decimal list-inside dark:text-white p-1 mt-1 [&_li]:mb-1 [&_p]:inline m-0',
39
- },
40
- },
41
- }),
42
- Markdown,
43
- ];
44
- export const MarkdownEditor = (props) => {
45
- return (_jsx("div", { className: 'text-md border border-gray-800 overflow-hidden ' + props.className, style: { borderWidth: props.editable ? 1 : 0 }, children: _jsx(EditorProvider, { slotBefore: props.editable ? _jsx(MenuBar, {}) : null, extensions: extensions, content: props.content, editable: props.editable, onUpdate: (e) => {
46
- props.onUpdate && props.onUpdate(e.editor.storage.markdown.getMarkdown());
47
- } }, (props.editable ? 'editable' : 'readonly') + props.content) }));
48
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,11 +0,0 @@
1
- interface Props {
2
- iconSize?: string;
3
- className?: string;
4
- disabled?: boolean;
5
- loading?: boolean;
6
- enablePushToTalk?: boolean;
7
- onRecordingStatusChange: (running: boolean) => void;
8
- onVoiceRecorded: (message: string) => void;
9
- }
10
- export declare const VoiceRecorder: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<unknown>>;
11
- export {};
@@ -1,95 +0,0 @@
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
- import { jsx as _jsx } from "react/jsx-runtime";
11
- import { useRimori } from '../../../providers/PluginProvider';
12
- import { FaMicrophone, FaSpinner } from 'react-icons/fa6';
13
- import { AudioController } from '@rimori/client';
14
- import { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react';
15
- export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className, disabled, loading, onRecordingStatusChange, enablePushToTalk = false, }, ref) => {
16
- const [isRecording, setIsRecording] = useState(false);
17
- const [internalIsProcessing, setInternalIsProcessing] = useState(false);
18
- const audioControllerRef = useRef(null);
19
- const { ai, plugin } = useRimori();
20
- // Ref for latest onVoiceRecorded callback
21
- const onVoiceRecordedRef = useRef(onVoiceRecorded);
22
- useEffect(() => {
23
- onVoiceRecordedRef.current = onVoiceRecorded;
24
- }, [onVoiceRecorded]);
25
- const startRecording = () => __awaiter(void 0, void 0, void 0, function* () {
26
- try {
27
- if (!audioControllerRef.current) {
28
- audioControllerRef.current = new AudioController(plugin.pluginId);
29
- }
30
- yield audioControllerRef.current.startRecording();
31
- setIsRecording(true);
32
- onRecordingStatusChange(true);
33
- }
34
- catch (error) {
35
- console.error('Failed to start recording:', error);
36
- // Handle permission denied or other errors
37
- }
38
- });
39
- const stopRecording = () => __awaiter(void 0, void 0, void 0, function* () {
40
- try {
41
- if (audioControllerRef.current && isRecording) {
42
- const audioResult = yield audioControllerRef.current.stopRecording();
43
- // console.log("audioResult: ", audioResult);
44
- setInternalIsProcessing(true);
45
- // Play the recorded audio from the Blob
46
- // const blobUrl = URL.createObjectURL(audioResult.recording);
47
- // const audioRef = new Audio(blobUrl);
48
- // audioRef.onended = () => URL.revokeObjectURL(blobUrl);
49
- // audioRef.play().catch((e) => console.error('Playback error:', e));
50
- // console.log("audioBlob: ", audioResult.recording);
51
- const text = yield ai.getTextFromVoice(audioResult.recording);
52
- // console.log("stt result", text);
53
- // throw new Error("test");
54
- setInternalIsProcessing(false);
55
- onVoiceRecordedRef.current(text);
56
- }
57
- }
58
- catch (error) {
59
- console.error('Failed to stop recording:', error);
60
- }
61
- finally {
62
- setIsRecording(false);
63
- onRecordingStatusChange(false);
64
- }
65
- });
66
- useImperativeHandle(ref, () => ({
67
- startRecording,
68
- stopRecording,
69
- }));
70
- // push to talk feature
71
- const spacePressedRef = useRef(false);
72
- useEffect(() => {
73
- if (!enablePushToTalk)
74
- return;
75
- const handleKeyDown = (event) => __awaiter(void 0, void 0, void 0, function* () {
76
- if (event.code === 'Space' && !spacePressedRef.current) {
77
- spacePressedRef.current = true;
78
- yield startRecording();
79
- }
80
- });
81
- const handleKeyUp = (event) => {
82
- if (event.code === 'Space' && spacePressedRef.current) {
83
- spacePressedRef.current = false;
84
- stopRecording();
85
- }
86
- };
87
- window.addEventListener('keydown', handleKeyDown);
88
- window.addEventListener('keyup', handleKeyUp);
89
- return () => {
90
- window.removeEventListener('keydown', handleKeyDown);
91
- window.removeEventListener('keyup', handleKeyUp);
92
- };
93
- }, [enablePushToTalk]);
94
- return (_jsx("button", { className: 'flex flex-row justify-center items-center rounded-full mx-auto disabled:opacity-50 ' + className, onClick: isRecording ? stopRecording : startRecording, disabled: disabled || loading || internalIsProcessing, children: loading || internalIsProcessing ? (_jsx(FaSpinner, { className: "animate-spin" })) : (_jsx(FaMicrophone, { size: iconSize, className: isRecording ? 'text-red-600' : '' })) }));
95
- });
@@ -1,2 +0,0 @@
1
- export declare function useTheme(theme?: string | null): boolean;
2
- export declare function isDarkTheme(theme?: string | null): boolean;
@@ -1,31 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- export function useTheme(theme) {
3
- const [isDark, setIsDark] = useState(false);
4
- useEffect(() => {
5
- const root = document.documentElement;
6
- const nextIsDark = isDarkTheme(theme);
7
- setIsDark(nextIsDark);
8
- root.classList.add('dark:text-gray-200');
9
- if (nextIsDark) {
10
- root.setAttribute('data-theme', 'dark');
11
- root.classList.add('dark', 'dark:bg-gray-950');
12
- root.style.background = 'hsl(var(--background))';
13
- return;
14
- }
15
- root.removeAttribute('data-theme');
16
- root.classList.remove('dark', 'dark:bg-gray-950');
17
- root.style.background = '';
18
- }, [theme]);
19
- return isDark;
20
- }
21
- export function isDarkTheme(theme) {
22
- // If no theme provided, try to get from URL as fallback (for standalone mode)
23
- if (!theme) {
24
- const urlParams = new URLSearchParams(window.location.search);
25
- theme = urlParams.get('theme');
26
- }
27
- if (!theme || theme === 'system') {
28
- return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
29
- }
30
- return theme === 'dark';
31
- }
@@ -1,2 +0,0 @@
1
- export declare function isFullscreen(): boolean;
2
- export declare function triggerFullscreen(onStateChange: (isFullscreen: boolean) => void, selector?: string): void;
@@ -1,23 +0,0 @@
1
- export function isFullscreen() {
2
- return !!document.fullscreenElement;
3
- }
4
- export function triggerFullscreen(onStateChange, selector) {
5
- document.addEventListener('fullscreenchange', () => {
6
- onStateChange(isFullscreen());
7
- });
8
- try {
9
- const ref = document.querySelector(selector || '#root');
10
- if (!isFullscreen()) {
11
- // @ts-ignore
12
- void (ref.requestFullscreen() || ref.webkitRequestFullscreen());
13
- }
14
- else {
15
- // @ts-ignore
16
- void (document.exitFullscreen() || document.webkitExitFullscreen());
17
- }
18
- }
19
- catch (error) {
20
- console.error('Failed to enter fullscreen', error.message);
21
- }
22
- onStateChange(isFullscreen());
23
- }