diff --git a/assets/layers/artwork/artwork.json b/assets/layers/artwork/artwork.json index 6f3ff86863..8d02b33d9d 100644 --- a/assets/layers/artwork/artwork.json +++ b/assets/layers/artwork/artwork.json @@ -610,23 +610,30 @@ "he": "האם יש אתר אינטרנט עם מידע נוסף על היצירה הזו?" }, "render": { - "en": "More information on this website", - "nl": "Meer informatie op deze website", - "fr": "Plus d'info sûr ce site web", - "de": "Weitere Informationen auf dieser Webseite", - "id": "Info lanjut tersedia di laman web ini", - "it": "Ulteriori informazioni su questo sito web", - "ru": "Больше информации на этом сайте", - "ja": "Webサイトに詳細情報がある", - "zh_Hant": "這個網站有更多資訊", - "nb_NO": "Mer info er å finne på denne nettsiden", - "pt": "Mais informações neste site", - "hu": "További információ ezen a weboldalon", - "pl": "Więcej informacji na tej stronie", - "es": "Más información en este sitio web", - "da": "Yderligere oplysninger på dette websted", - "cs": "Více informací na této webové stránce", - "ca": "Més informació a aquesta pàgina web" + "special": { + "type": "link", + "href": "{website}", + "text": { + "en": "More information on this website", + "nl": "Meer informatie op deze website", + "fr": "Plus d'info sûr ce site web", + "de": "Weitere Informationen auf dieser Webseite", + "id": "Info lanjut tersedia di laman web ini", + "it": "Ulteriori informazioni su questo sito web", + "ru": "Больше информации на этом сайте", + "ja": "Webサイトに詳細情報がある", + "zh_Hant": "這個網站有更多資訊", + "nb_NO": "Mer info er å finne på denne nettsiden", + "pt": "Mais informações neste site", + "hu": "További információ ezen a weboldalon", + "pl": "Więcej informacji na tej stronie", + "es": "Más información en este sitio web", + "da": "Yderligere oplysninger på dette websted", + "cs": "Více informací na této webové stránce", + "ca": "Més informació a aquesta pàgina web" + } + } + }, "freeform": { "key": "website", diff --git a/assets/layers/climbing/climbing.json b/assets/layers/climbing/climbing.json index 5f6a4546bd..6be535d0d9 100644 --- a/assets/layers/climbing/climbing.json +++ b/assets/layers/climbing/climbing.json @@ -32,7 +32,7 @@ "club=" ] }, - "render": "{url}", + "render": "{url}", "freeform": { "key": "url", "type": "url" diff --git a/assets/layers/nature_reserve/nature_reserve.json b/assets/layers/nature_reserve/nature_reserve.json index f8c555c55f..7e9ee06697 100644 --- a/assets/layers/nature_reserve/nature_reserve.json +++ b/assets/layers/nature_reserve/nature_reserve.json @@ -360,14 +360,7 @@ "ca": "A quina adreça de correu electrònic es pot enviar amb preguntes i problemes amb aquest parc natural?" }, "render": { - "nl": "{email}", - "en": "{email}", - "ca": "{email}", - "de": "{email}", - "fr": "{email}", - "it": "{email}", - "ru": "{email}", - "id": "{email}" + "*": "{email}" }, "freeform": { "key": "email", @@ -393,7 +386,7 @@ "ca": "A quin número de telèfon es pot trucar amb preguntes i problemes amb aquest parc natural?" }, "render": { - "*": "{phone}" + "*": "{phone}" }, "freeform": { "key": "phone", diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index c3412b52d7..ff9cca18c2 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -208,7 +208,7 @@ { "id": "osmlink", "render": { - "*": "" + "*": "" }, "mappings": [ { @@ -220,7 +220,7 @@ { "id": "email", "render": { - "*": "{email}" + "*": "{email}" }, "icon": "./assets/svg/envelope.svg", "labels": [ @@ -255,7 +255,7 @@ { "if": "contact:email~*", "icon": "./assets/svg/envelope.svg", - "then": "{contact:email}", + "then": "{contact:email}", "hideInAnswer": true } ], diff --git a/assets/layers/sport_pitch/sport_pitch.json b/assets/layers/sport_pitch/sport_pitch.json index 8ad88f522d..9005470995 100644 --- a/assets/layers/sport_pitch/sport_pitch.json +++ b/assets/layers/sport_pitch/sport_pitch.json @@ -491,7 +491,7 @@ "key": "email", "type": "email" }, - "render": "{email}", + "render": "{email}", "id": "sport_pitch-email" }, { diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index dde6c27916..dc039c2953 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -406,7 +406,7 @@ "special": { "type": "multi", "key": "_translation_links", - "tagrendering": "Translate entries of {id}" + "tagrendering": "Translate entries of {id}" } } }, @@ -416,20 +416,20 @@ { "if": "_mastodon_link~*", "then": { - "en": "A link to your Mastodon-profile has been been found: {_mastodon_link}", - "de": "Es wurde ein Link zu deinem Mastodon-Profil gefunden: {_mastodon_link}", - "nl": "Een link naar je Mastodon-profiel werd gevonden: {_mastodon_link}", - "fr": "Un lien vers votre profil Mastodon a été trouvé : {_mastodon_link}", - "ca": "S'ha trobat un enllaç al vostre perfil de Mastodon: {_mastodon_link}" + "en": "A link to your Mastodon-profile has been been found: {_mastodon_link}", + "de": "Es wurde ein Link zu deinem Mastodon-Profil gefunden: {_mastodon_link}", + "nl": "Een link naar je Mastodon-profiel werd gevonden: {_mastodon_link}", + "fr": "Un lien vers votre profil Mastodon a été trouvé : {_mastodon_link}", + "ca": "S'ha trobat un enllaç al vostre perfil de Mastodon: {_mastodon_link}" }, "icon": "mastodon" }, { "if": "_mastodon_candidate~*", "then": { - "en": "We found a link to what looks to be a mastodon account, but it is unverified. Edit your profile description and place the following there: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>", - "de": "Wir haben einen Link gefunden, der aussieht wie ein Mastodon-Konto, aber nicht verifiziert ist. Bearbeiten Sie Ihre Profilbeschreibung und fügen Sie dort Folgendes ein: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>", - "nl": "Je profielbeschrijving bevat een link die vermoedelijk naar je Mastodon gaat, maar deze link is niet verifieerdbaar voor Mastodon.Pas je profielbeschrijving aan en plaats er de volgende code: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>" + "en": "We found a link to what looks to be a mastodon account, but it is unverified. Edit your profile description and place the following there: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>", + "de": "Wir haben einen Link gefunden, der aussieht wie ein Mastodon-Konto, aber nicht verifiziert ist. Bearbeiten Sie Ihre Profilbeschreibung und fügen Sie dort Folgendes ein: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>", + "nl": "Je profielbeschrijving bevat een link die vermoedelijk naar je Mastodon gaat, maar deze link is niet verifieerdbaar voor Mastodon.Pas je profielbeschrijving aan en plaats er de volgende code: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>" }, "icon": "invalid" } diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 3bd0b3b295..dabbec8e1e 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -1,21 +1,13 @@ { "id": "mapcomplete-changes", "title": { - "en": "Changes made with MapComplete", - "de": "Mit MapComplete erstellte Änderungen", - "fr": "Changements faits avec MapComplete", - "nl": "Wijzigingen gemaakt met MapComplete" + "en": "Changes made with MapComplete" }, "shortDescription": { - "en": "Show changes made with MapComplete", - "de": "Mit MapComplete erstellte Änderungen anzeigen", - "nl": "Toon wijzigingen gemaakt met MapComplete" + "en": "Shows changes made by MapComplete" }, "description": { - "en": "This maps shows all the changes made with MapComplete", - "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen", - "fr": "Cette carte montre tous les changements faits avec MapComplete", - "nl": "Deze kaart toont alle wijzigingen die met MapComplete gemaakt werden" + "en": "This maps shows all the changes made with MapComplete" }, "icon": "./assets/svg/logo.svg", "hideFromOverview": true, @@ -28,9 +20,7 @@ { "id": "mapcomplete-changes", "name": { - "en": "Changeset centers", - "de": "Zentrum der Änderungssätze", - "nl": "Centerpunt van changeset" + "en": "Changeset centers" }, "minzoom": 0, "source": { @@ -41,57 +31,41 @@ }, "title": { "render": { - "en": "Changeset for {theme}", - "de": "Änderungssatz für {theme}", - "fr": "Groupe de modifications pour {theme}" + "en": "Changeset for {theme}" } }, "description": { - "en": "Show all MapComplete changes", - "de": "Alle MapComplete-Änderungen anzeigen", - "nl": "Toon alle MapComplete wijzigingen" + "en": "Shows all MapComplete changes" }, "tagRenderings": [ { "id": "show_changeset_id", "render": { - "en": "Changeset {id}", - "de": "Änderungssatz {id}", - "fr": "Groupe de modifications {id}" + "en": "Changeset {id}" } }, { "id": "contributor", "question": { - "en": "Which contributor made this change?", - "de": "Welcher Mitwirkende hat diese Änderung vorgenommen?", - "fr": "Quel contributeur a fait cette modification ?", - "nl": "Welke bijdrager maakte deze wijziging?" + "en": "What contributor did make this change?" }, "freeform": { "key": "user" }, "render": { - "en": "Change made by {user}", - "de": "Änderung gemacht von {user}", - "fr": "Modification faite par {user}", - "nl": "Wijziging gemaakt door {user}" + "en": "Change made by {user}" } }, { "id": "theme-id", "question": { - "en": "What theme was used to make this change?", - "de": "Welches Thema wurde für diese Änderung verwendet?", - "fr": "Quel thème a été utilisé pour faire cette modification ?" + "en": "What theme was used to make this change?" }, "freeform": { "key": "theme" }, "render": { - "en": "Change with theme {theme}", - "de": "Geändert mit Thema {theme}", - "fr": "Modifié avec le thème {theme}" + "en": "Change with theme {theme}" } }, { @@ -100,29 +74,19 @@ "key": "locale" }, "question": { - "en": "What locale (language) was this change made in?", - "de": "In welcher Sprache wurde diese Änderung vorgenommen?", - "fr": "En quelle langue est-ce que ce changement a été fait ?", - "nl": "In welke locale (taal) werd deze wijziging gemaakt?" + "en": "What locale (language) was this change made in?" }, "render": { - "en": "User locale is {locale}", - "de": "Usersprache ist {locale}", - "nl": "De gebruikerstaal is {locale}" + "en": "User locale is {locale}" } }, { "id": "host", "render": { - "en": "Change made with {host}", - "de": "Änderung vorgenommen mit {host}", - "fr": "Modification faite avec {host}", - "nl": "Wijziging gemaakt met {host}" + "en": "Change with with {host}" }, "question": { - "en": "What host (website) was this change made with?", - "de": "Mit welchem Host / welcher Website wurde diese Änderung gemacht?", - "nl": "Met welke host (website) werd deze wijziging gemaakt?" + "en": "What host (website) was this change made with?" }, "freeform": { "key": "host" @@ -143,14 +107,10 @@ { "id": "version", "question": { - "en": "What version of MapComplete was used to make this change?", - "de": "Mit welcher Version von MapComplete wurde diese Änderung gemacht?", - "fr": "Quelle version de MapComplete a été utilisée pour faire cette modification ?" + "en": "What version of MapComplete was used to make this change?" }, "render": { - "en": "Made with {editor}", - "de": "Erstellt mit {editor}", - "fr": "Fait avec {editor}" + "en": "Made with {editor}" }, "freeform": { "key": "editor" @@ -492,9 +452,7 @@ } ], "question": { - "en": "Theme name contains {search}", - "de": "Themenname enthält {search}", - "nl": "Themenaam bevat {search}" + "en": "Themename contains {search}" } } ] @@ -510,9 +468,7 @@ } ], "question": { - "en": "Made by contributor {search}", - "de": "Erstellt von {search}", - "nl": "Gemaakt door bijdrager {search}" + "en": "Made by contributor {search}" } } ] @@ -528,9 +484,7 @@ } ], "question": { - "en": "Not made by contributor {search}", - "de": "Nicht erstellt von {search}", - "nl": "Niet gemaakt door bijdrager {search}" + "en": "Not made by contributor {search}" } } ] @@ -547,9 +501,7 @@ } ], "question": { - "en": "Made before {search}", - "de": "Erstellt vor {search}", - "nl": "Gemaakt voor {search}" + "en": "Made before {search}" } } ] @@ -566,9 +518,7 @@ } ], "question": { - "en": "Made after {search}", - "de": "Erstellt nach {search}", - "nl": "Gemaakt na {search}" + "en": "Made after {search}" } } ] @@ -584,10 +534,7 @@ } ], "question": { - "en": "User language (iso-code) {search}", - "de": "Benutzersprache (ISO-Code) {search}", - "fr": "Langage utilisateur (code-ISO) {search}", - "nl": "De taal van de bijdrager is {search}" + "en": "User language (iso-code) {search}" } } ] @@ -603,9 +550,7 @@ } ], "question": { - "en": "Made with host {search}", - "de": "Erstellt mit Host {search}", - "nl": "Gemaakt met host {search}" + "en": "Made with host {search}" } } ] @@ -616,10 +561,7 @@ { "osmTags": "add-image>0", "question": { - "en": "Changeset added at least one image", - "de": "Changeset fügte mindestens ein Bild hinzu", - "fr": "Le groupe de modifications a ajouté au moins une image", - "nl": "Changeset bevat minstens één afbeelding" + "en": "Changeset added at least one image" } } ] @@ -634,9 +576,7 @@ { "id": "link_to_more", "render": { - "en": "More statistics can be found here", - "de": "Mehr Statistiken gibt es hier", - "fr": "D'autres statistiques sont disponibles ici" + "en": "More statistics can be found here" } }, { @@ -666,4 +606,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index e883af5751..2d7a87e741 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -4,9 +4,6 @@ import { Utils } from "../Utils" export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number] export default class Constants { - static { - console.log("Meta (package:json)", meta) - } public static vNumber = meta.version public static ImgurApiKey = meta.config.api_keys.imgur diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index d79c7bc17f..d51ae560eb 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -1,42 +1,43 @@ -import { DesugaringStep, Each, Fuse, On } from "./Conversion" -import { LayerConfigJson } from "../Json/LayerConfigJson" -import LayerConfig from "../LayerConfig" -import { Utils } from "../../../Utils" -import Constants from "../../Constants" -import { Translation } from "../../../UI/i18n/Translation" -import { LayoutConfigJson } from "../Json/LayoutConfigJson" -import LayoutConfig from "../LayoutConfig" -import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" -import { TagUtils } from "../../../Logic/Tags/TagUtils" -import { ExtractImages } from "./FixImages" -import { And } from "../../../Logic/Tags/And" -import Translations from "../../../UI/i18n/Translations" -import Svg from "../../../Svg" -import FilterConfigJson from "../Json/FilterConfigJson" -import DeleteConfig from "../DeleteConfig" -import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" -import Validators from "../../../UI/InputElement/Validators" -import TagRenderingConfig from "../TagRenderingConfig" +import { DesugaringStep, Each, Fuse, On } from "./Conversion"; +import { LayerConfigJson } from "../Json/LayerConfigJson"; +import LayerConfig from "../LayerConfig"; +import { Utils } from "../../../Utils"; +import Constants from "../../Constants"; +import { Translation } from "../../../UI/i18n/Translation"; +import { LayoutConfigJson } from "../Json/LayoutConfigJson"; +import LayoutConfig from "../LayoutConfig"; +import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"; +import { TagUtils } from "../../../Logic/Tags/TagUtils"; +import { ExtractImages } from "./FixImages"; +import { And } from "../../../Logic/Tags/And"; +import Translations from "../../../UI/i18n/Translations"; +import Svg from "../../../Svg"; +import FilterConfigJson from "../Json/FilterConfigJson"; +import DeleteConfig from "../DeleteConfig"; +import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"; +import Validators from "../../../UI/InputElement/Validators"; +import TagRenderingConfig from "../TagRenderingConfig"; +import { parse as parse_html } from "node-html-parser"; class ValidateLanguageCompleteness extends DesugaringStep { - private readonly _languages: string[] + private readonly _languages: string[]; constructor(...languages: string[]) { super( "Checks that the given object is fully translated in the specified languages", [], "ValidateLanguageCompleteness" - ) - this._languages = languages ?? ["en"] + ); + this._languages = languages ?? ["en"]; } convert( obj: any, context: string ): { result: LayerConfig; errors: string[]; warnings: string[] } { - const errors = [] - const warnings: string[] = [] - const translations = Translation.ExtractAllTranslationsFrom(obj) + const errors = []; + const warnings: string[] = []; + const translations = Translation.ExtractAllTranslationsFrom(obj); for (const neededLanguage of this._languages) { translations .filter( @@ -47,38 +48,38 @@ class ValidateLanguageCompleteness extends DesugaringStep { .forEach((missing) => { errors.push( context + - "A theme should be translation-complete for " + - neededLanguage + - ", but it lacks a translation for " + - missing.context + - ".\n\tThe known translation is " + - missing.tr.textFor("en") - ) - }) + "A theme should be translation-complete for " + + neededLanguage + + ", but it lacks a translation for " + + missing.context + + ".\n\tThe known translation is " + + missing.tr.textFor("en") + ); + }); } return { result: obj, errors, - warnings, - } + warnings + }; } } export class DoesImageExist extends DesugaringStep { - private readonly _knownImagePaths: Set - private readonly _ignore?: Set - private readonly doesPathExist: (path: string) => boolean = undefined + private readonly _knownImagePaths: Set; + private readonly _ignore?: Set; + private readonly doesPathExist: (path: string) => boolean = undefined; constructor( knownImagePaths: Set, checkExistsSync: (path: string) => boolean = undefined, ignore?: Set ) { - super("Checks if an image exists", [], "DoesImageExist") - this._ignore = ignore - this._knownImagePaths = knownImagePaths - this.doesPathExist = checkExistsSync + super("Checks if an image exists", [], "DoesImageExist"); + this._ignore = ignore; + this._knownImagePaths = knownImagePaths; + this.doesPathExist = checkExistsSync; } convert( @@ -86,53 +87,53 @@ export class DoesImageExist extends DesugaringStep { context: string ): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } { if (this._ignore?.has(image)) { - return { result: image } + return { result: image }; } - const errors = [] - const warnings = [] - const information = [] + const errors = []; + const warnings = []; + const information = []; if (image.indexOf("{") >= 0) { - information.push("Ignoring image with { in the path: " + image) - return { result: image } + information.push("Ignoring image with { in the path: " + image); + return { result: image }; } if (image === "assets/SocialImage.png") { - return { result: image } + return { result: image }; } if (image.match(/[a-z]*/)) { if (Svg.All[image + ".svg"] !== undefined) { // This is a builtin img, e.g. 'checkmark' or 'crosshair' - return { result: image } + return { result: image }; } } if (image.startsWith("<") && image.endsWith(">")) { // This is probably HTML, you're on your own here - return { result: image } + return { result: image }; } if (!this._knownImagePaths.has(image)) { if (this.doesPathExist === undefined) { errors.push( `Image with path ${image} not found or not attributed; it is used in ${context}` - ) + ); } else if (!this.doesPathExist(image)) { errors.push( `Image with path ${image} does not exist; it is used in ${context}.\n Check for typo's and missing directories in the path.` - ) + ); } else { errors.push( `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info` - ) + ); } } return { result: image, errors, warnings, - information, - } + information + }; } } @@ -141,11 +142,11 @@ class ValidateTheme extends DesugaringStep { * The paths where this layer is originally saved. Triggers some extra checks * @private */ - private readonly _path?: string - private readonly _isBuiltin: boolean + private readonly _path?: string; + private readonly _isBuiltin: boolean; //private readonly _sharedTagRenderings: Map - private readonly _validateImage: DesugaringStep - private readonly _extractImages: ExtractImages = undefined + private readonly _validateImage: DesugaringStep; + private readonly _extractImages: ExtractImages = undefined; constructor( doesImageExist: DoesImageExist, @@ -153,12 +154,12 @@ class ValidateTheme extends DesugaringStep { isBuiltin: boolean, sharedTagRenderings?: Set ) { - super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme") - this._validateImage = doesImageExist - this._path = path - this._isBuiltin = isBuiltin + super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme"); + this._validateImage = doesImageExist; + this._path = path; + this._isBuiltin = isBuiltin; if (sharedTagRenderings) { - this._extractImages = new ExtractImages(this._isBuiltin, sharedTagRenderings) + this._extractImages = new ExtractImages(this._isBuiltin, sharedTagRenderings); } } @@ -166,11 +167,11 @@ class ValidateTheme extends DesugaringStep { json: LayoutConfigJson, context: string ): { result: LayoutConfigJson; errors: string[]; warnings: string[]; information: string[] } { - const errors = [] - const warnings = [] - const information = [] + const errors = []; + const warnings = []; + const information = []; - const theme = new LayoutConfig(json, this._isBuiltin) + const theme = new LayoutConfig(json, this._isBuiltin); { // Legacy format checks @@ -178,31 +179,31 @@ class ValidateTheme extends DesugaringStep { if (json["units"] !== undefined) { errors.push( "The theme " + - json.id + - " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " - ) + json.id + + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " + ); } if (json["roamingRenderings"] !== undefined) { errors.push( "Theme " + - json.id + - " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" - ) + json.id + + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" + ); } } } if (this._isBuiltin && this._extractImages !== undefined) { // Check images: are they local, are the licenses there, is the theme icon square, ... - const images = this._extractImages.convertStrict(json, "validation") - const remoteImages = images.filter((img) => img.path.indexOf("http") == 0) + const images = this._extractImages.convertStrict(json, "validation"); + const remoteImages = images.filter((img) => img.path.indexOf("http") == 0); for (const remoteImage of remoteImages) { errors.push( "Found a remote image: " + - remoteImage + - " in theme " + - json.id + - ", please download it." - ) + remoteImage + + " in theme " + + json.id + + ", please download it." + ); } for (const image of images) { this._validateImage.convertJoin( @@ -211,30 +212,30 @@ class ValidateTheme extends DesugaringStep { errors, warnings, information - ) + ); } } try { if (this._isBuiltin) { if (theme.id !== theme.id.toLowerCase()) { - errors.push("Theme ids should be in lowercase, but it is " + theme.id) + errors.push("Theme ids should be in lowercase, but it is " + theme.id); } const filename = this._path.substring( this._path.lastIndexOf("/") + 1, this._path.length - 5 - ) + ); if (theme.id !== filename) { errors.push( "Theme ids should be the same as the name.json, but we got id: " + - theme.id + - " and filename " + - filename + - " (" + - this._path + - ")" - ) + theme.id + + " and filename " + + filename + + " (" + + this._path + + ")" + ); } this._validateImage.convertJoin( theme.icon, @@ -242,44 +243,44 @@ class ValidateTheme extends DesugaringStep { errors, warnings, information - ) + ); } - const dups = Utils.Dupiclates(json.layers.map((layer) => layer["id"])) + const dups = Utils.Dupiclates(json.layers.map((layer) => layer["id"])); if (dups.length > 0) { errors.push( `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}` - ) + ); } if (json["mustHaveLanguage"] !== undefined) { const checked = new ValidateLanguageCompleteness( ...json["mustHaveLanguage"] - ).convert(theme, theme.id) + ).convert(theme, theme.id); - errors.push(...checked.errors) + errors.push(...checked.errors); } if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) { // The first key in the the title-field must be english, otherwise the title in the loading page will be the different language - const targetLanguage = theme.title.SupportedLanguages()[0] + const targetLanguage = theme.title.SupportedLanguages()[0]; if (targetLanguage !== "en") { warnings.push( `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key` - ) + ); } // Official, public themes must have a full english translation - const checked = new ValidateLanguageCompleteness("en").convert(theme, theme.id) - errors.push(...checked.errors) + const checked = new ValidateLanguageCompleteness("en").convert(theme, theme.id); + errors.push(...checked.errors); } } catch (e) { - errors.push(e) + errors.push(e); } return { result: json, errors, warnings, - information, - } + information + }; } } @@ -294,7 +295,7 @@ export class ValidateThemeAndLayers extends Fuse { "Validates a theme and the contained layers", new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings), new On("layers", new Each(new ValidateLayer(undefined, isBuiltin, doesImageExist))) - ) + ); } } @@ -304,26 +305,26 @@ class OverrideShadowingCheck extends DesugaringStep { "Checks that an 'overrideAll' does not override a single override", [], "OverrideShadowingCheck" - ) + ); } convert( json: LayoutConfigJson, _: string ): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } { - const overrideAll = json.overrideAll + const overrideAll = json.overrideAll; if (overrideAll === undefined) { - return { result: json } + return { result: json }; } - const errors = [] - const withOverride = json.layers.filter((l) => l["override"] !== undefined) + const errors = []; + const withOverride = json.layers.filter((l) => l["override"] !== undefined); for (const layer of withOverride) { for (const key in overrideAll) { if (key.endsWith("+") || key.startsWith("+")) { // This key will _add_ to the list, not overwrite it - so no warning is needed - continue + continue; } if ( layer["override"][key] !== undefined || @@ -334,19 +335,19 @@ class OverrideShadowingCheck extends DesugaringStep { JSON.stringify(layer["builtin"]) + " has a shadowed property: " + key + - " is overriden by overrideAll of the theme" - errors.push(w) + " is overriden by overrideAll of the theme"; + errors.push(w); } } } - return { result: json, errors } + return { result: json, errors }; } } class MiscThemeChecks extends DesugaringStep { constructor() { - super("Miscelleanous checks on the theme", [], "MiscThemesChecks") + super("Miscelleanous checks on the theme", [], "MiscThemesChecks"); } convert( @@ -358,19 +359,19 @@ class MiscThemeChecks extends DesugaringStep { warnings?: string[] information?: string[] } { - const warnings = [] - const errors = [] + const warnings = []; + const errors = []; if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) { - errors.push("The theme " + json.id + " has no 'layers' defined (" + context + ")") + errors.push("The theme " + json.id + " has no 'layers' defined (" + context + ")"); } if (json.socialImage === "") { - warnings.push("Social image for theme " + json.id + " is the emtpy string") + warnings.push("Social image for theme " + json.id + " is the emtpy string"); } return { result: json, warnings, - errors, - } + errors + }; } } @@ -380,7 +381,7 @@ export class PrevalidateTheme extends Fuse { "Various consistency checks on the raw JSON", new MiscThemeChecks(), new OverrideShadowingCheck() - ) + ); } } @@ -390,7 +391,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep 0)) { - return { result: json } + return { result: json }; } - const tagRendering = new TagRenderingConfig(json) + const tagRendering = new TagRenderingConfig(json); - const errors = [] + const errors = []; for (let i = 0; i < tagRendering.mappings.length; i++) { - const mapping = tagRendering.mappings[i] + const mapping = tagRendering.mappings[i]; if (!mapping.addExtraTags) { - continue + continue; } - const keysInMapping = new Set(mapping.if.usedKeys()) + const keysInMapping = new Set(mapping.if.usedKeys()); - const keysInAddExtraTags = mapping.addExtraTags.map((t) => t.key) + const keysInAddExtraTags = mapping.addExtraTags.map((t) => t.key); - const duplicateKeys = keysInAddExtraTags.filter((k) => keysInMapping.has(k)) + const duplicateKeys = keysInAddExtraTags.filter((k) => keysInMapping.has(k)); if (duplicateKeys.length > 0) { errors.push( "At " + - context + - ".mappings[" + - i + - "]: AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + - duplicateKeys.join(", ") - ) + context + + ".mappings[" + + i + + "]: AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + + duplicateKeys.join(", ") + ); } } return { result: json, - errors, - } + errors + }; } } export class DetectShadowedMappings extends DesugaringStep { - private readonly _calculatedTagNames: string[] + private readonly _calculatedTagNames: string[]; constructor(layerConfig?: LayerConfigJson) { - super("Checks that the mappings don't shadow each other", [], "DetectShadowedMappings") - this._calculatedTagNames = DetectShadowedMappings.extractCalculatedTagNames(layerConfig) + super("Checks that the mappings don't shadow each other", [], "DetectShadowedMappings"); + this._calculatedTagNames = DetectShadowedMappings.extractCalculatedTagNames(layerConfig); } /** @@ -457,11 +458,11 @@ export class DetectShadowedMappings extends DesugaringStep { if (ct.indexOf(":=") >= 0) { - return ct.split(":=")[0] + return ct.split(":=")[0]; } - return ct.split("=")[0] + return ct.split("=")[0]; }) ?? [] - ) + ); } /** @@ -501,40 +502,40 @@ export class DetectShadowedMappings extends DesugaringStep { - const ctx = `${context}.mappings[${i}]` - const ifTags = TagUtils.Tag(m.if, ctx) - const hideInAnswer = m["hideInAnswer"] + const ctx = `${context}.mappings[${i}]`; + const ifTags = TagUtils.Tag(m.if, ctx); + const hideInAnswer = m["hideInAnswer"]; if (hideInAnswer !== undefined && hideInAnswer !== false && hideInAnswer !== true) { - let conditionTags = TagUtils.Tag(hideInAnswer) + let conditionTags = TagUtils.Tag(hideInAnswer); // Merge the condition too! - return new And([conditionTags, ifTags]) + return new And([conditionTags, ifTags]); } - return ifTags - }) + return ifTags; + }); for (let i = 0; i < json.mappings.length; i++) { if (!parsedConditions[i].isUsableAsAnswer()) { // There is no straightforward way to convert this mapping.if into a properties-object, so we simply skip this one // Yes, it might be shadowed, but running this check is to difficult right now - continue + continue; } - const keyValues = parsedConditions[i].asChange(defaultProperties) - const properties = {} + const keyValues = parsedConditions[i].asChange(defaultProperties); + const properties = {}; keyValues.forEach(({ k, v }) => { - properties[k] = v - }) + properties[k] = v; + }); for (let j = 0; j < i; j++) { - const doesMatch = parsedConditions[j].matchesProperties(properties) + const doesMatch = parsedConditions[j].matchesProperties(properties); if ( doesMatch && json.mappings[j]["hideInAnswer"] === true && @@ -542,15 +543,15 @@ export class DetectShadowedMappings extends DesugaringStep= 0 - const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? []) - const ctx = `${context}.mappings[${i}]` + const mapping = json.mappings[i]; + const ignore = mapping["#"]?.indexOf(ignoreToken) >= 0; + const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? []); + const ctx = `${context}.mappings[${i}]`; if (images.length > 0) { if (!ignore) { errors.push( `${ctx}: A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": \` instead. The images found are ${images.join( ", " )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged` - ) + ); } else { information.push( `${ctx}: Ignored image ${images.join( ", " )} in 'then'-clause of a mapping as this check has been disabled` - ) + ); for (const image of images) { - this._doesImageExist.convertJoin(image, ctx, errors, warnings, information) + this._doesImageExist.convertJoin(image, ctx, errors, warnings, information); } } } else if (ignore) { - warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`) + warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`); } } @@ -654,17 +655,72 @@ export class DetectMappingsWithImages extends DesugaringStep> { + constructor() { + super("Given a possible set of translations, validates that does have `rel='noopener'` set", [], "ValidatePossibleLinks"); + } + + public isTabnabbingProne(str: string): boolean { + const p = parse_html(str); + const links = Array.from(p.getElementsByTagName("a")); + if (links.length == 0) { + return false; } + for (const link of Array.from(links)) { + if (link.getAttribute("target") !== "_blank") { + continue; + } + const rel = new Set(link.getAttribute("rel")?.split(" ") ?? []); + if (rel.has("noopener")) { + continue; + } + const source = link.getAttribute("href"); + if (source.startsWith("http")) { + // No variable part - we assume the link is safe + continue; + } + return true; + } + return false; + } + + convert(json: string | Record, context: string): { + result: string | Record; + errors?: string[]; + warnings?: string[]; + information?: string[] + } { + + const errors = []; + if (typeof json === "string") { + if (this.isTabnabbingProne(json)) { + errors.push("At " + context + ": the string " + json + " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping"); + } + } else { + for (const k in json) { + if (this.isTabnabbingProne(json[k])) { + errors.push(`At ${context}: the translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`); + } + } + } + return { + errors, + result: json + }; } } class MiscTagRenderingChecks extends DesugaringStep { - private _options: { noQuestionHintCheck: boolean } + private _options: { noQuestionHintCheck: boolean }; constructor(options: { noQuestionHintCheck: boolean }) { - super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks") - this._options = options + super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks"); + this._options = options; } convert( @@ -676,25 +732,26 @@ class MiscTagRenderingChecks extends DesugaringStep { warnings?: string[] information?: string[] } { - const warnings = [] - const errors = [] + const warnings = []; + const errors = []; if (json["special"] !== undefined) { errors.push( "At " + - context + - ': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' - ) + context + + ": detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`" + ); } if (json["group"]) { errors.push( "At " + - context + - ': groups are deprecated, use `"label": ["' + - json["group"] + - '"]` instead' - ) + context + + ": groups are deprecated, use `\"label\": [\"" + + json["group"] + + "\"]` instead" + ); } - const freeformType = json["freeform"]?.["type"] + + const freeformType = json["freeform"]?.["type"]; if (freeformType) { if (Validators.availableTypes.indexOf(freeformType) < 0) { throw ( @@ -704,14 +761,14 @@ class MiscTagRenderingChecks extends DesugaringStep { freeformType + "; try one of " + Validators.availableTypes.join(", ") - ) + ); } } return { result: json, errors, - warnings, - } + warnings + }; } } @@ -726,8 +783,16 @@ export class ValidateTagRenderings extends Fuse { new DetectShadowedMappings(layerConfig), new DetectConflictingAddExtraTags(), new DetectMappingsWithImages(doesImageExist), + new On("render", + new ValidatePossibleLinks()), + new On("question", + new ValidatePossibleLinks()), + new On("questionHint", + new ValidatePossibleLinks()), + new On("mappings", + new Each(new On("then", new ValidatePossibleLinks()))), new MiscTagRenderingChecks(options) - ) + ); } } @@ -736,41 +801,41 @@ export class ValidateLayer extends DesugaringStep { * The paths where this layer is originally saved. Triggers some extra checks * @private */ - private readonly _path?: string - private readonly _isBuiltin: boolean - private readonly _doesImageExist: DoesImageExist + private readonly _path?: string; + private readonly _isBuiltin: boolean; + private readonly _doesImageExist: DoesImageExist; constructor(path: string, isBuiltin: boolean, doesImageExist: DoesImageExist) { - super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") - this._path = path - this._isBuiltin = isBuiltin - this._doesImageExist = doesImageExist + super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer"); + this._path = path; + this._isBuiltin = isBuiltin; + this._doesImageExist = doesImageExist; } convert( json: LayerConfigJson, context: string ): { result: LayerConfigJson; errors: string[]; warnings?: string[]; information?: string[] } { - const errors = [] - const warnings = [] - const information = [] - context = "While validating a layer: " + context + const errors = []; + const warnings = []; + const information = []; + context = "While validating a layer: " + context; if (typeof json === "string") { - errors.push(context + ": This layer hasn't been expanded: " + json) + errors.push(context + ": This layer hasn't been expanded: " + json); return { result: null, - errors, - } + errors + }; } if (json.source === "special") { if (!Constants.priviliged_layers.find((x) => x == json.id)) { errors.push( context + - ": layer " + - json.id + - " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" - ) + ": layer " + + json.id + + " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" + ); } } @@ -778,49 +843,49 @@ export class ValidateLayer extends DesugaringStep { if (json.title === undefined && json.source !== "special:library") { errors.push( context + - ": this layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." - ) + ": this layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." + ); } if (json.title === null) { information.push( context + - ": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." - ) + ": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." + ); } } if (json["builtin"] !== undefined) { - errors.push(context + ": This layer hasn't been expanded: " + json) + errors.push(context + ": This layer hasn't been expanded: " + json); return { result: null, - errors, - } + errors + }; } if (json.minzoom > Constants.minZoomLevelToAddNewPoint) { ;(json.presets?.length > 0 ? errors : warnings).push( `At ${context}: minzoom is ${json.minzoom}, this should be at most ${Constants.minZoomLevelToAddNewPoint} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates` - ) + ); } { // duplicate ids in tagrenderings check const duplicates = Utils.Dedup( Utils.Dupiclates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) - ) + ); if (duplicates.length > 0) { - console.log(json.tagRenderings) + console.log(json.tagRenderings); errors.push( "At " + - context + - ": some tagrenderings have a duplicate id: " + - duplicates.join(", ") - ) + context + + ": some tagrenderings have a duplicate id: " + + duplicates.join(", ") + ); } } if (json.deletion !== undefined && json.deletion instanceof DeleteConfig) { if (json.deletion.softDeletionTags === undefined) { - warnings.push("No soft-deletion tags in deletion block for layer " + json.id) + warnings.push("No soft-deletion tags in deletion block for layer " + json.id); } } @@ -831,9 +896,9 @@ export class ValidateLayer extends DesugaringStep { if (json["overpassTags"] !== undefined) { errors.push( "Layer " + - json.id + - 'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": }\' instead of "overpassTags": (note: this isn\'t your fault, the custom theme generator still spits out the old format)' - ) + json.id + + "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": }' instead of \"overpassTags\": (note: this isn't your fault, the custom theme generator still spits out the old format)" + ); } const forbiddenTopLevel = [ "icon", @@ -844,88 +909,88 @@ export class ValidateLayer extends DesugaringStep { "width", "color", "colour", - "iconOverlays", - ] + "iconOverlays" + ]; for (const forbiddenKey of forbiddenTopLevel) { if (json[forbiddenKey] !== undefined) errors.push( context + - ": layer " + - json.id + - " still has a forbidden key " + - forbiddenKey - ) + ": layer " + + json.id + + " still has a forbidden key " + + forbiddenKey + ); } if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { errors.push( context + - ": layer " + - json.id + - " contains an old 'hideUnderlayingFeaturesMinPercentage'" - ) + ": layer " + + json.id + + " contains an old 'hideUnderlayingFeaturesMinPercentage'" + ); } if ( json.isShown !== undefined && (json.isShown["render"] !== undefined || json.isShown["mappings"] !== undefined) ) { - warnings.push(context + " has a tagRendering as `isShown`") + warnings.push(context + " has a tagRendering as `isShown`"); } } if (this._isBuiltin) { // Check location of layer file - const expected: string = `assets/layers/${json.id}/${json.id}.json` + const expected: string = `assets/layers/${json.id}/${json.id}.json`; if (this._path != undefined && this._path.indexOf(expected) < 0) { errors.push( "Layer is in an incorrect place. The path is " + - this._path + - ", but expected " + - expected - ) + this._path + + ", but expected " + + expected + ); } } if (this._isBuiltin) { // Check for correct IDs if (json.tagRenderings?.some((tr) => tr["id"] === "")) { - const emptyIndexes: number[] = [] + const emptyIndexes: number[] = []; for (let i = 0; i < json.tagRenderings.length; i++) { - const tagRendering = json.tagRenderings[i] + const tagRendering = json.tagRenderings[i]; if (tagRendering["id"] === "") { - emptyIndexes.push(i) + emptyIndexes.push(i); } } errors.push( `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${context}.tagRenderings.[${emptyIndexes.join( "," )}])` - ) + ); } const duplicateIds = Utils.Dupiclates( (json.tagRenderings ?? []) ?.map((f) => f["id"]) .filter((id) => id !== "questions") - ) + ); if (duplicateIds.length > 0 && !Utils.runningFromConsole) { errors.push( `Some tagRenderings have a duplicate id: ${duplicateIds} (at ${context}.tagRenderings)` - ) + ); } if (json.description === undefined) { if (typeof json.source === null) { - errors.push(context + ": A priviliged layer must have a description") + errors.push(context + ": A priviliged layer must have a description"); } else { - warnings.push(context + ": A builtin layer should have a description") + warnings.push(context + ": A builtin layer should have a description"); } } } if (json.filter) { - const r = new On("filter", new Each(new ValidateFilter())).convert(json, context) - warnings.push(...(r.warnings ?? [])) - errors.push(...(r.errors ?? [])) - information.push(...(r.information ?? [])) + const r = new On("filter", new Each(new ValidateFilter())).convert(json, context); + warnings.push(...(r.warnings ?? [])); + errors.push(...(r.errors ?? [])); + information.push(...(r.information ?? [])); } if (json.tagRenderings !== undefined) { @@ -933,74 +998,74 @@ export class ValidateLayer extends DesugaringStep { "tagRenderings", new Each( new ValidateTagRenderings(json, this._doesImageExist, { - noQuestionHintCheck: json["#"]?.indexOf("no-question-hint-check") >= 0, + noQuestionHintCheck: json["#"]?.indexOf("no-question-hint-check") >= 0 }) ) - ).convert(json, context) - warnings.push(...(r.warnings ?? [])) - errors.push(...(r.errors ?? [])) - information.push(...(r.information ?? [])) + ).convert(json, context); + warnings.push(...(r.warnings ?? [])); + errors.push(...(r.errors ?? [])); + information.push(...(r.information ?? [])); } { const hasCondition = json.mapRendering?.filter( (mr) => mr["icon"] !== undefined && mr["icon"]["condition"] !== undefined - ) + ); if (hasCondition?.length > 0) { errors.push( "At " + - context + - ":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" + - JSON.stringify(hasCondition, null, " ") - ) + context + + ":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" + + JSON.stringify(hasCondition, null, " ") + ); } } if (json.presets !== undefined) { if (typeof json.source === "string") { - throw "A special layer cannot have presets" + throw "A special layer cannot have presets"; } // Check that a preset will be picked up by the layer itself - const baseTags = TagUtils.Tag(json.source["osmTags"]) + const baseTags = TagUtils.Tag(json.source["osmTags"]); for (let i = 0; i < json.presets.length; i++) { - const preset = json.presets[i] + const preset = json.presets[i]; const tags: { k: string; v: string }[] = new And( preset.tags.map((t) => TagUtils.Tag(t)) - ).asChange({ id: "node/-1" }) - const properties = {} + ).asChange({ id: "node/-1" }); + const properties = {}; for (const tag of tags) { - properties[tag.k] = tag.v + properties[tag.k] = tag.v; } - const doMatch = baseTags.matchesProperties(properties) + const doMatch = baseTags.matchesProperties(properties); if (!doMatch) { errors.push( context + - ".presets[" + - i + - "]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + - JSON.stringify(properties) + - "\n The required tags are: " + - baseTags.asHumanString(false, false, {}) - ) + ".presets[" + + i + + "]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + + JSON.stringify(properties) + + "\n The required tags are: " + + baseTags.asHumanString(false, false, {}) + ); } } } } catch (e) { - errors.push(e) + errors.push(e); } return { result: json, errors, warnings, - information, - } + information + }; } } export class ValidateFilter extends DesugaringStep { constructor() { - super("Detect common errors in the filters", [], "ValidateFilter") + super("Detect common errors in the filters", [], "ValidateFilter"); } convert( @@ -1014,22 +1079,22 @@ export class ValidateFilter extends DesugaringStep { } { if (typeof filter === "string") { // Calling another filter, we skip - return { result: filter } + return { result: filter }; } - const errors = [] + const errors = []; for (const option of filter.options) { for (let i = 0; i < option.fields?.length ?? 0; i++) { - const field = option.fields[i] - const type = field.type ?? "string" + const field = option.fields[i]; + const type = field.type ?? "string"; if (Validators.availableTypes.find((t) => t === type) === undefined) { const err = `Invalid filter: ${type} is not a valid textfield type (at ${context}.fields[${i}])\n\tTry one of ${Array.from( Validators.availableTypes - ).join(",")}` - errors.push(err) + ).join(",")}`; + errors.push(err); } } } - return { result: filter, errors } + return { result: filter, errors }; } } @@ -1042,7 +1107,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{ "Tries to detect layers where a shared filter can be used (or where similar filters occur)", [], "DetectDuplicateFilters" - ) + ); } convert( @@ -1054,11 +1119,11 @@ export class DetectDuplicateFilters extends DesugaringStep<{ warnings?: string[] information?: string[] } { - const errors: string[] = [] - const warnings: string[] = [] - const information: string[] = [] + const errors: string[] = []; + const warnings: string[] = []; + const information: string[] = []; - const { layers, themes } = json + const { layers, themes } = json; const perOsmTag = new Map< string, { @@ -1066,24 +1131,24 @@ export class DetectDuplicateFilters extends DesugaringStep<{ layout: LayoutConfigJson | undefined filter: FilterConfigJson }[] - >() + >(); for (const layer of layers) { - this.addLayerFilters(layer, perOsmTag) + this.addLayerFilters(layer, perOsmTag); } for (const theme of themes) { if (theme.id === "personal") { - continue + continue; } for (const layer of theme.layers) { if (typeof layer === "string") { - continue + continue; } if (layer["builtin"] !== undefined) { - continue + continue; } - this.addLayerFilters(layer, perOsmTag, theme) + this.addLayerFilters(layer, perOsmTag, theme); } } @@ -1091,25 +1156,25 @@ export class DetectDuplicateFilters extends DesugaringStep<{ perOsmTag.forEach((value, key) => { if (value.length <= 1) { // Seen this key just once, it is unique - return + return; } - let msg = "Possible duplicate filter: " + key + let msg = "Possible duplicate filter: " + key; for (const { filter, layer, layout } of value) { - let id = "" + let id = ""; if (layout !== undefined) { - id = layout.id + ":" + id = layout.id + ":"; } - msg += `\n - ${id}${layer.id}.${filter.id}` + msg += `\n - ${id}${layer.id}.${filter.id}`; } - warnings.push(msg) - }) + warnings.push(msg); + }); return { result: json, errors, warnings, - information, - } + information + }; } /** @@ -1128,33 +1193,33 @@ export class DetectDuplicateFilters extends DesugaringStep<{ layout?: LayoutConfigJson | undefined ): void { if (layer.filter === undefined || layer.filter === null) { - return + return; } if (layer.filter["sameAs"] !== undefined) { - return + return; } for (const filter of <(string | FilterConfigJson)[]>layer.filter) { if (typeof filter === "string") { - continue + continue; } if (filter["#"]?.indexOf("ignore-possible-duplicate") >= 0) { - continue + continue; } for (const option of filter.options) { if (option.osmTags === undefined) { - continue + continue; } - const key = JSON.stringify(option.osmTags) + const key = JSON.stringify(option.osmTags); if (!perOsmTag.has(key)) { - perOsmTag.set(key, []) + perOsmTag.set(key, []); } perOsmTag.get(key).push({ layer, filter, - layout, - }) + layout + }); } } } diff --git a/src/UI/i18n/Translation.ts b/src/UI/i18n/Translation.ts index 7a8eb6be32..26fe708061 100644 --- a/src/UI/i18n/Translation.ts +++ b/src/UI/i18n/Translation.ts @@ -233,6 +233,7 @@ export class Translation extends BaseUIElement { * * new Translation({"en": "This is a sentence. This is another sentence"}).FirstSentence().textFor("en") // "This is a sentence" * new Translation({"en": "This is a sentence
This is another sentence"}).FirstSentence().textFor("en") // "This is a sentence" + * new Translation({"en": "This is a sentence
This is another sentence"}).FirstSentence().textFor("en") // "This is a sentence" * new Translation({"en": "This is a sentence with a bold word. This is another sentence"}).FirstSentence().textFor("en") // "This is a sentence with a bold word" * @constructor */ @@ -243,7 +244,7 @@ export class Translation extends BaseUIElement { continue } let txt = this.translations[lng] - txt = txt.replace(/(\.|).*/, "") + txt = txt.replace(/(\.||
).*/, "") txt = Utils.EllipsesAfter(txt, 255) tr[lng] = txt.trim() } diff --git a/src/Utils.ts b/src/Utils.ts index 3a33ce2206..9f2f877d71 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,5 +1,4 @@ import colors from "./assets/colors.json" -import { HTMLElement } from "node-html-parser" export class Utils { /** @@ -490,7 +489,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be "\nThe value is", v ) - v = (v.InnerConstructElement())?.textContent + v = v.InnerConstructElement()?.textContent } if (typeof v !== "string") {