gendesign/tradein-mvp/frontend/src/lib/leaflet.ts
lekss361 d0d0d1fbab
All checks were successful
Deploy Trade-In / changes (push) Successful in 8s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 1m57s
Deploy Trade-In / deploy (push) Successful in 52s
fix(tradein/maps): убрать украинский флаг Leaflet и починить атрибуцию OSM (#2621)
2026-08-02 12:12:43 +00:00

108 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Единая точка работы с Leaflet для карт МЕРЫ.
*
* До этого модуля loadLeaflet() и константы LEAFLET_* были дословно
* скопированы в шести компонентах (MapCard, MapPicker, SaleShareMap,
* v2/HeroBar, v2/ParamsPanel, v2/SourcesMap). Копии разъезжались: правку
* атрибуции или версии приходилось вносить шесть раз, и любая пропущенная
* копия молча оставалась со старым поведением. Здесь один источник правды.
*
* Leaflet по-прежнему грузится с CDN (unpkg) с SRI-хэшами, а не как npm-дep —
* это сознательный статус-кво, менять его отдельной задачей.
*/
/* eslint-disable @typescript-eslint/no-explicit-any -- интероп с CDN-библиотекой Leaflet (window.L без типов) */
export const LEAFLET_VER = "1.9.4";
export const LEAFLET_CSS = `https://unpkg.com/leaflet@${LEAFLET_VER}/dist/leaflet.css`;
export const LEAFLET_JS = `https://unpkg.com/leaflet@${LEAFLET_VER}/dist/leaflet.js`;
export const LEAFLET_CSS_SRI = "sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=";
export const LEAFLET_JS_SRI = "sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=";
/** Тайлы OSM (стандартный слой). */
export const OSM_TILE_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
/** Максимальный зум стандартного слоя OSM. */
export const OSM_MAX_ZOOM = 19;
/**
* Атрибуция тайлов OSM — обязательна по ODbL и Attribution Guidelines OSMF.
*
* Раньше во всех картах стояло просто "© OpenStreetMap": без ссылки на
* лицензию и без слова contributors, то есть требованиям не соответствовало.
* Guidelines требуют кредит «© OpenStreetMap contributors» со ссылкой на
* https://www.openstreetmap.org/copyright; само имя OpenStreetMap переводить
* нельзя, поэтому строка остаётся в канонической английской форме.
*
* Вставляется Leaflet'ом через innerHTML контрола атрибуции, поэтому это
* намеренно HTML. Строка — константа модуля, пользовательского ввода в ней
* нет.
*/
export const OSM_ATTRIBUTION =
'© <a href="https://www.openstreetmap.org/copyright" target="_blank"' +
' rel="noopener noreferrer">OpenStreetMap</a> contributors';
/**
* Подгружает Leaflet с CDN ровно один раз на страницу, резолвит window.L.
*
* Повторные вызовы либо сразу отдают уже готовый window.L, либо подписываются
* на load уже висящего в DOM <script data-leaflet> — параллельные карты на
* одной странице не тянут библиотеку дважды.
*/
export function loadLeaflet(): Promise<any> {
return new Promise((resolve, reject) => {
const w = window as any;
if (w.L) {
resolve(w.L);
return;
}
if (!document.querySelector(`link[data-leaflet]`)) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = LEAFLET_CSS;
link.integrity = LEAFLET_CSS_SRI;
link.crossOrigin = "anonymous";
link.setAttribute("data-leaflet", "1");
document.head.appendChild(link);
}
const existing = document.querySelector<HTMLScriptElement>(`script[data-leaflet]`);
if (existing) {
existing.addEventListener("load", () => resolve(w.L));
existing.addEventListener("error", () => reject(new Error("leaflet load failed")));
return;
}
const script = document.createElement("script");
script.src = LEAFLET_JS;
script.integrity = LEAFLET_JS_SRI;
script.crossOrigin = "anonymous";
script.setAttribute("data-leaflet", "1");
script.onload = () => resolve(w.L);
script.onerror = () => reject(new Error("leaflet load failed"));
document.body.appendChild(script);
});
}
/**
* Обёртка над L.map(): создаёт карту и сразу гасит prefix контрола атрибуции.
*
* Зачем: Leaflet 1.9.4 по умолчанию подставляет в prefix украинский флаг
* (leaflet-src.js: `ukrainianFlag` + `prefix: '<a ...>' + flag + 'Leaflet</a>'`).
* Политического флага в продуктовых картах быть не должно. setPrefix(false)
* убирает флаг вместе с собственной подписью Leaflet — это законно, BSD-лицензия
* Leaflet не требует упоминания в UI.
*
* Что НЕ делаем: `attributionControl: false`. Это снесло бы весь контрол вместе
* с атрибуцией тайлов OSM, которая обязательна по лицензии. Контрол остаётся,
* из него уходит только prefix.
*
* Возвращает ту же карту, что и L.map(), поэтому чейнинг `.setView(...)`
* работает как раньше.
*/
export function createMap(
L: any,
container: HTMLElement,
options?: Record<string, unknown>,
): any {
const map = L.map(container, options);
map.attributionControl?.setPrefix(false);
return map;
}