Files
gochat/frontend/app/javascript/dashboard/components-next/message/chips/AttachmentChips.vue
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

107 lines
3.1 KiB
Vue

<script setup>
import { computed, useAttrs } from 'vue';
import ImageChip from 'next/message/chips/Image.vue';
import VideoChip from 'next/message/chips/Video.vue';
import AudioChip from 'next/message/chips/Audio.vue';
import FileChip from 'next/message/chips/File.vue';
import { useMessageContext } from '../provider.js';
import { ATTACHMENT_TYPES } from '../constants';
/**
* @typedef {Object} Attachment
* @property {number} id - Unique identifier for the attachment
* @property {number} messageId - ID of the associated message
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
* @property {number} accountId - ID of the associated account
* @property {string|null} extension - File extension
* @property {string} dataUrl - URL to access the full attachment data
* @property {string} thumbUrl - URL to access the thumbnail version
* @property {number} fileSize - Size of the file in bytes
* @property {number|null} width - Width of the image if applicable
* @property {number|null} height - Height of the image if applicable
*/
const props = defineProps({
attachments: {
type: Array,
default: () => [],
},
});
defineOptions({
inheritAttrs: false,
});
const attrs = useAttrs();
const { orientation } = useMessageContext();
const classToApply = computed(() => {
const baseClasses = [attrs.class, 'flex', 'flex-wrap'];
if (orientation.value === 'right') {
baseClasses.push('justify-end');
}
return baseClasses;
});
const allAttachments = computed(() => {
return Array.isArray(props.attachments) ? props.attachments : [];
});
const mediaAttachments = computed(() => {
const allowedTypes = [ATTACHMENT_TYPES.IMAGE, ATTACHMENT_TYPES.VIDEO];
const mediaTypes = allAttachments.value.filter(attachment =>
allowedTypes.includes(attachment.fileType)
);
return mediaTypes.sort(
(a, b) =>
allowedTypes.indexOf(a.fileType) - allowedTypes.indexOf(b.fileType)
);
});
const recordings = computed(() => {
return allAttachments.value.filter(
attachment => attachment.fileType === ATTACHMENT_TYPES.AUDIO
);
});
const files = computed(() => {
return allAttachments.value.filter(
attachment => attachment.fileType === ATTACHMENT_TYPES.FILE
);
});
</script>
<template>
<div v-if="mediaAttachments.length" :class="classToApply">
<template v-for="attachment in mediaAttachments" :key="attachment.id">
<ImageChip
v-if="attachment.fileType === ATTACHMENT_TYPES.IMAGE"
:attachment="attachment"
/>
<VideoChip
v-else-if="attachment.fileType === ATTACHMENT_TYPES.VIDEO"
:attachment="attachment"
/>
</template>
</div>
<div v-if="recordings.length" :class="classToApply">
<div v-for="attachment in recordings" :key="attachment.id">
<AudioChip
class="bg-n-alpha-3 dark:bg-n-alpha-2 text-n-slate-12"
:attachment="attachment"
/>
</div>
</div>
<div v-if="files.length" :class="classToApply">
<FileChip
v-for="attachment in files"
:key="attachment.id"
:attachment="attachment"
/>
</div>
</template>