Files
gochat/frontend/app/javascript/shared/helpers/FileHelper.js
T
rogee 321c61aaae Vendor Chatwoot Vue 3 frontend into frontend/
Copy the Chatwoot (v4.14.0) frontend runnable subset into frontend/ for
customization:
- app/javascript/ (Vue SPA: dashboard, widget, sdk, portal, superadmin)
- app/views/ (ERB templates for vite-plugin-ruby entrypoint resolution)
- app/helpers/, app/assets/ (Rails view helpers, static assets)
- enterprise/ (Enterprise edition frontend overlay)
- config/vite.json, vite.config.ts, bin/vite (Vite-Rails toolchain)
- package.json, pnpm-lock.yaml, tailwind/postcss/eslint configs
- Gemfile, Gemfile.lock (vite_rails gem for bin/vite binstub)

Excluded Rails backend: controllers, models, services, jobs, mailers,
policies, db, lib, spec, public, node_modules.

Update references to the new frontend location:
- .gitignore: exclude frontend build artifacts (node_modules, tmp, packs),
  keep frontend/bin/ and frontend/vendor/ via negation
- backend/scripts/parity_frontend_smoke.sh: CHATWOOT_DIR default
  reference/chatwoot -> ../frontend
- backend/scripts/parity_frontend_browser_smoke.mjs: same default update
- AGENTS.md: add frontend section with Rails/Vite coupling notes
- README: architecture tree includes frontend/
2026-07-07 14:56:01 +08:00

88 lines
2.8 KiB
JavaScript

import { getAllowedFileTypesByChannel } from '@chatwoot/utils';
import { INBOX_TYPES } from 'dashboard/helper/inbox';
export const DEFAULT_MAXIMUM_FILE_UPLOAD_SIZE = 40;
export const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / k ** i).toFixed(dm)) + ' ' + sizes[i];
};
export const fileSizeInMegaBytes = bytes => {
return bytes / (1024 * 1024);
};
export const checkFileSizeLimit = (file, maximumUploadLimit) => {
const fileSize = file?.file?.size || file?.size;
const fileSizeInMB = fileSizeInMegaBytes(fileSize);
return fileSizeInMB <= maximumUploadLimit;
};
export const resolveMaximumFileUploadSize = value => {
const parsedValue = Number(value);
if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
return DEFAULT_MAXIMUM_FILE_UPLOAD_SIZE;
}
return parsedValue;
};
/**
* Validates if a file type is allowed for a specific channel
* @param {File} file - The file to validate
* @param {Object} options - Validation options
* @param {string} options.channelType - The channel type
* @param {string} options.medium - The channel medium
* @param {string} options.conversationType - The conversation type (for Instagram DM detection)
* @param {boolean} options.isInstagramChannel - Whether it's an Instagram channel
* @param {boolean} options.isOnPrivateNote - Whether composing a private note (uses broader file type list)
* @returns {boolean} - True if file type is allowed, false otherwise
*/
export const isFileTypeAllowedForChannel = (file, options = {}) => {
if (!file || file.size === 0) return false;
const {
channelType: originalChannelType,
medium,
conversationType,
isInstagramChannel,
isOnPrivateNote,
} = options;
const allowedFileTypes = isOnPrivateNote
? getAllowedFileTypesByChannel()
: getAllowedFileTypesByChannel({
channelType:
isInstagramChannel || conversationType === 'instagram_direct_message'
? INBOX_TYPES.INSTAGRAM
: originalChannelType,
medium,
});
// Convert to array and validate
const allowedTypesArray = allowedFileTypes.split(',').map(t => t.trim());
const fileExtension = `.${file.name.split('.').pop()}`;
return allowedTypesArray.some(allowedType => {
// Check for exact file extension match
if (allowedType === fileExtension) return true;
// Check for wildcard MIME type (e.g., image/*)
if (allowedType.endsWith('/*')) {
const prefix = allowedType.slice(0, -2); // Remove '/*'
return file.type.startsWith(prefix + '/');
}
// Check for exact MIME type match
return allowedType === file.type;
});
};