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 {
- private readonly _doesImageExist: DoesImageExist
+ private readonly _doesImageExist: DoesImageExist;
constructor(doesImageExist: DoesImageExist) {
super(
"Checks that 'then'clauses in mappings don't have images, but use 'icon' instead",
[],
"DetectMappingsWithImages"
- )
- this._doesImageExist = doesImageExist
+ );
+ this._doesImageExist = doesImageExist;
}
/**
@@ -615,38 +616,38 @@ export class DetectMappingsWithImages 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") {