forked from MapComplete/MapComplete
Scripts: rework more of the documentation files, prepere for translated docs
This commit is contained in:
parent
4d9133fed3
commit
84cc512271
7 changed files with 462 additions and 299 deletions
|
@ -1,4 +1,3 @@
|
||||||
import BaseUIElement from "../src/UI/BaseUIElement"
|
|
||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
||||||
import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts"
|
import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts"
|
||||||
import SimpleMetaTaggers from "../src/Logic/SimpleMetaTagger"
|
import SimpleMetaTaggers from "../src/Logic/SimpleMetaTagger"
|
||||||
|
@ -34,6 +33,8 @@ import * as unitUsage from "../Docs/Schemas/UnitConfigJson.schema.json"
|
||||||
import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson"
|
import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson"
|
||||||
import { ServerSourceInfo, SourceOverview } from "../src/Models/SourceOverview"
|
import { ServerSourceInfo, SourceOverview } from "../src/Models/SourceOverview"
|
||||||
import { Lists } from "../src/Utils/Lists"
|
import { Lists } from "../src/Utils/Lists"
|
||||||
|
import { Translation, TypedTranslation } from "../src/UI/i18n/Translation"
|
||||||
|
import language_translations from "../src/assets/language_translations.json"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use
|
* Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use
|
||||||
|
@ -177,8 +178,13 @@ export class GenerateDocs extends Script {
|
||||||
this.generateBuiltinUnits()
|
this.generateBuiltinUnits()
|
||||||
await this.generateSourcesOverview()
|
await this.generateSourcesOverview()
|
||||||
|
|
||||||
|
if (!existsSync("./Docs/themes_nl")) {
|
||||||
|
mkdirSync("./Docs/themes_nl")
|
||||||
|
}
|
||||||
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
|
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
|
||||||
this.generateForTheme(theme)
|
this.generateForTheme(theme)
|
||||||
|
// this.generateForTheme(theme, { path: "./Docs/themes_nl", lang: "nl" })
|
||||||
|
|
||||||
ScriptUtils.erasableLog("Written docs for theme", theme.id)
|
ScriptUtils.erasableLog("Written docs for theme", theme.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -247,7 +253,7 @@ export class GenerateDocs extends Script {
|
||||||
|
|
||||||
let md = markdown
|
let md = markdown
|
||||||
|
|
||||||
if (options?.noTableOfContents !== false) {
|
if (options?.noTableOfContents !== true) {
|
||||||
md = TableOfContents.insertTocIntoMd(md, lang, options?.tocMaxDepth)
|
md = TableOfContents.insertTocIntoMd(md, lang, options?.tocMaxDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,15 +266,17 @@ export class GenerateDocs extends Script {
|
||||||
const warnAutomated =
|
const warnAutomated =
|
||||||
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n"
|
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n"
|
||||||
|
|
||||||
const generatedFrom = [
|
|
||||||
"This document is autogenerated from",
|
const sources = autogenSource.map(
|
||||||
autogenSource
|
(s) => `[${s}](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/${s})`).join(", ")
|
||||||
.map(
|
|
||||||
(s) =>
|
const generatedFrom =
|
||||||
`[${s}](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/${s})`
|
new TypedTranslation<{ sources }>({
|
||||||
)
|
en: "This document is autogenerated from {sources}",
|
||||||
.join(", "),
|
nl: "Dit document werd gegenereerd op basis van {sources}",
|
||||||
].join(" ")
|
}).Subs({ sources }).textFor(lang)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
writeFileSync(filename, warnAutomated + md + "\n\n" + generatedFrom + "\n")
|
writeFileSync(filename, warnAutomated + md + "\n\n" + generatedFrom + "\n")
|
||||||
}
|
}
|
||||||
|
@ -417,7 +425,7 @@ export class GenerateDocs extends Script {
|
||||||
if (inlineSource !== undefined) {
|
if (inlineSource !== undefined) {
|
||||||
source = `assets/themes/${inlineSource}/${inlineSource}.json`
|
source = `assets/themes/${inlineSource}/${inlineSource}.json`
|
||||||
}
|
}
|
||||||
this.writeMarkdownFile(targetDirectory+ "/" + layer.id + ".md", element, [source])
|
this.writeMarkdownFile(targetDirectory + "/" + layer.id + ".md", element, [source], { lang })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,45 +517,73 @@ export class GenerateDocs extends Script {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateForTheme(theme: ThemeConfig): void {
|
private generateForTheme(theme: ThemeConfig, options?: { path?: string, lang?: string }): void {
|
||||||
const allLayers = AllSharedLayers.sharedLayers
|
const allLayers = AllSharedLayers.sharedLayers
|
||||||
const layersToShow = theme.layers.filter(
|
const layersToShow = theme.layers.filter(
|
||||||
(l) => l.id !== "favourite" && Constants.added_by_default.indexOf(<any>l.id) < 0
|
(l) => l.id !== "favourite" && Constants.added_by_default.indexOf(<any>l.id) < 0
|
||||||
)
|
)
|
||||||
|
const lang = options?.lang ?? "en"
|
||||||
const layersToInline = layersToShow.filter((l) => !allLayers.has(l.id))
|
const layersToInline = layersToShow.filter((l) => !allLayers.has(l.id))
|
||||||
const el = [
|
const el = [
|
||||||
[
|
[
|
||||||
"##",
|
"##",
|
||||||
theme.title,
|
theme.title.textFor(lang),
|
||||||
"(",
|
"(",
|
||||||
`[${theme.id}](https://mapcomplete.org/${theme.id})`,
|
`[${theme.id}](https://mapcomplete.org/${theme.id})`,
|
||||||
")",
|
")",
|
||||||
].join(" "),
|
].join(" "),
|
||||||
|
|
||||||
"_This document details some technical information about this MapComplete theme, mostly about the attributes used in the theme. Various links point toward more information about the attributes, e.g. to the OpenStreetMap-wiki, to TagInfo or tools creating statistics_",
|
|
||||||
"The theme introduction reads:\n",
|
|
||||||
"> " + parse_html(theme.description.textFor("en")).textContent.replace(/\n/g, " "),
|
"> " + parse_html(theme.description.textFor("en")).textContent.replace(/\n/g, " "),
|
||||||
"",
|
"",
|
||||||
"This theme contains the following layers:",
|
new Translation({
|
||||||
|
en: "This theme contains the following layers:",
|
||||||
|
nl: "Dit kaartthema bevat de volgende lagen",
|
||||||
|
}).textFor(lang),
|
||||||
MarkdownUtils.list(
|
MarkdownUtils.list(
|
||||||
layersToShow.map((l) => {
|
layersToShow.map((l) => {
|
||||||
if (allLayers.has(l.id)) {
|
if (allLayers.has(l.id)) {
|
||||||
return `[${l.id}](../Layers/${l.id}.md)`
|
return `[${l.id}](../Layers/${l.id}.md)`
|
||||||
}
|
}
|
||||||
return `[${l.id} (defined in this theme)](#${l.id.trim().replace(/ /g, "-")})`
|
return `[${l.id} (${l.name?.textFor(lang)})](#${l.id.trim().replace(/ /g, "-")})`
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
"Available languages:",
|
new Translation(
|
||||||
MarkdownUtils.list(theme.language.filter((ln) => ln !== "_context")),
|
{
|
||||||
"# Layers defined in this theme configuration file",
|
en: "This theme is available in the following languages:",
|
||||||
"These layers can not be reused in different themes.",
|
nl: "Deze kaart is beschikbaar in de volgende talen:",
|
||||||
...layersToInline.map((l) => l.generateDocumentation({ usedInThemes: null })),
|
},
|
||||||
].join("\n")
|
).textFor(lang),
|
||||||
|
MarkdownUtils.list(theme.language.filter((ln) => ln !== "_context").map(ln => {
|
||||||
|
if (language_translations[ln]) {
|
||||||
|
return ln + " (" + new Translation(language_translations[ln]).textFor(lang) + ")"
|
||||||
|
} else {
|
||||||
|
return ln
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
]
|
||||||
|
|
||||||
|
if (layersToInline.length > 0) {
|
||||||
|
el.push(MarkdownUtils.title(1, new Translation({
|
||||||
|
en: "Layers defined in this theme configuration file",
|
||||||
|
nl: "Lagen gedefinieerd in dit kaartthema-bestand",
|
||||||
|
})).textFor(lang))
|
||||||
|
el.push(MarkdownUtils.list(layersToInline.map(id => `[${id}](#${id})`)))
|
||||||
|
|
||||||
|
el.push(new Translation({
|
||||||
|
en: "These layers can not be reused in different themes.",
|
||||||
|
nl: "Deze lagen kunnen niet in andere kaartthemas hergebruikt worden",
|
||||||
|
}).textFor(lang))
|
||||||
|
el.push(
|
||||||
|
...layersToInline.map((l) => l.generateDocumentation({ usedInThemes: null, lang })),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = options?.path ?? "./Docs/Themes"
|
||||||
this.writeMarkdownFile(
|
this.writeMarkdownFile(
|
||||||
"./Docs/Themes/" + theme.id + ".md",
|
path + "/" + theme.id + ".md",
|
||||||
el,
|
el.join("\n"),
|
||||||
[`assets/themes/${theme.id}/${theme.id}.json`],
|
[`assets/themes/${theme.id}/${theme.id}.json`],
|
||||||
{ noTableOfContents: true }
|
{ noTableOfContents: true, lang },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -681,7 +717,7 @@ export class GenerateDocs extends Script {
|
||||||
* Generates the documentation for the layers overview page
|
* Generates the documentation for the layers overview page
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
private generateLayerOverviewText(): BaseUIElement {
|
private generateLayerOverviewText(): void {
|
||||||
for (const id of Constants.priviliged_layers) {
|
for (const id of Constants.priviliged_layers) {
|
||||||
if (!AllSharedLayers.sharedLayers.has(id)) {
|
if (!AllSharedLayers.sharedLayers.has(id)) {
|
||||||
console.error("Priviliged layer definition not found: " + id)
|
console.error("Priviliged layer definition not found: " + id)
|
||||||
|
|
|
@ -252,12 +252,24 @@ export class RegexTag extends TagsFilter {
|
||||||
return this.invert
|
return this.invert
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString(linkToWiki: boolean) {
|
||||||
if (typeof this.key === "string") {
|
if (typeof this.key === "string") {
|
||||||
const oper = typeof this.value === "string" ? "=" : "~"
|
const oper = typeof this.value === "string" ? "=" : "~"
|
||||||
return `${this.key}${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}`
|
if(linkToWiki){
|
||||||
|
return `[${this.key}](https://wiki.osm.org/wiki/Key:${this.key})\`${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}\``
|
||||||
|
}
|
||||||
|
|
||||||
|
const v= `${this.key}${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}`
|
||||||
|
if(linkToWiki){
|
||||||
|
return `\`${v}\``
|
||||||
|
}
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
return `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
|
const v = `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
|
||||||
|
if(linkToWiki){
|
||||||
|
return `\`${v}\``
|
||||||
|
}
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -171,9 +171,7 @@ export class SourceOverview {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
console.log(">>> SPlitting", url)
|
|
||||||
const split = url.split(",https://")
|
const split = url.split(",https://")
|
||||||
console.log("Multisplit:", split)
|
|
||||||
url = split[0]
|
url = split[0]
|
||||||
let urlClipped = url
|
let urlClipped = url
|
||||||
if (url.indexOf("?") > 0) {
|
if (url.indexOf("?") > 0) {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { Utils } from "../../Utils"
|
||||||
import { RegexTag } from "../../Logic/Tags/RegexTag"
|
import { RegexTag } from "../../Logic/Tags/RegexTag"
|
||||||
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
||||||
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
|
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
|
||||||
import { Lists } from "../../Utils/Lists"
|
|
||||||
|
|
||||||
export type FilterConfigOption = {
|
export type FilterConfigOption = {
|
||||||
question: Translation
|
question: Translation
|
||||||
|
@ -223,23 +222,29 @@ export default class FilterConfig {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenerateDocs(): string {
|
public generateDocs(options?: { lang?: string }): string {
|
||||||
const hasField = this.options.some((opt) => opt.fields?.length > 0)
|
const lang = options?.lang ?? "en"
|
||||||
return MarkdownUtils.table(
|
const header = ["id", { en: "Default", nl: "Standaard" }, { en: "Question", nl: "Vraag" }, {
|
||||||
Lists.noNull(["id", "question", "osmTags", hasField ? "fields" : undefined]),
|
en: "Attributes",
|
||||||
this.options.map((opt, i) => {
|
nl: "Attributen",
|
||||||
const isDefault = this.options.length > 1 && (this.defaultSelection ?? 0) == i
|
},
|
||||||
return <string[]>(
|
{ en: "Fields", nl: "Velden" },
|
||||||
Lists.noNull([
|
].map(x => typeof x === "string" ? new Translation({ "*": x }) : new Translation(x))
|
||||||
this.id + "." + i,
|
.map(tr => tr.textFor(lang))
|
||||||
isDefault ? `*${opt.question.txt}* (default)` : opt.question,
|
|
||||||
opt.osmTags?.asHumanString() ?? "",
|
const content: string[][] = []
|
||||||
opt.fields?.length > 0
|
for (let i = 0; i < this.options.length; i++) {
|
||||||
? opt.fields.map((f) => f.name + " (" + f.type + ")").join(" ")
|
const opt = this.options[i]
|
||||||
: undefined,
|
const isDefault = this.options.length > 1 && (this.defaultSelection ?? 0) == i
|
||||||
])
|
content.push([
|
||||||
)
|
this.id + "." + i,
|
||||||
})
|
isDefault ? "✔️" : "",
|
||||||
)
|
opt.question.textFor(lang),
|
||||||
|
opt.osmTags?.asHumanString(true) ?? (new Translation({en: "_None - show all_", nl: "_Geen - toon alles_"})).textFor(lang),
|
||||||
|
opt.fields?.map((f) => f.name + " (" + f.type + ")").join(" "),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarkdownUtils.table(header, content, { dropEmptyColumns: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Translation } from "../../UI/i18n/Translation"
|
import { Translation, TypedTranslation } from "../../UI/i18n/Translation"
|
||||||
import SourceConfig from "./SourceConfig"
|
import SourceConfig from "./SourceConfig"
|
||||||
import TagRenderingConfig from "./TagRenderingConfig"
|
import TagRenderingConfig from "./TagRenderingConfig"
|
||||||
import PresetConfig, { PreciseInput } from "./PresetConfig"
|
import PresetConfig, { PreciseInput } from "./PresetConfig"
|
||||||
|
@ -393,52 +393,6 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
return this.mapRendering.some((r) => r.location.has("point"))
|
return this.mapRendering.some((r) => r.location.has("point"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A quick overview table of all the elements in the popup-box
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private generateDocumentationQuickTable(): string {
|
|
||||||
return MarkdownUtils.table(
|
|
||||||
["id", "question", "labels", "freeform key"],
|
|
||||||
this.tagRenderings
|
|
||||||
.filter((tr) => tr.labels.indexOf("ignore_docs") < 0)
|
|
||||||
.map((tr) => {
|
|
||||||
let key = "_Multiple choice only_"
|
|
||||||
if (tr.freeform) {
|
|
||||||
const type = `[${tr.freeform.type}](../SpecialInputElements.md#${tr.freeform.type})`
|
|
||||||
|
|
||||||
key = `*[${tr.freeform.key}](https://wiki.osm.org/wiki/Key:${tr.freeform.key})* (${type})`
|
|
||||||
}
|
|
||||||
let origDef = ""
|
|
||||||
if (tr._definedIn) {
|
|
||||||
let [layer, id] = tr._definedIn
|
|
||||||
if (layer == "questions") {
|
|
||||||
layer = "./BuiltinQuestions"
|
|
||||||
} else {
|
|
||||||
layer = "./" + layer
|
|
||||||
}
|
|
||||||
origDef = `<br/> _(Original in [${tr._definedIn[0]}](${layer}.md#${id}))_`
|
|
||||||
}
|
|
||||||
const q = tr.question?.Subs(this.baseTags)?.txt?.trim()
|
|
||||||
let r = tr.render?.txt
|
|
||||||
if (r && r !== "") {
|
|
||||||
r = `_${r}_`
|
|
||||||
}
|
|
||||||
let options: string = undefined
|
|
||||||
if (tr.mappings?.length > 0) {
|
|
||||||
options = `${tr.mappings.length} options`
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
`[${tr.id}](#${tr.id}) ${origDef}`,
|
|
||||||
Lists.noNull([q, r, options]).join("<br/>"),
|
|
||||||
tr.labels.join(", "),
|
|
||||||
key,
|
|
||||||
]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public generateDocumentation({
|
public generateDocumentation({
|
||||||
usedInThemes = [],
|
usedInThemes = [],
|
||||||
layerIsNeededBy,
|
layerIsNeededBy,
|
||||||
|
@ -456,234 +410,367 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
reusedTagRenderings?: Map<string, { layer: string }[]>
|
reusedTagRenderings?: Map<string, { layer: string }[]>
|
||||||
lang?: string
|
lang?: string
|
||||||
}): string {
|
}): string {
|
||||||
const extraProps: string[] = []
|
|
||||||
extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
|
|
||||||
|
|
||||||
if (canBeIncluded) {
|
const paragraphs: (string | Translation)[] = [
|
||||||
if (addedByDefault) {
|
"# " + this.id,
|
||||||
extraProps.push(
|
this.description,
|
||||||
"**This layer is included automatically in every theme. This layer might contain no points**"
|
]
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.shownByDefault === false) {
|
|
||||||
extraProps.push(
|
|
||||||
"This layer is not visible by default and must be enabled in the filter by the user. "
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.title === undefined) {
|
|
||||||
extraProps.push(
|
|
||||||
"Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.name === undefined && this.shownByDefault === false) {
|
|
||||||
extraProps.push(
|
|
||||||
"This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.name === undefined) {
|
|
||||||
extraProps.push(
|
|
||||||
"Not visible in the layer selection by default. If you want to make this layer toggable, override `name`"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.mapRendering.length === 0) {
|
|
||||||
extraProps.push(
|
|
||||||
"Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.source?.geojsonSource !== undefined) {
|
function add(item: Translation | string | Record<string, string> & { en: string, nl: string }) {
|
||||||
extraProps.push(
|
if (item instanceof Translation || typeof item === "string") {
|
||||||
[
|
paragraphs.push(item)
|
||||||
"<img src='../warning.svg' height='1rem'/>",
|
} else if (item["en"] !== undefined) {
|
||||||
"This layer is loaded from an external source, namely ",
|
paragraphs.push(new Translation(item))
|
||||||
"`" + this.source.geojsonSource + "`",
|
|
||||||
].join("\n\n")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (this._basedOn) {
|
||||||
|
add(new TypedTranslation<{ basedOn }>(
|
||||||
|
{
|
||||||
|
en: `This layer is based on [{basedOn}](../Layers/{basedOn}.md)`,
|
||||||
|
nl: `Deze laag is gebaseerd op [{basedOn}](../Layers/{basedOn}.md)`,
|
||||||
|
},
|
||||||
|
).Subs({ basedOn: this._basedOn }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Various extra properties, added in a list below the description
|
||||||
|
{
|
||||||
|
const extraProps: Translation[] = []
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
"This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data."
|
new TypedTranslation<{ minzoom }>({
|
||||||
)
|
en: "This layer is shown at zoomlevel **{minzoom}** and higher",
|
||||||
}
|
nl: "Deze laag wordt getoond vanaf zoomlevel **{minzoom}**",
|
||||||
|
}).Subs(this))
|
||||||
|
|
||||||
let usingLayer: string[] = []
|
if (canBeIncluded) {
|
||||||
if (!addedByDefault) {
|
if (addedByDefault) {
|
||||||
if (usedInThemes?.length > 0) {
|
extraProps.push(
|
||||||
usingLayer = [
|
new Translation(
|
||||||
"## Themes using this layer",
|
{
|
||||||
MarkdownUtils.list(
|
en:
|
||||||
(usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`)
|
"**This layer is included automatically in every theme. This layer might contain no points**",
|
||||||
),
|
nl: "**Deze laag wordt automatisch toegevoegd aan ieder kaartthema. Deze laag bevat mogelijks geen punten",
|
||||||
]
|
},
|
||||||
} else if (this.source !== null) {
|
),
|
||||||
usingLayer = ["No themes use this layer"]
|
)
|
||||||
}
|
}
|
||||||
}
|
if (this.shownByDefault === false) {
|
||||||
|
extraProps.push(
|
||||||
|
new Translation({
|
||||||
|
en: "This layer is not visible by default and must be enabled in the filter by the user. ",
|
||||||
|
nl: "Deze laag is standaard niet zichtbaar en moet door de gebruiker aangezet worden in het 'filter'-menu ",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (this.title === undefined) {
|
||||||
|
extraProps.push(
|
||||||
|
new Translation({
|
||||||
|
en:
|
||||||
|
"This layers doesn't have a title set. As such, elements will appear on the map but cannot be clicked. If you import this layer in your theme, override `title` to make sure elements can be opened."
|
||||||
|
,
|
||||||
|
nl: "Deze laag heeft geen 'title' ingesteld. Elementen zullen op de kaart tonen, maar kunnen niet opengeklikt worden door de gebruiker. Hergebruik je deze laag? Voeg een `title` toe zodat element wel opengeklikt kunnen worden.",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (this.name === undefined && this.shownByDefault === false) {
|
||||||
|
if (this.shownByDefault === false) {
|
||||||
|
|
||||||
for (const dep of dependencies) {
|
extraProps.push(
|
||||||
extraProps.push(
|
new TypedTranslation<{ id }>({
|
||||||
[
|
en: "This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by adding the URL-parameter `layer-{id}=true`",
|
||||||
"This layer will automatically load ",
|
nl: "Deze laag is standaard niet zichtbaar én heeft geen `name`. Dit betekent dat de zichtbaarheid niet door de gebruiker ingesteld kan worden, wat in een volledig verborgen laag resulteert. Dit kan nuttig zij, bv. om metatags uit te rekenen. Wil je deze laag toch tonen (bv om te debuggen?)? Voeg de URL-parameter `layer-{id}=true` toe",
|
||||||
`[${dep.neededLayer}](./${dep.neededLayer}.md)`,
|
}).Subs(this),
|
||||||
" into the layout as it depends on it: ",
|
|
||||||
dep.reason,
|
|
||||||
"(" + dep.context + ")",
|
|
||||||
].join(" ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let presets: string[] = []
|
|
||||||
if (this.presets.length > 0) {
|
|
||||||
presets = [
|
|
||||||
"## Presets",
|
|
||||||
"The following options to create new points are included:",
|
|
||||||
MarkdownUtils.list(
|
|
||||||
this.presets.map((preset) => {
|
|
||||||
let snaps = ""
|
|
||||||
if (preset.preciseInput?.snapToLayers) {
|
|
||||||
snaps =
|
|
||||||
" (snaps to layers " +
|
|
||||||
preset.preciseInput.snapToLayers
|
|
||||||
.map((id) => `\`${id}\``)
|
|
||||||
.join(", ") +
|
|
||||||
")"
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
"**" +
|
|
||||||
preset.title.textFor(lang) +
|
|
||||||
"** which has the following tags:" +
|
|
||||||
new And(preset.tags).asHumanString(true) +
|
|
||||||
snaps
|
|
||||||
)
|
)
|
||||||
})
|
} else {
|
||||||
),
|
extraProps.push(
|
||||||
]
|
new Translation({
|
||||||
}
|
en: "Not visible in the layer selection by default. If you want to make this layer toggable, override `name`",
|
||||||
|
nl: "Deze laag kan niet onzichtbaar gemaakt worden in het filtermenu. Overschrijf `name` indien je dit wel wilt.",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.mapRendering.length === 0) {
|
||||||
|
extraProps.push(
|
||||||
|
new Translation({
|
||||||
|
en: "Not rendered on the map by default.",
|
||||||
|
nl: "Deze laag wordt standaard niet weergegeven op de kaart.",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
for (const revDep of Lists.dedup(layerIsNeededBy?.get(this.id) ?? [])) {
|
} else {
|
||||||
extraProps.push(
|
extraProps.push(
|
||||||
["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join(
|
new Translation({
|
||||||
" "
|
en: "This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.",
|
||||||
|
nl: "Deze laag kan **niet** aan een kaartthema toegevoegd worden. Deze laag wordt enkel en alleen gebruikt om een [speciale rendering](SpecialRenderings.md) te ondersteunen. ",
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (const dep of dependencies) {
|
||||||
|
extraProps.push(
|
||||||
|
new TypedTranslation<{ neededLayer, reason, context? }>({
|
||||||
|
en: "This layer will automatically load [`{neededLayer}`](./{neededLayer}.md) into the theme as it depends on it: {reason} ({context??no context given})",
|
||||||
|
nl: "Deze laag laadt automatisch de laag [`{neededLayer}`](./{neededLayer}.md) in het kaartthema want deze laag steunt hierop: {reason} ({context??geen context gekend})",
|
||||||
|
|
||||||
|
}).Subs(dep),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for (const revDep of Lists.dedup(layerIsNeededBy?.get(this.id) ?? [])) {
|
||||||
|
extraProps.push(
|
||||||
|
new TypedTranslation<{ revDep }>({
|
||||||
|
en: "This layer is needed as dependency for layer [`{revDep}`](#{revDep})",
|
||||||
|
}).Subs({ revDep }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
add(MarkdownUtils.list(extraProps.map(tr => tr.textFor(lang))))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableRows: string[][] = Lists.noNull(
|
// Overview of what themes use this layer
|
||||||
this.tagRenderings
|
{
|
||||||
|
if (!addedByDefault) {
|
||||||
|
if (usedInThemes?.length > 0) {
|
||||||
|
add(
|
||||||
|
MarkdownUtils.title(3,
|
||||||
|
new Translation({
|
||||||
|
en: "Themes using this layer",
|
||||||
|
nl: "Kaartthemas die deze laag gebruiken",
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
MarkdownUtils.list(
|
||||||
|
(usedInThemes ?? []).map((id) => `[\`${id}\`](https://mapcomplete.org/${id})`),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else if (this.source !== null) {
|
||||||
|
add({
|
||||||
|
en: "No themes use this layer",
|
||||||
|
nl: "Geen enkel kaartthema gebruikt deze laag",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data source information
|
||||||
|
{
|
||||||
|
paragraphs.push(MarkdownUtils.title(3, new Translation({ en: "Data source", nl: "Databron" })))
|
||||||
|
|
||||||
|
if (this.source?.geojsonSource) {
|
||||||
|
paragraphs.push(
|
||||||
|
new TypedTranslation<{ geojsonSource? }>({
|
||||||
|
en: "⚠️ This layer is loaded from an external source, namely `{geojsonSource}`",
|
||||||
|
nl: "⚠️ Deze laag wordt van een externe bron geladen, namelijk `{geojsonSource}`",
|
||||||
|
},
|
||||||
|
).Subs(this.source),
|
||||||
|
)
|
||||||
|
} else if (!this.source) {
|
||||||
|
add({
|
||||||
|
en: "This is a special layer, probably a library layer or support layer for MapComplete",
|
||||||
|
nl: "Dit is een speciale laag, waarschijnlijk een bibliotheeklaag of een ondersteunende laag",
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const neededTags = <TagsFilter>this.source.osmTags.optimize()
|
||||||
|
if (neededTags["and"]) {
|
||||||
|
add(
|
||||||
|
{
|
||||||
|
en: "Elements on this layer match **all** of the following expressions:",
|
||||||
|
nl: "Elementen in deze laag hebben **alle** van de volgende kenmerken:",
|
||||||
|
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const parts = neededTags["and"]
|
||||||
|
add(
|
||||||
|
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"),
|
||||||
|
)
|
||||||
|
} else if (neededTags["or"]) {
|
||||||
|
const parts = neededTags["or"]
|
||||||
|
add(
|
||||||
|
{
|
||||||
|
en: "Elements on this layer match **any** of the following expressions:",
|
||||||
|
nl: "Elementen in deze laag hebben **minstens één** van de volgende kenmerken:",
|
||||||
|
|
||||||
|
})
|
||||||
|
add(
|
||||||
|
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
add({
|
||||||
|
en: "Elements on this layer match the following expression:",
|
||||||
|
nl: "Elementen in deze laag hebben de volgende kenmerken:",
|
||||||
|
|
||||||
|
})
|
||||||
|
add(
|
||||||
|
neededTags.asHumanString(true, false, {}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const link = Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize())
|
||||||
|
.replaceAll("(", "%28")
|
||||||
|
.replaceAll(")", "%29")
|
||||||
|
const txt = new TypedTranslation<{ link }>(
|
||||||
|
{
|
||||||
|
en: "Execute on overpass-turbo.eu",
|
||||||
|
nl: "Uitvoeren op overpass-turbo.eu",
|
||||||
|
},
|
||||||
|
).textFor(lang)
|
||||||
|
paragraphs.push(`[🗺️ ${txt}](${link})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Presets
|
||||||
|
{
|
||||||
|
if (this.presets.length > 0) {
|
||||||
|
add(new Translation({
|
||||||
|
en: "## Presets",
|
||||||
|
nl: "## Nieuwe punten toevoegen",
|
||||||
|
}))
|
||||||
|
|
||||||
|
add(new Translation({
|
||||||
|
en: "The following options to create new points are included:",
|
||||||
|
nl: "De volgende opties bestaan om nieuwe punten toe te voegen:",
|
||||||
|
}))
|
||||||
|
|
||||||
|
const hTags = new Translation({
|
||||||
|
en: "Used tags",
|
||||||
|
nl: "Gebruikte tags",
|
||||||
|
})
|
||||||
|
const hTitle = new Translation({
|
||||||
|
en: "Title",
|
||||||
|
nl: "Titel",
|
||||||
|
})
|
||||||
|
const hSnaps = new Translation({
|
||||||
|
en: "Snaps to layers",
|
||||||
|
nl: "Klikt vast aan lagen",
|
||||||
|
})
|
||||||
|
const hDescription = new Translation({
|
||||||
|
en: "Description",
|
||||||
|
nl: "Beschrijving",
|
||||||
|
})
|
||||||
|
|
||||||
|
add(MarkdownUtils.table(
|
||||||
|
[
|
||||||
|
hTags,
|
||||||
|
hTitle,
|
||||||
|
hSnaps,
|
||||||
|
hDescription,
|
||||||
|
].map(t => t.textFor(lang)),
|
||||||
|
this.presets.map(preset => {
|
||||||
|
let category = preset.title.textFor(lang)
|
||||||
|
category = "**" + category + "**"
|
||||||
|
return [
|
||||||
|
new And(preset.tags).asHumanString(true),
|
||||||
|
Translations.t.general.add.addNew.Subs({ category }).textFor(lang),
|
||||||
|
preset.preciseInput?.snapToLayers
|
||||||
|
?.map((id) => `[\`${id}\`](./Layers/${id}.md)`)
|
||||||
|
?.join(", "),
|
||||||
|
preset.description?.textFor(lang),
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
{ dropEmptyColumns: true },
|
||||||
|
))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
add({
|
||||||
|
en: "This layer does not allowing adding new points",
|
||||||
|
nl: "Deze laag laat niet toe om nieuwe punten toe te voegen",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supported attributes table
|
||||||
|
{
|
||||||
|
const keyValues = (Lists.noNull(this.tagRenderings
|
||||||
.map((tr) => tr.FreeformValues())
|
.map((tr) => tr.FreeformValues())
|
||||||
.filter((values) => values !== undefined)
|
.filter((values) => values !== undefined)
|
||||||
.filter((values) => values.key !== "id")
|
.filter((values) => values.key !== "id")))
|
||||||
.map((values) => {
|
keyValues.sort((a, b) => a.key < b.key ? -1 : 1)
|
||||||
|
if (keyValues.length > 0) {
|
||||||
|
add(MarkdownUtils.title(2, new Translation({
|
||||||
|
en: "Attribute overview",
|
||||||
|
nl: "Attributenoverzicht",
|
||||||
|
})))
|
||||||
|
add({
|
||||||
|
en: "This table gives an overview of most OpenStreetMap [keys](https://wiki.openstreetmap.org/wiki/Tags) that this layer shows and/or edits",
|
||||||
|
nl: "Deze tabel geeft een overzicht van de meeste OpenStreetMap [sleutels](https://wiki.openstreetmap.org/wiki/Tags) die deze laag toont en/of aanpast",
|
||||||
|
})
|
||||||
|
const tableRows: string[][] = keyValues.map((values) => {
|
||||||
const embedded: string[] = values.values?.map((v) =>
|
const embedded: string[] = values.values?.map((v) =>
|
||||||
OsmWiki.constructLinkMd(values.key, v)
|
OsmWiki.constructLinkMd(values.key, v),
|
||||||
) ?? ["_no preset options defined, or no values in them_"]
|
) ?? ["_no preset options defined, or no values in them_"]
|
||||||
const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent(
|
const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent(
|
||||||
values.key
|
values.key,
|
||||||
)}/`
|
)}/`
|
||||||
const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values`
|
const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values`
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
`<a target="_blank" href='${tagInfo}'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a>`,
|
`[🔎](${tagInfo})`,
|
||||||
`<a target="_blank" href='${statistics}'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a>`,
|
`[📈](${statistics})`,
|
||||||
OsmWiki.constructLinkMd(values.key),
|
OsmWiki.constructLinkMd(values.key),
|
||||||
].join(" "),
|
].join(" "),
|
||||||
values.type === undefined
|
values.type === undefined
|
||||||
? "Multiple choice"
|
? ""
|
||||||
: `[${values.type}](../SpecialInputElements.md#${values.type})`,
|
: `[${values.type}](../SpecialInputElements.md#${values.type})`,
|
||||||
embedded.join(" "),
|
embedded.join(" "),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
let quickOverview: string[] = []
|
const header: { en: string, nl: string }[] = [
|
||||||
if (tableRows.length > 0) {
|
{ en: "Key", nl: "OSM-sleutel" }, {
|
||||||
quickOverview = [
|
en: "Type (for freeform input)",
|
||||||
"**Warning:**: this quick overview is incomplete",
|
nl: "Inputtype",
|
||||||
|
}, { en: "Predefined, supported options", nl: "Voorgedefinieerde, ondersteunde opties" },
|
||||||
|
]
|
||||||
|
|
||||||
|
add(
|
||||||
MarkdownUtils.table(
|
MarkdownUtils.table(
|
||||||
["attribute", "type", "values which are supported by this layer"],
|
header.map(tr => new Translation(tr).textFor(lang)),
|
||||||
tableRows
|
tableRows
|
||||||
),
|
)
|
||||||
]
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let overpassLink: string = undefined
|
}
|
||||||
if (this.source !== undefined) {
|
|
||||||
try {
|
|
||||||
overpassLink =
|
// Elements in the popup
|
||||||
"[Execute on overpass](" +
|
{
|
||||||
Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize())
|
add(MarkdownUtils.title(2, new Translation({
|
||||||
.replaceAll("(", "%28")
|
en: "Overview of questions (and other elements) in the popup",
|
||||||
.replaceAll(")", "%29") +
|
nl: "Overzicht van vragen (en andere elementen) in de popup",
|
||||||
")"
|
})))
|
||||||
} catch (e) {
|
for (const tagRendering of this.tagRenderings) {
|
||||||
console.error("Could not generate overpasslink for " + this.id)
|
if (tagRendering.labels.indexOf("ignore_docs") >= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
add(tagRendering.generateDocumentation(
|
||||||
|
this.id,
|
||||||
|
lang,
|
||||||
|
reusedTagRenderings?.get(tagRendering.id)?.map((l) => l.layer),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterDocs: string[] = []
|
// Filters
|
||||||
if (this.filters.length > 0) {
|
if (this.filters.length > 0) {
|
||||||
filterDocs.push("## Filters")
|
add(MarkdownUtils.title(2, new Translation({
|
||||||
filterDocs.push(...this.filters.map((filter) => filter.GenerateDocs()))
|
en: "Filters",
|
||||||
}
|
nl: "Filters"
|
||||||
|
})))
|
||||||
const tagsDescription: string[] = []
|
for (const filter of this.filters) {
|
||||||
if (this.source !== null) {
|
add(filter.generateDocs({lang}))
|
||||||
tagsDescription.push("## Basic tags for this layer")
|
|
||||||
|
|
||||||
const neededTags = <TagsFilter>this.source.osmTags.optimize()
|
|
||||||
if (neededTags["and"]) {
|
|
||||||
const parts = neededTags["and"]
|
|
||||||
tagsDescription.push(
|
|
||||||
"Elements must match **all** of the following expressions:",
|
|
||||||
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n")
|
|
||||||
)
|
|
||||||
} else if (neededTags["or"]) {
|
|
||||||
const parts = neededTags["or"]
|
|
||||||
tagsDescription.push(
|
|
||||||
"Elements must match **any** of the following expressions:",
|
|
||||||
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n")
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
tagsDescription.push(
|
|
||||||
"Elements must match the expression **" +
|
|
||||||
neededTags.asHumanString(true, false, {}) +
|
|
||||||
"**"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tagsDescription.push(overpassLink)
|
|
||||||
} else {
|
|
||||||
tagsDescription.push("This is a special layer - data is not sourced from OpenStreetMap")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
return (Lists.noEmpty(Lists.noNull(paragraphs).map(p => typeof p === "string" ? p : p.textFor(lang)))).join("\n\n")
|
||||||
"# " + this.id + "\n",
|
|
||||||
this._basedOn
|
|
||||||
? `This layer is based on [${this._basedOn}](../Layers/${this._basedOn}.md)`
|
|
||||||
: "",
|
|
||||||
this.description.textFor(lang),
|
|
||||||
"\n",
|
|
||||||
].join("\n\n"),
|
|
||||||
MarkdownUtils.list(extraProps),
|
|
||||||
...usingLayer,
|
|
||||||
...presets,
|
|
||||||
...tagsDescription,
|
|
||||||
"## Supported attributes",
|
|
||||||
...quickOverview,
|
|
||||||
"## Featureview elements and TagRenderings",
|
|
||||||
this.generateDocumentationQuickTable(),
|
|
||||||
...this.tagRenderings
|
|
||||||
.filter((tr) => tr.labels.indexOf("ignore_docs") < 0)
|
|
||||||
.map((tr) =>
|
|
||||||
tr.generateDocumentation(
|
|
||||||
this.id,
|
|
||||||
lang,
|
|
||||||
reusedTagRenderings?.get(tr.id)?.map((l) => l.layer)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
...filterDocs,
|
|
||||||
].join("\n\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomCodeSnippets(): string[] {
|
public CustomCodeSnippets(): string[] {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { Unit } from "../Unit"
|
||||||
import { Lists } from "../../Utils/Lists"
|
import { Lists } from "../../Utils/Lists"
|
||||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||||
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag"
|
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag"
|
||||||
|
import { Strings } from "../../Utils/Strings"
|
||||||
|
|
||||||
export interface Mapping {
|
export interface Mapping {
|
||||||
readonly if: UploadableTag
|
readonly if: UploadableTag
|
||||||
|
@ -753,6 +754,8 @@ export default class TagRenderingConfig {
|
||||||
* Given a value for the freeform key and an overview of the selected mappings, construct the correct tagsFilter to apply.
|
* Given a value for the freeform key and an overview of the selected mappings, construct the correct tagsFilter to apply.
|
||||||
* Result should be interpreted as "and"
|
* Result should be interpreted as "and"
|
||||||
*
|
*
|
||||||
|
* If this matches 'invalidValues', will return undefined
|
||||||
|
*
|
||||||
* const config = new TagRenderingConfig({"id":"bookcase-booktypes","render":{"en":"This place mostly serves {books}" },
|
* const config = new TagRenderingConfig({"id":"bookcase-booktypes","render":{"en":"This place mostly serves {books}" },
|
||||||
* "question":{"en":"What kind of books can be found in this public bookcase?"},
|
* "question":{"en":"What kind of books can be found in this public bookcase?"},
|
||||||
* "freeform":{"key":"books","addExtraTags":["fixme=Freeform tag `books` used, to be doublechecked"],
|
* "freeform":{"key":"books","addExtraTags":["fixme=Freeform tag `books` used, to be doublechecked"],
|
||||||
|
@ -959,17 +962,12 @@ export default class TagRenderingConfig {
|
||||||
this.description]
|
this.description]
|
||||||
|
|
||||||
if (this.question === undefined) {
|
if (this.question === undefined) {
|
||||||
paragraphs.push(new Translation({
|
paragraphs.push(MarkdownUtils.quote(new Translation({
|
||||||
en: "_This tagrendering has no question and is thus read-only_",
|
en: "_This tagrendering has no question and is thus read-only_",
|
||||||
nl: "_Deze tagRendering heeft geen vraag en wordt dus enkel weergegeven_",
|
nl: "_Deze tagRendering heeft geen vraag en wordt dus enkel weergegeven_",
|
||||||
}))
|
})))
|
||||||
} else {
|
} else {
|
||||||
paragraphs.push(new TypedTranslation<{ question }>(
|
paragraphs.push(MarkdownUtils.quote(this.question))
|
||||||
{
|
|
||||||
en: "If no attribute matches, the question *{question}* will be asked",
|
|
||||||
nl: "Indien geen attribuut overeenkomt, wordt *{question}* gevraagd",
|
|
||||||
},
|
|
||||||
).Subs({ question: this.question.textFor(lang) }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.render) {
|
if (this.render) {
|
||||||
|
@ -1017,10 +1015,13 @@ export default class TagRenderingConfig {
|
||||||
this.mappings.map((m) => {
|
this.mappings.map((m) => {
|
||||||
let icon = ""
|
let icon = ""
|
||||||
if (m.icon?.indexOf(";") < 0) {
|
if (m.icon?.indexOf(";") < 0) {
|
||||||
icon =
|
if (Strings.isEmoji(m.icon)) {
|
||||||
"<img width='38px' height='38px' src='https://dev.mapcomplete.org/" +
|
icon = m.icon
|
||||||
|
} else {
|
||||||
|
icon = "<img width='38px' height='38px' src='https://dev.mapcomplete.org/" +
|
||||||
m.icon +
|
m.icon +
|
||||||
"'>"
|
"'>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const msgs: Translation[] = [
|
const msgs: Translation[] = [
|
||||||
new TypedTranslation<{ icon, then, cond }>({
|
new TypedTranslation<{ icon, then, cond }>({
|
||||||
|
@ -1064,6 +1065,18 @@ export default class TagRenderingConfig {
|
||||||
).Subs({ conditionAsLink }))
|
).Subs({ conditionAsLink }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.invalidValues) {
|
||||||
|
paragraphs.push(
|
||||||
|
new Translation({
|
||||||
|
en: "This tagRendering has some values that are not valid and cannot be entered. The following values are _not_ accepted:",
|
||||||
|
nl: "Deze tagRendering heeft waardes die als ongeldig beschouwd worden en _niet_ ingevoerd kunnen worden:",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
paragraphs.push(
|
||||||
|
"❌ " + this.invalidValues.asHumanString(true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.labels?.length > 0) {
|
if (this.labels?.length > 0) {
|
||||||
paragraphs.push(new Translation({
|
paragraphs.push(new Translation({
|
||||||
en: "This tagRendering has the following labels:",
|
en: "This tagRendering has the following labels:",
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { Translation } from "../UI/i18n/Translation"
|
||||||
|
import { Utils } from "../Utils"
|
||||||
|
|
||||||
export default class MarkdownUtils {
|
export default class MarkdownUtils {
|
||||||
public static table(
|
public static table(
|
||||||
header: string[],
|
header: string[],
|
||||||
|
@ -41,4 +44,13 @@ export default class MarkdownUtils {
|
||||||
}
|
}
|
||||||
return "\n\n" + strings.map((item) => " - " + item).join("\n") + "\n\n"
|
return "\n\n" + strings.map((item) => " - " + item).join("\n") + "\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static quote(translation: Translation) {
|
||||||
|
return translation.OnEveryLanguage(s => "> "+s)
|
||||||
|
}
|
||||||
|
|
||||||
|
static title(number: number, translation: Translation) {
|
||||||
|
const t = Utils.times(() => "#", number)+" "
|
||||||
|
return translation.OnEveryLanguage(l => t+ l)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue