Skip to content

feat: attachments component#148

Merged
hexqi merged 16 commits intoopentiny:developfrom
SonyLeo:feat/attachments
Jul 25, 2025
Merged

feat: attachments component#148
hexqi merged 16 commits intoopentiny:developfrom
SonyLeo:feat/attachments

Conversation

@SonyLeo
Copy link
Copy Markdown
Collaborator

@SonyLeo SonyLeo commented Jul 10, 2025

Summary by CodeRabbit

  • New Features

    • Introduced the Attachments component with file list display, image preview, download, status indicators, wrapping, custom icons, and actions.
    • Added image preview with navigation, thumbnail selection, and download support.
    • Included demos demonstrating basic usage, status display, picture lists, wrapping, custom icons, custom file types, and custom download logic.
    • Enabled custom file type matching and icon customization.
    • Integrated the Attachments component into the component library and public API.
  • Documentation

    • Added comprehensive documentation covering component API, props, events, interfaces, and usage examples.
    • Updated sidebar navigation to include the Attachments component.
  • Style

    • Applied scoped styles for file cards, image previews, and attachment lists to ensure a polished, responsive UI.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 10, 2025

Warning

Rate limit exceeded

@SonyLeo has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 6 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 4701f22 and f3912c8.

📒 Files selected for processing (2)
  • packages/components/src/attachments/composables/useImagePreview.ts (1 hunks)
  • packages/components/src/attachments/index.vue (1 hunks)

Walkthrough

A new Attachments component system is introduced, including Vue components, composables, and comprehensive documentation. The implementation supports file lists with preview, download, status display, custom actions, and extensibility for file types and icons. Multiple demos illustrate usage scenarios, and type definitions ensure strong typing for all interfaces and events.

Changes

File(s) Change Summary
docs/.vitepress/config.mts Added sidebar entry for Attachments documentation.
docs/src/components/attachments.md New documentation for the Attachments component, detailing usage, API, events, and extensibility.
docs/demos/attachments/basic.vue
docs/demos/attachments/status.vue
docs/demos/attachments/wrap.vue
docs/demos/attachments/picture-list.vue
Added demo Vue components showing basic usage, status display, wrapping, and picture list features of the Attachments component.
docs/demos/attachments/custom-icon.vue
docs/demos/attachments/custom-file-type.vue
docs/demos/attachments/download.vue
Added demos for custom file icons, custom file type matchers, and download handling in Attachments.
packages/components/src/attachments/index.vue New main Attachments Vue component managing file lists, variants, actions, preview, and events.
packages/components/src/attachments/components/FileCard.vue
packages/components/src/attachments/components/ImagePreview.vue
Added FileCard and ImagePreview Vue components for rendering file cards and image previews.
packages/components/src/attachments/components/index.ts Re-exports FileCard and ImagePreview components for centralized import.
packages/components/src/attachments/composables/useFileType.ts
packages/components/src/attachments/composables/useFileCard.ts
packages/components/src/attachments/composables/useImagePreview.ts
packages/components/src/attachments/composables/useListType.ts
Added composable functions for file type detection, card interactions, image preview logic, and list type determination.
packages/components/src/attachments/composables/index.ts Re-exports all attachment-related composables for easy import.
packages/components/src/attachments/index.type.ts Introduced TypeScript types and interfaces for attachments, file types, statuses, events, and props.
packages/components/src/attachments/index.ts Exports Attachments component with plugin install method for Vue, enabling global registration.
packages/components/src/index.ts Registers Attachments in the component library, re-exports types and composables, and provides named/aliased exports.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Attachments as Attachments.vue
  participant FileCard
  participant ImagePreview
  participant Composables

  User->>Attachments: Provide items, props, actions
  Attachments->>Composables: useListType, useImagePreview
  Attachments->>FileCard: Render for each file
  FileCard->>Attachments: Emit remove/preview/download/retry/action events
  Attachments->>User: Emit corresponding events
  FileCard->>Composables: useFileCard for file interactions
  Attachments->>ImagePreview: Render when preview triggered
  ImagePreview->>Attachments: Emit close or update events
Loading

Estimated code review effort

3 (120 minutes)

Poem

🐇
A hop, a skip, attachments abound,
With icons and previews, new features are found.
Cards and pictures, statuses too,
Custom types and actions—so much you can do!
The docs are now clearer, the demos delight,
This bunny’s attachments are working just right!
📎✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@SonyLeo SonyLeo changed the title feat: init component feat: attachments component Jul 10, 2025
@SonyLeo SonyLeo force-pushed the feat/attachments branch from 775d733 to efbb81e Compare July 14, 2025 03:37
@SonyLeo SonyLeo marked this pull request as ready for review July 14, 2025 03:39
Comment thread packages/components/src/attachments/index.type.ts Outdated
@gene9831
Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

♻️ Duplicate comments (1)
packages/components/src/attachments/index.type.ts (1)

5-7: Consider consolidating FileStatus and StatusDisplayMode

As mentioned in a previous review, these two types control overlapping UI aspects and could potentially be merged into a single variant prop for better API clarity.

🧹 Nitpick comments (8)
docs/demos/attachments/custom-icon.vue (1)

11-14: Consider using more descriptive icons for better UX.

While the emoji icons work functionally, consider using more conventional file type icons or SVG icons for better visual consistency and accessibility.

const fileIcons: Record<FileType, Component> = {
-  word: h('span', '📄'),
-  excel: h('span', '🐼'),
+  word: h('span', { class: 'file-icon-word' }, '📄'),
+  excel: h('span', { class: 'file-icon-excel' }, '📊'),
}
docs/demos/attachments/basic.vue (1)

16-16: Consider using constants for file size calculations.

The file size calculations are repeated and could be made more maintainable using constants.

+const MB = 1024 * 1024
+
const infoFiles = ref<Attachment[]>([
  {
    id: '1',
    name: '设计文档.docx',
    fileType: 'word',
-    size: 1024 * 1024 * 1.2, // 1.2MB
+    size: MB * 1.2, // 1.2MB
    status: 'success',
  },
  // ... apply similar changes to other entries
])

Also applies to: 23-23, 30-30

packages/components/src/attachments/composables/useFileCard.ts (1)

89-92: Review the custom action logic for image files.

The current logic only triggers preview/download for image files when action.type matches, but this might not cover all use cases. Consider if non-image files should also support these action types.

Consider this refactor to make the logic more explicit:

