Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b31b8b93c | ||
|
|
4502ed6213 | ||
|
|
f0e376c0d9 | ||
|
|
0be8af9c3d | ||
|
|
f6c4224400 |
@@ -315,6 +315,9 @@ const componentToRender = computed(() => {
|
||||
});
|
||||
|
||||
const shouldShowContextMenu = computed(() => {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (window.__WOOT_ISOLATED_SHELL__) return false;
|
||||
|
||||
return !props.contentAttributes?.isUnsupported;
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Chatwoot React Components
|
||||
|
||||
This module exports Chatwoot UI components as React components that can be embedded in other applications. It uses a hybrid approach combining Vue Web Components with React wrappers.
|
||||
|
||||
## Architecture
|
||||
|
||||
The system works by:
|
||||
1. **Vue Components** → Compiled as custom web components using Vue's `customElement: true`
|
||||
2. **React Wrappers** → Provide React-friendly interfaces to the Vue web components
|
||||
3. **Vite Build** → Packages everything into distributable ES/CJS modules
|
||||
|
||||
## Build System
|
||||
|
||||
### Build Modes (vite.config.ts)
|
||||
- **`BUILD_MODE=react-components`** - Builds React components library
|
||||
- Entry point: `./app/javascript/react-components/src/index.jsx`
|
||||
- Output: ES modules (`react-components.es.js`) and CommonJS (`react-components.cjs.js`)
|
||||
- External dependencies: `react` and `react-dom` (peer dependencies)
|
||||
|
||||
### Available Scripts (package.json)
|
||||
```bash
|
||||
# Build the React components once
|
||||
pnpm build:react
|
||||
|
||||
# Build and watch for changes during development
|
||||
pnpm dev:react
|
||||
|
||||
# Package for NPM distribution (builds + creates dist/react-components/)
|
||||
pnpm package:react
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
In any React app, version 16+, you could use the following snippet
|
||||
|
||||
```jsx
|
||||
import * as ChatwootComponents from '@chatwoot/agent-react-components';
|
||||
import '@chatwoot/agent-react-components/style.css'
|
||||
|
||||
const { ChatwootProvider, ChatwootConversation } = ChatwootComponents;
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ChatwootProvider
|
||||
baseURL="http://localhost:3000"
|
||||
accountId="1"
|
||||
conversationId={13918}
|
||||
userToken="XLwgvCQiewUJMGuYpZqa4J3M"
|
||||
pubsubToken="HkAhoq8aWm2ecdY2tn9FV4BU"
|
||||
websocketURL="ws://localhost:3000"
|
||||
>
|
||||
<ChatwootConversation/>
|
||||
</ChatwootProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
```
|
||||
|
||||
The `<ChatwootProvider>` component ensures all the data necessary for mounting the web component is present before rendering the ChatwootConversation.
|
||||
|
||||
The package also exposes a `useChatwoot` hook for accessing the config instance being used by the conversation, it can be used as a read-only source of truth for the config being used.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
To test this package, you can create a separate React application where you can use the snippet from above, run the following command in your Rails App:
|
||||
|
||||
```bash
|
||||
pnpm dev:react
|
||||
```
|
||||
|
||||
This will start a development server that serves the React components and their dependencies.
|
||||
|
||||
Following this, in a separate terminal, run the following command
|
||||
|
||||
```bash
|
||||
watchexec --restart -N --watch public/packs/react-components.es.js "pnpm package:react --copyMode"
|
||||
```
|
||||
|
||||
This will ensure that the built files are copied correctly to the dist/react-components folder.
|
||||
|
||||
The `package:react` script has a copy mode, which just copies the build from the `public/` folder to the dist folder for linking purposes. It also creates a package.json file with proper entry points, ensure you run the script without copyMode once before.
|
||||
|
||||
Once done, in your React app, you can use `pnpm link <rails-app-dir>/dist/react-components` to link the package. Running the development server will automatically link the package and any changes to it.
|
||||
|
||||
## Publishing
|
||||
|
||||
The `scripts/package-react-components.js` handles:
|
||||
- Building the components with Vite
|
||||
- Copying built files to `dist/react-components/`
|
||||
- Generating `package.json` with proper entry points
|
||||
- Creating development versions with git hash suffixes
|
||||
|
||||
Once the script is packaged, you can run `npm publish` to publish the package. It will be published as `@chatwoot/agent-react-components` with a version that is the latest commit hash
|
||||
@@ -18,6 +18,7 @@ export const ChatwootProvider = ({
|
||||
conversationId,
|
||||
disableUpload,
|
||||
disableEditor,
|
||||
disablePrivateNote,
|
||||
children,
|
||||
}) => {
|
||||
const isInitialized = useRef(false);
|
||||
@@ -38,6 +39,7 @@ export const ChatwootProvider = ({
|
||||
accountId: Number(accountId),
|
||||
conversationId: Number(conversationId),
|
||||
disableEditor: disableEditor || false,
|
||||
disablePrivateNote: disablePrivateNote || false,
|
||||
disableUpload: disableUpload || false,
|
||||
websocketURL: websocketURL,
|
||||
pubsubToken: pubsubToken,
|
||||
@@ -56,6 +58,7 @@ export const ChatwootProvider = ({
|
||||
window.__PUBSUB_TOKEN__ = config.pubsubToken;
|
||||
window.__WOOT_CONVERSATION_ID__ = config.conversationId;
|
||||
window.__EDITOR_DISABLE_UPLOAD__ = config.disableUpload;
|
||||
window.__EDITOR_DISABLE_PRIVATE_NOTE__ = config.disablePrivateNote;
|
||||
window.__DISABLE_EDITOR__ = config.disableEditor;
|
||||
window.__WOOT_ISOLATED_SHELL__ = true;
|
||||
/* eslint-enable no-underscore-dangle */
|
||||
|
||||
@@ -107,6 +107,10 @@ export default {
|
||||
maxLength() {
|
||||
return MESSAGE_MAX_LENGTH.GENERAL;
|
||||
},
|
||||
allowPrivateNote() {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
return window.__EDITOR_DISABLE_PRIVATE_NOTE__ !== true;
|
||||
},
|
||||
isEditorDisabled() {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
return window.__DISABLE_EDITOR__;
|
||||
@@ -123,7 +127,7 @@ export default {
|
||||
},
|
||||
replyBoxClass() {
|
||||
return {
|
||||
'is-private': this.isPrivate,
|
||||
'is-private': this.isPrivate && this.allowPrivateNote,
|
||||
'is-focused': this.isFocused || this.hasAttachments,
|
||||
'pointer-events-none grayscale opacity-70': this.isEditorDisabled,
|
||||
};
|
||||
@@ -132,6 +136,7 @@ export default {
|
||||
return this.attachedFiles.length;
|
||||
},
|
||||
isOnPrivateNote() {
|
||||
if (!this.allowPrivateNote) return false;
|
||||
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
||||
},
|
||||
isOnExpandedLayout() {
|
||||
@@ -222,6 +227,8 @@ export default {
|
||||
}
|
||||
},
|
||||
setReplyMode(mode = REPLY_EDITOR_MODES.REPLY) {
|
||||
if (!this.allowPrivateNote) return;
|
||||
|
||||
const { can_reply: canReply } = this.currentChat;
|
||||
this.$store.dispatch('draftMessages/setReplyEditorMode', {
|
||||
mode,
|
||||
@@ -381,6 +388,7 @@ export default {
|
||||
<template>
|
||||
<div ref="replyEditor" class="reply-box" :class="replyBoxClass">
|
||||
<ReplyTopPanel
|
||||
v-if="allowPrivateNote"
|
||||
:mode="replyType"
|
||||
disable-popout
|
||||
:is-message-length-reaching-threshold="isMessageLengthReachingThreshold"
|
||||
|
||||
@@ -53,6 +53,11 @@ const allMessages = computed(() => {
|
||||
return useCamelCase(conversation.value.messages, { deep: true }).reverse();
|
||||
});
|
||||
|
||||
const disablePrivateNote = computed(() => {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
return window.__EDITOR_DISABLE_PRIVATE_NOTE__ === true;
|
||||
});
|
||||
|
||||
const fetchMore = async () => {
|
||||
if (isFetching.value) return;
|
||||
if (!conversation?.value?.id) return;
|
||||
@@ -99,7 +104,11 @@ useInfiniteScroll(messageListRef, useThrottleFn(fetchMore, 1000), {
|
||||
<div class="relative">
|
||||
<ul
|
||||
ref="messageListRef"
|
||||
class="px-4 pt-4 flex flex-col-reverse pb-60 bg-n-background overflow-scroll h-screen"
|
||||
class="px-4 pt-4 flex flex-col-reverse bg-n-background overflow-scroll h-screen"
|
||||
:class="{
|
||||
'pb-60': !disablePrivateNote,
|
||||
'pb-48': disablePrivateNote,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="isAnyoneTyping"
|
||||
|
||||
@@ -35,27 +35,34 @@ function generatePackageJson(packageDir) {
|
||||
// Read version from main package.json
|
||||
const mainPackage = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
|
||||
// Get current git commit hash for development builds
|
||||
function getVersionSuffix() {
|
||||
// Generate a clean epoch-based version
|
||||
function generateTimestampVersion() {
|
||||
try {
|
||||
const epochSeconds = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Get git hash for reference
|
||||
const gitHash = execSync('git rev-parse --short HEAD', {
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe',
|
||||
}).trim();
|
||||
|
||||
return `dev.${gitHash}`;
|
||||
// Parse base version and increment patch
|
||||
const [major, minor, patch] = mainPackage.version.split('.').map(Number);
|
||||
const newPatch = patch + 1;
|
||||
|
||||
return `${major}.${minor}.${newPatch}-${epochSeconds}.${gitHash}`;
|
||||
} catch (error) {
|
||||
console.warn(' ⚠️ Could not get git hash, using beta.1');
|
||||
return 'beta.1';
|
||||
console.warn(
|
||||
' ⚠️ Could not generate timestamp version, using incremented patch'
|
||||
);
|
||||
const [major, minor, patch] = mainPackage.version.split('.').map(Number);
|
||||
return `${major}.${minor}.${patch + 1}`;
|
||||
}
|
||||
}
|
||||
|
||||
const versionSuffix = getVersionSuffix();
|
||||
const finalVersion = `${mainPackage.version}-${versionSuffix}`;
|
||||
const finalVersion = generateTimestampVersion();
|
||||
|
||||
console.log(
|
||||
` 📋 Package version: ${finalVersion} (suffix: ${versionSuffix})`
|
||||
);
|
||||
console.log(` 📋 Package version: ${finalVersion}`);
|
||||
|
||||
const packageJson = {
|
||||
name: '@chatwoot/agent-react-components',
|
||||
|
||||
Reference in New Issue
Block a user