From 5839f64d9877044d2e8e3c636b402c506c8ccf97 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Fri, 10 Jul 2026 17:59:07 +0300 Subject: [PATCH 1/9] Filter tags on backend --- .../components/wpt/WptTagsProvider.js | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 6722f3600..5cd3d2ceb 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -17,7 +17,7 @@ import { ReactComponent as DisplayLanguageIcon } from '../../../assets/icons/ic_ import { changeIconColor } from '../../../map/markers/MarkerOptions'; import { createPoiCache, getIconNameForPoiType, updatePoiCache } from '../../../manager/PoiManager'; import React from 'react'; -import { apiGet } from '../../../util/HttpApi'; +import { apiGet, apiPost } from '../../../util/HttpApi'; import { parseTagWithLang } from '../../../manager/SearchManager'; import { localizeWeekTokens } from '../../../util/dateFmt'; import { @@ -52,7 +52,6 @@ const EMAIL = 'email'; const WEBSITE = 'website'; const CUISINE = 'cuisine'; export const CUISINE_PREFIX = 'cuisine_'; -const ROUTE = 'route'; export const IMAGE_OSM_TAG = 'image'; export const MAPILLARY_OSM_TAG = 'mapillary'; export const WIKIDATA = 'wikidata'; @@ -233,6 +232,7 @@ async function getWptTags(obj, type, ctx) { } tags = fixTagsKeys(tags); + tags = await filterTagsByVisibility(tags); for (const [key, value] of Object.entries(tags)) { if (!shouldSkipKey(key)) { let tagObj = {}; @@ -397,6 +397,24 @@ export async function addPoiTypeTag({ return tagObj; } +async function filterTagsByVisibility(tags) { + if (Object.keys(tags).length === 0) return tags; + + try { + let response = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/search/get-tags-visibility`, tags, { + apiCache: true, + }); + + if (!response?.data || typeof response.data !== 'object' || Array.isArray(response.data)) { + return tags; + } + + return response.data; + } catch { + return tags; + } +} + function fixTagsKeys(tags) { let res = {}; for (const [key, value] of Object.entries(tags)) { @@ -614,14 +632,7 @@ function getWikipediaURL(key, value) { } function shouldSkipKey(key) { - return ( - key === 'idObj' || - key === 'name' || - key === 'subway_region' || - key === 'note' || - key === 'lang_yes' || - key.includes(ROUTE) - ); + return key === 'idObj' || key === 'note'; } export function openWikipediaContent(tag, setDevWikiContent) { From 4ebff792e4734c689fdc671a2f5b22d9fb423528 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 14 Jul 2026 22:05:12 +0300 Subject: [PATCH 2/9] Refactoring tag filter --- .../components/wpt/WptTagsProvider.js | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 5cd3d2ceb..973e9d10a 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -52,6 +52,7 @@ const EMAIL = 'email'; const WEBSITE = 'website'; const CUISINE = 'cuisine'; export const CUISINE_PREFIX = 'cuisine_'; +const ROUTE = 'route'; export const IMAGE_OSM_TAG = 'image'; export const MAPILLARY_OSM_TAG = 'mapillary'; export const WIKIDATA = 'wikidata'; @@ -64,7 +65,6 @@ export const ICON_NAME_EXTENSION = 'icon'; export const BACKGROUND_TYPE_EXTENSION = 'background'; export const PROFILE_TYPE_EXTENSION = 'profile'; export const ADDRESS_EXTENSION = 'address'; -export const AMENITY_ORIGIN_EXTENSION = 'amenity_origin'; export const NAME = 'name'; export const EN_NAME = 'en_name'; export const ALT_NAME = 'osm_tag_alt_name'; @@ -108,14 +108,9 @@ const HIDDEN_EXTENSIONS = [ BACKGROUND_TYPE_EXTENSION, PROFILE_TYPE_EXTENSION, ADDRESS_EXTENSION, - AMENITY_ORIGIN_EXTENSION, - AMENITY_PREFIX + NAME, - AMENITY_PREFIX + TYPE, - AMENITY_PREFIX + SUBTYPE, ]; const HIDDEN_EXTENSIONS_POI = [ - ...HIDDEN_EXTENSIONS, ICON_KEY_NAME, POI_ICON_NAME, TYPE_OSM_TAG, @@ -231,7 +226,7 @@ async function getWptTags(obj, type, ctx) { } } - tags = fixTagsKeys(tags); + tags = filterWebKeys(tags); tags = await filterTagsByVisibility(tags); for (const [key, value] of Object.entries(tags)) { if (!shouldSkipKey(key)) { @@ -400,33 +395,24 @@ export async function addPoiTypeTag({ async function filterTagsByVisibility(tags) { if (Object.keys(tags).length === 0) return tags; - try { - let response = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/search/get-tags-visibility`, tags, { - apiCache: true, - }); - - if (!response?.data || typeof response.data !== 'object' || Array.isArray(response.data)) { - return tags; - } + let response = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/search/get-tags-visibility`, tags, { + apiCache: true, + }); + if (response?.data && typeof response.data === 'object' && !Array.isArray(response.data)) { return response.data; - } catch { - return tags; } + + return fixTagsKeysFallback(tags); } -function fixTagsKeys(tags) { +function fixTagsKeysFallback(tags) { let res = {}; for (const [key, value] of Object.entries(tags)) { let newKey = key; if (key === AMENITY_PREFIX + OPENING_HOURS) { newKey = key.replace(AMENITY_PREFIX, ''); - } else if ( - key.startsWith(AMENITY_PREFIX) || - key.startsWith(ALT_NAME) || - HIDDEN_EXTENSIONS.includes(key) || - HIDDEN_EXTENSIONS_POI.includes(key) - ) { + } else if (key.startsWith(AMENITY_PREFIX) || HIDDEN_EXTENSIONS.includes(key)) { continue; } else { newKey = key.replace(OSM_PREFIX, ''); @@ -436,6 +422,17 @@ function fixTagsKeys(tags) { return res; } +function filterWebKeys(tags) { + let res = {}; + for (const [key, value] of Object.entries(tags)) { + if (key.startsWith(ALT_NAME) || HIDDEN_EXTENSIONS_POI.includes(key)) { + continue; + } + res[key] = value; + } + return res; +} + function isNumeric(value) { const IS_NUMERIC_REGEX = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)$/; return IS_NUMERIC_REGEX.test(value); @@ -632,7 +629,14 @@ function getWikipediaURL(key, value) { } function shouldSkipKey(key) { - return key === 'idObj' || key === 'note'; + return ( + key === 'idObj' || + key === 'name' || + key === 'subway_region' || + key === 'note' || + key === 'lang_yes' || + key.includes(ROUTE) + ); } export function openWikipediaContent(tag, setDevWikiContent) { From 4c3c9610dbd5119c1117744b26e9b6f4a5c072cf Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Wed, 15 Jul 2026 20:21:56 +0300 Subject: [PATCH 3/9] Refactor tags with lang --- .../components/wpt/WptTagsProvider.js | 264 ++++++++++-------- 1 file changed, 144 insertions(+), 120 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 973e9d10a..361811c2e 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -228,144 +228,168 @@ async function getWptTags(obj, type, ctx) { tags = filterWebKeys(tags); tags = await filterTagsByVisibility(tags); - for (const [key, value] of Object.entries(tags)) { - if (!shouldSkipKey(key)) { - let tagObj = {}; - tagObj.key = key; - if (key.includes(WIKIPEDIA)) { - tagObj.value = getWikipediaURL(key, value); - } else { - tagObj.value = value; - } - tagObj.isUrl = isUrl(tagObj.value); - if (!tagObj.isUrl) { - tagObj.socialMediaUrl = getSocialMediaUrl(key, value); - if (tagObj.socialMediaUrl != null) { - tagObj.isUrl = true; - } - } - switch (key) { - case WIKIPEDIA: - tagObj.icon = ; - break; - case CUISINE: - tagObj.icon = ; - break; - case SERVICE_TIMES: - case COLLECTION_TIMES: - tagObj.icon = ; - tagObj.needLinks = false; - tagObj.textPrefix = value; - break; - case OPENING_HOURS: - tagObj.icon = ; - break; - case PHONE: - tagObj.icon = ; - tagObj.isPhoneNumber = true; - break; - case MOBILE: - tagObj.icon = ; - tagObj.isPhoneNumber = true; - break; - case WEBSITE: - tagObj.icon = ; - tagObj.isUrl = true; - break; - case DESCRIPTION: - tagObj.icon = ; - tagObj.desc = true; - break; - case INSTAGRAM: - tagObj.icon = ; - break; - case EMAIL: - tagObj.isEmail = true; - tagObj.icon = ; - break; - default: - if (key === 'addr:housename' || key === 'whitewater:rapid_name') { - tagObj.icon = ; - } else if (key.startsWith('operator') || key.startsWith('brand')) { - tagObj.icon = ; - } else if (key === 'internet_access_fee_yes') { - tagObj.icon = ; - } else if (key.includes('internet_access')) { - const prepValue = value.replace(TYPE, '').replace('__', '_'); - const svgData = await getSvgIcon({ value: prepValue, ctx }); - tagObj.icon = getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR); - } else if (parseTagWithLang(key).lang) { - tagObj.icon = ; - } else { - const svgData = await getSvgIcon({ key, value, ctx }); - tagObj.icon = getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR); - } - } + const groupedTags = groupLocalizedTags(tags); + for (const entry of groupedTags) { + if (shouldSkipKey(entry.key)) { + continue; + } + let tagObj = await buildTagObj(entry.key, entry.value, entry.lang, ctx, subtypeTag); - const formattedPrefixAndText = getFormattedPrefixAndText( - key, - tagObj.textPrefix, - value, - subtypeTag, - ctx - ); - tagObj.textPrefix = formattedPrefixAndText[0]; - tagObj.value = formattedPrefixAndText[1]; - - if (key.startsWith(COLLAPSABLE_PREFIX)) { - tagObj.collapsable = true; - tagObj.textPrefix = key.replace(COLLAPSABLE_PREFIX, ''); - if (tagObj.textPrefix === CUISINE) { - tagObj.icon = ; - hasCuisine = true; + if (entry.otherLangs?.length) { + tagObj.otherLangs = []; + for (const other of entry.otherLangs) { + if (shouldSkipKey(other.key)) { + continue; } + tagObj.otherLangs.push(await buildTagObj(other.key, other.value, other.lang, ctx, subtypeTag)); } + } - if (tagObj.key === OPENING_HOURS) { - tagObj.value = localizeWeekTokens(tagObj.value); - } - - if (tagObj.key.startsWith(POI_NAME)) { - tagObj.key = tagObj.key.replace(POI_NAME, 'shared_string_name'); - tagObj.textPrefix = tagObj.key; - } - - if (key.includes(WIKIDATA)) { - tagObj = addWikidataTags(key, value, tagObj); - } - res.push(tagObj); + if (tagObj.collapsable && tagObj.textPrefix === CUISINE) { + hasCuisine = true; } + res.push(tagObj); } if (hasCuisine) { res = res.filter((t) => t.key !== CUISINE); } } - res = res.filter((t) => !t.key.startsWith(WEB_PREFIX)); - res = mergeTagsWithLang(res); return { res, id, type: typeTag, subtype: subtypeTag }; } -function mergeTagsWithLang(tags) { - tags.forEach((tag) => { - if (tag.key.includes(':')) { - const arr = tag.key.split(':'); - tag.key = arr[0]; - tag.lang = arr[1]; +async function buildTagObj(key, value, lang, ctx, subtypeTag) { + let tagObj = {}; + tagObj.key = key; + if (key.includes(WIKIPEDIA)) { + tagObj.value = getWikipediaURL(key, value); + } else { + tagObj.value = value; + } + tagObj.isUrl = isUrl(tagObj.value); + if (!tagObj.isUrl) { + tagObj.socialMediaUrl = getSocialMediaUrl(key, value); + if (tagObj.socialMediaUrl != null) { + tagObj.isUrl = true; } - }); - let tagsWithLang = tags.filter((tag) => tag.lang); - tagsWithLang.forEach((tag) => { - if (tags.includes(tag)) { - const sameTags = tags.filter((t) => t.key === tag.key && t !== tag); - if (sameTags?.length > 0) { - tag.otherLangs = sameTags; - tags = tags.filter((t) => !sameTags.includes(t)); + } + + switch (key) { + case WIKIPEDIA: + tagObj.icon = ; + break; + case CUISINE: + tagObj.icon = ; + break; + case SERVICE_TIMES: + case COLLECTION_TIMES: + tagObj.icon = ; + tagObj.needLinks = false; + tagObj.textPrefix = value; + break; + case OPENING_HOURS: + tagObj.icon = ; + break; + case PHONE: + tagObj.icon = ; + tagObj.isPhoneNumber = true; + break; + case MOBILE: + tagObj.icon = ; + tagObj.isPhoneNumber = true; + break; + case WEBSITE: + tagObj.icon = ; + tagObj.isUrl = true; + break; + case DESCRIPTION: + tagObj.icon = ; + tagObj.desc = true; + break; + case INSTAGRAM: + tagObj.icon = ; + break; + case EMAIL: + tagObj.isEmail = true; + tagObj.icon = ; + break; + default: + if (key === 'addr:housename' || key === 'whitewater:rapid_name') { + tagObj.icon = ; + } else if (key.startsWith('operator') || key.startsWith('brand')) { + tagObj.icon = ; + } else if (key === 'internet_access_fee_yes') { + tagObj.icon = ; + } else if (key.includes('internet_access')) { + const prepValue = value.replace(TYPE, '').replace('__', '_'); + const svgData = await getSvgIcon({ value: prepValue, ctx }); + tagObj.icon = getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR); + } else if (lang || parseTagWithLang(key).lang) { + tagObj.icon = ; + } else { + const svgData = await getSvgIcon({ key, value, ctx }); + tagObj.icon = getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR); } + } + + const formattedPrefixAndText = getFormattedPrefixAndText(key, tagObj.textPrefix, value, subtypeTag, ctx); + tagObj.textPrefix = formattedPrefixAndText[0]; + tagObj.value = formattedPrefixAndText[1]; + + if (key.startsWith(COLLAPSABLE_PREFIX)) { + tagObj.collapsable = true; + tagObj.textPrefix = key.replace(COLLAPSABLE_PREFIX, ''); + if (tagObj.textPrefix === CUISINE) { + tagObj.icon = ; + } + } + + if (tagObj.key === OPENING_HOURS) { + tagObj.value = localizeWeekTokens(tagObj.value); + } + + if (tagObj.key.startsWith(POI_NAME)) { + tagObj.key = tagObj.key.replace(POI_NAME, 'shared_string_name'); + tagObj.textPrefix = tagObj.key; + } + + if (key.includes(WIKIDATA)) { + tagObj = addWikidataTags(key, value, tagObj); + } + + if (lang) { + tagObj.lang = lang; + tagObj.textPrefix = `${tagObj.textPrefix}:${lang}`; + } + + return tagObj; +} + +function groupLocalizedTags(tags) { + const result = []; + + Object.entries(tags).forEach(([key, value]) => { + if (typeof value === 'string') { + result.push({ key, value }); + return; + } + + const entries = Object.entries(value.localizations || {}).map(([k, v]) => { + const [base, lang] = k.split(':'); + return lang ? { key: base, value: v, lang } : { key: base, value: v }; + }); + + const mainTag = entries.find((t) => t.lang); + if (mainTag) { + const otherLangs = entries.filter((t) => t !== mainTag); + result.push({ ...mainTag, ...(otherLangs.length > 0 && { otherLangs }) }); + } else if (entries.length > 0) { + result.push(entries[0]); } }); - return tags; + + return result; } export async function addPoiTypeTag({ @@ -425,7 +449,7 @@ function fixTagsKeysFallback(tags) { function filterWebKeys(tags) { let res = {}; for (const [key, value] of Object.entries(tags)) { - if (key.startsWith(ALT_NAME) || HIDDEN_EXTENSIONS_POI.includes(key)) { + if (key.startsWith(WEB_PREFIX) || key.startsWith(ALT_NAME) || HIDDEN_EXTENSIONS_POI.includes(key)) { continue; } res[key] = value; From 011c708965654804205ef6043ad7ca7372581d67 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Fri, 17 Jul 2026 12:25:52 +0300 Subject: [PATCH 4/9] Fix name output --- .../components/wpt/WptTagsProvider.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 361811c2e..5c0472570 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -111,6 +111,7 @@ const HIDDEN_EXTENSIONS = [ ]; const HIDDEN_EXTENSIONS_POI = [ + ...HIDDEN_EXTENSIONS, ICON_KEY_NAME, POI_ICON_NAME, TYPE_OSM_TAG, @@ -349,8 +350,8 @@ async function buildTagObj(key, value, lang, ctx, subtypeTag) { tagObj.value = localizeWeekTokens(tagObj.value); } - if (tagObj.key.startsWith(POI_NAME)) { - tagObj.key = tagObj.key.replace(POI_NAME, 'shared_string_name'); + if (tagObj.key.startsWith(NAME)) { + tagObj.key = tagObj.key.replace(NAME, 'shared_string_name'); tagObj.textPrefix = tagObj.key; } @@ -418,7 +419,12 @@ export async function addPoiTypeTag({ async function filterTagsByVisibility(tags) { if (Object.keys(tags).length === 0) return tags; - + tags = Object.fromEntries( + Object.entries(tags).map(([key, value]) => [ + key, + typeof typeof value === 'number' || typeof value === 'boolean' ? String(value) : value, + ]) + ); let response = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/search/get-tags-visibility`, tags, { apiCache: true, }); @@ -449,10 +455,14 @@ function fixTagsKeysFallback(tags) { function filterWebKeys(tags) { let res = {}; for (const [key, value] of Object.entries(tags)) { - if (key.startsWith(WEB_PREFIX) || key.startsWith(ALT_NAME) || HIDDEN_EXTENSIONS_POI.includes(key)) { + let newKey = key; + if (key.startsWith(ALT_NAME) || HIDDEN_EXTENSIONS_POI.includes(key)) { continue; } - res[key] = value; + if(key.startsWith(POI_NAME)) { + newKey = key.replace(WEB_POI_PREFIX, ''); + } + res[newKey] = value; } return res; } @@ -655,7 +665,6 @@ function getWikipediaURL(key, value) { function shouldSkipKey(key) { return ( key === 'idObj' || - key === 'name' || key === 'subway_region' || key === 'note' || key === 'lang_yes' || From 96c15599159ecd0142bebee05c6438f71fe97021 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Fri, 17 Jul 2026 17:37:26 +0300 Subject: [PATCH 5/9] Fix name output --- .../components/wpt/WptTagsProvider.js | 80 +++++++++++++++---- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 5c0472570..60da4b919 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -68,6 +68,8 @@ export const ADDRESS_EXTENSION = 'address'; export const NAME = 'name'; export const EN_NAME = 'en_name'; export const ALT_NAME = 'osm_tag_alt_name'; +const ORIGINAL_ICON = 'originalIcon'; +const SVG = 'svg'; // from Amenity object export const POI_NAME = WEB_POI_PREFIX + 'name'; @@ -227,7 +229,22 @@ async function getWptTags(obj, type, ctx) { } } - tags = filterWebKeys(tags); + const poiNameTags = {}; + const remainingTags = {}; + Object.entries(tags).forEach(([key, value]) => { + if (key.startsWith(POI_NAME) && value) { + poiNameTags[key] = value; + } else { + remainingTags[key] = value; + } + }); + + const poiNameTagObj = await buildPoiNameTagObj(poiNameTags, ctx, subtypeTag); + if (poiNameTagObj) { + res.push(poiNameTagObj); + } + + tags = filterWebKeys(remainingTags); tags = await filterTagsByVisibility(tags); const groupedTags = groupLocalizedTags(tags); @@ -238,13 +255,7 @@ async function getWptTags(obj, type, ctx) { let tagObj = await buildTagObj(entry.key, entry.value, entry.lang, ctx, subtypeTag); if (entry.otherLangs?.length) { - tagObj.otherLangs = []; - for (const other of entry.otherLangs) { - if (shouldSkipKey(other.key)) { - continue; - } - tagObj.otherLangs.push(await buildTagObj(other.key, other.value, other.lang, ctx, subtypeTag)); - } + tagObj.otherLangs = await buildOtherLangTags(entry.otherLangs, ctx, subtypeTag, true); } if (tagObj.collapsable && tagObj.textPrefix === CUISINE) { @@ -350,8 +361,8 @@ async function buildTagObj(key, value, lang, ctx, subtypeTag) { tagObj.value = localizeWeekTokens(tagObj.value); } - if (tagObj.key.startsWith(NAME)) { - tagObj.key = tagObj.key.replace(NAME, 'shared_string_name'); + if (tagObj.key.startsWith(POI_NAME)) { + tagObj.key = tagObj.key.replace(POI_NAME, 'shared_string_name'); tagObj.textPrefix = tagObj.key; } @@ -393,6 +404,38 @@ function groupLocalizedTags(tags) { return result; } +async function buildPoiNameTagObj(poiNameTags, ctx, subtypeTag) { + const entries = Object.entries(poiNameTags).map(([key, value]) => { + const [base, lang] = key.split(':'); + return lang ? { key: base, value, lang } : { key: base, value }; + }); + + const mainEntry = entries.find((entry) => entry.lang) ?? entries[0]; + if (!mainEntry) { + return null; + } + + const tagObj = await buildTagObj(mainEntry.key, mainEntry.value, mainEntry.lang, ctx, subtypeTag); + const otherEntries = entries.filter((entry) => entry !== mainEntry); + if (otherEntries.length > 0) { + tagObj.otherLangs = await buildOtherLangTags(otherEntries, ctx, subtypeTag, false); + } + + return tagObj; +} + +async function buildOtherLangTags(entries, ctx, subtypeTag, filterSkipped) { + const otherLangs = []; + for (const entry of entries) { + if (filterSkipped && shouldSkipKey(entry.key)) { + continue; + } + otherLangs.push(await buildTagObj(entry.key, entry.value, entry.lang, ctx, subtypeTag)); + } + + return otherLangs; +} + export async function addPoiTypeTag({ key, value, @@ -422,7 +465,7 @@ async function filterTagsByVisibility(tags) { tags = Object.fromEntries( Object.entries(tags).map(([key, value]) => [ key, - typeof typeof value === 'number' || typeof value === 'boolean' ? String(value) : value, + typeof value === 'number' || typeof value === 'boolean' ? String(value) : value, ]) ); let response = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/search/get-tags-visibility`, tags, { @@ -455,15 +498,17 @@ function fixTagsKeysFallback(tags) { function filterWebKeys(tags) { let res = {}; for (const [key, value] of Object.entries(tags)) { - let newKey = key; - if (key.startsWith(ALT_NAME) || HIDDEN_EXTENSIONS_POI.includes(key)) { + if ( + key.startsWith(ALT_NAME) || + HIDDEN_EXTENSIONS_POI.includes(key) || + key.startsWith(ORIGINAL_ICON) || + key === SVG + ) { continue; } - if(key.startsWith(POI_NAME)) { - newKey = key.replace(WEB_POI_PREFIX, ''); - } - res[newKey] = value; + res[key] = value; } + return res; } @@ -665,6 +710,7 @@ function getWikipediaURL(key, value) { function shouldSkipKey(key) { return ( key === 'idObj' || + key === 'name' || key === 'subway_region' || key === 'note' || key === 'lang_yes' || From 64adefa51cc79e6436aad814d50d6ba915766307 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Wed, 22 Jul 2026 15:12:18 +0300 Subject: [PATCH 6/9] Remove fallback, move groupLocalizedTags to backend --- .../components/wpt/WptTagsProvider.js | 77 ++----------------- 1 file changed, 5 insertions(+), 72 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 60da4b919..6d1414816 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -52,7 +52,6 @@ const EMAIL = 'email'; const WEBSITE = 'website'; const CUISINE = 'cuisine'; export const CUISINE_PREFIX = 'cuisine_'; -const ROUTE = 'route'; export const IMAGE_OSM_TAG = 'image'; export const MAPILLARY_OSM_TAG = 'mapillary'; export const WIKIDATA = 'wikidata'; @@ -61,10 +60,7 @@ const INSTAGRAM = 'instagram'; export const OSM_PREFIX = 'osm_tag_'; const COLLAPSABLE_PREFIX = 'collapsable_'; export const COLOR_NAME_EXTENSION = 'color'; -export const ICON_NAME_EXTENSION = 'icon'; export const BACKGROUND_TYPE_EXTENSION = 'background'; -export const PROFILE_TYPE_EXTENSION = 'profile'; -export const ADDRESS_EXTENSION = 'address'; export const NAME = 'name'; export const EN_NAME = 'en_name'; export const ALT_NAME = 'osm_tag_alt_name'; @@ -104,16 +100,7 @@ export const PARAM_FIELDS = 'fields=id,geometry,compass_angle,captured_at,camera export const TITLE = 'title'; -const HIDDEN_EXTENSIONS = [ - COLOR_NAME_EXTENSION, - ICON_NAME_EXTENSION, - BACKGROUND_TYPE_EXTENSION, - PROFILE_TYPE_EXTENSION, - ADDRESS_EXTENSION, -]; - const HIDDEN_EXTENSIONS_POI = [ - ...HIDDEN_EXTENSIONS, ICON_KEY_NAME, POI_ICON_NAME, TYPE_OSM_TAG, @@ -245,10 +232,9 @@ async function getWptTags(obj, type, ctx) { } tags = filterWebKeys(remainingTags); - tags = await filterTagsByVisibility(tags); + const tagList = await filterTagsByVisibility(tags); - const groupedTags = groupLocalizedTags(tags); - for (const entry of groupedTags) { + for (const entry of tagList) { if (shouldSkipKey(entry.key)) { continue; } @@ -378,32 +364,6 @@ async function buildTagObj(key, value, lang, ctx, subtypeTag) { return tagObj; } -function groupLocalizedTags(tags) { - const result = []; - - Object.entries(tags).forEach(([key, value]) => { - if (typeof value === 'string') { - result.push({ key, value }); - return; - } - - const entries = Object.entries(value.localizations || {}).map(([k, v]) => { - const [base, lang] = k.split(':'); - return lang ? { key: base, value: v, lang } : { key: base, value: v }; - }); - - const mainTag = entries.find((t) => t.lang); - if (mainTag) { - const otherLangs = entries.filter((t) => t !== mainTag); - result.push({ ...mainTag, ...(otherLangs.length > 0 && { otherLangs }) }); - } else if (entries.length > 0) { - result.push(entries[0]); - } - }); - - return result; -} - async function buildPoiNameTagObj(poiNameTags, ctx, subtypeTag) { const entries = Object.entries(poiNameTags).map(([key, value]) => { const [base, lang] = key.split(':'); @@ -461,7 +421,7 @@ export async function addPoiTypeTag({ } async function filterTagsByVisibility(tags) { - if (Object.keys(tags).length === 0) return tags; + if (Object.keys(tags).length === 0) return []; tags = Object.fromEntries( Object.entries(tags).map(([key, value]) => [ key, @@ -472,27 +432,7 @@ async function filterTagsByVisibility(tags) { apiCache: true, }); - if (response?.data && typeof response.data === 'object' && !Array.isArray(response.data)) { - return response.data; - } - - return fixTagsKeysFallback(tags); -} - -function fixTagsKeysFallback(tags) { - let res = {}; - for (const [key, value] of Object.entries(tags)) { - let newKey = key; - if (key === AMENITY_PREFIX + OPENING_HOURS) { - newKey = key.replace(AMENITY_PREFIX, ''); - } else if (key.startsWith(AMENITY_PREFIX) || HIDDEN_EXTENSIONS.includes(key)) { - continue; - } else { - newKey = key.replace(OSM_PREFIX, ''); - } - res[newKey] = value; - } - return res; + return Array.isArray(response?.data) ? response.data : []; } function filterWebKeys(tags) { @@ -708,14 +648,7 @@ function getWikipediaURL(key, value) { } function shouldSkipKey(key) { - return ( - key === 'idObj' || - key === 'name' || - key === 'subway_region' || - key === 'note' || - key === 'lang_yes' || - key.includes(ROUTE) - ); + return key === 'idObj'; } export function openWikipediaContent(tag, setDevWikiContent) { From 2186facf4890fa990b757be924547b2afe62bccc Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Wed, 22 Jul 2026 20:25:21 +0300 Subject: [PATCH 7/9] Rename and refactor --- .../components/wpt/WptTagsProvider.js | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 6d1414816..14a67d958 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -60,12 +60,14 @@ const INSTAGRAM = 'instagram'; export const OSM_PREFIX = 'osm_tag_'; const COLLAPSABLE_PREFIX = 'collapsable_'; export const COLOR_NAME_EXTENSION = 'color'; +const ICON_NAME_EXTENSION = 'icon'; export const BACKGROUND_TYPE_EXTENSION = 'background'; export const NAME = 'name'; export const EN_NAME = 'en_name'; export const ALT_NAME = 'osm_tag_alt_name'; const ORIGINAL_ICON = 'originalIcon'; -const SVG = 'svg'; +const MARKER_ICON_SVG = 'svg'; +const MARKER_OBJ_ID = 'idObj'; // from Amenity object export const POI_NAME = WEB_POI_PREFIX + 'name'; @@ -100,7 +102,7 @@ export const PARAM_FIELDS = 'fields=id,geometry,compass_angle,captured_at,camera export const TITLE = 'title'; -const HIDDEN_EXTENSIONS_POI = [ +const WEB_TAGS_TO_SKIP = [ ICON_KEY_NAME, POI_ICON_NAME, TYPE_OSM_TAG, @@ -112,6 +114,7 @@ const HIDDEN_EXTENSIONS_POI = [ POI_NAME, POI_ID, POI_OSM_URL, + MARKER_OBJ_ID, ]; export const SEPARATOR = ';'; @@ -217,12 +220,12 @@ async function getWptTags(obj, type, ctx) { } const poiNameTags = {}; - const remainingTags = {}; + const mainTags = {}; Object.entries(tags).forEach(([key, value]) => { if (key.startsWith(POI_NAME) && value) { poiNameTags[key] = value; } else { - remainingTags[key] = value; + mainTags[key] = value; } }); @@ -231,17 +234,14 @@ async function getWptTags(obj, type, ctx) { res.push(poiNameTagObj); } - tags = filterWebKeys(remainingTags); - const tagList = await filterTagsByVisibility(tags); + tags = filterWebKeys(mainTags); + const tagList = await fetchVisibleTags(tags); for (const entry of tagList) { - if (shouldSkipKey(entry.key)) { - continue; - } - let tagObj = await buildTagObj(entry.key, entry.value, entry.lang, ctx, subtypeTag); + let tagObj = await buildTagObj({ key: entry.key, value: entry.value, lang: entry.lang, ctx, subtypeTag }); if (entry.otherLangs?.length) { - tagObj.otherLangs = await buildOtherLangTags(entry.otherLangs, ctx, subtypeTag, true); + tagObj.otherLangs = await buildOtherLangTags(entry.otherLangs, ctx, subtypeTag); } if (tagObj.collapsable && tagObj.textPrefix === CUISINE) { @@ -257,7 +257,7 @@ async function getWptTags(obj, type, ctx) { return { res, id, type: typeTag, subtype: subtypeTag }; } -async function buildTagObj(key, value, lang, ctx, subtypeTag) { +async function buildTagObj({ key, value, lang, ctx, subtypeTag }) { let tagObj = {}; tagObj.key = key; if (key.includes(WIKIPEDIA)) { @@ -375,22 +375,19 @@ async function buildPoiNameTagObj(poiNameTags, ctx, subtypeTag) { return null; } - const tagObj = await buildTagObj(mainEntry.key, mainEntry.value, mainEntry.lang, ctx, subtypeTag); + const tagObj = await buildTagObj({ key: mainEntry.key, value: mainEntry.value, lang: mainEntry.lang, ctx, subtypeTag }); const otherEntries = entries.filter((entry) => entry !== mainEntry); if (otherEntries.length > 0) { - tagObj.otherLangs = await buildOtherLangTags(otherEntries, ctx, subtypeTag, false); + tagObj.otherLangs = await buildOtherLangTags(otherEntries, ctx, subtypeTag); } return tagObj; } -async function buildOtherLangTags(entries, ctx, subtypeTag, filterSkipped) { +async function buildOtherLangTags(entries, ctx, subtypeTag) { const otherLangs = []; for (const entry of entries) { - if (filterSkipped && shouldSkipKey(entry.key)) { - continue; - } - otherLangs.push(await buildTagObj(entry.key, entry.value, entry.lang, ctx, subtypeTag)); + otherLangs.push(await buildTagObj({ key: entry.key, value: entry.value, lang: entry.lang, ctx, subtypeTag })); } return otherLangs; @@ -420,7 +417,7 @@ export async function addPoiTypeTag({ return tagObj; } -async function filterTagsByVisibility(tags) { +async function fetchVisibleTags(tags) { if (Object.keys(tags).length === 0) return []; tags = Object.fromEntries( Object.entries(tags).map(([key, value]) => [ @@ -428,7 +425,7 @@ async function filterTagsByVisibility(tags) { typeof value === 'number' || typeof value === 'boolean' ? String(value) : value, ]) ); - let response = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/search/get-tags-visibility`, tags, { + let response = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/search/get-visible-tags`, tags, { apiCache: true, }); @@ -439,10 +436,12 @@ function filterWebKeys(tags) { let res = {}; for (const [key, value] of Object.entries(tags)) { if ( + key.startsWith(WEB_PREFIX) || key.startsWith(ALT_NAME) || - HIDDEN_EXTENSIONS_POI.includes(key) || + WEB_TAGS_TO_SKIP.includes(key) || key.startsWith(ORIGINAL_ICON) || - key === SVG + key === MARKER_ICON_SVG || + key === ICON_NAME_EXTENSION ) { continue; } @@ -647,10 +646,6 @@ function getWikipediaURL(key, value) { return value; } -function shouldSkipKey(key) { - return key === 'idObj'; -} - export function openWikipediaContent(tag, setDevWikiContent) { if (tag.key === WIKIPEDIA) { getWikipediaContent(tag).then((data) => { @@ -738,7 +733,7 @@ function parseUrl(url, site) { } export function filterTag(tag) { - return tag.key !== WIKIMEDIA_COMMONS && !otherImgTags(tag.key) && tag.key !== 'svg' && tag.key !== 'type'; + return tag.key !== WIKIMEDIA_COMMONS && !otherImgTags(tag.key) && tag.key !== MARKER_ICON_SVG && tag.key !== 'type'; } export const otherImgTags = (tag) => { From 658b5fa0234b4d08658b558681035104ed5944fe Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Fri, 24 Jul 2026 09:48:50 +0300 Subject: [PATCH 8/9] Fix wikidata, wikipedia tags, fix name not updating --- map/src/infoblock/components/wpt/WptTagInfo.jsx | 1 + map/src/infoblock/components/wpt/WptTagsProvider.js | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagInfo.jsx b/map/src/infoblock/components/wpt/WptTagInfo.jsx index d4c8a6e63..5393f48df 100644 --- a/map/src/infoblock/components/wpt/WptTagInfo.jsx +++ b/map/src/infoblock/components/wpt/WptTagInfo.jsx @@ -29,6 +29,7 @@ export default function WptTagInfo({ tag = null, baseTag = null, copy = false, s } useEffect(() => { + setNewTag(null); if (tag?.collapsable) { const items = tag.value.split(SEPARATOR); if (items.length > 1) { diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 14a67d958..2633884a2 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -221,13 +221,19 @@ async function getWptTags(obj, type, ctx) { const poiNameTags = {}; const mainTags = {}; - Object.entries(tags).forEach(([key, value]) => { + for (const [key, value] of Object.entries(tags)) { if (key.startsWith(POI_NAME) && value) { poiNameTags[key] = value; + } else if (key.includes(WIKIDATA) || key.includes(WIKIPEDIA)) { + const tagKey = key.includes(WIKIDATA) ? WIKIDATA : WIKIPEDIA; + const tagObj = await buildTagObj({ key: tagKey, value: value, lang: null, ctx, subtypeTag }); + if (tagObj) { + res.push(tagObj); + } } else { mainTags[key] = value; } - }); + } const poiNameTagObj = await buildPoiNameTagObj(poiNameTags, ctx, subtypeTag); if (poiNameTagObj) { From 007035872f479307cd584a4ecfd4da5b822d82cb Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Fri, 24 Jul 2026 10:30:33 +0300 Subject: [PATCH 9/9] Refactor buildTagObj --- .../components/wpt/WptTagsProvider.js | 140 +++++++++--------- 1 file changed, 71 insertions(+), 69 deletions(-) diff --git a/map/src/infoblock/components/wpt/WptTagsProvider.js b/map/src/infoblock/components/wpt/WptTagsProvider.js index 2633884a2..d83a7e28c 100644 --- a/map/src/infoblock/components/wpt/WptTagsProvider.js +++ b/map/src/infoblock/components/wpt/WptTagsProvider.js @@ -264,13 +264,8 @@ async function getWptTags(obj, type, ctx) { } async function buildTagObj({ key, value, lang, ctx, subtypeTag }) { - let tagObj = {}; - tagObj.key = key; - if (key.includes(WIKIPEDIA)) { - tagObj.value = getWikipediaURL(key, value); - } else { - tagObj.value = value; - } + let tagObj = { key }; + tagObj.value = key.includes(WIKIPEDIA) ? getWikipediaURL(key, value) : value; tagObj.isUrl = isUrl(tagObj.value); if (!tagObj.isUrl) { tagObj.socialMediaUrl = getSocialMediaUrl(key, value); @@ -279,68 +274,82 @@ async function buildTagObj({ key, value, lang, ctx, subtypeTag }) { } } + Object.assign(tagObj, await getTagIconProps({ key, value, lang, ctx })); + + const [formattedPrefix, formattedValue] = getFormattedPrefixAndText(key, tagObj.textPrefix, value, subtypeTag, ctx); + tagObj.textPrefix = formattedPrefix; + tagObj.value = formattedValue; + + applyCollapsablePrefix(tagObj, key); + applySharedNameTag(tagObj); + + if (tagObj.key === OPENING_HOURS) { + tagObj.value = localizeWeekTokens(tagObj.value); + } + + if (key.includes(WIKIDATA)) { + tagObj = addWikidataTags(key, value, tagObj); + } + + if (lang) { + tagObj.lang = lang; + tagObj.textPrefix = `${tagObj.textPrefix}:${lang}`; + } + + return tagObj; +} + +async function getTagIconProps({ key, value, lang, ctx }) { switch (key) { case WIKIPEDIA: - tagObj.icon = ; - break; + return { icon: }; case CUISINE: - tagObj.icon = ; - break; + return { icon: }; case SERVICE_TIMES: case COLLECTION_TIMES: - tagObj.icon = ; - tagObj.needLinks = false; - tagObj.textPrefix = value; - break; + return { icon: , needLinks: false, textPrefix: value }; case OPENING_HOURS: - tagObj.icon = ; - break; + return { icon: }; case PHONE: - tagObj.icon = ; - tagObj.isPhoneNumber = true; - break; + return { icon: , isPhoneNumber: true }; case MOBILE: - tagObj.icon = ; - tagObj.isPhoneNumber = true; - break; + return { icon: , isPhoneNumber: true }; case WEBSITE: - tagObj.icon = ; - tagObj.isUrl = true; - break; + return { icon: , isUrl: true }; case DESCRIPTION: - tagObj.icon = ; - tagObj.desc = true; - break; + return { icon: , desc: true }; case INSTAGRAM: - tagObj.icon = ; - break; + return { icon: }; case EMAIL: - tagObj.isEmail = true; - tagObj.icon = ; - break; + return { icon: , isEmail: true }; + case 'addr:housename': + case 'whitewater:rapid_name': + return { icon: }; + case 'internet_access_fee_yes': + return { icon: }; default: - if (key === 'addr:housename' || key === 'whitewater:rapid_name') { - tagObj.icon = ; - } else if (key.startsWith('operator') || key.startsWith('brand')) { - tagObj.icon = ; - } else if (key === 'internet_access_fee_yes') { - tagObj.icon = ; - } else if (key.includes('internet_access')) { - const prepValue = value.replace(TYPE, '').replace('__', '_'); - const svgData = await getSvgIcon({ value: prepValue, ctx }); - tagObj.icon = getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR); - } else if (lang || parseTagWithLang(key).lang) { - tagObj.icon = ; - } else { - const svgData = await getSvgIcon({ key, value, ctx }); - tagObj.icon = getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR); - } + return await getOtherTagIconProps({ key, value, lang, ctx }); } +} - const formattedPrefixAndText = getFormattedPrefixAndText(key, tagObj.textPrefix, value, subtypeTag, ctx); - tagObj.textPrefix = formattedPrefixAndText[0]; - tagObj.value = formattedPrefixAndText[1]; +async function getOtherTagIconProps({ key, value, lang, ctx }) { + if (key.startsWith('operator') || key.startsWith('brand')) { + return { icon: }; + } + if (key.includes('internet_access')) { + const prepValue = value.replace(TYPE, '').replace('__', '_'); + const svgData = await getSvgIcon({ value: prepValue, ctx }); + return { icon: getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR) }; + } + if (lang || parseTagWithLang(key).lang) { + return { icon: }; + } + const svgData = await getSvgIcon({ key, value, ctx }); + + return { icon: getIcon(svgData, DEFAULT_TAG_ICON_SIZE, DEFAULT_TAG_ICON_COLOR) }; +} +function applyCollapsablePrefix(tagObj, key) { if (key.startsWith(COLLAPSABLE_PREFIX)) { tagObj.collapsable = true; tagObj.textPrefix = key.replace(COLLAPSABLE_PREFIX, ''); @@ -348,26 +357,13 @@ async function buildTagObj({ key, value, lang, ctx, subtypeTag }) { tagObj.icon = ; } } +} - if (tagObj.key === OPENING_HOURS) { - tagObj.value = localizeWeekTokens(tagObj.value); - } - +function applySharedNameTag(tagObj) { if (tagObj.key.startsWith(POI_NAME)) { tagObj.key = tagObj.key.replace(POI_NAME, 'shared_string_name'); tagObj.textPrefix = tagObj.key; } - - if (key.includes(WIKIDATA)) { - tagObj = addWikidataTags(key, value, tagObj); - } - - if (lang) { - tagObj.lang = lang; - tagObj.textPrefix = `${tagObj.textPrefix}:${lang}`; - } - - return tagObj; } async function buildPoiNameTagObj(poiNameTags, ctx, subtypeTag) { @@ -381,7 +377,13 @@ async function buildPoiNameTagObj(poiNameTags, ctx, subtypeTag) { return null; } - const tagObj = await buildTagObj({ key: mainEntry.key, value: mainEntry.value, lang: mainEntry.lang, ctx, subtypeTag }); + const tagObj = await buildTagObj({ + key: mainEntry.key, + value: mainEntry.value, + lang: mainEntry.lang, + ctx, + subtypeTag, + }); const otherEntries = entries.filter((entry) => entry !== mainEntry); if (otherEntries.length > 0) { tagObj.otherLangs = await buildOtherLangTags(otherEntries, ctx, subtypeTag);