-    if (action.type === 'preview' && isImage.value) {
-      handlePreview()
-    } else if (action.type === 'download' && isImage.value) {
-      downloadFile()
+    if (action.type === 'preview') {
+      handlePreview()
+    } else if (action.type === 'download') {
+      downloadFile()
     } else {
       emit('action', { action, file: props.file })
     }
docs/demos/attachments/picture-list.vue (1)

34-84: Consider using local or more reliable image sources.

The demo uses external CDN URLs (https://res.hc-cdn.com/...) which may become unavailable over time, potentially breaking the demo.

Consider using:

  1. Local placeholder images in the project
  2. A more reliable CDN like https://picsum.photos/ for demo purposes
  3. Data URLs for small placeholder images

Example with placeholder service:

-    previewUrl: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/fruit.jpg',
+    previewUrl: 'https://picsum.photos/200/150?random=1',
packages/components/src/attachments/composables/useImagePreview.ts (1)

43-45: Consider using a more robust image filtering strategy.

The current filtering excludes error and uploading states, but consider if other states should also be excluded from preview.

Consider defining allowed states more explicitly:

-      previewImages.value = fileList.value.filter(
-        (item) => item.fileType === 'image' && item.status !== 'error' && item.status !== 'uploading',
-      )
+      const allowedStatuses = ['success', 'warning', 'info']
+      previewImages.value = fileList.value.filter(
+        (item) => item.fileType === 'image' && allowedStatuses.includes(item.status),
+      )
packages/components/src/attachments/index.vue (1)

63-71: Consider optimizing the deep watch strategy.

The current deep watch on props.items might cause performance issues with large file lists. Consider using a more targeted approach.

Consider using watchEffect or a more specific watch:

-// 监听props.items变化
-watch(
-  () => props.items,
-  (newItems) => {
-    if (newItems) {
-      fileList.value = newItems
-    }
-  },
-  { deep: true },
-)
+// 监听props.items变化
+watchEffect(() => {
+  if (props.items) {
+    fileList.value = [...props.items]
+  }
+})
packages/components/src/attachments/composables/useFileType.ts (2)

87-89: Consider using a more robust ID generation method

The current implementation using Math.random() could theoretically produce collisions. Consider using a more robust approach like nanoid or UUID.

For better uniqueness guarantees:

-  const generateID = (): string => {
-    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
-  }
+  const generateID = (): string => {
+    // Use timestamp + random for better uniqueness
+    return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
+  }

94-102: Add support for gigabyte file sizes

The function only handles up to MB. Consider adding GB support for larger files.

  const formatFileSize = (size: number): string => {
    if (size < 1024) {
      return size + ' B'
    } else if (size < 1024 * 1024) {
      return (size / 1024).toFixed(2) + ' KB'
-    } else {
+    } else if (size < 1024 * 1024 * 1024) {
      return (size / (1024 * 1024)).toFixed(2) + ' MB'
+    } else {
+      return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
    }
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 561794a and efbb81e.

⛔ Files ignored due to path filters (13)
  • packages/svgs/src/assets/file-excel.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-folder.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-image.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-none.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-other.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-pdf.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-ppt.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-remove.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/file-word.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/image-loading.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/image-warning.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/upload-failed.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/upload-loading.svg is excluded by !**/*.svg
📒 Files selected for processing (19)
  • docs/.vitepress/config.mts (1 hunks)
  • docs/demos/attachments/basic.vue (1 hunks)
  • docs/demos/attachments/custom-icon.vue (1 hunks)
  • docs/demos/attachments/picture-list.vue (1 hunks)
  • docs/demos/attachments/status.vue (1 hunks)
  • docs/demos/attachments/wrap.vue (1 hunks)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/components/FileCard.vue (1 hunks)
  • packages/components/src/attachments/components/ImagePreview.vue (1 hunks)
  • packages/components/src/attachments/components/index.ts (1 hunks)
  • packages/components/src/attachments/composables/index.ts (1 hunks)
  • packages/components/src/attachments/composables/useFileCard.ts (1 hunks)
  • packages/components/src/attachments/composables/useFileType.ts (1 hunks)
  • packages/components/src/attachments/composables/useImagePreview.ts (1 hunks)
  • packages/components/src/attachments/composables/useListType.ts (1 hunks)
  • packages/components/src/attachments/index.ts (1 hunks)
  • packages/components/src/attachments/index.type.ts (1 hunks)
  • packages/components/src/attachments/index.vue (1 hunks)
  • packages/components/src/index.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/.vitepress/config.mts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/demos/attachments/custom-icon.vue (2)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/demos/attachments/basic.vue (2)
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/demos/attachments/picture-list.vue (2)
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/composables/useFileCard.ts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/demos/attachments/status.vue (2)
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/components/ImagePreview.vue (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/index.vue (2)
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/composables/useFileType.ts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/components/FileCard.vue (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/index.type.ts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
🧬 Code Graph Analysis (4)
packages/components/src/attachments/index.ts (1)
packages/components/src/index.ts (3)
  • Attachments (59-59)
  • Attachments (60-60)
  • install (50-55)
packages/components/src/attachments/composables/useListType.ts (2)
packages/components/src/attachments/composables/index.ts (1)
  • useListType (6-6)
packages/components/src/attachments/index.type.ts (2)
  • Attachment (11-20)
  • DisplayVariant (9-9)
packages/components/src/attachments/composables/useImagePreview.ts (2)
packages/components/src/attachments/index.type.ts (2)
  • Attachment (11-20)
  • AttachmentListEmits (45-52)
packages/components/src/attachments/composables/index.ts (1)
  • useImagePreview (6-6)
packages/components/src/attachments/composables/useFileType.ts (2)
packages/components/src/attachments/index.type.ts (1)
  • FileType (3-3)
packages/components/src/attachments/composables/index.ts (1)
  • useFileType (6-6)
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

9-9: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


31-31: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (23)
docs/.vitepress/config.mts (1)

67-67: LGTM! Clean integration with existing sidebar structure.

The new attachments component entry follows the established pattern with bilingual text and consistent formatting.

packages/components/src/index.ts (2)

4-4: LGTM! Consistent component import pattern.

The import follows the established convention for component imports in this library.


31-31: LGTM! Proper component registration.

The component is correctly added to the components array for automatic registration.

packages/components/src/attachments/components/index.ts (1)

1-2: LGTM! Clean barrel export pattern.

The component exports follow the standard pattern for creating a consolidated entry point for related components.

packages/components/src/attachments/composables/index.ts (1)

1-6: LGTM! Well-organized composables barrel export.

The composables are properly imported and re-exported, providing a clean single entry point for related functionality.

packages/components/src/attachments/index.ts (4)

1-2: LGTM! Standard Vue plugin imports.

The imports follow the established pattern for Vue component plugin setup.


4-4: LGTM! Consistent naming convention.

The component name TrAttachments follows the established naming pattern used by other components in the library.


6-8: LGTM! Standard Vue plugin install function.

The install function properly registers the component globally using the component's name property.


10-12: LGTM! Correct plugin setup and TypeScript typing.

The install method is properly attached to the component, and the TypeScript type assertion correctly extends the component type with the install method.

packages/components/src/attachments/composables/useListType.ts (2)

10-33: Well-implemented auto-detection logic.

The composable correctly handles the three scenarios:

  1. Explicit list type specification (non-auto)
  2. Empty file list fallback to card mode
  3. Automatic detection based on file content

The computed property ensures reactivity when the file list changes.


23-23: Image detection logic validated.

  • Confirmed in packages/components/src/attachments/index.type.ts that FileType is defined as
    'image' | 'pdf' | 'word' | 'excel' | 'ppt' | 'folder' | 'other'.
  • Checking file.fileType === 'image' for picture mode aligns with this enum and intended behavior.

No changes required.

docs/demos/attachments/status.vue (2)

52-52: External CDN URL used for demo preview.

The demo uses an external CDN URL for the preview image. This is acceptable for demonstration purposes, but ensure this URL remains accessible or consider using a more stable demo asset.


21-43: Good coverage of different file statuses.

The demo effectively demonstrates various file statuses (success, uploading, error) which helps users understand how the component handles different states and the retry functionality.

docs/demos/attachments/wrap.vue (2)

27-27: Placeholder preview URLs are acceptable for demo purposes.

The demo uses placeholder paths like /path/to/preview.docx which won't work in practice, but this is acceptable for demonstration purposes since the focus is on the wrap functionality rather than actual file preview.

Also applies to: 35-35, 43-43, 51-51, 59-59


4-7: Clear and intuitive wrap toggle control.

The radio group provides an intuitive way to demonstrate the wrap functionality with clear labels.

packages/components/src/attachments/composables/useFileCard.ts (2)

9-17: LGTM - Clean download implementation.

The download trigger function correctly creates a temporary link element and handles the download process with proper cleanup.


99-104: Excellent memory management with blob URL cleanup.

The automatic cleanup of blob URLs on component unmount prevents memory leaks, which is crucial for file handling components.

docs/demos/attachments/picture-list.vue (1)

1-27: Well-structured demo showcasing component features.

The demo effectively demonstrates the different display modes and configurations of the attachments component with clear explanations.

packages/components/src/attachments/composables/useImagePreview.ts (1)

73-83: Clean implementation of the render function.

The conditional rendering using Vue's h function is well-implemented and handles the preview component lifecycle correctly.

docs/src/components/attachments.md (2)

33-38: Excellent comprehensive status mode documentation.

The table clearly explains the different status modes and their behaviors, which will be very helpful for users understanding the component.


44-89: Comprehensive API documentation.

The props, events, and type definitions are well-documented with clear explanations and examples.

packages/components/src/attachments/index.vue (2)

76-93: Clean template structure with proper event handling.

The template effectively renders the file cards with all necessary props and event handlers. The conditional rendering and event binding are well-structured.


100-133: Well-organized styles with flexible layout options.

The CSS provides good support for both wrapping and non-wrapping layouts with proper scrolling behavior.

Comment thread packages/components/src/index.ts
Comment thread packages/components/src/attachments/composables/useFileCard.ts Outdated
Comment thread packages/components/src/attachments/composables/useImagePreview.ts
Comment thread docs/src/components/attachments.md Outdated
Comment thread docs/src/components/attachments.md Outdated
Comment thread packages/components/src/attachments/components/ImagePreview.vue
Comment thread packages/components/src/attachments/composables/useFileType.ts Outdated
Comment thread packages/components/src/attachments/composables/useFileType.ts Outdated
Comment thread packages/components/src/attachments/components/FileCard.vue Outdated
Comment thread packages/components/src/attachments/components/FileCard.vue Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
docs/src/components/attachments.md (1)

31-31: Convert emphasized text to proper heading.

🧹 Nitpick comments (4)
packages/components/src/attachments/components/FileCard.vue (3)

73-73: Add accessibility improvements for the close button.

The close button has basic aria-label but could benefit from additional accessibility enhancements for better screen reader experience.

Consider enhancing the accessibility:

-    <button v-if="!disabled" class="tr-file-card__close-btn" @click.stop="handleRemove" aria-label="移除文件">
+    <button 
+      v-if="!disabled" 
+      class="tr-file-card__close-btn" 
+      @click.stop="handleRemove" 
+      aria-label="移除文件"
+      type="button"
+      :aria-describedby="`file-${file.id}-name`"
+    >

And add an id to the file name:

-            <div class="tr-file-card__name" :title="file.name">
+            <div class="tr-file-card__name" :title="file.name" :id="`file-${file.id}-name`">

94-98: Consider extracting loading text logic to computed property.

The conditional loading text rendering could be more maintainable if extracted to a computed property, especially since this pattern might be reused.

+const loadingDisplayText = computed(() => {
+  return isUploading.value && props.file.uploadTimeoutText 
+    ? props.file.uploadTimeoutText 
+    : null
+})

// In template:
-            <span v-if="file.uploadTimeoutText" class="tr-file-card__status-icon--loading-text">
-              {{ file.uploadTimeoutText }}
-            </span>
+            <span v-if="loadingDisplayText" class="tr-file-card__status-icon--loading-text">
+              {{ loadingDisplayText }}
+            </span>

243-265: Potential CSS specificity issues with nested selectors.

The close button visibility logic uses nested selectors that might cause specificity conflicts. Consider using CSS custom properties or data attributes for cleaner state management.

Alternative approach using data attributes:

  &__close-btn {
    position: absolute;
    top: -8px;
    right: -8px;
    z-index: 20;
    border: none;
    background: transparent;
    cursor: pointer;
    border-radius: 50%;
    transition: opacity 0.2s;
    opacity: 0;

-    // 悬浮时显示
-    .tr-file-card:hover &,
-    .tr-file-card--picture:hover & {
-      opacity: 1;
-    }
-
-    // 成功状态始终显示
-    .tr-file-card--success & {
-      opacity: 1;
-    }
+    [data-card-state="hover"] &,
+    [data-card-state="success"] & {
+      opacity: 1;
+    }
  }

Then add data attributes to the main div in the template.

docs/src/components/attachments.md (1)

83-88: Event documentation could be more detailed.

The events table lacks important details about when events are triggered and what developers should expect.

Consider expanding the event descriptions:

| 事件名        | 参数类型                                     | 说明                     |
| ------------- | -------------------------------------------- | ------------------------ |
-| update:items  | `Attachment[]`                               | 附件列表更新时触发       |
-| file-remove   | `Attachment`                                 | 文件被移除时触发         |
-| file-download   | `Attachment`                               | 文件下载时触发         |
-| file-preview  | `Attachment`                                 | 文件预览时触发           |
-| file-retry    | `Attachment`                                 | 文件重试上传时触发       |
-| action        | `{ action: ActionButton, file: Attachment }` | 自定义操作按钮点击时触发 |
+| update:items  | `Attachment[]`                               | 附件列表更新时触发(用于v-model双向绑定)     |
+| file-remove   | `Attachment`                                 | 用户点击移除按钮时触发                       |
+| file-download | `Attachment`                                 | 用户点击下载操作时触发                       |
+| file-preview  | `Attachment`                                 | 用户点击预览操作时触发(仅图片类型)         |
+| file-retry    | `Attachment`                                 | 用户点击重试按钮时触发(仅error状态)        |
+| action        | `{ action: ActionButton, file: Attachment }` | 自定义操作按钮点击时触发(statusMode='actions'时) |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efbb81e and 7b6f90a.

📒 Files selected for processing (5)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/components/FileCard.vue (1 hunks)
  • packages/components/src/attachments/composables/useFileType.ts (1 hunks)
  • packages/components/src/attachments/index.vue (1 hunks)
  • packages/components/src/index.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/components/src/attachments/index.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/components/src/index.ts
  • packages/components/src/attachments/composables/useFileType.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
packages/components/src/attachments/components/FileCard.vue (2)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
Learnt from: gene9831
PR: opentiny/tiny-robot#59
File: packages/components/src/suggestion-popover/index.vue:131-133
Timestamp: 2025-05-27T03:35:11.008Z
Learning: In the SuggestionPopover component (packages/components/src/suggestion-popover/index.vue), the click handler can be bound unconditionally because the `show` computed property has a custom setter that prevents state mutations when `props.trigger === 'manual'`. This design centralizes trigger mode logic in the computed property rather than requiring conditional checks in event handlers.
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

31-31: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

Comment thread packages/components/src/attachments/components/FileCard.vue Outdated
Comment thread packages/components/src/attachments/components/FileCard.vue Outdated
Comment thread docs/src/components/attachments.md Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/src/components/attachments.md (1)

31-31: Convert the emphasised text to a proper heading.
The line is still formatted as bold text, which breaks the document’s heading hierarchy and fails MD036 again.

-**文件卡片状态显示模式**
+### 文件卡片状态显示模式
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6f90a and c7c6d1d.

⛔ Files ignored due to path filters (2)
  • packages/svgs/src/assets/arrow-left.svg is excluded by !**/*.svg
  • packages/svgs/src/assets/arrow-right.svg is excluded by !**/*.svg
📒 Files selected for processing (2)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/components/ImagePreview.vue (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/components/src/attachments/components/ImagePreview.vue
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

31-31: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

Comment thread docs/src/components/attachments.md Outdated
Comment thread packages/components/src/attachments/index.type.ts Outdated
Comment thread packages/components/src/attachments/index.type.ts Outdated
Comment thread packages/components/src/attachments/composables/useFileCard.ts Outdated
Comment thread packages/components/src/attachments/composables/useFileType.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
packages/components/src/attachments/composables/useFileType.ts (1)

161-166: Memory leak: Blob URLs are not cleaned up.

The createPreviewUrl function creates blob URLs that persist in memory until explicitly revoked, which can cause memory leaks when previewing many images.

This is a known issue from previous reviews. The consuming component should handle cleanup. Consider documenting this requirement:

  /**
   * 创建文件预览URL
+   * @returns 预览URL (需要在组件卸载时调用 URL.revokeObjectURL 清理)
   */
  const createPreviewUrl = (file: File): string => {
    if (file.type?.startsWith('image/')) {
      return URL.createObjectURL(file)
    }
    return ''
  }
🧹 Nitpick comments (10)
docs/demos/attachments/custom-download.vue (2)

64-64: Consider using a more user-friendly error handling approach.

Using alert() for error messages is not ideal for user experience in modern web applications.

Consider using a toast notification or inline error message instead:

-   alert(`下载失败: ${error.message}`)
+   console.error('下载失败:', error)
+   // TODO: Replace with proper error notification system
+   // Example: showToast({ type: 'error', message: `下载失败: ${error.message}` })

23-23: Consider adding comments about mock URLs.

The hard-coded URLs might confuse users about real implementation.

-   previewUrl: 'https://secure-api.example.com/files/secret-doc.pdf',
+   previewUrl: 'https://secure-api.example.com/files/secret-doc.pdf', // 仅供演示,实际使用时替换为真实API

Also applies to: 31-31

docs/demos/attachments/custom-file-type.vue (2)

22-24: Move icon creation outside setup for better performance.

Creating icons with h() function in setup can be inefficient as they're recreated on each component mount.

Consider defining icons as constants outside the component:

+const TextIcon = h('div', { style: { color: '#52c41a', fontSize: '20px' } }, '📄')
+const MDIcon = h('div', { style: { color: '#1890ff', fontSize: '20px' } }, '📝')
+const JsonIcon = h('div', { style: { color: '#fa8c16', fontSize: '20px' } }, '📊')
+
<script setup lang="ts">
import { ref, h } from 'vue'
import { TrAttachments } from '@opentiny/tiny-robot'
import type { Attachment, FileTypeMatcher } from '@opentiny/tiny-robot'

-// 自定义图标组件
-const TextIcon = h('div', { style: { color: '#52c41a', fontSize: '20px' } }, '📄')
-const MDIcon = h('div', { style: { color: '#1890ff', fontSize: '20px' } }, '📝')
-const JsonIcon = h('div', { style: { color: '#fa8c16', fontSize: '20px' } }, '📊')

93-95: Add file validation before processing.

The code doesn't validate the selected file before processing it.

  if (files && files.length > 0) {
    const file = files[0]
+   
+   // 验证文件大小和类型
+   if (file.size > 10 * 1024 * 1024) { // 10MB limit
+     alert('文件大小不能超过10MB')
+     return
+   }
docs/src/components/attachments.md (1)

80-80: Improve description clarity for actions prop.

The current description could be clearer about when and how the actions prop works.

-| actions          | `ActionButton[]`              | 图片默认: `['preview', 'download']` | 自定义操作按钮,详见 [自定义操作按钮](#自定义操作按钮-actions)。 |
+| actions          | `ActionButton[]`              | 图片默认提供预览和下载按钮 | 自定义操作按钮列表,详见 [自定义操作按钮](#自定义操作按钮-actions)。 |
packages/components/src/attachments/composables/useFileType.ts (2)

140-142: Use a more robust ID generation method.

Math.random() is not cryptographically secure and may not guarantee uniqueness in high-concurrency scenarios.

Consider using a more robust approach:

  const generateID = (): string => {
-   return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
+   return `${Date.now()}_${Math.random().toString(36).substring(2, 15)}`
  }

Or better yet, use crypto.randomUUID() if available:

  const generateID = (): string => {
+   if (typeof crypto !== 'undefined' && crypto.randomUUID) {
+     return crypto.randomUUID()
+   }
    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
  }

32-34: Improve file extension detection robustness.

The current extension detection might fail with files that have no extension or multiple dots.

-     const extension = file.split('.').pop()?.toLowerCase() || ''
+     const extension = file.toLowerCase().split('.').pop() || ''

This ensures the entire filename is lowercased before splitting, making the detection more reliable.

packages/components/src/attachments/index.type.ts (3)

21-23: Add JSDoc documentation for DownloadHandler.

The DownloadHandler interface lacks documentation about its expected behavior.

-// 下载处理器
+/**
+ * 自定义下载处理器
+ * @param file 要下载的附件对象
+ * @returns Promise<void> 或 void,用于处理异步或同步下载逻辑
+ */
export interface DownloadHandler {
  (file: Attachment): Promise<void> | void
}

13-18: Add comprehensive JSDoc for FileTypeMatcher.

The FileTypeMatcher interface needs better documentation for its properties and usage.

-// 文件类型匹配器
+/**
+ * 文件类型匹配器,用于自定义文件类型检测
+ */
export interface FileTypeMatcher {
+  /** 文件类型标识符 */
  type: string
+  /** 匹配函数,接收 File 对象或文件名字符串,返回是否匹配 */
  matcher: (file: File | string) => boolean
+  /** 该文件类型对应的图标组件 */
  icon?: Component
-  priority?: number // 优先级,数字越大优先级越高
+  /** 优先级,数字越大优先级越高,默认为 0 */
+  priority?: number
}

25-34: Add JSDoc documentation for Attachment interface.

The core Attachment interface needs comprehensive documentation for all its properties.

+/**
+ * 附件对象,描述一个文件的完整信息
+ */
export interface Attachment {
+  /** 唯一标识符 */
  id: string
+  /** 文件名 */
  name: string
+  /** 文件状态 */
  status: FileStatus
+  /** 预览URL,主要用于图片预览 */
  previewUrl?: string
+  /** 文件类型 */
  fileType?: FileType
+  /** 文件大小(字节) */
  size?: number
+  /** 上传超时提示文本 */
  uploadTimeoutText?: string
-  rawFile?: File // 原始文件对象,用于下载和预览
+  /** 原始文件对象,用于下载和预览 */
+  rawFile?: File
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abb1ded and 71f5766.

📒 Files selected for processing (12)
  • docs/demos/attachments/basic.vue (1 hunks)
  • docs/demos/attachments/custom-download.vue (1 hunks)
  • docs/demos/attachments/custom-file-type.vue (1 hunks)
  • docs/demos/attachments/picture-list.vue (1 hunks)
  • docs/demos/attachments/status.vue (1 hunks)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/components/FileCard.vue (1 hunks)
  • packages/components/src/attachments/composables/useFileCard.ts (1 hunks)
  • packages/components/src/attachments/composables/useFileType.ts (1 hunks)
  • packages/components/src/attachments/composables/useImagePreview.ts (1 hunks)
  • packages/components/src/attachments/index.type.ts (1 hunks)
  • packages/components/src/attachments/index.vue (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/components/src/attachments/components/FileCard.vue
🚧 Files skipped from review as they are similar to previous changes (6)
  • docs/demos/attachments/status.vue
  • docs/demos/attachments/picture-list.vue
  • packages/components/src/attachments/composables/useFileCard.ts
  • packages/components/src/attachments/composables/useImagePreview.ts
  • docs/demos/attachments/basic.vue
  • packages/components/src/attachments/index.vue
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/demos/attachments/custom-file-type.vue (2)
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/composables/useFileType.ts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/index.type.ts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
🧬 Code Graph Analysis (1)
packages/components/src/attachments/composables/useFileType.ts (2)
packages/components/src/attachments/index.type.ts (4)
  • BaseFileType (4-4)
  • FileTypeMatcher (13-18)
  • FileType (6-6)
  • FileStatus (8-8)
packages/components/src/attachments/composables/index.ts (1)
  • useFileType (6-6)
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

79-79: Link fragments should be valid

(MD051, link-fragments)

🔇 Additional comments (2)
packages/components/src/attachments/composables/useFileType.ts (1)

171-181: LGTM: Status parameter properly addressed.

The createAttachments function now properly accepts a defaultStatus parameter, addressing the previous review comment about hardcoded status.

packages/components/src/attachments/index.type.ts (1)

1-93: LGTM: Comprehensive type system addresses past feedback.

The type definitions properly address previous review comments:

  • Custom file type support is implemented via FileTypeMatcher and customMatchers
  • The types are well-structured without unnecessary prefixes
  • The component properly separates concerns with different interfaces for different use cases

Comment thread docs/demos/attachments/custom-download.vue Outdated
Comment thread docs/demos/attachments/custom-file-type.vue Outdated
Comment thread docs/src/components/attachments.md Outdated
Comment thread docs/src/components/attachments.md Outdated
@hexqi hexqi added this to the v0.3.x milestone Jul 17, 2025
@hexqi hexqi changed the base branch from release/v0.2.x to develop July 17, 2025 08:05
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
docs/src/components/attachments.md (1)

81-81: Fix invalid link fragment reference.

The link fragment #展示形式-variant contains a hyphen that makes it invalid for markdown navigation.

-| variant         | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式-variant)。                      |
+| variant         | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式variant)。                      |
🧹 Nitpick comments (2)
docs/demos/attachments/basic.vue (1)

22-40: Consider using more realistic file sizes in the demo.

All files in the demo use the same size (3.5MB), which makes the demo less realistic. Consider varying the file sizes to better demonstrate the component's capabilities.

const infoFiles = ref<Attachment[]>([
  {
-    size: 1024 * 1024 * 3.5, // 3.5MB
+    size: 1024 * 1024 * 2.1, // 2.1MB
    url: 'https://tinyengine-assets.obs.cn-north-4.myhuaweicloud.com/files/images/icons-250711.rar',
  },
])

const pictureFiles = ref<Attachment[]>([
  {
-    size: 1024 * 1024 * 3.5, // 3.5MB
+    size: 1024 * 512, // 512KB
    url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/fruit.jpg',
  },
  {
-    size: 1024 * 1024 * 3.5, // 3.5MB
+    size: 1024 * 256, // 256KB
    url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/book.jpg',
  },
  {
-    size: 1024 * 1024 * 3.5, // 3.5MB
+    size: 1024 * 384, // 384KB
    url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/fruit.jpg',
  },
])
docs/demos/attachments/download.vue (1)

25-32: Consider using varied file sizes for better demonstration.

Similar to the basic demo, all files use identical sizes. Using different sizes would make the demo more realistic and showcase the size display feature better.

const attachments = ref<Attachment[]>([
  {
-    size: 1024 * 1024 * 3.5, // 3.5MB
+    size: 1024 * 1024 * 1.8, // 1.8MB
    url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/fruit.jpg',
  },
  {
-    size: 1024 * 1024 * 3.5, // 3.5MB
+    size: 1024 * 768, // 768KB
    url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/fruit.jpg',
  },
])
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 908732c and a5610ee.

📒 Files selected for processing (14)
  • docs/demos/attachments/basic.vue (1 hunks)
  • docs/demos/attachments/custom-file-type.vue (1 hunks)
  • docs/demos/attachments/custom-icon.vue (1 hunks)
  • docs/demos/attachments/download.vue (1 hunks)
  • docs/demos/attachments/picture-list.vue (1 hunks)
  • docs/demos/attachments/status.vue (1 hunks)
  • docs/demos/attachments/wrap.vue (1 hunks)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/components/FileCard.vue (1 hunks)
  • packages/components/src/attachments/composables/useFileCard.ts (1 hunks)
  • packages/components/src/attachments/composables/useFileType.ts (1 hunks)
  • packages/components/src/attachments/index.type.ts (1 hunks)
  • packages/components/src/attachments/index.vue (1 hunks)
  • packages/components/src/index.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/demos/attachments/status.vue
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/components/src/index.ts
  • docs/demos/attachments/custom-icon.vue
  • docs/demos/attachments/picture-list.vue
  • docs/demos/attachments/custom-file-type.vue
  • packages/components/src/attachments/composables/useFileCard.ts
  • packages/components/src/attachments/index.vue
  • packages/components/src/attachments/components/FileCard.vue
  • docs/demos/attachments/wrap.vue
  • packages/components/src/attachments/index.type.ts
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/demos/attachments/basic.vue (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/demos/attachments/download.vue (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/composables/useFileType.ts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
🧬 Code Graph Analysis (1)
packages/components/src/attachments/composables/useFileType.ts (2)
packages/components/src/attachments/index.type.ts (4)
  • BaseFileType (4-4)
  • FileTypeMatcher (13-17)
  • FileType (6-6)
  • Attachment (19-39)
packages/components/src/attachments/composables/index.ts (1)
  • useFileType (6-6)
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

81-81: Link fragments should be valid

(MD051, link-fragments)

🔇 Additional comments (5)
docs/demos/attachments/basic.vue (1)

1-17: Well-structured basic demo component.

The component structure follows Vue 3 Composition API best practices with proper TypeScript integration and clear component usage examples.

docs/demos/attachments/download.vue (1)

36-52: Excellent file handling and event demonstration.

The file input handling and download event handler are well-implemented, properly demonstrating the component's capabilities for both local and remote file management.

docs/src/components/attachments.md (1)

1-151: Comprehensive and well-structured documentation.

The documentation provides thorough coverage of the Attachments component's API, usage patterns, and customization options. The examples and type definitions are clear and helpful for developers.

packages/components/src/attachments/composables/useFileType.ts (2)

193-271: Well-designed composable with comprehensive file type utilities.

The composable provides a solid foundation for file type detection and attachment handling with good separation of concerns and extensibility through custom matchers and icons.


251-262: Blob URL cleanup already implemented

The useFileCard.ts composable creates blob URLs with URL.createObjectURL and automatically revokes them in an onUnmounted hook (lines 109–114), so there’s no memory-leak risk. No further action or documentation is needed.

Comment thread docs/src/components/attachments.md Outdated
Comment thread packages/components/src/attachments/components/FileCard.vue Outdated
Comment thread packages/components/src/attachments/composables/useFileType.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
docs/src/components/attachments.md (1)

83-83: Fix invalid link fragment reference.

The link fragment contains an invalid character that will prevent proper navigation.

-| variant         | `'picture' \| 'card' \| 'auto'`     | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式-variant)。                      |
+| variant         | `'picture' \| 'card' \| 'auto'`     | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式variant)。                      |
packages/components/src/attachments/components/FileCard.vue (2)

68-68: Hardcoded Chinese text needs internationalization support.

The component contains hardcoded Chinese text that limits its reusability in international contexts.

Consider making these texts configurable through props or using an i18n solution:

-        <div v-if="isUploadSuccess" class="tr-file-card__picture-overlay">
-          <span class="tr-file-card__picture-overlay-text">预览</span>
-        </div>
+        <div v-if="isUploadSuccess" class="tr-file-card__picture-overlay">
+          <span class="tr-file-card__picture-overlay-text">{{ previewText || '预览' }}</span>
+        </div>

-              <div v-else-if="isUploading" class="tr-file-card__status-uploading">
-                <span class="tr-file-card__uploading-text">上传中...</span>
-              </div>
+              <div v-else-if="isUploading" class="tr-file-card__status-uploading">
+                <span class="tr-file-card__uploading-text">{{ uploadingText || '上传中...' }}</span>
+              </div>

-              <div v-else-if="isUploadFailed" class="tr-file-card__status-error">
-                <span class="tr-file-card__error-text">上传失败</span>
-                <button class="tr-file-card__retry-btn" @click="handleRetry">重试</button>
+              <div v-else-if="isUploadFailed" class="tr-file-card__status-error">
+                <span class="tr-file-card__error-text">{{ uploadFailedText || '上传失败' }}</span>
+                <button class="tr-file-card__retry-btn" @click="handleRetry">{{ retryText || '重试' }}</button>

Also applies to: 171-171, 176-177


148-165: Action buttons lack accessibility attributes.

The action buttons need proper accessibility support for keyboard navigation and screen readers.

                <div v-if="actions" class="tr-file-card__actions">
-                  <div v-for="(action, index) in actions" :key="index" class="tr-file-card__action-btn">
+                  <div v-for="(action, index) in actions" :key="index" class="tr-file-card__action-btn">
                    <a
                      v-if="action.type === 'download'"
                      class="tr-file-card__action-btn--download"
                      :href="file.url ?? 'javascript:void(0)'"
                      :target="file.url ? '_blank' : '_self'"
+                     :aria-label="`${action.label} ${file.name}`"
                      @click="handleCustomAction(action, $event)"
                    >
                      {{ action.label }}</a
                    >
                    <button
                      v-else
+                     type="button"
                      :class="`tr-file-card__action-btn--${action.type}`"
+                     :aria-label="`${action.label} ${file.name}`"
+                     @keydown.enter="handleCustomAction(action, $event)"
+                     @keydown.space.prevent="handleCustomAction(action, $event)"
                      @click="handleCustomAction(action, $event)"
                    >
                      {{ action.label }}
                    </button>
                  </div>
                </div>
🧹 Nitpick comments (1)
docs/src/components/attachments.md (1)

138-140: Fix inconsistent event parameter type documentation.

The event parameter types are inconsistently documented. Some show function signatures while others show parameter types.

-| preview      | `(file: Attachment) => void`                 | 点击内置预览按钮时触发。   |
-| retry        | `(file: Attachment) => void`                 | 点击重试按钮时触发。       |
+| preview      | `Attachment`                                 | 点击内置预览按钮时触发。   |
+| retry        | `Attachment`                                 | 点击重试按钮时触发。       |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5610ee and d23d134.

📒 Files selected for processing (7)
  • docs/demos/attachments/download.vue (1 hunks)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/components/FileCard.vue (1 hunks)
  • packages/components/src/attachments/composables/useFileCard.ts (1 hunks)
  • packages/components/src/attachments/composables/useFileType.ts (1 hunks)
  • packages/components/src/attachments/index.type.ts (1 hunks)
  • packages/components/src/attachments/index.vue (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/components/src/attachments/composables/useFileCard.ts
  • docs/demos/attachments/download.vue
  • packages/components/src/attachments/index.vue
  • packages/components/src/attachments/index.type.ts
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
packages/components/src/attachments/composables/useFileType.ts (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
packages/components/src/attachments/components/FileCard.vue (2)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
Learnt from: gene9831
PR: opentiny/tiny-robot#59
File: packages/components/src/suggestion-popover/index.vue:131-133
Timestamp: 2025-05-27T03:35:11.008Z
Learning: In the SuggestionPopover component (packages/components/src/suggestion-popover/index.vue), the click handler can be bound unconditionally because the `show` computed property has a custom setter that prevents state mutations when `props.trigger === 'manual'`. This design centralizes trigger mode logic in the computed property rather than requiring conditional checks in event handlers.
🧬 Code Graph Analysis (1)
packages/components/src/attachments/composables/useFileType.ts (2)
packages/components/src/attachments/index.type.ts (6)
  • BaseFileType (4-4)
  • FileTypeMatcher (13-17)
  • FileType (6-6)
  • RawFileAttachment (36-40)
  • UrlAttachment (29-33)
  • Attachment (43-43)
packages/components/src/attachments/composables/index.ts (1)
  • useFileType (6-6)
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

83-83: Link fragments should be valid

(MD051, link-fragments)

🔇 Additional comments (5)
packages/components/src/attachments/composables/useFileType.ts (3)

25-99: Excellent file type detection implementation.

The default matchers provide comprehensive coverage for common file types with proper dual handling of File objects and filename strings. The consistent pattern and thorough MIME type + extension matching ensures reliable file type detection.


134-156: Well-designed icon resolution with proper fallback chain.

The icon component resolution follows a logical priority order and properly returns a computed reference for Vue 3 reactivity. The fallback chain ensures there's always a valid icon component.


210-264: Robust attachment normalization with good type safety.

The normalization logic properly handles different input types with appropriate type guards and transformation functions. The common properties extraction and type-specific transformations create a clean API for handling various attachment sources.

packages/components/src/attachments/components/FileCard.vue (2)

8-46: Well-structured component setup with proper composable usage.

The component properly leverages composables for file type detection and card interactions, uses appropriate computed properties for reactive state management, and has clean prop definitions with sensible defaults.


188-550: Comprehensive and well-organized component styles.

The LESS styles provide excellent coverage for both card variants with proper hover states, transitions, and responsive behavior. The nested structure is clean and maintainable, with appropriate use of CSS variables and good visual hierarchy.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/src/components/attachments.md (1)

83-83: Fix invalid link fragment reference.

The link fragment #展示形式-variant contains an invalid character (hyphen) and will not work properly for navigation.

-| variant         | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式-variant)。                      |
+| variant         | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式variant)。                      |
🧹 Nitpick comments (1)
docs/demos/attachments/basic.vue (1)

20-33: Consider using more diverse sample data and local fallbacks.

While the current implementation works, consider these improvements:

  1. URL duplication: Lines 23 and 31 use the same image URL, which doesn't showcase variety
  2. External dependency: All images rely on external CDN which could be unavailable during development
  3. Missing required fields: The Attachment objects lack id, name, and status fields that appear to be required based on the documentation

Consider this improved sample data:

const basicFiles = ref<Attachment[]>([
  {
+   id: '1',
+   name: 'fruit.jpg',
+   status: 'success',
    size: 1024 * 1024 * 3.5, // 3.5MB
    url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/fruit.jpg',
  },
  {
+   id: '2',
+   name: 'book.jpg',
+   status: 'success',
    size: 1024 * 1024 * 3.5, // 3.5MB
    url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/book.jpg',
  },
  {
+   id: '3',
+   name: 'landscape.jpg',
+   status: 'success',
-   size: 1024 * 1024 * 3.5, // 3.5MB
+   size: 1024 * 1024 * 2.1, // 2.1MB
-   url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/fruit.jpg',
+   url: 'https://res.hc-cdn.com/tiny-vue-web-doc/3.23.0.20250521142915/static/images/landscape.jpg',
  },
])
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d23d134 and ce8012d.

📒 Files selected for processing (4)
  • docs/demos/attachments/basic.vue (1 hunks)
  • docs/demos/attachments/download.vue (1 hunks)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/composables/useFileCard.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/components/src/attachments/composables/useFileCard.ts
  • docs/demos/attachments/download.vue
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/demos/attachments/basic.vue (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

83-83: Link fragments should be valid

(MD051, link-fragments)

🔇 Additional comments (6)
docs/demos/attachments/basic.vue (2)

1-12: Template structure looks clean and demonstrates key variants effectively.

The template clearly showcases both the 'card' variant and the default behavior of the TrAttachments component with proper semantic HTML structure.


14-17: LGTM: Proper TypeScript imports and component setup.

The imports are correctly structured using the composition API and TypeScript support with proper type importing.

docs/src/components/attachments.md (4)

47-51: Clarify the local vs network file download distinction.

Based on the retrieved learning from gene9831's comment, there seems to be confusion about the local vs network file download descriptions. The current explanation may be misleading.

The documentation states that local files (with rawFile) use automatic download while network files (with url) trigger events. However, this distinction needs clarification - typically both scenarios might need custom handling depending on the security context and user requirements.

Can you verify if this download behavior accurately reflects the actual implementation in the component?


74-87: API documentation is comprehensive and well-structured.

The Props table provides clear descriptions, appropriate types, and helpful cross-references to related sections. The default values and explanations are informative for developers.


94-129: TypeScript interfaces are well-defined and documented.

The type definitions for Attachment, ActionButton, and FileTypeMatcher are comprehensive with clear field descriptions and appropriate type annotations. This provides excellent developer experience with strong typing.


142-153: Built-in file types section provides good reference.

The list of supported file types with their extensions gives developers a clear understanding of what's supported out-of-the-box and sets expectations for when custom matchers might be needed.

Comment thread docs/src/components/attachments.md Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
docs/src/components/attachments.md (1)

83-83: Fix invalid link fragment #展示形式-variant.

markdownlint (MD051) still flags this anchor as invalid.
The heading is ### 展示形式(variant), whose automatic slug becomes #展示形式variant (parentheses are stripped, no dash). Update the link to keep in-page navigation working.

-| variant      | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式-variant)。                    |
+| variant      | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式variant)。                    |
🧹 Nitpick comments (2)
docs/src/components/attachments.md (2)

78-86: Keep the “默认值” cell pure; move explanatory text to the “说明” cell.

Placing “图片默认: …” in the 默认值 column mixes default-value data with explanatory prose, reducing table clarity. Shift the note into the 说明 column and leave the 默认值 cell as `-` (or the literal default if one exists).

-| actions      | `ActionButton[]`                | 图片默认: `['preview', 'download']` | 自定义操作按钮,详见 [自定义操作按钮](#自定义操作按钮-actions)。            |
+| actions      | `ActionButton[]`                | -                                   | 自定义操作按钮(图片文件默认显示 `下载 | 预览`),详见 [自定义操作按钮](#自定义操作按钮-actions)。 |

47-52: Tighten wording and typography around download behaviour.

The blockquote currently reads “本地文件 和 网络文件 的下载方式不同”,with extra spaces around /. Consider rephrasing for smoother Chinese copy and to match the bullet titles below, e.g.:

-> 本地文件 和 网络文件 的下载方式不同
+> 本地文件与网络文件的下载方式不同
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce8012d and a3fb707.

📒 Files selected for processing (1)
  • docs/src/components/attachments.md (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/src/components/attachments.md (1)
Learnt from: SonyLeo
PR: opentiny/tiny-robot#119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。
🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

83-83: Link fragments should be valid

(MD051, link-fragments)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/src/components/attachments.md (1)

83-85: Invalid anchor fragment – previous feedback still unresolved.

The link target #展示形式-variant is still invalid, causing broken navigation. Please update the fragment to match the actual heading slug (likely #展示形式variant).

-| variant      | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式-variant)。                    |
+| variant      | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式variant)。                    |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9730fc5 and 47b7b08.

📒 Files selected for processing (7)
  • docs/demos/attachments/download.vue (1 hunks)
  • docs/src/components/attachments.md (1 hunks)
  • packages/components/src/attachments/components/FileCard.vue (1 hunks)
  • packages/components/src/attachments/composables/useFileCard.ts (1 hunks)
  • packages/components/src/attachments/composables/useImagePreview.ts (1 hunks)
  • packages/components/src/attachments/index.type.ts (1 hunks)
  • packages/components/src/attachments/index.vue (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/components/src/attachments/index.type.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/demos/attachments/download.vue
  • packages/components/src/attachments/composables/useFileCard.ts
  • packages/components/src/attachments/composables/useImagePreview.ts
  • packages/components/src/attachments/index.vue
  • packages/components/src/attachments/components/FileCard.vue
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/src/components/attachments.md (1)

Learnt from: SonyLeo
PR: #119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。

🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

84-84: Link fragments should be valid

(MD051, link-fragments)

Comment thread docs/src/components/attachments.md
Comment thread docs/src/components/attachments.md Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
docs/src/components/attachments.md (1)

84-84: Fix invalid link fragment for “variant” heading.

The anchor #展示形式-variant does not exist – GitHub’s slug for ### 展示形式(variant) is #展示形式variant (no hyphen). Broken links hurt navigation.

-| variant      | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式-variant)。                    |
+| variant      | `'picture' \| 'card' \| 'auto'` | `'auto'`                            | 附件列表的展示形式,详见 [展示形式](#展示形式variant)。                    |
🧹 Nitpick comments (1)
docs/src/components/attachments.md (1)

47-53: Streamline the “预览和下载” note for readability.

The double-space around “本地文件 和 网络文件” and split sentences make the note a bit choppy. Tightening the phrasing improves flow without changing meaning.

-> 本地文件 和 网络文件 的下载方式不同
+> 本地文件与网络文件的下载方式不同
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47b7b08 and 4701f22.

📒 Files selected for processing (1)
  • docs/src/components/attachments.md (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gene9831
PR: opentiny/tiny-robot#123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
docs/src/components/attachments.md (3)

Learnt from: SonyLeo
PR: #119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。

Learnt from: gene9831
PR: #59
File: packages/components/src/suggestion-popover/index.vue:0-0
Timestamp: 2025-05-27T03:45:56.392Z
Learning: In Vue components using VueUse's onClickOutside, calling stopPropagation() in the onClickOutside handler can prevent subsequent click event handlers on the same element from being triggered. This means that clicking a trigger element while a popover is open can successfully close the popover without immediately reopening it, even when the onClickOutside only listens to the popover element and not the trigger element.

Learnt from: gene9831
PR: #59
File: packages/components/src/suggestion-popover/index.vue:131-133
Timestamp: 2025-05-27T03:35:11.008Z
Learning: In the SuggestionPopover component (packages/components/src/suggestion-popover/index.vue), the click handler can be bound unconditionally because the show computed property has a custom setter that prevents state mutations when props.trigger === 'manual'. This design centralizes trigger mode logic in the computed property rather than requiring conditional checks in event handlers.

🪛 markdownlint-cli2 (0.17.2)
docs/src/components/attachments.md

84-84: Link fragments should be valid

(MD051, link-fragments)

@hexqi hexqi merged commit 971997a into opentiny:develop Jul 25, 2025
@SonyLeo SonyLeo deleted the feat/attachments branch August 4, 2025 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants