Attachments

فایل‌هایی که کنار یک پیام آماده شده‌اند: <r-attachments> فهرست را نگه می‌دارد، پیش‌نمایش می‌سازد، آنچه را وارد می‌شود اعتبارسنجی می‌کند و object URLهایی را که می‌سازد خودش آزاد می‌کند.

کجا به کار می‌آید: وقتی یک ناحیه نگارش باید نشان دهد چه چیزی در آستانه ارسال است. خودش فایل جمع نمی‌کند؛ چسباندن، کشیدن و رها کردن، و انتخابگر فایل سه حرکت جداگانه‌اند، هر کدام متعلق به عنصر دیگری از ناحیه نگارش، و اینکه برنامه شما کدام‌یک را ارائه کند تصمیم خود شماست. از همان جایی که سیم‌کشی می‌کنید add() را صدا بزنید.

شروع سریع

کاربرد پایه

<r-attachments accept="image/*,.pdf" max-size="5242880" max-count="4"></r-attachments>
const strip = document.createElement('r-attachments');

// یک انتخابگر فایل
picker.addEventListener('change', () => strip.add(picker.files));

// چسباندن — تنها وقتی کلیپ‌بورد واقعاً فایل دارد. گرفتن هر چسباندنی، چسباندن متن را
// خراب می‌کند، همان کاری که این جعبه بیشتر وقت‌ها برایش هست.
input.addEventListener('paste', (event) => {
  if (event.clipboardData?.files.length) {
    event.preventDefault();
    strip.add(event.clipboardData.files);
  }
});

// کشیدن و رها کردن
dropZone.addEventListener('drop', (event) => {
  event.preventDefault();
  strip.add(event.dataTransfer.files);
});

composer.append(strip);

نوار برای هر فایل یک ردیف می‌کشد: بندانگشتی (برای تصویرها)، نام، اندازه و یک دکمه حذف. مقدار count روی میزبان بازتاب می‌یابد و وقتی نوار خالی است به جای رسیدن به 0 برداشته می‌شود، تا یک نوار خالی جایی اشغال نکند:

r-attachments:not([count]) {
  display: none;
}

ارسال

const body = new FormData();
for (const file of strip.files) body.append('files', file);
await fetch('/api/messages', { method: 'POST', body });
strip.clear();

files تنها شیءهای File است، به ترتیب: همان شکلی که بدنه یک درخواست می‌خواهد. attachments فهرست پرمایه‌تر است (id، name، size، type، previewUrl) برای وقتی که بخواهید نمای خودتان را از همان وضعیت بکشید.

رد شدن اعلام می‌شود، هرگز در سکوت نمی‌ماند

فایلی که ناپدید می‌شود چون ۳ مگابایت از حدی که کسی نامش را نبرده بیشتر بوده، برای کاربر یعنی صفحه خراب است. هر رد شدن یک رویداد می‌فرستد با خود فایل و قاعده‌ای که شکسته است:

const explain = {
  'too-large': 'این فایل بزرگ‌تر از ۵ مگابایت است.',
  'type-not-accepted': 'این نوع فایل اینجا پذیرفته نمی‌شود.',
  'too-many': 'حداکثر می‌توانید ۴ فایل پیوست کنید.',
  duplicate: 'این فایل از قبل پیوست شده است.',
};

strip.addEventListener('attachmentrejected', (event) => {
  toast(explain[event.detail.reason]);
});

duplicate نام، اندازه و زمان آخرین تغییر را با هم می‌سنجد، درست همان‌طور که یک مدیر فایل دو فایل را یکی می‌شمارد. دوبار پیوست‌کردن یک فایل یک لغزش است، نه یک دستور.

مرجع API

ویژگی‌ها

ویژگی اتریبیوت نوع پیش‌فرض توضیح
accept accept string '' نوع‌ها یا پسوندهای جداشده با ویرگول، به شکلی که <input accept> می‌گیرد.
maxSize max-size number 10 MB بزرگ‌ترین فایل پذیرفته‌شده، بر حسب بایت.
maxCount max-count number بیشترین شمار فایل آماده در یک زمان؛ اگر تعیین نشود بدون سقف.
attachments readonly Attachment[] [] فایل‌های آماده، به ترتیب رسیدن.
files File[] [] تنها خود فایل‌ها، برای ساختن بدنه یک درخواست.
sheet sheet string '' CSSی که به shadow root تزریق می‌شود.

attachments و files نماهای فقط‌خواندنی‌اند. آماده‌سازی فایل با add() انجام می‌شود.

متدها

متد بازگشت توضیح
add(files) Attachment[] یک iterable از File را آماده می‌کند و پذیرفته‌شده‌ها را برمی‌گرداند.
detach(id) boolean پیوست را با id حذف می‌کند؛ اگر چنین idی نبود false.
clear() void همه را حذف و object URLهایشان را آزاد می‌کند.

رویدادها

رویداد detail انتشار توضیح
attachmentschange { attachments } bubbles، composed فهرست فایل‌های آماده تغییر کرد.
attachmentrejected { file, reason } bubbles، composed فایلی رد شد. reason یکی از too-large، type-not-accepted، too-many، duplicate است.

تایپ‌ها

interface Attachment {
  id: string; // تا وقتی این پیوست زنده است ثابت می‌ماند
  file: File;
  name: string;
  size: number;
  type: string;
  previewUrl: string | null; // برای تصویرها object URL، در بقیه موارد null
}

type AttachmentRejection = 'too-large' | 'type-not-accepted' | 'too-many' | 'duplicate';

Partها

list · attachment · thumb · icon · name · size · remove

پیش‌نمایش‌ها چطور کار می‌کنند

پیش‌نمایش‌ها object URL هستند، نه data URL. هزینه یک پیش‌نمایش تنها یک ارجاع به بایت‌هایی است که مرورگر همین حالا دارد؛ اما خواندن یک عکس ۱۰ مگابایتی به یک رشته base64 فقط برای نشان‌دادن یک بندانگشتی ۴۰ پیکسلی، هزینه‌اش خود آن رشته است. data URL را بعداً بسازید، یک بار، در همان جایی که واقعاً ارسال می‌کند.

هر URLی که این عنصر می‌سازد خودش هم آزاد می‌کند: هنگام جدا کردن یک پیوست، هنگام پاک‌کردن و هنگام قطع اتصال. previewUrl را فراتر از عمر پیوستش نگه ندارید.

دسترس‌پذیری

متن جایگزین یک بندانگشتی نام فایل است، نه «تصویر»: چهار پیوست که همه‌شان «تصویر» خوانده شوند، به خواننده هیچ نگفته‌اند که کدام کدام است. هر دکمه حذف هم به همین دلیل نام فایل خودش را با خود دارد.

استایل‌دهی

<r-attachments> ۱۷ ویژگی سفارشی CSS از آنِ خود دارد، به‌علاوه توکن‌های معنایی که از پوسته می‌خواند. هر جا که ارث برسد می‌توانید یکی را تعیین کنید: :root، یک دربرگیرنده، یا خود عنصر:

r-attachments {
  --ran-attachment-background: var(--ran-color-bg-subtle);
}

Partها: attachment · icon · list · name · remove · size · thumb

فهرست کامل در توکن‌های استایل است؛ اینکه سراغ کدام توکن بروید کار سیستم طراحی است.

بهترین شیوه‌ها

  • روی سرور هم اعتبارسنجی کنید. accept و max-size نوعی ادب در حق کسی است که پیوست می‌کند، نه مرزی امنیتی.
  • پس از ارسال موفق پاک کنید، نه پیش از آن. درخواستی که شکست می‌خورد باید فایل‌ها را آماده باقی بگذارد تا بشود دوباره تلاش کرد.
  • هر رد شدن را توضیح دهید. این رویداد هست تا نوار هرگز فایلی را در سکوت دور نیندازد.