@emailmaker/filemanager 0.10.68 → 0.10.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,21 +4,6 @@
4
4
  http://jedwatson.github.io/classnames
5
5
  */
6
6
 
7
- /*! *****************************************************************************
8
- Copyright (c) Microsoft Corporation. All rights reserved.
9
- Licensed under the Apache License, Version 2.0 (the "License"); you may not use
10
- this file except in compliance with the License. You may obtain a copy of the
11
- License at http://www.apache.org/licenses/LICENSE-2.0
12
-
13
- THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
- KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
15
- WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
16
- MERCHANTABLITY OR NON-INFRINGEMENT.
17
-
18
- See the Apache Version 2.0 License for specific language governing permissions
19
- and limitations under the License.
20
- ***************************************************************************** */
21
-
22
7
  /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
23
8
 
24
9
  /**
@@ -1,8 +1,13 @@
1
1
  import { FormInstance } from 'antd/es/form';
2
- import { File, Folder, FileManagerDataProviders, PaginationState, PathItem, MenuItem, FilesQueryParams, LibraryMenuKey } from '../../types';
2
+ import { File, Folder, FileManagerDataProviders, PaginationState, PathItem, MenuItem, FilesQueryParams, LibraryMenuKey, SortOptionType } from '../../types';
3
3
  export type SortByType = 'name' | 'size' | 'date' | 'type';
4
4
  export type SortOrderType = 'asc' | 'desc';
5
- export type SortOptionType = 'date-desc' | 'date-asc' | 'size-desc' | 'size-asc' | 'name-asc' | 'name-desc' | 'type-asc';
5
+ export type { SortOptionType };
6
+ /** Полный список опций сортировки в дропдауне (порядок отображения). */
7
+ export declare const ALL_SORT_OPTIONS: {
8
+ value: SortOptionType;
9
+ labelKey: string;
10
+ }[];
6
11
  export declare const parseSortOption: (option: SortOptionType) => {
7
12
  sortBy: SortByType;
8
13
  sortOrder: SortOrderType;
package/hooks/index.d.ts CHANGED
@@ -3,3 +3,5 @@ export { useFileActions } from './useFileActions';
3
3
  export { usePixieEditor } from './usePixieEditor';
4
4
  export { useSidebarResize } from './useSidebarResize';
5
5
  export { useLastDirectory } from './useLastDirectory';
6
+ export { useControlledImageLoader } from './useControlledImageLoader';
7
+ export { useViewportMetricsSync } from './useViewportMetricsSync';
@@ -0,0 +1,6 @@
1
+ import type { NotifyEvent } from '../../types';
2
+ export declare function getNotificationDedupeKey(event: NotifyEvent): string;
3
+ /** Если то же событие уже показывали недавно — глушим видимость (silent). */
4
+ export declare function suppressIfDuplicateWithinTtl(event: NotifyEvent, ttlMs: number): void;
5
+ /** Вызывать только после фактического показа уведомления пользователю. */
6
+ export declare function recordNotificationShown(event: NotifyEvent, ttlMs: number): void;
@@ -0,0 +1,18 @@
1
+ export type ControlledImageStatus = 'idle' | 'queued' | 'loading' | 'loaded' | 'error';
2
+ export interface ControlledImageSnapshot {
3
+ status: ControlledImageStatus;
4
+ shouldLoad: boolean;
5
+ }
6
+ export interface ControlledImageLoader {
7
+ getSnapshot: (id: string, src: string) => ControlledImageSnapshot;
8
+ subscribe: (id: string, src: string, listener: (snapshot: ControlledImageSnapshot) => void) => (() => void);
9
+ setActive: (id: string, src: string, active: boolean) => void;
10
+ markLoaded: (id: string) => void;
11
+ markError: (id: string) => void;
12
+ release: (id: string) => void;
13
+ }
14
+ interface UseControlledImageLoaderOptions {
15
+ maxConcurrent?: number;
16
+ }
17
+ export declare function useControlledImageLoader({ maxConcurrent, }?: UseControlledImageLoaderOptions): ControlledImageLoader;
18
+ export {};
@@ -0,0 +1,2 @@
1
+ import { getViewportMetrics } from '../presentation/list-core/viewportMetrics';
2
+ export declare function useViewportMetricsSync<TElement extends HTMLElement>(onViewportChange: (metrics: ReturnType<typeof getViewportMetrics>) => void, deps: readonly unknown[]): import("react").MutableRefObject<TElement | null>;
package/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export { FileContent } from './components/FileContent/FileContent';
17
17
  export { FileModals } from './components/FileModals/FileModals';
18
18
  export { FolderSidebar } from './components/FolderSidebar/FolderSidebar';
19
19
  export { installResizeObserverErrorHandler, uninstallResizeObserverErrorHandler } from './utils/resizeObserverHandler';
20
- export { type Folder, type File, type PathItem, type MenuItem, type FileManagerOptions, type FileManagerHookReturn, type FileManagerErrorCode, type IFileManagerApiError, } from './types';
20
+ export { type Folder, type File, type PathItem, type MenuItem, type FileManagerOptions, type FileManagerHookReturn, type FileManagerErrorCode, type IFileManagerApiError, type SortOptionType, type FileManagerItemMeta, } from './types';
21
21
  export { FileManagerApiError } from './shared/fileManagerApiError';
22
22
  export { mapHttpStatusToErrorCode } from './shared/mapHttpStatusToErrorCode';
23
23
  export { type InitPixieEditorOptions, type Options as FileManagerInitOptions, type UniversalInitOptions, } from './file-manager';
package/notification.d.ts CHANGED
@@ -143,6 +143,8 @@ interface FilesQueryParams {
143
143
  sortBy?: 'name' | 'size' | 'date' | 'type';
144
144
  sortOrder?: 'asc' | 'desc';
145
145
  itemType?: 'file' | 'folder' | 'all';
146
+ /** Человекочитаемое имя папки для UI (не отправляется на API). */
147
+ folderName?: string;
146
148
  }
147
149
 
148
150
  /**
@@ -352,6 +354,8 @@ declare namespace Notify {
352
354
  */
353
355
  export interface FetchFilesErrorPayload {
354
356
  params?: FilesQueryParams;
357
+ /** Отображаемое имя папки списка (после resolve getItem / path / кэша). */
358
+ folderName?: string | null;
355
359
  }
356
360
 
357
361
  /**
@@ -409,6 +413,11 @@ declare namespace Notify {
409
413
  * Level of the operation: 'item' for single item operations, 'batch' for batch operations.
410
414
  */
411
415
  level?: 'item' | 'batch';
416
+ /**
417
+ * Если true — применять TTL-дедупликацию одинаковых уведомлений (после interceptors).
418
+ * Для upload/мутаций обычно не задаётся или false.
419
+ */
420
+ notificationDedupe?: boolean;
412
421
  };
