feat: «Скачать все» для вложений заявки
Над списком вложений (от двух файлов) — кнопка «Скачать все»: файлы скачиваются по очереди, без архива, через download.aspx SharePoint, чтобы PDF и картинки не открывались в просмотре. Обёртка над AttachmentList из ui-kit подменена в shared/ui, поэтому кнопка появилась во всех формах и карточках без правок в них; в чате выключена. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { forcedDownloadUrl } from './downloadAll';
|
||||
|
||||
const SITE = 'https://tnsukz.sharepoint.com/sites/requests';
|
||||
|
||||
describe('forcedDownloadUrl', () => {
|
||||
it('серверный относительный путь — через download.aspx с абсолютным SourceUrl', () => {
|
||||
expect(forcedDownloadUrl('/sites/requests/Lists/List4/Attachments/5/скан.pdf', SITE)).toBe(
|
||||
`${SITE}/_layouts/15/download.aspx?SourceUrl=${encodeURIComponent(
|
||||
'https://tnsukz.sharepoint.com/sites/requests/Lists/List4/Attachments/5/скан.pdf'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('абсолютный url остаётся как есть внутри SourceUrl', () => {
|
||||
const file = 'https://tnsukz.sharepoint.com/sites/requests/Shared Documents/a.docx';
|
||||
expect(forcedDownloadUrl(file, SITE)).toBe(
|
||||
`${SITE}/_layouts/15/download.aspx?SourceUrl=${encodeURIComponent(file)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('слеш на конце адреса сайта не удваивается', () => {
|
||||
expect(forcedDownloadUrl('/x.pdf', `${SITE}/`)).toContain(`${SITE}/_layouts/15/download.aspx`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { DATA_SITE_URL } from '../config';
|
||||
|
||||
/** Вложение в том виде, в каком его показывает AttachmentList. */
|
||||
export interface IDownloadableFile {
|
||||
fileName: string;
|
||||
/** Серверный относительный (как у вложений элементов) или абсолютный url. */
|
||||
url: string;
|
||||
}
|
||||
|
||||
// Пауза между загрузками. Если запустить все сразу, браузер часть загрузок
|
||||
// молча отбрасывает; полсекунды хватает, чтобы каждая успела стартовать.
|
||||
const DOWNLOAD_INTERVAL_MS = 500;
|
||||
|
||||
function absoluteUrl(url: string, siteUrl: string): string {
|
||||
if (/^https?:\/\//i.test(url)) return url;
|
||||
return `${new URL(siteUrl).origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ссылка, по которой SharePoint ОТДАЁТ файл загрузкой, а не открывает его:
|
||||
* прямая ссылка на PDF или картинку открылась бы в просмотре браузера.
|
||||
* download.aspx — стандартная страница SharePoint, отдаёт файл с
|
||||
* Content-Disposition: attachment.
|
||||
*/
|
||||
export function forcedDownloadUrl(url: string, siteUrl: string = DATA_SITE_URL): string {
|
||||
const site = siteUrl.replace(/\/+$/, '');
|
||||
return `${site}/_layouts/15/download.aspx?SourceUrl=${encodeURIComponent(absoluteUrl(url, siteUrl))}`;
|
||||
}
|
||||
|
||||
function startDownload(file: IDownloadableFile): void {
|
||||
const link = document.createElement('a');
|
||||
link.href = forcedDownloadUrl(file.url);
|
||||
link.download = file.fileName;
|
||||
link.rel = 'noopener';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Скачать все файлы по очереди — отдельными загрузками, без архива. При первом
|
||||
* использовании браузер спросит, разрешить ли сайту скачивать несколько файлов:
|
||||
* это его защита, обойти её нельзя, достаточно один раз разрешить.
|
||||
* Промис завершается, когда запущена последняя загрузка.
|
||||
*/
|
||||
export function downloadAllFiles(files: IDownloadableFile[]): Promise<void> {
|
||||
const withUrl = files.filter((f) => !!f.url);
|
||||
return new Promise((resolve) => {
|
||||
withUrl.forEach((file, i) => {
|
||||
window.setTimeout(() => {
|
||||
startDownload(file);
|
||||
if (i === withUrl.length - 1) resolve();
|
||||
}, i * DOWNLOAD_INTERVAL_MS);
|
||||
});
|
||||
if (withUrl.length === 0) resolve();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user