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هایشان را آزاد میکند. |
اسمش detach(id) است، نه remove(id)
هر عنصر از پیش یک remove() دارد که آرگومان نمیگیرد و خودش را از سند بیرون میکشد. پوشاندن آن با معنایی دیگر دامی است برای کسی که سراغ متد استاندارد میرود.
رویدادها
| رویداد | 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نوعی ادب در حق کسی است که پیوست میکند، نه مرزی امنیتی. - پس از ارسال موفق پاک کنید، نه پیش از آن. درخواستی که شکست میخورد باید فایلها را آماده باقی بگذارد تا بشود دوباره تلاش کرد.
- هر رد شدن را توضیح دهید. این رویداد هست تا نوار هرگز فایلی را در سکوت دور نیندازد.