413
422
 
414
423
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emailmaker/filemanager",
3
- "version": "0.10.68",
3
+ "version": "0.10.70",
4
4
  "main": "./file-manager.js",
5
5
  "module": "./file-manager.esm.js",
6
6
  "types": "./index.d.ts",
@@ -3,8 +3,11 @@ import type { File as AppFile } from '../../types';
3
3
  import type { ListItemAdapter } from '../list-core/ListItemAdapter';
4
4
  import type { FileListRowModel } from './types';
5
5
  export interface FileListContext {
6
- folderFileCounts: Record<string, number>;
6
+ /** null — ошибка загрузки счётчика, показываем прочерк */
7
+ folderFileCounts: Record<string, number | null>;
7
8
  features: Record<string, boolean | undefined>;
8
9
  t: TFunction;
10
+ /** См. Config.previewFallbackToFileUrl */
11
+ previewFallbackToFileUrl?: boolean;
9
12
  }
10
13
  export declare const fileListAdapter: ListItemAdapter<AppFile, FileListContext, FileListRowModel>;
@@ -12,6 +12,7 @@ export interface FileListRowModel extends ListRowModelBase<AppFile> {
12
12
  gridInfoText: string;
13
13
  thumbnailSrc?: string;
14
14
  previewSrc?: string;
15
+ lightboxSrc?: string;
15
16
  canEdit: boolean;
16
17
  canMove: boolean;
17
18
  canCopy: boolean;
@@ -4,10 +4,11 @@ import type { RenderWindow, ViewportMetrics } from '../list-core/types';
4
4
  import type { FileListRowModel } from './types';
5
5
  interface UseFileListEngineParams {
6
6
  files: AppFile[];
7
- folderFileCounts: Record<string, number>;
7
+ folderFileCounts: Record<string, number | null>;
8
8
  features: Record<string, boolean | undefined>;
9
9
  t: TFunction;
10
10
  viewMode: 'table' | 'grid';
11
+ previewFallbackToFileUrl?: boolean;
11
12
  }
12
13
  interface UseFileListEngineResult {
13
14
  rowModels: FileListRowModel[];
@@ -22,5 +23,5 @@ interface UseFileListEngineResult {
22
23
  * Здесь файловый менеджер только передаёт сырые данные и контекст derive-логики,
23
24
  * а само ядро уже отвечает за reuse row-model и подготовку render-window.
24
25
  */
25
- export declare function useFileListEngine({ files, folderFileCounts, features, t, viewMode, }: UseFileListEngineParams): UseFileListEngineResult;
26
+ export declare function useFileListEngine({ files, folderFileCounts, features, t, viewMode, previewFallbackToFileUrl, }: UseFileListEngineParams): UseFileListEngineResult;
26
27
  export {};
@@ -1,4 +1,4 @@
1
- export type RenderStrategy = 'full' | 'windowed';
1
+ export type RenderStrategy = 'full' | 'windowed' | 'positioned';
2
2
  export interface ListRowModelBase<TItem = unknown> {
3
3
  id: string;
4
4
  signature: string;
@@ -11,9 +11,25 @@ export interface WindowedRenderConfig {
11
11
  estimatedItemSize?: number;
12
12
  itemsPerLane?: number;
13
13
  }
14
+ export interface PositionedLayoutItem {
15
+ index: number;
16
+ top: number;
17
+ left: number;
18
+ width: number;
19
+ height: number;
20
+ bottom: number;
21
+ }
22
+ export interface PositionedRenderConfig {
23
+ layout: PositionedLayoutItem[];
24
+ viewportTop: number;
25
+ viewportBottom: number;
26
+ overscanPx?: number;
27
+ containerHeight: number;
28
+ }
14
29
  export interface ListEngineOptions {
15
30
  renderStrategy?: RenderStrategy;
16
31
  windowed?: WindowedRenderConfig;
32
+ positioned?: PositionedRenderConfig;
17
33
  }
18
34
  export interface ViewportMetrics {
19
35
  scrollTop: number;
@@ -28,4 +44,9 @@ export interface RenderWindow<TRowModel> {
28
44
  topOffset: number;
29
45
  bottomOffset: number;
30
46
  totalCount: number;
47
+ containerHeight?: number;
48
+ positionedItems?: Array<{
49
+ row: TRowModel;
50
+ layout: PositionedLayoutItem;
51
+ }>;
31
52
  }
@@ -0,0 +1,9 @@
1
+ import type { ListItemAdapter } from '../list-core/ListItemAdapter';
2
+ import type { MediaListRowModel } from './types';
3
+ export interface MediaListContext<TItem> {
4
+ getPreviewSrc: (item: TItem) => string | undefined;
5
+ getAltText: (item: TItem) => string;
6
+ getWidth?: (item: TItem) => number | undefined;
7
+ getHeight?: (item: TItem) => number | undefined;
8
+ }
9
+ export declare function createMediaListAdapter<TItem>(getId: (item: TItem) => string): ListItemAdapter<TItem, MediaListContext<TItem>, MediaListRowModel<TItem>>;
@@ -0,0 +1,8 @@
1
+ import type { ListRowModelBase } from '../list-core/types';
2
+ export interface MediaListRowModel<TItem> extends ListRowModelBase<TItem> {
3
+ previewSrc?: string;
4
+ altText: string;
5
+ width?: number;
6
+ height?: number;
7
+ aspectRatio: number;
8
+ }
@@ -0,0 +1,34 @@
1
+ import type { PositionedLayoutItem, ViewportMetrics, RenderWindow } from '../list-core/types';
2
+ import type { MediaListRowModel } from './types';
3
+ type MediaLayoutMode = 'masonry' | 'grid';
4
+ interface UseMediaListEngineParams<TItem> {
5
+ items: TItem[];
6
+ getId: (item: TItem) => string;
7
+ getPreviewSrc: (item: TItem) => string | undefined;
8
+ getAltText: (item: TItem) => string;
9
+ getWidth?: (item: TItem) => number | undefined;
10
+ getHeight?: (item: TItem) => number | undefined;
11
+ layoutMode?: MediaLayoutMode;
12
+ minColumnWidth?: number;
13
+ estimatedItemHeight?: number;
14
+ gap?: number;
15
+ overscanPx?: number;
16
+ prefetchThresholdPx?: number;
17
+ resolveColumnCount?: (viewportWidth: number) => number;
18
+ virtualizationThreshold?: number;
19
+ }
20
+ interface UseMediaListEngineResult<TItem> {
21
+ rowModels: MediaListRowModel<TItem>[];
22
+ renderRows: MediaListRowModel<TItem>[];
23
+ renderWindow: RenderWindow<MediaListRowModel<TItem>>;
24
+ positionedItems: Array<{
25
+ row: MediaListRowModel<TItem>;
26
+ layout: PositionedLayoutItem;
27
+ }>;
28
+ containerHeight: number;
29
+ isNearEnd: boolean;
30
+ viewportMetrics: ViewportMetrics;
31
+ handleViewportChange: (metrics: ViewportMetrics) => void;
32
+ }
33
+ export declare function useMediaListEngine<TItem>({ items, getId, getPreviewSrc, getAltText, getWidth, getHeight, layoutMode, minColumnWidth, estimatedItemHeight, gap, overscanPx, prefetchThresholdPx, resolveColumnCount, virtualizationThreshold, }: UseMediaListEngineParams<TItem>): UseMediaListEngineResult<TItem>;
34
+ export {};
package/types.d.ts CHANGED
@@ -44,8 +44,11 @@ export interface PixieSavePayload {
44
44
  blob: UploadBinary;
45
45
  mimeType: string;
46
46
  thumbnail: string;
47
+ /** Обычное сохранение или «Сохранить как копию». */
47
48
  mode: 'save' | 'saveCopy';
48
49
  }
50
+ /** Передаётся вторым аргументом в `onEditorOk` после сохранения из Pixie. */
51
+ export type EditorSaveMeta = Pick<PixieSavePayload, 'mode'>;
49
52
  export interface PathItem {
50
53
  id: string;
51
54
  name: string;
@@ -64,6 +67,8 @@ export interface FilesQueryParams {
64
67
  sortBy?: 'name' | 'size' | 'date' | 'type';
65
68
  sortOrder?: 'asc' | 'desc';
66
69
  itemType?: 'file' | 'folder' | 'all';
70
+ /** Имя папки для уведомлений; не уходит в dataProviders.getFiles при явной передаче полей. */
71
+ folderName?: string;
67
72
  }
68
73
  export interface ApiPaginationResponse {
69
74
  page: number;
@@ -152,7 +157,7 @@ export interface FileManagerOptions {
152
157
  id: string;
153
158
  folderId: string | undefined;
154
159
  name: string;
155
- }) => void | Promise<void>;
160
+ }, meta?: EditorSaveMeta) => void | Promise<void>;
156
161
  config?: Config;
157
162
  customIcons?: CustomIcons;
158
163
  dragDropIcon?: string;
@@ -213,7 +218,18 @@ export interface UploadFilesRequest {
213
218
  items: UploadFileItemInput[];
214
219
  folderId?: string | null;
215
220
  }
221
+ /** Метаданные элемента из кэша хоста (getItem). */
222
+ export interface FileManagerItemMeta {
223
+ id: string;
224
+ name: string;
225
+ isFolder: boolean;
226
+ }
216
227
  export interface FileManagerDataProviders {
228
+ /**
229
+ * Имя/тип по id из кэша хоста (уже загруженные папки и файлы).
230
+ * Опционально; для уведомлений и подписей в UI.
231
+ */
232
+ getItem?: (id: string) => Promise<FileManagerItemMeta | null> | FileManagerItemMeta | null;
217
233
  getFileData: (fileId: string) => Promise<Blob>;
218
234
  findFileByUrl?: (url: string) => Promise<File | null>;
219
235
  getFolders?: (options?: {
@@ -368,11 +384,18 @@ interface Limits {
368
384
  export type CustomTheme = Partial<import('antd/es/theme/interface').AliasToken> & {
369
385
  [key: string]: string | number | boolean | undefined;
370
386
  };
387
+ /** Варианты комбинированной сортировки в списке файлов (значение Select). */
388
+ export type SortOptionType = 'date-desc' | 'date-asc' | 'size-desc' | 'size-asc' | 'name-asc' | 'name-desc' | 'type-asc';
371
389
  export interface Config {
372
390
  theme?: 'light' | 'dark' | 'system';
373
391
  disableMockServer?: boolean;
374
392
  activeTab?: string;
375
393
  showNotifications?: boolean;
394
+ /**
395
+ * TTL (мс) подавления повторных одинаковых уведомлений (type + id + data).
396
+ * 0 — выключить. По умолчанию 12 с, если не задано.
397
+ */
398
+ notificationDedupeTtlMs?: number;
376
399
  apiEndpoints?: ApiEndpoints;
377
400
  REACT_APP_GIPHY_KEY?: string;
378
401
  locale?: string;
@@ -406,10 +429,25 @@ export interface Config {
406
429
  dataProcessingMode?: 'server' | 'client' | 'auto';
407
430
  enableClientSideProcessing?: boolean;
408
431
  disablePagination?: boolean;
409
- /** При false (или при disablePagination) — пагинации нет, фронт проверяет дубликаты имён и добавляет _1 */
432
+ /** При false (или при disablePagination) — пагинации нет в UI и в запросах списка (без page/limit). */
410
433
  hasPagination?: boolean;
411
- /** Наличие пагинации. При false проверка дубликатов имён папок при создании и при copy/move */
434
+ /** Наличие пагинации на бэкенде (отображение постраничности и т.п.). */
412
435
  paginationAvailable?: boolean;
436
+ /**
437
+ * Клиент устраняет дубликаты имён при загрузке, копии из Pixie, URL, batch, copy/move, создании папки.
438
+ * Отдельно от настроек пагинации (`disablePagination`, `hasPagination`, `paginationAvailable`).
439
+ */
440
+ dedupeUploadNamesOnClient?: boolean;
441
+ /**
442
+ * Для превью в таблице/плитке разрешить fallback на `file.url` (только изображения),
443
+ * если миниатюры нет или она загрузилась с ошибкой.
444
+ */
445
+ previewFallbackToFileUrl?: boolean;
446
+ /**
447
+ * Сохранять копию из Pixie в текущую папку исходного файла.
448
+ * По умолчанию copy-save идёт через `noFolder` во временную папку на стороне хоста.
449
+ */
450
+ saveCopyToCurrentFolder?: boolean;
413
451
  enableTrashFolder?: boolean;
414
452
  staticData?: {
415
453
  files?: File[];
@@ -462,6 +500,8 @@ export interface Config {
462
500
  /** Alias для compessorUrl с корректным написанием. */
463
501
  compressorUrl?: string;
464
502
  multiSelect?: boolean;
503
+ /** Белый список сортировок для отображения в дропдауне. Если не указан — доступны все. */
504
+ sortOptions?: SortOptionType[];
465
505
  defaultFolderName?: string;
466
506
  availableImageExtensions?: Array<(typeof IMAGE_EXTENSIONS)[number]>;
467
507
  /** Настройки обработки имен файлов при загрузке */
@@ -519,6 +559,18 @@ export interface GifItem {
519
559
  images: {
520
560
  downsized: {
521
561
  url: string;
562
+ width?: string;
563
+ height?: string;
564
+ };
565
+ fixed_width?: {
566
+ url: string;
567
+ width?: string;
568
+ height?: string;
569
+ };
570
+ fixed_width_downsampled?: {
571
+ url: string;
572
+ width?: string;
573
+ height?: string;
522
574
  };
523
575
  };
524
576
  }
@@ -535,6 +587,8 @@ export interface ImageGifProps {
535
587
  }
536
588
  export interface UnsplashImage {
537
589
  id: string;
590
+ width?: number;
591
+ height?: number;
538
592
  description: string;
539
593
  urls: {
540
594
  raw: string;
@@ -635,7 +689,7 @@ export interface FileManagerProps {
635
689
  id: string;
636
690
  folderId: string | undefined;
637
691
  name: string;
638
- }) => void;
692
+ }, meta?: EditorSaveMeta) => void;
639
693
  searchQuery?: string;
640
694
  sortBySize?: string;
641
695
  dataProviders?: FileManagerDataProviders;
@@ -660,6 +714,7 @@ export interface FileManagerProps {
660
714
  export interface SafeImageProps {
661
715
  thumbnailSrc?: string;
662
716
  previewSrc?: string;
717
+ lightboxSrc?: string;
663
718
  alt: string;
664
719
  className?: string;
665
720
  width?: number;
@@ -0,0 +1,6 @@
1
+ import type { Config } from '../types';
2
+ /**
3
+ * Флаг `Config.dedupeUploadNamesOnClient`: клиент подбирает уникальные имена при загрузке,
4
+ * копии из Pixie, upload по URL, batch, copy/move, создании папки.
5
+ */
6
+ export declare function isDedupeUploadNamesOnClientEnabled(config?: Pick<Config, 'dedupeUploadNamesOnClient'>): boolean;
@@ -1,4 +1,4 @@
1
- import { FileManagerDataProviders, File, Folder, Config, UploadFilesRequest, UploadFileResultData, BatchResult, UploadBinary } from '../types';
1
+ import { FileManagerDataProviders, File, FileManagerItemMeta, Folder, Config, UploadFilesRequest, UploadFileResultData, BatchResult, UploadBinary } from '../types';
2
2
  export declare class JSONDataProvider implements FileManagerDataProviders {
3
3
  private files;
4
4
  private folders;
@@ -12,6 +12,7 @@ export declare class JSONDataProvider implements FileManagerDataProviders {
12
12
  addFolders: (newFolders: Folder[]) => void;
13
13
  private normalizeComparableUrl;
14
14
  getFileData: (fileId: string) => Promise<Blob>;
15
+ getItem: (id: string) => Promise<FileManagerItemMeta | null>;
15
16
  findFileByUrl: (url: string) => Promise<File | null>;
16
17
  getFolders: (options?: {
17
18
  parentId?: string | null;
@@ -9,7 +9,8 @@ export interface NameProcessingOptions {
9
9
  toLowerCase?: boolean;
10
10
  }
11
11
  /**
12
- * Транслитерирует кириллицу в латиницу
12
+ * Транслитерирует кириллицу в латиницу.
13
+ * NFC: «й» в NFD — это и + combining breve; без нормализации получится «ĭ» вместо «y».
13
14
  */
14
15
  export declare function transliterateRuToEn(text: string): string;
15
16
  /**
@@ -0,0 +1,9 @@
1
+ import type { TFunction } from 'i18next';
2
+ import type { FileManagerDataProviders, Folder, PathItem } from '../types';
3
+ export declare function resolveFolderDisplayName(opts: {
4
+ folderId: string | null | undefined;
5
+ dataProviders?: FileManagerDataProviders;
6
+ folders?: Folder[];
7
+ pathHistory?: PathItem[];
8
+ t: TFunction;
9
+ }): Promise<string>;
@@ -0,0 +1,5 @@
1
+ import type { FileManagerDataProviders } from '../types';
2
+ /**
3
+ * Уникальное имя файла в папке (клиент), если включён `Config.dedupeUploadNamesOnClient`.
4
+ */
5
+ export declare function resolveUniqueUploadNameWithProvider(dataProviders: FileManagerDataProviders | undefined, originalName: string, folderId?: string | null, noFolder?: boolean, dedupeUploadNamesOnClient?: boolean): Promise<string>;
@@ -3,3 +3,8 @@
3
3
  * Убирает hash, трекинговые query-параметры, нормализует host и сортирует query.
4
4
  */
5
5
  export declare function normalizeExternalFileUrl(url: string): string;
6
+ /**
7
+ * Совпадает ли origin URL с текущей страницей (protocol + host + port).
8
+ * Для same-origin прокси не используем — загрузка напрямую.
9
+ */
10
+ export declare function isUrlSameOriginAsCurrentPage(url: string): boolean;
@@ -2,6 +2,7 @@ type TFunction = (key: string) => string;
2
2
  /**
3
3
  * Валидация URL на фронте без дополнительных запросов.
4
4
  * Проверяет: формат, протокол (только http/https), SSRF (localhost, приватные IP).
5
+ * Если приложение открыто с localhost, URL на localhost разрешены (удобство локальной разработки).
5
6
  *
6
7
  * @param url - строка URL для проверки
7
8
  * @param t - функция перевода для сообщений об ошибках